Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(98)

Side by Side Diff: chrome/browser/extensions/api/settings_private/prefs_util.cc

Issue 1310373008: Add cr_policy_indicator for settings controls (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@md_settings_compiled_resources_3
Patch Set: Better observer pattern for cr_policy_indicator Created 5 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2015 The Chromium Authors. All rights reserved. 1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "chrome/browser/extensions/api/settings_private/prefs_util.h"
6
5 #include "base/prefs/pref_service.h" 7 #include "base/prefs/pref_service.h"
6 #include "chrome/browser/browser_process.h" 8 #include "chrome/browser/browser_process.h"
7 #include "chrome/browser/extensions/api/settings_private/prefs_util.h"
8 #include "chrome/browser/extensions/chrome_extension_function.h" 9 #include "chrome/browser/extensions/chrome_extension_function.h"
9 #include "chrome/browser/profiles/profile.h" 10 #include "chrome/browser/profiles/profile.h"
10 #include "chrome/common/pref_names.h" 11 #include "chrome/common/pref_names.h"
11 #include "components/proxy_config/proxy_config_pref_names.h" 12 #include "components/proxy_config/proxy_config_pref_names.h"
12 #include "components/url_formatter/url_fixer.h" 13 #include "components/url_formatter/url_fixer.h"
14 #include "extensions/browser/extension_pref_value_map.h"
15 #include "extensions/browser/extension_pref_value_map_factory.h"
16 #include "extensions/browser/extension_registry.h"
13 17
14 #if defined(OS_CHROMEOS) 18 #if defined(OS_CHROMEOS)
15 #include "chrome/browser/chromeos/ownership/owner_settings_service_chromeos.h" 19 #include "chrome/browser/chromeos/ownership/owner_settings_service_chromeos.h"
16 #include "chrome/browser/chromeos/ownership/owner_settings_service_chromeos_fact ory.h" 20 #include "chrome/browser/chromeos/ownership/owner_settings_service_chromeos_fact ory.h"
21 #include "chrome/browser/chromeos/policy/browser_policy_connector_chromeos.h"
22 #include "chrome/browser/chromeos/profiles/profile_helper.h"
17 #include "chrome/browser/chromeos/settings/cros_settings.h" 23 #include "chrome/browser/chromeos/settings/cros_settings.h"
24 #include "chromeos/settings/cros_settings_names.h"
18 #endif 25 #endif
19 26
27 namespace {
28
29 #if defined(OS_CHROMEOS)
30 bool IsPrivilegedCrosSetting(const std::string& pref_name) {
31 if (!chromeos::CrosSettings::IsCrosSettings(pref_name))
32 return false;
33 // kSystemTimezone should be changeable by all users.
34 if (pref_name == chromeos::kSystemTimezone)
35 return false;
36 // All other Cros settings are considered privileged and are either policy
37 // controlled or owner controlled.
38 return true;
39 }
40 #endif
41
42 } // namespace
43
20 namespace extensions { 44 namespace extensions {
21 45
22 namespace settings_private = api::settings_private; 46 namespace settings_private = api::settings_private;
23 47
24 PrefsUtil::PrefsUtil(Profile* profile) : profile_(profile) { 48 PrefsUtil::PrefsUtil(Profile* profile) : profile_(profile) {}
25 }
26 49
27 PrefsUtil::~PrefsUtil() { 50 PrefsUtil::~PrefsUtil() {}
28 }
29 51
30 #if defined(OS_CHROMEOS) 52 #if defined(OS_CHROMEOS)
31 using CrosSettings = chromeos::CrosSettings; 53 using CrosSettings = chromeos::CrosSettings;
32 #endif 54 #endif
33 55
34 const PrefsUtil::TypedPrefMap& PrefsUtil::GetWhitelistedKeys() { 56 const PrefsUtil::TypedPrefMap& PrefsUtil::GetWhitelistedKeys() {
35 static PrefsUtil::TypedPrefMap* s_whitelist = nullptr; 57 static PrefsUtil::TypedPrefMap* s_whitelist = nullptr;
36 if (s_whitelist) 58 if (s_whitelist)
37 return *s_whitelist; 59 return *s_whitelist;
38 s_whitelist = new PrefsUtil::TypedPrefMap(); 60 s_whitelist = new PrefsUtil::TypedPrefMap();
(...skipping 64 matching lines...) Expand 10 before | Expand all | Expand 10 after
103 settings_private::PrefType::PREF_TYPE_BOOLEAN; 125 settings_private::PrefType::PREF_TYPE_BOOLEAN;
104 (*s_whitelist)["cros.device.attestation_for_content_protection_enabled"] = 126 (*s_whitelist)["cros.device.attestation_for_content_protection_enabled"] =
105 settings_private::PrefType::PREF_TYPE_BOOLEAN; 127 settings_private::PrefType::PREF_TYPE_BOOLEAN;
106 (*s_whitelist)["settings.internet.wake_on_wifi_ssid"] = 128 (*s_whitelist)["settings.internet.wake_on_wifi_ssid"] =
107 settings_private::PrefType::PREF_TYPE_BOOLEAN; 129 settings_private::PrefType::PREF_TYPE_BOOLEAN;
108 #endif 130 #endif
109 131
110 return *s_whitelist; 132 return *s_whitelist;
111 } 133 }
112 134
113 api::settings_private::PrefType PrefsUtil::GetType(const std::string& name, 135 settings_private::PrefType PrefsUtil::GetType(const std::string& name,
114 base::Value::Type type) { 136 base::Value::Type type) {
115 switch (type) { 137 switch (type) {
116 case base::Value::Type::TYPE_BOOLEAN: 138 case base::Value::Type::TYPE_BOOLEAN:
117 return api::settings_private::PrefType::PREF_TYPE_BOOLEAN; 139 return settings_private::PrefType::PREF_TYPE_BOOLEAN;
118 case base::Value::Type::TYPE_INTEGER: 140 case base::Value::Type::TYPE_INTEGER:
119 case base::Value::Type::TYPE_DOUBLE: 141 case base::Value::Type::TYPE_DOUBLE:
120 return api::settings_private::PrefType::PREF_TYPE_NUMBER; 142 return settings_private::PrefType::PREF_TYPE_NUMBER;
121 case base::Value::Type::TYPE_STRING: 143 case base::Value::Type::TYPE_STRING:
122 return IsPrefTypeURL(name) 144 return IsPrefTypeURL(name) ? settings_private::PrefType::PREF_TYPE_URL
123 ? api::settings_private::PrefType::PREF_TYPE_URL 145 : settings_private::PrefType::PREF_TYPE_STRING;
124 : api::settings_private::PrefType::PREF_TYPE_STRING;
125 case base::Value::Type::TYPE_LIST: 146 case base::Value::Type::TYPE_LIST:
126 return api::settings_private::PrefType::PREF_TYPE_LIST; 147 return settings_private::PrefType::PREF_TYPE_LIST;
127 default: 148 default:
128 return api::settings_private::PrefType::PREF_TYPE_NONE; 149 return settings_private::PrefType::PREF_TYPE_NONE;
129 } 150 }
130 } 151 }
131 152
132 scoped_ptr<api::settings_private::PrefObject> PrefsUtil::GetCrosSettingsPref( 153 scoped_ptr<settings_private::PrefObject> PrefsUtil::GetCrosSettingsPref(
133 const std::string& name) { 154 const std::string& name) {
134 scoped_ptr<api::settings_private::PrefObject> pref_object( 155 scoped_ptr<settings_private::PrefObject> pref_object(
135 new api::settings_private::PrefObject()); 156 new settings_private::PrefObject());
136 157
137 #if defined(OS_CHROMEOS) 158 #if defined(OS_CHROMEOS)
138 const base::Value* value = CrosSettings::Get()->GetPref(name); 159 const base::Value* value = CrosSettings::Get()->GetPref(name);
160 DCHECK(value);
michaelpg 2015/09/17 02:19:25 value shouldn't be NULL, but it could be, so a DCH
stevenjb 2015/09/17 17:33:31 GetPref() already has a NOTREACHED if it returns n
139 pref_object->key = name; 161 pref_object->key = name;
140 pref_object->type = GetType(name, value->GetType()); 162 pref_object->type = GetType(name, value->GetType());
141 pref_object->value.reset(value->DeepCopy()); 163 pref_object->value.reset(value->DeepCopy());
142 #endif 164 #endif
143 165
144 return pref_object.Pass(); 166 return pref_object.Pass();
145 } 167 }
146 168
147 scoped_ptr<api::settings_private::PrefObject> PrefsUtil::GetPref( 169 scoped_ptr<settings_private::PrefObject> PrefsUtil::GetPref(
148 const std::string& name) { 170 const std::string& name) {
149 if (IsCrosSetting(name)) 171 const PrefService::Preference* pref = nullptr;
150 return GetCrosSettingsPref(name); 172 scoped_ptr<settings_private::PrefObject> pref_object;
173 if (IsCrosSetting(name)) {
174 pref_object = GetCrosSettingsPref(name);
175 } else {
176 PrefService* pref_service = FindServiceForPref(name);
177 pref = pref_service->FindPreference(name);
178 if (!pref)
179 return nullptr;
180 pref_object.reset(new settings_private::PrefObject());
181 pref_object->key = pref->name();
182 pref_object->type = GetType(name, pref->GetType());
183 pref_object->value.reset(pref->GetValue()->DeepCopy());
184 }
151 185
152 PrefService* pref_service = FindServiceForPref(name); 186 #if defined(OS_CHROMEOS)
153 const PrefService::Preference* pref = pref_service->FindPreference(name); 187 if (IsPrefPrimaryUserControlled(name)) {
154 if (!pref) 188 pref_object->policy_source =
155 return nullptr; 189 settings_private::PolicySource::POLICY_SOURCE_PRIMARY_USER;
190 pref_object->policy_enforcement =
191 settings_private::PolicyEnforcement::POLICY_ENFORCEMENT_ENFORCED;
192 return pref_object.Pass();
193 }
194 if (IsPrefEnterpriseManaged(name)) {
195 // Enterprise managed prefs are treated the same as device policy restricted
196 // prefs in the UI.
197 pref_object->policy_source =
198 settings_private::PolicySource::POLICY_SOURCE_DEVICE_POLICY;
199 pref_object->policy_enforcement =
200 settings_private::PolicyEnforcement::POLICY_ENFORCEMENT_ENFORCED;
201 return pref_object.Pass();
202 }
203 #endif
156 204
157 scoped_ptr<api::settings_private::PrefObject> pref_object( 205 if (pref && pref->IsManaged()) {
158 new api::settings_private::PrefObject()); 206 pref_object->policy_source =
159 pref_object->key = pref->name(); 207 settings_private::PolicySource::POLICY_SOURCE_USER_POLICY;
160 pref_object->type = GetType(name, pref->GetType()); 208 pref_object->policy_enforcement =
161 pref_object->value.reset(pref->GetValue()->DeepCopy()); 209 settings_private::PolicyEnforcement::POLICY_ENFORCEMENT_ENFORCED;
210 return pref_object.Pass();
211 }
212 if (pref && pref->IsRecommended()) {
213 pref_object->policy_source =
214 settings_private::PolicySource::POLICY_SOURCE_USER_POLICY;
215 pref_object->policy_enforcement =
216 settings_private::PolicyEnforcement::POLICY_ENFORCEMENT_RECOMMENDED;
217 pref_object->recommended_value.reset(
218 pref->GetRecommendedValue()->DeepCopy());
219 return pref_object.Pass();
220 }
162 221
163 if (pref->IsManaged()) { 222 #if defined(OS_CHROMEOS)
164 if (pref->IsManagedByCustodian()) { 223 if (IsPrefOwnerControlled(name)) {
165 pref_object->policy_source = 224 // Check for owner controlled after managed checks because if there is a
166 api::settings_private::PolicySource::POLICY_SOURCE_DEVICE; 225 // device policy there is no "owner". (In the unlikely case that both
167 } else { 226 // situations apply, either badge is potentially relevent, so the order
michaelpg 2015/09/17 02:19:25 sp: relevant
stevenjb 2015/09/17 17:33:31 Done.
168 pref_object->policy_source = 227 // is somewhat arbitrary).
169 api::settings_private::PolicySource::POLICY_SOURCE_USER; 228 pref_object->policy_source =
170 } 229 settings_private::PolicySource::POLICY_SOURCE_OWNER;
171 pref_object->policy_enforcement = 230 pref_object->policy_enforcement =
172 pref->IsRecommended() ? api::settings_private::PolicyEnforcement:: 231 settings_private::PolicyEnforcement::POLICY_ENFORCEMENT_ENFORCED;
173 POLICY_ENFORCEMENT_RECOMMENDED 232 return pref_object.Pass();
174 : api::settings_private::PolicyEnforcement:: 233 }
175 POLICY_ENFORCEMENT_ENFORCED; 234 #endif
176 } else if (!IsPrefUserModifiable(name)) { 235
236 if (pref && pref->IsExtensionControlled()) {
177 pref_object->policy_source = 237 pref_object->policy_source =
178 api::settings_private::PolicySource::POLICY_SOURCE_USER; 238 settings_private::PolicySource::POLICY_SOURCE_EXTENSION;
179 pref_object->policy_enforcement = 239 pref_object->policy_enforcement =
180 api::settings_private::PolicyEnforcement::POLICY_ENFORCEMENT_ENFORCED; 240 settings_private::PolicyEnforcement::POLICY_ENFORCEMENT_ENFORCED;
241 std::string extension_id =
242 ExtensionPrefValueMapFactory::GetForBrowserContext(profile_)
243 ->GetExtensionControllingPref(pref->name());
244 pref_object->extension_id.reset(new std::string(extension_id));
245 return pref_object.Pass();
246 }
247 if (pref && (!pref->IsUserModifiable() || IsPrefSupervisorControlled(name))) {
248 // TODO(stevenjb): Investigate whether either of these should be badged.
249 pref_object->read_only.reset(new bool(true));
250 return pref_object.Pass();
181 } 251 }
182 252
183 return pref_object.Pass(); 253 return pref_object.Pass();
184 } 254 }
185 255
186 PrefsUtil::SetPrefResult PrefsUtil::SetPref(const std::string& pref_name, 256 PrefsUtil::SetPrefResult PrefsUtil::SetPref(const std::string& pref_name,
187 const base::Value* value) { 257 const base::Value* value) {
188 if (IsCrosSetting(pref_name)) 258 if (IsCrosSetting(pref_name))
189 return SetCrosSettingsPref(pref_name, value); 259 return SetCrosSettingsPref(pref_name, value);
190 260
191 PrefService* pref_service = FindServiceForPref(pref_name); 261 PrefService* pref_service = FindServiceForPref(pref_name);
192 262
193 if (!IsPrefUserModifiable(pref_name)) 263 if (!IsPrefUserModifiable(pref_name))
194 return PREF_NOT_MODIFIABLE; 264 return PREF_NOT_MODIFIABLE;
195 265
196 const PrefService::Preference* pref = 266 const PrefService::Preference* pref = pref_service->FindPreference(pref_name);
197 pref_service->FindPreference(pref_name);
198 if (!pref) 267 if (!pref)
199 return PREF_NOT_FOUND; 268 return PREF_NOT_FOUND;
200 269
201 DCHECK_EQ(pref->GetType(), value->GetType()); 270 DCHECK_EQ(pref->GetType(), value->GetType());
202 271
203 switch (pref->GetType()) { 272 switch (pref->GetType()) {
204 case base::Value::TYPE_BOOLEAN: 273 case base::Value::TYPE_BOOLEAN:
205 case base::Value::TYPE_DOUBLE: 274 case base::Value::TYPE_DOUBLE:
206 case base::Value::TYPE_LIST: 275 case base::Value::TYPE_LIST:
207 pref_service->Set(pref_name, *value); 276 pref_service->Set(pref_name, *value);
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after
244 chromeos::OwnerSettingsServiceChromeOSFactory::GetForBrowserContext( 313 chromeos::OwnerSettingsServiceChromeOSFactory::GetForBrowserContext(
245 profile_); 314 profile_);
246 315
247 // Check if setting requires owner. 316 // Check if setting requires owner.
248 if (service && service->HandlesSetting(pref_name)) { 317 if (service && service->HandlesSetting(pref_name)) {
249 if (service->Set(pref_name, *value)) 318 if (service->Set(pref_name, *value))
250 return SUCCESS; 319 return SUCCESS;
251 return PREF_NOT_MODIFIABLE; 320 return PREF_NOT_MODIFIABLE;
252 } 321 }
253 322
254 chromeos::CrosSettings::Get()->Set(pref_name, *value); 323 CrosSettings::Get()->Set(pref_name, *value);
255 return SUCCESS; 324 return SUCCESS;
256 #else 325 #else
257 return PREF_NOT_FOUND; 326 return PREF_NOT_FOUND;
258 #endif 327 #endif
259 } 328 }
260 329
261 bool PrefsUtil::AppendToListCrosSetting(const std::string& pref_name, 330 bool PrefsUtil::AppendToListCrosSetting(const std::string& pref_name,
262 const base::Value& value) { 331 const base::Value& value) {
263 #if defined(OS_CHROMEOS) 332 #if defined(OS_CHROMEOS)
264 chromeos::OwnerSettingsServiceChromeOS* service = 333 chromeos::OwnerSettingsServiceChromeOS* service =
265 chromeos::OwnerSettingsServiceChromeOSFactory::GetForBrowserContext( 334 chromeos::OwnerSettingsServiceChromeOSFactory::GetForBrowserContext(
266 profile_); 335 profile_);
267 336
268 // Returns false if not the owner, for settings requiring owner. 337 // Returns false if not the owner, for settings requiring owner.
269 if (service && service->HandlesSetting(pref_name)) { 338 if (service && service->HandlesSetting(pref_name)) {
270 return service->AppendToList(pref_name, value); 339 return service->AppendToList(pref_name, value);
271 } 340 }
272 341
273 chromeos::CrosSettings::Get()->AppendToList(pref_name, &value); 342 CrosSettings::Get()->AppendToList(pref_name, &value);
274 return true; 343 return true;
275 #else 344 #else
276 return false; 345 return false;
277 #endif 346 #endif
278 } 347 }
279 348
280 bool PrefsUtil::RemoveFromListCrosSetting(const std::string& pref_name, 349 bool PrefsUtil::RemoveFromListCrosSetting(const std::string& pref_name,
281 const base::Value& value) { 350 const base::Value& value) {
282 #if defined(OS_CHROMEOS) 351 #if defined(OS_CHROMEOS)
283 chromeos::OwnerSettingsServiceChromeOS* service = 352 chromeos::OwnerSettingsServiceChromeOS* service =
284 chromeos::OwnerSettingsServiceChromeOSFactory::GetForBrowserContext( 353 chromeos::OwnerSettingsServiceChromeOSFactory::GetForBrowserContext(
285 profile_); 354 profile_);
286 355
287 // Returns false if not the owner, for settings requiring owner. 356 // Returns false if not the owner, for settings requiring owner.
288 if (service && service->HandlesSetting(pref_name)) { 357 if (service && service->HandlesSetting(pref_name)) {
289 return service->RemoveFromList(pref_name, value); 358 return service->RemoveFromList(pref_name, value);
290 } 359 }
291 360
292 chromeos::CrosSettings::Get()->RemoveFromList(pref_name, &value); 361 CrosSettings::Get()->RemoveFromList(pref_name, &value);
293 return true; 362 return true;
294 #else 363 #else
295 return false; 364 return false;
296 #endif 365 #endif
297 } 366 }
298 367
299 bool PrefsUtil::IsPrefTypeURL(const std::string& pref_name) { 368 bool PrefsUtil::IsPrefTypeURL(const std::string& pref_name) {
300 settings_private::PrefType pref_type = 369 settings_private::PrefType pref_type =
301 settings_private::PrefType::PREF_TYPE_NONE; 370 settings_private::PrefType::PREF_TYPE_NONE;
302 371
303 const TypedPrefMap keys = GetWhitelistedKeys(); 372 const TypedPrefMap keys = GetWhitelistedKeys();
304 const auto& iter = keys.find(pref_name); 373 const auto& iter = keys.find(pref_name);
305 if (iter != keys.end()) 374 if (iter != keys.end())
306 pref_type = iter->second; 375 pref_type = iter->second;
307 376
308 return pref_type == settings_private::PrefType::PREF_TYPE_URL; 377 return pref_type == settings_private::PrefType::PREF_TYPE_URL;
309 } 378 }
310 379
311 bool PrefsUtil::IsPrefUserModifiable(const std::string& pref_name) { 380 #if defined(OS_CHROMEOS)
381 bool PrefsUtil::IsPrefEnterpriseManaged(const std::string& pref_name) {
382 if (IsPrivilegedCrosSetting(pref_name)) {
383 policy::BrowserPolicyConnectorChromeOS* connector =
384 g_browser_process->platform_part()->browser_policy_connector_chromeos();
385 if (connector->IsEnterpriseManaged())
386 return true;
387 }
388 return false;
389 }
390
391 bool PrefsUtil::IsPrefOwnerControlled(const std::string& pref_name) {
392 if (IsPrivilegedCrosSetting(pref_name)) {
393 if (!chromeos::ProfileHelper::IsOwnerProfile(profile_))
394 return true;
395 }
396 return false;
397 }
398
399 bool PrefsUtil::IsPrefPrimaryUserControlled(const std::string& pref_name) {
400 if (pref_name == prefs::kWakeOnWifiSsid) {
401 user_manager::UserManager* user_manager = user_manager::UserManager::Get();
402 const user_manager::User* user =
403 chromeos::ProfileHelper::Get()->GetUserByProfile(profile_);
404 if (user && user->email() != user_manager->GetPrimaryUser()->email())
405 return true;
406 }
407 return false;
408 }
409 #endif
410
411 bool PrefsUtil::IsPrefSupervisorControlled(const std::string& pref_name) {
312 if (pref_name != prefs::kBrowserGuestModeEnabled && 412 if (pref_name != prefs::kBrowserGuestModeEnabled &&
313 pref_name != prefs::kBrowserAddPersonEnabled) { 413 pref_name != prefs::kBrowserAddPersonEnabled) {
314 return true; 414 return false;
315 } 415 }
416 return profile_->IsSupervised();
417 }
316 418
419 bool PrefsUtil::IsPrefUserModifiable(const std::string& pref_name) {
317 PrefService* pref_service = profile_->GetPrefs(); 420 PrefService* pref_service = profile_->GetPrefs();
318 const PrefService::Preference* pref = 421 const PrefService::Preference* pref =
319 pref_service->FindPreference(pref_name.c_str()); 422 pref_service->FindPreference(pref_name.c_str());
320 if (!pref || !pref->IsUserModifiable() || profile_->IsSupervised()) 423 return pref && pref->IsUserModifiable();
321 return false;
322
323 return true;
324 } 424 }
325 425
326 PrefService* PrefsUtil::FindServiceForPref(const std::string& pref_name) { 426 PrefService* PrefsUtil::FindServiceForPref(const std::string& pref_name) {
327 PrefService* user_prefs = profile_->GetPrefs(); 427 PrefService* user_prefs = profile_->GetPrefs();
328 428
329 // Proxy is a peculiar case: on ChromeOS, settings exist in both user 429 // Proxy is a peculiar case: on ChromeOS, settings exist in both user
330 // prefs and local state, but chrome://settings should affect only user prefs. 430 // prefs and local state, but chrome://settings should affect only user prefs.
331 // Elsewhere the proxy settings are stored in local state. 431 // Elsewhere the proxy settings are stored in local state.
332 // See http://crbug.com/157147 432 // See http://crbug.com/157147
333 433
(...skipping 19 matching lines...) Expand all
353 453
354 bool PrefsUtil::IsCrosSetting(const std::string& pref_name) { 454 bool PrefsUtil::IsCrosSetting(const std::string& pref_name) {
355 #if defined(OS_CHROMEOS) 455 #if defined(OS_CHROMEOS)
356 return CrosSettings::Get()->IsCrosSettings(pref_name); 456 return CrosSettings::Get()->IsCrosSettings(pref_name);
357 #else 457 #else
358 return false; 458 return false;
359 #endif 459 #endif
360 } 460 }
361 461
362 } // namespace extensions 462 } // namespace extensions
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698