Commit 7f12fada authored by Rayan Kanso's avatar Rayan Kanso Committed by Commit Bot

[Background Fetch] Create a Job Controller for active fetches on restart

After a fetch resumes, the Job Controller needs a
BackgroundFetchRequestInfo to update and complete the download.

Change-Id: Id8f8721381049cdf057214f08fa7372b5256c095
Reviewed-on: https://chromium-review.googlesource.com/1155584
Commit-Queue: Rayan Kanso <rayankans@chromium.org>
Reviewed-by: default avatarPeter Beverloo <peter@chromium.org>
Cr-Commit-Position: refs/heads/master@{#579894}
parent 40443959
...@@ -75,7 +75,7 @@ void BackgroundFetchContext::DidGetInitializationData( ...@@ -75,7 +75,7 @@ void BackgroundFetchContext::DidGetInitializationData(
for (auto& data : initialization_data) { for (auto& data : initialization_data) {
CreateController(data.registration_id, data.registration, data.options, CreateController(data.registration_id, data.registration, data.options,
data.icon, data.ui_title, data.num_completed_requests, data.icon, data.ui_title, data.num_completed_requests,
data.num_requests, data.active_fetch_guids); data.num_requests, std::move(data.active_fetch_requests));
} }
} }
...@@ -266,7 +266,7 @@ void BackgroundFetchContext::OnRegistrationCreated( ...@@ -266,7 +266,7 @@ void BackgroundFetchContext::OnRegistrationCreated(
CreateController(registration_id, registration, options, icon, options.title, CreateController(registration_id, registration, options, icon, options.title,
0u /* num_completed_requests */, num_requests, 0u /* num_completed_requests */, num_requests,
{} /* outstanding_guids */); {} /* active_fetch_requests */);
} }
void BackgroundFetchContext::OnUpdatedUI( void BackgroundFetchContext::OnUpdatedUI(
...@@ -300,7 +300,8 @@ void BackgroundFetchContext::CreateController( ...@@ -300,7 +300,8 @@ void BackgroundFetchContext::CreateController(
const std::string& ui_title, const std::string& ui_title,
size_t num_completed_requests, size_t num_completed_requests,
size_t num_requests, size_t num_requests,
const std::vector<std::string>& outstanding_guids) { std::vector<scoped_refptr<BackgroundFetchRequestInfo>>
active_fetch_requests) {
DCHECK_CURRENTLY_ON(BrowserThread::IO); DCHECK_CURRENTLY_ON(BrowserThread::IO);
auto controller = std::make_unique<BackgroundFetchJobController>( auto controller = std::make_unique<BackgroundFetchJobController>(
...@@ -314,7 +315,8 @@ void BackgroundFetchContext::CreateController( ...@@ -314,7 +315,8 @@ void BackgroundFetchContext::CreateController(
base::Bind(&background_fetch::RecordSchedulerFinishedError))); base::Bind(&background_fetch::RecordSchedulerFinishedError)));
controller->InitializeRequestStatus(num_completed_requests, num_requests, controller->InitializeRequestStatus(num_completed_requests, num_requests,
outstanding_guids, ui_title); std::move(active_fetch_requests),
ui_title);
scheduler_->AddJobController(controller.get()); scheduler_->AddJobController(controller.get());
job_controllers_.emplace(registration_id.unique_id(), std::move(controller)); job_controllers_.emplace(registration_id.unique_id(), std::move(controller));
} }
......
...@@ -34,6 +34,7 @@ class BackgroundFetchDataManager; ...@@ -34,6 +34,7 @@ class BackgroundFetchDataManager;
struct BackgroundFetchOptions; struct BackgroundFetchOptions;
class BackgroundFetchRegistrationId; class BackgroundFetchRegistrationId;
class BackgroundFetchRegistrationNotifier; class BackgroundFetchRegistrationNotifier;
class BackgroundFetchRequestInfo;
class BackgroundFetchScheduler; class BackgroundFetchScheduler;
class BrowserContext; class BrowserContext;
class CacheStorageContextImpl; class CacheStorageContextImpl;
...@@ -158,7 +159,8 @@ class CONTENT_EXPORT BackgroundFetchContext ...@@ -158,7 +159,8 @@ class CONTENT_EXPORT BackgroundFetchContext
const std::string& ui_title, const std::string& ui_title,
size_t num_completed_requests, size_t num_completed_requests,
size_t num_requests, size_t num_requests,
const std::vector<std::string>& outstanding_guids); std::vector<scoped_refptr<BackgroundFetchRequestInfo>>
active_fetch_requests);
// Called when an existing registration has been retrieved from the data // Called when an existing registration has been retrieved from the data
// manager. If the registration does not exist then |registration| is nullptr. // manager. If the registration does not exist then |registration| is nullptr.
......
...@@ -1552,7 +1552,7 @@ TEST_F(BackgroundFetchDataManagerTest, GetInitializationData) { ...@@ -1552,7 +1552,7 @@ TEST_F(BackgroundFetchDataManagerTest, GetInitializationData) {
EXPECT_EQ(init.ui_title, kInitialTitle); EXPECT_EQ(init.ui_title, kInitialTitle);
EXPECT_EQ(init.num_requests, requests.size()); EXPECT_EQ(init.num_requests, requests.size());
EXPECT_EQ(init.num_completed_requests, 0u); EXPECT_EQ(init.num_completed_requests, 0u);
EXPECT_TRUE(init.active_fetch_guids.empty()); EXPECT_TRUE(init.active_fetch_requests.empty());
// Check icon. // Check icon.
ASSERT_FALSE(init.icon.drawsNothing()); ASSERT_FALSE(init.icon.drawsNothing());
...@@ -1574,7 +1574,16 @@ TEST_F(BackgroundFetchDataManagerTest, GetInitializationData) { ...@@ -1574,7 +1574,16 @@ TEST_F(BackgroundFetchDataManagerTest, GetInitializationData) {
EXPECT_EQ(data[0].num_requests, requests.size()); EXPECT_EQ(data[0].num_requests, requests.size());
EXPECT_EQ(data[0].num_completed_requests, 1u); EXPECT_EQ(data[0].num_completed_requests, 1u);
EXPECT_EQ(data[0].active_fetch_guids.size(), 1u); ASSERT_EQ(data[0].active_fetch_requests.size(), 1u);
const auto& init_request_info = data[0].active_fetch_requests[0];
ASSERT_TRUE(init_request_info);
EXPECT_EQ(request_info->download_guid(),
init_request_info->download_guid());
EXPECT_EQ(request_info->request_index(),
init_request_info->request_index());
EXPECT_EQ(request_info->fetch_request().Serialize(),
init_request_info->fetch_request().Serialize());
} }
// Create another registration. // Create another registration.
......
...@@ -241,8 +241,16 @@ void BackgroundFetchDelegateProxy::Core::OnDelegateShutdown() { ...@@ -241,8 +241,16 @@ void BackgroundFetchDelegateProxy::Core::OnDelegateShutdown() {
} }
BackgroundFetchDelegateProxy::JobDetails::JobDetails( BackgroundFetchDelegateProxy::JobDetails::JobDetails(
base::WeakPtr<Controller> controller) base::WeakPtr<Controller> controller,
: controller(controller) {} std::vector<scoped_refptr<BackgroundFetchRequestInfo>>
active_fetch_requests)
: controller(controller) {
for (auto& request_info : active_fetch_requests) {
DCHECK(request_info);
std::string download_guid = request_info->download_guid();
current_request_map[std::move(download_guid)] = std::move(request_info);
}
}
BackgroundFetchDelegateProxy::JobDetails::JobDetails(JobDetails&& details) = BackgroundFetchDelegateProxy::JobDetails::JobDetails(JobDetails&& details) =
default; default;
...@@ -283,12 +291,15 @@ void BackgroundFetchDelegateProxy::GetIconDisplaySize( ...@@ -283,12 +291,15 @@ void BackgroundFetchDelegateProxy::GetIconDisplaySize(
void BackgroundFetchDelegateProxy::CreateDownloadJob( void BackgroundFetchDelegateProxy::CreateDownloadJob(
base::WeakPtr<Controller> controller, base::WeakPtr<Controller> controller,
std::unique_ptr<BackgroundFetchDescription> fetch_description) { std::unique_ptr<BackgroundFetchDescription> fetch_description,
std::vector<scoped_refptr<BackgroundFetchRequestInfo>>
active_fetch_requests) {
DCHECK_CURRENTLY_ON(BrowserThread::IO); DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK(!job_details_map_.count(fetch_description->job_unique_id)); DCHECK(!job_details_map_.count(fetch_description->job_unique_id));
job_details_map_.emplace(fetch_description->job_unique_id, job_details_map_.emplace(
JobDetails(controller)); fetch_description->job_unique_id,
JobDetails(controller, std::move(active_fetch_requests)));
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
base::BindOnce(&Core::CreateDownloadJob, ui_core_ptr_, base::BindOnce(&Core::CreateDownloadJob, ui_core_ptr_,
...@@ -371,6 +382,7 @@ void BackgroundFetchDelegateProxy::DidStartRequest( ...@@ -371,6 +382,7 @@ void BackgroundFetchDelegateProxy::DidStartRequest(
const scoped_refptr<BackgroundFetchRequestInfo>& request_info = const scoped_refptr<BackgroundFetchRequestInfo>& request_info =
job_details.current_request_map[guid]; job_details.current_request_map[guid];
DCHECK(request_info);
DCHECK_EQ(guid, request_info->download_guid()); DCHECK_EQ(guid, request_info->download_guid());
request_info->PopulateWithResponse(std::move(response)); request_info->PopulateWithResponse(std::move(response));
...@@ -403,6 +415,7 @@ void BackgroundFetchDelegateProxy::OnDownloadUpdated( ...@@ -403,6 +415,7 @@ void BackgroundFetchDelegateProxy::OnDownloadUpdated(
if (job_details.controller) { if (job_details.controller) {
const scoped_refptr<BackgroundFetchRequestInfo>& request_info = const scoped_refptr<BackgroundFetchRequestInfo>& request_info =
job_details.current_request_map[guid]; job_details.current_request_map[guid];
DCHECK(request_info);
DCHECK_EQ(guid, request_info->download_guid()); DCHECK_EQ(guid, request_info->download_guid());
job_details.controller->DidUpdateRequest(request_info, bytes_downloaded); job_details.controller->DidUpdateRequest(request_info, bytes_downloaded);
} }
...@@ -424,6 +437,7 @@ void BackgroundFetchDelegateProxy::OnDownloadComplete( ...@@ -424,6 +437,7 @@ void BackgroundFetchDelegateProxy::OnDownloadComplete(
const scoped_refptr<BackgroundFetchRequestInfo>& request_info = const scoped_refptr<BackgroundFetchRequestInfo>& request_info =
job_details.current_request_map[guid]; job_details.current_request_map[guid];
DCHECK(request_info);
DCHECK_EQ(guid, request_info->download_guid()); DCHECK_EQ(guid, request_info->download_guid());
request_info->SetResult(std::move(result)); request_info->SetResult(std::move(result));
......
...@@ -74,11 +74,15 @@ class CONTENT_EXPORT BackgroundFetchDelegateProxy { ...@@ -74,11 +74,15 @@ class CONTENT_EXPORT BackgroundFetchDelegateProxy {
// GUIDs of in progress downloads, while completed downloads are recorded in // GUIDs of in progress downloads, while completed downloads are recorded in
// |fetch_description.completed_parts|. The size of the completed parts is // |fetch_description.completed_parts|. The size of the completed parts is
// recorded in |fetch_description.completed_parts_size| and total download // recorded in |fetch_description.completed_parts_size| and total download
// size is stored in |fetch_description.total_parts_size|. Should only be // size is stored in |fetch_description.total_parts_size|.
// called from the Controller (on the IO thread). // |active_fetch_requests| contains the BackgroundFetchRequestInfos
// needed to correctly resume an ongoing fetch.
// Should only be called from the Controller (on the IO thread).
void CreateDownloadJob( void CreateDownloadJob(
base::WeakPtr<Controller> controller, base::WeakPtr<Controller> controller,
std::unique_ptr<BackgroundFetchDescription> fetch_description); std::unique_ptr<BackgroundFetchDescription> fetch_description,
std::vector<scoped_refptr<BackgroundFetchRequestInfo>>
active_fetch_requests);
// Requests that the download manager start fetching |request|. // Requests that the download manager start fetching |request|.
// Should only be called from the Controller (on the IO // Should only be called from the Controller (on the IO
...@@ -131,7 +135,9 @@ class CONTENT_EXPORT BackgroundFetchDelegateProxy { ...@@ -131,7 +135,9 @@ class CONTENT_EXPORT BackgroundFetchDelegateProxy {
base::WeakPtr<Core> ui_core_ptr_; base::WeakPtr<Core> ui_core_ptr_;
struct JobDetails { struct JobDetails {
explicit JobDetails(base::WeakPtr<Controller> controller); JobDetails(base::WeakPtr<Controller> controller,
std::vector<scoped_refptr<BackgroundFetchRequestInfo>>
active_fetch_requests);
JobDetails(JobDetails&& details); JobDetails(JobDetails&& details);
~JobDetails(); ~JobDetails();
......
...@@ -164,7 +164,8 @@ TEST_F(BackgroundFetchDelegateProxyTest, StartRequest) { ...@@ -164,7 +164,8 @@ TEST_F(BackgroundFetchDelegateProxyTest, StartRequest) {
0 /* completed_parts_size */, 0 /* total_parts_size */, 0 /* completed_parts_size */, 0 /* total_parts_size */,
std::vector<std::string>()); std::vector<std::string>());
delegate_proxy_.CreateDownloadJob(controller.weak_ptr_factory_.GetWeakPtr(), delegate_proxy_.CreateDownloadJob(controller.weak_ptr_factory_.GetWeakPtr(),
std::move(fetch_description)); std::move(fetch_description),
{} /* active_fetch_requests */);
delegate_proxy_.StartRequest(kExampleUniqueId, url::Origin(), request); delegate_proxy_.StartRequest(kExampleUniqueId, url::Origin(), request);
base::RunLoop().RunUntilIdle(); base::RunLoop().RunUntilIdle();
...@@ -188,7 +189,8 @@ TEST_F(BackgroundFetchDelegateProxyTest, StartRequest_NotCompleted) { ...@@ -188,7 +189,8 @@ TEST_F(BackgroundFetchDelegateProxyTest, StartRequest_NotCompleted) {
0 /* completed_parts_size */, 0 /* total_parts_size */, 0 /* completed_parts_size */, 0 /* total_parts_size */,
std::vector<std::string>()); std::vector<std::string>());
delegate_proxy_.CreateDownloadJob(controller.weak_ptr_factory_.GetWeakPtr(), delegate_proxy_.CreateDownloadJob(controller.weak_ptr_factory_.GetWeakPtr(),
std::move(fetch_description)); std::move(fetch_description),
{} /* active_fetch_requests */);
delegate_proxy_.StartRequest(kExampleUniqueId, url::Origin(), request); delegate_proxy_.StartRequest(kExampleUniqueId, url::Origin(), request);
base::RunLoop().RunUntilIdle(); base::RunLoop().RunUntilIdle();
...@@ -216,7 +218,8 @@ TEST_F(BackgroundFetchDelegateProxyTest, Abort) { ...@@ -216,7 +218,8 @@ TEST_F(BackgroundFetchDelegateProxyTest, Abort) {
0 /* completed_parts_size */, 0 /* total_parts_size */, 0 /* completed_parts_size */, 0 /* total_parts_size */,
std::vector<std::string>()); std::vector<std::string>());
delegate_proxy_.CreateDownloadJob(controller.weak_ptr_factory_.GetWeakPtr(), delegate_proxy_.CreateDownloadJob(controller.weak_ptr_factory_.GetWeakPtr(),
std::move(fetch_description1)); std::move(fetch_description1),
{} /* active_fetch_requests */);
auto fetch_description2 = std::make_unique<BackgroundFetchDescription>( auto fetch_description2 = std::make_unique<BackgroundFetchDescription>(
kExampleUniqueId2, "Job 2", url::Origin(), SkBitmap(), kExampleUniqueId2, "Job 2", url::Origin(), SkBitmap(),
...@@ -224,7 +227,8 @@ TEST_F(BackgroundFetchDelegateProxyTest, Abort) { ...@@ -224,7 +227,8 @@ TEST_F(BackgroundFetchDelegateProxyTest, Abort) {
0 /* completed_parts_size */, 0 /* total_parts_size */, 0 /* completed_parts_size */, 0 /* total_parts_size */,
std::vector<std::string>()); std::vector<std::string>());
delegate_proxy_.CreateDownloadJob(controller2.weak_ptr_factory_.GetWeakPtr(), delegate_proxy_.CreateDownloadJob(controller2.weak_ptr_factory_.GetWeakPtr(),
std::move(fetch_description2)); std::move(fetch_description2),
{} /* active_fetch_requests */);
delegate_proxy_.StartRequest(kExampleUniqueId, url::Origin(), request); delegate_proxy_.StartRequest(kExampleUniqueId, url::Origin(), request);
delegate_proxy_.StartRequest(kExampleUniqueId2, url::Origin(), request2); delegate_proxy_.StartRequest(kExampleUniqueId2, url::Origin(), request2);
...@@ -260,7 +264,8 @@ TEST_F(BackgroundFetchDelegateProxyTest, UpdateUI) { ...@@ -260,7 +264,8 @@ TEST_F(BackgroundFetchDelegateProxyTest, UpdateUI) {
std::vector<std::string>()); std::vector<std::string>());
delegate_proxy_.CreateDownloadJob(controller.weak_ptr_factory_.GetWeakPtr(), delegate_proxy_.CreateDownloadJob(controller.weak_ptr_factory_.GetWeakPtr(),
std::move(fetch_description)); std::move(fetch_description),
{} /* active_fetch_requests */);
delegate_proxy_.StartRequest(kExampleUniqueId, url::Origin(), request); delegate_proxy_.StartRequest(kExampleUniqueId, url::Origin(), request);
base::RunLoop().RunUntilIdle(); base::RunLoop().RunUntilIdle();
......
...@@ -35,7 +35,8 @@ BackgroundFetchJobController::BackgroundFetchJobController( ...@@ -35,7 +35,8 @@ BackgroundFetchJobController::BackgroundFetchJobController(
void BackgroundFetchJobController::InitializeRequestStatus( void BackgroundFetchJobController::InitializeRequestStatus(
int completed_downloads, int completed_downloads,
int total_downloads, int total_downloads,
const std::vector<std::string>& outstanding_guids, std::vector<scoped_refptr<BackgroundFetchRequestInfo>>
active_fetch_requests,
const std::string& ui_title) { const std::string& ui_title) {
DCHECK_CURRENTLY_ON(BrowserThread::IO); DCHECK_CURRENTLY_ON(BrowserThread::IO);
...@@ -49,14 +50,19 @@ void BackgroundFetchJobController::InitializeRequestStatus( ...@@ -49,14 +50,19 @@ void BackgroundFetchJobController::InitializeRequestStatus(
// TODO(nator): Update this when we support uploads. // TODO(nator): Update this when we support uploads.
int total_downloads_size = options_.download_total; int total_downloads_size = options_.download_total;
std::vector<std::string> active_guids;
active_guids.reserve(active_fetch_requests.size());
for (const auto& request_info : active_fetch_requests)
active_guids.push_back(request_info->download_guid());
auto fetch_description = std::make_unique<BackgroundFetchDescription>( auto fetch_description = std::make_unique<BackgroundFetchDescription>(
registration_id().unique_id(), ui_title, registration_id().origin(), registration_id().unique_id(), ui_title, registration_id().origin(),
icon_, completed_downloads, total_downloads, icon_, completed_downloads, total_downloads,
complete_requests_downloaded_bytes_cache_, total_downloads_size, complete_requests_downloaded_bytes_cache_, total_downloads_size,
outstanding_guids); std::move(active_guids));
delegate_proxy_->CreateDownloadJob(GetWeakPtr(), delegate_proxy_->CreateDownloadJob(GetWeakPtr(), std::move(fetch_description),
std::move(fetch_description)); std::move(active_fetch_requests));
} }
BackgroundFetchJobController::~BackgroundFetchJobController() { BackgroundFetchJobController::~BackgroundFetchJobController() {
......
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
#include "base/callback.h" #include "base/callback.h"
#include "base/macros.h" #include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h" #include "base/memory/weak_ptr.h"
#include "base/optional.h" #include "base/optional.h"
#include "content/browser/background_fetch/background_fetch_delegate_proxy.h" #include "content/browser/background_fetch/background_fetch_delegate_proxy.h"
...@@ -65,7 +66,8 @@ class CONTENT_EXPORT BackgroundFetchJobController final ...@@ -65,7 +66,8 @@ class CONTENT_EXPORT BackgroundFetchJobController final
void InitializeRequestStatus( void InitializeRequestStatus(
int completed_downloads, int completed_downloads,
int total_downloads, int total_downloads,
const std::vector<std::string>& outstanding_guids, std::vector<scoped_refptr<BackgroundFetchRequestInfo>>
active_fetch_requests,
const std::string& ui_title); const std::string& ui_title);
// Gets the number of bytes downloaded for jobs that are currently running. // Gets the number of bytes downloaded for jobs that are currently running.
......
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
#include "base/task_scheduler/task_traits.h" #include "base/task_scheduler/task_traits.h"
#include "content/browser/background_fetch/background_fetch.pb.h" #include "content/browser/background_fetch/background_fetch.pb.h"
#include "content/browser/background_fetch/background_fetch_data_manager.h" #include "content/browser/background_fetch/background_fetch_data_manager.h"
#include "content/browser/background_fetch/background_fetch_request_info.h"
#include "content/browser/background_fetch/storage/database_helpers.h" #include "content/browser/background_fetch/storage/database_helpers.h"
#include "content/browser/background_fetch/storage/image_helpers.h" #include "content/browser/background_fetch/storage/image_helpers.h"
#include "content/browser/background_fetch/storage/mark_registration_for_deletion_task.h" #include "content/browser/background_fetch/storage/mark_registration_for_deletion_task.h"
...@@ -233,8 +234,16 @@ class GetRequestsTask : public InitializationSubTask { ...@@ -233,8 +234,16 @@ class GetRequestsTask : public InitializationSubTask {
return; return;
} }
DCHECK_EQ(sub_task_init().unique_id, active_request.unique_id()); DCHECK_EQ(sub_task_init().unique_id, active_request.unique_id());
sub_task_init().initialization_data->active_fetch_guids.push_back(
active_request.download_guid()); auto request_info = base::MakeRefCounted<BackgroundFetchRequestInfo>(
active_request.request_index(),
ServiceWorkerFetchRequest::ParseFromString(
active_request.serialized_request()));
request_info->SetDownloadGuid(active_request.download_guid());
sub_task_init().initialization_data->active_fetch_requests.push_back(
std::move(request_info));
pending_requests_to_delete.push_back(PendingRequestKey( pending_requests_to_delete.push_back(PendingRequestKey(
active_request.unique_id(), active_request.request_index())); active_request.unique_id(), active_request.request_index()));
} }
......
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
#include <vector> #include <vector>
#include "base/callback.h" #include "base/callback.h"
#include "base/memory/scoped_refptr.h"
#include "content/browser/background_fetch/background_fetch_registration_id.h" #include "content/browser/background_fetch/background_fetch_registration_id.h"
#include "content/browser/background_fetch/storage/database_task.h" #include "content/browser/background_fetch/storage/database_task.h"
#include "content/browser/service_worker/service_worker_info.h" #include "content/browser/service_worker/service_worker_info.h"
...@@ -21,6 +22,8 @@ ...@@ -21,6 +22,8 @@
namespace content { namespace content {
class BackgroundFetchRequestInfo;
namespace background_fetch { namespace background_fetch {
// All the information needed to create a JobController and resume the fetch // All the information needed to create a JobController and resume the fetch
...@@ -36,7 +39,7 @@ struct CONTENT_EXPORT BackgroundFetchInitializationData { ...@@ -36,7 +39,7 @@ struct CONTENT_EXPORT BackgroundFetchInitializationData {
BackgroundFetchRegistration registration; BackgroundFetchRegistration registration;
size_t num_requests; size_t num_requests;
size_t num_completed_requests; size_t num_completed_requests;
std::vector<std::string> active_fetch_guids; std::vector<scoped_refptr<BackgroundFetchRequestInfo>> active_fetch_requests;
std::string ui_title; std::string ui_title;
// The error, if any, when getting the registration data. // The error, if any, when getting the registration data.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment