@@ -4311,6 +4311,159 @@ void test_async_save_acknowledgement_publishes_without_pointer_input()
43114311 webscene_engine_destroy(engine);
43124312}
43134313
4314+ void test_identical_custom_property_writes_do_not_schedule_visual_work()
4315+ {
4316+ auto* engine = webscene_engine_create(0);
4317+ require(engine != nullptr, "custom-property no-op engine creation failed");
4318+
4319+ const webscene_scene_view* initial = nullptr;
4320+ for (auto attempt = 0; attempt < 100 && initial == nullptr; ++attempt) {
4321+ initial = webscene_engine_acquire_latest_scene(engine);
4322+ if (initial == nullptr) {
4323+ std::this_thread::sleep_for(std::chrono::milliseconds(2));
4324+ }
4325+ }
4326+ require(initial != nullptr, "custom-property no-op fixture had no initial scene");
4327+ require(
4328+ webscene_scene_acknowledge(initial) != 0,
4329+ "custom-property no-op fixture could not acknowledge its initial scene");
4330+ webscene_scene_release(initial);
4331+
4332+ execute_and_wait(
4333+ engine,
4334+ R"JS(
4335+ const style = document.createElement('style');
4336+ style.textContent = '#custom-property-noop { width: var(--editor-width); height: 24px; }';
4337+ document.head.appendChild(style);
4338+ const target = document.createElement('div');
4339+ target.id = 'custom-property-noop';
4340+ target.style.setProperty('--editor-width', '120px');
4341+ document.body.appendChild(target);
4342+ )JS",
4343+ "custom-property-noop-setup.js");
4344+ require(
4345+ evaluate(
4346+ engine,
4347+ "document.getElementById('custom-property-noop').offsetWidth",
4348+ "custom-property-noop-ready.js") == "120",
4349+ "custom-property no-op fixture did not complete initial layout");
4350+
4351+ auto setup_scene_acknowledged = false;
4352+ for (auto attempt = 0; attempt < 100; ++attempt) {
4353+ const auto* scene = webscene_engine_acquire_latest_scene(engine);
4354+ if (scene != nullptr) {
4355+ require(
4356+ webscene_scene_acknowledge(scene) != 0,
4357+ "custom-property no-op setup scene could not be acknowledged");
4358+ webscene_scene_release(scene);
4359+ setup_scene_acknowledged = true;
4360+ break;
4361+ }
4362+ std::this_thread::sleep_for(std::chrono::milliseconds(2));
4363+ }
4364+ require(
4365+ setup_scene_acknowledged,
4366+ "custom-property no-op setup scene was not published");
4367+ std::this_thread::sleep_for(std::chrono::milliseconds(25));
4368+
4369+ webscene_engine_metrics connected_before{};
4370+ webscene_engine_get_metrics(engine, &connected_before);
4371+ execute_and_wait(
4372+ engine,
4373+ R"JS(
4374+ const target = document.getElementById('custom-property-noop');
4375+ for (let index = 0; index < 4096; ++index) {
4376+ target.style.setProperty('--editor-width', '120px');
4377+ }
4378+ )JS",
4379+ "custom-property-noop-connected.js");
4380+ std::this_thread::sleep_for(std::chrono::milliseconds(50));
4381+ webscene_engine_metrics connected_after{};
4382+ webscene_engine_get_metrics(engine, &connected_after);
4383+ require(
4384+ connected_after.layout_passes == connected_before.layout_passes
4385+ && connected_after.published_scenes == connected_before.published_scenes,
4386+ "identical connected custom-property writes scheduled layout or publication");
4387+
4388+ execute_and_wait(
4389+ engine,
4390+ R"JS(
4391+ globalThis.__customPropertyNoopTarget =
4392+ document.getElementById('custom-property-noop');
4393+ globalThis.__customPropertyNoopTarget.remove();
4394+ )JS",
4395+ "custom-property-noop-detach.js");
4396+ auto detach_scene_acknowledged = false;
4397+ for (auto attempt = 0; attempt < 100; ++attempt) {
4398+ const auto* scene = webscene_engine_acquire_latest_scene(engine);
4399+ if (scene != nullptr) {
4400+ require(
4401+ webscene_scene_acknowledge(scene) != 0,
4402+ "custom-property no-op detach scene could not be acknowledged");
4403+ webscene_scene_release(scene);
4404+ detach_scene_acknowledged = true;
4405+ break;
4406+ }
4407+ std::this_thread::sleep_for(std::chrono::milliseconds(2));
4408+ }
4409+ require(
4410+ detach_scene_acknowledged,
4411+ "custom-property no-op detach scene was not published");
4412+ std::this_thread::sleep_for(std::chrono::milliseconds(25));
4413+
4414+ webscene_engine_metrics detached_before{};
4415+ webscene_engine_get_metrics(engine, &detached_before);
4416+ execute_and_wait(
4417+ engine,
4418+ R"JS(
4419+ for (let index = 0; index < 4096; ++index) {
4420+ __customPropertyNoopTarget.style.setProperty('--editor-width', '120px');
4421+ }
4422+ )JS",
4423+ "custom-property-noop-detached.js");
4424+ std::this_thread::sleep_for(std::chrono::milliseconds(50));
4425+ webscene_engine_metrics detached_after{};
4426+ webscene_engine_get_metrics(engine, &detached_after);
4427+ require(
4428+ detached_after.layout_passes == detached_before.layout_passes
4429+ && detached_after.published_scenes == detached_before.published_scenes,
4430+ "identical detached custom-property writes scheduled visual work");
4431+
4432+ execute_and_wait(
4433+ engine,
4434+ R"JS(
4435+ document.body.appendChild(__customPropertyNoopTarget);
4436+ delete globalThis.__customPropertyNoopTarget;
4437+ )JS",
4438+ "custom-property-noop-reattach.js");
4439+ webscene_engine_metrics reattached{};
4440+ for (auto attempt = 0; attempt < 100; ++attempt) {
4441+ webscene_engine_get_metrics(engine, &reattached);
4442+ if (reattached.published_scenes > detached_after.published_scenes) break;
4443+ std::this_thread::sleep_for(std::chrono::milliseconds(2));
4444+ }
4445+ require(
4446+ evaluate(
4447+ engine,
4448+ R"JS((() => {
4449+ const target = document.getElementById('custom-property-noop');
4450+ return {
4451+ specified: target.style.getPropertyValue('--editor-width'),
4452+ width: target.offsetWidth,
4453+ connected: target.isConnected
4454+ };
4455+ })())JS",
4456+ "custom-property-noop-reattach-result.js")
4457+ == R"({"specified":"120px","width":120,"connected":true})",
4458+ "custom-property no-op state did not survive detach and reconnect");
4459+ webscene_engine_get_metrics(engine, &reattached);
4460+ require(
4461+ reattached.layout_passes == detached_after.layout_passes + 1U
4462+ && reattached.published_scenes == detached_after.published_scenes + 1U,
4463+ "custom-property no-op reconnect escaped one layout/publication");
4464+ webscene_engine_destroy(engine);
4465+ }
4466+
43144467void test_ordered_scene_consumer_preserves_two_diff_chain()
43154468{
43164469 auto* engine = webscene_engine_create(64);
0 commit comments