blob: 81b85ef97db1fba804a49dcc666e117dd5ae3e26 [file] [log] [blame]
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/extensions/error_console/error_console.h"
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/lazy_instance.h"
#include "base/stl_util.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/extensions/error_console/error_console_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/pref_names.h"
#include "components/crx_file/id_util.h"
#include "components/prefs/pref_service.h"
#include "components/version_info/version_info.h"
#include "content/public/browser/notification_details.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/notification_source.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_system.h"
#include "extensions/common/constants.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_set.h"
#include "extensions/common/feature_switch.h"
#include "extensions/common/features/feature_channel.h"
namespace extensions {
namespace {
// The key into the Extension prefs for an Extension's specific reporting
// settings.
const char kStoreExtensionErrorsPref[] = "store_extension_errors";
// This is the default mask for which errors to report. That is, if an extension
// does not have specific preference set, this will be used instead.
const int kDefaultMask = 0;
const char kAppsDeveloperToolsExtensionId[] =
"ohmmkhmmmpcnpikjeljgnaoabkaalbgc";
} // namespace
void ErrorConsole::Observer::OnErrorAdded(const ExtensionError* error) {
}
void ErrorConsole::Observer::OnErrorsRemoved(
const std::set<std::string>& extension_ids) {
}
void ErrorConsole::Observer::OnErrorConsoleDestroyed() {
}
ErrorConsole::ErrorConsole(Profile* profile)
: enabled_(false),
default_mask_(kDefaultMask),
profile_(profile),
prefs_(nullptr),
registry_observer_(this) {
pref_registrar_.Init(profile_->GetPrefs());
pref_registrar_.Add(prefs::kExtensionsUIDeveloperMode,
base::Bind(&ErrorConsole::OnPrefChanged,
base::Unretained(this)));
registry_observer_.Add(ExtensionRegistry::Get(profile_));
CheckEnabled();
}
ErrorConsole::~ErrorConsole() {
for (auto& observer : observers_)
observer.OnErrorConsoleDestroyed();
}
// static
ErrorConsole* ErrorConsole::Get(content::BrowserContext* browser_context) {
return ErrorConsoleFactory::GetForBrowserContext(browser_context);
}
void ErrorConsole::SetReportingForExtension(const std::string& extension_id,
ExtensionError::Type type,
bool enabled) {
DCHECK(thread_checker_.CalledOnValidThread());
if (!enabled_ || !crx_file::id_util::IdIsValid(extension_id))
return;
int mask = default_mask_;
// This call can fail if the preference isn't set, but we don't really care
// if it does, because we just use the default mask instead.
prefs_->ReadPrefAsInteger(extension_id, kStoreExtensionErrorsPref, &mask);
if (enabled)
mask |= 1 << type;
else
mask &= ~(1 << type);
prefs_->UpdateExtensionPref(extension_id, kStoreExtensionErrorsPref,
std::make_unique<base::Value>(mask));
}
void ErrorConsole::SetReportingAllForExtension(
const std::string& extension_id, bool enabled) {
DCHECK(thread_checker_.CalledOnValidThread());
if (!enabled_ || !crx_file::id_util::IdIsValid(extension_id))
return;
int mask = enabled ? (1 << ExtensionError::NUM_ERROR_TYPES) - 1 : 0;
prefs_->UpdateExtensionPref(extension_id, kStoreExtensionErrorsPref,
std::make_unique<base::Value>(mask));
}
bool ErrorConsole::IsReportingEnabledForExtension(
const std::string& extension_id) const {
DCHECK(thread_checker_.CalledOnValidThread());
if (!enabled_ || !crx_file::id_util::IdIsValid(extension_id))
return false;
return GetMaskForExtension(extension_id) != 0;
}
void ErrorConsole::UseDefaultReportingForExtension(
const std::string& extension_id) {
DCHECK(thread_checker_.CalledOnValidThread());
if (!enabled_ || !crx_file::id_util::IdIsValid(extension_id))
return;
prefs_->UpdateExtensionPref(extension_id, kStoreExtensionErrorsPref, nullptr);
}
void ErrorConsole::ReportError(std::unique_ptr<ExtensionError> error) {
DCHECK(thread_checker_.CalledOnValidThread());
if (!enabled_ || !crx_file::id_util::IdIsValid(error->extension_id()))
return;
DCHECK_GE(error->level(), extension_misc::kMinimumSeverityToReportError)
<< "Errors less than severity warning should not be reported.";
int mask = GetMaskForExtension(error->extension_id());
if (!(mask & (1 << error->type())))
return;
const ExtensionError* weak_error = errors_.AddError(std::move(error));
for (auto& observer : observers_)
observer.OnErrorAdded(weak_error);
}
void ErrorConsole::RemoveErrors(const ErrorMap::Filter& filter) {
std::set<std::string> affected_ids;
errors_.RemoveErrors(filter, &affected_ids);
for (auto& observer : observers_)
observer.OnErrorsRemoved(affected_ids);
}
const ErrorList& ErrorConsole::GetErrorsForExtension(
const std::string& extension_id) const {
return errors_.GetErrorsForExtension(extension_id);
}
void ErrorConsole::AddObserver(Observer* observer) {
DCHECK(thread_checker_.CalledOnValidThread());
observers_.AddObserver(observer);
}
void ErrorConsole::RemoveObserver(Observer* observer) {
DCHECK(thread_checker_.CalledOnValidThread());
observers_.RemoveObserver(observer);
}
bool ErrorConsole::IsEnabledForChromeExtensionsPage() const {
if (!profile_->GetPrefs()->GetBoolean(prefs::kExtensionsUIDeveloperMode)) {
return false; // Only enabled in developer mode.
}
// If there is a command line switch or override, respect that.
if (FeatureSwitch::error_console()->HasValue()) {
return FeatureSwitch::error_console()->IsEnabled();
}
// Enable by default on dev channel, disabled on other channels.
return GetCurrentChannel() <= version_info::Channel::DEV;
}
bool ErrorConsole::IsEnabledForAppsDeveloperTools() const {
return ExtensionRegistry::Get(profile_)->enabled_extensions()
.Contains(kAppsDeveloperToolsExtensionId);
}
void ErrorConsole::CheckEnabled() {
bool should_be_enabled = IsEnabledForChromeExtensionsPage() ||
IsEnabledForAppsDeveloperTools();
if (should_be_enabled && !enabled_)
Enable();
if (!should_be_enabled && enabled_)
Disable();
}
void ErrorConsole::Enable() {
enabled_ = true;
// We postpone the initialization of |prefs_| until now because they can be
// nullptr in unit_tests. Any unit tests that enable the error console should
// also create an ExtensionPrefs object.
prefs_ = ExtensionPrefs::Get(profile_);
notification_registrar_.Add(
this,
chrome::NOTIFICATION_PROFILE_DESTROYED,
content::NotificationService::AllBrowserContextsAndSources());
const ExtensionSet& extensions =
ExtensionRegistry::Get(profile_)->enabled_extensions();
for (ExtensionSet::const_iterator iter = extensions.begin();
iter != extensions.end();
++iter) {
AddManifestErrorsForExtension(iter->get());
}
}
void ErrorConsole::Disable() {
notification_registrar_.RemoveAll();
errors_.RemoveAllErrors();
enabled_ = false;
}
void ErrorConsole::OnPrefChanged() {
CheckEnabled();
}
void ErrorConsole::OnExtensionUnloaded(content::BrowserContext* browser_context,
const Extension* extension,
UnloadedExtensionReason reason) {
CheckEnabled();
}
void ErrorConsole::OnExtensionLoaded(content::BrowserContext* browser_context,
const Extension* extension) {
CheckEnabled();
}
void ErrorConsole::OnExtensionInstalled(
content::BrowserContext* browser_context,
const Extension* extension,
bool is_update) {
// We don't want to have manifest errors from previous installs. We want
// to keep runtime errors, though, because extensions are reloaded on a
// refresh of chrome:extensions, and we don't want to wipe our history
// whenever that happens.
errors_.RemoveErrors(ErrorMap::Filter::ErrorsForExtensionWithType(
extension->id(), ExtensionError::MANIFEST_ERROR), nullptr);
AddManifestErrorsForExtension(extension);
}
void ErrorConsole::OnExtensionUninstalled(
content::BrowserContext* browser_context,
const Extension* extension,
extensions::UninstallReason reason) {
errors_.RemoveErrors(ErrorMap::Filter::ErrorsForExtension(extension->id()),
nullptr);
}
void ErrorConsole::AddManifestErrorsForExtension(const Extension* extension) {
const std::vector<InstallWarning>& warnings =
extension->install_warnings();
for (std::vector<InstallWarning>::const_iterator iter = warnings.begin();
iter != warnings.end(); ++iter) {
ReportError(std::unique_ptr<ExtensionError>(new ManifestError(
extension->id(), base::UTF8ToUTF16(iter->message),
base::UTF8ToUTF16(iter->key), base::UTF8ToUTF16(iter->specific))));
}
}
void ErrorConsole::Observe(int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) {
DCHECK_EQ(chrome::NOTIFICATION_PROFILE_DESTROYED, type);
Profile* profile = content::Source<Profile>(source).ptr();
// If incognito profile which we are associated with is destroyed, also
// destroy all incognito errors.
if (profile->IsOffTheRecord() && profile_->IsSameProfile(profile))
errors_.RemoveErrors(ErrorMap::Filter::IncognitoErrors(), nullptr);
}
int ErrorConsole::GetMaskForExtension(const std::string& extension_id) const {
// Registered preferences take priority over everything else.
int pref = 0;
if (prefs_->ReadPrefAsInteger(extension_id, kStoreExtensionErrorsPref, &pref))
return pref;
// If the extension is unpacked, we report all error types by default.
const Extension* extension =
ExtensionRegistry::Get(profile_)->GetExtensionById(
extension_id, ExtensionRegistry::EVERYTHING);
if (extension && extension->location() == Manifest::UNPACKED)
return (1 << ExtensionError::NUM_ERROR_TYPES) - 1;
// Otherwise, use the default mask.
return default_mask_;
}
} // namespace extensions