Commit 6e36b463 authored by pmarko's avatar pmarko Committed by Commit bot

Make extensions developer mode adhere to policy

Make the developer mode checkbox on chrome://extensions adhere to the DeveloperToolsDisabled policy. If it is disabled, display an indicator.
Add corresponding indicator test to policy_prefs_browsertest.
Add an end-to-end test to policy_browsertest.

BUG=633684
TEST=browser_tests *PolicyPrefIndicatorTest*, PolicyTest.DeveloperToolsDisabledExtensionsDevMode
CQ_INCLUDE_TRYBOTS=master.tryserver.chromium.linux:closure_compilation

Review-Url: https://codereview.chromium.org/2529083002
Cr-Commit-Position: refs/heads/master@{#438215}
parent a40ee20d
...@@ -164,9 +164,12 @@ std::unique_ptr<developer::ProfileInfo> CreateProfileInfo(Profile* profile) { ...@@ -164,9 +164,12 @@ std::unique_ptr<developer::ProfileInfo> CreateProfileInfo(Profile* profile) {
std::unique_ptr<developer::ProfileInfo> info(new developer::ProfileInfo()); std::unique_ptr<developer::ProfileInfo> info(new developer::ProfileInfo());
info->is_supervised = profile->IsSupervised(); info->is_supervised = profile->IsSupervised();
PrefService* prefs = profile->GetPrefs(); PrefService* prefs = profile->GetPrefs();
const PrefService::Preference* pref =
prefs->FindPreference(prefs::kExtensionsUIDeveloperMode);
info->is_incognito_available = info->is_incognito_available =
IncognitoModePrefs::GetAvailability(prefs) != IncognitoModePrefs::GetAvailability(prefs) !=
IncognitoModePrefs::DISABLED; IncognitoModePrefs::DISABLED;
info->is_developer_mode_controlled_by_policy = pref->IsManaged();
info->in_developer_mode = info->in_developer_mode =
!info->is_supervised && !info->is_supervised &&
prefs->GetBoolean(prefs::kExtensionsUIDeveloperMode); prefs->GetBoolean(prefs::kExtensionsUIDeveloperMode);
......
...@@ -25,6 +25,13 @@ ...@@ -25,6 +25,13 @@
#include "chrome/common/pref_names.h" #include "chrome/common/pref_names.h"
#include "chrome/test/base/test_browser_window.h" #include "chrome/test/base/test_browser_window.h"
#include "components/crx_file/id_util.h" #include "components/crx_file/id_util.h"
#include "components/policy/core/browser/browser_policy_connector.h"
#include "components/policy/core/common/mock_configuration_policy_provider.h"
#include "components/policy/core/common/policy_map.h"
#include "components/policy/core/common/policy_service_impl.h"
#include "components/policy/core/common/policy_types.h"
#include "components/policy/policy_constants.h"
#include "components/sync_preferences/testing_pref_service_syncable.h"
#include "content/public/test/web_contents_tester.h" #include "content/public/test/web_contents_tester.h"
#include "extensions/browser/event_router_factory.h" #include "extensions/browser/event_router_factory.h"
#include "extensions/browser/extension_error_test_util.h" #include "extensions/browser/extension_error_test_util.h"
...@@ -40,6 +47,9 @@ ...@@ -40,6 +47,9 @@
#include "extensions/common/test_util.h" #include "extensions/common/test_util.h"
#include "extensions/common/value_builder.h" #include "extensions/common/value_builder.h"
using testing::Return;
using testing::_;
namespace extensions { namespace extensions {
namespace { namespace {
...@@ -95,6 +105,18 @@ class DeveloperPrivateApiUnitTest : public ExtensionServiceTestBase { ...@@ -95,6 +105,18 @@ class DeveloperPrivateApiUnitTest : public ExtensionServiceTestBase {
api::developer_private::PackStatus expected_status, api::developer_private::PackStatus expected_status,
int expected_flags); int expected_flags);
// Execute the updateProfileConfiguration API call with a specified
// dev_mode. This is done from the webui when the user checks the
// "Developer Mode" checkbox.
void UpdateProfileConfigurationDevMode(bool dev_mode);
// Execute the getProfileConfiguration API and parse its result into a
// ProfileInfo structure for further verification in the calling test.
// Will reset the profile_info unique_ptr.
// Uses ASSERT_* inside - callers should use ASSERT_NO_FATAL_FAILURE.
void GetProfileConfiguration(
std::unique_ptr<api::developer_private::ProfileInfo>* profile_info);
Browser* browser() { return browser_.get(); } Browser* browser() { return browser_.get(); }
private: private:
...@@ -107,6 +129,7 @@ class DeveloperPrivateApiUnitTest : public ExtensionServiceTestBase { ...@@ -107,6 +129,7 @@ class DeveloperPrivateApiUnitTest : public ExtensionServiceTestBase {
std::unique_ptr<Browser> browser_; std::unique_ptr<Browser> browser_;
std::vector<std::unique_ptr<TestExtensionDir>> test_extension_dirs_; std::vector<std::unique_ptr<TestExtensionDir>> test_extension_dirs_;
policy::MockConfigurationPolicyProvider mock_policy_provider_;
DISALLOW_COPY_AND_ASSIGN(DeveloperPrivateApiUnitTest); DISALLOW_COPY_AND_ASSIGN(DeveloperPrivateApiUnitTest);
}; };
...@@ -242,9 +265,43 @@ testing::AssertionResult DeveloperPrivateApiUnitTest::TestPackExtensionFunction( ...@@ -242,9 +265,43 @@ testing::AssertionResult DeveloperPrivateApiUnitTest::TestPackExtensionFunction(
return testing::AssertionSuccess(); return testing::AssertionSuccess();
} }
void DeveloperPrivateApiUnitTest::UpdateProfileConfigurationDevMode(
bool dev_mode) {
scoped_refptr<UIThreadExtensionFunction> function(
new api::DeveloperPrivateUpdateProfileConfigurationFunction());
std::unique_ptr<base::ListValue> args =
ListBuilder()
.Append(DictionaryBuilder()
.SetBoolean("inDeveloperMode", dev_mode)
.Build())
.Build();
EXPECT_TRUE(RunFunction(function, *args)) << function->GetError();
}
void DeveloperPrivateApiUnitTest::GetProfileConfiguration(
std::unique_ptr<api::developer_private::ProfileInfo>* profile_info) {
scoped_refptr<UIThreadExtensionFunction> function(
new api::DeveloperPrivateGetProfileConfigurationFunction());
base::ListValue args;
EXPECT_TRUE(RunFunction(function, args)) << function->GetError();
ASSERT_TRUE(function->GetResultList());
ASSERT_EQ(1u, function->GetResultList()->GetSize());
const base::Value* response_value = nullptr;
function->GetResultList()->Get(0u, &response_value);
*profile_info =
api::developer_private::ProfileInfo::FromValue(*response_value);
}
void DeveloperPrivateApiUnitTest::SetUp() { void DeveloperPrivateApiUnitTest::SetUp() {
ExtensionServiceTestBase::SetUp(); ExtensionServiceTestBase::SetUp();
InitializeEmptyExtensionService();
// By not specifying a pref_file filepath, we get a
// sync_preferences::TestingPrefServiceSyncable
// - see BuildTestingProfile in extension_service_test_base.cc.
ExtensionServiceInitParams init_params = CreateDefaultInitParams();
init_params.pref_file.clear();
InitializeExtensionService(init_params);
browser_window_.reset(new TestBrowserWindow()); browser_window_.reset(new TestBrowserWindow());
Browser::CreateParams params(profile()); Browser::CreateParams params(profile());
...@@ -576,4 +633,38 @@ TEST_F(DeveloperPrivateApiUnitTest, DeveloperPrivateDeleteExtensionErrors) { ...@@ -576,4 +633,38 @@ TEST_F(DeveloperPrivateApiUnitTest, DeveloperPrivateDeleteExtensionErrors) {
EXPECT_TRUE(error_console->GetErrorsForExtension(extension->id()).empty()); EXPECT_TRUE(error_console->GetErrorsForExtension(extension->id()).empty());
} }
// Test developerPrivate.updateProfileConfiguration: Try to turn on devMode
// when DeveloperToolsDisabled policy is active.
TEST_F(DeveloperPrivateApiUnitTest, DeveloperPrivateDevModeDisabledPolicy) {
testing_pref_service()->SetManagedPref(prefs::kExtensionsUIDeveloperMode,
new base::FundamentalValue(false));
UpdateProfileConfigurationDevMode(true);
EXPECT_FALSE(
profile()->GetPrefs()->GetBoolean(prefs::kExtensionsUIDeveloperMode));
std::unique_ptr<api::developer_private::ProfileInfo> profile_info;
ASSERT_NO_FATAL_FAILURE(GetProfileConfiguration(&profile_info));
EXPECT_FALSE(profile_info->in_developer_mode);
EXPECT_TRUE(profile_info->is_developer_mode_controlled_by_policy);
}
// Test developerPrivate.updateProfileConfiguration: Try to turn on devMode
// (without DeveloperToolsDisabled policy).
TEST_F(DeveloperPrivateApiUnitTest, DeveloperPrivateDevMode) {
EXPECT_FALSE(
profile()->GetPrefs()->GetBoolean(prefs::kExtensionsUIDeveloperMode));
UpdateProfileConfigurationDevMode(true);
EXPECT_TRUE(
profile()->GetPrefs()->GetBoolean(prefs::kExtensionsUIDeveloperMode));
std::unique_ptr<api::developer_private::ProfileInfo> profile_info;
ASSERT_NO_FATAL_FAILURE(GetProfileConfiguration(&profile_info));
EXPECT_TRUE(profile_info->in_developer_mode);
EXPECT_FALSE(profile_info->is_developer_mode_controlled_by_policy);
}
} // namespace extensions } // namespace extensions
...@@ -303,6 +303,11 @@ Profile* ExtensionServiceTestBase::profile() { ...@@ -303,6 +303,11 @@ Profile* ExtensionServiceTestBase::profile() {
return profile_.get(); return profile_.get();
} }
sync_preferences::TestingPrefServiceSyncable*
ExtensionServiceTestBase::testing_pref_service() {
return profile_->GetTestingPrefService();
}
void ExtensionServiceTestBase::CreateExtensionService( void ExtensionServiceTestBase::CreateExtensionService(
const ExtensionServiceInitParams& params) { const ExtensionServiceInitParams& params) {
TestExtensionSystem* system = TestExtensionSystem* system =
......
...@@ -35,6 +35,10 @@ namespace content { ...@@ -35,6 +35,10 @@ namespace content {
class BrowserContext; class BrowserContext;
} }
namespace sync_preferences {
class TestingPrefServiceSyncable;
}
namespace extensions { namespace extensions {
class ExtensionRegistry; class ExtensionRegistry;
...@@ -51,9 +55,9 @@ class ExtensionServiceTestBase : public testing::Test { ...@@ -51,9 +55,9 @@ class ExtensionServiceTestBase : public testing::Test {
base::FilePath profile_path; base::FilePath profile_path;
base::FilePath pref_file; base::FilePath pref_file;
base::FilePath extensions_install_dir; base::FilePath extensions_install_dir;
bool autoupdate_enabled; // defaults to false. bool autoupdate_enabled; // defaults to false.
bool is_first_run; // defaults to true. bool is_first_run; // defaults to true.
bool profile_is_supervised; // defaults to false. bool profile_is_supervised; // defaults to false.
// Though you could use this constructor, you probably want to use // Though you could use this constructor, you probably want to use
// CreateDefaultInitParams(), and then make a change or two. // CreateDefaultInitParams(), and then make a change or two.
...@@ -117,6 +121,7 @@ class ExtensionServiceTestBase : public testing::Test { ...@@ -117,6 +121,7 @@ class ExtensionServiceTestBase : public testing::Test {
content::BrowserContext* browser_context(); content::BrowserContext* browser_context();
Profile* profile(); Profile* profile();
sync_preferences::TestingPrefServiceSyncable* testing_pref_service();
ExtensionService* service() { return service_; } ExtensionService* service() { return service_; }
ExtensionRegistry* registry() { return registry_; } ExtensionRegistry* registry() { return registry_; }
const base::FilePath& extensions_install_dir() const { const base::FilePath& extensions_install_dir() const {
......
...@@ -712,6 +712,31 @@ void GetExtensionAllowedTypesMap( ...@@ -712,6 +712,31 @@ void GetExtensionAllowedTypesMap(
new base::FundamentalValue(entry.manifest_type)))); new base::FundamentalValue(entry.manifest_type))));
} }
} }
// Piggy-back kDeveloperToolsDisabled set to true to also force-disable
// kExtensionsUIDeveloperMode.
class DevToolsExtensionsUIPolicyHandler : public TypeCheckingPolicyHandler {
public:
DevToolsExtensionsUIPolicyHandler()
: TypeCheckingPolicyHandler(key::kDeveloperToolsDisabled,
base::Value::Type::BOOLEAN) {}
~DevToolsExtensionsUIPolicyHandler() override {}
// ConfigurationPolicyHandler implementation:
void ApplyPolicySettings(const PolicyMap& policies,
PrefValueMap* prefs) override {
const base::Value* value = policies.GetValue(policy_name());
bool developerToolsDisabled;
if (value && value->GetAsBoolean(&developerToolsDisabled) &&
developerToolsDisabled) {
prefs->SetValue(prefs::kExtensionsUIDeveloperMode,
base::MakeUnique<base::FundamentalValue>(false));
}
}
private:
DISALLOW_COPY_AND_ASSIGN(DevToolsExtensionsUIPolicyHandler);
};
#endif #endif
void GetDeprecatedFeaturesMap( void GetDeprecatedFeaturesMap(
...@@ -798,6 +823,7 @@ std::unique_ptr<ConfigurationPolicyHandlerList> BuildHandlerList( ...@@ -798,6 +823,7 @@ std::unique_ptr<ConfigurationPolicyHandlerList> BuildHandlerList(
handlers->AddHandler( handlers->AddHandler(
base::MakeUnique<extensions::ExtensionSettingsPolicyHandler>( base::MakeUnique<extensions::ExtensionSettingsPolicyHandler>(
chrome_schema)); chrome_schema));
handlers->AddHandler(base::MakeUnique<DevToolsExtensionsUIPolicyHandler>());
#endif #endif
#if !defined(OS_CHROMEOS) && !defined(OS_ANDROID) #if !defined(OS_CHROMEOS) && !defined(OS_ANDROID)
......
...@@ -1318,6 +1318,98 @@ IN_PROC_BROWSER_TEST_F(PolicyTest, DeveloperToolsDisabled) { ...@@ -1318,6 +1318,98 @@ IN_PROC_BROWSER_TEST_F(PolicyTest, DeveloperToolsDisabled) {
EXPECT_FALSE(DevToolsWindow::GetInstanceForInspectedWebContents(contents)); EXPECT_FALSE(DevToolsWindow::GetInstanceForInspectedWebContents(contents));
} }
namespace {
// Utility for waiting until the dev-mode controls are visible/hidden
// Uses a MutationObserver on the attributes of the DOM element.
void WaitForExtensionsDevModeControlsVisibility(
content::WebContents* contents,
const char* dev_controls_accessor_js,
const char* dev_controls_visibility_check_js,
bool expected_visible) {
bool done = false;
ASSERT_TRUE(content::ExecuteScriptAndExtractBool(
contents,
base::StringPrintf(
"var screenElement = %s;"
"function SendReplyIfAsExpected() {"
" var is_visible = %s;"
" if (is_visible != %s)"
" return false;"
" observer.disconnect();"
" domAutomationController.send(true);"
" return true;"
"}"
"var observer = new MutationObserver(SendReplyIfAsExpected);"
"if (!SendReplyIfAsExpected()) {"
" var options = { 'attributes': true };"
" observer.observe(screenElement, options);"
"}",
dev_controls_accessor_js,
dev_controls_visibility_check_js,
(expected_visible ? "true" : "false")),
&done));
}
} // namespace
IN_PROC_BROWSER_TEST_F(PolicyTest, DeveloperToolsDisabledExtensionsDevMode) {
// Verifies that when DeveloperToolsDisabled policy is set, the "dev mode"
// in chrome://extensions-frame is actively turned off and the checkbox
// is disabled.
// Note: We don't test the indicator as it is tested in the policy pref test
// for kDeveloperToolsDisabled.
// This test currently depends on the following assumptions about the webui:
// (1) The ID of the checkbox to toggle dev mode
const char toggle_dev_mode_accessor_js[] =
"document.getElementById('toggle-dev-on')";
// (2) The ID of the dev controls containing element
const char dev_controls_accessor_js[] =
"document.getElementById('dev-controls')";
// (3) the fact that dev-controls is displayed/hidden using its height attr
const char dev_controls_visibility_check_js[] =
"parseFloat(document.getElementById('dev-controls').style.height) > 0";
// Navigate to the extensions frame and enabled "Developer mode"
ui_test_utils::NavigateToURL(browser(),
GURL(chrome::kChromeUIExtensionsFrameURL));
content::WebContents* contents =
browser()->tab_strip_model()->GetActiveWebContents();
EXPECT_TRUE(content::ExecuteScript(
contents, base::StringPrintf("domAutomationController.send(%s.click());",
toggle_dev_mode_accessor_js)));
WaitForExtensionsDevModeControlsVisibility(contents,
dev_controls_accessor_js,
dev_controls_visibility_check_js,
true);
// Disable devtools via policy.
PolicyMap policies;
policies.Set(key::kDeveloperToolsDisabled, POLICY_LEVEL_MANDATORY,
POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD,
base::MakeUnique<base::FundamentalValue>(true), nullptr);
UpdateProviderPolicy(policies);
// Expect devcontrols to be hidden now...
WaitForExtensionsDevModeControlsVisibility(contents,
dev_controls_accessor_js,
dev_controls_visibility_check_js,
false);
// ... and checkbox is not disabled
bool is_toggle_dev_mode_checkbox_enabled = false;
EXPECT_TRUE(content::ExecuteScriptAndExtractBool(
contents,
base::StringPrintf(
"domAutomationController.send(!%s.hasAttribute('disabled'))",
toggle_dev_mode_accessor_js),
&is_toggle_dev_mode_checkbox_enabled));
EXPECT_FALSE(is_toggle_dev_mode_checkbox_enabled);
}
// TODO(samarth): remove along with rest of NTP4 code. // TODO(samarth): remove along with rest of NTP4 code.
IN_PROC_BROWSER_TEST_F(PolicyTest, DISABLED_WebStoreIconHidden) { IN_PROC_BROWSER_TEST_F(PolicyTest, DISABLED_WebStoreIconHidden) {
// Verifies that the web store icons can be hidden from the new tab page. // Verifies that the web store icons can be hidden from the new tab page.
......
...@@ -101,12 +101,14 @@ class PrefMapping { ...@@ -101,12 +101,14 @@ class PrefMapping {
bool is_local_state, bool is_local_state,
bool check_for_mandatory, bool check_for_mandatory,
bool check_for_recommended, bool check_for_recommended,
const std::string& indicator_test_url,
const std::string& indicator_test_setup_js, const std::string& indicator_test_setup_js,
const std::string& indicator_selector) const std::string& indicator_selector)
: pref_(pref), : pref_(pref),
is_local_state_(is_local_state), is_local_state_(is_local_state),
check_for_mandatory_(check_for_mandatory), check_for_mandatory_(check_for_mandatory),
check_for_recommended_(check_for_recommended), check_for_recommended_(check_for_recommended),
indicator_test_url_(indicator_test_url),
indicator_test_setup_js_(indicator_test_setup_js), indicator_test_setup_js_(indicator_test_setup_js),
indicator_selector_(indicator_selector) {} indicator_selector_(indicator_selector) {}
~PrefMapping() {} ~PrefMapping() {}
...@@ -119,6 +121,8 @@ class PrefMapping { ...@@ -119,6 +121,8 @@ class PrefMapping {
bool check_for_recommended() const { return check_for_recommended_; } bool check_for_recommended() const { return check_for_recommended_; }
const std::string& indicator_test_url() const { return indicator_test_url_; }
const std::string& indicator_test_setup_js() const { const std::string& indicator_test_setup_js() const {
return indicator_test_setup_js_; return indicator_test_setup_js_;
} }
...@@ -139,6 +143,7 @@ class PrefMapping { ...@@ -139,6 +143,7 @@ class PrefMapping {
const bool is_local_state_; const bool is_local_state_;
const bool check_for_mandatory_; const bool check_for_mandatory_;
const bool check_for_recommended_; const bool check_for_recommended_;
const std::string indicator_test_url_;
const std::string indicator_test_setup_js_; const std::string indicator_test_setup_js_;
const std::string indicator_selector_; const std::string indicator_selector_;
ScopedVector<IndicatorTestCase> indicator_test_cases_; ScopedVector<IndicatorTestCase> indicator_test_cases_;
...@@ -218,7 +223,7 @@ class PolicyTestCase { ...@@ -218,7 +223,7 @@ class PolicyTestCase {
DISALLOW_COPY_AND_ASSIGN(PolicyTestCase); DISALLOW_COPY_AND_ASSIGN(PolicyTestCase);
}; };
// Parses all policy test cases and makes then available in a map. // Parses all policy test cases and makes them available in a map.
class PolicyTestCases { class PolicyTestCases {
public: public:
typedef std::vector<PolicyTestCase*> PolicyTestCaseVector; typedef std::vector<PolicyTestCase*> PolicyTestCaseVector;
...@@ -327,6 +332,8 @@ class PolicyTestCases { ...@@ -327,6 +332,8 @@ class PolicyTestCases {
bool check_for_recommended = true; bool check_for_recommended = true;
pref_mapping_dict->GetBoolean("check_for_recommended", pref_mapping_dict->GetBoolean("check_for_recommended",
&check_for_recommended); &check_for_recommended);
std::string indicator_test_url;
pref_mapping_dict->GetString("indicator_test_url", &indicator_test_url);
std::string indicator_test_setup_js; std::string indicator_test_setup_js;
pref_mapping_dict->GetString("indicator_test_setup_js", pref_mapping_dict->GetString("indicator_test_setup_js",
&indicator_test_setup_js); &indicator_test_setup_js);
...@@ -336,6 +343,7 @@ class PolicyTestCases { ...@@ -336,6 +343,7 @@ class PolicyTestCases {
is_local_state, is_local_state,
check_for_mandatory, check_for_mandatory,
check_for_recommended, check_for_recommended,
indicator_test_url,
indicator_test_setup_js, indicator_test_setup_js,
indicator_selector); indicator_selector);
const base::ListValue* indicator_tests = NULL; const base::ListValue* indicator_tests = NULL;
...@@ -693,6 +701,18 @@ IN_PROC_BROWSER_TEST_P(PolicyPrefIndicatorTest, CheckPolicyIndicators) { ...@@ -693,6 +701,18 @@ IN_PROC_BROWSER_TEST_P(PolicyPrefIndicatorTest, CheckPolicyIndicators) {
(*pref_mapping)->indicator_test_setup_js())); (*pref_mapping)->indicator_test_setup_js()));
} }
// A non-empty indicator_test_url is expected to be used in very
// few cases, so it's currently implemented by navigating to the URL
// right before the test and navigating back afterwards.
// If you introduce many test cases with the same non-empty
// indicator_test_url, this would be inefficient. We could consider
// navigting to a specific indicator_test_url once for many test cases
// instead.
if (!(*pref_mapping)->indicator_test_url().empty()) {
ui_test_utils::NavigateToURL(
browser(), GURL((*pref_mapping)->indicator_test_url()));
}
std::string indicator_selector = (*pref_mapping)->indicator_selector(); std::string indicator_selector = (*pref_mapping)->indicator_selector();
if (indicator_selector.empty()) if (indicator_selector.empty())
indicator_selector = "[pref=\"" + (*pref_mapping)->pref() + "\"]"; indicator_selector = "[pref=\"" + (*pref_mapping)->pref() + "\"]";
...@@ -756,6 +776,9 @@ IN_PROC_BROWSER_TEST_P(PolicyPrefIndicatorTest, CheckPolicyIndicators) { ...@@ -756,6 +776,9 @@ IN_PROC_BROWSER_TEST_P(PolicyPrefIndicatorTest, CheckPolicyIndicators) {
(*indicator_test_case)->readonly()); (*indicator_test_case)->readonly());
prefs->ClearPref((*pref_mapping)->pref().c_str()); prefs->ClearPref((*pref_mapping)->pref().c_str());
} }
if (!(*pref_mapping)->indicator_test_url().empty())
ui_test_utils::NavigateToURL(browser(), GURL(kMainSettingsPage));
} }
} }
} }
......
...@@ -77,6 +77,8 @@ ...@@ -77,6 +77,8 @@
<label> <label>
<input id="toggle-dev-on" type="checkbox"> <input id="toggle-dev-on" type="checkbox">
<span>$i18n{extensionSettingsDeveloperMode}</span> <span>$i18n{extensionSettingsDeveloperMode}</span>
<span id="dev-toggle-disabled-by-policy-indicator"
class="controlled-setting-indicator"></span>
</label> </label>
</div> </div>
</div> </div>
......
...@@ -196,6 +196,8 @@ cr.define('extensions', function() { ...@@ -196,6 +196,8 @@ cr.define('extensions', function() {
/** @const */ /** @const */
var supervised = profileInfo.isSupervised; var supervised = profileInfo.isSupervised;
var developerModeControlledByPolicy =
profileInfo.isDeveloperModeControlledByPolicy;
var pageDiv = $('extension-settings'); var pageDiv = $('extension-settings');
pageDiv.classList.toggle('profile-is-supervised', supervised); pageDiv.classList.toggle('profile-is-supervised', supervised);
...@@ -203,7 +205,13 @@ cr.define('extensions', function() { ...@@ -203,7 +205,13 @@ cr.define('extensions', function() {
var devControlsCheckbox = $('toggle-dev-on'); var devControlsCheckbox = $('toggle-dev-on');
devControlsCheckbox.checked = profileInfo.inDeveloperMode; devControlsCheckbox.checked = profileInfo.inDeveloperMode;
devControlsCheckbox.disabled = supervised; devControlsCheckbox.disabled =
supervised || developerModeControlledByPolicy;
// This is necessary e.g. if developer mode is now disabled by policy
// but extension developer tools were visible.
this.updateDevControlsVisibility_(false);
this.updateDevToggleControlledIndicator_(developerModeControlledByPolicy);
$('load-unpacked').disabled = !profileInfo.canLoadUnpacked; $('load-unpacked').disabled = !profileInfo.canLoadUnpacked;
var extensionList = $('extension-settings-list'); var extensionList = $('extension-settings-list');
...@@ -218,6 +226,36 @@ cr.define('extensions', function() { ...@@ -218,6 +226,36 @@ cr.define('extensions', function() {
}.bind(this)); }.bind(this));
}, },
/**
* Shows or hides the 'controlled by policy' indicator on the dev-toggle
* checkbox.
* @param {boolean} devModeControlledByPolicy true if the indicator
* should be showing.
* @private
*/
updateDevToggleControlledIndicator_: function(devModeControlledByPolicy) {
var controlledIndicator = document.querySelector(
'#dev-toggle .controlled-setting-indicator');
if (!(controlledIndicator instanceof cr.ui.ControlledIndicator))
cr.ui.ControlledIndicator.decorate(controlledIndicator);
// We control the visibility of the ControlledIndicator by setting or
// removing the 'controlled-by' attribute (see controlled_indicator.css).
var isVisible = controlledIndicator.getAttribute('controlled-by');
if (devModeControlledByPolicy && !isVisible) {
var controlledBy = 'policy';
controlledIndicator.setAttribute(
'controlled-by', controlledBy);
controlledIndicator.setAttribute(
'text' + controlledBy,
loadTimeData.getString('extensionControlledSettingPolicy'));
} else if (!devModeControlledByPolicy && isVisible) {
// This hides the element - see above.
controlledIndicator.removeAttribute('controlled-by');
}
},
/** /**
* Shows the loading spinner and hides elements that shouldn't be visible * Shows the loading spinner and hides elements that shouldn't be visible
* while loading. * while loading.
......
...@@ -276,6 +276,11 @@ void ExtensionSettingsHandler::GetLocalizedValues( ...@@ -276,6 +276,11 @@ void ExtensionSettingsHandler::GetLocalizedValues(
source->AddString("extensionCommandsRegular", source->AddString("extensionCommandsRegular",
l10n_util::GetStringUTF16(IDS_EXTENSION_COMMANDS_NOT_GLOBAL)); l10n_util::GetStringUTF16(IDS_EXTENSION_COMMANDS_NOT_GLOBAL));
source->AddString("ok", l10n_util::GetStringUTF16(IDS_OK)); source->AddString("ok", l10n_util::GetStringUTF16(IDS_OK));
// 'Bubble' text for the controlled-setting-indicator
source->AddString(
"extensionControlledSettingPolicy",
l10n_util::GetStringUTF16(IDS_OPTIONS_CONTROLLED_SETTING_POLICY));
} }
void ExtensionSettingsHandler::DidStartNavigationToPendingEntry( void ExtensionSettingsHandler::DidStartNavigationToPendingEntry(
......
...@@ -226,6 +226,7 @@ namespace developerPrivate { ...@@ -226,6 +226,7 @@ namespace developerPrivate {
boolean appInfoDialogEnabled; boolean appInfoDialogEnabled;
boolean canLoadUnpacked; boolean canLoadUnpacked;
boolean inDeveloperMode; boolean inDeveloperMode;
boolean isDeveloperModeControlledByPolicy;
boolean isIncognitoAvailable; boolean isIncognitoAvailable;
boolean isSupervised; boolean isSupervised;
}; };
......
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
"check_for_recommended": "Should the preference be tested when a recommended value is set for the policy? Defaults to |true| if not specified.", "check_for_recommended": "Should the preference be tested when a recommended value is set for the policy? Defaults to |true| if not specified.",
"note": "When |can_be_recommended| is |false|, the policy is never set to a recommended value so |check_for_recommended| has no effect.", "note": "When |can_be_recommended| is |false|, the policy is never set to a recommended value so |check_for_recommended| has no effect.",
"note": "The following entries should be specified if controlled setting indicators exist for |pref| in the settings UI.", "note": "The following entries should be specified if controlled setting indicators exist for |pref| in the settings UI.",
"indicator_test_url": "The URL to navigate to in order to test the indicators. Defaults to |chrome://settings-frame/| if not specified.",
"indicator_test_setup_js": "Any JavaScript that should be executed before testing the indicators. This should be specified only if an explicit user action must be simulated (e.g. clicking a button).", "indicator_test_setup_js": "Any JavaScript that should be executed before testing the indicators. This should be specified only if an explicit user action must be simulated (e.g. clicking a button).",
"indicator_selector": "A CSS selector that locates all controlled setting indicators for |pref|. This is appended to the selector 'span.controlled-setting-indicator' and if not specified, defaults to '[pref=(the value of |pref|)', e.g. '[pref=homepage]'.", "indicator_selector": "A CSS selector that locates all controlled setting indicators for |pref|. This is appended to the selector 'span.controlled-setting-indicator' and if not specified, defaults to '[pref=(the value of |pref|)', e.g. '[pref=homepage]'.",
"note": "Any number of test cases may be specified in the following array.", "note": "Any number of test cases may be specified in the following array.",
...@@ -845,7 +846,13 @@ ...@@ -845,7 +846,13 @@
"os": ["win", "linux", "mac", "chromeos"], "os": ["win", "linux", "mac", "chromeos"],
"test_policy": { "DeveloperToolsDisabled": true }, "test_policy": { "DeveloperToolsDisabled": true },
"pref_mappings": [ "pref_mappings": [
{ "pref": "devtools.disabled" } { "pref": "devtools.disabled",
"indicator_test_url": "chrome://extensions-frame/",
"indicator_selector": "#dev-toggle-disabled-by-policy-indicator",
"indicator_tests": [
{ "policy": { "DeveloperToolsDisabled": true } }
]
}
] ]
}, },
......
...@@ -322,6 +322,7 @@ chrome.developerPrivate.ExtensionInfo; ...@@ -322,6 +322,7 @@ chrome.developerPrivate.ExtensionInfo;
* appInfoDialogEnabled: boolean, * appInfoDialogEnabled: boolean,
* canLoadUnpacked: boolean, * canLoadUnpacked: boolean,
* inDeveloperMode: boolean, * inDeveloperMode: boolean,
* isDeveloperModeControlledByPolicy: boolean,
* isIncognitoAvailable: boolean, * isIncognitoAvailable: boolean,
* isSupervised: boolean * isSupervised: boolean
* }} * }}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment