blob: 1053872e71aa94dd65298b60b232b520baefafa5 [file] [log] [blame]
// Copyright (c) 2012 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/managed_mode.h"
#include "base/command_line.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/extension_system.h"
#include "chrome/browser/managed_mode_url_filter.h"
#include "chrome/browser/prefs/pref_service.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/common/chrome_notification_types.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/pref_names.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/notification_service.h"
#include "grit/generated_resources.h"
#include "ui/base/l10n/l10n_util.h"
using content::BrowserThread;
// A bridge from ManagedMode (which lives on the UI thread) to
// ManagedModeURLFilter (which lives on the IO thread).
class ManagedMode::URLFilterContext {
public:
URLFilterContext() {}
~URLFilterContext() {}
const ManagedModeURLFilter* url_filter() const {
return &url_filter_;
}
void SetActive(bool in_managed_mode) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
// Because ManagedMode is a singleton, we can pass the pointer to
// |url_filter_| unretained.
BrowserThread::PostTask(BrowserThread::IO,
FROM_HERE,
base::Bind(
&ManagedModeURLFilter::SetActive,
base::Unretained(&url_filter_),
in_managed_mode));
}
void ShutdownOnUIThread() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
BrowserThread::DeleteSoon(BrowserThread::IO, FROM_HERE, this);
}
private:
ManagedModeURLFilter url_filter_;
DISALLOW_COPY_AND_ASSIGN(URLFilterContext);
};
// static
ManagedMode* ManagedMode::GetInstance() {
return Singleton<ManagedMode, LeakySingletonTraits<ManagedMode> >::get();
}
// static
void ManagedMode::RegisterPrefs(PrefService* prefs) {
prefs->RegisterBooleanPref(prefs::kInManagedMode, false);
}
// static
void ManagedMode::Init(Profile* profile) {
GetInstance()->InitImpl(profile);
}
void ManagedMode::InitImpl(Profile* profile) {
DCHECK(g_browser_process);
DCHECK(g_browser_process->local_state());
Profile* original_profile = profile->GetOriginalProfile();
// Set the value directly in the PrefService instead of using
// CommandLinePrefStore so we can change it at runtime.
if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kNoManaged)) {
SetInManagedMode(NULL);
} else if (IsInManagedModeImpl() ||
CommandLine::ForCurrentProcess()->HasSwitch(switches::kManaged)) {
SetInManagedMode(original_profile);
}
}
// static
bool ManagedMode::IsInManagedMode() {
return GetInstance()->IsInManagedModeImpl();
}
bool ManagedMode::IsInManagedModeImpl() const {
// |g_browser_process| can be NULL during startup.
if (!g_browser_process)
return false;
// Local State can be NULL during unit tests.
if (!g_browser_process->local_state())
return false;
return g_browser_process->local_state()->GetBoolean(prefs::kInManagedMode);
}
// static
void ManagedMode::EnterManagedMode(Profile* profile,
const EnterCallback& callback) {
GetInstance()->EnterManagedModeImpl(profile, callback);
}
void ManagedMode::EnterManagedModeImpl(Profile* profile,
const EnterCallback& callback) {
Profile* original_profile = profile->GetOriginalProfile();
if (IsInManagedModeImpl()) {
callback.Run(original_profile == managed_profile_);
return;
}
if (!callbacks_.empty()) {
// We are already in the process of entering managed mode, waiting for
// browsers to close. Don't allow entering managed mode again for a
// different profile, and queue the callback for the same profile.
if (original_profile != managed_profile_)
callback.Run(false);
else
callbacks_.push_back(callback);
return;
}
if (!PlatformConfirmEnter()) {
callback.Run(false);
return;
}
// Close all other profiles.
// At this point, we shouldn't be waiting for other browsers to close (yet).
DCHECK_EQ(0u, browsers_to_close_.size());
for (BrowserList::const_iterator i = BrowserList::begin();
i != BrowserList::end(); ++i) {
if ((*i)->profile()->GetOriginalProfile() != original_profile)
browsers_to_close_.insert(*i);
}
if (browsers_to_close_.empty()) {
SetInManagedMode(original_profile);
callback.Run(true);
return;
}
// Remember the profile we're trying to manage while we wait for other
// browsers to close.
managed_profile_ = original_profile;
callbacks_.push_back(callback);
registrar_.Add(this, chrome::NOTIFICATION_CLOSE_ALL_BROWSERS_REQUEST,
content::NotificationService::AllSources());
registrar_.Add(this, chrome::NOTIFICATION_BROWSER_CLOSE_CANCELLED,
content::NotificationService::AllSources());
for (std::set<Browser*>::const_iterator i = browsers_to_close_.begin();
i != browsers_to_close_.end(); ++i) {
(*i)->window()->Close();
}
}
// static
void ManagedMode::LeaveManagedMode() {
GetInstance()->LeaveManagedModeImpl();
}
void ManagedMode::LeaveManagedModeImpl() {
bool confirmed = PlatformConfirmLeave();
if (confirmed)
SetInManagedMode(NULL);
}
// static
const ManagedModeURLFilter* ManagedMode::GetURLFilter() {
return GetInstance()->GetURLFilterImpl();
}
const ManagedModeURLFilter* ManagedMode::GetURLFilterImpl() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
return url_filter_context_->url_filter();
}
std::string ManagedMode::GetDebugPolicyProviderName() const {
// Save the string space in official builds.
#ifdef NDEBUG
NOTREACHED();
return std::string();
#else
return "Managed Mode";
#endif
}
bool ManagedMode::UserMayLoad(const extensions::Extension* extension,
string16* error) const {
return ExtensionManagementPolicyImpl(error);
}
bool ManagedMode::UserMayModifySettings(const extensions::Extension* extension,
string16* error) const {
return ExtensionManagementPolicyImpl(error);
}
bool ManagedMode::ExtensionManagementPolicyImpl(string16* error) const {
if (!IsInManagedModeImpl())
return true;
if (error)
*error = l10n_util::GetStringUTF16(IDS_EXTENSIONS_LOCKED_MANAGED_MODE);
return false;
}
void ManagedMode::OnBrowserAdded(Browser* browser) {
// Return early if we don't have any queued callbacks.
if (callbacks_.empty())
return;
DCHECK(managed_profile_);
if (browser->profile()->GetOriginalProfile() != managed_profile_)
FinalizeEnter(false);
}
void ManagedMode::OnBrowserRemoved(Browser* browser) {
// Return early if we don't have any queued callbacks.
if (callbacks_.empty())
return;
DCHECK(managed_profile_);
if (browser->profile()->GetOriginalProfile() == managed_profile_) {
// Ignore closing browser windows that are in managed mode.
return;
}
size_t count = browsers_to_close_.erase(browser);
DCHECK_EQ(1u, count);
if (browsers_to_close_.empty())
FinalizeEnter(true);
}
ManagedMode::ManagedMode() : managed_profile_(NULL),
url_filter_context_(new URLFilterContext) {
BrowserList::AddObserver(this);
}
ManagedMode::~ManagedMode() {
// This class usually is a leaky singleton, so this destructor shouldn't be
// called. We still do some cleanup, in case we're owned by a unit test.
BrowserList::RemoveObserver(this);
DCHECK_EQ(0u, callbacks_.size());
DCHECK_EQ(0u, browsers_to_close_.size());
url_filter_context_.release()->ShutdownOnUIThread();
}
void ManagedMode::Observe(int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) {
// Return early if we don't have any queued callbacks.
if (callbacks_.empty())
return;
switch (type) {
case chrome::NOTIFICATION_CLOSE_ALL_BROWSERS_REQUEST: {
FinalizeEnter(false);
return;
}
case chrome::NOTIFICATION_BROWSER_CLOSE_CANCELLED: {
Browser* browser = content::Source<Browser>(source).ptr();
if (browsers_to_close_.find(browser) != browsers_to_close_.end())
FinalizeEnter(false);
return;
}
case chrome::NOTIFICATION_EXTENSION_LOADED:
case chrome::NOTIFICATION_EXTENSION_UNLOADED: {
if (managed_profile_)
UpdateWhitelist();
break;
}
default:
NOTREACHED();
}
}
void ManagedMode::FinalizeEnter(bool result) {
if (result)
SetInManagedMode(managed_profile_);
for (std::vector<EnterCallback>::iterator it = callbacks_.begin();
it != callbacks_.end(); ++it) {
it->Run(result);
}
callbacks_.clear();
browsers_to_close_.clear();
registrar_.RemoveAll();
}
bool ManagedMode::PlatformConfirmEnter() {
// TODO(bauerb): Show platform-specific confirmation dialog.
return true;
}
bool ManagedMode::PlatformConfirmLeave() {
// TODO(bauerb): Show platform-specific confirmation dialog.
return true;
}
void ManagedMode::SetInManagedMode(Profile* newly_managed_profile) {
// Register the ManagementPolicy::Provider before changing the pref when
// setting it, and unregister it after changing the pref when clearing it,
// so pref observers see the correct ManagedMode state.
bool in_managed_mode = !!newly_managed_profile;
if (in_managed_mode) {
DCHECK(!managed_profile_ || managed_profile_ == newly_managed_profile);
extensions::ExtensionSystem::Get(
newly_managed_profile)->management_policy()->RegisterProvider(this);
registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_LOADED,
content::Source<Profile>(newly_managed_profile));
registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED,
content::Source<Profile>(newly_managed_profile));
} else {
extensions::ExtensionSystem::Get(
managed_profile_)->management_policy()->UnregisterProvider(this);
registrar_.Remove(this, chrome::NOTIFICATION_EXTENSION_LOADED,
content::Source<Profile>(managed_profile_));
registrar_.Remove(this, chrome::NOTIFICATION_EXTENSION_UNLOADED,
content::Source<Profile>(managed_profile_));
}
managed_profile_ = newly_managed_profile;
url_filter_context_->SetActive(in_managed_mode);
g_browser_process->local_state()->SetBoolean(prefs::kInManagedMode,
in_managed_mode);
if (in_managed_mode)
UpdateWhitelist();
// This causes the avatar and the profile menu to get updated.
content::NotificationService::current()->Notify(
chrome::NOTIFICATION_PROFILE_CACHED_INFO_CHANGED,
content::NotificationService::AllBrowserContextsAndSources(),
content::NotificationService::NoDetails());
}
void ManagedMode::UpdateWhitelist() {
DCHECK(managed_profile_);
// TODO(bauerb): Update URL filter with whitelist.
}