From 1f320fd0895afe3c56a577f803b87351208a9a3f Mon Sep 17 00:00:00 2001 From: Bart Louwers Date: Tue, 9 Dec 2025 13:21:22 +0100 Subject: [PATCH] Cancel pending style request when loading style JSON (#3989) Signed-off-by: Bart Louwers --- src/mbgl/style/style_impl.cpp | 5 ++++ src/mbgl/style/style_impl.hpp | 8 +++++ test/src/mbgl/test/stub_file_source.cpp | 15 ++++++++-- test/src/mbgl/test/stub_file_source.hpp | 5 +++- test/style/style.test.cpp | 39 +++++++++++++++++++++++++ 5 files changed, 69 insertions(+), 3 deletions(-) diff --git a/src/mbgl/style/style_impl.cpp b/src/mbgl/style/style_impl.cpp index 2b4bf88b8..88d65d0ed 100644 --- a/src/mbgl/style/style_impl.cpp +++ b/src/mbgl/style/style_impl.cpp @@ -39,10 +39,15 @@ Style::Impl::Impl(std::shared_ptr fileSource_, float pixelRatio, con Style::Impl::~Impl() = default; +void Style::Impl::cancelPendingRequest() noexcept { + styleRequest.reset(); +} + void Style::Impl::loadJSON(const std::string& json_) { lastError = nullptr; observer->onStyleLoading(); + cancelPendingRequest(); url.clear(); parse(json_); } diff --git a/src/mbgl/style/style_impl.hpp b/src/mbgl/style/style_impl.hpp index 8ef03e98f..14364877f 100644 --- a/src/mbgl/style/style_impl.hpp +++ b/src/mbgl/style/style_impl.hpp @@ -45,6 +45,14 @@ public: void loadJSON(const std::string&); void loadURL(const std::string&); + /** + * @brief Cancels any pending style request. + * + * This will cancel any in-progress style URL load. Has no effect if no + * style load is in progress. + */ + void cancelPendingRequest() noexcept; + std::string getJSON() const; std::string getURL() const; diff --git a/test/src/mbgl/test/stub_file_source.cpp b/test/src/mbgl/test/stub_file_source.cpp index 598b20b66..ac4465d5d 100644 --- a/test/src/mbgl/test/stub_file_source.cpp +++ b/test/src/mbgl/test/stub_file_source.cpp @@ -63,8 +63,7 @@ StubFileSource::~StubFileSource() = default; std::unique_ptr StubFileSource::request(const Resource& resource, Callback callback) { auto req = std::make_unique(*this); if (type == ResponseType::Synchronous) { - std::optional res = response(resource); - if (res) { + if (const std::optional res = response(resource)) { callback(*res); } } else { @@ -79,6 +78,18 @@ void StubFileSource::remove(AsyncRequest* req) { pending.erase(it); } } +void StubFileSource::respondToAll() { + if (type != ResponseType::Manual) { + throw std::runtime_error("this method can only be called with ResponseType::Manual"); + } + for (const auto& [key, value] : pending) { + auto& [resource, responseFunction, callback] = value; + if (const std::optional res = responseFunction(resource)) { + callback(*res); + } + pending.erase(key); + } +} void StubFileSource::setProperty(const std::string& key, const mapbox::base::Value& value) { properties[key] = value; diff --git a/test/src/mbgl/test/stub_file_source.hpp b/test/src/mbgl/test/stub_file_source.hpp index 1bad4a74d..92e76d373 100644 --- a/test/src/mbgl/test/stub_file_source.hpp +++ b/test/src/mbgl/test/stub_file_source.hpp @@ -16,7 +16,8 @@ class StubFileSource : public FileSource { public: enum class ResponseType { Asynchronous = 0, - Synchronous + Synchronous, + Manual }; StubFileSource(const ResourceOptions&, const ClientOptions&, ResponseType = ResponseType::Asynchronous); @@ -26,6 +27,8 @@ public: std::unique_ptr request(const Resource&, Callback) override; bool canRequest(const Resource&) const override { return true; } void remove(AsyncRequest*); + void respondToAll(); + void setProperty(const std::string&, const mapbox::base::Value&) override; mapbox::base::Value getProperty(const std::string&) const override; diff --git a/test/style/style.test.cpp b/test/style/style.test.cpp index b08989cd9..34ac3a5fc 100644 --- a/test/style/style.test.cpp +++ b/test/style/style.test.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -9,6 +10,7 @@ #include #include #include +#include #include @@ -110,6 +112,43 @@ TEST(Style, RemoveSourceInUse) { EXPECT_EQ(log.count(logMessage), 1u); } +TEST(Style, LoadJSONCancelsPendingLoadURL) { + util::RunLoop loop; + + auto fileSource = std::make_shared<::StubFileSource>( + ResourceOptions::Default(), ClientOptions(), StubFileSource::ResponseType::Manual); + Style::Impl style{fileSource, 1.0, {Scheduler::GetBackground(), {}}}; + + // Start loading a URL (this will be pending) + auto url = "http://some-url"; + fileSource->styleResponse = [](const Resource&) { + Response result; + result.data = std::make_shared(util::read_file("test/fixtures/resources/style_vector.json")); + return result; + }; + style.loadURL(url); + + // Before the URL request completes, load JSON directly + const std::string json = R"STYLE({ + "version": 8, + "name": "Test Style", + "sources": {}, + "layers": [] + })STYLE"; + style.loadJSON(json); + + // The style should now be loaded with our JSON content + ASSERT_EQ("Test Style", style.getName()); + ASSERT_EQ("", style.getURL()); + ASSERT_TRUE(style.getJSON().find("Test Style") != std::string::npos); + + fileSource->respondToAll(); + + // The style should still show our JSON content, not the URL content + ASSERT_EQ("Test Style", style.getName()); + ASSERT_NE("Streets", style.getName()); +} + TEST(Style, SourceImplsOrder) { util::RunLoop loop; auto fileSource = std::make_shared();