Commit 30cd990c authored by Rayan Kanso's avatar Rayan Kanso Committed by Commit Bot

[Background Fetch] Fix UpdateUI race condition.

The jobs were sometimes being erased before the icon was updated. The
delegate now notifies the clients after the new UI information has been
passed along.

This adds another internal Job State to the delegate to know when the
job was complete.

Bug: 917384

Change-Id: I00c599e298e342fbfafaaa9a39d5d7bcdc5cf7ba
Reviewed-on: https://chromium-review.googlesource.com/c/1388646
Commit-Queue: Rayan Kanso <rayankans@chromium.org>
Reviewed-by: default avatarAvi Drissman <avi@chromium.org>
Reviewed-by: default avatarMugdha Lakhani <nator@chromium.org>
Cr-Commit-Position: refs/heads/master@{#619916}
parent 3065e0c4
...@@ -707,6 +707,9 @@ IN_PROC_BROWSER_TEST_F(BackgroundFetchBrowserTest, ...@@ -707,6 +707,9 @@ IN_PROC_BROWSER_TEST_F(BackgroundFetchBrowserTest,
FetchesRunToCompletionAndUpdateTitle_Fetched) { FetchesRunToCompletionAndUpdateTitle_Fetched) {
ASSERT_NO_FATAL_FAILURE(RunScriptAndCheckResultingMessage( ASSERT_NO_FATAL_FAILURE(RunScriptAndCheckResultingMessage(
"RunFetchTillCompletion()", "backgroundfetchsuccess")); "RunFetchTillCompletion()", "backgroundfetchsuccess"));
EXPECT_EQ(offline_content_provider_observer_->latest_item().state,
offline_items_collection::OfflineItemState::COMPLETE);
base::RunLoop().RunUntilIdle(); // Give `updateUI` a chance to propagate. base::RunLoop().RunUntilIdle(); // Give `updateUI` a chance to propagate.
EXPECT_TRUE( EXPECT_TRUE(
base::StartsWith(offline_content_provider_observer_->latest_item().title, base::StartsWith(offline_content_provider_observer_->latest_item().title,
...@@ -720,6 +723,9 @@ IN_PROC_BROWSER_TEST_F(BackgroundFetchBrowserTest, ...@@ -720,6 +723,9 @@ IN_PROC_BROWSER_TEST_F(BackgroundFetchBrowserTest,
FetchesRunToCompletionAndUpdateTitle_Failed) { FetchesRunToCompletionAndUpdateTitle_Failed) {
ASSERT_NO_FATAL_FAILURE(RunScriptAndCheckResultingMessage( ASSERT_NO_FATAL_FAILURE(RunScriptAndCheckResultingMessage(
"RunFetchTillCompletionWithMissingResource()", "backgroundfetchfail")); "RunFetchTillCompletionWithMissingResource()", "backgroundfetchfail"));
EXPECT_EQ(offline_content_provider_observer_->latest_item().state,
offline_items_collection::OfflineItemState::COMPLETE);
base::RunLoop().RunUntilIdle(); // Give `updateUI` a chance to propagate. base::RunLoop().RunUntilIdle(); // Give `updateUI` a chance to propagate.
EXPECT_TRUE( EXPECT_TRUE(
base::StartsWith(offline_content_provider_observer_->latest_item().title, base::StartsWith(offline_content_provider_observer_->latest_item().title,
......
...@@ -108,6 +108,13 @@ void BackgroundFetchDelegateImpl::JobDetails::MarkJobAsStarted() { ...@@ -108,6 +108,13 @@ void BackgroundFetchDelegateImpl::JobDetails::MarkJobAsStarted() {
job_state = State::kStartedButPaused; job_state = State::kStartedButPaused;
} }
void BackgroundFetchDelegateImpl::JobDetails::UpdateJobOnDownloadComplete() {
fetch_description->completed_parts++;
in_progress_parts_size = 0u;
if (fetch_description->completed_parts == fetch_description->total_parts)
job_state = State::kDownloadsComplete;
}
void BackgroundFetchDelegateImpl::JobDetails::UpdateOfflineItem() { void BackgroundFetchDelegateImpl::JobDetails::UpdateOfflineItem() {
DCHECK_GT(fetch_description->total_parts, 0); DCHECK_GT(fetch_description->total_parts, 0);
...@@ -116,8 +123,7 @@ void BackgroundFetchDelegateImpl::JobDetails::UpdateOfflineItem() { ...@@ -116,8 +123,7 @@ void BackgroundFetchDelegateImpl::JobDetails::UpdateOfflineItem() {
// If we have completed all downloads, update progress max to // If we have completed all downloads, update progress max to
// completed_parts_size in case total_parts_size was set too high. This // completed_parts_size in case total_parts_size was set too high. This
// avoid unnecessary jumping in the progress bar. // avoid unnecessary jumping in the progress bar.
offline_item.progress.max = offline_item.progress.max = job_state == State::kDownloadsComplete
(fetch_description->completed_parts == fetch_description->total_parts)
? fetch_description->completed_parts_size ? fetch_description->completed_parts_size
: fetch_description->total_parts_size; : fetch_description->total_parts_size;
} else { } else {
...@@ -134,18 +140,21 @@ void BackgroundFetchDelegateImpl::JobDetails::UpdateOfflineItem() { ...@@ -134,18 +140,21 @@ void BackgroundFetchDelegateImpl::JobDetails::UpdateOfflineItem() {
offline_item.is_resumable = true; offline_item.is_resumable = true;
using OfflineItemState = offline_items_collection::OfflineItemState; using OfflineItemState = offline_items_collection::OfflineItemState;
if (job_state == State::kCancelled) { switch (job_state) {
case State::kCancelled:
offline_item.state = OfflineItemState::CANCELLED; offline_item.state = OfflineItemState::CANCELLED;
} else if (fetch_description->completed_parts == break;
fetch_description->total_parts) { case State::kDownloadsComplete:
// This includes cases when the download failed, or completed but the // This includes cases when the download failed, or completed but the
// response was an HTTP error, e.g. 404. // response was an HTTP error, e.g. 404.
offline_item.state = OfflineItemState::COMPLETE; offline_item.state = OfflineItemState::COMPLETE;
offline_item.is_openable = true; offline_item.is_openable = true;
} else if (job_state == State::kPendingWillStartPaused || break;
job_state == State::kStartedButPaused) { case State::kPendingWillStartPaused:
case State::kStartedButPaused:
offline_item.state = OfflineItemState::PAUSED; offline_item.state = OfflineItemState::PAUSED;
} else { break;
default:
offline_item.state = OfflineItemState::IN_PROGRESS; offline_item.state = OfflineItemState::IN_PROGRESS;
} }
} }
...@@ -361,6 +370,17 @@ void BackgroundFetchDelegateImpl::UpdateUI( ...@@ -361,6 +370,17 @@ void BackgroundFetchDelegateImpl::UpdateUI(
job_details.offline_item.refresh_visuals = true; job_details.offline_item.refresh_visuals = true;
} }
bool should_update_visuals = job_details.offline_item.refresh_visuals;
#if !defined(OS_ANDROID)
should_update_visuals = false;
#endif
if (!should_update_visuals) {
// Notify the client that the UI updates have been handed over.
if (job_details.client)
job_details.client->OnUIUpdated(job_unique_id);
}
UpdateOfflineItemAndUpdateObservers(&job_details); UpdateOfflineItemAndUpdateObservers(&job_details);
} }
...@@ -438,9 +458,8 @@ void BackgroundFetchDelegateImpl::OnDownloadFailed( ...@@ -438,9 +458,8 @@ void BackgroundFetchDelegateImpl::OnDownloadFailed(
const std::string& job_unique_id = download_job_unique_id_iter->second; const std::string& job_unique_id = download_job_unique_id_iter->second;
JobDetails& job_details = job_details_map_.find(job_unique_id)->second; JobDetails& job_details = job_details_map_.find(job_unique_id)->second;
++job_details.fetch_description->completed_parts;
job_details.in_progress_parts_size = 0u;
job_details.UpdateJobOnDownloadComplete();
UpdateOfflineItemAndUpdateObservers(&job_details); UpdateOfflineItemAndUpdateObservers(&job_details);
// The client cancelled or aborted the download so no need to notify it. // The client cancelled or aborted the download so no need to notify it.
...@@ -472,8 +491,7 @@ void BackgroundFetchDelegateImpl::OnDownloadSucceeded( ...@@ -472,8 +491,7 @@ void BackgroundFetchDelegateImpl::OnDownloadSucceeded(
const std::string& job_unique_id = download_job_unique_id_iter->second; const std::string& job_unique_id = download_job_unique_id_iter->second;
JobDetails& job_details = job_details_map_.find(job_unique_id)->second; JobDetails& job_details = job_details_map_.find(job_unique_id)->second;
++job_details.fetch_description->completed_parts; job_details.UpdateJobOnDownloadComplete();
job_details.in_progress_parts_size = 0u;
job_details.fetch_description->completed_parts_size += job_details.fetch_description->completed_parts_size +=
profile_->IsOffTheRecord() ? result->blob_handle->size() profile_->IsOffTheRecord() ? result->blob_handle->size()
...@@ -633,9 +651,14 @@ void BackgroundFetchDelegateImpl::GetVisualsForItem( ...@@ -633,9 +651,14 @@ void BackgroundFetchDelegateImpl::GetVisualsForItem(
std::make_unique<offline_items_collection::OfflineItemVisuals>(); std::make_unique<offline_items_collection::OfflineItemVisuals>();
auto it = job_details_map_.find(id.id); auto it = job_details_map_.find(id.id);
if (it != job_details_map_.end()) { if (it != job_details_map_.end()) {
auto& job_details = it->second;
visuals->icon = visuals->icon =
gfx::Image::CreateFrom1xBitmap(it->second.fetch_description->icon); gfx::Image::CreateFrom1xBitmap(job_details.fetch_description->icon);
it->second.offline_item.refresh_visuals = false; job_details.offline_item.refresh_visuals = false;
if (job_details.client &&
job_details.job_state == JobDetails::State::kDownloadsComplete) {
job_details.client->OnUIUpdated(id.id);
}
} }
base::ThreadTaskRunnerHandle::Get()->PostTask( base::ThreadTaskRunnerHandle::Get()->PostTask(
......
...@@ -142,7 +142,10 @@ class BackgroundFetchDelegateImpl ...@@ -142,7 +142,10 @@ class BackgroundFetchDelegateImpl
kPendingWillStartDownloading, kPendingWillStartDownloading,
kStartedButPaused, kStartedButPaused,
kStartedAndDownloading, kStartedAndDownloading,
// The job was aborted.
kCancelled, kCancelled,
// All requests were processed (either succeeded or failed).
kDownloadsComplete,
}; };
JobDetails(JobDetails&&); JobDetails(JobDetails&&);
...@@ -155,6 +158,7 @@ class BackgroundFetchDelegateImpl ...@@ -155,6 +158,7 @@ class BackgroundFetchDelegateImpl
void UpdateOfflineItem(); void UpdateOfflineItem();
void MarkJobAsStarted(); void MarkJobAsStarted();
void UpdateJobOnDownloadComplete();
// Returns how many bytes have been processed by the Download Service so // Returns how many bytes have been processed by the Download Service so
// far. // far.
......
...@@ -229,8 +229,8 @@ void BackgroundFetchContext::UpdateUI( ...@@ -229,8 +229,8 @@ void BackgroundFetchContext::UpdateUI(
blink::mojom::BackgroundFetchService::UpdateUICallback callback) { blink::mojom::BackgroundFetchService::UpdateUICallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::IO); DCHECK_CURRENTLY_ON(BrowserThread::IO);
delegate_proxy_.UpdateUI(registration_id.unique_id(), title, icon); delegate_proxy_.UpdateUI(registration_id.unique_id(), title, icon,
std::move(callback).Run(blink::mojom::BackgroundFetchError::NONE); std::move(callback));
} }
void BackgroundFetchContext::Abort( void BackgroundFetchContext::Abort(
......
...@@ -207,6 +207,7 @@ class BackgroundFetchDelegateProxy::Core ...@@ -207,6 +207,7 @@ class BackgroundFetchDelegateProxy::Core
const std::string& guid, const std::string& guid,
std::unique_ptr<content::BackgroundFetchResponse> response) override; std::unique_ptr<content::BackgroundFetchResponse> response) override;
void OnUIActivated(const std::string& unique_id) override; void OnUIActivated(const std::string& unique_id) override;
void OnUIUpdated(const std::string& unique_id) override;
void GetUploadData( void GetUploadData(
const std::string& job_unique_id, const std::string& job_unique_id,
const std::string& download_guid, const std::string& download_guid,
...@@ -279,6 +280,16 @@ void BackgroundFetchDelegateProxy::Core::OnUIActivated( ...@@ -279,6 +280,16 @@ void BackgroundFetchDelegateProxy::Core::OnUIActivated(
job_unique_id)); job_unique_id));
} }
void BackgroundFetchDelegateProxy::Core::OnUIUpdated(
const std::string& job_unique_id) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
base::PostTaskWithTraits(
FROM_HERE, {BrowserThread::IO},
base::BindOnce(&BackgroundFetchDelegateProxy::DidUpdateUI, io_parent_,
job_unique_id));
}
void BackgroundFetchDelegateProxy::Core::GetUploadData( void BackgroundFetchDelegateProxy::Core::GetUploadData(
const std::string& job_unique_id, const std::string& job_unique_id,
const std::string& download_guid, const std::string& download_guid,
...@@ -405,9 +416,20 @@ void BackgroundFetchDelegateProxy::StartRequest( ...@@ -405,9 +416,20 @@ void BackgroundFetchDelegateProxy::StartRequest(
void BackgroundFetchDelegateProxy::UpdateUI( void BackgroundFetchDelegateProxy::UpdateUI(
const std::string& job_unique_id, const std::string& job_unique_id,
const base::Optional<std::string>& title, const base::Optional<std::string>& title,
const base::Optional<SkBitmap>& icon) { const base::Optional<SkBitmap>& icon,
blink::mojom::BackgroundFetchService::UpdateUICallback update_ui_callback) {
DCHECK_CURRENTLY_ON(BrowserThread::IO); DCHECK_CURRENTLY_ON(BrowserThread::IO);
auto job_details_iter = job_details_map_.find(job_unique_id);
if (job_details_iter == job_details_map_.end()) {
std::move(update_ui_callback)
.Run(blink::mojom::BackgroundFetchError::INVALID_ID);
return;
}
JobDetails& job_details = job_details_iter->second;
job_details.update_ui_callback = std::move(update_ui_callback);
base::PostTaskWithTraits(FROM_HERE, {BrowserThread::UI}, base::PostTaskWithTraits(FROM_HERE, {BrowserThread::UI},
base::BindOnce(&Core::UpdateUI, ui_core_ptr_, base::BindOnce(&Core::UpdateUI, ui_core_ptr_,
job_unique_id, title, icon)); job_unique_id, title, icon));
...@@ -481,6 +503,19 @@ void BackgroundFetchDelegateProxy::DidActivateUI( ...@@ -481,6 +503,19 @@ void BackgroundFetchDelegateProxy::DidActivateUI(
click_event_dispatcher_callback_.Run(job_unique_id); click_event_dispatcher_callback_.Run(job_unique_id);
} }
void BackgroundFetchDelegateProxy::DidUpdateUI(
const std::string& job_unique_id) {
auto job_details_iter = job_details_map_.find(job_unique_id);
if (job_details_iter == job_details_map_.end())
return;
JobDetails& job_details = job_details_iter->second;
DCHECK(job_details.update_ui_callback);
std::move(job_details.update_ui_callback)
.Run(blink::mojom::BackgroundFetchError::NONE);
}
void BackgroundFetchDelegateProxy::OnDownloadUpdated( void BackgroundFetchDelegateProxy::OnDownloadUpdated(
const std::string& job_unique_id, const std::string& job_unique_id,
const std::string& guid, const std::string& guid,
......
...@@ -33,7 +33,7 @@ class CONTENT_EXPORT BackgroundFetchDelegateProxy { ...@@ -33,7 +33,7 @@ class CONTENT_EXPORT BackgroundFetchDelegateProxy {
// Subclasses must only be destroyed on the IO thread, since these methods // Subclasses must only be destroyed on the IO thread, since these methods
// will be called on the IO thread. // will be called on the IO thread.
using DispatchClickEventCallback = using DispatchClickEventCallback =
base::RepeatingCallback<void(const std::string& /* unique_id */)>; base::RepeatingCallback<void(const std::string& unique_id)>;
class Controller { class Controller {
public: public:
// Called when the given |request| has started fetching. // Called when the given |request| has started fetching.
...@@ -110,11 +110,14 @@ class CONTENT_EXPORT BackgroundFetchDelegateProxy { ...@@ -110,11 +110,14 @@ class CONTENT_EXPORT BackgroundFetchDelegateProxy {
scoped_refptr<BackgroundFetchRequestInfo> request); scoped_refptr<BackgroundFetchRequestInfo> request);
// Updates the representation of this registration in the user interface to // Updates the representation of this registration in the user interface to
// match the given |title| or |icon|. // match the given |title| or |icon|. |update_ui_callback| should be called
// after all the relevant UI information has been processed.
// Called from the Controller (on the IO thread). // Called from the Controller (on the IO thread).
void UpdateUI(const std::string& job_unique_id, void UpdateUI(const std::string& job_unique_id,
const base::Optional<std::string>& title, const base::Optional<std::string>& title,
const base::Optional<SkBitmap>& icon); const base::Optional<SkBitmap>& icon,
blink::mojom::BackgroundFetchService::UpdateUICallback
update_ui_callback);
// Aborts in progress downloads for the given registration. Called from the // Aborts in progress downloads for the given registration. Called from the
// Controller (on the IO thread) after it is aborted. May occur even if all // Controller (on the IO thread) after it is aborted. May occur even if all
...@@ -155,6 +158,9 @@ class CONTENT_EXPORT BackgroundFetchDelegateProxy { ...@@ -155,6 +158,9 @@ class CONTENT_EXPORT BackgroundFetchDelegateProxy {
// Should only be called from the BackgroundFetchDelegate (on the IO thread). // Should only be called from the BackgroundFetchDelegate (on the IO thread).
void DidActivateUI(const std::string& job_unique_id); void DidActivateUI(const std::string& job_unique_id);
// Should only be called from the BackgroundFetchDelegate (on the IO thread).
void DidUpdateUI(const std::string& job_unique_id);
// Should only be called from the BackgroundFetchDelegate (on the IO thread). // Should only be called from the BackgroundFetchDelegate (on the IO thread).
void GetUploadData(const std::string& job_unique_id, void GetUploadData(const std::string& job_unique_id,
const std::string& download_guid, const std::string& download_guid,
...@@ -176,6 +182,8 @@ class CONTENT_EXPORT BackgroundFetchDelegateProxy { ...@@ -176,6 +182,8 @@ class CONTENT_EXPORT BackgroundFetchDelegateProxy {
base::flat_map<std::string, scoped_refptr<BackgroundFetchRequestInfo>> base::flat_map<std::string, scoped_refptr<BackgroundFetchRequestInfo>>
current_request_map; current_request_map;
blink::mojom::BackgroundFetchService::UpdateUICallback update_ui_callback;
private: private:
DISALLOW_COPY_AND_ASSIGN(JobDetails); DISALLOW_COPY_AND_ASSIGN(JobDetails);
}; };
......
...@@ -324,7 +324,8 @@ TEST_F(BackgroundFetchDelegateProxyTest, UpdateUI) { ...@@ -324,7 +324,8 @@ TEST_F(BackgroundFetchDelegateProxyTest, UpdateUI) {
EXPECT_TRUE(controller.request_started_); EXPECT_TRUE(controller.request_started_);
EXPECT_TRUE(controller.request_completed_); EXPECT_TRUE(controller.request_completed_);
delegate_proxy_.UpdateUI(kExampleUniqueId, "Job 1 Complete!", base::nullopt); delegate_proxy_.UpdateUI(kExampleUniqueId, "Job 1 Complete!", base::nullopt,
base::DoNothing());
base::RunLoop().RunUntilIdle(); base::RunLoop().RunUntilIdle();
EXPECT_EQ(delegate_->ui_update_count_, 1); EXPECT_EQ(delegate_->ui_update_count_, 1);
} }
......
...@@ -199,7 +199,9 @@ void MockBackgroundFetchDelegate::MarkJobComplete( ...@@ -199,7 +199,9 @@ void MockBackgroundFetchDelegate::MarkJobComplete(
void MockBackgroundFetchDelegate::UpdateUI( void MockBackgroundFetchDelegate::UpdateUI(
const std::string& job_unique_id, const std::string& job_unique_id,
const base::Optional<std::string>& title, const base::Optional<std::string>& title,
const base::Optional<SkBitmap>& icon) {} const base::Optional<SkBitmap>& icon) {
job_id_to_client_map_[job_unique_id]->OnUIUpdated(job_unique_id);
}
void MockBackgroundFetchDelegate::RegisterResponse( void MockBackgroundFetchDelegate::RegisterResponse(
const GURL& url, const GURL& url,
......
...@@ -99,6 +99,9 @@ class CONTENT_EXPORT BackgroundFetchDelegate { ...@@ -99,6 +99,9 @@ class CONTENT_EXPORT BackgroundFetchDelegate {
// Called when the UI of a background fetch job is activated. // Called when the UI of a background fetch job is activated.
virtual void OnUIActivated(const std::string& job_unique_id) = 0; virtual void OnUIActivated(const std::string& job_unique_id) = 0;
// Called after the UI has been updated.
virtual void OnUIUpdated(const std::string& job_unique_id) = 0;
// Called by the Download Client when it needs the upload data for // Called by the Download Client when it needs the upload data for
// the given |download_guid|. // the given |download_guid|.
virtual void GetUploadData(const std::string& job_unique_id, virtual void GetUploadData(const std::string& job_unique_id,
......
...@@ -182,6 +182,11 @@ class WebTestBackgroundFetchDelegate::WebTestBackgroundFetchDownloadClient ...@@ -182,6 +182,11 @@ class WebTestBackgroundFetchDelegate::WebTestBackgroundFetchDownloadClient
std::move(callback).Run(std::move(request_body)); std::move(callback).Run(std::move(request_body));
} }
const base::WeakPtr<content::BackgroundFetchDelegate::Client>& client()
const {
return client_;
}
private: private:
base::WeakPtr<content::BackgroundFetchDelegate::Client> client_; base::WeakPtr<content::BackgroundFetchDelegate::Client> client_;
base::flat_map<std::string, std::string> guid_to_unique_job_id_mapping_; base::flat_map<std::string, std::string> guid_to_unique_job_id_mapping_;
...@@ -286,6 +291,8 @@ void WebTestBackgroundFetchDelegate::MarkJobComplete( ...@@ -286,6 +291,8 @@ void WebTestBackgroundFetchDelegate::MarkJobComplete(
void WebTestBackgroundFetchDelegate::UpdateUI( void WebTestBackgroundFetchDelegate::UpdateUI(
const std::string& job_unique_id, const std::string& job_unique_id,
const base::Optional<std::string>& title, const base::Optional<std::string>& title,
const base::Optional<SkBitmap>& icon) {} const base::Optional<SkBitmap>& icon) {
background_fetch_client_->client()->OnUIUpdated(job_unique_id);
}
} // namespace content } // namespace content
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