blob: a4097e844819c9f071ed11ce26c1d3bbda928dad [file] [log] [blame]
[email protected]de0fdca22014-08-19 05:26:091// Copyright 2014 The Chromium Authors. All rights reserved.
[email protected]e8f96ff2011-08-03 05:07:332// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
[email protected]de0fdca22014-08-19 05:26:095#include "components/component_updater/component_updater_service.h"
[email protected]e8f96ff2011-08-03 05:07:336
7#include <algorithm>
sorin7c717622015-05-26 19:59:098#include <map>
9#include <string>
10#include <utility>
[email protected]e8f96ff2011-08-03 05:07:3311#include <vector>
12
[email protected]44da56e2011-11-21 19:59:1413#include "base/bind.h"
sorin395c2ac2014-09-16 21:31:0714#include "base/bind_helpers.h"
[email protected]78efe2e92014-08-08 15:53:2215#include "base/callback.h"
[email protected]57999812013-02-24 05:40:5216#include "base/files/file_path.h"
thestig819adcc82014-09-10 22:24:5317#include "base/files/file_util.h"
[email protected]e8f96ff2011-08-03 05:07:3318#include "base/logging.h"
sorin5cb1f5492014-09-23 04:07:4419#include "base/macros.h"
dchenga0ee5fb82016-04-26 02:46:5520#include "base/memory/ptr_util.h"
sorin85953dc2016-03-10 00:32:4821#include "base/metrics/histogram_macros.h"
[email protected]f5d27e32014-01-31 06:48:5322#include "base/sequenced_task_runner.h"
sorin7c717622015-05-26 19:59:0923#include "base/single_thread_task_runner.h"
waffles77255cc2016-08-02 17:25:1224#include "base/strings/utf_string_conversions.h"
[email protected]8f5f2ea2013-10-31 09:39:1025#include "base/threading/sequenced_worker_pool.h"
[email protected]ed6fb982014-07-23 16:56:5226#include "base/threading/thread_checker.h"
gab7966d312016-05-11 20:35:0127#include "base/threading/thread_task_runner_handle.h"
sorin85953dc2016-03-10 00:32:4828#include "base/time/time.h"
[email protected]41a17c52013-06-28 00:27:5329#include "base/timer/timer.h"
sorin7c717622015-05-26 19:59:0930#include "components/component_updater/component_updater_service_internal.h"
31#include "components/component_updater/timer.h"
sorineae115a2016-08-26 02:27:2032#if defined(OS_WIN)
33#include "components/component_updater/updater_state_win.h"
34#endif
sorin52ac0882015-01-24 01:15:0035#include "components/update_client/configurator.h"
sorin52ac0882015-01-24 01:15:0036#include "components/update_client/crx_update_item.h"
sorin52ac0882015-01-24 01:15:0037#include "components/update_client/update_client.h"
sorin7b8650522016-11-02 18:23:4138#include "components/update_client/update_client_errors.h"
sorin52ac0882015-01-24 01:15:0039#include "components/update_client/utils.h"
[email protected]761fa4702013-07-02 15:25:1540#include "url/gurl.h"
[email protected]e8f96ff2011-08-03 05:07:3341
sorin7c717622015-05-26 19:59:0942using CrxInstaller = update_client::CrxInstaller;
43using UpdateClient = update_client::UpdateClient;
sorin52ac0882015-01-24 01:15:0044
sorin85953dc2016-03-10 00:32:4845namespace {
46
47enum UpdateType {
48 UPDATE_TYPE_MANUAL = 0,
49 UPDATE_TYPE_AUTOMATIC,
50 UPDATE_TYPE_COUNT,
51};
52
sorineae115a2016-08-26 02:27:2053const char kRecoveryComponentId[] = "npdjjkjlcidkjlamlmmdelcjbcpdjocm";
54
sorin85953dc2016-03-10 00:32:4855} // namespace
56
[email protected]055981f2014-01-17 20:22:3257namespace component_updater {
[email protected]3a0092d2013-12-18 03:04:3558
wafflese7759f72016-10-10 23:41:2559ComponentInfo::ComponentInfo(const std::string& id, const base::string16& name,
60 const base::Version& version)
61 : id(id), name(name), version(version) {}
waffles77255cc2016-08-02 17:25:1262ComponentInfo::~ComponentInfo() {}
63
sorin7c717622015-05-26 19:59:0964CrxUpdateService::CrxUpdateService(
65 const scoped_refptr<Configurator>& config,
66 const scoped_refptr<UpdateClient>& update_client)
[email protected]e8f96ff2011-08-03 05:07:3367 : config_(config),
sorin7c717622015-05-26 19:59:0968 update_client_(update_client),
69 blocking_task_runner_(config->GetSequencedTaskRunner()) {
70 AddObserver(this);
[email protected]1ea21ad2013-09-02 04:20:3971}
[email protected]e8f96ff2011-08-03 05:07:3372
73CrxUpdateService::~CrxUpdateService() {
sorin7c717622015-05-26 19:59:0974 DCHECK(thread_checker_.CalledOnValidThread());
75
76 for (const auto item : ready_callbacks_) {
77 item.second.Run();
78 }
79
80 RemoveObserver(this);
81
[email protected]e8f96ff2011-08-03 05:07:3382 Stop();
[email protected]1ea21ad2013-09-02 04:20:3983}
[email protected]e8f96ff2011-08-03 05:07:3384
[email protected]d3268fe2014-04-25 02:14:2385void CrxUpdateService::AddObserver(Observer* observer) {
[email protected]ed6fb982014-07-23 16:56:5286 DCHECK(thread_checker_.CalledOnValidThread());
sorin7c717622015-05-26 19:59:0987 update_client_->AddObserver(observer);
[email protected]d3268fe2014-04-25 02:14:2388}
89
90void CrxUpdateService::RemoveObserver(Observer* observer) {
[email protected]ed6fb982014-07-23 16:56:5291 DCHECK(thread_checker_.CalledOnValidThread());
sorin7c717622015-05-26 19:59:0992 update_client_->RemoveObserver(observer);
[email protected]d3268fe2014-04-25 02:14:2393}
94
sorin7c717622015-05-26 19:59:0995void CrxUpdateService::Start() {
96 DCHECK(thread_checker_.CalledOnValidThread());
97 VLOG(1) << "CrxUpdateService starting up. "
98 << "First update attempt will take place in "
99 << config_->InitialDelay() << " seconds. "
100 << "Next update attempt will take place in "
101 << config_->NextCheckDelay() << " seconds. ";
[email protected]e8f96ff2011-08-03 05:07:33102
sorin7c717622015-05-26 19:59:09103 timer_.Start(
104 base::TimeDelta::FromSeconds(config_->InitialDelay()),
105 base::TimeDelta::FromSeconds(config_->NextCheckDelay()),
106 base::Bind(base::IgnoreResult(&CrxUpdateService::CheckForUpdates),
107 base::Unretained(this)));
[email protected]e8f96ff2011-08-03 05:07:33108}
109
sorin7c717622015-05-26 19:59:09110// Stops the update loop. In flight operations will be completed.
111void CrxUpdateService::Stop() {
112 DCHECK(thread_checker_.CalledOnValidThread());
[email protected]fb53e652014-04-30 11:27:19113 VLOG(1) << "CrxUpdateService stopping";
[email protected]e8f96ff2011-08-03 05:07:33114 timer_.Stop();
sorinecaad3e2015-11-13 19:15:52115 update_client_->Stop();
[email protected]e8f96ff2011-08-03 05:07:33116}
117
118// Adds a component to be checked for upgrades. If the component exists it
sorin7c717622015-05-26 19:59:09119// it will be replaced.
120bool CrxUpdateService::RegisterComponent(const CrxComponent& component) {
[email protected]ed6fb982014-07-23 16:56:52121 DCHECK(thread_checker_.CalledOnValidThread());
[email protected]d0c8b8b42014-05-06 05:11:45122 if (component.pk_hash.empty() || !component.version.IsValid() ||
sorin7c717622015-05-26 19:59:09123 !component.installer) {
124 return false;
[email protected]e8f96ff2011-08-03 05:07:33125 }
126
sorin7c717622015-05-26 19:59:09127 // Update the registration data if the component has been registered before.
128 const std::string id(GetCrxComponentID(component));
129 auto it = components_.find(id);
130 if (it != components_.end()) {
131 it->second = component;
132 return true;
[email protected]10bc0222014-06-11 13:44:38133 }
[email protected]e8f96ff2011-08-03 05:07:33134
sorin7c717622015-05-26 19:59:09135 components_.insert(std::make_pair(id, component));
136 components_order_.push_back(id);
waffles77255cc2016-08-02 17:25:12137 for (const auto& mime_type : component.handled_mime_types)
138 component_ids_by_mime_type_[mime_type] = id;
sorin7c717622015-05-26 19:59:09139
140 // Create an initial state for this component. The state is mutated in
141 // response to events from the UpdateClient instance.
142 CrxUpdateItem item;
143 item.id = id;
144 item.component = component;
145 const auto inserted = component_states_.insert(std::make_pair(id, item));
146 DCHECK(inserted.second);
147
148 // Start the timer if this is the first component registered. The first timer
149 // event occurs after an interval defined by the component update
150 // configurator. The subsequent timer events are repeated with a period
151 // defined by the same configurator.
152 if (components_.size() == 1)
153 Start();
154
155 return true;
[email protected]e8f96ff2011-08-03 05:07:33156}
157
sorin7c717622015-05-26 19:59:09158bool CrxUpdateService::UnregisterComponent(const std::string& id) {
159 DCHECK(thread_checker_.CalledOnValidThread());
160 auto it = components_.find(id);
161 if (it == components_.end())
162 return false;
bauerb1f6657e72015-02-09 00:00:27163
sorin7c717622015-05-26 19:59:09164 DCHECK_EQ(id, it->first);
bauerb1f6657e72015-02-09 00:00:27165
sorin7c717622015-05-26 19:59:09166 // Delay the uninstall of the component if the component is being updated.
167 if (update_client_->IsUpdating(id)) {
168 components_pending_unregistration_.push_back(id);
169 return true;
170 }
171
172 return DoUnregisterComponent(it->second);
173}
174
175bool CrxUpdateService::DoUnregisterComponent(const CrxComponent& component) {
176 DCHECK(thread_checker_.CalledOnValidThread());
177
178 const auto id = GetCrxComponentID(component);
179 DCHECK(ready_callbacks_.find(id) == ready_callbacks_.end());
180
181 const bool result = component.installer->Uninstall();
182
183 const auto pos =
184 std::find(components_order_.begin(), components_order_.end(), id);
185 if (pos != components_order_.end())
186 components_order_.erase(pos);
187
188 components_.erase(id);
189 component_states_.erase(id);
190
191 return result;
bauerb1f6657e72015-02-09 00:00:27192}
193
[email protected]68bf09e2014-06-03 00:10:56194std::vector<std::string> CrxUpdateService::GetComponentIDs() const {
[email protected]ed6fb982014-07-23 16:56:52195 DCHECK(thread_checker_.CalledOnValidThread());
sorin7c717622015-05-26 19:59:09196 std::vector<std::string> ids;
197 for (const auto& it : components_)
198 ids.push_back(it.first);
199 return ids;
[email protected]68bf09e2014-06-03 00:10:56200}
201
waffles77255cc2016-08-02 17:25:12202std::unique_ptr<ComponentInfo> CrxUpdateService::GetComponentForMimeType(
203 const std::string& mime_type) const {
204 DCHECK(thread_checker_.CalledOnValidThread());
205 const auto it = component_ids_by_mime_type_.find(mime_type);
206 if (it == component_ids_by_mime_type_.end())
207 return nullptr;
208 const auto component = GetComponent(it->second);
209 if (!component)
210 return nullptr;
211 return base::MakeUnique<ComponentInfo>(GetCrxComponentID(*component),
wafflese7759f72016-10-10 23:41:25212 base::UTF8ToUTF16(component->name),
213 component->version);
waffles77255cc2016-08-02 17:25:12214}
215
[email protected]78efe2e92014-08-08 15:53:22216OnDemandUpdater& CrxUpdateService::GetOnDemandUpdater() {
sorin7c717622015-05-26 19:59:09217 DCHECK(thread_checker_.CalledOnValidThread());
[email protected]78efe2e92014-08-08 15:53:22218 return *this;
219}
220
sorin7c717622015-05-26 19:59:09221const CrxComponent* CrxUpdateService::GetComponent(
222 const std::string& id) const {
223 DCHECK(thread_checker_.CalledOnValidThread());
224 const auto it(components_.find(id));
225 return it != components_.end() ? &(it->second) : NULL;
226}
227
228const CrxUpdateItem* CrxUpdateService::GetComponentState(
229 const std::string& id) const {
230 DCHECK(thread_checker_.CalledOnValidThread());
231 const auto it(component_states_.find(id));
232 return it != component_states_.end() ? &it->second : NULL;
233}
234
235void CrxUpdateService::MaybeThrottle(const std::string& id,
[email protected]78efe2e92014-08-08 15:53:22236 const base::Closure& callback) {
237 DCHECK(thread_checker_.CalledOnValidThread());
sorin7c717622015-05-26 19:59:09238 auto it = components_.find(id);
239 if (it != components_.end()) {
240 DCHECK_EQ(it->first, id);
241 if (OnDemandUpdateWithCooldown(id)) {
242 ready_callbacks_.insert(std::make_pair(id, callback));
243 return;
244 }
[email protected]78efe2e92014-08-08 15:53:22245 }
sorin7c717622015-05-26 19:59:09246
247 callback.Run(); // Unblock the request if the request can't be throttled.
248}
249
sorin4c520182016-08-19 17:27:44250void CrxUpdateService::OnDemandUpdate(const std::string& id,
251 CompletionCallback callback) {
sorin7c717622015-05-26 19:59:09252 DCHECK(thread_checker_.CalledOnValidThread());
253
sorin4c520182016-08-19 17:27:44254 if (!GetComponent(id)) {
255 base::ThreadTaskRunnerHandle::Get()->PostTask(
256 FROM_HERE,
sorin7b8650522016-11-02 18:23:41257 base::Bind(callback, update_client::Error::INVALID_ARGUMENT));
sorin4c520182016-08-19 17:27:44258 return;
259 }
sorin7c717622015-05-26 19:59:09260
sorin4c520182016-08-19 17:27:44261 OnDemandUpdateInternal(id, callback);
sorin7c717622015-05-26 19:59:09262}
263
264bool CrxUpdateService::OnDemandUpdateWithCooldown(const std::string& id) {
265 DCHECK(thread_checker_.CalledOnValidThread());
266
267 DCHECK(GetComponent(id));
268
269 // Check if the request is too soon.
vmpstr2de366b2016-07-20 21:35:48270 const auto* component_state(GetComponentState(id));
sorin7c717622015-05-26 19:59:09271 if (component_state) {
sorin1827db12016-09-15 20:43:31272 base::TimeDelta delta =
273 base::TimeTicks::Now() - component_state->last_check;
sorin7c717622015-05-26 19:59:09274 if (delta < base::TimeDelta::FromSeconds(config_->OnDemandDelay()))
275 return false;
276 }
277
sorin4c520182016-08-19 17:27:44278 OnDemandUpdateInternal(id, CompletionCallback());
279 return true;
sorin7c717622015-05-26 19:59:09280}
281
sorin4c520182016-08-19 17:27:44282void CrxUpdateService::OnDemandUpdateInternal(const std::string& id,
283 CompletionCallback callback) {
sorin7c717622015-05-26 19:59:09284 DCHECK(thread_checker_.CalledOnValidThread());
285
sorin85953dc2016-03-10 00:32:48286 UMA_HISTOGRAM_ENUMERATION("ComponentUpdater.Calls", UPDATE_TYPE_MANUAL,
287 UPDATE_TYPE_COUNT);
sorin7c717622015-05-26 19:59:09288 update_client_->Install(
289 id, base::Bind(&CrxUpdateService::OnUpdate, base::Unretained(this)),
sorin85953dc2016-03-10 00:32:48290 base::Bind(&CrxUpdateService::OnUpdateComplete, base::Unretained(this),
sorin4c520182016-08-19 17:27:44291 callback, base::TimeTicks::Now()));
sorin7c717622015-05-26 19:59:09292}
293
294bool CrxUpdateService::CheckForUpdates() {
295 DCHECK(thread_checker_.CalledOnValidThread());
sorin85953dc2016-03-10 00:32:48296
297 UMA_HISTOGRAM_ENUMERATION("ComponentUpdater.Calls", UPDATE_TYPE_AUTOMATIC,
298 UPDATE_TYPE_COUNT);
299
sorinfccbf2d2016-04-04 20:34:34300 std::vector<std::string> secure_ids; // Requires HTTPS for update checks.
301 std::vector<std::string> unsecure_ids; // Can fallback to HTTP.
sorin7c717622015-05-26 19:59:09302 for (const auto id : components_order_) {
303 DCHECK(components_.find(id) != components_.end());
sorinfccbf2d2016-04-04 20:34:34304
vmpstr2de366b2016-07-20 21:35:48305 auto* component(GetComponent(id));
sorinfccbf2d2016-04-04 20:34:34306 if (!component || component->requires_network_encryption)
307 secure_ids.push_back(id);
308 else
309 unsecure_ids.push_back(id);
sorin7c717622015-05-26 19:59:09310 }
311
sorinfccbf2d2016-04-04 20:34:34312 if (!unsecure_ids.empty()) {
313 update_client_->Update(
314 unsecure_ids,
315 base::Bind(&CrxUpdateService::OnUpdate, base::Unretained(this)),
316 base::Bind(&CrxUpdateService::OnUpdateComplete, base::Unretained(this),
sorin4c520182016-08-19 17:27:44317 CompletionCallback(), base::TimeTicks::Now()));
sorinfccbf2d2016-04-04 20:34:34318 }
319
320 if (!secure_ids.empty()) {
321 update_client_->Update(
322 secure_ids,
323 base::Bind(&CrxUpdateService::OnUpdate, base::Unretained(this)),
324 base::Bind(&CrxUpdateService::OnUpdateComplete, base::Unretained(this),
sorin4c520182016-08-19 17:27:44325 CompletionCallback(), base::TimeTicks::Now()));
sorinfccbf2d2016-04-04 20:34:34326 }
sorin7c717622015-05-26 19:59:09327
328 return true;
[email protected]78efe2e92014-08-08 15:53:22329}
330
331scoped_refptr<base::SequencedTaskRunner>
332CrxUpdateService::GetSequencedTaskRunner() {
sorin7c717622015-05-26 19:59:09333 DCHECK(thread_checker_.CalledOnValidThread());
bauerb1f6657e72015-02-09 00:00:27334 return blocking_task_runner_;
[email protected]78efe2e92014-08-08 15:53:22335}
336
sorin7c717622015-05-26 19:59:09337bool CrxUpdateService::GetComponentDetails(const std::string& id,
[email protected]f392e372014-06-12 07:25:57338 CrxUpdateItem* item) const {
[email protected]ed6fb982014-07-23 16:56:52339 DCHECK(thread_checker_.CalledOnValidThread());
sorin7c717622015-05-26 19:59:09340
341 // First, if this component is currently being updated, return its state from
342 // the update client.
343 if (update_client_->GetCrxUpdateState(id, item))
344 return true;
345
346 // Otherwise, return the last seen state of the component, if such a
347 // state exists.
348 const auto component_states_it = component_states_.find(id);
349 if (component_states_it != component_states_.end()) {
350 *item = component_states_it->second;
351 return true;
352 }
353
354 return false;
[email protected]2e919ddd2013-08-21 05:05:17355}
356
sorin7c717622015-05-26 19:59:09357void CrxUpdateService::OnUpdate(const std::vector<std::string>& ids,
358 std::vector<CrxComponent>* components) {
359 DCHECK(thread_checker_.CalledOnValidThread());
360 DCHECK(components->empty());
361
362 for (const auto& id : ids) {
sorineae115a2016-08-26 02:27:20363 const update_client::CrxComponent* registered_component(GetComponent(id));
sorin7c717622015-05-26 19:59:09364 if (registered_component) {
365 components->push_back(*registered_component);
sorineae115a2016-08-26 02:27:20366 if (id == kRecoveryComponentId) {
367 // Override the installer attributes for the recovery component in the
368 // components which will be checked for updates.
369 update_client::CrxComponent& recovery_component(components->back());
370 recovery_component.installer_attributes =
371 GetInstallerAttributesForRecoveryComponentInstaller(
372 recovery_component);
373 }
sorin7c717622015-05-26 19:59:09374 }
375 }
[email protected]ed6fb982014-07-23 16:56:52376}
377
sorin4c520182016-08-19 17:27:44378void CrxUpdateService::OnUpdateComplete(CompletionCallback callback,
379 const base::TimeTicks& start_time,
sorin7b8650522016-11-02 18:23:41380 update_client::Error error) {
sorin7c717622015-05-26 19:59:09381 DCHECK(thread_checker_.CalledOnValidThread());
sorin7b8650522016-11-02 18:23:41382 VLOG(1) << "Update completed with error " << static_cast<int>(error);
sorin7c717622015-05-26 19:59:09383
sorin7b8650522016-11-02 18:23:41384 UMA_HISTOGRAM_BOOLEAN("ComponentUpdater.UpdateCompleteResult",
385 error != update_client::Error::NONE);
sorin85953dc2016-03-10 00:32:48386 UMA_HISTOGRAM_LONG_TIMES_100("ComponentUpdater.UpdateCompleteTime",
387 base::TimeTicks::Now() - start_time);
388
sorin7c717622015-05-26 19:59:09389 for (const auto id : components_pending_unregistration_) {
390 if (!update_client_->IsUpdating(id)) {
vmpstr2de366b2016-07-20 21:35:48391 const auto* component = GetComponent(id);
sorin7c717622015-05-26 19:59:09392 if (component)
393 DoUnregisterComponent(*component);
394 }
395 }
sorin4c520182016-08-19 17:27:44396
397 if (!callback.is_null()) {
398 base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
399 base::Bind(callback, error));
400 }
sorin7c717622015-05-26 19:59:09401}
402
403void CrxUpdateService::OnEvent(Events event, const std::string& id) {
[email protected]ed6fb982014-07-23 16:56:52404 DCHECK(thread_checker_.CalledOnValidThread());
[email protected]93e8e2c2014-01-04 12:29:23405
sorin7c717622015-05-26 19:59:09406 // Unblock all throttles for the component.
407 if (event == Observer::Events::COMPONENT_UPDATED ||
408 event == Observer::Events::COMPONENT_NOT_UPDATED) {
409 auto callbacks = ready_callbacks_.equal_range(id);
410 for (auto it = callbacks.first; it != callbacks.second; ++it) {
411 it->second.Run();
412 }
413 ready_callbacks_.erase(id);
414 }
415
416 CrxUpdateItem update_item;
417 if (!update_client_->GetCrxUpdateState(id, &update_item))
[email protected]e8f96ff2011-08-03 05:07:33418 return;
[email protected]93e8e2c2014-01-04 12:29:23419
sorin7c717622015-05-26 19:59:09420 // Update the state of the item.
421 auto it = component_states_.find(id);
422 DCHECK(it != component_states_.end());
423 it->second = update_item;
bauerb1f6657e72015-02-09 00:00:27424
sorin7c717622015-05-26 19:59:09425 // Update the component registration with the new version.
426 if (event == Observer::Events::COMPONENT_UPDATED) {
vmpstr2de366b2016-07-20 21:35:48427 auto* component(const_cast<CrxComponent*>(GetComponent(id)));
sorin7c717622015-05-26 19:59:09428 if (component) {
429 component->version = update_item.next_version;
430 component->fingerprint = update_item.next_fp;
bauerb1f6657e72015-02-09 00:00:27431 }
432 }
[email protected]68bf09e2014-06-03 00:10:56433}
434
sorineae115a2016-08-26 02:27:20435update_client::InstallerAttributes
436CrxUpdateService::GetInstallerAttributesForRecoveryComponentInstaller(
437 const CrxComponent& crx_component) const {
438 update_client::InstallerAttributes installer_attributes;
439#if defined(OS_WIN)
440 DCHECK_EQ("recovery", crx_component.name);
441
442 const bool is_machine =
443 crx_component.installer_attributes.count("ismachine") &&
444 crx_component.installer_attributes.at("ismachine") == "1";
445
446 auto updater_state(UpdaterState::Create(is_machine));
447 if (updater_state) {
448 installer_attributes = updater_state->MakeInstallerAttributes();
449 }
450#endif
451 return installer_attributes;
452}
453
[email protected]00a77fa2013-11-02 04:18:46454///////////////////////////////////////////////////////////////////////////////
455
[email protected]e8f96ff2011-08-03 05:07:33456// The component update factory. Using the component updater as a singleton
457// is the job of the browser process.
sorin7c717622015-05-26 19:59:09458// TODO(sorin): consider making this a singleton.
dchenga0ee5fb82016-04-26 02:46:55459std::unique_ptr<ComponentUpdateService> ComponentUpdateServiceFactory(
sorin9797aba2015-04-17 17:15:03460 const scoped_refptr<Configurator>& config) {
[email protected]e8f96ff2011-08-03 05:07:33461 DCHECK(config);
sorin7c717622015-05-26 19:59:09462 auto update_client = update_client::UpdateClientFactory(config);
ricea85ec57952016-08-31 09:34:10463 return base::MakeUnique<CrxUpdateService>(config, std::move(update_client));
[email protected]e8f96ff2011-08-03 05:07:33464}
[email protected]2cddef42013-11-22 08:23:22465
[email protected]055981f2014-01-17 20:22:32466} // namespace component_updater