1 // Copyright (c) 2012 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.
5 #include "chrome/installer/util/installer_state.h"
11 #include "base/command_line.h"
12 #include "base/file_version_info.h"
13 #include "base/files/file_enumerator.h"
14 #include "base/files/file_util.h"
15 #include "base/logging.h"
16 #include "base/memory/scoped_ptr.h"
17 #include "base/strings/string_util.h"
18 #include "base/strings/utf_string_conversions.h"
19 #include "base/win/registry.h"
20 #include "base/win/scoped_handle.h"
21 #include "chrome/installer/util/delete_tree_work_item.h"
22 #include "chrome/installer/util/helper.h"
23 #include "chrome/installer/util/install_util.h"
24 #include "chrome/installer/util/installation_state.h"
25 #include "chrome/installer/util/master_preferences.h"
26 #include "chrome/installer/util/master_preferences_constants.h"
27 #include "chrome/installer/util/product.h"
28 #include "chrome/installer/util/work_item.h"
29 #include "chrome/installer/util/work_item_list.h"
33 bool InstallerState::IsMultiInstallUpdate(
34 const MasterPreferences
& prefs
,
35 const InstallationState
& machine_state
) {
36 // First, are the binaries present?
37 const ProductState
* binaries
=
38 machine_state
.GetProductState(level_
== SYSTEM_LEVEL
,
39 BrowserDistribution::CHROME_BINARIES
);
40 if (binaries
== NULL
) {
41 // The multi-install binaries have not been installed, so they certainly
42 // aren't being updated.
46 if (prefs
.install_chrome()) {
47 const ProductState
* product
=
48 machine_state
.GetProductState(level_
== SYSTEM_LEVEL
,
49 BrowserDistribution::CHROME_BROWSER
);
50 if (product
== NULL
) {
51 VLOG(2) << "It seems that chrome is being installed for the first time.";
54 if (!product
->channel().Equals(binaries
->channel())) {
55 VLOG(2) << "It seems that chrome is being over installed.";
60 VLOG(2) << "It seems that the binaries are being updated.";
65 InstallerState::InstallerState()
66 : operation_(UNINITIALIZED
),
67 state_type_(BrowserDistribution::CHROME_BROWSER
),
68 multi_package_distribution_(NULL
),
69 level_(UNKNOWN_LEVEL
),
70 package_type_(UNKNOWN_PACKAGE_TYPE
),
73 verbose_logging_(false) {
76 InstallerState::InstallerState(Level level
)
77 : operation_(UNINITIALIZED
),
78 state_type_(BrowserDistribution::CHROME_BROWSER
),
79 multi_package_distribution_(NULL
),
80 level_(UNKNOWN_LEVEL
),
81 package_type_(UNKNOWN_PACKAGE_TYPE
),
84 verbose_logging_(false) {
85 // Use set_level() so that root_key_ is updated properly.
89 void InstallerState::Initialize(const base::CommandLine
& command_line
,
90 const MasterPreferences
& prefs
,
91 const InstallationState
& machine_state
) {
95 if (!prefs
.GetBool(master_preferences::kSystemLevel
, &pref_bool
))
97 set_level(pref_bool
? SYSTEM_LEVEL
: USER_LEVEL
);
99 if (!prefs
.GetBool(master_preferences::kVerboseLogging
, &verbose_logging_
))
100 verbose_logging_
= false;
102 if (!prefs
.GetBool(master_preferences::kMultiInstall
, &pref_bool
))
104 set_package_type(pref_bool
? MULTI_PACKAGE
: SINGLE_PACKAGE
);
106 if (!prefs
.GetBool(master_preferences::kMsi
, &msi_
))
109 const bool is_uninstall
= command_line
.HasSwitch(switches::kUninstall
);
111 if (prefs
.install_chrome()) {
112 Product
* p
= AddProductFromPreferences(
113 BrowserDistribution::CHROME_BROWSER
, prefs
, machine_state
);
114 VLOG(1) << (is_uninstall
? "Uninstall" : "Install")
115 << " distribution: " << p
->distribution()->GetDisplayName();
118 // Binaries are only used by Chrome.
119 if (is_multi_install() &&
120 FindProduct(BrowserDistribution::CHROME_BROWSER
)) {
121 Product
* p
= AddProductFromPreferences(
122 BrowserDistribution::CHROME_BINARIES
, prefs
, machine_state
);
123 VLOG(1) << (is_uninstall
? "Uninstall" : "Install")
124 << " distribution: " << p
->distribution()->GetDisplayName();
127 BrowserDistribution
* operand
= NULL
;
130 operation_
= UNINSTALL
;
131 } else if (!prefs
.is_multi_install()) {
132 // For a single-install, the current browser dist is the operand.
133 operand
= BrowserDistribution::GetDistribution();
134 operation_
= SINGLE_INSTALL_OR_UPDATE
;
135 } else if (IsMultiInstallUpdate(prefs
, machine_state
)) {
136 // Updates driven by Google Update take place under the multi-installer's
138 operand
= multi_package_distribution_
;
139 operation_
= MULTI_UPDATE
;
141 operation_
= MULTI_INSTALL
;
144 // Initial, over, and un-installs will take place under Chrome or Binaries
146 if (operand
== NULL
) {
147 operand
= BrowserDistribution::GetSpecificDistribution(
148 prefs
.install_chrome() ?
149 BrowserDistribution::CHROME_BROWSER
:
150 BrowserDistribution::CHROME_BINARIES
);
153 state_key_
= operand
->GetStateKey();
154 state_type_
= operand
->GetType();
156 // Parse --critical-update-version=W.X.Y.Z
157 std::string
critical_version_value(
158 command_line
.GetSwitchValueASCII(switches::kCriticalUpdateVersion
));
159 critical_update_version_
= Version(critical_version_value
);
162 void InstallerState::set_level(Level level
) {
166 root_key_
= HKEY_CURRENT_USER
;
169 root_key_
= HKEY_LOCAL_MACHINE
;
172 DCHECK(level
== UNKNOWN_LEVEL
);
173 level_
= UNKNOWN_LEVEL
;
179 void InstallerState::set_package_type(PackageType type
) {
180 package_type_
= type
;
183 multi_package_distribution_
= NULL
;
186 multi_package_distribution_
=
187 BrowserDistribution::GetSpecificDistribution(
188 BrowserDistribution::CHROME_BINARIES
);
191 DCHECK(type
== UNKNOWN_PACKAGE_TYPE
);
192 package_type_
= UNKNOWN_PACKAGE_TYPE
;
193 multi_package_distribution_
= NULL
;
198 // Returns the Chrome binaries directory for multi-install or |dist|'s directory
200 base::FilePath
InstallerState::GetDefaultProductInstallPath(
201 BrowserDistribution
* dist
) const {
203 DCHECK(package_type_
!= UNKNOWN_PACKAGE_TYPE
);
205 if (package_type_
== SINGLE_PACKAGE
) {
206 return GetChromeInstallPath(system_install(), dist
);
208 return GetChromeInstallPath(system_install(),
209 BrowserDistribution::GetSpecificDistribution(
210 BrowserDistribution::CHROME_BINARIES
));
214 // Evaluates a product's eligibility for participation in this operation.
215 // We never expect these checks to fail, hence they all terminate the process in
216 // debug builds. See the log messages for details.
217 bool InstallerState::CanAddProduct(const Product
& product
,
218 const base::FilePath
* product_dir
) const {
219 switch (package_type_
) {
221 if (!products_
.empty()) {
222 LOG(DFATAL
) << "Cannot process more than one single-install product.";
227 if (!product
.HasOption(kOptionMultiInstall
)) {
228 LOG(DFATAL
) << "Cannot process a single-install product with a "
229 "multi-install state.";
232 if (FindProduct(product
.distribution()->GetType()) != NULL
) {
233 LOG(DFATAL
) << "Cannot process more than one product of the same type.";
236 if (!target_path_
.empty()) {
237 base::FilePath default_dir
;
238 if (product_dir
== NULL
)
239 default_dir
= GetDefaultProductInstallPath(product
.distribution());
240 if (!base::FilePath::CompareEqualIgnoreCase(
241 (product_dir
== NULL
? default_dir
: *product_dir
).value(),
242 target_path_
.value())) {
243 LOG(DFATAL
) << "Cannot process products in different directories.";
249 DCHECK_EQ(UNKNOWN_PACKAGE_TYPE
, package_type_
);
255 // Adds |product|, installed in |product_dir| to this object's collection. If
256 // |product_dir| is NULL, the product's default install location is used.
257 // Returns NULL if |product| is incompatible with this object. Otherwise,
258 // returns a pointer to the product (ownership is held by this object).
259 Product
* InstallerState::AddProductInDirectory(
260 const base::FilePath
* product_dir
,
261 scoped_ptr
<Product
>* product
) {
262 DCHECK(product
!= NULL
);
263 DCHECK(product
->get() != NULL
);
264 const Product
& the_product
= *product
->get();
266 if (!CanAddProduct(the_product
, product_dir
))
269 if (package_type_
== UNKNOWN_PACKAGE_TYPE
) {
270 set_package_type(the_product
.HasOption(kOptionMultiInstall
) ?
271 MULTI_PACKAGE
: SINGLE_PACKAGE
);
274 if (target_path_
.empty()) {
275 if (product_dir
== NULL
)
276 target_path_
= GetDefaultProductInstallPath(the_product
.distribution());
278 target_path_
= *product_dir
;
281 if (state_key_
.empty())
282 state_key_
= the_product
.distribution()->GetStateKey();
284 products_
.push_back(product
->release());
285 return products_
[products_
.size() - 1];
288 Product
* InstallerState::AddProduct(scoped_ptr
<Product
>* product
) {
289 return AddProductInDirectory(NULL
, product
);
292 // Adds a product of type |distribution_type| constructed on the basis of
293 // |prefs|, setting this object's msi flag if the product is represented in
294 // |machine_state| and is msi-installed. Returns the product that was added,
295 // or NULL if |state| is incompatible with this object. Ownership is not passed
297 Product
* InstallerState::AddProductFromPreferences(
298 BrowserDistribution::Type distribution_type
,
299 const MasterPreferences
& prefs
,
300 const InstallationState
& machine_state
) {
301 scoped_ptr
<Product
> product_ptr(
302 new Product(BrowserDistribution::GetSpecificDistribution(
303 distribution_type
)));
304 product_ptr
->InitializeFromPreferences(prefs
);
306 Product
* product
= AddProductInDirectory(NULL
, &product_ptr
);
308 if (product
!= NULL
&& !msi_
) {
309 const ProductState
* product_state
= machine_state
.GetProductState(
310 system_install(), distribution_type
);
311 if (product_state
!= NULL
)
312 msi_
= product_state
->is_msi();
318 Product
* InstallerState::AddProductFromState(
319 BrowserDistribution::Type type
,
320 const ProductState
& state
) {
321 scoped_ptr
<Product
> product_ptr(
322 new Product(BrowserDistribution::GetSpecificDistribution(type
)));
323 product_ptr
->InitializeFromUninstallCommand(state
.uninstall_command());
325 // Strip off <version>/Installer/setup.exe; see GetInstallerDirectory().
326 base::FilePath product_dir
=
327 state
.GetSetupPath().DirName().DirName().DirName();
329 Product
* product
= AddProductInDirectory(&product_dir
, &product_ptr
);
332 msi_
|= state
.is_msi();
337 bool InstallerState::system_install() const {
338 DCHECK(level_
== USER_LEVEL
|| level_
== SYSTEM_LEVEL
);
339 return level_
== SYSTEM_LEVEL
;
342 bool InstallerState::is_multi_install() const {
343 DCHECK(package_type_
== SINGLE_PACKAGE
|| package_type_
== MULTI_PACKAGE
);
344 return package_type_
!= SINGLE_PACKAGE
;
347 bool InstallerState::RemoveProduct(const Product
* product
) {
348 ScopedVector
<Product
>::iterator it
=
349 std::find(products_
.begin(), products_
.end(), product
);
350 if (it
!= products_
.end()) {
351 products_
.weak_erase(it
);
357 const Product
* InstallerState::FindProduct(
358 BrowserDistribution::Type distribution_type
) const {
359 for (Products::const_iterator scan
= products_
.begin(), end
= products_
.end();
360 scan
!= end
; ++scan
) {
361 if ((*scan
)->is_type(distribution_type
))
367 Version
* InstallerState::GetCurrentVersion(
368 const InstallationState
& machine_state
) const {
369 DCHECK(!products_
.empty());
370 scoped_ptr
<Version
> current_version
;
371 // If we're doing a multi-install, the current version may be either an
372 // existing multi or an existing single product that is being migrated
373 // in place (i.e., Chrome). In the latter case, there is no existing
374 // CHROME_BINARIES installation so we need to search for the product.
375 BrowserDistribution::Type prod_type
;
376 if (package_type_
== MULTI_PACKAGE
) {
377 prod_type
= BrowserDistribution::CHROME_BINARIES
;
378 if (machine_state
.GetProductState(level_
== SYSTEM_LEVEL
,
379 prod_type
) == NULL
) {
380 // Search for a product on which we're operating that is installed in our
382 Products::const_iterator end
= products().end();
383 for (Products::const_iterator scan
= products().begin(); scan
!= end
;
385 BrowserDistribution::Type product_type
=
386 (*scan
)->distribution()->GetType();
387 const ProductState
* state
=
388 machine_state
.GetProductState(level_
== SYSTEM_LEVEL
, product_type
);
389 if (state
!= NULL
&& target_path_
.IsParent(state
->GetSetupPath())) {
390 prod_type
= product_type
;
396 prod_type
= products_
[0]->distribution()->GetType();
398 const ProductState
* product_state
=
399 machine_state
.GetProductState(level_
== SYSTEM_LEVEL
, prod_type
);
401 if (product_state
!= NULL
) {
402 const Version
* version
= NULL
;
404 // Be aware that there might be a pending "new_chrome.exe" already in the
405 // installation path. If so, we use old_version, which holds the version of
406 // "chrome.exe" itself.
407 if (base::PathExists(target_path().Append(kChromeNewExe
)))
408 version
= product_state
->old_version();
411 version
= &product_state
->version();
413 current_version
.reset(new Version(*version
));
416 return current_version
.release();
419 Version
InstallerState::DetermineCriticalVersion(
420 const Version
* current_version
,
421 const Version
& new_version
) const {
422 DCHECK(current_version
== NULL
|| current_version
->IsValid());
423 DCHECK(new_version
.IsValid());
424 if (critical_update_version_
.IsValid() &&
425 (current_version
== NULL
||
426 (current_version
->CompareTo(critical_update_version_
) < 0)) &&
427 new_version
.CompareTo(critical_update_version_
) >= 0) {
428 return critical_update_version_
;
433 bool InstallerState::IsChromeFrameRunning(
434 const InstallationState
& machine_state
) const {
435 return AnyExistsAndIsInUse(machine_state
, CHROME_FRAME_DLL
);
438 bool InstallerState::AreBinariesInUse(
439 const InstallationState
& machine_state
) const {
440 return AnyExistsAndIsInUse(
442 (CHROME_FRAME_HELPER_EXE
| CHROME_FRAME_HELPER_DLL
|
443 CHROME_FRAME_DLL
| CHROME_DLL
));
446 base::FilePath
InstallerState::GetInstallerDirectory(
447 const Version
& version
) const {
448 return target_path().AppendASCII(version
.GetString()).Append(kInstallerDir
);
452 bool InstallerState::IsFileInUse(const base::FilePath
& file
) {
453 // Call CreateFile with a share mode of 0 which should cause this to fail
454 // with ERROR_SHARING_VIOLATION if the file exists and is in-use.
455 return !base::win::ScopedHandle(CreateFile(file
.value().c_str(),
456 GENERIC_WRITE
, 0, NULL
,
457 OPEN_EXISTING
, 0, 0)).IsValid();
460 void InstallerState::Clear() {
461 operation_
= UNINITIALIZED
;
462 target_path_
.clear();
464 state_type_
= BrowserDistribution::CHROME_BROWSER
;
466 multi_package_distribution_
= NULL
;
467 critical_update_version_
= base::Version();
468 level_
= UNKNOWN_LEVEL
;
469 package_type_
= UNKNOWN_PACKAGE_TYPE
;
472 verbose_logging_
= false;
475 bool InstallerState::AnyExistsAndIsInUse(
476 const InstallationState
& machine_state
,
477 uint32 file_bits
) const {
478 static const wchar_t* const kBinaryFileNames
[] = {
481 kChromeFrameHelperDll
,
482 kChromeFrameHelperExe
,
484 DCHECK_NE(file_bits
, 0U);
485 DCHECK_LT(file_bits
, 1U << NUM_BINARIES
);
486 COMPILE_ASSERT(CHROME_DLL
== 1, no_youre_out_of_order
);
487 COMPILE_ASSERT(CHROME_FRAME_DLL
== 2, no_youre_out_of_order
);
488 COMPILE_ASSERT(CHROME_FRAME_HELPER_DLL
== 4, no_youre_out_of_order
);
489 COMPILE_ASSERT(CHROME_FRAME_HELPER_EXE
== 8, no_youre_out_of_order
);
491 // Check only for the current version (i.e., the version we are upgrading
492 // _from_). Later versions from pending in-use updates need not be checked
493 // since the current version is guaranteed to be in use if any such are.
494 scoped_ptr
<Version
> current_version(GetCurrentVersion(machine_state
));
495 if (!current_version
)
497 base::FilePath
directory(
498 target_path().AppendASCII(current_version
->GetString()));
499 for (int i
= 0; i
< NUM_BINARIES
; ++i
) {
500 if (!(file_bits
& (1U << i
)))
502 base::FilePath
file(directory
.Append(kBinaryFileNames
[i
]));
503 if (base::PathExists(file
) && IsFileInUse(file
))
509 void InstallerState::GetExistingExeVersions(
510 std::set
<std::string
>* existing_versions
) const {
512 static const wchar_t* const kChromeFilenames
[] = {
513 installer::kChromeExe
,
514 installer::kChromeNewExe
,
515 installer::kChromeOldExe
,
518 for (int i
= 0; i
< arraysize(kChromeFilenames
); ++i
) {
519 base::FilePath
chrome_exe(target_path().Append(kChromeFilenames
[i
]));
520 scoped_ptr
<FileVersionInfo
> file_version_info(
521 FileVersionInfo::CreateFileVersionInfo(chrome_exe
));
522 if (file_version_info
) {
523 base::string16 version_string
= file_version_info
->file_version();
524 if (!version_string
.empty() && base::IsStringASCII(version_string
))
525 existing_versions
->insert(base::UTF16ToASCII(version_string
));
530 void InstallerState::RemoveOldVersionDirectories(
531 const Version
& new_version
,
532 Version
* existing_version
,
533 const base::FilePath
& temp_path
) const {
535 scoped_ptr
<WorkItem
> item
;
537 std::set
<std::string
> existing_version_strings
;
538 existing_version_strings
.insert(new_version
.GetString());
539 if (existing_version
)
540 existing_version_strings
.insert(existing_version
->GetString());
542 // Make sure not to delete any version dir that is "referenced" by an existing
543 // Chrome executable.
544 GetExistingExeVersions(&existing_version_strings
);
546 // Try to delete all directories that are not in the set we care to keep.
547 base::FileEnumerator
version_enum(target_path(), false,
548 base::FileEnumerator::DIRECTORIES
);
549 for (base::FilePath next_version
= version_enum
.Next(); !next_version
.empty();
550 next_version
= version_enum
.Next()) {
551 base::FilePath
dir_name(next_version
.BaseName());
552 version
= Version(base::UTF16ToASCII(dir_name
.value()));
553 // Delete the version folder if it is less than the new version and not
554 // equal to the old version (if we have an old version).
555 if (version
.IsValid() &&
556 existing_version_strings
.count(version
.GetString()) == 0) {
557 // Note: temporarily log old version deletion at ERROR level to make it
558 // more likely we see this in the installer log.
559 LOG(ERROR
) << "Deleting old version directory: " << next_version
.value();
561 // Attempt to recursively delete the old version dir.
562 bool delete_succeeded
= base::DeleteFile(next_version
, true);
564 // Note: temporarily log old version deletion at ERROR level to make it
565 // more likely we see this in the installer log.
566 LOG_IF(ERROR
, !delete_succeeded
)
567 << "Failed to delete old version directory: " << next_version
.value();
572 void InstallerState::AddComDllList(
573 std::vector
<base::FilePath
>* com_dll_list
) const {
574 std::for_each(products_
.begin(), products_
.end(),
575 std::bind2nd(std::mem_fun(&Product::AddComDllList
),
579 bool InstallerState::SetChannelFlags(bool set
,
580 ChannelInfo
* channel_info
) const {
581 bool modified
= false;
582 for (Products::const_iterator scan
= products_
.begin(), end
= products_
.end();
583 scan
!= end
; ++scan
) {
584 modified
|= (*scan
)->SetChannelFlags(set
, channel_info
);
589 void InstallerState::UpdateStage(installer::InstallerStage stage
) const {
590 InstallUtil::UpdateInstallerStage(system_install(), state_key_
, stage
);
593 void InstallerState::UpdateChannels() const {
594 if (operation_
!= MULTI_INSTALL
&& operation_
!= MULTI_UPDATE
) {
595 VLOG(1) << "InstallerState::UpdateChannels noop: " << operation_
;
599 // Update the "ap" value for the product being installed/updated. We get the
600 // current value from the registry since the InstallationState instance used
601 // by the bulk of the installer does not track changes made by UpdateStage.
602 // Create the app's ClientState key if it doesn't exist.
603 ChannelInfo channel_info
;
604 base::win::RegKey state_key
;
606 state_key
.Create(root_key_
,
608 KEY_QUERY_VALUE
| KEY_SET_VALUE
| KEY_WOW64_32KEY
);
609 if (result
== ERROR_SUCCESS
) {
610 channel_info
.Initialize(state_key
);
612 // This is a multi-install product.
613 bool modified
= channel_info
.SetMultiInstall(true);
615 // Add the appropriate modifiers for all products and their options.
616 modified
|= SetChannelFlags(true, &channel_info
);
618 VLOG(1) << "ap: " << channel_info
.value();
620 // Write the results if needed.
622 channel_info
.Write(&state_key
);
624 // Remove the -stage: modifier since we don't want to propagate that to the
626 channel_info
.SetStage(NULL
);
628 // Synchronize the other products and the package with this one.
629 ChannelInfo other_info
;
630 for (int i
= 0; i
< BrowserDistribution::NUM_TYPES
; ++i
) {
631 BrowserDistribution::Type type
=
632 static_cast<BrowserDistribution::Type
>(i
);
633 // Skip the app_guid we started with.
634 if (type
== state_type_
)
636 BrowserDistribution
* dist
= NULL
;
637 // Always operate on the binaries.
638 if (i
== BrowserDistribution::CHROME_BINARIES
) {
639 dist
= multi_package_distribution_
;
641 const Product
* product
= FindProduct(type
);
642 // Skip this one if it's for a product we're not operating on.
645 dist
= product
->distribution();
648 state_key
.Create(root_key_
,
649 dist
->GetStateKey().c_str(),
650 KEY_QUERY_VALUE
| KEY_SET_VALUE
| KEY_WOW64_32KEY
);
651 if (result
== ERROR_SUCCESS
) {
652 other_info
.Initialize(state_key
);
653 if (!other_info
.Equals(channel_info
))
654 channel_info
.Write(&state_key
);
656 LOG(ERROR
) << "Failed opening key " << dist
->GetStateKey()
657 << " to update app channels; result: " << result
;
661 LOG(ERROR
) << "Failed opening key " << state_key_
662 << " to update app channels; result: " << result
;
666 void InstallerState::WriteInstallerResult(
667 InstallStatus status
,
668 int string_resource_id
,
669 const std::wstring
* const launch_cmd
) const {
670 // Use a no-rollback list since this is a best-effort deal.
671 scoped_ptr
<WorkItemList
> install_list(
672 WorkItem::CreateNoRollbackWorkItemList());
673 const bool system_install
= this->system_install();
674 // Write the value for all products upon which we're operating.
675 Products::const_iterator end
= products().end();
676 for (Products::const_iterator scan
= products().begin(); scan
!= end
;
678 InstallUtil::AddInstallerResultItems(
679 system_install
, (*scan
)->distribution()->GetStateKey(), status
,
680 string_resource_id
, launch_cmd
, install_list
.get());
682 // And for the binaries if this is a multi-install.
683 if (is_multi_install()) {
684 InstallUtil::AddInstallerResultItems(
685 system_install
, multi_package_binaries_distribution()->GetStateKey(),
686 status
, string_resource_id
, launch_cmd
, install_list
.get());
688 if (!install_list
->Do())
689 LOG(ERROR
) << "Failed to record installer error information in registry.";
692 bool InstallerState::RequiresActiveSetup() const {
693 return system_install() && FindProduct(BrowserDistribution::CHROME_BROWSER
);
696 } // namespace installer