1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/browser/bitmap_fetcher/bitmap_fetcher_service.h"
6
7#include "base/memory/weak_ptr.h"
8#include "chrome/browser/bitmap_fetcher/bitmap_fetcher.h"
9#include "chrome/browser/profiles/profile.h"
10#include "net/base/load_flags.h"
11#include "third_party/skia/include/core/SkBitmap.h"
12
13namespace {
14
15const size_t kMaxRequests = 25;  // Maximum number of inflight requests allowed.
16const int kMaxCacheEntries = 5;  // Maximum number of cache entries.
17
18}  // namespace.
19
20class BitmapFetcherRequest {
21 public:
22  BitmapFetcherRequest(BitmapFetcherService::RequestId request_id,
23                       BitmapFetcherService::Observer* observer);
24  ~BitmapFetcherRequest();
25
26  void NotifyImageChanged(const SkBitmap& bitmap);
27  BitmapFetcherService::RequestId request_id() const { return request_id_; }
28
29  // Weak ptr |fetcher| is used to identify associated fetchers.
30  void set_fetcher(const chrome::BitmapFetcher* fetcher) { fetcher_ = fetcher; }
31  const chrome::BitmapFetcher* get_fetcher() const { return fetcher_; }
32
33 private:
34  const BitmapFetcherService::RequestId request_id_;
35  scoped_ptr<BitmapFetcherService::Observer> observer_;
36  const chrome::BitmapFetcher* fetcher_;
37
38  DISALLOW_COPY_AND_ASSIGN(BitmapFetcherRequest);
39};
40
41BitmapFetcherRequest::BitmapFetcherRequest(
42    BitmapFetcherService::RequestId request_id,
43    BitmapFetcherService::Observer* observer)
44    : request_id_(request_id), observer_(observer) {
45}
46
47BitmapFetcherRequest::~BitmapFetcherRequest() {
48}
49
50void BitmapFetcherRequest::NotifyImageChanged(const SkBitmap& bitmap) {
51  if (!bitmap.empty())
52    observer_->OnImageChanged(request_id_, bitmap);
53}
54
55BitmapFetcherService::CacheEntry::CacheEntry() {
56}
57
58BitmapFetcherService::CacheEntry::~CacheEntry() {
59}
60
61BitmapFetcherService::BitmapFetcherService(content::BrowserContext* context)
62    : cache_(kMaxCacheEntries), current_request_id_(1), context_(context) {
63}
64
65BitmapFetcherService::~BitmapFetcherService() {
66}
67
68void BitmapFetcherService::CancelRequest(int request_id) {
69  ScopedVector<BitmapFetcherRequest>::iterator iter;
70  for (iter = requests_.begin(); iter != requests_.end(); ++iter) {
71    if ((*iter)->request_id() == request_id) {
72      requests_.erase(iter);
73      // Deliberately leave the associated fetcher running to populate cache.
74      return;
75    }
76  }
77}
78
79BitmapFetcherService::RequestId BitmapFetcherService::RequestImage(
80    const GURL& url,
81    Observer* observer) {
82  // Create a new request, assigning next available request ID.
83  ++current_request_id_;
84  if (current_request_id_ == REQUEST_ID_INVALID)
85    ++current_request_id_;
86  int request_id = current_request_id_;
87  scoped_ptr<BitmapFetcherRequest> request(
88      new BitmapFetcherRequest(request_id, observer));
89
90  // Reject invalid URLs.
91  if (!url.is_valid())
92    return REQUEST_ID_INVALID;
93
94  // Check for existing images first.
95  base::OwningMRUCache<GURL, CacheEntry*>::iterator iter = cache_.Get(url);
96  if (iter != cache_.end()) {
97    BitmapFetcherService::CacheEntry* entry = iter->second;
98    request->NotifyImageChanged(*(entry->bitmap.get()));
99
100    // There is no request ID associated with this - data is already delivered.
101    return REQUEST_ID_INVALID;
102  }
103
104  // Limit number of simultaneous in-flight requests.
105  if (requests_.size() > kMaxRequests)
106    return REQUEST_ID_INVALID;
107
108  // Make sure there's a fetcher for this URL and attach to request.
109  const chrome::BitmapFetcher* fetcher = EnsureFetcherForUrl(url);
110  request->set_fetcher(fetcher);
111
112  requests_.push_back(request.release());
113  return requests_.back()->request_id();
114}
115
116void BitmapFetcherService::Prefetch(const GURL& url) {
117  if (url.is_valid())
118    EnsureFetcherForUrl(url);
119}
120
121chrome::BitmapFetcher* BitmapFetcherService::CreateFetcher(const GURL& url) {
122  chrome::BitmapFetcher* new_fetcher = new chrome::BitmapFetcher(url, this);
123
124  new_fetcher->Start(
125      context_->GetRequestContext(),
126      std::string(),
127      net::URLRequest::CLEAR_REFERRER_ON_TRANSITION_FROM_SECURE_TO_INSECURE,
128      net::LOAD_NORMAL);
129  return new_fetcher;
130}
131
132const chrome::BitmapFetcher* BitmapFetcherService::EnsureFetcherForUrl(
133    const GURL& url) {
134  const chrome::BitmapFetcher* fetcher = FindFetcherForUrl(url);
135  if (fetcher)
136    return fetcher;
137
138  chrome::BitmapFetcher* new_fetcher = CreateFetcher(url);
139  active_fetchers_.push_back(new_fetcher);
140  return new_fetcher;
141}
142
143const chrome::BitmapFetcher* BitmapFetcherService::FindFetcherForUrl(
144    const GURL& url) {
145  for (BitmapFetchers::iterator iter = active_fetchers_.begin();
146       iter != active_fetchers_.end();
147       ++iter) {
148    if (url == (*iter)->url())
149      return *iter;
150  }
151  return NULL;
152}
153
154void BitmapFetcherService::RemoveFetcher(const chrome::BitmapFetcher* fetcher) {
155  for (BitmapFetchers::iterator iter = active_fetchers_.begin();
156       iter != active_fetchers_.end();
157       ++iter) {
158    if (fetcher == (*iter)) {
159      active_fetchers_.erase(iter);
160      return;
161    }
162  }
163  NOTREACHED();  // RemoveFetcher should always result in removal.
164}
165
166void BitmapFetcherService::OnFetchComplete(const GURL url,
167                                           const SkBitmap* bitmap) {
168  DCHECK(bitmap);  // can never be NULL, guaranteed by BitmapFetcher.
169
170  const chrome::BitmapFetcher* fetcher = FindFetcherForUrl(url);
171  DCHECK(fetcher);
172
173  // Notify all attached requests of completion.
174  ScopedVector<BitmapFetcherRequest>::iterator iter = requests_.begin();
175  while (iter != requests_.end()) {
176    if ((*iter)->get_fetcher() == fetcher) {
177      (*iter)->NotifyImageChanged(*bitmap);
178      iter = requests_.erase(iter);
179    } else {
180      ++iter;
181    }
182  }
183
184  if (!bitmap->isNull()) {
185    CacheEntry* entry = new CacheEntry;
186    entry->bitmap.reset(new SkBitmap(*bitmap));
187    cache_.Put(fetcher->url(), entry);
188  }
189
190  RemoveFetcher(fetcher);
191}
192