diff --git a/include/mbgl/style/style.hpp b/include/mbgl/style/style.hpp index 0d2d45719..6629775f2 100644 --- a/include/mbgl/style/style.hpp +++ b/include/mbgl/style/style.hpp @@ -71,6 +71,8 @@ public: void addLayer(std::unique_ptr, const std::optional& beforeLayerID = std::nullopt); std::unique_ptr removeLayer(const std::string& layerID); + void cancelPendingRequests(); + // Private implementation class Impl; const std::unique_ptr impl; 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/CMakeLists.txt b/test/CMakeLists.txt index e1a882d60..5610e2e3e 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -29,6 +29,7 @@ add_library( ${PROJECT_SOURCE_DIR}/test/src/mbgl/test/getrss.cpp ${PROJECT_SOURCE_DIR}/test/src/mbgl/test/sqlite3_test_fs.cpp ${PROJECT_SOURCE_DIR}/test/src/mbgl/test/stub_file_source.cpp + ${PROJECT_SOURCE_DIR}/test/src/mbgl/test/delayed_file_source.cpp ${PROJECT_SOURCE_DIR}/test/src/mbgl/test/test.cpp ${PROJECT_SOURCE_DIR}/test/src/mbgl/test/util.cpp ${PROJECT_SOURCE_DIR}/test/storage/asset_file_source.test.cpp diff --git a/test/src/mbgl/test/delayed_file_source.cpp b/test/src/mbgl/test/delayed_file_source.cpp new file mode 100644 index 000000000..6f9d8e22a --- /dev/null +++ b/test/src/mbgl/test/delayed_file_source.cpp @@ -0,0 +1,32 @@ +#include + +namespace mbgl { + +DelayedFileSource::AsyncRequestImpl::AsyncRequestImpl(DelayedFileSource& fs, Callback callback_) + : fileSource(fs), + callback(std::move(callback_)) {} + +void DelayedFileSource::AsyncRequestImpl::respond() { + if (callback) { + callback(*fileSource.response); + } +} + +DelayedFileSource::DelayedFileSource() + : StubFileSource(StubFileSource::ResponseType::Synchronous) {} + +std::unique_ptr DelayedFileSource::request(const Resource&, Callback callback) { + auto req = std::make_unique(*this, std::move(callback)); + pendingRequest = req.get(); + return req; +} + +void DelayedFileSource::respondToRequest(const std::string& data) { + response = Response(); + response->data = std::make_shared(data); + if (pendingRequest) { + pendingRequest->respond(); + } +} + +} // namespace mbgl diff --git a/test/src/mbgl/test/delayed_file_source.hpp b/test/src/mbgl/test/delayed_file_source.hpp new file mode 100644 index 000000000..df7229019 --- /dev/null +++ b/test/src/mbgl/test/delayed_file_source.hpp @@ -0,0 +1,43 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace mbgl { + +/* + This is a file source that allows control of when a request is responded to. +*/ +class DelayedFileSource : public StubFileSource { +public: + DelayedFileSource(); + ~DelayedFileSource() override = default; + + std::unique_ptr request(const Resource&, Callback) override; + + // Custom method to respond to pending request + void respondToRequest(const std::string& data); + +private: + class AsyncRequestImpl : public AsyncRequest { + public: + using Callback = std::function; + + AsyncRequestImpl(DelayedFileSource& fs, Callback callback_); + void respond(); + + private: + DelayedFileSource& fileSource; + Callback callback; + }; + + friend class AsyncRequestImpl; + std::optional response; + AsyncRequestImpl* pendingRequest = nullptr; +}; + +} // namespace mbgl diff --git a/test/style/style.test.cpp b/test/style/style.test.cpp index b08989cd9..4d1951d02 100644 --- a/test/style/style.test.cpp +++ b/test/style/style.test.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include @@ -110,6 +111,38 @@ TEST(Style, RemoveSourceInUse) { EXPECT_EQ(log.count(logMessage), 1u); } +TEST(Style, LoadJSONCancelsPendingLoadURL) { + util::RunLoop loop; + + auto fileSource = std::make_shared<::DelayedFileSource>(); + Style::Impl style{fileSource, 1.0, {Scheduler::GetBackground(), {}}}; + + // Start loading a URL (this will be pending) + auto url = "http://some-url"; + 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); + + // Now respond to the original URL request - this should be ignored + fileSource->respondToRequest(R"STYLE({"version":8,"name":"Streets"})STYLE"); + + // 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();