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

Side by Side Diff: chrome/browser/extensions/api/extension_action/extension_action_api.cc

Issue 11638022: Consolidate [BrowserAction,PageAction,ScriptBadge]API into ExtensionActionAPI (Closed) Base URL: http://git.chromium.org/chromium/src.git@dc_unref_script_badge
Patch Set: nits Created 8 years 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 (c) 2012 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2012 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/extension_action/extension_actions_api.h " 5 #include "chrome/browser/extensions/api/extension_action/extension_action_api.h"
6 6
7 #include <string> 7 #include <string>
8 8
9 #include "base/base64.h" 9 #include "base/base64.h"
10 #include "base/string_number_conversions.h" 10 #include "base/string_number_conversions.h"
11 #include "base/string_util.h" 11 #include "base/string_util.h"
12 #include "base/values.h" 12 #include "base/values.h"
13 #include "chrome/browser/extensions/api/extension_action/extension_page_actions_ api_constants.h" 13 #include "chrome/browser/extensions/api/extension_action/extension_page_actions_ api_constants.h"
14 #include "chrome/browser/extensions/extension_action.h" 14 #include "chrome/browser/extensions/extension_action.h"
15 #include "chrome/browser/extensions/extension_action_manager.h" 15 #include "chrome/browser/extensions/extension_action_manager.h"
16 #include "chrome/browser/extensions/extension_service.h" 16 #include "chrome/browser/extensions/extension_service.h"
17 #include "chrome/browser/extensions/extension_system.h" 17 #include "chrome/browser/extensions/extension_system.h"
18 #include "chrome/browser/extensions/extension_tab_util.h" 18 #include "chrome/browser/extensions/extension_tab_util.h"
19 #include "chrome/browser/extensions/location_bar_controller.h" 19 #include "chrome/browser/extensions/location_bar_controller.h"
20 #include "chrome/browser/extensions/state_store.h" 20 #include "chrome/browser/extensions/state_store.h"
21 #include "chrome/browser/extensions/tab_helper.h" 21 #include "chrome/browser/extensions/tab_helper.h"
22 #include "chrome/browser/profiles/profile.h" 22 #include "chrome/browser/profiles/profile.h"
23 #include "chrome/common/chrome_notification_types.h" 23 #include "chrome/common/chrome_notification_types.h"
24 #include "chrome/common/extensions/api/extension_action/action_info.h" 24 #include "chrome/common/extensions/api/extension_action/action_info.h"
25 #include "chrome/common/extensions/api/extension_action/script_badge_handler.h"
26 #include "chrome/common/extensions/extension_manifest_constants.h"
27 #include "chrome/common/extensions/manifest_handler.h"
25 #include "chrome/common/render_messages.h" 28 #include "chrome/common/render_messages.h"
26 #include "content/public/browser/navigation_entry.h" 29 #include "content/public/browser/navigation_entry.h"
27 #include "content/public/browser/notification_service.h" 30 #include "content/public/browser/notification_service.h"
28 #include "extensions/common/error_utils.h" 31 #include "extensions/common/error_utils.h"
29 32
30 namespace { 33 namespace {
31 34
32 const char kBrowserActionStorageKey[] = "browser_action"; 35 const char kBrowserActionStorageKey[] = "browser_action";
33 const char kPopupUrlStorageKey[] = "poupup_url"; 36 const char kPopupUrlStorageKey[] = "poupup_url";
34 const char kTitleStorageKey[] = "title"; 37 const char kTitleStorageKey[] = "title";
35 const char kIconStorageKey[] = "icon"; 38 const char kIconStorageKey[] = "icon";
36 const char kBadgeTextStorageKey[] = "badge_text"; 39 const char kBadgeTextStorageKey[] = "badge_text";
37 const char kBadgeBackgroundColorStorageKey[] = "badge_background_color"; 40 const char kBadgeBackgroundColorStorageKey[] = "badge_background_color";
38 const char kBadgeTextColorStorageKey[] = "badge_text_color"; 41 const char kBadgeTextColorStorageKey[] = "badge_text_color";
39 const char kAppearanceStorageKey[] = "appearance"; 42 const char kAppearanceStorageKey[] = "appearance";
40 43
41 // Errors. 44 // Errors.
42 const char kNoExtensionActionError[] = 45 const char kNoExtensionActionError[] =
43 "This extension has no action specified."; 46 "This extension has no action specified.";
44 const char kNoTabError[] = "No tab with id: *."; 47 const char kNoTabError[] = "No tab with id: *.";
48 const char kNoPageActionError[] =
49 "This extension has no page action specified.";
50 const char kUrlNotActiveError[] = "This url is no longer active: *.";
51
45 52
46 struct IconRepresentationInfo { 53 struct IconRepresentationInfo {
47 // Size as a string that will be used to retrieve representation value from 54 // Size as a string that will be used to retrieve representation value from
48 // SetIcon function arguments. 55 // SetIcon function arguments.
49 const char* size_string; 56 const char* size_string;
50 // Scale factor for which the represantion should be used. 57 // Scale factor for which the represantion should be used.
51 ui::ScaleFactor scale; 58 ui::ScaleFactor scale;
52 }; 59 };
53 60
54 61
(...skipping 110 matching lines...) Expand 10 before | Expand all | Expand 10 after
165 dict->Set(kIconStorageKey, icon_value); 172 dict->Set(kIconStorageKey, icon_value);
166 } 173 }
167 return dict.Pass(); 174 return dict.Pass();
168 } 175 }
169 176
170 } // namespace 177 } // namespace
171 178
172 namespace extensions { 179 namespace extensions {
173 180
174 // 181 //
182 // ExtensionActionAPI
183 //
184
185 ExtensionActionAPI::ExtensionActionAPI(Profile* profile) {
186 ManifestHandler::Register(extension_manifest_keys::kScriptBadge,
187 new ScriptBadgeHandler);
188 }
189
190 ExtensionActionAPI::~ExtensionActionAPI() {
191 }
192
193 void ExtensionActionAPI::Shutdown() {
194 }
195
196 //
175 // ExtensionActionStorageManager 197 // ExtensionActionStorageManager
176 // 198 //
177 199
178 ExtensionActionStorageManager::ExtensionActionStorageManager(Profile* profile) 200 ExtensionActionStorageManager::ExtensionActionStorageManager(Profile* profile)
179 : profile_(profile) { 201 : profile_(profile) {
180 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_LOADED, 202 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_LOADED,
181 content::Source<Profile>(profile_)); 203 content::Source<Profile>(profile_));
182 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_BROWSER_ACTION_UPDATED, 204 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_BROWSER_ACTION_UPDATED,
183 content::NotificationService::AllBrowserContextsAndSources()); 205 content::NotificationService::AllBrowserContextsAndSources());
184 206
185 StateStore* storage = ExtensionSystem::Get(profile_)->state_store(); 207 StateStore* storage = ExtensionSystem::Get(profile_)->state_store();
186 if (storage) 208 if (storage)
187 storage->RegisterKey(kBrowserActionStorageKey); 209 storage->RegisterKey(kBrowserActionStorageKey);
188 } 210 }
189 211
190 ExtensionActionStorageManager::~ExtensionActionStorageManager() { 212 ExtensionActionStorageManager::~ExtensionActionStorageManager() {
191 } 213 }
192 214
193 void ExtensionActionStorageManager::Observe( 215 void ExtensionActionStorageManager::Observe(
194 int type, 216 int type,
195 const content::NotificationSource& source, 217 const content::NotificationSource& source,
196 const content::NotificationDetails& details) { 218 const content::NotificationDetails& details) {
197 switch (type) { 219 switch (type) {
198 case chrome::NOTIFICATION_EXTENSION_LOADED: { 220 case chrome::NOTIFICATION_EXTENSION_LOADED: {
199 const Extension* extension = 221 const Extension* extension =
200 content::Details<const Extension>(details).ptr(); 222 content::Details<const Extension>(details).ptr();
201 if (!extensions::ExtensionActionManager::Get(profile_)-> 223 if (!ExtensionActionManager::Get(profile_)->
202 GetBrowserAction(*extension)) { 224 GetBrowserAction(*extension)) {
203 break; 225 break;
204 } 226 }
205 227
206 StateStore* storage = ExtensionSystem::Get(profile_)->state_store(); 228 StateStore* storage = ExtensionSystem::Get(profile_)->state_store();
207 if (storage) { 229 if (storage) {
208 storage->GetExtensionValue(extension->id(), kBrowserActionStorageKey, 230 storage->GetExtensionValue(extension->id(), kBrowserActionStorageKey,
209 base::Bind(&ExtensionActionStorageManager::ReadFromStorage, 231 base::Bind(&ExtensionActionStorageManager::ReadFromStorage,
210 AsWeakPtr(), extension->id())); 232 AsWeakPtr(), extension->id()));
211 } 233 }
(...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after
260 if (browser_action->has_changed()) 282 if (browser_action->has_changed())
261 return; 283 return;
262 284
263 const base::DictionaryValue* dict = NULL; 285 const base::DictionaryValue* dict = NULL;
264 if (!value.get() || !value->GetAsDictionary(&dict)) 286 if (!value.get() || !value->GetAsDictionary(&dict))
265 return; 287 return;
266 288
267 SetDefaultsFromValue(dict, browser_action); 289 SetDefaultsFromValue(dict, browser_action);
268 } 290 }
269 291
270 } // namespace extensions
271
272
273 // 292 //
274 // ExtensionActionFunction 293 // ExtensionActionFunction
275 // 294 //
276 295
277 ExtensionActionFunction::ExtensionActionFunction() 296 ExtensionActionFunction::ExtensionActionFunction()
278 : details_(NULL), 297 : details_(NULL),
279 tab_id_(ExtensionAction::kDefaultTabId), 298 tab_id_(ExtensionAction::kDefaultTabId),
280 contents_(NULL), 299 contents_(NULL),
281 extension_action_(NULL) { 300 extension_action_(NULL) {
282 } 301 }
283 302
284 ExtensionActionFunction::~ExtensionActionFunction() { 303 ExtensionActionFunction::~ExtensionActionFunction() {
285 } 304 }
286 305
287 bool ExtensionActionFunction::RunImpl() { 306 bool ExtensionActionFunction::RunImpl() {
288 extensions::ExtensionActionManager* manager = 307 ExtensionActionManager* manager = ExtensionActionManager::Get(profile_);
289 extensions::ExtensionActionManager::Get(profile_); 308 const Extension* extension = GetExtension();
290 const extensions::Extension* extension = GetExtension();
291 if (StartsWithASCII(name(), "scriptBadge.", false)) { 309 if (StartsWithASCII(name(), "scriptBadge.", false)) {
292 extension_action_ = manager->GetScriptBadge(*extension); 310 extension_action_ = manager->GetScriptBadge(*extension);
293 } else if (StartsWithASCII(name(), "systemIndicator.", false)) { 311 } else if (StartsWithASCII(name(), "systemIndicator.", false)) {
294 extension_action_ = manager->GetSystemIndicator(*extension); 312 extension_action_ = manager->GetSystemIndicator(*extension);
295 } else { 313 } else {
296 extension_action_ = manager->GetBrowserAction(*extension); 314 extension_action_ = manager->GetBrowserAction(*extension);
297 if (!extension_action_) { 315 if (!extension_action_) {
298 extension_action_ = manager->GetPageAction(*extension); 316 extension_action_ = manager->GetPageAction(*extension);
299 } 317 }
300 } 318 }
301 if (!extension_action_) { 319 if (!extension_action_) {
302 // TODO(kalman): ideally the browserAction/pageAction APIs wouldn't event 320 // TODO(kalman): ideally the browserAction/pageAction APIs wouldn't event
303 // exist for extensions that don't have one declared. This should come as 321 // exist for extensions that don't have one declared. This should come as
304 // part of the Feature system. 322 // part of the Feature system.
305 error_ = kNoExtensionActionError; 323 error_ = kNoExtensionActionError;
306 return false; 324 return false;
307 } 325 }
308 326
309 // Populates the tab_id_ and details_ members. 327 // Populates the tab_id_ and details_ members.
310 EXTENSION_FUNCTION_VALIDATE(ExtractDataFromArguments()); 328 EXTENSION_FUNCTION_VALIDATE(ExtractDataFromArguments());
311 329
312 // Find the WebContents that contains this tab id if one is required. 330 // Find the WebContents that contains this tab id if one is required.
313 if (tab_id_ != ExtensionAction::kDefaultTabId) { 331 if (tab_id_ != ExtensionAction::kDefaultTabId) {
314 ExtensionTabUtil::GetTabById( 332 ExtensionTabUtil::GetTabById(
315 tab_id_, profile(), include_incognito(), NULL, NULL, &contents_, NULL); 333 tab_id_, profile(), include_incognito(), NULL, NULL, &contents_, NULL);
316 if (!contents_) { 334 if (!contents_) {
317 error_ = extensions::ErrorUtils::FormatErrorMessage( 335 error_ = ErrorUtils::FormatErrorMessage(
318 kNoTabError, base::IntToString(tab_id_)); 336 kNoTabError, base::IntToString(tab_id_));
319 return false; 337 return false;
320 } 338 }
321 } else { 339 } else {
322 // Only browser actions and system indicators have a default tabId. 340 // Only browser actions and system indicators have a default tabId.
323 extensions::ActionInfo::Type action_type = extension_action_->action_type(); 341 ActionInfo::Type action_type = extension_action_->action_type();
324 EXTENSION_FUNCTION_VALIDATE( 342 EXTENSION_FUNCTION_VALIDATE(
325 action_type == extensions::ActionInfo::TYPE_BROWSER || 343 action_type == ActionInfo::TYPE_BROWSER ||
326 action_type == extensions::ActionInfo::TYPE_SYSTEM_INDICATOR); 344 action_type == ActionInfo::TYPE_SYSTEM_INDICATOR);
327 } 345 }
328 return RunExtensionAction(); 346 return RunExtensionAction();
329 } 347 }
330 348
331 bool ExtensionActionFunction::ExtractDataFromArguments() { 349 bool ExtensionActionFunction::ExtractDataFromArguments() {
332 // There may or may not be details (depends on the function). 350 // There may or may not be details (depends on the function).
333 // The tabId might appear in details (if it exists), as the first 351 // The tabId might appear in details (if it exists), as the first
334 // argument besides the action type (depends on the function), or be omitted 352 // argument besides the action type (depends on the function), or be omitted
335 // entirely. 353 // entirely.
336 base::Value* first_arg = NULL; 354 base::Value* first_arg = NULL;
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after
370 388
371 default: 389 default:
372 return false; 390 return false;
373 } 391 }
374 392
375 return true; 393 return true;
376 } 394 }
377 395
378 void ExtensionActionFunction::NotifyChange() { 396 void ExtensionActionFunction::NotifyChange() {
379 switch (extension_action_->action_type()) { 397 switch (extension_action_->action_type()) {
380 case extensions::ActionInfo::TYPE_BROWSER: 398 case ActionInfo::TYPE_BROWSER:
381 case extensions::ActionInfo::TYPE_PAGE: 399 case ActionInfo::TYPE_PAGE:
382 if (extensions::ExtensionActionManager::Get(profile_)-> 400 if (ExtensionActionManager::Get(profile_)->
383 GetBrowserAction(*extension_)) { 401 GetBrowserAction(*extension_)) {
384 NotifyBrowserActionChange(); 402 NotifyBrowserActionChange();
385 } else if (extensions::ExtensionActionManager::Get(profile_)-> 403 } else if (ExtensionActionManager::Get(profile_)->
386 GetPageAction(*extension_)) { 404 GetPageAction(*extension_)) {
387 NotifyLocationBarChange(); 405 NotifyLocationBarChange();
388 } 406 }
389 return; 407 return;
390 case extensions::ActionInfo::TYPE_SCRIPT_BADGE: 408 case ActionInfo::TYPE_SCRIPT_BADGE:
391 NotifyLocationBarChange(); 409 NotifyLocationBarChange();
392 return; 410 return;
393 case extensions::ActionInfo::TYPE_SYSTEM_INDICATOR: 411 case ActionInfo::TYPE_SYSTEM_INDICATOR:
394 NotifySystemIndicatorChange(); 412 NotifySystemIndicatorChange();
395 return; 413 return;
396 } 414 }
397 NOTREACHED(); 415 NOTREACHED();
398 } 416 }
399 417
400 void ExtensionActionFunction::NotifyBrowserActionChange() { 418 void ExtensionActionFunction::NotifyBrowserActionChange() {
401 content::NotificationService::current()->Notify( 419 content::NotificationService::current()->Notify(
402 chrome::NOTIFICATION_EXTENSION_BROWSER_ACTION_UPDATED, 420 chrome::NOTIFICATION_EXTENSION_BROWSER_ACTION_UPDATED,
403 content::Source<ExtensionAction>(extension_action_), 421 content::Source<ExtensionAction>(extension_action_),
404 content::Details<Profile>(profile())); 422 content::Details<Profile>(profile()));
405 } 423 }
406 424
407 void ExtensionActionFunction::NotifyLocationBarChange() { 425 void ExtensionActionFunction::NotifyLocationBarChange() {
408 extensions::TabHelper::FromWebContents(contents_)-> 426 TabHelper::FromWebContents(contents_)->
409 location_bar_controller()->NotifyChange(); 427 location_bar_controller()->NotifyChange();
410 } 428 }
411 429
412 void ExtensionActionFunction::NotifySystemIndicatorChange() { 430 void ExtensionActionFunction::NotifySystemIndicatorChange() {
413 content::NotificationService::current()->Notify( 431 content::NotificationService::current()->Notify(
414 chrome::NOTIFICATION_EXTENSION_SYSTEM_INDICATOR_UPDATED, 432 chrome::NOTIFICATION_EXTENSION_SYSTEM_INDICATOR_UPDATED,
415 content::Source<Profile>(profile()), 433 content::Source<Profile>(profile()),
416 content::Details<ExtensionAction>(extension_action_)); 434 content::Details<ExtensionAction>(extension_action_));
417 } 435 }
418 436
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after
455 473
456 bool ExtensionActionFunction::SetVisible(bool visible) { 474 bool ExtensionActionFunction::SetVisible(bool visible) {
457 if (extension_action_->GetIsVisible(tab_id_) == visible) 475 if (extension_action_->GetIsVisible(tab_id_) == visible)
458 return true; 476 return true;
459 extension_action_->SetAppearance( 477 extension_action_->SetAppearance(
460 tab_id_, visible ? ExtensionAction::ACTIVE : ExtensionAction::INVISIBLE); 478 tab_id_, visible ? ExtensionAction::ACTIVE : ExtensionAction::INVISIBLE);
461 NotifyChange(); 479 NotifyChange();
462 return true; 480 return true;
463 } 481 }
464 482
465 extensions::TabHelper& ExtensionActionFunction::tab_helper() const { 483 TabHelper& ExtensionActionFunction::tab_helper() const {
466 CHECK(contents_); 484 CHECK(contents_);
467 return *extensions::TabHelper::FromWebContents(contents_); 485 return *TabHelper::FromWebContents(contents_);
468 } 486 }
469 487
470 bool ExtensionActionShowFunction::RunExtensionAction() { 488 bool ExtensionActionShowFunction::RunExtensionAction() {
471 return SetVisible(true); 489 return SetVisible(true);
472 } 490 }
473 491
474 bool ExtensionActionHideFunction::RunExtensionAction() { 492 bool ExtensionActionHideFunction::RunExtensionAction() {
475 return SetVisible(false); 493 return SetVisible(false);
476 } 494 }
477 495
(...skipping 104 matching lines...) Expand 10 before | Expand all | Expand 10 after
582 bool ExtensionActionGetBadgeBackgroundColorFunction::RunExtensionAction() { 600 bool ExtensionActionGetBadgeBackgroundColorFunction::RunExtensionAction() {
583 ListValue* list = new ListValue(); 601 ListValue* list = new ListValue();
584 SkColor color = extension_action_->GetBadgeBackgroundColor(tab_id_); 602 SkColor color = extension_action_->GetBadgeBackgroundColor(tab_id_);
585 list->Append(Value::CreateIntegerValue(SkColorGetR(color))); 603 list->Append(Value::CreateIntegerValue(SkColorGetR(color)));
586 list->Append(Value::CreateIntegerValue(SkColorGetG(color))); 604 list->Append(Value::CreateIntegerValue(SkColorGetG(color)));
587 list->Append(Value::CreateIntegerValue(SkColorGetB(color))); 605 list->Append(Value::CreateIntegerValue(SkColorGetB(color)));
588 list->Append(Value::CreateIntegerValue(SkColorGetA(color))); 606 list->Append(Value::CreateIntegerValue(SkColorGetA(color)));
589 SetResult(list); 607 SetResult(list);
590 return true; 608 return true;
591 } 609 }
610
611 //
612 // ScriptBadgeGetAttentionFunction
613 //
614
615 ScriptBadgeGetAttentionFunction::~ScriptBadgeGetAttentionFunction() {}
616
617 bool ScriptBadgeGetAttentionFunction::RunExtensionAction() {
618 tab_helper().location_bar_controller()->GetAttentionFor(extension_id());
619 return true;
620 }
621
622 } // namespace extensions
623
624 //
625 // PageActionsFunction (deprecated)
626 //
627
628 namespace keys = extension_page_actions_api_constants;
629
630 PageActionsFunction::PageActionsFunction() {
631 }
632
633 PageActionsFunction::~PageActionsFunction() {
634 }
635
636 bool PageActionsFunction::SetPageActionEnabled(bool enable) {
637 std::string extension_action_id;
638 EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &extension_action_id));
639 DictionaryValue* action = NULL;
640 EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(1, &action));
641
642 int tab_id;
643 EXTENSION_FUNCTION_VALIDATE(action->GetInteger(keys::kTabIdKey, &tab_id));
644 std::string url;
645 EXTENSION_FUNCTION_VALIDATE(action->GetString(keys::kUrlKey, &url));
646
647 std::string title;
648 if (enable) {
649 if (action->HasKey(keys::kTitleKey))
650 EXTENSION_FUNCTION_VALIDATE(action->GetString(keys::kTitleKey, &title));
651 }
652
653 ExtensionAction* page_action =
654 extensions::ExtensionActionManager::Get(profile())->
655 GetPageAction(*GetExtension());
656 if (!page_action) {
657 error_ = kNoPageActionError;
658 return false;
659 }
660
661 // Find the WebContents that contains this tab id.
662 content::WebContents* contents = NULL;
663 bool result = ExtensionTabUtil::GetTabById(
664 tab_id, profile(), include_incognito(), NULL, NULL, &contents, NULL);
665 if (!result || !contents) {
666 error_ = extensions::ErrorUtils::FormatErrorMessage(
667 kNoTabError, base::IntToString(tab_id));
668 return false;
669 }
670
671 // Make sure the URL hasn't changed.
672 content::NavigationEntry* entry = contents->GetController().GetActiveEntry();
673 if (!entry || url != entry->GetURL().spec()) {
674 error_ =
675 extensions::ErrorUtils::FormatErrorMessage(kUrlNotActiveError, url);
676 return false;
677 }
678
679 // Set visibility and broadcast notifications that the UI should be updated.
680 page_action->SetAppearance(
681 tab_id, enable ? ExtensionAction::ACTIVE : ExtensionAction::INVISIBLE);
682 page_action->SetTitle(tab_id, title);
683 extensions::TabHelper::FromWebContents(contents)->
684 location_bar_controller()->NotifyChange();
685
686 return true;
687 }
688
689 bool EnablePageActionsFunction::RunImpl() {
690 return SetPageActionEnabled(true);
691 }
692
693 bool DisablePageActionsFunction::RunImpl() {
694 return SetPageActionEnabled(false);
695 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698