Index: chrome/browser/download/download_extension_api.cc |
diff --git a/chrome/browser/download/download_extension_api.cc b/chrome/browser/download/download_extension_api.cc |
index 3ad3a46d1b364bb8883b061228440497f137f98b..0f92f2bdc5ee9c6345fa84174b058689f4288ce2 100644 |
--- a/chrome/browser/download/download_extension_api.cc |
+++ b/chrome/browser/download/download_extension_api.cc |
@@ -860,7 +860,9 @@ void DownloadsGetFileIconFunction::OnIconURLExtracted(const std::string& url) { |
ExtensionDownloadsEventRouter::ExtensionDownloadsEventRouter(Profile* profile) |
: profile_(profile), |
- manager_(NULL) { |
+ manager_(NULL), |
+ delete_item_jsons_(&item_jsons_), |
+ delete_on_changed_stats_(&on_changed_stats_) { |
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
DCHECK(profile_); |
// Register a callback with the DownloadService for this profile to be called |
@@ -876,56 +878,149 @@ ExtensionDownloadsEventRouter::ExtensionDownloadsEventRouter(Profile* profile) |
void ExtensionDownloadsEventRouter::Init(DownloadManager* manager) { |
DCHECK(manager_ == NULL); |
manager_ = manager; |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
manager_->AddObserver(this); |
} |
ExtensionDownloadsEventRouter::~ExtensionDownloadsEventRouter() { |
if (manager_ != NULL) |
manager_->RemoveObserver(this); |
+ for (ItemMap::const_iterator iter = downloads_.begin(); |
+ iter != downloads_.end(); ++iter) { |
+ if (iter->second != NULL) |
+ iter->second->RemoveObserver(this); |
+ } |
+} |
+ |
+ExtensionDownloadsEventRouter::OnChangedStat::OnChangedStat() |
+ : fires(0), |
+ total(0) { |
+} |
+ |
+ExtensionDownloadsEventRouter::OnChangedStat::~OnChangedStat() { |
+ if (total > 0) |
+ UMA_HISTOGRAM_PERCENTAGE("Download.OnChanged", (fires * 100 / total)); |
+} |
+ |
+void ExtensionDownloadsEventRouter::OnDownloadUpdated(DownloadItem* item) { |
+ int download_id = item->GetId(); |
+ if (item->GetState() == DownloadItem::REMOVING) { |
+ // The REMOVING state indicates that this item is being erased. |
+ // Let's unregister as an observer so that we don't see any more updates |
+ // from it, dispatch the onErased event, and remove its json and is |
+ // OnChangedStat from our maps. |
+ downloads_.erase(download_id); |
+ item->RemoveObserver(this); |
+ DispatchEvent(extension_event_names::kOnDownloadErased, |
+ base::Value::CreateIntegerValue(download_id)); |
+ delete item_jsons_[download_id]; |
+ item_jsons_.erase(download_id); |
+ delete on_changed_stats_[download_id]; |
+ on_changed_stats_.erase(download_id); |
+ return; |
+ } |
+ |
+ base::DictionaryValue* old_json = item_jsons_[download_id]; |
+ scoped_ptr<base::DictionaryValue> new_json(DownloadItemToJSON(item)); |
+ scoped_ptr<base::DictionaryValue> delta(new base::DictionaryValue()); |
+ delta->SetInteger(kIdKey, download_id); |
+ std::set<std::string> new_fields; |
+ bool changed = false; |
+ |
+ // For each field in the new json representation of the item except the |
+ // bytesReceived field, if the field has changed from the previous old json, |
+ // set the differences in the |delta| object and remember that something |
+ // significant changed. |
+ for (base::DictionaryValue::Iterator iter(*new_json.get()); |
+ iter.HasNext(); iter.Advance()) { |
+ new_fields.insert(iter.key()); |
+ if (iter.key() != kBytesReceivedKey) { |
+ base::Value* old_value = NULL; |
+ if (!old_json->HasKey(iter.key()) || |
+ (old_json->Get(iter.key(), &old_value) && |
+ !iter.value().Equals(old_value))) { |
+ delta->Set(iter.key() + ".new", iter.value().DeepCopy()); |
+ if (old_value) |
+ delta->Set(iter.key() + ".old", old_value->DeepCopy()); |
+ changed = true; |
+ } |
+ } |
+ } |
+ |
+ // If a field was in the previous json but is not in the new json, set the |
+ // difference in |delta|. |
+ for (base::DictionaryValue::Iterator iter(*old_json); |
+ iter.HasNext(); iter.Advance()) { |
+ if (new_fields.find(iter.key()) == new_fields.end()) { |
+ delta->Set(iter.key() + ".old", iter.value().DeepCopy()); |
+ changed = true; |
+ } |
+ } |
+ |
+ // Update the OnChangedStat and dispatch the event if something significant |
+ // changed. Replace the stored json with the new json. |
+ ++(on_changed_stats_[download_id]->total); |
+ if (changed) { |
+ DispatchEvent(extension_event_names::kOnDownloadChanged, delta.release()); |
+ ++(on_changed_stats_[download_id]->fires); |
+ } |
+ item_jsons_[download_id]->Swap(new_json.get()); |
+} |
+ |
+void ExtensionDownloadsEventRouter::OnDownloadOpened(DownloadItem* item) { |
} |
void ExtensionDownloadsEventRouter::ModelChanged(DownloadManager* manager) { |
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
DCHECK(manager_ == manager); |
+ typedef std::set<int> DownloadIdSet; |
+ |
+ // Get all the download items. |
DownloadManager::DownloadVector current_vec; |
manager_->SearchDownloads(string16(), ¤t_vec); |
- DownloadIdSet current_set; |
+ |
+ // Populate set<>s of download item identifiers so that we can find |
+ // differences between the old and the new set of download items. |
+ DownloadIdSet current_set, prev_set; |
+ for (ItemMap::const_iterator iter = downloads_.begin(); |
+ iter != downloads_.end(); ++iter) { |
+ prev_set.insert(iter->first); |
+ } |
ItemMap current_map; |
for (DownloadManager::DownloadVector::const_iterator iter = |
current_vec.begin(); |
iter != current_vec.end(); ++iter) { |
DownloadItem* item = *iter; |
int item_id = item->GetId(); |
- // TODO(benjhayden): Remove the following line when every item's id >= 0, |
- // which will allow firing onErased events for items from the history. |
- if (item_id < 0) continue; |
+ CHECK(item_id >= 0); |
DCHECK(current_map.find(item_id) == current_map.end()); |
current_set.insert(item_id); |
current_map[item_id] = item; |
} |
- DownloadIdSet new_set; // current_set - downloads_; |
- DownloadIdSet erased_set; // downloads_ - current_set; |
- std::insert_iterator<DownloadIdSet> new_insertor(new_set, new_set.begin()); |
- std::insert_iterator<DownloadIdSet> erased_insertor( |
- erased_set, erased_set.begin()); |
+ DownloadIdSet new_set; // current_set - prev_set; |
std::set_difference(current_set.begin(), current_set.end(), |
- downloads_.begin(), downloads_.end(), |
- new_insertor); |
- std::set_difference(downloads_.begin(), downloads_.end(), |
- current_set.begin(), current_set.end(), |
- erased_insertor); |
+ prev_set.begin(), prev_set.end(), |
+ std::insert_iterator<DownloadIdSet>( |
+ new_set, new_set.begin())); |
+ |
+ // For each download that was not in the old set of downloads but is in the |
+ // new set of downloads, fire an onCreated event, register as an Observer of |
+ // the item, store a json representation of the item so that we can easily |
+ // find changes in that json representation, and make an OnChangedStat. |
for (DownloadIdSet::const_iterator iter = new_set.begin(); |
iter != new_set.end(); ++iter) { |
scoped_ptr<base::DictionaryValue> item( |
DownloadItemToJSON(current_map[*iter])); |
- DispatchEvent(extension_event_names::kOnDownloadCreated, item.release()); |
+ DispatchEvent(extension_event_names::kOnDownloadCreated, item->DeepCopy()); |
+ DCHECK(item_jsons_.find(*iter) == item_jsons_.end()); |
+ on_changed_stats_[*iter] = new OnChangedStat(); |
+ current_map[*iter]->AddObserver(this); |
+ item_jsons_[*iter] = item.release(); |
} |
- for (DownloadIdSet::const_iterator iter = erased_set.begin(); |
- iter != erased_set.end(); ++iter) { |
- DispatchEvent(extension_event_names::kOnDownloadErased, |
- base::Value::CreateIntegerValue(*iter)); |
- } |
- downloads_.swap(current_set); |
+ downloads_.swap(current_map); |
+ |
+ // Dispatching onErased is handled in OnDownloadUpdated when an item |
+ // transitions to the REMOVING state. |
} |
void ExtensionDownloadsEventRouter::ManagerGoingDown( |