[Win] Add reporting of total number of modules loaded in browser process.
BUG=617176,619923 CQ_INCLUDE_TRYBOTS=master.tryserver.chromium.win:win10_chromium_x64_rel_ng Review-Url: https://codereview.chromium.org/2037883004 Cr-Commit-Position: refs/heads/master@{#415375}
This commit is contained in:
chrome/browser
tools/metrics/histograms
@ -294,16 +294,6 @@ enum NotificationType {
|
||||
// No details are expected.
|
||||
NOTIFICATION_OUTDATED_INSTALL_NO_AU,
|
||||
|
||||
// Software incompatibility notifications ----------------------------------
|
||||
|
||||
// Sent when Chrome has finished compiling the list of loaded modules (and
|
||||
// other modules of interest). No details are expected.
|
||||
NOTIFICATION_MODULE_LIST_ENUMERATED,
|
||||
|
||||
// Sent when Chrome is done scanning the module list and when the user has
|
||||
// acknowledged the module incompatibility. No details are expected.
|
||||
NOTIFICATION_MODULE_INCOMPATIBILITY_ICON_CHANGE,
|
||||
|
||||
// Content Settings --------------------------------------------------------
|
||||
|
||||
// Sent when the collect cookies dialog is shown. The source is a
|
||||
|
@ -25,7 +25,7 @@ namespace diagnostics {
|
||||
// This is the count of diagnostic tests on each platform. This should
|
||||
// only be used by testing code.
|
||||
#if defined(OS_WIN)
|
||||
const int DiagnosticsModel::kDiagnosticsTestCount = 18;
|
||||
const int DiagnosticsModel::kDiagnosticsTestCount = 17;
|
||||
#elif defined(OS_MACOSX)
|
||||
const int DiagnosticsModel::kDiagnosticsTestCount = 14;
|
||||
#elif defined(OS_POSIX)
|
||||
@ -151,7 +151,6 @@ class DiagnosticsModelWin : public DiagnosticsModelImpl {
|
||||
public:
|
||||
DiagnosticsModelWin() {
|
||||
tests_.push_back(MakeOperatingSystemTest());
|
||||
tests_.push_back(MakeConflictingDllsTest());
|
||||
tests_.push_back(MakeInstallTypeTest());
|
||||
tests_.push_back(MakeVersionTest());
|
||||
tests_.push_back(MakeUserDirTest());
|
||||
|
@ -52,62 +52,6 @@ const int64_t kOneMegabyte = 1024 * kOneKilobyte;
|
||||
class InstallTypeTest;
|
||||
InstallTypeTest* g_install_type = 0;
|
||||
|
||||
// Check if any conflicting DLLs are loaded.
|
||||
class ConflictingDllsTest : public DiagnosticsTest {
|
||||
public:
|
||||
ConflictingDllsTest()
|
||||
: DiagnosticsTest(DIAGNOSTICS_CONFLICTING_DLLS_TEST) {}
|
||||
|
||||
bool ExecuteImpl(DiagnosticsModel::Observer* observer) override {
|
||||
#if defined(OS_WIN)
|
||||
EnumerateModulesModel* model = EnumerateModulesModel::GetInstance();
|
||||
model->set_limited_mode(true);
|
||||
model->ScanNow();
|
||||
std::unique_ptr<base::ListValue> list(model->GetModuleList());
|
||||
if (!model->confirmed_bad_modules_detected() &&
|
||||
!model->suspected_bad_modules_detected()) {
|
||||
RecordSuccess("No conflicting modules found");
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string failures = "Possibly conflicting modules:";
|
||||
base::DictionaryValue* dictionary;
|
||||
for (size_t i = 0; i < list->GetSize(); ++i) {
|
||||
if (!list->GetDictionary(i, &dictionary))
|
||||
RecordFailure(DIAG_RECON_DICTIONARY_LOOKUP_FAILED,
|
||||
"Dictionary lookup failed");
|
||||
int status;
|
||||
std::string location;
|
||||
std::string name;
|
||||
if (!dictionary->GetInteger("status", &status))
|
||||
RecordFailure(DIAG_RECON_NO_STATUS_FIELD, "No 'status' field found");
|
||||
if (status < ModuleEnumerator::SUSPECTED_BAD)
|
||||
continue;
|
||||
|
||||
if (!dictionary->GetString("location", &location)) {
|
||||
RecordFailure(DIAG_RECON_NO_LOCATION_FIELD,
|
||||
"No 'location' field found");
|
||||
return true;
|
||||
}
|
||||
if (!dictionary->GetString("name", &name)) {
|
||||
RecordFailure(DIAG_RECON_NO_NAME_FIELD, "No 'name' field found");
|
||||
return true;
|
||||
}
|
||||
|
||||
failures += "\n" + location + name;
|
||||
}
|
||||
RecordFailure(DIAG_RECON_CONFLICTING_MODULES, failures);
|
||||
return true;
|
||||
#else
|
||||
RecordFailure(DIAG_RECON_NOT_IMPLEMENTED, "Not implemented");
|
||||
return true;
|
||||
#endif // defined(OS_WIN)
|
||||
}
|
||||
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(ConflictingDllsTest);
|
||||
};
|
||||
|
||||
// Check that the disk space in the volume where the user data directory
|
||||
// normally lives is not dangerously low.
|
||||
class DiskSpaceTest : public DiagnosticsTest {
|
||||
@ -386,8 +330,6 @@ class VersionTest : public DiagnosticsTest {
|
||||
|
||||
} // namespace
|
||||
|
||||
DiagnosticsTest* MakeConflictingDllsTest() { return new ConflictingDllsTest(); }
|
||||
|
||||
DiagnosticsTest* MakeDiskSpaceTest() { return new DiskSpaceTest(); }
|
||||
|
||||
DiagnosticsTest* MakeInstallTypeTest() { return new InstallTypeTest(); }
|
||||
|
@ -15,14 +15,6 @@ enum OutcomeCodes {
|
||||
// OperatingSystemTest
|
||||
DIAG_RECON_PRE_WINDOW_XP_SP2,
|
||||
|
||||
// ConflictingDllsTest
|
||||
DIAG_RECON_DICTIONARY_LOOKUP_FAILED,
|
||||
DIAG_RECON_NO_STATUS_FIELD,
|
||||
DIAG_RECON_NO_NAME_FIELD,
|
||||
DIAG_RECON_NO_LOCATION_FIELD,
|
||||
DIAG_RECON_CONFLICTING_MODULES,
|
||||
DIAG_RECON_NOT_IMPLEMENTED,
|
||||
|
||||
// InstallTypeTest
|
||||
DIAG_RECON_INSTALL_PATH_PROVIDER,
|
||||
|
||||
@ -52,7 +44,6 @@ enum OutcomeCodes {
|
||||
};
|
||||
|
||||
DiagnosticsTest* MakeBookMarksTest();
|
||||
DiagnosticsTest* MakeConflictingDllsTest();
|
||||
DiagnosticsTest* MakeDictonaryDirTest();
|
||||
DiagnosticsTest* MakeDiskSpaceTest();
|
||||
DiagnosticsTest* MakeInstallTypeTest();
|
||||
|
@ -189,13 +189,11 @@ void ShowExtensions(Browser* browser,
|
||||
void ShowConflicts(Browser* browser) {
|
||||
#if defined(OS_WIN)
|
||||
EnumerateModulesModel* model = EnumerateModulesModel::GetInstance();
|
||||
if (model->modules_to_notify_about() > 0) {
|
||||
GURL help_center_url = model->GetFirstNotableConflict();
|
||||
if (help_center_url.is_valid()) {
|
||||
ShowSingletonTab(browser, help_center_url);
|
||||
model->AcknowledgeConflictNotification();
|
||||
return;
|
||||
}
|
||||
GURL conflict_url = model->GetConflictUrl();
|
||||
if (conflict_url.is_valid()) {
|
||||
ShowSingletonTab(browser, conflict_url);
|
||||
model->AcknowledgeConflictNotification();
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -66,9 +66,7 @@ bool ShouldShowUpgradeRecommended() {
|
||||
// Returns true if we should show the warning for incompatible software.
|
||||
bool ShouldShowIncompatibilityWarning() {
|
||||
#if defined(OS_WIN)
|
||||
EnumerateModulesModel* loaded_modules = EnumerateModulesModel::GetInstance();
|
||||
loaded_modules->MaybePostScanningTask();
|
||||
return loaded_modules->ShouldShowConflictWarning();
|
||||
return EnumerateModulesModel::GetInstance()->ShouldShowConflictWarning();
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
@ -88,12 +86,17 @@ AppMenuIconController::AppMenuIconController(Profile* profile,
|
||||
content::Source<Profile>(profile_));
|
||||
|
||||
#if defined(OS_WIN)
|
||||
registrar_.Add(this, chrome::NOTIFICATION_MODULE_INCOMPATIBILITY_ICON_CHANGE,
|
||||
content::NotificationService::AllSources());
|
||||
auto* modules = EnumerateModulesModel::GetInstance();
|
||||
modules->AddObserver(this);
|
||||
modules->MaybePostScanningTask();
|
||||
#endif
|
||||
}
|
||||
|
||||
AppMenuIconController::~AppMenuIconController() {}
|
||||
AppMenuIconController::~AppMenuIconController() {
|
||||
#if defined(OS_WIN)
|
||||
EnumerateModulesModel::GetInstance()->RemoveObserver(this);
|
||||
#endif
|
||||
}
|
||||
|
||||
void AppMenuIconController::UpdateDelegate() {
|
||||
if (ShouldShowUpgradeRecommended()) {
|
||||
@ -123,6 +126,17 @@ void AppMenuIconController::UpdateDelegate() {
|
||||
delegate_->UpdateSeverity(IconType::NONE,
|
||||
AppMenuIconPainter::SEVERITY_NONE, true);
|
||||
}
|
||||
|
||||
#if defined(OS_WIN)
|
||||
void AppMenuIconController::OnScanCompleted() {
|
||||
UpdateDelegate();
|
||||
}
|
||||
|
||||
void AppMenuIconController::OnConflictsAcknowledged() {
|
||||
UpdateDelegate();
|
||||
}
|
||||
#endif
|
||||
|
||||
void AppMenuIconController::Observe(
|
||||
int type,
|
||||
const content::NotificationSource& source,
|
||||
|
@ -12,11 +12,19 @@
|
||||
#include "content/public/browser/notification_registrar.h"
|
||||
#include "content/public/browser/notification_service.h"
|
||||
|
||||
#if defined(OS_WIN)
|
||||
#include "chrome/browser/win/enumerate_modules_model.h"
|
||||
#endif
|
||||
|
||||
class Profile;
|
||||
|
||||
// AppMenuIconController encapsulates the logic for badging the app menu icon
|
||||
// as a result of various events - such as available updates, errors, etc.
|
||||
class AppMenuIconController : public content::NotificationObserver {
|
||||
class AppMenuIconController :
|
||||
#if defined(OS_WIN)
|
||||
public EnumerateModulesModel::Observer,
|
||||
#endif
|
||||
public content::NotificationObserver {
|
||||
public:
|
||||
enum class IconType {
|
||||
NONE,
|
||||
@ -56,6 +64,12 @@ class AppMenuIconController : public content::NotificationObserver {
|
||||
const content::NotificationSource& source,
|
||||
const content::NotificationDetails& details) override;
|
||||
|
||||
#if defined(OS_WIN)
|
||||
// EnumerateModulesModel:
|
||||
void OnScanCompleted() override;
|
||||
void OnConflictsAcknowledged() override;
|
||||
#endif
|
||||
|
||||
Profile* profile_;
|
||||
Delegate* delegate_;
|
||||
content::NotificationRegistrar registrar_;
|
||||
|
@ -683,24 +683,14 @@ bool AppMenuModel::IsCommandIdVisible(int command_id) const {
|
||||
case kEmptyMenuItemCommand:
|
||||
return false; // Always hidden (see CreateActionToolbarOverflowMenu).
|
||||
#endif
|
||||
#if defined(OS_WIN)
|
||||
case IDC_VIEW_INCOMPATIBILITIES: {
|
||||
EnumerateModulesModel* loaded_modules =
|
||||
EnumerateModulesModel::GetInstance();
|
||||
if (loaded_modules->confirmed_bad_modules_detected() <= 0)
|
||||
return false;
|
||||
// We'll leave the app menu adornment on until the user clicks the link.
|
||||
if (loaded_modules->modules_to_notify_about() <= 0)
|
||||
loaded_modules->AcknowledgeConflictNotification();
|
||||
return true;
|
||||
}
|
||||
case IDC_PIN_TO_START_SCREEN:
|
||||
return false;
|
||||
#else
|
||||
case IDC_VIEW_INCOMPATIBILITIES:
|
||||
case IDC_PIN_TO_START_SCREEN:
|
||||
#if defined(OS_WIN)
|
||||
return EnumerateModulesModel::GetInstance()->ShouldShowConflictWarning();
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
case IDC_PIN_TO_START_SCREEN:
|
||||
return false;
|
||||
case IDC_UPGRADE_DIALOG:
|
||||
return browser_defaults::kShowUpgradeMenuItem &&
|
||||
UpgradeDetector::GetInstance()->notify_upgrade();
|
||||
|
@ -9,7 +9,6 @@
|
||||
#include "chrome/browser/profiles/profile.h"
|
||||
#include "chrome/browser/ui/browser.h"
|
||||
#include "chrome/browser/ui/layout_constants.h"
|
||||
#include "chrome/browser/win/enumerate_modules_model.h"
|
||||
#include "chrome/common/pref_names.h"
|
||||
#include "chrome/grit/chromium_strings.h"
|
||||
#include "chrome/grit/generated_resources.h"
|
||||
@ -44,6 +43,7 @@ ConflictingModuleView::ConflictingModuleView(views::View* anchor_view,
|
||||
const GURL& help_center_url)
|
||||
: BubbleDialogDelegateView(anchor_view, views::BubbleBorder::TOP_RIGHT),
|
||||
browser_(browser),
|
||||
observer_(this),
|
||||
help_center_url_(help_center_url) {
|
||||
set_close_on_deactivate(false);
|
||||
|
||||
@ -51,8 +51,7 @@ ConflictingModuleView::ConflictingModuleView(views::View* anchor_view,
|
||||
set_anchor_view_insets(gfx::Insets(
|
||||
GetLayoutConstant(LOCATION_BAR_BUBBLE_ANCHOR_VERTICAL_INSET), 0));
|
||||
|
||||
registrar_.Add(this, chrome::NOTIFICATION_MODULE_INCOMPATIBILITY_ICON_CHANGE,
|
||||
content::NotificationService::AllSources());
|
||||
observer_.Add(EnumerateModulesModel::GetInstance());
|
||||
}
|
||||
|
||||
// static
|
||||
@ -62,8 +61,8 @@ void ConflictingModuleView::MaybeShow(Browser* browser,
|
||||
if (done_checking)
|
||||
return; // Only show the bubble once per launch.
|
||||
|
||||
EnumerateModulesModel* model = EnumerateModulesModel::GetInstance();
|
||||
GURL url = model->GetFirstNotableConflict();
|
||||
auto* model = EnumerateModulesModel::GetInstance();
|
||||
GURL url = model->GetConflictUrl();
|
||||
if (!url.is_valid()) {
|
||||
done_checking = true;
|
||||
return;
|
||||
@ -163,11 +162,7 @@ void ConflictingModuleView::GetAccessibleState(
|
||||
state->role = ui::AX_ROLE_ALERT_DIALOG;
|
||||
}
|
||||
|
||||
void ConflictingModuleView::Observe(
|
||||
int type,
|
||||
const content::NotificationSource& source,
|
||||
const content::NotificationDetails& details) {
|
||||
DCHECK_EQ(chrome::NOTIFICATION_MODULE_INCOMPATIBILITY_ICON_CHANGE, type);
|
||||
void ConflictingModuleView::OnConflictsAcknowledged() {
|
||||
EnumerateModulesModel* model = EnumerateModulesModel::GetInstance();
|
||||
if (!model->ShouldShowConflictWarning())
|
||||
GetWidget()->Close();
|
||||
|
@ -6,8 +6,8 @@
|
||||
#define CHROME_BROWSER_UI_VIEWS_CONFLICTING_MODULE_VIEW_WIN_H_
|
||||
|
||||
#include "base/macros.h"
|
||||
#include "content/public/browser/notification_observer.h"
|
||||
#include "content/public/browser/notification_registrar.h"
|
||||
#include "base/scoped_observer.h"
|
||||
#include "chrome/browser/win/enumerate_modules_model.h"
|
||||
#include "ui/views/bubble/bubble_dialog_delegate.h"
|
||||
#include "url/gurl.h"
|
||||
|
||||
@ -16,7 +16,7 @@ class Browser;
|
||||
// This is the class that implements the UI for the bubble showing that there
|
||||
// is a 3rd party module loaded that conflicts with Chrome.
|
||||
class ConflictingModuleView : public views::BubbleDialogDelegateView,
|
||||
public content::NotificationObserver {
|
||||
public EnumerateModulesModel::Observer {
|
||||
public:
|
||||
ConflictingModuleView(views::View* anchor_view,
|
||||
Browser* browser,
|
||||
@ -40,15 +40,13 @@ class ConflictingModuleView : public views::BubbleDialogDelegateView,
|
||||
// views::View implementation.
|
||||
void GetAccessibleState(ui::AXViewState* state) override;
|
||||
|
||||
// content::NotificationObserver implementation.
|
||||
void Observe(
|
||||
int type,
|
||||
const content::NotificationSource& source,
|
||||
const content::NotificationDetails& details) override;
|
||||
// EnumerateModulesModel::Observer:
|
||||
void OnConflictsAcknowledged() override;
|
||||
|
||||
Browser* browser_;
|
||||
|
||||
content::NotificationRegistrar registrar_;
|
||||
ScopedObserver<EnumerateModulesModel,
|
||||
EnumerateModulesModel::Observer> observer_;
|
||||
|
||||
// The link to the help center for this conflict.
|
||||
GURL help_center_url_;
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include "base/bind_helpers.h"
|
||||
#include "base/macros.h"
|
||||
#include "base/memory/ref_counted_memory.h"
|
||||
#include "base/scoped_observer.h"
|
||||
#include "base/strings/string_number_conversions.h"
|
||||
#include "base/strings/utf_string_conversions.h"
|
||||
#include "base/values.h"
|
||||
@ -77,11 +78,11 @@ content::WebUIDataSource* CreateConflictsUIHTMLSource() {
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// The handler for JavaScript messages for the about:flags page.
|
||||
// The handler for JavaScript messages for the about:conflicts page.
|
||||
class ConflictsDOMHandler : public WebUIMessageHandler,
|
||||
public content::NotificationObserver {
|
||||
public EnumerateModulesModel::Observer {
|
||||
public:
|
||||
ConflictsDOMHandler() {}
|
||||
ConflictsDOMHandler();
|
||||
~ConflictsDOMHandler() override {}
|
||||
|
||||
// WebUIMessageHandler implementation.
|
||||
@ -93,15 +94,19 @@ class ConflictsDOMHandler : public WebUIMessageHandler,
|
||||
private:
|
||||
void SendModuleList();
|
||||
|
||||
void Observe(int type,
|
||||
const content::NotificationSource& source,
|
||||
const content::NotificationDetails& details) override;
|
||||
// EnumerateModulesModel::Observer implementation.
|
||||
void OnScanCompleted() override;
|
||||
|
||||
content::NotificationRegistrar registrar_;
|
||||
ScopedObserver<EnumerateModulesModel,
|
||||
EnumerateModulesModel::Observer> observer_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(ConflictsDOMHandler);
|
||||
};
|
||||
|
||||
ConflictsDOMHandler::ConflictsDOMHandler()
|
||||
: observer_(this) {
|
||||
}
|
||||
|
||||
void ConflictsDOMHandler::RegisterMessages() {
|
||||
web_ui()->RegisterMessageCallback("requestModuleList",
|
||||
base::Bind(&ConflictsDOMHandler::HandleRequestModuleList,
|
||||
@ -109,14 +114,20 @@ void ConflictsDOMHandler::RegisterMessages() {
|
||||
}
|
||||
|
||||
void ConflictsDOMHandler::HandleRequestModuleList(const base::ListValue* args) {
|
||||
// This request is handled asynchronously. See Observe for when we reply back.
|
||||
registrar_.Add(this, chrome::NOTIFICATION_MODULE_LIST_ENUMERATED,
|
||||
content::NotificationService::AllSources());
|
||||
EnumerateModulesModel::GetInstance()->ScanNow();
|
||||
// The request is handled asynchronously, and will callback via
|
||||
// OnScanCompleted on completion.
|
||||
auto* model = EnumerateModulesModel::GetInstance();
|
||||
|
||||
// The JS shouldn't be abusive and call 'requestModuleList' twice, but it's
|
||||
// easy enough to defend against this.
|
||||
if (!observer_.IsObserving(model)) {
|
||||
observer_.Add(model);
|
||||
model->ScanNow();
|
||||
}
|
||||
}
|
||||
|
||||
void ConflictsDOMHandler::SendModuleList() {
|
||||
EnumerateModulesModel* loaded_modules = EnumerateModulesModel::GetInstance();
|
||||
auto* loaded_modules = EnumerateModulesModel::GetInstance();
|
||||
base::ListValue* list = loaded_modules->GetModuleList();
|
||||
base::DictionaryValue results;
|
||||
results.Set("moduleList", list);
|
||||
@ -141,13 +152,9 @@ void ConflictsDOMHandler::SendModuleList() {
|
||||
web_ui()->CallJavascriptFunctionUnsafe("returnModuleList", results);
|
||||
}
|
||||
|
||||
void ConflictsDOMHandler::Observe(int type,
|
||||
const content::NotificationSource& source,
|
||||
const content::NotificationDetails& details) {
|
||||
DCHECK_EQ(chrome::NOTIFICATION_MODULE_LIST_ENUMERATED, type);
|
||||
|
||||
void ConflictsDOMHandler::OnScanCompleted() {
|
||||
SendModuleList();
|
||||
registrar_.RemoveAll();
|
||||
observer_.Remove(EnumerateModulesModel::GetInstance());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -5,13 +5,13 @@
|
||||
#ifndef CHROME_BROWSER_WIN_ENUMERATE_MODULES_MODEL_H_
|
||||
#define CHROME_BROWSER_WIN_ENUMERATE_MODULES_MODEL_H_
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "base/gtest_prod_util.h"
|
||||
#include "base/macros.h"
|
||||
#include "base/memory/ref_counted.h"
|
||||
#include "base/memory/singleton.h"
|
||||
#include "base/observer_list.h"
|
||||
#include "base/strings/string16.h"
|
||||
#include "base/timer/timer.h"
|
||||
#include "content/public/browser/browser_thread.h"
|
||||
@ -24,9 +24,10 @@ class FilePath;
|
||||
class ListValue;
|
||||
}
|
||||
|
||||
// A helper class that implements the enumerate module functionality on the File
|
||||
// thread.
|
||||
class ModuleEnumerator : public base::RefCountedThreadSafe<ModuleEnumerator> {
|
||||
// A helper class that implements the enumerate module functionality on the FILE
|
||||
// thread. Not to be used directly.
|
||||
// TODO(chrisha): Move this to a separate .h and .cc.
|
||||
class ModuleEnumerator {
|
||||
public:
|
||||
// What type of module we are dealing with. Loaded modules are modules we
|
||||
// detect as loaded in the process at the time of scanning. The others are
|
||||
@ -115,17 +116,6 @@ class ModuleEnumerator : public base::RefCountedThreadSafe<ModuleEnumerator> {
|
||||
// A vector typedef of all modules enumerated.
|
||||
typedef std::vector<Module> ModulesVector;
|
||||
|
||||
// A structure we populate with the blacklist entries.
|
||||
struct BlacklistEntry {
|
||||
const char* filename;
|
||||
const char* location;
|
||||
const char* desc_or_signer;
|
||||
const char* version_from; // Version where conflict started.
|
||||
const char* version_to; // First version that works.
|
||||
OperatingSystem os; // Bitmask, representing what OS this entry applies to.
|
||||
RecommendedAction help_tip;
|
||||
};
|
||||
|
||||
// A static function that normalizes the module information in the |module|
|
||||
// struct. Module information needs to be normalized before comparing against
|
||||
// the blacklist. This is because the same module can be described in many
|
||||
@ -135,39 +125,26 @@ class ModuleEnumerator : public base::RefCountedThreadSafe<ModuleEnumerator> {
|
||||
// against the blacklist.
|
||||
static void NormalizeModule(Module* module);
|
||||
|
||||
// A static function that checks whether |module| has been |blacklisted|.
|
||||
static ModuleStatus Match(const Module& module,
|
||||
const BlacklistEntry& blacklisted);
|
||||
|
||||
// Constructs a ModuleEnumerator that will notify the provided |observer| once
|
||||
// enumeration is complete. |observer| must outlive the ModuleEnumerator.
|
||||
explicit ModuleEnumerator(EnumerateModulesModel* observer);
|
||||
|
||||
~ModuleEnumerator();
|
||||
|
||||
// Start scanning the loaded module list (if a scan is not already in
|
||||
// progress). This function does not block while reading the module list
|
||||
// (unless we are in limited_mode, see below), and will notify when done
|
||||
// through the MODULE_LIST_ENUMERATED notification.
|
||||
// The process will also send MODULE_INCOMPATIBILITY_BADGE_CHANGE to let
|
||||
// observers know when it is time to update the wrench menu badge.
|
||||
// When in |limited_mode|, this function will not leverage the File thread
|
||||
// to run asynchronously and will therefore block until scanning is done
|
||||
// (and will also not send out any notifications).
|
||||
void ScanNow(ModulesVector* list, bool limited_mode);
|
||||
// progress). This function does not block while reading the module list and
|
||||
// will notify when done by calling the DoneScanning method of |observer_|.
|
||||
void ScanNow(ModulesVector* list);
|
||||
|
||||
private:
|
||||
FRIEND_TEST_ALL_PREFIXES(EnumerateModulesTest, CollapsePath);
|
||||
|
||||
friend class base::RefCountedThreadSafe<ModuleEnumerator>;
|
||||
~ModuleEnumerator();
|
||||
|
||||
// The (currently) hard coded blacklist of known bad modules.
|
||||
static const BlacklistEntry kModuleBlacklist[];
|
||||
|
||||
// This function does the actual file scanning work on the FILE thread (or
|
||||
// block the main thread when in limited_mode). It enumerates all loaded
|
||||
// modules in the process and other modules of interest, such as the
|
||||
// registered Winsock LSP modules and stores them in |enumerated_modules_|.
|
||||
// It then normalizes the module info and matches them against a blacklist
|
||||
// of known bad modules. Finally, it calls ReportBack to let the observer
|
||||
// know we are done.
|
||||
// This function does the actual file scanning work on the FILE thread. It
|
||||
// enumerates all loaded modules in the process and other modules of interest,
|
||||
// such as the registered Winsock LSP modules and stores them in
|
||||
// |enumerated_modules_|. It then normalizes the module info and matches them
|
||||
// against a blacklist of known bad modules. Finally, notifies the observer
|
||||
// that the enumeration is complete by invoking DoneScanning.
|
||||
void ScanImpl();
|
||||
|
||||
// Enumerate all modules loaded into the Chrome process.
|
||||
@ -205,21 +182,18 @@ class ModuleEnumerator : public base::RefCountedThreadSafe<ModuleEnumerator> {
|
||||
// based on the |path_mapping_| vector.
|
||||
void CollapsePath(Module* module);
|
||||
|
||||
// Takes each module in the |enumerated_modules_| vector and matches it
|
||||
// against a fixed blacklist of bad and suspected bad modules.
|
||||
void MatchAgainstBlacklist();
|
||||
|
||||
// This function executes on the UI thread when the scanning and matching
|
||||
// process is done. It notifies the observer.
|
||||
void ReportBack();
|
||||
|
||||
// Given a filename, returns the Subject (who signed it) retrieved from
|
||||
// the digital signature (Authenticode).
|
||||
base::string16 GetSubjectNameFromDigitalSignature(
|
||||
const base::FilePath& filename);
|
||||
|
||||
// Reports (via UMA) a handful of high-level metrics regarding third party
|
||||
// modules in this process. Called by ScanImpl after modules have been
|
||||
// enumerated and processed.
|
||||
void ReportThirdPartyMetrics();
|
||||
|
||||
// The typedef for the vector that maps a regular file path to %env_var%.
|
||||
typedef std::vector< std::pair<base::string16, base::string16> > PathMapping;
|
||||
typedef std::vector<std::pair<base::string16, base::string16>> PathMapping;
|
||||
|
||||
// The vector of paths to %env_var%, used to account for differences in
|
||||
// where people keep there files, c:\windows vs. d:\windows, etc.
|
||||
@ -229,15 +203,9 @@ class ModuleEnumerator : public base::RefCountedThreadSafe<ModuleEnumerator> {
|
||||
// interest).
|
||||
ModulesVector* enumerated_modules_;
|
||||
|
||||
// The observer, who needs to be notified when we are done.
|
||||
// The observer, which needs to be notified when the scan is complete.
|
||||
EnumerateModulesModel* observer_;
|
||||
|
||||
// See limited_mode below.
|
||||
bool limited_mode_;
|
||||
|
||||
// The thread that we need to call back on to report that we are done.
|
||||
content::BrowserThread::ID callback_thread_id_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(ModuleEnumerator);
|
||||
};
|
||||
|
||||
@ -251,9 +219,12 @@ class ModuleEnumerator : public base::RefCountedThreadSafe<ModuleEnumerator> {
|
||||
// Then wait to get notified through MODULE_LIST_ENUMERATED when the list is
|
||||
// ready.
|
||||
//
|
||||
// This class can be used on the UI thread as it asynchronously offloads the
|
||||
// file work over to the FILE thread and reports back to the caller with a
|
||||
// notification.
|
||||
// The member functions of this class may only be used from the UI thread. The
|
||||
// bulk of the work is actually performed on the FILE thread.
|
||||
//
|
||||
// TODO(chrisha): If this logic is ever extended to other platforms, then make
|
||||
// this file generic for all platforms, and remove the precompiler logic in
|
||||
// app_menu_icon_controller.*.
|
||||
class EnumerateModulesModel {
|
||||
public:
|
||||
// UMA histogram constants.
|
||||
@ -264,8 +235,31 @@ class EnumerateModulesModel {
|
||||
ACTION_BOUNDARY, // Must be the last value.
|
||||
};
|
||||
|
||||
// Observer class used to determine when a scan has completed and when any
|
||||
// associated UI elements have been dismissed.
|
||||
class Observer {
|
||||
public:
|
||||
// Invoked when EnumerateModulesModel has completed a scan of modules.
|
||||
virtual void OnScanCompleted() {}
|
||||
|
||||
// Invoked when a user has acknowledged incompatible modules found in a
|
||||
// module scan.
|
||||
virtual void OnConflictsAcknowledged() {}
|
||||
|
||||
protected:
|
||||
virtual ~Observer() = default;
|
||||
};
|
||||
|
||||
// Returns the singleton instance of this class.
|
||||
static EnumerateModulesModel* GetInstance();
|
||||
|
||||
// Adds an |observer| to the enumerator. Callbacks will occur on the UI
|
||||
// thread.
|
||||
void AddObserver(Observer* observer);
|
||||
|
||||
// Removes an |observer| from the enumerator.
|
||||
void RemoveObserver(Observer* observer);
|
||||
|
||||
// Returns true if we should show the conflict notification. The conflict
|
||||
// notification is only shown once during the lifetime of the process.
|
||||
bool ShouldShowConflictWarning() const;
|
||||
@ -275,91 +269,71 @@ class EnumerateModulesModel {
|
||||
|
||||
// Returns the number of suspected bad modules found in the last scan.
|
||||
// Returns 0 if no scan has taken place yet.
|
||||
int suspected_bad_modules_detected() const {
|
||||
return suspected_bad_modules_detected_;
|
||||
}
|
||||
int suspected_bad_modules_detected() const;
|
||||
|
||||
// Returns the number of confirmed bad modules found in the last scan.
|
||||
// Returns 0 if no scan has taken place yet.
|
||||
int confirmed_bad_modules_detected() const {
|
||||
return confirmed_bad_modules_detected_;
|
||||
}
|
||||
int confirmed_bad_modules_detected() const;
|
||||
|
||||
// Returns how many modules to notify the user about.
|
||||
int modules_to_notify_about() const {
|
||||
return modules_to_notify_about_;
|
||||
}
|
||||
|
||||
// Set to true when we the scanning process can not rely on certain Chrome
|
||||
// services to exists.
|
||||
void set_limited_mode(bool limited_mode) {
|
||||
limited_mode_ = limited_mode;
|
||||
}
|
||||
int modules_to_notify_about() const;
|
||||
|
||||
// Checks to see if a scanning task should be started and sets one off, if so.
|
||||
void MaybePostScanningTask();
|
||||
|
||||
// Asynchronously start the scan for the loaded module list, except when in
|
||||
// limited_mode (in which case it blocks).
|
||||
// Asynchronously start the scan for the loaded module list.
|
||||
void ScanNow();
|
||||
|
||||
// Gets the whole module list as a ListValue.
|
||||
base::ListValue* GetModuleList() const;
|
||||
base::ListValue* GetModuleList();
|
||||
|
||||
// Gets the Help Center URL for the first *notable* conflict module that we've
|
||||
// elected to notify the user about.
|
||||
GURL GetFirstNotableConflict();
|
||||
// Returns the site to which the user should be taken when the conflict bubble
|
||||
// or app menu item is clicked. For now this is simply chrome://conflicts,
|
||||
// which contains detailed information about conflicts. Returns an empty URL
|
||||
// if there are no conficts. May only be called on UI thread.
|
||||
GURL GetConflictUrl();
|
||||
|
||||
private:
|
||||
friend struct base::DefaultSingletonTraits<EnumerateModulesModel>;
|
||||
friend class ModuleEnumerator;
|
||||
|
||||
// Private to enforce singleton nature of this class.
|
||||
EnumerateModulesModel();
|
||||
virtual ~EnumerateModulesModel();
|
||||
~EnumerateModulesModel();
|
||||
|
||||
// Called on the UI thread when the helper class is done scanning.
|
||||
// Called on the UI thread when the helper class is done scanning. The
|
||||
// ModuleEnumerator that calls this must not do any work after causing this
|
||||
// function to be called, as the EnumerateModulesModel may delete the
|
||||
// ModuleEnumerator.
|
||||
void DoneScanning();
|
||||
|
||||
// Constructs a Help Center article URL for help with a particular module.
|
||||
// The module must have the SEE_LINK attribute for |recommended_action| set,
|
||||
// otherwise this returns a blank string.
|
||||
GURL ConstructHelpCenterUrl(const ModuleEnumerator::Module& module) const;
|
||||
|
||||
// The vector containing all the modules enumerated. Will be normalized and
|
||||
// any bad modules will be marked.
|
||||
// any bad modules will be marked. Written to on the FILE thread by the
|
||||
// |module_enumerator_|, read from on the UI thread by this class.
|
||||
ModuleEnumerator::ModulesVector enumerated_modules_;
|
||||
|
||||
// The object responsible for enumerating the modules on the File thread.
|
||||
scoped_refptr<ModuleEnumerator> module_enumerator_;
|
||||
// The object responsible for enumerating the modules on the FILE thread.
|
||||
// Only used from the UI thread. This ends up internally doing its work on the
|
||||
// FILE thread.
|
||||
std::unique_ptr<ModuleEnumerator> module_enumerator_;
|
||||
|
||||
// When this singleton object is constructed we go and fire off this timer to
|
||||
// start scanning for modules after a certain amount of time has passed.
|
||||
base::OneShotTimer check_modules_timer_;
|
||||
|
||||
// While normally |false|, this mode can be set to indicate that the scanning
|
||||
// process should not rely on certain services normally available to Chrome,
|
||||
// such as the resource bundle and the notification system, not to mention
|
||||
// having multiple threads. This mode is useful during diagnostics, which
|
||||
// runs without firing up all necessary Chrome services first.
|
||||
bool limited_mode_;
|
||||
|
||||
// True if we are currently scanning for modules.
|
||||
bool scanning_;
|
||||
|
||||
// Whether the conflict notification has been acknowledged by the user.
|
||||
// Whether the conflict notification has been acknowledged by the user. Only
|
||||
// modified on the UI thread.
|
||||
bool conflict_notification_acknowledged_;
|
||||
|
||||
// The number of confirmed bad modules (not including suspected bad ones)
|
||||
// found during last scan.
|
||||
// found during last scan. Only modified on the UI thread.
|
||||
int confirmed_bad_modules_detected_;
|
||||
|
||||
// The number of bad modules the user needs to be aggressively notified about.
|
||||
// Only modified on the UI thread.
|
||||
int modules_to_notify_about_;
|
||||
|
||||
// The number of suspected bad modules (not including confirmed bad ones)
|
||||
// found during last scan.
|
||||
// found during last scan. Only modified on the UI thread.
|
||||
int suspected_bad_modules_detected_;
|
||||
|
||||
base::ObserverList<Observer> observers_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(EnumerateModulesModel);
|
||||
};
|
||||
|
||||
|
@ -24,9 +24,6 @@ static const ModuleEnumerator::ModuleStatus kStatus =
|
||||
static const ModuleEnumerator::RecommendedAction kAction =
|
||||
ModuleEnumerator::NONE;
|
||||
|
||||
static const ModuleEnumerator::OperatingSystem kOs =
|
||||
ModuleEnumerator::ALL;
|
||||
|
||||
// This is a list of test cases to normalize.
|
||||
static const struct NormalizationEntryList {
|
||||
ModuleEnumerator::Module test_case;
|
||||
@ -92,125 +89,6 @@ const ModuleEnumerator::Module kStandardModuleNoSignature =
|
||||
{ kType, kStatus, L"c:\\foo\\bar.dll", L"", L"Prod", L"Desc", L"1.0", L"",
|
||||
ModuleEnumerator::NONE };
|
||||
|
||||
// Name, location, description and signature are compared by hashing.
|
||||
static const char kMatchName[] = "88e8c9e0"; // "bar.dll".
|
||||
static const char kMatchLocation[] = "e6ca7b1c"; // "c:\\foo\\".
|
||||
static const char kNoMatchLocation[] = "c:\\foobar\\";
|
||||
static const char kMatchDesc[] = "5c4419a6"; // "Desc".
|
||||
static const char kVersionHigh[] = "2.0";
|
||||
static const char kVersionLow[] = "0.5";
|
||||
static const char kMatchSignature[] = "7bfd87e1"; // "Sig".
|
||||
static const char kEmpty[] = "";
|
||||
|
||||
const struct MatchingEntryList {
|
||||
ModuleEnumerator::ModuleStatus expected_result;
|
||||
ModuleEnumerator::Module test_case;
|
||||
ModuleEnumerator::BlacklistEntry blacklist;
|
||||
} kMatchineEntryList[] = {
|
||||
// Each BlacklistEntry is:
|
||||
// Filename, location, desc_or_signer, version from, version to, help_tip.
|
||||
|
||||
{ // Matches: Name (location doesn't match) => Not enough for a match.
|
||||
ModuleEnumerator::NOT_MATCHED,
|
||||
kStandardModule,
|
||||
{ kMatchName, kNoMatchLocation, kEmpty, kEmpty, kEmpty, kOs,
|
||||
ModuleEnumerator::SEE_LINK }
|
||||
}, { // Matches: Name (location not given) => Suspected match.
|
||||
ModuleEnumerator::SUSPECTED_BAD,
|
||||
kStandardModule,
|
||||
{ kMatchName, kEmpty, kEmpty, kEmpty, kEmpty, kOs,
|
||||
ModuleEnumerator::SEE_LINK }
|
||||
}, { // Matches: Name, not version (location not given) => Not a match.
|
||||
ModuleEnumerator::NOT_MATCHED,
|
||||
kStandardModule,
|
||||
{ kMatchName, kEmpty, kEmpty, kVersionHigh, kVersionHigh, kOs,
|
||||
ModuleEnumerator::SEE_LINK }
|
||||
}, { // Matches: Name, location => Suspected match.
|
||||
ModuleEnumerator::SUSPECTED_BAD,
|
||||
kStandardModule,
|
||||
{ kMatchName, kMatchLocation, kEmpty, kEmpty, kEmpty, kOs,
|
||||
ModuleEnumerator::SEE_LINK }
|
||||
}, { // Matches: Name, location, (description not given) => Confirmed match.
|
||||
ModuleEnumerator::CONFIRMED_BAD,
|
||||
kStandardModuleNoDescription, // Note: No description.
|
||||
{ kMatchName, kMatchLocation, kEmpty, kEmpty, kEmpty, kOs,
|
||||
ModuleEnumerator::SEE_LINK }
|
||||
}, { // Matches: Name, location, (signature not given) => Confirmed match.
|
||||
ModuleEnumerator::CONFIRMED_BAD,
|
||||
kStandardModuleNoSignature, // Note: No signature.
|
||||
{ kMatchName, kMatchLocation, kEmpty, kEmpty, kEmpty, kOs,
|
||||
ModuleEnumerator::SEE_LINK }
|
||||
}, { // Matches: Name, location (not version) => Not a match.
|
||||
ModuleEnumerator::NOT_MATCHED,
|
||||
kStandardModule,
|
||||
{ kMatchName, kMatchLocation, kEmpty, kVersionHigh, kVersionLow, kOs,
|
||||
ModuleEnumerator::SEE_LINK }
|
||||
}, { // Matches: Name, location, signature => Confirmed match.
|
||||
ModuleEnumerator::CONFIRMED_BAD,
|
||||
kStandardModule,
|
||||
{ kMatchName, kMatchLocation, kMatchSignature, kEmpty, kEmpty, kOs,
|
||||
ModuleEnumerator::SEE_LINK }
|
||||
}, { // Matches: Name, location, signature (not version) => No match.
|
||||
ModuleEnumerator::NOT_MATCHED,
|
||||
kStandardModule,
|
||||
{ kMatchName, kMatchLocation, kMatchSignature,
|
||||
kVersionLow, kVersionLow, kOs, ModuleEnumerator::SEE_LINK }
|
||||
}, { // Matches: Name, location, description => Confirmed match.
|
||||
ModuleEnumerator::CONFIRMED_BAD,
|
||||
kStandardModule,
|
||||
{ kMatchName, kMatchLocation, kMatchDesc, kEmpty, kEmpty, kOs,
|
||||
ModuleEnumerator::SEE_LINK }
|
||||
}, { // Matches: Name, location, description (not version) => No match.
|
||||
ModuleEnumerator::NOT_MATCHED,
|
||||
kStandardModule,
|
||||
{ kMatchName, kMatchLocation, kMatchDesc,
|
||||
kVersionHigh, kVersionHigh, kOs, ModuleEnumerator::SEE_LINK }
|
||||
}, { // Matches: Name, location, signature, version => Confirmed match.
|
||||
ModuleEnumerator::CONFIRMED_BAD,
|
||||
kStandardModule,
|
||||
{ kMatchName, kMatchLocation, kMatchSignature,
|
||||
kVersionLow, kVersionHigh, kOs, ModuleEnumerator::SEE_LINK }
|
||||
}, { // Matches: Name, location, signature, version (lower) => Confirmed.
|
||||
ModuleEnumerator::CONFIRMED_BAD,
|
||||
kStandardModule,
|
||||
{ kMatchName, kMatchLocation, kMatchSignature,
|
||||
kVersionLow, kEmpty, kOs, ModuleEnumerator::SEE_LINK }
|
||||
}, { // Matches: Name, location, signature, version (upper) => Confirmed.
|
||||
ModuleEnumerator::CONFIRMED_BAD,
|
||||
kStandardModule,
|
||||
{ kMatchName, kMatchLocation, kMatchSignature,
|
||||
kEmpty, kVersionHigh, kOs, ModuleEnumerator::SEE_LINK }
|
||||
}, { // Matches: Name, Location, Version lower is inclusive => Confirmed.
|
||||
ModuleEnumerator::CONFIRMED_BAD,
|
||||
kStandardModule,
|
||||
{ kMatchName, kMatchLocation, kMatchSignature,
|
||||
"1.0", "2.0", kOs, ModuleEnumerator::SEE_LINK }
|
||||
}, { // Matches: Name, Location, Version higher is exclusive => No match.
|
||||
ModuleEnumerator::NOT_MATCHED,
|
||||
kStandardModule,
|
||||
{ kMatchName, kMatchLocation, kEmpty,
|
||||
"0.0", "1.0", kOs, ModuleEnumerator::SEE_LINK }
|
||||
}, { // All empty fields doesn't produce a match.
|
||||
ModuleEnumerator::NOT_MATCHED,
|
||||
{ kType, kStatus, L"", L"", L"", L"", L"", L"", ModuleEnumerator::NONE },
|
||||
{ "a.dll", "", "", "", "", kOs, ModuleEnumerator::SEE_LINK }
|
||||
},
|
||||
};
|
||||
|
||||
TEST_F(EnumerateModulesTest, MatchFunction) {
|
||||
for (size_t i = 0; i < arraysize(kMatchineEntryList); ++i) {
|
||||
ModuleEnumerator::Module test = kMatchineEntryList[i].test_case;
|
||||
ModuleEnumerator::NormalizeModule(&test);
|
||||
ModuleEnumerator::BlacklistEntry blacklist =
|
||||
kMatchineEntryList[i].blacklist;
|
||||
|
||||
SCOPED_TRACE("Test case no " + base::IntToString(i) +
|
||||
": '" + base::UTF16ToASCII(test.name) + "'");
|
||||
EXPECT_EQ(kMatchineEntryList[i].expected_result,
|
||||
ModuleEnumerator::Match(test, blacklist));
|
||||
}
|
||||
}
|
||||
|
||||
const struct CollapsePathList {
|
||||
base::string16 expected_result;
|
||||
base::string16 test_case;
|
||||
@ -224,17 +102,17 @@ const struct CollapsePathList {
|
||||
};
|
||||
|
||||
TEST_F(EnumerateModulesTest, CollapsePath) {
|
||||
scoped_refptr<ModuleEnumerator> module_enumerator(new ModuleEnumerator(NULL));
|
||||
module_enumerator->path_mapping_.clear();
|
||||
module_enumerator->path_mapping_.push_back(
|
||||
ModuleEnumerator module_enumerator(nullptr);
|
||||
module_enumerator.path_mapping_.clear();
|
||||
module_enumerator.path_mapping_.push_back(
|
||||
std::make_pair(L"c:\\foo\\", L"%foo%"));
|
||||
module_enumerator->path_mapping_.push_back(
|
||||
module_enumerator.path_mapping_.push_back(
|
||||
std::make_pair(L"c:\\foo\\bar\\", L"%x%"));
|
||||
|
||||
for (size_t i = 0; i < arraysize(kCollapsePathList); ++i) {
|
||||
ModuleEnumerator::Module module;
|
||||
module.location = kCollapsePathList[i].test_case;
|
||||
module_enumerator->CollapsePath(&module);
|
||||
module_enumerator.CollapsePath(&module);
|
||||
|
||||
SCOPED_TRACE("Test case no " + base::IntToString(i) + ": '" +
|
||||
base::UTF16ToASCII(kCollapsePathList[i].expected_result) +
|
||||
|
@ -61621,6 +61621,31 @@ http://cs/file:chrome/histograms.xml - but prefer this file for new entries.
|
||||
</summary>
|
||||
</histogram>
|
||||
|
||||
<histogram name="ThirdPartyModules.Modules.Signed" units="modules">
|
||||
<owner>chrisha@chromium.org</owner>
|
||||
<summary>
|
||||
The total number of signed modules loaded (or potentially loaded) into the
|
||||
browser process. Measured shortly after startup. Windows only.
|
||||
</summary>
|
||||
</histogram>
|
||||
|
||||
<histogram name="ThirdPartyModules.Modules.Signed.Microsoft" units="modules">
|
||||
<owner>chrisha@chromium.org</owner>
|
||||
<summary>
|
||||
The total number of modules loaded (or potentially loaded) into the browser
|
||||
process that are signed by Microsoft. Measured shortly after startup.
|
||||
Windows only.
|
||||
</summary>
|
||||
</histogram>
|
||||
|
||||
<histogram name="ThirdPartyModules.Modules.Total" units="modules">
|
||||
<owner>chrisha@chromium.org</owner>
|
||||
<summary>
|
||||
The total number of modules loaded (or potentially loaded) into the browser
|
||||
process. Measured shortly after startup. Windows only.
|
||||
</summary>
|
||||
</histogram>
|
||||
|
||||
<histogram name="ThreadWatcher.ResponseTime" units="ms">
|
||||
<owner>rtenneti@chromium.org</owner>
|
||||
<summary>
|
||||
|
Reference in New Issue
Block a user