Commit 84895c02 authored by Dan Harrington's avatar Dan Harrington Committed by Commit Bot

Track when the auto-fetch in progress notification should be shown

Watch for navigations and request coordinator changes to determine when
the in-progress notification should be shown.

A follow-up CL will introduce an instrumentation test to supplement the
unit tests here.

Bug: 883486

RELAND: This is a reland for https://chromium-review.googlesource.com/c/1381233
Changes to OfflinePageBridge were removed.

TBR=petewil@chromium.org
  This is the same patch, but without the base CL. The base CL was unintentially
  submitted previously.

Change-Id: I8e6988edcc0e8ca810f99c1961c9cdb9f730ba40
Reviewed-on: https://chromium-review.googlesource.com/c/1406939
Commit-Queue: Dan H <harringtond@google.com>
Reviewed-by: default avatarDan H <harringtond@google.com>
Cr-Commit-Position: refs/heads/master@{#622047}
parent 2e5acea4
...@@ -5,13 +5,20 @@ ...@@ -5,13 +5,20 @@
#include "chrome/browser/offline_pages/android/auto_fetch_page_load_watcher.h" #include "chrome/browser/offline_pages/android/auto_fetch_page_load_watcher.h"
#include <memory> #include <memory>
#include <utility>
#include "base/macros.h" #include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "chrome/browser/android/tab_android.h"
#include "chrome/browser/offline_pages/android/offline_page_auto_fetcher.h" #include "chrome/browser/offline_pages/android/offline_page_auto_fetcher.h"
#include "chrome/browser/offline_pages/android/offline_page_auto_fetcher_service.h" #include "chrome/browser/offline_pages/android/offline_page_auto_fetcher_service.h"
#include "chrome/browser/offline_pages/android/offline_page_auto_fetcher_service_factory.h" #include "chrome/browser/offline_pages/android/offline_page_auto_fetcher_service_factory.h"
#include "chrome/browser/offline_pages/request_coordinator_factory.h" #include "chrome/browser/offline_pages/request_coordinator_factory.h"
#include "chrome/browser/ui/android/tab_model/tab_model.h"
#include "chrome/browser/ui/android/tab_model/tab_model_list.h"
#include "components/offline_pages/core/auto_fetch.h"
#include "components/offline_pages/core/background/request_coordinator.h" #include "components/offline_pages/core/background/request_coordinator.h"
#include "components/offline_pages/core/background/save_page_request.h"
#include "components/offline_pages/core/client_namespace_constants.h" #include "components/offline_pages/core/client_namespace_constants.h"
#include "content/public/browser/navigation_handle.h" #include "content/public/browser/navigation_handle.h"
#include "content/public/browser/render_frame_host.h" #include "content/public/browser/render_frame_host.h"
...@@ -20,6 +27,48 @@ ...@@ -20,6 +27,48 @@
#include "content/public/browser/web_contents_user_data.h" #include "content/public/browser/web_contents_user_data.h"
namespace offline_pages { namespace offline_pages {
using auto_fetch_internal::AndroidTabFinder;
using auto_fetch_internal::InternalImpl;
using auto_fetch_internal::MakeRequestInfo;
using auto_fetch_internal::RequestInfo;
using auto_fetch_internal::TabInfo;
AndroidTabFinder::~AndroidTabFinder() = default;
TabInfo AnroidTabInfo(const TabAndroid& tab) {
return {tab.GetAndroidId(), tab.GetURL()};
}
std::map<int, TabInfo> AndroidTabFinder::FindAndroidTabs(
std::vector<int> android_tab_ids) {
std::map<int, TabInfo> result;
if (android_tab_ids.empty())
return result;
for (TabModelList::const_iterator i = TabModelList::begin();
i != TabModelList::end(); i++) {
TabModel* model = *i;
if (model->IsOffTheRecord())
continue;
for (int index = 0; index < model->GetTabCount(); ++index) {
TabAndroid* tab = model->GetTabAt(index);
if (std::find(android_tab_ids.begin(), android_tab_ids.end(),
tab->GetAndroidId()) != android_tab_ids.end()) {
result[tab->GetAndroidId()] = AnroidTabInfo(*tab);
}
}
}
return result;
}
base::Optional<TabInfo> AndroidTabFinder::FindNavigationTab(
content::WebContents* web_contents) {
TabAndroid* tab = TabAndroid::FromWebContents(web_contents);
if (!tab)
return base::nullopt;
return AnroidTabInfo(*tab);
}
// Observes a WebContents to relay navigation events to // Observes a WebContents to relay navigation events to
// AutoFetchPageLoadWatcher. // AutoFetchPageLoadWatcher.
...@@ -30,30 +79,26 @@ class AutoFetchPageLoadWatcher::NavigationObserver ...@@ -30,30 +79,26 @@ class AutoFetchPageLoadWatcher::NavigationObserver
public: public:
explicit NavigationObserver(content::WebContents* web_contents) explicit NavigationObserver(content::WebContents* web_contents)
: content::WebContentsObserver(web_contents) { : content::WebContentsObserver(web_contents) {
helper_ = OfflinePageAutoFetcherServiceFactory::GetForBrowserContext( page_load_watcher_ =
web_contents->GetBrowserContext()) OfflinePageAutoFetcherServiceFactory::GetForBrowserContext(
->page_load_watcher(); web_contents->GetBrowserContext())
DCHECK(helper_); ->page_load_watcher();
DCHECK(page_load_watcher_);
} }
// content::WebContentsObserver implementation. // content::WebContentsObserver implementation.
void DidFinishNavigation( void DidFinishNavigation(
content::NavigationHandle* navigation_handle) override { content::NavigationHandle* navigation_handle) override {
if (!navigation_handle->IsInMainFrame() || if (!navigation_handle->IsInMainFrame() ||
!navigation_handle->HasCommitted() || navigation_handle->IsErrorPage()) !navigation_handle->HasCommitted())
return; return;
page_load_watcher_->HandleNavigation(navigation_handle);
// Note: The redirect chain includes the final URL. We consider all URLs
// along the redirect chain as successful.
for (const auto& u : navigation_handle->GetRedirectChain()) {
helper_->HandlePageNavigation(u);
}
} }
private: private:
friend class content::WebContentsUserData< friend class content::WebContentsUserData<
AutoFetchPageLoadWatcher::NavigationObserver>; AutoFetchPageLoadWatcher::NavigationObserver>;
AutoFetchPageLoadWatcher* helper_; AutoFetchPageLoadWatcher* page_load_watcher_;
DISALLOW_COPY_AND_ASSIGN(NavigationObserver); DISALLOW_COPY_AND_ASSIGN(NavigationObserver);
WEB_CONTENTS_USER_DATA_KEY_DECL(); WEB_CONTENTS_USER_DATA_KEY_DECL();
...@@ -74,84 +119,253 @@ void AutoFetchPageLoadWatcher::CreateForWebContents( ...@@ -74,84 +119,253 @@ void AutoFetchPageLoadWatcher::CreateForWebContents(
} }
} }
AutoFetchPageLoadWatcher::AutoFetchPageLoadWatcher( namespace auto_fetch_internal {
RequestCoordinator* request_coordinator)
: request_coordinator_(request_coordinator) { base::Optional<RequestInfo> MakeRequestInfo(const SavePageRequest& request) {
request_coordinator_->AddObserver(this); base::Optional<auto_fetch::ClientIdMetadata> metadata =
request_coordinator_->GetAllRequests( auto_fetch::ExtractMetadata(request.client_id());
base::BindOnce(&AutoFetchPageLoadWatcher::ObserverInitialize, if (!metadata)
weak_ptr_factory_.GetWeakPtr())); return base::nullopt;
RequestInfo info;
info.request_id = request.request_id();
info.url = request.url();
info.metadata = metadata.value();
info.notification_state = request.auto_fetch_notification_state();
return info;
} }
AutoFetchPageLoadWatcher::~AutoFetchPageLoadWatcher() { InternalImpl::InternalImpl(AutoFetchNotifier* notifier,
request_coordinator_->RemoveObserver(this); Delegate* delegate,
std::unique_ptr<AndroidTabFinder> tab_finder)
: notifier_(notifier),
delegate_(delegate),
tab_finder_(std::move(tab_finder)) {}
InternalImpl::~InternalImpl() {}
void InternalImpl::RequestListInitialized(std::vector<RequestInfo> request) {
DCHECK(!requests_initialized_);
requests_initialized_ = true;
requests_ = std::move(request);
for (const GURL& url : pages_loaded_before_observer_ready_) {
SuccessfulPageNavigation(url);
}
pages_loaded_before_observer_ready_.clear();
// Now that we have the full list of requests, we need to verify that the
// notification state is correct. For instance, if a tab was closed or
// naviagated away from the request URL, we need to trigger the in-progress
// notification.
// For requests that haven't yet produced an in-progress notification, we need
// to find out if the request URL is currently bound to the expected tab. If
// not, trigger the in-progress notification.
std::vector<int> android_tab_ids;
for (const RequestInfo& request : requests_) {
if (request.notification_state ==
SavePageRequest::AutoFetchNotificationState::kUnknown) {
android_tab_ids.push_back(request.metadata.android_tab_id);
}
}
const std::map<int, TabInfo> android_tabs =
tab_finder_->FindAndroidTabs(android_tab_ids);
for (RequestInfo& request : requests_) {
if (request.notification_state ==
SavePageRequest::AutoFetchNotificationState::kUnknown) {
auto tab_iterator = android_tabs.find(request.metadata.android_tab_id);
if (tab_iterator == android_tabs.end() ||
tab_iterator->second.current_url != request.url) {
SetNotificationStateToShown(request.request_id);
}
}
}
} }
void AutoFetchPageLoadWatcher::OnAdded(const SavePageRequest& request) { void InternalImpl::RequestAdded(RequestInfo request) {
if (!observer_ready_) if (!requests_initialized_)
return; return;
if (request.client_id().name_space == kAutoAsyncNamespace) {
live_auto_fetch_requests_[request.url()].push_back(request.request_id()); requests_.push_back(request);
} // Because interaction with RequestCoordinator is asynchronous, we need to
// check if the request is no longer tied to a tab, and issue the in-progress
// notification.
if (request.notification_state ==
SavePageRequest::AutoFetchNotificationState::kShown)
return;
const std::map<int, TabInfo> android_tabs =
tab_finder_->FindAndroidTabs({request.metadata.android_tab_id});
if (android_tabs.empty())
delegate_->SetNotificationStateToShown(request.request_id);
// TODO(harringtond): it's also possible that the request should be removed
// because a successful navigation happened before the request could be added
// to the database. We might be able to catch this case by remembering some
// set of previous successful navigations along with timestamps, but even that
// isn't perfect.
// The upshot is that we risk auto-fetching a page and notifying the user even
// after they've already loaded it.
} }
void AutoFetchPageLoadWatcher::OnCompleted( void InternalImpl::RequestRemoved(RequestInfo request) {
const SavePageRequest& request, if (!requests_initialized_)
RequestNotifier::BackgroundSavePageResult status) {
if (!observer_ready_)
return; return;
// A SavePageRequest is complete, remove our bookeeping of it in
// |live_auto_fetch_requests_|. for (size_t i = 0; i < requests_.size(); ++i) {
auto iter = live_auto_fetch_requests_.find(request.url()); RequestInfo info = requests_[i];
if (iter != live_auto_fetch_requests_.end()) { if (info.request_id == request.request_id)
std::vector<int64_t>& id_list = iter->second; requests_.erase(requests_.begin() + i);
auto id_iter =
std::find(id_list.begin(), id_list.end(), request.request_id());
if (id_iter != id_list.end()) {
id_list.erase(id_iter);
if (id_list.empty())
live_auto_fetch_requests_.erase(iter);
}
} }
notifier_->InProgressCountChanged(requests_.size());
} }
void AutoFetchPageLoadWatcher::RemoveRequests( void InternalImpl::SetNotificationStateComplete(int64_t request_id,
const std::vector<int64_t>& request_ids) { bool success) {
request_coordinator_->RemoveRequests(request_ids, base::DoNothing()); if (!success)
return;
notifier_->NotifyInProgress(requests_.size());
} }
void AutoFetchPageLoadWatcher::HandlePageNavigation(const GURL& url) { // Called when a successful navigation to |url| happens.
// If URL is loaded successfully on tab, cancel the auto-fetch request.
void InternalImpl::SuccessfulPageNavigation(const GURL& url) {
// Early exit for the common-case. // Early exit for the common-case.
if (observer_ready_ && live_auto_fetch_requests_.empty()) { if (requests_initialized_ && requests_.empty())
return; return;
}
// Note: It is possible that this method is called before // If the request list isn't yet initialized, we have to defer handling of the
// ObserverInitialized, in which case we have to defer handling of the
// event. Never accumulate more than a few, so we can't have a boundless // event. Never accumulate more than a few, so we can't have a boundless
// array if ObserverInitialize is never called. // array. This means we will fail to cancel an auto-fetch request if too many
if (!observer_ready_) { // navigations occur before |RequestListInitialized|.
if (!requests_initialized_) {
if (pages_loaded_before_observer_ready_.size() < 10) if (pages_loaded_before_observer_ready_.size() < 10)
pages_loaded_before_observer_ready_.push_back(url); pages_loaded_before_observer_ready_.push_back(url);
return; return;
} }
auto iter = live_auto_fetch_requests_.find(url); std::vector<int64_t> remove_ids;
if (iter == live_auto_fetch_requests_.end()) for (const RequestInfo& request : requests_) {
if (request.url == url)
remove_ids.push_back(request.request_id);
}
if (!remove_ids.empty())
delegate_->RemoveRequests(remove_ids);
}
void InternalImpl::NavigationFrom(const GURL& previous_url,
content::WebContents* web_contents) {
// Early exit for the common-case. We can ignore events from before the
// request list is initialized because we reconcile things in
// |RequestListInitialized|.
if (!requests_initialized_ || requests_.empty())
return; return;
RemoveRequests(iter->second);
// Find requests that haven't yet been notified, and that match the
// navigated-from URL.
for (RequestInfo& request : requests_) {
if (request.url == previous_url &&
request.notification_state ==
SavePageRequest::AutoFetchNotificationState::kUnknown) {
// Check that the navigation is happening on the tab from which the
// request came.
base::Optional<TabInfo> tab =
tab_finder_->FindNavigationTab(web_contents);
if (tab && tab->android_tab_id == request.metadata.android_tab_id)
SetNotificationStateToShown(request.request_id);
}
}
} }
void AutoFetchPageLoadWatcher::ObserverInitialize( void InternalImpl::SetNotificationStateToShown(int64_t request_id) {
std::vector<std::unique_ptr<SavePageRequest>> all_requests) { const auto kShown = SavePageRequest::AutoFetchNotificationState::kShown;
observer_ready_ = true; for (RequestInfo& request : requests_) {
for (const std::unique_ptr<SavePageRequest>& request : all_requests) { if (request.request_id == request_id)
OnAdded(*request); request.notification_state = kShown;
} }
for (const GURL& url : pages_loaded_before_observer_ready_) { delegate_->SetNotificationStateToShown(request_id);
HandlePageNavigation(url); }
} // namespace auto_fetch_internal
AutoFetchPageLoadWatcher::AutoFetchPageLoadWatcher(
AutoFetchNotifier* notifier,
RequestCoordinator* request_coordinator,
std::unique_ptr<AndroidTabFinder> tab_finder)
: request_coordinator_(request_coordinator),
impl_(notifier, this, std::move(tab_finder)) {
request_coordinator_->AddObserver(this);
request_coordinator_->GetAllRequests(base::BindOnce(
&AutoFetchPageLoadWatcher::InitializeRequestList, GetWeakPtr()));
}
AutoFetchPageLoadWatcher::~AutoFetchPageLoadWatcher() {
request_coordinator_->RemoveObserver(this);
}
void AutoFetchPageLoadWatcher::RemoveRequests(
const std::vector<int64_t>& request_ids) {
request_coordinator_->RemoveRequests(request_ids, base::DoNothing());
}
void AutoFetchPageLoadWatcher::HandleNavigation(
content::NavigationHandle* navigation_handle) {
// First, call HandleSuccessfulPageNavigation() if this is a successful
// navigation.
if (!navigation_handle->IsErrorPage()) {
// Note: The redirect chain includes the final URL. We consider all URLs
// along the redirect chain as successful.
for (const auto& url : navigation_handle->GetRedirectChain()) {
impl_.SuccessfulPageNavigation(url);
}
} }
pages_loaded_before_observer_ready_.clear();
// Ignore if the URL didn't change.
const GURL& previous_url = navigation_handle->GetPreviousURL();
if (navigation_handle->GetURL() == previous_url)
return;
impl_.NavigationFrom(previous_url, navigation_handle->GetWebContents());
}
void AutoFetchPageLoadWatcher::SetNotificationStateToShown(int64_t request_id) {
request_coordinator_->SetAutoFetchNotificationState(
request_id, SavePageRequest::AutoFetchNotificationState::kShown,
base::BindOnce(&InternalImpl::SetNotificationStateComplete,
impl_.GetWeakPtr(), request_id));
}
void AutoFetchPageLoadWatcher::OnAdded(const SavePageRequest& request) {
base::Optional<RequestInfo> info = MakeRequestInfo(request);
if (!info)
return;
impl_.RequestAdded(std::move(info.value()));
}
void AutoFetchPageLoadWatcher::OnCompleted(
const SavePageRequest& request,
RequestNotifier::BackgroundSavePageResult status) {
base::Optional<RequestInfo> info = MakeRequestInfo(request);
if (!info)
return;
impl_.RequestRemoved(std::move(info.value()));
}
void AutoFetchPageLoadWatcher::InitializeRequestList(
std::vector<std::unique_ptr<SavePageRequest>> requests) {
std::vector<RequestInfo> request_infos;
for (const auto& request : requests) {
base::Optional<RequestInfo> info = MakeRequestInfo(*request);
if (!info)
continue;
request_infos.push_back(info.value());
}
impl_.RequestListInitialized(std::move(request_infos));
} }
} // namespace offline_pages } // namespace offline_pages
...@@ -12,60 +12,175 @@ ...@@ -12,60 +12,175 @@
#include "base/macros.h" #include "base/macros.h"
#include "base/memory/weak_ptr.h" #include "base/memory/weak_ptr.h"
#include "components/offline_pages/core/auto_fetch.h"
#include "components/offline_pages/core/background/request_coordinator.h" #include "components/offline_pages/core/background/request_coordinator.h"
#include "components/offline_pages/core/client_id.h" #include "components/offline_pages/core/client_id.h"
#include "url/gurl.h" #include "url/gurl.h"
namespace content { namespace content {
class WebContents; class WebContents;
class NavigationHandle;
} // namespace content } // namespace content
namespace offline_pages { namespace offline_pages {
class SavePageRequest; class SavePageRequest;
class RequestCoordinator; class RequestCoordinator;
// Cancels active auto fetch requests if a page loads. // Manages showing the in-progress notification.
class AutoFetchPageLoadWatcher : public RequestCoordinator::Observer { class AutoFetchNotifier {
public: public:
static void CreateForWebContents(content::WebContents* web_contents); virtual ~AutoFetchNotifier() {}
// Ensures that the in-progress notification is showing with the appropriate
// request count.
virtual void NotifyInProgress(int in_flight_count) {}
// Update the request count if the in-progress notification is already
// showing. This won't trigger showing the notification if it's not already
// shown. If |in_flight_count| is 0, the notification will be hidden.
virtual void InProgressCountChanged(int in_flight_count) {}
};
explicit AutoFetchPageLoadWatcher(RequestCoordinator* request_coordinator); // Types and functions internal to AutoFetchPageLoadWatcher. Included in the
~AutoFetchPageLoadWatcher() override; // header for testing.
namespace auto_fetch_internal {
// RequestCoordinator::Observer methods. These keep // Information about an Android browser tab.
// |live_auto_fetch_requests_| in sync with the state of RequestCoordinator. struct TabInfo {
void OnAdded(const SavePageRequest& request) override; int android_tab_id = 0;
void OnCompleted(const SavePageRequest& request, GURL current_url;
RequestNotifier::BackgroundSavePageResult status) override; };
void OnChanged(const SavePageRequest& request) override {}
void OnNetworkProgress(const SavePageRequest& request,
int64_t received_bytes) override {}
protected: // Interface to Android tabs used by |AutoFetchPageLoadWatcher|. This is the
class NavigationObserver; // real implementation, methods are virtual for testing only.
class AndroidTabFinder {
public:
virtual ~AndroidTabFinder();
// Returns a mapping of Android tab ID to TabInfo.
virtual std::map<int, TabInfo> FindAndroidTabs(
std::vector<int> android_tab_ids);
virtual base::Optional<TabInfo> FindNavigationTab(
content::WebContents* web_contents);
};
// Information about an auto-fetch |SavePageRequest|.
struct RequestInfo {
int64_t request_id;
GURL url;
SavePageRequest::AutoFetchNotificationState notification_state;
auto_fetch::ClientIdMetadata metadata;
};
base::Optional<RequestInfo> MakeRequestInfo(const SavePageRequest& request);
// |AutoFetchPageLoadWatcher|'s more unit-testable internal implementation.
// This class was designed to have few dependencies to make testing more
// tractable. Events are communicated to |InternalImpl| through its public
// member functions, and functions are injected through |AutoFetchNotifier|,
// |Delegate|, and |AndroidTabFinder|.
class InternalImpl {
public:
// Injected functions, implemented by |AutoFetchPageLoadWatcher|.
// We need this because we can't call these functions directly.
class Delegate {
public:
virtual ~Delegate() {}
// Sets the notification state of a request to
// |SavePageRequest::AutoFetchNotificationState::kShown|. Results in a call
// to |SetNotificationStateComplete|.
virtual void SetNotificationStateToShown(int64_t request_id) {}
// Removes all |SavePageRequest|s with the given IDs.
virtual void RemoveRequests(const std::vector<int64_t>& request_ids) {}
};
InternalImpl(AutoFetchNotifier* notifier,
Delegate* delegate,
std::unique_ptr<AndroidTabFinder> tab_finder);
~InternalImpl();
// Request state change methods.
void RequestListInitialized(std::vector<RequestInfo> requests);
void RequestAdded(RequestInfo info);
void RequestRemoved(RequestInfo info);
void SetNotificationStateComplete(int64_t request_id, bool success);
// Navigation methods.
void SuccessfulPageNavigation(const GURL& url);
void NavigationFrom(const GURL& previous_url,
content::WebContents* web_contents);
base::WeakPtr<InternalImpl> GetWeakPtr() {
return weak_ptr_factory_.GetWeakPtr();
}
private:
void SetNotificationStateToShown(int64_t request_id);
AutoFetchNotifier* notifier_;
Delegate* delegate_;
std::unique_ptr<AndroidTabFinder> tab_finder_;
std::vector<RequestInfo> requests_;
// Tracks whether |RequestListInitialized| has been called. If false,
// |RequestAdded| and |RequestRemoved| should be ignored, as per the
// documentation in |RequestCoordinator::Observer|.
bool requests_initialized_ = false;
std::vector<GURL> pages_loaded_before_observer_ready_;
// Virtual for testing only. base::WeakPtrFactory<InternalImpl> weak_ptr_factory_{this};
virtual void RemoveRequests(const std::vector<int64_t>& request_ids); };
// Called when navigation completes. Triggers cancellation of any } // namespace auto_fetch_internal
// in-flight auto fetch requests that match a successful navigation.
void HandlePageNavigation(const GURL& url); // Watches for page loads and |RequestCoordinator| requests.
// Given an active auto-fetch request for <tab, URL>:
// - If URL is loaded successfully on tab, cancel the auto-fetch request.
// - If a different URL is loaded successfully on tab, trigger the in-progress
// notification.
// - If an auto-fetch request is removed, update the in-progress notification's
// displayed request count.
//
// Implementation note:
// This class simply observes events and passes them down to |InternalImpl|
// for processing. All code here is run on the UI thread.
class AutoFetchPageLoadWatcher
: public RequestCoordinator::Observer,
public auto_fetch_internal::InternalImpl::Delegate {
public:
using AndroidTabFinder = auto_fetch_internal::AndroidTabFinder;
void ObserverInitialize( static void CreateForWebContents(content::WebContents* web_contents);
std::vector<std::unique_ptr<SavePageRequest>> all_requests);
offline_pages::RequestCoordinator* GetRequestCoordinator(); AutoFetchPageLoadWatcher(AutoFetchNotifier* notifier,
RequestCoordinator* request_coordinator,
std::unique_ptr<AndroidTabFinder> tab_finder);
// Whether ObserverInitialize has been called. ~AutoFetchPageLoadWatcher() override;
bool observer_ready_ = false;
RequestCoordinator* request_coordinator_; // Called when navigation completes, even on errors. This is only called
std::vector<GURL> pages_loaded_before_observer_ready_; // once per navigation.
void HandleNavigation(content::NavigationHandle* navigation_handle);
private:
class NavigationObserver;
base::WeakPtr<AutoFetchPageLoadWatcher> GetWeakPtr() {
return weak_ptr_factory_.GetWeakPtr();
}
// This represents the set of auto fetch request IDs currently in the void InitializeRequestList(
// RequestCoordinator's queue, mapped by URL. std::vector<std::unique_ptr<SavePageRequest>> requests);
std::map<GURL, std::vector<int64_t>> live_auto_fetch_requests_;
// InternalImpl::Delegate.
void SetNotificationStateToShown(int64_t request_id) override;
void RemoveRequests(const std::vector<int64_t>& request_ids) override;
// RequestCoordinator::Observer.
void OnAdded(const SavePageRequest& request) override;
void OnCompleted(const SavePageRequest& request,
RequestNotifier::BackgroundSavePageResult status) override;
void OnChanged(const SavePageRequest& request) override {}
void OnNetworkProgress(const SavePageRequest& request,
int64_t received_bytes) override {}
RequestCoordinator* request_coordinator_; // Not owned.
auto_fetch_internal::InternalImpl impl_;
base::WeakPtrFactory<AutoFetchPageLoadWatcher> weak_ptr_factory_{this}; base::WeakPtrFactory<AutoFetchPageLoadWatcher> weak_ptr_factory_{this};
DISALLOW_COPY_AND_ASSIGN(AutoFetchPageLoadWatcher); DISALLOW_COPY_AND_ASSIGN(AutoFetchPageLoadWatcher);
......
...@@ -8,253 +8,238 @@ ...@@ -8,253 +8,238 @@
#include <string> #include <string>
#include <utility> #include <utility>
#include "base/test/bind_test_util.h" #include "chrome/browser/offline_pages/android/offline_page_auto_fetcher_service.h"
#include "base/test/test_simple_task_runner.h" #include "chrome/browser/offline_pages/android/offline_page_auto_fetcher_service_factory.h"
#include "base/threading/thread_task_runner_handle.h"
#include "chrome/browser/offline_pages/request_coordinator_factory.h"
#include "chrome/test/base/chrome_render_view_host_test_harness.h"
#include "chrome/test/base/testing_profile.h"
#include "components/offline_pages/core/background/request_coordinator_stub_taco.h"
#include "components/offline_pages/core/client_namespace_constants.h" #include "components/offline_pages/core/client_namespace_constants.h"
#include "content/public/test/navigation_simulator.h" #include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
namespace offline_pages { namespace offline_pages {
namespace { namespace {
using content::NavigationSimulator; using ::offline_pages::auto_fetch_internal::AndroidTabFinder;
using ::offline_pages::auto_fetch_internal::InternalImpl;
using ::offline_pages::auto_fetch_internal::RequestInfo;
using ::offline_pages::auto_fetch_internal::TabInfo;
using ::testing::_;
const int kDefaultTabId = 123;
const base::Time kEpoch = base::Time::FromDoubleT(1.0e6);
GURL TestURL() { GURL TestURL() {
return GURL("http://www.url.com"); return GURL("http://www.url.com");
} }
GURL OtherURL() {
return GURL("http://other.com");
}
SavePageRequest TestRequest(int64_t id, const GURL& url = TestURL()) { RequestInfo TestInfo(
return SavePageRequest(id, url, int64_t id,
ClientId(kAutoAsyncNamespace, std::to_string(id)), const GURL& url = TestURL(),
base::Time::Now(), false); SavePageRequest::AutoFetchNotificationState notification_state =
SavePageRequest::AutoFetchNotificationState::kUnknown) {
RequestInfo info;
info.request_id = id;
info.url = url;
info.notification_state = notification_state;
info.metadata.android_tab_id = kDefaultTabId;
return info;
} }
// For access to protected methods. class MockAutoFetchNotifier : public AutoFetchNotifier {
class TestAutoFetchPageLoadWatcher : public AutoFetchPageLoadWatcher {
public: public:
explicit TestAutoFetchPageLoadWatcher(RequestCoordinator* request_coordinator) MOCK_METHOD1(NotifyInProgress, void(int in_flight_count));
: AutoFetchPageLoadWatcher(request_coordinator) {} MOCK_METHOD1(InProgressCountChanged, void(int in_flight_count));
void RemoveRequests(const std::vector<int64_t>& request_ids) override {
removed_ids_ = request_ids;
}
std::vector<int64_t> removed_ids() const { return removed_ids_; }
std::map<GURL, std::vector<int64_t>>* live_auto_fetch_requests() {
return &live_auto_fetch_requests_;
}
using AutoFetchPageLoadWatcher::HandlePageNavigation;
using AutoFetchPageLoadWatcher::ObserverInitialize;
private:
std::vector<int64_t> removed_ids_;
}; };
// Tests AutoFetchPageLoadWatcher in a realistic way by simulating navigations. class FakeInternalImplDelegate : public InternalImpl::Delegate {
class AutoFetchPageLoadWatcherNavigationTest
: public ChromeRenderViewHostTestHarness {
public: public:
AutoFetchPageLoadWatcherNavigationTest() = default; ~FakeInternalImplDelegate() override {}
~AutoFetchPageLoadWatcherNavigationTest() override = default; void SetNotificationStateToShown(int64_t request_id) override {
set_notification_state_requests.push_back(request_id);
void SetUp() override {
ChromeRenderViewHostTestHarness::SetUp();
RequestCoordinatorFactory::GetInstance()->SetTestingFactoryAndUse(
profile(), request_coordinator_taco_.FactoryFunction());
AutoFetchPageLoadWatcher::CreateForWebContents(web_contents());
} }
void RemoveRequests(const std::vector<int64_t>& request_ids) override {
std::unique_ptr<NavigationSimulator> CreateNavigation(const GURL& url) { removed_requests.insert(removed_requests.end(), request_ids.begin(),
return NavigationSimulator::CreateRendererInitiated(url, main_rfh()); request_ids.end());
} }
RequestCoordinator* request_coordinator() { std::vector<int64_t> removed_requests;
return request_coordinator_taco_.request_coordinator(); std::vector<int64_t> set_notification_state_requests;
} };
void AddAutoSavePageRequest() { // Note that TabAndroid doesn't work in unit tests, so this stubs out access to
RequestCoordinator::SavePageLaterParams params; // tab information.
params.url = TestURL(); class StubTabFinder : public AutoFetchPageLoadWatcher::AndroidTabFinder {
params.client_id = ClientId(kAutoAsyncNamespace, "request1"); public:
request_coordinator()->SavePageLater(params, base::DoNothing()); ~StubTabFinder() override {}
// AutoFetchPageLoadWatcher::AndroidTabFinder.
std::map<int, TabInfo> FindAndroidTabs(
std::vector<int> android_tab_ids) override {
std::map<int, TabInfo> result;
for (const int tab_id : android_tab_ids) {
if (tabs_.count(tab_id)) {
result[tab_id] = TabInfo{tab_id, tabs_[tab_id]};
}
}
return result;
} }
std::vector<GURL> RequestsInQueue() { base::Optional<TabInfo> FindNavigationTab(
std::vector<GURL> request_urls; content::WebContents* web_contents) override {
request_coordinator()->GetAllRequests(base::BindLambdaForTesting( if (!tabs_.count(current_tab_id_))
[&](std::vector<std::unique_ptr<SavePageRequest>> requests) { return base::nullopt;
for (const auto& request : requests) { return TabInfo{current_tab_id_, tabs_[current_tab_id_]};
request_urls.push_back(request->url());
}
}));
RunUntilIdle();
return request_urls;
} }
void RunUntilIdle() { thread_bundle()->RunUntilIdle(); } // Methods to alter stub behavior.
void SetTabs(std::map<int, GURL> tab_urls) { tabs_ = std::move(tab_urls); }
void SetCurrentTabId(int tab_id) { current_tab_id_ = tab_id; }
private: private:
RequestCoordinatorStubTaco request_coordinator_taco_; std::map<int, GURL> tabs_;
// ID of the current tab to return from FindNavigationTab.
int current_tab_id_ = kDefaultTabId;
};
DISALLOW_COPY_AND_ASSIGN(AutoFetchPageLoadWatcherNavigationTest); // Note: This unittest doesn't attempt to directly test
// |AutoFetchPageLoadWatcher| because the set-up is difficult, especially for
// testing various call orderings. Additionally, TabAndroid can't be tested
// in a unit test. Instead, see OfflinePageAutoFetchTest.java for coverage.
class AutoFetchInternalImplTest : public testing::Test {
public:
protected:
// A WebContents* is needed for some |InternalImpl| methods, but nullptr is
// sufficient because |StubTabFinder| doesn't inspect the value.
content::WebContents* const web_contents_ = nullptr;
MockAutoFetchNotifier notifier_;
FakeInternalImplDelegate delegate_;
StubTabFinder* tab_finder_ = new StubTabFinder;
InternalImpl impl_{&notifier_, &delegate_, base::WrapUnique(tab_finder_)};
}; };
// Navigation results in an error page, and has no effect on TEST_F(AutoFetchInternalImplTest, NoInitialization) {
// AutoFetchPageLoadWatcher. // Just verify there is no crash.
TEST_F(AutoFetchPageLoadWatcherNavigationTest, NavigateToErrorPage) { impl_.SuccessfulPageNavigation(TestURL());
AddAutoSavePageRequest(); impl_.NavigationFrom(TestURL(), web_contents_);
RunUntilIdle(); }
std::unique_ptr<NavigationSimulator> simulator = CreateNavigation(TestURL()); TEST_F(AutoFetchInternalImplTest, RemoveRequestOnSuccessfulNavigation) {
simulator->Start(); tab_finder_->SetTabs(std::map<int, GURL>{{kDefaultTabId, TestURL()}});
simulator->Fail(net::ERR_TIMED_OUT); impl_.RequestListInitialized(
simulator->CommitErrorPage(); std::vector<RequestInfo>{TestInfo(1, TestURL())});
impl_.SuccessfulPageNavigation(TestURL());
std::vector<GURL> expected_requests{TestURL()}; EXPECT_EQ(std::vector<int64_t>({1}), delegate_.removed_requests);
EXPECT_EQ(expected_requests, RequestsInQueue());
} }
// Successful navigation results in cancellation of request. TEST_F(AutoFetchInternalImplTest,
TEST_F(AutoFetchPageLoadWatcherNavigationTest, NavigateAndCancel) { RemoveRequestOnSuccessfulNavigationBeforeInitialization) {
AddAutoSavePageRequest(); tab_finder_->SetTabs(std::map<int, GURL>{{kDefaultTabId, TestURL()}});
RunUntilIdle(); impl_.SuccessfulPageNavigation(TestURL());
impl_.RequestListInitialized(
std::unique_ptr<NavigationSimulator> simulator = CreateNavigation(TestURL()); std::vector<RequestInfo>{TestInfo(1, TestURL())});
simulator->Start();
simulator->Commit();
std::vector<GURL> expected_requests{}; EXPECT_EQ(std::vector<int64_t>({1}), delegate_.removed_requests);
EXPECT_EQ(expected_requests, RequestsInQueue());
} }
TEST_F(AutoFetchPageLoadWatcherNavigationTest, NavigateToDifferentURL) { // Successful navigation to a URL that we are not auto fetching should not
AddAutoSavePageRequest(); // remove any requests from the list of in progress auto fetches.
RunUntilIdle(); TEST_F(AutoFetchInternalImplTest, SuccessfulNavigationToOtherURL) {
tab_finder_->SetTabs(std::map<int, GURL>{{kDefaultTabId, TestURL()}});
impl_.RequestListInitialized(
std::vector<RequestInfo>{TestInfo(1, TestURL())});
impl_.SuccessfulPageNavigation(OtherURL());
std::unique_ptr<NavigationSimulator> simulator = EXPECT_EQ(std::vector<int64_t>(), delegate_.removed_requests);
CreateNavigation(GURL("http://www.different.com"));
simulator->Start();
simulator->Commit();
std::vector<GURL> expected_requests{TestURL()};
EXPECT_EQ(expected_requests, RequestsInQueue());
} }
TEST_F(AutoFetchPageLoadWatcherNavigationTest, RedirectToAndCancel) { TEST_F(AutoFetchInternalImplTest,
AddAutoSavePageRequest(); SuccessfulNavigationToOtherURLBeforeInitialization) {
RunUntilIdle(); tab_finder_->SetTabs(std::map<int, GURL>{{kDefaultTabId, TestURL()}});
impl_.SuccessfulPageNavigation(OtherURL());
std::unique_ptr<NavigationSimulator> simulator = impl_.RequestListInitialized(
CreateNavigation(GURL("http://different.com")); std::vector<RequestInfo>{TestInfo(1, TestURL())});
simulator->Start();
simulator->Redirect(TestURL());
simulator->Commit();
std::vector<GURL> expected_requests{}; EXPECT_EQ(std::vector<int64_t>(), delegate_.removed_requests);
EXPECT_EQ(expected_requests, RequestsInQueue());
} }
TEST_F(AutoFetchPageLoadWatcherNavigationTest, RedirectFromAndCancel) { TEST_F(AutoFetchInternalImplTest, NavigatingFromNotifies) {
AddAutoSavePageRequest(); tab_finder_->SetTabs(std::map<int, GURL>{{kDefaultTabId, TestURL()}});
RunUntilIdle(); impl_.RequestListInitialized(
std::vector<RequestInfo>{TestInfo(1, TestURL())});
std::unique_ptr<NavigationSimulator> simulator = CreateNavigation(TestURL()); tab_finder_->SetTabs(std::map<int, GURL>{{kDefaultTabId, OtherURL()}});
impl_.NavigationFrom(TestURL(), web_contents_);
EXPECT_EQ(std::vector<int64_t>({1}),
delegate_.set_notification_state_requests);
simulator->Start(); EXPECT_CALL(notifier_, NotifyInProgress(1));
simulator->Redirect(GURL("http://different.com")); impl_.SetNotificationStateComplete(1, true);
simulator->Commit();
std::vector<GURL> expected_requests{};
EXPECT_EQ(expected_requests, RequestsInQueue());
} }
// Tests some details of AutoFetchPageLoadWatcher TEST_F(AutoFetchInternalImplTest, CompletedRequestUpdatesInProgressCount) {
class AutoFetchPageLoadWatcherTest : public testing::Test { tab_finder_->SetTabs(std::map<int, GURL>{{kDefaultTabId, TestURL()}});
public: impl_.RequestListInitialized(
AutoFetchPageLoadWatcherTest() {} std::vector<RequestInfo>{TestInfo(1, TestURL())});
~AutoFetchPageLoadWatcherTest() override {} tab_finder_->SetTabs(std::map<int, GURL>{{kDefaultTabId, OtherURL()}});
impl_.NavigationFrom(TestURL(), web_contents_);
void SetUp() override {
testing::Test::SetUp();
taco_.CreateRequestCoordinator();
}
RequestCoordinator* request_coordinator() { EXPECT_CALL(notifier_, InProgressCountChanged(0));
return taco_.request_coordinator(); impl_.RequestRemoved(TestInfo(1, TestURL()));
} }
private: TEST_F(AutoFetchInternalImplTest, NavigatingFromNotifiesTwoRequests) {
scoped_refptr<base::TestSimpleTaskRunner> task_runner_ = tab_finder_->SetTabs(std::map<int, GURL>{{kDefaultTabId, TestURL()}});
base::MakeRefCounted<base::TestSimpleTaskRunner>(); impl_.RequestListInitialized(
base::ThreadTaskRunnerHandle handle_{task_runner_}; {TestInfo(1, TestURL()), TestInfo(2, TestURL())});
RequestCoordinatorStubTaco taco_; tab_finder_->SetTabs(std::map<int, GURL>{{kDefaultTabId, OtherURL()}});
}; impl_.NavigationFrom(TestURL(), web_contents_);
EXPECT_EQ(std::vector<int64_t>({1, 2}),
delegate_.set_notification_state_requests);
// Simulate navigation to a URL prior to ObserverInitialize. Verify the EXPECT_CALL(notifier_, NotifyInProgress(2)).Times(2);
// RemoveRequests is called. impl_.SetNotificationStateComplete(1, true);
TEST_F(AutoFetchPageLoadWatcherTest, NavigateBeforeObserverInitialize) { impl_.SetNotificationStateComplete(2, true);
TestAutoFetchPageLoadWatcher tab_helper(request_coordinator());
tab_helper.HandlePageNavigation(TestURL());
std::vector<std::unique_ptr<SavePageRequest>> all_requests;
all_requests.push_back(std::make_unique<SavePageRequest>(TestRequest(1)));
all_requests.push_back(std::make_unique<SavePageRequest>(
TestRequest(2, GURL("http://different.com"))));
tab_helper.ObserverInitialize(std::move(all_requests));
std::vector<int64_t> expected_requests{1};
EXPECT_EQ(expected_requests, tab_helper.removed_ids());
} }
TEST_F(AutoFetchPageLoadWatcherTest, OnCompletedNoRequest) { TEST_F(AutoFetchInternalImplTest, NavigatingFromBeforeInitialization) {
TestAutoFetchPageLoadWatcher tab_helper(request_coordinator()); tab_finder_->SetTabs(std::map<int, GURL>{{kDefaultTabId, OtherURL()}});
tab_helper.ObserverInitialize({}); impl_.RequestListInitialized(
{TestInfo(1, TestURL()), TestInfo(2, TestURL())});
tab_helper.OnCompleted(TestRequest(1), EXPECT_EQ(std::vector<int64_t>({1, 2}),
RequestNotifier::BackgroundSavePageResult()); delegate_.set_notification_state_requests);
// Nothing happens, just verify there is no crash. EXPECT_CALL(notifier_, NotifyInProgress(2)).Times(2);
SUCCEED(); impl_.SetNotificationStateComplete(1, true);
impl_.SetNotificationStateComplete(2, true);
} }
TEST_F(AutoFetchPageLoadWatcherTest, OnCompletedOneRequestWithURL) { TEST_F(AutoFetchInternalImplTest, TabCloseNotifies) {
TestAutoFetchPageLoadWatcher tab_helper(request_coordinator()); tab_finder_->SetTabs(std::map<int, GURL>{{kDefaultTabId + 1, TestURL()}});
tab_helper.ObserverInitialize({}); impl_.RequestListInitialized(
std::map<GURL, std::vector<int64_t>>* requests = std::vector<RequestInfo>{TestInfo(1, TestURL())});
tab_helper.live_auto_fetch_requests();
tab_helper.OnAdded(TestRequest(1));
ASSERT_EQ(1ul, requests->count(TestURL()));
tab_helper.OnCompleted(TestRequest(1), EXPECT_EQ(std::vector<int64_t>({1}),
RequestNotifier::BackgroundSavePageResult()); delegate_.set_notification_state_requests);
EXPECT_EQ(0ul, requests->count(TestURL())); EXPECT_CALL(notifier_, NotifyInProgress(1)).Times(1);
impl_.SetNotificationStateComplete(1, true);
} }
// Verify multiple requests with the same URL are handled as expected. TEST_F(AutoFetchInternalImplTest, RequestRemovedWhileSettingNotificationState) {
TEST_F(AutoFetchPageLoadWatcherTest, OnCompletedMultipleRequestsWithURL) { tab_finder_->SetTabs(std::map<int, GURL>{{kDefaultTabId, OtherURL()}});
TestAutoFetchPageLoadWatcher tab_helper(request_coordinator()); impl_.RequestListInitialized(
tab_helper.ObserverInitialize({}); std::vector<RequestInfo>{TestInfo(1, TestURL())});
// Three requests with the same URL. EXPECT_EQ(std::vector<int64_t>({1}),
tab_helper.OnAdded(TestRequest(1)); delegate_.set_notification_state_requests);
tab_helper.OnAdded(TestRequest(2));
tab_helper.OnAdded(TestRequest(3));
// Only one is completed. impl_.RequestRemoved({TestInfo(1, TestURL())});
tab_helper.OnCompleted(TestRequest(2),
RequestNotifier::BackgroundSavePageResult());
std::vector<int64_t> expected_requests = {1, 3}; EXPECT_CALL(notifier_, NotifyInProgress(1)).Times(0);
EXPECT_EQ(expected_requests, impl_.SetNotificationStateComplete(1, false);
(*tab_helper.live_auto_fetch_requests())[TestURL()]);
} }
} // namespace } // namespace
......
...@@ -7,12 +7,9 @@ ...@@ -7,12 +7,9 @@
#include <string> #include <string>
#include <utility> #include <utility>
#include "base/android/jni_android.h"
#include "base/android/jni_string.h"
#include "base/optional.h" #include "base/optional.h"
#include "base/strings/strcat.h" #include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h" #include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h" #include "base/time/time.h"
#include "chrome/browser/offline_pages/request_coordinator_factory.h" #include "chrome/browser/offline_pages/request_coordinator_factory.h"
#include "components/offline_pages/core/auto_fetch.h" #include "components/offline_pages/core/auto_fetch.h"
...@@ -77,7 +74,11 @@ OfflinePageAutoFetcherService::OfflinePageAutoFetcherService( ...@@ -77,7 +74,11 @@ OfflinePageAutoFetcherService::OfflinePageAutoFetcherService(
RequestCoordinator* request_coordinator, RequestCoordinator* request_coordinator,
OfflinePageModel* offline_page_model, OfflinePageModel* offline_page_model,
Delegate* delegate) Delegate* delegate)
: page_load_watcher_(request_coordinator), : notifier_(std::make_unique<AutoFetchNotifier>()),
page_load_watcher_(
notifier_.get(),
request_coordinator,
std::make_unique<AutoFetchPageLoadWatcher::AndroidTabFinder>()),
request_coordinator_(request_coordinator), request_coordinator_(request_coordinator),
offline_page_model_(offline_page_model), offline_page_model_(offline_page_model),
delegate_(delegate) { delegate_(delegate) {
...@@ -159,6 +160,7 @@ void OfflinePageAutoFetcherService::TryScheduleStep2( ...@@ -159,6 +160,7 @@ void OfflinePageAutoFetcherService::TryScheduleStep2(
return; return;
} }
} }
// Finally, schedule a new request, and proceed to step 3. // Finally, schedule a new request, and proceed to step 3.
RequestCoordinator::SavePageLaterParams params; RequestCoordinator::SavePageLaterParams params;
params.url = url; params.url = url;
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
#define CHROME_BROWSER_OFFLINE_PAGES_ANDROID_OFFLINE_PAGE_AUTO_FETCHER_SERVICE_H_ #define CHROME_BROWSER_OFFLINE_PAGES_ANDROID_OFFLINE_PAGE_AUTO_FETCHER_SERVICE_H_
#include <memory> #include <memory>
#include <queue>
#include <utility> #include <utility>
#include <vector> #include <vector>
...@@ -146,6 +147,7 @@ class OfflinePageAutoFetcherService : public KeyedService, ...@@ -146,6 +147,7 @@ class OfflinePageAutoFetcherService : public KeyedService,
void AutoFetchComplete(const OfflinePageItem* page); void AutoFetchComplete(const OfflinePageItem* page);
std::unique_ptr<AutoFetchNotifier> notifier_;
AutoFetchPageLoadWatcher page_load_watcher_; AutoFetchPageLoadWatcher page_load_watcher_;
RequestCoordinator* request_coordinator_; RequestCoordinator* request_coordinator_;
OfflinePageModel* offline_page_model_; OfflinePageModel* offline_page_model_;
......
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