Index: chrome/browser/engagement/site_engagement_service.cc |
diff --git a/chrome/browser/engagement/site_engagement_service.cc b/chrome/browser/engagement/site_engagement_service.cc |
index fa8f699fa73d44699dceb25ff8fd40be2f399503..80671f721c91d8679450f2583f87d8744738e2fd 100644 |
--- a/chrome/browser/engagement/site_engagement_service.cc |
+++ b/chrome/browser/engagement/site_engagement_service.cc |
@@ -139,6 +139,10 @@ SiteEngagementService::~SiteEngagementService() { |
profile_, ServiceAccessType::IMPLICIT_ACCESS); |
if (history) |
history->RemoveObserver(this); |
+ |
+ // Clean up any callbacks which haven't been called. |
+ for (const auto& wrapper : engagement_callbacks_) |
+ delete wrapper.callback; |
} |
SiteEngagementService::EngagementLevel |
@@ -209,6 +213,33 @@ bool SiteEngagementService::IsEngagementAtLeast( |
return false; |
} |
+void SiteEngagementService::RegisterCallback( |
+ const EngagementCallback& callback, |
+ const GURL& url, |
+ EngagementLevel level) { |
+ RegisterCallback(callback, url, GetScoreForEngagementLevel(level)); |
+} |
+ |
+void SiteEngagementService::RegisterCallback( |
+ const EngagementCallback& callback, |
+ const GURL& url, |
+ double score) { |
+ // We copy the callback so that when it is invoked, we can immediately delete |
+ // the wrapper item in engagement_callbacks_ and ensure it cannot be called |
+ // twice. If the callback is run, the copy is deleted as soon as its execution |
+ // finishes in RunAndDeleteCallback. Otherwise, the copy is cleaned up in the |
+ // destructor. |
+ EngagementCallback* callback_ptr = new EngagementCallback(callback); |
+ double current_score = GetScore(url); |
+ if (current_score >= score) { |
+ // Dispatch the callback immediately if its score is already at or above the |
+ // requested level. |
+ PostCallback(callback_ptr, url.GetOrigin(), current_score); |
+ } else { |
calamity
2016/05/19 03:28:22
nit: replace with early return.
dominickn
2016/05/19 04:36:14
Done.
|
+ engagement_callbacks_.push_back({callback_ptr, url.GetOrigin(), score}); |
+ } |
+} |
+ |
void SiteEngagementService::ResetScoreForURL(const GURL& url, double score) { |
ResetScoreAndAccessTimesForURL(url, score, nullptr); |
} |
@@ -261,7 +292,10 @@ double SiteEngagementService::GetTotalEngagementPoints() const { |
SiteEngagementService::SiteEngagementService(Profile* profile, |
std::unique_ptr<base::Clock> clock) |
- : profile_(profile), clock_(std::move(clock)), weak_factory_(this) { |
+ : profile_(profile), |
+ clock_(std::move(clock)), |
+ engagement_callbacks_(), |
calamity
2016/05/19 03:28:22
vectors should initialize fine without being in th
dominickn
2016/05/19 04:36:14
Done.
|
+ weak_factory_(this) { |
// May be null in tests. |
history::HistoryService* history = HistoryServiceFactory::GetForProfile( |
profile, ServiceAccessType::IMPLICIT_ACCESS); |
@@ -282,6 +316,7 @@ void SiteEngagementService::AddPoints(const GURL& url, double points) { |
url, GURL(), CONTENT_SETTINGS_TYPE_SITE_ENGAGEMENT, std::string(), |
score_dict.release()); |
} |
+ ProcessCallbacks(url, score.GetScore()); |
calamity
2016/05/19 03:28:22
Does this also need to be run when scores are rese
dominickn
2016/05/19 04:36:14
I explicitly decided to not do that: this runs onl
|
} |
void SiteEngagementService::AfterStartupTask() { |
@@ -365,6 +400,25 @@ double SiteEngagementService::GetMedianEngagement( |
return (scores[mid - 1] + scores[mid]) / 2; |
} |
+double SiteEngagementService::GetScoreForEngagementLevel( |
+ EngagementLevel level) const { |
+ switch (level) { |
+ case ENGAGEMENT_LEVEL_NONE: |
+ return 0; |
+ case ENGAGEMENT_LEVEL_LOW: |
+ return SiteEngagementScore::GetMinimumEngagementIncrement(); |
+ case ENGAGEMENT_LEVEL_MEDIUM: |
+ return SiteEngagementScore::GetMediumEngagementBoundary(); |
+ case ENGAGEMENT_LEVEL_HIGH: |
+ return SiteEngagementScore::GetHighEngagementBoundary(); |
+ case ENGAGEMENT_LEVEL_MAX: |
+ return SiteEngagementScore::kMaxPoints; |
+ default: |
+ NOTREACHED(); |
+ return 0; |
+ } |
+} |
+ |
void SiteEngagementService::HandleMediaPlaying(const GURL& url, |
bool is_hidden) { |
SiteEngagementMetrics::RecordEngagement( |
@@ -503,6 +557,32 @@ void SiteEngagementService::GetCountsAndLastVisitForOriginsComplete( |
} |
} |
+void SiteEngagementService::ProcessCallbacks(const GURL& url, double score) { |
+ GURL origin = url.GetOrigin(); |
+ for (auto it = engagement_callbacks_.begin(); |
+ it != engagement_callbacks_.end();) { |
+ if (origin == it->origin && score >= it->score) { |
+ // Found a match. Post the callback to be run on the UI thread and |
+ // immediately erase the wrapper object so we don't call it twice if the |
+ // score increments again. The callback object will be deleted in |
+ // RunAndDeleteCallback. |
+ PostCallback(it->callback, url, score); |
calamity
2016/05/19 03:28:22
Why do we post this rather than running immediatel
dominickn
2016/05/19 04:36:14
This is potentially run on user input (like scroll
|
+ it = engagement_callbacks_.erase(it); |
+ } else { |
+ ++it; |
+ } |
+ } |
+} |
+ |
+void SiteEngagementService::PostCallback(const EngagementCallback* callback, |
+ const GURL& url, |
+ double score) { |
calamity
2016/05/19 03:28:22
nit: alignment
dominickn
2016/05/19 04:36:15
Done.
|
+ content::BrowserThread::PostTask( |
+ content::BrowserThread::UI, FROM_HERE, |
+ base::Bind(&SiteEngagementService::RunAndDeleteCallback, |
+ weak_factory_.GetWeakPtr(), callback, url, score)); |
+} |
+ |
void SiteEngagementService::ResetScoreAndAccessTimesForURL( |
const GURL& url, double score, const base::Time* updated_time) { |
DCHECK(url.is_valid()); |
@@ -529,3 +609,12 @@ void SiteEngagementService::ResetScoreAndAccessTimesForURL( |
score_dict.release()); |
} |
} |
+ |
+void SiteEngagementService::RunAndDeleteCallback( |
calamity
2016/05/19 03:28:22
This can be in the anonymous namespace. Doesn't ne
dominickn
2016/05/19 04:36:15
Done.
|
+ const EngagementCallback* callback, |
+ const GURL& origin, |
+ double score) const { |
+ callback->Run(origin, score); |
+ delete callback; |
+ |
+} |