Own Web platform services directly

This commit is contained in:
2026-06-17 16:02:26 +02:00
parent d80289665d
commit b5d3bc131d
9 changed files with 461 additions and 19 deletions

View File

@@ -18,6 +18,12 @@ agent or engineer to remove them without reconstructing context from chat.
## Reductions
- 2026-06-17: `DEBT-0017`/`DEBT-0050`/`DEBT-0053`/`DEBT-0057` were narrowed
again. `webgl/src/main.cpp` now binds a concrete
`src/platform_web/web_platform_services.*` `PlatformServices` instance
directly, so the live WebGL entrypoint no longer depends on
`src/platform_legacy/legacy_platform_services.*` for render-context,
prepared-file, picker, app-close, or default-canvas behavior.
- 2026-06-17: `DEBT-0003` was narrowed again.
`src/platform_windows/windows_runtime_shell.cpp` now binds a local
`MainWindowSession` object for the live Win32 session, and the window handle,

View File

@@ -70,6 +70,22 @@ What is already real:
- `pp_app_core`
Latest slice:
- `src/platform_web/web_platform_services.*` now owns the concrete WebGL
`PlatformServices` implementation as well as the narrower
`WebPlatformServices` helper surface.
- `webgl/src/main.cpp` now binds that owned concrete Web `PlatformServices`
instance directly instead of constructing the cross-platform
`platform_legacy` adapter around Web-specific injections.
- The touched Web render-context acquire/present, app-close dispatch,
file/image picker routing, prepared-file handoff, default render-target
binding, and default canvas / prepared-file policy path now route through
`src/platform_web/web_platform_services.*` instead of the fallback adapter.
- `pp_platform_api_tests` now compiles and exercises the concrete Web platform
service surface on the Windows host build, so the Web platform target is no
longer only indirectly covered through the narrower helper interface.
- `src/platform_legacy/legacy_platform_services.*` no longer has a live
platform-entrypoint consumer; it remains only as generic fallback scaffolding
still linked into the retained app target graph.
- `src/platform_web/web_platform_services.*` now owns the concrete Web
`PlatformServices` implementation and `webgl/src/main.cpp` now binds that
owned service directly at the WebGL entrypoint.

View File

@@ -139,6 +139,16 @@ Current slice:
document-service provider/configuration surface or the touched Apple method
branches, so the retained fallback adapter is narrower and now focused on the
Android/Linux/Web path.
- `src/platform_web/web_platform_services.*` now owns the concrete WebGL
`PlatformServices` surface in addition to the narrower helper interface used
by `pp_platform_api` tests.
- `webgl/src/main.cpp` now binds that owned Web `PlatformServices` instance
directly instead of constructing the retained `platform_legacy` adapter.
- The touched Web render-context acquire/present, app-close dispatch,
file/image picker routing, prepared-file handoff, and default render-target
binding now route through `src/platform_web/web_platform_services.*`.
- `src/platform_legacy/legacy_platform_services.*` no longer has a live
platform-entrypoint consumer and is now down to generic fallback scaffolding.
- The previous Web narrowing step had already moved WebGL
publish/flush/default-canvas/save-prepared-file behavior off the old
`try_*legacy_web*` fallback path before this concrete Web service cut.

View File

@@ -0,0 +1,29 @@
#pragma once
#include <string>
#include <string_view>
#include "legacy_ui_gl_dispatch.h"
#include "platform_api/platform_services.h"
#include "renderer_gl/opengl_capabilities.h"
namespace pp::platform::legacy {
inline void bind_default_opengl_render_target()
{
pp::legacy::ui_gl::bind_opengl_framebuffer(
pp::renderer::gl::framebuffer_target(),
pp::renderer::gl::default_framebuffer_id());
}
inline void complete_prepared_file_without_save(
std::string_view path,
const pp::platform::PreparedFileCallback& callback)
{
if (!callback)
return;
callback(std::string(path), false);
}
}

View File

