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

Side by Side Diff: chrome/browser/extensions/api/extension_action/extension_actions_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: Created 7 years, 11 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
(Empty)
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
3 // found in the LICENSE file.
4
5 #include "chrome/browser/extensions/api/extension_action/extension_actions_api.h "
6
7 #include <string>
8
9 #include "base/base64.h"
10 #include "base/string_number_conversions.h"
11 #include "base/string_util.h"
12 #include "base/values.h"
13 #include "chrome/browser/extensions/api/extension_action/extension_page_actions_ api_constants.h"
14 #include "chrome/browser/extensions/extension_action.h"
15 #include "chrome/browser/extensions/extension_action_manager.h"
16 #include "chrome/browser/extensions/extension_service.h"
17 #include "chrome/browser/extensions/extension_system.h"
18 #include "chrome/browser/extensions/extension_tab_util.h"
19 #include "chrome/browser/extensions/location_bar_controller.h"
20 #include "chrome/browser/extensions/state_store.h"
21 #include "chrome/browser/extensions/tab_helper.h"
22 #include "chrome/browser/profiles/profile.h"
23 #include "chrome/common/chrome_notification_types.h"
24 #include "chrome/common/extensions/api/extension_action/action_info.h"
25 #include "chrome/common/render_messages.h"
26 #include "content/public/browser/navigation_entry.h"
27 #include "content/public/browser/notification_service.h"
28 #include "extensions/common/error_utils.h"
29
30 namespace {
31
32 const char kBrowserActionStorageKey[] = "browser_action";
33 const char kPopupUrlStorageKey[] = "poupup_url";
34 const char kTitleStorageKey[] = "title";
35 const char kIconStorageKey[] = "icon";
36 const char kBadgeTextStorageKey[] = "badge_text";
37 const char kBadgeBackgroundColorStorageKey[] = "badge_background_color";
38 const char kBadgeTextColorStorageKey[] = "badge_text_color";
39 const char kAppearanceStorageKey[] = "appearance";
40
41 // Errors.
42 const char kNoExtensionActionError[] =
43 "This extension has no action specified.";
44 const char kNoTabError[] = "No tab with id: *.";
45
46 struct IconRepresentationInfo {
47 // Size as a string that will be used to retrieve representation value from
48 // SetIcon function arguments.
49 const char* size_string;
50 // Scale factor for which the represantion should be used.
51 ui::ScaleFactor scale;
52 };
53
54
55 const IconRepresentationInfo kIconSizes[] = {
56 { "19", ui::SCALE_FACTOR_100P },
57 { "38", ui::SCALE_FACTOR_200P }
58 };
59
60 // Conversion function for reading/writing to storage.
61 SkColor RawStringToSkColor(const std::string& str) {
62 uint64 value = 0;
63 base::StringToUint64(str, &value);
64 SkColor color = static_cast<SkColor>(value);
65 DCHECK(value == color); // ensure value fits into color's 32 bits
66 return color;
67 }
68
69 // Conversion function for reading/writing to storage.
70 std::string SkColorToRawString(SkColor color) {
71 return base::Uint64ToString(color);
72 }
73
74 // Conversion function for reading/writing to storage.
75 bool StringToSkBitmap(const std::string& str, SkBitmap* bitmap) {
76 // TODO(mpcomplete): Remove the base64 encode/decode step when
77 // http://crbug.com/140546 is fixed.
78 std::string raw_str;
79 if (!base::Base64Decode(str, &raw_str))
80 return false;
81 IPC::Message bitmap_pickle(raw_str.data(), raw_str.size());
82 PickleIterator iter(bitmap_pickle);
83 return IPC::ReadParam(&bitmap_pickle, &iter, bitmap);
84 }
85
86 // Conversion function for reading/writing to storage.
87 std::string RepresentationToString(const gfx::ImageSkia& image,
88 ui::ScaleFactor scale) {
89 SkBitmap bitmap = image.GetRepresentation(scale).sk_bitmap();
90 IPC::Message bitmap_pickle;
91 // Clear the header values so they don't vary in serialization.
92 bitmap_pickle.SetHeaderValues(0, 0, 0);
93 IPC::WriteParam(&bitmap_pickle, bitmap);
94 std::string raw_str(static_cast<const char*>(bitmap_pickle.data()),
95 bitmap_pickle.size());
96 std::string base64_str;
97 if (!base::Base64Encode(raw_str, &base64_str))
98 return std::string();
99 return base64_str;
100 }
101
102 // Set |action|'s default values to those specified in |dict|.
103 void SetDefaultsFromValue(const base::DictionaryValue* dict,
104 ExtensionAction* action) {
105 const int kTabId = ExtensionAction::kDefaultTabId;
106 std::string str_value;
107 int int_value;
108 SkBitmap bitmap;
109 gfx::ImageSkia icon;
110
111 if (dict->GetString(kPopupUrlStorageKey, &str_value))
112 action->SetPopupUrl(kTabId, GURL(str_value));
113 if (dict->GetString(kTitleStorageKey, &str_value))
114 action->SetTitle(kTabId, str_value);
115 if (dict->GetString(kBadgeTextStorageKey, &str_value))
116 action->SetBadgeText(kTabId, str_value);
117 if (dict->GetString(kBadgeBackgroundColorStorageKey, &str_value))
118 action->SetBadgeBackgroundColor(kTabId, RawStringToSkColor(str_value));
119 if (dict->GetString(kBadgeTextColorStorageKey, &str_value))
120 action->SetBadgeTextColor(kTabId, RawStringToSkColor(str_value));
121 if (dict->GetInteger(kAppearanceStorageKey, &int_value))
122 action->SetAppearance(kTabId,
123 static_cast<ExtensionAction::Appearance>(int_value));
124
125 const base::DictionaryValue* icon_value = NULL;
126 if (dict->GetDictionary(kIconStorageKey, &icon_value)) {
127 for (size_t i = 0; i < arraysize(kIconSizes); i++) {
128 if (icon_value->GetString(kIconSizes[i].size_string, &str_value) &&
129 StringToSkBitmap(str_value, &bitmap)) {
130 CHECK(!bitmap.isNull());
131 icon.AddRepresentation(gfx::ImageSkiaRep(bitmap, kIconSizes[i].scale));
132 }
133 }
134 action->SetIcon(kTabId, gfx::Image(icon));
135 }
136 }
137
138 // Store |action|'s default values in a DictionaryValue for use in storing to
139 // disk.
140 scoped_ptr<base::DictionaryValue> DefaultsToValue(ExtensionAction* action) {
141 const int kTabId = ExtensionAction::kDefaultTabId;
142 scoped_ptr<base::DictionaryValue> dict(new DictionaryValue());
143
144 dict->SetString(kPopupUrlStorageKey, action->GetPopupUrl(kTabId).spec());
145 dict->SetString(kTitleStorageKey, action->GetTitle(kTabId));
146 dict->SetString(kBadgeTextStorageKey, action->GetBadgeText(kTabId));
147 dict->SetString(kBadgeBackgroundColorStorageKey,
148 SkColorToRawString(action->GetBadgeBackgroundColor(kTabId)));
149 dict->SetString(kBadgeTextColorStorageKey,
150 SkColorToRawString(action->GetBadgeTextColor(kTabId)));
151 dict->SetInteger(kAppearanceStorageKey,
152 action->GetIsVisible(kTabId) ?
153 ExtensionAction::ACTIVE : ExtensionAction::INVISIBLE);
154
155 gfx::ImageSkia icon = action->GetExplicitlySetIcon(kTabId);
156 if (!icon.isNull()) {
157 base::DictionaryValue* icon_value = new base::DictionaryValue();
158 for (size_t i = 0; i < arraysize(kIconSizes); i++) {
159 if (icon.HasRepresentation(kIconSizes[i].scale)) {
160 icon_value->SetString(
161 kIconSizes[i].size_string,
162 RepresentationToString(icon, kIconSizes[i].scale));
163 }
164 }
165 dict->Set(kIconStorageKey, icon_value);
166 }
167 return dict.Pass();
168 }
169
170 } // namespace
171
172 namespace extensions {
173
174 //
175 // ExtensionActionStorageManager
176 //
177
178 ExtensionActionStorageManager::ExtensionActionStorageManager(Profile* profile)
179 : profile_(profile) {
180 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_LOADED,
181 content::Source<Profile>(profile_));
182 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_BROWSER_ACTION_UPDATED,
183 content::NotificationService::AllBrowserContextsAndSources());
184
185 StateStore* storage = ExtensionSystem::Get(profile_)->state_store();
186 if (storage)
187 storage->RegisterKey(kBrowserActionStorageKey);
188 }
189
190 ExtensionActionStorageManager::~ExtensionActionStorageManager() {
191 }
192
193 void ExtensionActionStorageManager::Observe(
194 int type,
195 const content::NotificationSource& source,
196 const content::NotificationDetails& details) {
197 switch (type) {
198 case chrome::NOTIFICATION_EXTENSION_LOADED: {
199 const Extension* extension =
200 content::Details<const Extension>(details).ptr();
201 if (!extensions::ExtensionActionManager::Get(profile_)->
202 GetBrowserAction(*extension)) {
203 break;
204 }
205
206 StateStore* storage = ExtensionSystem::Get(profile_)->state_store();
207 if (storage) {
208 storage->GetExtensionValue(extension->id(), kBrowserActionStorageKey,
209 base::Bind(&ExtensionActionStorageManager::ReadFromStorage,
210 AsWeakPtr(), extension->id()));
211 }
212 break;
213 }
214 case chrome::NOTIFICATION_EXTENSION_BROWSER_ACTION_UPDATED: {
215 ExtensionAction* extension_action =
216 content::Source<ExtensionAction>(source).ptr();
217 Profile* profile = content::Details<Profile>(details).ptr();
218 if (profile != profile_)
219 break;
220
221 extension_action->set_has_changed(true);
222 WriteToStorage(extension_action);
223 break;
224 }
225 default:
226 NOTREACHED();
227 break;
228 }
229 }
230
231 void ExtensionActionStorageManager::WriteToStorage(
232 ExtensionAction* extension_action) {
233 StateStore* storage = ExtensionSystem::Get(profile_)->state_store();
234 if (!storage)
235 return;
236
237 scoped_ptr<base::DictionaryValue> defaults =
238 DefaultsToValue(extension_action);
239 storage->SetExtensionValue(extension_action->extension_id(),
240 kBrowserActionStorageKey,
241 defaults.PassAs<base::Value>());
242 }
243
244 void ExtensionActionStorageManager::ReadFromStorage(
245 const std::string& extension_id, scoped_ptr<base::Value> value) {
246 const Extension* extension =
247 ExtensionSystem::Get(profile_)->extension_service()->
248 extensions()->GetByID(extension_id);
249 if (!extension)
250 return;
251
252 ExtensionAction* browser_action =
253 ExtensionActionManager::Get(profile_)->GetBrowserAction(*extension);
254 CHECK(browser_action);
255
256 // Don't load values from storage if the extension has updated a value
257 // already. The extension may have only updated some of the values, but
258 // this is a good first approximation. If the extension is doing stuff
259 // to the browser action, we can assume it is ready to take over.
260 if (browser_action->has_changed())
261 return;
262
263 const base::DictionaryValue* dict = NULL;
264 if (!value.get() || !value->GetAsDictionary(&dict))
265 return;
266
267 SetDefaultsFromValue(dict, browser_action);
268 }
269
270 } // namespace extensions
271
272
273 //
274 // ExtensionActionFunction
275 //
276
277 ExtensionActionFunction::ExtensionActionFunction()
278 : details_(NULL),
279 tab_id_(ExtensionAction::kDefaultTabId),
280 contents_(NULL),
281 extension_action_(NULL) {
282 }
283
284 ExtensionActionFunction::~ExtensionActionFunction() {
285 }
286
287 bool ExtensionActionFunction::RunImpl() {
288 extensions::ExtensionActionManager* manager =
289 extensions::ExtensionActionManager::Get(profile_);
290 const extensions::Extension* extension = GetExtension();
291 if (StartsWithASCII(name(), "scriptBadge.", false)) {
292 extension_action_ = manager->GetScriptBadge(*extension);
293 } else if (StartsWithASCII(name(), "systemIndicator.", false)) {
294 extension_action_ = manager->GetSystemIndicator(*extension);
295 } else {
296 extension_action_ = manager->GetBrowserAction(*extension);
297 if (!extension_action_) {
298 extension_action_ = manager->GetPageAction(*extension);
299 }
300 }
301 if (!extension_action_) {
302 // TODO(kalman): ideally the browserAction/pageAction APIs wouldn't event
303 // exist for extensions that don't have one declared. This should come as
304 // part of the Feature system.
305 error_ = kNoExtensionActionError;
306 return false;
307 }
308
309 // Populates the tab_id_ and details_ members.
310 EXTENSION_FUNCTION_VALIDATE(ExtractDataFromArguments());
311
312 // Find the WebContents that contains this tab id if one is required.
313 if (tab_id_ != ExtensionAction::kDefaultTabId) {
314 ExtensionTabUtil::GetTabById(
315 tab_id_, profile(), include_incognito(), NULL, NULL, &contents_, NULL);
316 if (!contents_) {
317 error_ = extensions::ErrorUtils::FormatErrorMessage(
318 kNoTabError, base::IntToString(tab_id_));
319 return false;
320 }
321 } else {
322 // Only browser actions and system indicators have a default tabId.
323 extensions::ActionInfo::Type action_type = extension_action_->action_type();
324 EXTENSION_FUNCTION_VALIDATE(
325 action_type == extensions::ActionInfo::TYPE_BROWSER ||
326 action_type == extensions::ActionInfo::TYPE_SYSTEM_INDICATOR);
327 }
328 return RunExtensionAction();
329 }
330
331 bool ExtensionActionFunction::ExtractDataFromArguments() {
332 // There may or may not be details (depends on the function).
333 // 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
335 // entirely.
336 base::Value* first_arg = NULL;
337 if (!args_->Get(0, &first_arg))
338 return true;
339
340 switch (first_arg->GetType()) {
341 case Value::TYPE_INTEGER:
342 CHECK(first_arg->GetAsInteger(&tab_id_));
343 break;
344
345 case Value::TYPE_DICTIONARY: {
346 // Found the details argument.
347 details_ = static_cast<base::DictionaryValue*>(first_arg);
348 // Still need to check for the tabId within details.
349 base::Value* tab_id_value = NULL;
350 if (details_->Get("tabId", &tab_id_value)) {
351 switch (tab_id_value->GetType()) {
352 case Value::TYPE_NULL:
353 // OK; tabId is optional, leave it default.
354 return true;
355 case Value::TYPE_INTEGER:
356 CHECK(tab_id_value->GetAsInteger(&tab_id_));
357 return true;
358 default:
359 // Boom.
360 return false;
361 }
362 }
363 // Not found; tabId is optional, leave it default.
364 break;
365 }
366
367 case Value::TYPE_NULL:
368 // The tabId might be an optional argument.
369 break;
370
371 default:
372 return false;
373 }
374
375 return true;
376 }
377
378 void ExtensionActionFunction::NotifyChange() {
379 switch (extension_action_->action_type()) {
380 case extensions::ActionInfo::TYPE_BROWSER:
381 case extensions::ActionInfo::TYPE_PAGE:
382 if (extensions::ExtensionActionManager::Get(profile_)->
383 GetBrowserAction(*extension_)) {
384 NotifyBrowserActionChange();
385 } else if (extensions::ExtensionActionManager::Get(profile_)->
386 GetPageAction(*extension_)) {
387 NotifyLocationBarChange();
388 }
389 return;
390 case extensions::ActionInfo::TYPE_SCRIPT_BADGE:
391 NotifyLocationBarChange();
392 return;
393 case extensions::ActionInfo::TYPE_SYSTEM_INDICATOR:
394 NotifySystemIndicatorChange();
395 return;
396 }
397 NOTREACHED();
398 }
399
400 void ExtensionActionFunction::NotifyBrowserActionChange() {
401 content::NotificationService::current()->Notify(
402 chrome::NOTIFICATION_EXTENSION_BROWSER_ACTION_UPDATED,
403 content::Source<ExtensionAction>(extension_action_),
404 content::Details<Profile>(profile()));
405 }
406
407 void ExtensionActionFunction::NotifyLocationBarChange() {
408 extensions::TabHelper::FromWebContents(contents_)->
409 location_bar_controller()->NotifyChange();
410 }
411
412 void ExtensionActionFunction::NotifySystemIndicatorChange() {
413 content::NotificationService::current()->Notify(
414 chrome::NOTIFICATION_EXTENSION_SYSTEM_INDICATOR_UPDATED,
415 content::Source<Profile>(profile()),
416 content::Details<ExtensionAction>(extension_action_));
417 }
418
419 // static
420 bool ExtensionActionFunction::ParseCSSColorString(
421 const std::string& color_string,
422 SkColor* result) {
423 std::string formatted_color = "#";
424 // Check the string for incorrect formatting.
425 if (color_string[0] != '#')
426 return false;
427
428 // Convert the string from #FFF format to #FFFFFF format.
429 if (color_string.length() == 4) {
430 for (size_t i = 1; i < color_string.length(); i++) {
431 formatted_color += color_string[i];
432 formatted_color += color_string[i];
433 }
434 } else {
435 formatted_color = color_string;
436 }
437
438 if (formatted_color.length() != 7)
439 return false;
440
441 // Convert the string to an integer and make sure it is in the correct value
442 // range.
443 int color_ints[3] = {0};
444 for (int i = 0; i < 3; i++) {
445 if (!base::HexStringToInt(formatted_color.substr(1 + (2 * i), 2),
446 color_ints + i))
447 return false;
448 if (color_ints[i] > 255 || color_ints[i] < 0)
449 return false;
450 }
451
452 *result = SkColorSetARGB(255, color_ints[0], color_ints[1], color_ints[2]);
453 return true;
454 }
455
456 bool ExtensionActionFunction::SetVisible(bool visible) {
457 if (extension_action_->GetIsVisible(tab_id_) == visible)
458 return true;
459 extension_action_->SetAppearance(
460 tab_id_, visible ? ExtensionAction::ACTIVE : ExtensionAction::INVISIBLE);
461 NotifyChange();
462 return true;
463 }
464
465 extensions::TabHelper& ExtensionActionFunction::tab_helper() const {
466 CHECK(contents_);
467 return *extensions::TabHelper::FromWebContents(contents_);
468 }
469
470 bool ExtensionActionShowFunction::RunExtensionAction() {
471 return SetVisible(true);
472 }
473
474 bool ExtensionActionHideFunction::RunExtensionAction() {
475 return SetVisible(false);
476 }
477
478 bool ExtensionActionSetIconFunction::RunExtensionAction() {
479 // setIcon can take a variant argument: either a dictionary of canvas
480 // ImageData, or an icon index.
481 base::DictionaryValue* canvas_set = NULL;
482 int icon_index;
483 if (details_->GetDictionary("imageData", &canvas_set)) {
484 gfx::ImageSkia icon;
485 // Extract icon representations from the ImageDataSet dictionary.
486 for (size_t i = 0; i < arraysize(kIconSizes); i++) {
487 base::BinaryValue* binary;
488 if (canvas_set->GetBinary(kIconSizes[i].size_string, &binary)) {
489 IPC::Message pickle(binary->GetBuffer(), binary->GetSize());
490 PickleIterator iter(pickle);
491 SkBitmap bitmap;
492 EXTENSION_FUNCTION_VALIDATE(IPC::ReadParam(&pickle, &iter, &bitmap));
493 CHECK(!bitmap.isNull());
494 icon.AddRepresentation(gfx::ImageSkiaRep(bitmap, kIconSizes[i].scale));
495 }
496 }
497
498 extension_action_->SetIcon(tab_id_, gfx::Image(icon));
499 } else if (details_->GetInteger("iconIndex", &icon_index)) {
500 // Obsolete argument: ignore it.
501 return true;
502 } else {
503 EXTENSION_FUNCTION_VALIDATE(false);
504 }
505 NotifyChange();
506 return true;
507 }
508
509 bool ExtensionActionSetTitleFunction::RunExtensionAction() {
510 std::string title;
511 EXTENSION_FUNCTION_VALIDATE(details_->GetString("title", &title));
512 extension_action_->SetTitle(tab_id_, title);
513 NotifyChange();
514 return true;
515 }
516
517 bool ExtensionActionSetPopupFunction::RunExtensionAction() {
518 std::string popup_string;
519 EXTENSION_FUNCTION_VALIDATE(details_->GetString("popup", &popup_string));
520
521 GURL popup_url;
522 if (!popup_string.empty())
523 popup_url = GetExtension()->GetResourceURL(popup_string);
524
525 extension_action_->SetPopupUrl(tab_id_, popup_url);
526 NotifyChange();
527 return true;
528 }
529
530 bool ExtensionActionSetBadgeTextFunction::RunExtensionAction() {
531 std::string badge_text;
532 EXTENSION_FUNCTION_VALIDATE(details_->GetString("text", &badge_text));
533 extension_action_->SetBadgeText(tab_id_, badge_text);
534 NotifyChange();
535 return true;
536 }
537
538 bool ExtensionActionSetBadgeBackgroundColorFunction::RunExtensionAction() {
539 Value* color_value = NULL;
540 details_->Get("color", &color_value);
541 SkColor color = 0;
542 if (color_value->IsType(Value::TYPE_LIST)) {
543 ListValue* list = NULL;
544 EXTENSION_FUNCTION_VALIDATE(details_->GetList("color", &list));
545 EXTENSION_FUNCTION_VALIDATE(list->GetSize() == 4);
546
547 int color_array[4] = {0};
548 for (size_t i = 0; i < arraysize(color_array); ++i) {
549 EXTENSION_FUNCTION_VALIDATE(list->GetInteger(i, &color_array[i]));
550 }
551
552 color = SkColorSetARGB(color_array[3], color_array[0],
553 color_array[1], color_array[2]);
554 } else if (color_value->IsType(Value::TYPE_STRING)) {
555 std::string color_string;
556 EXTENSION_FUNCTION_VALIDATE(details_->GetString("color", &color_string));
557 if (!ParseCSSColorString(color_string, &color))
558 return false;
559 }
560
561 extension_action_->SetBadgeBackgroundColor(tab_id_, color);
562 NotifyChange();
563 return true;
564 }
565
566 bool ExtensionActionGetTitleFunction::RunExtensionAction() {
567 SetResult(Value::CreateStringValue(extension_action_->GetTitle(tab_id_)));
568 return true;
569 }
570
571 bool ExtensionActionGetPopupFunction::RunExtensionAction() {
572 SetResult(
573 Value::CreateStringValue(extension_action_->GetPopupUrl(tab_id_).spec()));
574 return true;
575 }
576
577 bool ExtensionActionGetBadgeTextFunction::RunExtensionAction() {
578 SetResult(Value::CreateStringValue(extension_action_->GetBadgeText(tab_id_)));
579 return true;
580 }
581
582 bool ExtensionActionGetBadgeBackgroundColorFunction::RunExtensionAction() {
583 ListValue* list = new ListValue();
584 SkColor color = extension_action_->GetBadgeBackgroundColor(tab_id_);
585 list->Append(Value::CreateIntegerValue(SkColorGetR(color)));
586 list->Append(Value::CreateIntegerValue(SkColorGetG(color)));
587 list->Append(Value::CreateIntegerValue(SkColorGetB(color)));
588 list->Append(Value::CreateIntegerValue(SkColorGetA(color)));
589 SetResult(list);
590 return true;
591 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698