OLD | NEW |
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_action_api.h" | 5 #include "chrome/browser/extensions/api/extension_action/extension_action_api.h" |
6 | 6 |
7 #include "base/base64.h" | |
8 #include "base/lazy_instance.h" | 7 #include "base/lazy_instance.h" |
9 #include "base/strings/string_number_conversions.h" | 8 #include "base/strings/string_number_conversions.h" |
10 #include "base/strings/string_util.h" | |
11 #include "base/values.h" | 9 #include "base/values.h" |
12 #include "chrome/browser/extensions/active_script_controller.h" | 10 #include "chrome/browser/extensions/active_script_controller.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" | 11 #include "chrome/browser/extensions/extension_action_manager.h" |
16 #include "chrome/browser/extensions/extension_tab_util.h" | 12 #include "chrome/browser/extensions/extension_tab_util.h" |
17 #include "chrome/browser/extensions/extension_toolbar_model.h" | 13 #include "chrome/browser/extensions/extension_toolbar_model.h" |
18 #include "chrome/browser/extensions/tab_helper.h" | 14 #include "chrome/browser/extensions/tab_helper.h" |
19 #include "chrome/browser/profiles/profile.h" | 15 #include "chrome/browser/profiles/profile.h" |
20 #include "chrome/browser/sessions/session_tab_helper.h" | 16 #include "chrome/browser/sessions/session_tab_helper.h" |
21 #include "chrome/browser/ui/browser.h" | 17 #include "chrome/browser/ui/browser.h" |
22 #include "chrome/browser/ui/browser_finder.h" | 18 #include "chrome/browser/ui/browser_finder.h" |
23 #include "chrome/browser/ui/browser_window.h" | 19 #include "chrome/browser/ui/browser_window.h" |
24 #include "chrome/browser/ui/location_bar/location_bar.h" | 20 #include "chrome/browser/ui/location_bar/location_bar.h" |
25 #include "chrome/browser/ui/tabs/tab_strip_model.h" | 21 #include "chrome/browser/ui/tabs/tab_strip_model.h" |
26 #include "chrome/common/extensions/api/extension_action/action_info.h" | 22 #include "chrome/common/extensions/api/extension_action/action_info.h" |
27 #include "chrome/common/render_messages.h" | 23 #include "chrome/common/render_messages.h" |
28 #include "content/public/browser/navigation_entry.h" | |
29 #include "content/public/browser/notification_service.h" | 24 #include "content/public/browser/notification_service.h" |
30 #include "extensions/browser/event_router.h" | 25 #include "extensions/browser/event_router.h" |
31 #include "extensions/browser/extension_function_registry.h" | 26 #include "extensions/browser/extension_function_registry.h" |
32 #include "extensions/browser/extension_host.h" | 27 #include "extensions/browser/extension_host.h" |
33 #include "extensions/browser/extension_registry.h" | 28 #include "extensions/browser/extension_registry.h" |
34 #include "extensions/browser/extension_system.h" | |
35 #include "extensions/browser/image_util.h" | 29 #include "extensions/browser/image_util.h" |
36 #include "extensions/browser/notification_types.h" | 30 #include "extensions/browser/notification_types.h" |
37 #include "extensions/browser/state_store.h" | |
38 #include "extensions/common/error_utils.h" | 31 #include "extensions/common/error_utils.h" |
39 #include "ui/gfx/codec/png_codec.h" | |
40 #include "ui/gfx/image/image.h" | 32 #include "ui/gfx/image/image.h" |
41 #include "ui/gfx/image/image_skia.h" | 33 #include "ui/gfx/image/image_skia.h" |
42 | 34 |
43 using content::WebContents; | 35 using content::WebContents; |
44 | 36 |
45 namespace page_actions_keys = extension_page_actions_api_constants; | |
46 | |
47 namespace extensions { | 37 namespace extensions { |
48 | 38 |
49 namespace { | 39 namespace { |
50 | 40 |
51 const char kBrowserActionStorageKey[] = "browser_action"; | |
52 const char kPopupUrlStorageKey[] = "poupup_url"; | |
53 const char kTitleStorageKey[] = "title"; | |
54 const char kIconStorageKey[] = "icon"; | |
55 const char kBadgeTextStorageKey[] = "badge_text"; | |
56 const char kBadgeBackgroundColorStorageKey[] = "badge_background_color"; | |
57 const char kBadgeTextColorStorageKey[] = "badge_text_color"; | |
58 const char kAppearanceStorageKey[] = "appearance"; | |
59 | |
60 // Only add values to the end of this enum, since it's stored in the user's | |
61 // Extension State, under the kAppearanceStorageKey. It represents the | |
62 // ExtensionAction's default visibility. | |
63 enum StoredAppearance { | |
64 // The action icon is hidden. | |
65 INVISIBLE = 0, | |
66 // The action is trying to get the user's attention but isn't yet | |
67 // running on the page. Was only used for script badges. | |
68 OBSOLETE_WANTS_ATTENTION = 1, | |
69 // The action icon is visible with its normal appearance. | |
70 ACTIVE = 2, | |
71 }; | |
72 | |
73 // Whether the browser action is visible in the toolbar. | 41 // Whether the browser action is visible in the toolbar. |
74 const char kBrowserActionVisible[] = "browser_action_visible"; | 42 const char kBrowserActionVisible[] = "browser_action_visible"; |
75 | 43 |
76 // Errors. | 44 // Errors. |
77 const char kNoExtensionActionError[] = | 45 const char kNoExtensionActionError[] = |
78 "This extension has no action specified."; | 46 "This extension has no action specified."; |
79 const char kNoTabError[] = "No tab with id: *."; | 47 const char kNoTabError[] = "No tab with id: *."; |
80 const char kOpenPopupError[] = | 48 const char kOpenPopupError[] = |
81 "Failed to show popup either because there is an existing popup or another " | 49 "Failed to show popup either because there is an existing popup or another " |
82 "error occurred."; | 50 "error occurred."; |
83 const char kInternalError[] = "Internal error."; | 51 const char kInternalError[] = "Internal error."; |
84 | 52 |
85 struct IconRepresentationInfo { | |
86 // Size as a string that will be used to retrieve representation value from | |
87 // SetIcon function arguments. | |
88 const char* size_string; | |
89 // Scale factor for which the represantion should be used. | |
90 ui::ScaleFactor scale; | |
91 }; | |
92 | |
93 const IconRepresentationInfo kIconSizes[] = { | |
94 { "19", ui::SCALE_FACTOR_100P }, | |
95 { "38", ui::SCALE_FACTOR_200P } | |
96 }; | |
97 | |
98 // Conversion function for reading/writing to storage. | |
99 SkColor RawStringToSkColor(const std::string& str) { | |
100 uint64 value = 0; | |
101 base::StringToUint64(str, &value); | |
102 SkColor color = static_cast<SkColor>(value); | |
103 DCHECK(value == color); // ensure value fits into color's 32 bits | |
104 return color; | |
105 } | |
106 | |
107 // Conversion function for reading/writing to storage. | |
108 std::string SkColorToRawString(SkColor color) { | |
109 return base::Uint64ToString(color); | |
110 } | |
111 | |
112 // Conversion function for reading/writing to storage. | |
113 bool StringToSkBitmap(const std::string& str, SkBitmap* bitmap) { | |
114 // TODO(mpcomplete): Remove the base64 encode/decode step when | |
115 // http://crbug.com/140546 is fixed. | |
116 std::string raw_str; | |
117 if (!base::Base64Decode(str, &raw_str)) | |
118 return false; | |
119 | |
120 bool success = gfx::PNGCodec::Decode( | |
121 reinterpret_cast<unsigned const char*>(raw_str.data()), raw_str.size(), | |
122 bitmap); | |
123 return success; | |
124 } | |
125 | |
126 // Conversion function for reading/writing to storage. | |
127 std::string RepresentationToString(const gfx::ImageSkia& image, float scale) { | |
128 SkBitmap bitmap = image.GetRepresentation(scale).sk_bitmap(); | |
129 SkAutoLockPixels lock_image(bitmap); | |
130 std::vector<unsigned char> data; | |
131 bool success = gfx::PNGCodec::EncodeBGRASkBitmap(bitmap, false, &data); | |
132 if (!success) | |
133 return std::string(); | |
134 | |
135 base::StringPiece raw_str( | |
136 reinterpret_cast<const char*>(&data[0]), data.size()); | |
137 std::string base64_str; | |
138 base::Base64Encode(raw_str, &base64_str); | |
139 return base64_str; | |
140 } | |
141 | |
142 // Set |action|'s default values to those specified in |dict|. | |
143 void SetDefaultsFromValue(const base::DictionaryValue* dict, | |
144 ExtensionAction* action) { | |
145 const int kDefaultTabId = ExtensionAction::kDefaultTabId; | |
146 std::string str_value; | |
147 int int_value; | |
148 SkBitmap bitmap; | |
149 gfx::ImageSkia icon; | |
150 | |
151 // For each value, don't set it if it has been modified already. | |
152 if (dict->GetString(kPopupUrlStorageKey, &str_value) && | |
153 !action->HasPopupUrl(kDefaultTabId)) { | |
154 action->SetPopupUrl(kDefaultTabId, GURL(str_value)); | |
155 } | |
156 if (dict->GetString(kTitleStorageKey, &str_value) && | |
157 !action->HasTitle(kDefaultTabId)) { | |
158 action->SetTitle(kDefaultTabId, str_value); | |
159 } | |
160 if (dict->GetString(kBadgeTextStorageKey, &str_value) && | |
161 !action->HasBadgeText(kDefaultTabId)) { | |
162 action->SetBadgeText(kDefaultTabId, str_value); | |
163 } | |
164 if (dict->GetString(kBadgeBackgroundColorStorageKey, &str_value) && | |
165 !action->HasBadgeBackgroundColor(kDefaultTabId)) { | |
166 action->SetBadgeBackgroundColor(kDefaultTabId, | |
167 RawStringToSkColor(str_value)); | |
168 } | |
169 if (dict->GetString(kBadgeTextColorStorageKey, &str_value) && | |
170 !action->HasBadgeTextColor(kDefaultTabId)) { | |
171 action->SetBadgeTextColor(kDefaultTabId, RawStringToSkColor(str_value)); | |
172 } | |
173 if (dict->GetInteger(kAppearanceStorageKey, &int_value) && | |
174 !action->HasIsVisible(kDefaultTabId)) { | |
175 switch (int_value) { | |
176 case INVISIBLE: | |
177 case OBSOLETE_WANTS_ATTENTION: | |
178 action->SetIsVisible(kDefaultTabId, false); | |
179 break; | |
180 case ACTIVE: | |
181 action->SetIsVisible(kDefaultTabId, true); | |
182 break; | |
183 } | |
184 } | |
185 | |
186 const base::DictionaryValue* icon_value = NULL; | |
187 if (dict->GetDictionary(kIconStorageKey, &icon_value) && | |
188 !action->HasIcon(kDefaultTabId)) { | |
189 for (size_t i = 0; i < arraysize(kIconSizes); i++) { | |
190 if (icon_value->GetString(kIconSizes[i].size_string, &str_value) && | |
191 StringToSkBitmap(str_value, &bitmap)) { | |
192 CHECK(!bitmap.isNull()); | |
193 float scale = ui::GetScaleForScaleFactor(kIconSizes[i].scale); | |
194 icon.AddRepresentation(gfx::ImageSkiaRep(bitmap, scale)); | |
195 } | |
196 } | |
197 action->SetIcon(kDefaultTabId, gfx::Image(icon)); | |
198 } | |
199 } | |
200 | |
201 // Store |action|'s default values in a DictionaryValue for use in storing to | |
202 // disk. | |
203 scoped_ptr<base::DictionaryValue> DefaultsToValue(ExtensionAction* action) { | |
204 const int kDefaultTabId = ExtensionAction::kDefaultTabId; | |
205 scoped_ptr<base::DictionaryValue> dict(new base::DictionaryValue()); | |
206 | |
207 dict->SetString(kPopupUrlStorageKey, | |
208 action->GetPopupUrl(kDefaultTabId).spec()); | |
209 dict->SetString(kTitleStorageKey, action->GetTitle(kDefaultTabId)); | |
210 dict->SetString(kBadgeTextStorageKey, action->GetBadgeText(kDefaultTabId)); | |
211 dict->SetString( | |
212 kBadgeBackgroundColorStorageKey, | |
213 SkColorToRawString(action->GetBadgeBackgroundColor(kDefaultTabId))); | |
214 dict->SetString(kBadgeTextColorStorageKey, | |
215 SkColorToRawString(action->GetBadgeTextColor(kDefaultTabId))); | |
216 dict->SetInteger(kAppearanceStorageKey, | |
217 action->GetIsVisible(kDefaultTabId) ? ACTIVE : INVISIBLE); | |
218 | |
219 gfx::ImageSkia icon = action->GetExplicitlySetIcon(kDefaultTabId); | |
220 if (!icon.isNull()) { | |
221 base::DictionaryValue* icon_value = new base::DictionaryValue(); | |
222 for (size_t i = 0; i < arraysize(kIconSizes); i++) { | |
223 float scale = ui::GetScaleForScaleFactor(kIconSizes[i].scale); | |
224 if (icon.HasRepresentation(scale)) { | |
225 icon_value->SetString( | |
226 kIconSizes[i].size_string, | |
227 RepresentationToString(icon, scale)); | |
228 } | |
229 } | |
230 dict->Set(kIconStorageKey, icon_value); | |
231 } | |
232 return dict.Pass(); | |
233 } | |
234 | |
235 } // namespace | 53 } // namespace |
236 | 54 |
237 // | 55 // |
238 // ExtensionActionAPI::Observer | 56 // ExtensionActionAPI::Observer |
239 // | 57 // |
240 | 58 |
241 void ExtensionActionAPI::Observer::OnExtensionActionUpdated( | 59 void ExtensionActionAPI::Observer::OnExtensionActionUpdated( |
242 ExtensionAction* extension_action, | 60 ExtensionAction* extension_action, |
243 content::WebContents* web_contents, | 61 content::WebContents* web_contents, |
244 content::BrowserContext* browser_context) { | 62 content::BrowserContext* browser_context) { |
(...skipping 234 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
479 location_bar->UpdatePageActions(); | 297 location_bar->UpdatePageActions(); |
480 | 298 |
481 FOR_EACH_OBSERVER(Observer, observers_, OnPageActionsUpdated(web_contents)); | 299 FOR_EACH_OBSERVER(Observer, observers_, OnPageActionsUpdated(web_contents)); |
482 } | 300 } |
483 | 301 |
484 void ExtensionActionAPI::Shutdown() { | 302 void ExtensionActionAPI::Shutdown() { |
485 FOR_EACH_OBSERVER(Observer, observers_, OnExtensionActionAPIShuttingDown()); | 303 FOR_EACH_OBSERVER(Observer, observers_, OnExtensionActionAPIShuttingDown()); |
486 } | 304 } |
487 | 305 |
488 // | 306 // |
489 // ExtensionActionStorageManager | |
490 // | |
491 | |
492 ExtensionActionStorageManager::ExtensionActionStorageManager(Profile* profile) | |
493 : profile_(profile), | |
494 extension_action_observer_(this), | |
495 extension_registry_observer_(this) { | |
496 extension_action_observer_.Add(ExtensionActionAPI::Get(profile_)); | |
497 extension_registry_observer_.Add(ExtensionRegistry::Get(profile_)); | |
498 | |
499 StateStore* storage = ExtensionSystem::Get(profile_)->state_store(); | |
500 if (storage) | |
501 storage->RegisterKey(kBrowserActionStorageKey); | |
502 } | |
503 | |
504 ExtensionActionStorageManager::~ExtensionActionStorageManager() { | |
505 } | |
506 | |
507 void ExtensionActionStorageManager::OnExtensionLoaded( | |
508 content::BrowserContext* browser_context, | |
509 const Extension* extension) { | |
510 if (!ExtensionActionManager::Get(profile_)->GetBrowserAction(*extension)) | |
511 return; | |
512 | |
513 StateStore* storage = ExtensionSystem::Get(profile_)->state_store(); | |
514 if (storage) { | |
515 storage->GetExtensionValue( | |
516 extension->id(), | |
517 kBrowserActionStorageKey, | |
518 base::Bind(&ExtensionActionStorageManager::ReadFromStorage, | |
519 AsWeakPtr(), | |
520 extension->id())); | |
521 } | |
522 } | |
523 | |
524 void ExtensionActionStorageManager::OnExtensionActionUpdated( | |
525 ExtensionAction* extension_action, | |
526 content::WebContents* web_contents, | |
527 content::BrowserContext* browser_context) { | |
528 if (profile_ == browser_context && | |
529 extension_action->action_type() == ActionInfo::TYPE_BROWSER) | |
530 WriteToStorage(extension_action); | |
531 } | |
532 | |
533 void ExtensionActionStorageManager::OnExtensionActionAPIShuttingDown() { | |
534 extension_action_observer_.RemoveAll(); | |
535 } | |
536 | |
537 void ExtensionActionStorageManager::WriteToStorage( | |
538 ExtensionAction* extension_action) { | |
539 StateStore* storage = ExtensionSystem::Get(profile_)->state_store(); | |
540 if (!storage) | |
541 return; | |
542 | |
543 scoped_ptr<base::DictionaryValue> defaults = | |
544 DefaultsToValue(extension_action); | |
545 storage->SetExtensionValue(extension_action->extension_id(), | |
546 kBrowserActionStorageKey, | |
547 defaults.PassAs<base::Value>()); | |
548 } | |
549 | |
550 void ExtensionActionStorageManager::ReadFromStorage( | |
551 const std::string& extension_id, scoped_ptr<base::Value> value) { | |
552 const Extension* extension = ExtensionRegistry::Get(profile_)-> | |
553 enabled_extensions().GetByID(extension_id); | |
554 if (!extension) | |
555 return; | |
556 | |
557 ExtensionAction* browser_action = | |
558 ExtensionActionManager::Get(profile_)->GetBrowserAction(*extension); | |
559 if (!browser_action) { | |
560 // This can happen if the extension is updated between startup and when the | |
561 // storage read comes back, and the update removes the browser action. | |
562 // http://crbug.com/349371 | |
563 return; | |
564 } | |
565 | |
566 const base::DictionaryValue* dict = NULL; | |
567 if (!value.get() || !value->GetAsDictionary(&dict)) | |
568 return; | |
569 | |
570 SetDefaultsFromValue(dict, browser_action); | |
571 } | |
572 | |
573 // | |
574 // ExtensionActionFunction | 307 // ExtensionActionFunction |
575 // | 308 // |
576 | 309 |
577 ExtensionActionFunction::ExtensionActionFunction() | 310 ExtensionActionFunction::ExtensionActionFunction() |
578 : details_(NULL), | 311 : details_(NULL), |
579 tab_id_(ExtensionAction::kDefaultTabId), | 312 tab_id_(ExtensionAction::kDefaultTabId), |
580 contents_(NULL), | 313 contents_(NULL), |
581 extension_action_(NULL) { | 314 extension_action_(NULL) { |
582 } | 315 } |
583 | 316 |
(...skipping 98 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
682 } | 415 } |
683 | 416 |
684 bool ExtensionActionFunction::SetVisible(bool visible) { | 417 bool ExtensionActionFunction::SetVisible(bool visible) { |
685 if (extension_action_->GetIsVisible(tab_id_) == visible) | 418 if (extension_action_->GetIsVisible(tab_id_) == visible) |
686 return true; | 419 return true; |
687 extension_action_->SetIsVisible(tab_id_, visible); | 420 extension_action_->SetIsVisible(tab_id_, visible); |
688 NotifyChange(); | 421 NotifyChange(); |
689 return true; | 422 return true; |
690 } | 423 } |
691 | 424 |
692 TabHelper& ExtensionActionFunction::tab_helper() const { | |
693 CHECK(contents_); | |
694 return *TabHelper::FromWebContents(contents_); | |
695 } | |
696 | |
697 bool ExtensionActionShowFunction::RunExtensionAction() { | 425 bool ExtensionActionShowFunction::RunExtensionAction() { |
698 return SetVisible(true); | 426 return SetVisible(true); |
699 } | 427 } |
700 | 428 |
701 bool ExtensionActionHideFunction::RunExtensionAction() { | 429 bool ExtensionActionHideFunction::RunExtensionAction() { |
702 return SetVisible(false); | 430 return SetVisible(false); |
703 } | 431 } |
704 | 432 |
705 bool ExtensionActionSetIconFunction::RunExtensionAction() { | 433 bool ExtensionActionSetIconFunction::RunExtensionAction() { |
706 EXTENSION_FUNCTION_VALIDATE(details_); | 434 EXTENSION_FUNCTION_VALIDATE(details_); |
707 | 435 |
708 // setIcon can take a variant argument: either a dictionary of canvas | 436 // setIcon can take a variant argument: either a dictionary of canvas |
709 // ImageData, or an icon index. | 437 // ImageData, or an icon index. |
710 base::DictionaryValue* canvas_set = NULL; | 438 base::DictionaryValue* canvas_set = NULL; |
711 int icon_index; | 439 int icon_index; |
712 if (details_->GetDictionary("imageData", &canvas_set)) { | 440 if (details_->GetDictionary("imageData", &canvas_set)) { |
713 gfx::ImageSkia icon; | 441 gfx::ImageSkia icon; |
714 // Extract icon representations from the ImageDataSet dictionary. | 442 // Extract icon representations from the ImageDataSet dictionary. |
715 for (size_t i = 0; i < arraysize(kIconSizes); i++) { | 443 for (size_t i = 0; i < extension_misc::kNumExtensionActionIconSizes; i++) { |
716 base::BinaryValue* binary; | 444 base::BinaryValue* binary = NULL; |
717 if (canvas_set->GetBinary(kIconSizes[i].size_string, &binary)) { | 445 const extension_misc::IconRepresentationInfo& icon_info = |
| 446 extension_misc::kExtensionActionIconSizes[i]; |
| 447 if (canvas_set->GetBinary(icon_info.size_string, &binary)) { |
718 IPC::Message pickle(binary->GetBuffer(), binary->GetSize()); | 448 IPC::Message pickle(binary->GetBuffer(), binary->GetSize()); |
719 PickleIterator iter(pickle); | 449 PickleIterator iter(pickle); |
720 SkBitmap bitmap; | 450 SkBitmap bitmap; |
721 EXTENSION_FUNCTION_VALIDATE(IPC::ReadParam(&pickle, &iter, &bitmap)); | 451 EXTENSION_FUNCTION_VALIDATE(IPC::ReadParam(&pickle, &iter, &bitmap)); |
722 CHECK(!bitmap.isNull()); | 452 CHECK(!bitmap.isNull()); |
723 float scale = ui::GetScaleForScaleFactor(kIconSizes[i].scale); | 453 float scale = ui::GetScaleForScaleFactor(icon_info.scale); |
724 icon.AddRepresentation(gfx::ImageSkiaRep(bitmap, scale)); | 454 icon.AddRepresentation(gfx::ImageSkiaRep(bitmap, scale)); |
725 } | 455 } |
726 } | 456 } |
727 | 457 |
728 extension_action_->SetIcon(tab_id_, gfx::Image(icon)); | 458 extension_action_->SetIcon(tab_id_, gfx::Image(icon)); |
729 } else if (details_->GetInteger("iconIndex", &icon_index)) { | 459 } else if (details_->GetInteger("iconIndex", &icon_index)) { |
730 // Obsolete argument: ignore it. | 460 // Obsolete argument: ignore it. |
731 return true; | 461 return true; |
732 } else { | 462 } else { |
733 EXTENSION_FUNCTION_VALIDATE(false); | 463 EXTENSION_FUNCTION_VALIDATE(false); |
(...skipping 147 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
881 if (host->extension_host_type() != VIEW_TYPE_EXTENSION_POPUP || | 611 if (host->extension_host_type() != VIEW_TYPE_EXTENSION_POPUP || |
882 host->extension()->id() != extension_->id()) | 612 host->extension()->id() != extension_->id()) |
883 return; | 613 return; |
884 | 614 |
885 SendResponse(true); | 615 SendResponse(true); |
886 response_sent_ = true; | 616 response_sent_ = true; |
887 registrar_.RemoveAll(); | 617 registrar_.RemoveAll(); |
888 } | 618 } |
889 | 619 |
890 } // namespace extensions | 620 } // namespace extensions |
OLD | NEW |