@@ -3,11 +3,10 @@
#include <memory>
#include "legacy_ui_gl_dispatch.h"
#include "log.h"
#include "platform_legacy/legacy_platform_fallback_behavior.h"
#include "platform_api/network_tls_policy.h"
#include "platform_api/platform_policy.h"
#include "renderer_gl/opengl_capabilities.h"
namespace {
@@ -77,9 +76,7 @@ public:
void bind_default_render_target() override
{
pp::legacy::ui_gl::bind_opengl_framebuffer(
pp::renderer::gl::framebuffer_target(),
pp::renderer::gl::default_framebuffer_id());
pp::platform::legacy::bind_default_opengl_render_target();
}
void bind_main_render_target() override
@@ -298,10 +295,8 @@ public:
std::string_view suggested_name,
pp::platform::PreparedFileCallback callback) override
{
const std::string value(path);
const std::string name(suggested_name);
(void)name;
callback(value, false);
(void)suggested_name;
pp::platform::legacy::complete_prepared_file_without_save(path, callback);
}
};

View File

@@ -1,12 +1,18 @@
#include "pch.h"
#include "platform_web/web_platform_services.h"
#include "legacy_ui_gl_dispatch.h"
#include "platform_api/network_tls_policy.h"
#include "platform_api/platform_policy.h"
#include "renderer_gl/opengl_capabilities.h"
#include <memory>
#include <string>
#include <utility>
#if defined(__WEB__)
void webgl_pick_file(std::function<void(std::string)> callback);
void webgl_pick_file_save(
const std::string& path,
const std::string& name,
@@ -15,6 +21,115 @@ void webgl_sync();
#endif
namespace pp::platform::web {
namespace {
constexpr auto kPlatformFamily = pp::platform::PlatformFamily::webgl;
}
WebPlatformServices::WebPlatformServices(WebPlatformServicesConfig config)
: config_(std::move(config))
{
}
PlatformStoragePaths WebPlatformServices::prepare_storage_paths()
{
return {};
}
void WebPlatformServices::log_stacktrace()
{
}
void WebPlatformServices::trigger_crash_test()
{
}
std::string WebPlatformServices::clipboard_text()
{
return {};
}
bool WebPlatformServices::set_clipboard_text(std::string_view text)
{
const std::string value(text);
(void)value;
return false;
}
void WebPlatformServices::set_cursor_visible(bool visible)
{
(void)visible;
}
void WebPlatformServices::set_virtual_keyboard_visible(bool visible)
{
(void)visible;
}
void WebPlatformServices::attach_ui_thread()
{
}
void WebPlatformServices::detach_ui_thread()
{
}
void WebPlatformServices::acquire_render_context()
{
if (config_.shell.acquire_render_context)
config_.shell.acquire_render_context();
}
void WebPlatformServices::release_render_context()
{
}
void WebPlatformServices::present_render_context()
{
if (config_.shell.present_render_context)
config_.shell.present_render_context();
}
void WebPlatformServices::bind_default_render_target()
{
#if defined(__WEB__)
pp::legacy::ui_gl::bind_opengl_framebuffer(
pp::renderer::gl::framebuffer_target(),
pp::renderer::gl::default_framebuffer_id());
#endif
}
void WebPlatformServices::bind_main_render_target()
{
bind_default_render_target();
}
void WebPlatformServices::apply_render_platform_hints()
{
}
void WebPlatformServices::install_render_debug_callback()
{
}
void WebPlatformServices::begin_render_capture_frame()
{
}
void WebPlatformServices::end_render_capture_frame()
{
}
bool WebPlatformServices::deletes_recorded_files_on_clear()
{
return pp::platform::platform_deletes_recorded_files_on_clear(kPlatformFamily);
}
void WebPlatformServices::clear_recorded_files(std::string_view recording_path)
{
(void)recording_path;
}
void WebPlatformServices::publish_exported_image(std::string_view path)
{
@@ -28,9 +143,169 @@ void WebPlatformServices::flush_persistent_storage()
#endif
}
[[nodiscard]] int WebPlatformServices::default_canvas_resolution()
std::vector<std::string> WebPlatformServices::document_browse_roots(
std::string_view work_path,
std::string_view data_path)
{
return pp::platform::platform_default_canvas_resolution(pp::platform::PlatformFamily::webgl);
return pp::platform::platform_document_browse_roots(
kPlatformFamily,
work_path,
data_path);
}
void WebPlatformServices::save_ui_state()
{
if (!pp::platform::platform_saves_native_ui_state(kPlatformFamily))
return;
}
bool WebPlatformServices::enables_live_asset_reloading()
{
return pp::platform::platform_enables_live_asset_reloading(kPlatformFamily);
}
void WebPlatformServices::update_platform_frame(float delta_time_seconds)
{
(void)delta_time_seconds;
}
void WebPlatformServices::report_rendered_frames(int frames)
{
(void)frames;
}
void WebPlatformServices::display_file(std::string_view path)
{
(void)path;
}
void WebPlatformServices::share_file(std::string_view path)
{
(void)path;
}
void WebPlatformServices::request_app_close()
{
if (config_.shell.request_app_close)
config_.shell.request_app_close();
}
bool WebPlatformServices::start_vr_mode()
{
return false;
}
void WebPlatformServices::stop_vr_mode()
{
}
void WebPlatformServices::pick_image(PickedPathCallback callback)
{
#if defined(__WEB__)
webgl_pick_file(std::move(callback));
#else
(void)callback;
#endif
}
void WebPlatformServices::pick_file(
std::vector<std::string> file_types,
PickedPathCallback callback)
{
(void)file_types;
#if defined(__WEB__)
webgl_pick_file(std::move(callback));
#else
(void)callback;
#endif
}
void WebPlatformServices::pick_save_file(
std::vector<std::string> file_types,
PickedPathCallback callback)
{
(void)file_types;
(void)callback;
}
void WebPlatformServices::pick_directory(PickedPathCallback callback)
{
(void)callback;
}
bool WebPlatformServices::supports_working_directory_picker()
{
return pp::platform::platform_supports_working_directory_picker(kPlatformFamily);
}
std::string WebPlatformServices::format_working_directory_path(std::string_view path)
{
return std::string(path);
}
bool WebPlatformServices::uses_prepared_file_writes()
{
return pp::platform::platform_uses_prepared_file_writes(kPlatformFamily);
}
bool WebPlatformServices::uses_work_directory_document_export_collections()
{
return pp::platform::platform_uses_work_directory_document_export_collections(kPlatformFamily);
}
bool WebPlatformServices::disables_network_tls_verification()
{
return pp::platform::default_disables_network_tls_verification();
}
bool WebPlatformServices::uses_ppbr_export_data_directory_override()
{
return pp::platform::platform_uses_ppbr_export_data_directory_override(kPlatformFamily);
}
bool WebPlatformServices::supports_sonarpen()
{
return pp::platform::platform_supports_sonarpen(kPlatformFamily);
}
void WebPlatformServices::start_sonarpen()
{
}
int WebPlatformServices::default_canvas_resolution()
{
return pp::platform::platform_default_canvas_resolution(kPlatformFamily);
}
bool WebPlatformServices::draws_canvas_tip_for_pointer(
bool is_mouse,
bool is_stylus,
bool is_left_button_release)
{
return pp::platform::platform_draws_canvas_tip_for_pointer(
kPlatformFamily,
is_mouse,
is_stylus,
is_left_button_release);
}
float WebPlatformServices::adjust_canvas_input_pressure(float pressure)
{
return pressure;
}
PreparedFileTarget WebPlatformServices::prepare_writable_file(
std::string_view type,
std::string_view default_name,
std::string_view data_path,
std::string_view temporary_path)
{
return pp::platform::plan_platform_writable_file(
kPlatformFamily,
type,
default_name,
data_path,
temporary_path);
}
void WebPlatformServices::save_prepared_file(
@@ -53,7 +328,13 @@ void WebPlatformServices::save_prepared_file(
#endif
}
[[nodiscard]] std::unique_ptr<pp::platform::WebPlatformServices> create_web_platform_services()
std::unique_ptr<pp::platform::PlatformServices> create_platform_services(
WebPlatformServicesConfig config)
{
return std::make_unique<WebPlatformServices>(std::move(config));
}
std::unique_ptr<pp::platform::WebPlatformServices> create_web_platform_services()
{
return std::make_unique<WebPlatformServices>();
}

View File

@@ -2,22 +2,96 @@
#include "platform_api/platform_services.h"
#include <functional>
#include <memory>
#include <string_view>
namespace pp::platform::web {
class WebPlatformServices final : public pp::platform::WebPlatformServices {
struct WebPlatformShell {
std::function<void()> acquire_render_context;
std::function<void()> present_render_context;
std::function<void()> request_app_close;
};
struct WebPlatformServicesConfig {
WebPlatformShell shell;
};
class WebPlatformServices final
: public pp::platform::PlatformServices
, public pp::platform::WebPlatformServices {
public:
explicit WebPlatformServices(WebPlatformServicesConfig config = {});
[[nodiscard]] pp::platform::PlatformStoragePaths prepare_storage_paths() override;
void log_stacktrace() override;
void trigger_crash_test() override;
[[nodiscard]] std::string clipboard_text() override;
[[nodiscard]] bool set_clipboard_text(std::string_view text) override;
void set_cursor_visible(bool visible) override;
void set_virtual_keyboard_visible(bool visible) override;
void attach_ui_thread() override;
void detach_ui_thread() override;
void acquire_render_context() override;
void release_render_context() override;
void present_render_context() override;
void bind_default_render_target() override;
void bind_main_render_target() override;
void apply_render_platform_hints() override;
void install_render_debug_callback() override;
void begin_render_capture_frame() override;
void end_render_capture_frame() override;
[[nodiscard]] bool deletes_recorded_files_on_clear() override;
void clear_recorded_files(std::string_view recording_path) override;
void publish_exported_image(std::string_view path) override;
void flush_persistent_storage() override;
[[nodiscard]] std::vector<std::string> document_browse_roots(
std::string_view work_path,
std::string_view data_path) override;
void save_ui_state() override;
[[nodiscard]] bool enables_live_asset_reloading() override;
void update_platform_frame(float delta_time_seconds) override;
void report_rendered_frames(int frames) override;
void display_file(std::string_view path) override;
void share_file(std::string_view path) override;
void request_app_close() override;
[[nodiscard]] bool start_vr_mode() override;
void stop_vr_mode() override;
void pick_image(PickedPathCallback callback) override;
void pick_file(std::vector<std::string> file_types, PickedPathCallback callback) override;
void pick_save_file(std::vector<std::string> file_types, PickedPathCallback callback) override;
void pick_directory(PickedPathCallback callback) override;
[[nodiscard]] bool supports_working_directory_picker() override;
[[nodiscard]] std::string format_working_directory_path(std::string_view path) override;
[[nodiscard]] bool uses_prepared_file_writes() override;
[[nodiscard]] bool uses_work_directory_document_export_collections() override;
[[nodiscard]] bool disables_network_tls_verification() override;
[[nodiscard]] bool uses_ppbr_export_data_directory_override() override;
[[nodiscard]] bool supports_sonarpen() override;
void start_sonarpen() override;
[[nodiscard]] int default_canvas_resolution() override;
[[nodiscard]] bool draws_canvas_tip_for_pointer(
bool is_mouse,
bool is_stylus,
bool is_left_button_release) override;
[[nodiscard]] float adjust_canvas_input_pressure(float pressure) override;
[[nodiscard]] PreparedFileTarget prepare_writable_file(
std::string_view type,
std::string_view default_name,
std::string_view data_path,
std::string_view temporary_path) override;
void save_prepared_file(
std::string_view path,
std::string_view suggested_name,
PreparedFileCallback callback) override;
private:
WebPlatformServicesConfig config_;
};
[[nodiscard]] std::unique_ptr<pp::platform::PlatformServices> create_platform_services(
WebPlatformServicesConfig config = {});
[[nodiscard]] std::unique_ptr<pp::platform::WebPlatformServices> create_web_platform_services();
}

View File

@@ -5,6 +5,7 @@
#include "platform_api/platform_policy.h"
#include "platform_api/platform_services.h"
#include "platform_apple/apple_platform_services.h"
#include "platform_web/web_platform_services.h"
#include <filesystem>
#include <fstream>
@@ -1000,6 +1001,37 @@ void web_platform_services_resolve_injected_services(pp::tests::Harness& harness
PP_EXPECT(harness, pp::platform::injected_web_platform_services() == nullptr);
}
void web_platform_services_dispatch_full_platform_surface(pp::tests::Harness& harness)
{
auto services = pp::platform::web::create_platform_services();
std::string saved_path;
bool saved = true;
PP_EXPECT(harness, services->uses_prepared_file_writes());
PP_EXPECT(harness, !services->supports_working_directory_picker());
PP_EXPECT(harness, services->default_canvas_resolution() == 512);
PP_EXPECT(harness, services->document_browse_roots("D:/Paint/work", "D:/Paint").size() == 1);
PP_EXPECT(harness, services->document_browse_roots("D:/Paint/work", "D:/Paint")[0] == "D:/Paint/work");
PP_EXPECT(harness, services->format_working_directory_path("D:/Paint/.") == "D:/Paint/.");
PP_EXPECT(harness, services->draws_canvas_tip_for_pointer(true, false, false));
const auto target = services->prepare_writable_file("png", "export", "/PanoPainter", "/tmp");
PP_EXPECT(harness, target.path == "/PanoPainter/export.png");
PP_EXPECT(harness, target.suggested_name == "export.png");
PP_EXPECT(harness, !target.write_on_background_thread);
services->save_prepared_file(
"/PanoPainter/export.png",
"export.png",
[&](std::string path, bool success) {
saved_path = std::move(path);
saved = success;
});
PP_EXPECT(harness, saved_path == "/PanoPainter/export.png");
PP_EXPECT(harness, !saved);
}
void platform_services_dispatch_writable_file_target(pp::tests::Harness& harness)
{
FakePlatformServices fake("unused");
@@ -1385,6 +1417,9 @@ int main()
apple_document_platform_services_preserve_working_directory_picker_policy);
harness.run("web platform services preserve default web policy", web_platform_services_preserve_default_web_policy);
harness.run("web platform services resolve injected services", web_platform_services_resolve_injected_services);
harness.run(
"web platform services dispatch full platform surface",
web_platform_services_dispatch_full_platform_surface);
harness.run("platform services dispatch writable file target", platform_services_dispatch_writable_file_target);
harness.run(
"platform services dispatch document export collection policy",

View File

@@ -5,7 +5,6 @@
#include <thread>
#include <chrono>
#include <app.h>
#include <platform_legacy/legacy_platform_services.h>
#include <platform_web/web_platform_services.h>
#include <fstream>
#include <keymap.h>
@@ -16,7 +15,6 @@ App app;
GLFWwindow* wnd;
float theta = 0;
glm::vec2 g_cursor_pos;
std::unique_ptr<pp::platform::WebPlatformServices> g_web_platform_services;
std::unique_ptr<pp::platform::PlatformServices> g_platform_services;
template<typename F>
@@ -201,14 +199,12 @@ int main()
if (glfwInit() != GL_TRUE)
printf("Failed to init GLFW");
wnd = glfwCreateWindow(1024, 768, "PanoPainter", nullptr, nullptr);
g_web_platform_services = pp::platform::web::create_web_platform_services();
g_platform_services = pp::platform::legacy::create_platform_services({
.glfw_shell = {
g_platform_services = pp::platform::web::create_platform_services({
.shell = {
.acquire_render_context = [wnd] { glfwMakeContextCurrent(wnd); },
.present_render_context = [wnd] { glfwSwapBuffers(wnd); },
.request_app_close = [wnd] { glfwSetWindowShouldClose(wnd, GLFW_TRUE); },
},
.web_platform_services = g_web_platform_services.get(),
});
glfwMakeContextCurrent(wnd);