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

Side by Side Diff: chrome/browser/ui/extensions/extension_message_bubble_browsertest.cc

Issue 2105393002: [Extensions UI] Handle multiple warning bubbles racing to show (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@2743
Patch Set: Created 4 years, 5 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/ui/extensions/extension_message_bubble_browsertest.h" 5 #include "chrome/browser/ui/extensions/extension_message_bubble_browsertest.h"
6 6
7 #include "base/bind_helpers.h" 7 #include "base/bind_helpers.h"
8 #include "base/run_loop.h" 8 #include "base/run_loop.h"
9 #include "chrome/app/chrome_command_ids.h" 9 #include "chrome/app/chrome_command_ids.h"
10 #include "chrome/browser/extensions/extension_action_test_util.h" 10 #include "chrome/browser/extensions/extension_action_test_util.h"
11 #include "chrome/browser/extensions/extension_service.h" 11 #include "chrome/browser/extensions/extension_service.h"
12 #include "chrome/browser/extensions/test_extension_dir.h" 12 #include "chrome/browser/extensions/test_extension_dir.h"
13 #include "chrome/browser/ui/browser.h" 13 #include "chrome/browser/ui/browser.h"
14 #include "chrome/browser/ui/browser_commands.h" 14 #include "chrome/browser/ui/browser_commands.h"
15 #include "chrome/browser/ui/browser_window.h" 15 #include "chrome/browser/ui/browser_window.h"
16 #include "chrome/browser/ui/extensions/extension_message_bubble_factory.h" 16 #include "chrome/browser/ui/extensions/extension_message_bubble_factory.h"
17 #include "chrome/browser/ui/location_bar/location_bar.h" 17 #include "chrome/browser/ui/location_bar/location_bar.h"
18 #include "chrome/browser/ui/tabs/tab_strip_model.h"
18 #include "chrome/browser/ui/toolbar/toolbar_actions_bar.h" 19 #include "chrome/browser/ui/toolbar/toolbar_actions_bar.h"
19 #include "chrome/common/pref_names.h" 20 #include "chrome/common/pref_names.h"
21 #include "chrome/common/url_constants.h"
22 #include "components/omnibox/browser/omnibox_edit_model.h"
20 #include "components/omnibox/browser/omnibox_view.h" 23 #include "components/omnibox/browser/omnibox_view.h"
24 #include "content/public/test/browser_test_utils.h"
25 #include "extensions/browser/extension_registry.h"
26 #include "extensions/common/extension.h"
21 #include "extensions/common/feature_switch.h" 27 #include "extensions/common/feature_switch.h"
22 #include "extensions/test/extension_test_message_listener.h" 28 #include "extensions/test/extension_test_message_listener.h"
23 29
24 ExtensionMessageBubbleBrowserTest::ExtensionMessageBubbleBrowserTest() { 30 ExtensionMessageBubbleBrowserTest::ExtensionMessageBubbleBrowserTest() {
25 } 31 }
26 32
27 ExtensionMessageBubbleBrowserTest::~ExtensionMessageBubbleBrowserTest() { 33 ExtensionMessageBubbleBrowserTest::~ExtensionMessageBubbleBrowserTest() {
28 } 34 }
29 35
30 void ExtensionMessageBubbleBrowserTest::SetUpCommandLine( 36 void ExtensionMessageBubbleBrowserTest::SetUpCommandLine(
(...skipping 27 matching lines...) Expand all
58 " 'manifest_version': 2,\n" 64 " 'manifest_version': 2,\n"
59 " 'description': 'controls settings',\n" 65 " 'description': 'controls settings',\n"
60 " 'chrome_settings_overrides': {\n" 66 " 'chrome_settings_overrides': {\n"
61 " %s\n" 67 " %s\n"
62 " }\n" 68 " }\n"
63 "}", settings_override_value.c_str()); 69 "}", settings_override_value.c_str());
64 custom_extension_dir_->WriteManifestWithSingleQuotes(manifest); 70 custom_extension_dir_->WriteManifestWithSingleQuotes(manifest);
65 ASSERT_TRUE(LoadExtension(custom_extension_dir_->unpacked_path())); 71 ASSERT_TRUE(LoadExtension(custom_extension_dir_->unpacked_path()));
66 } 72 }
67 73
74 void ExtensionMessageBubbleBrowserTest::CheckBubble(
75 Browser* browser,
76 AnchorPosition position,
77 bool should_be_highlighting) {
78 EXPECT_EQ(should_be_highlighting, toolbar_model()->is_highlighting());
79 EXPECT_TRUE(toolbar_model()->has_active_bubble());
80 EXPECT_TRUE(browser->window()->GetToolbarActionsBar()->is_showing_bubble());
81 CheckBubbleNative(browser, position);
82 }
83
84 void ExtensionMessageBubbleBrowserTest::CheckBubbleIsNotPresent(
85 Browser* browser,
86 bool should_profile_have_bubble,
87 bool should_be_highlighting) {
88 // We should never be highlighting without an active bubble.
89 ASSERT_TRUE(!should_be_highlighting || should_profile_have_bubble);
90 EXPECT_EQ(should_be_highlighting, toolbar_model()->is_highlighting());
91 EXPECT_EQ(should_profile_have_bubble, toolbar_model()->has_active_bubble());
92 EXPECT_FALSE(browser->window()->GetToolbarActionsBar()->is_showing_bubble());
93 CheckBubbleIsNotPresentNative(browser);
94 }
95
96 void ExtensionMessageBubbleBrowserTest::CloseBubble(Browser* browser) {
97 CloseBubbleNative(browser);
98 base::RunLoop().RunUntilIdle();
99 CheckBubbleIsNotPresent(browser, false, false);
100 }
101
68 void ExtensionMessageBubbleBrowserTest::TestBubbleAnchoredToExtensionAction() { 102 void ExtensionMessageBubbleBrowserTest::TestBubbleAnchoredToExtensionAction() {
69 scoped_refptr<const extensions::Extension> action_extension = 103 scoped_refptr<const extensions::Extension> action_extension =
70 extensions::extension_action_test_util::CreateActionExtension( 104 extensions::extension_action_test_util::CreateActionExtension(
71 "action_extension", 105 "action_extension",
72 extensions::extension_action_test_util::BROWSER_ACTION, 106 extensions::extension_action_test_util::BROWSER_ACTION,
73 extensions::Manifest::UNPACKED); 107 extensions::Manifest::UNPACKED);
74 extension_service()->AddExtension(action_extension.get()); 108 extension_service()->AddExtension(action_extension.get());
75 109
76 Browser* second_browser = new Browser(Browser::CreateParams(profile())); 110 Browser* second_browser = new Browser(Browser::CreateParams(profile()));
77 base::RunLoop().RunUntilIdle(); 111 base::RunLoop().RunUntilIdle();
78 112
79 CheckBubble(second_browser, ANCHOR_BROWSER_ACTION); 113 CheckBubble(second_browser, ANCHOR_BROWSER_ACTION, true);
80 CloseBubble(second_browser); 114 CloseBubble(second_browser);
81 } 115 }
82 116
83 void ExtensionMessageBubbleBrowserTest::TestBubbleAnchoredToAppMenu() { 117 void ExtensionMessageBubbleBrowserTest::TestBubbleAnchoredToAppMenu() {
84 scoped_refptr<const extensions::Extension> no_action_extension = 118 scoped_refptr<const extensions::Extension> no_action_extension =
85 extensions::extension_action_test_util::CreateActionExtension( 119 extensions::extension_action_test_util::CreateActionExtension(
86 "no_action_extension", 120 "no_action_extension",
87 extensions::extension_action_test_util::NO_ACTION, 121 extensions::extension_action_test_util::NO_ACTION,
88 extensions::Manifest::UNPACKED); 122 extensions::Manifest::UNPACKED);
89 extension_service()->AddExtension(no_action_extension.get()); 123 extension_service()->AddExtension(no_action_extension.get());
90 Browser* second_browser = new Browser(Browser::CreateParams(profile())); 124 Browser* second_browser = new Browser(Browser::CreateParams(profile()));
91 base::RunLoop().RunUntilIdle(); 125 base::RunLoop().RunUntilIdle();
92 CheckBubble(second_browser, ANCHOR_APP_MENU); 126 CheckBubble(second_browser, ANCHOR_APP_MENU, false);
93 CloseBubble(second_browser); 127 CloseBubble(second_browser);
94 } 128 }
95 129
96 void ExtensionMessageBubbleBrowserTest:: 130 void ExtensionMessageBubbleBrowserTest::
97 TestBubbleAnchoredToAppMenuWithOtherAction() { 131 TestBubbleAnchoredToAppMenuWithOtherAction() {
98 scoped_refptr<const extensions::Extension> no_action_extension = 132 scoped_refptr<const extensions::Extension> no_action_extension =
99 extensions::extension_action_test_util::CreateActionExtension( 133 extensions::extension_action_test_util::CreateActionExtension(
100 "no_action_extension", 134 "no_action_extension",
101 extensions::extension_action_test_util::NO_ACTION, 135 extensions::extension_action_test_util::NO_ACTION,
102 extensions::Manifest::UNPACKED); 136 extensions::Manifest::UNPACKED);
103 extension_service()->AddExtension(no_action_extension.get()); 137 extension_service()->AddExtension(no_action_extension.get());
104 138
105 scoped_refptr<const extensions::Extension> action_extension = 139 scoped_refptr<const extensions::Extension> action_extension =
106 extensions::extension_action_test_util::CreateActionExtension( 140 extensions::extension_action_test_util::CreateActionExtension(
107 "action_extension", 141 "action_extension",
108 extensions::extension_action_test_util::BROWSER_ACTION, 142 extensions::extension_action_test_util::BROWSER_ACTION,
109 extensions::Manifest::INTERNAL); 143 extensions::Manifest::INTERNAL);
110 extension_service()->AddExtension(action_extension.get()); 144 extension_service()->AddExtension(action_extension.get());
111 145
112 Browser* second_browser = new Browser(Browser::CreateParams(profile())); 146 Browser* second_browser = new Browser(Browser::CreateParams(profile()));
113 base::RunLoop().RunUntilIdle(); 147 base::RunLoop().RunUntilIdle();
114 148
115 CheckBubble(second_browser, ANCHOR_APP_MENU); 149 CheckBubble(second_browser, ANCHOR_APP_MENU, false);
116 CloseBubble(second_browser); 150 CloseBubble(second_browser);
117 } 151 }
118 152
119 void ExtensionMessageBubbleBrowserTest::TestUninstallDangerousExtension() { 153 void ExtensionMessageBubbleBrowserTest::TestUninstallDangerousExtension() {
120 // Load an extension that overrides the proxy setting. 154 // Load an extension that overrides the proxy setting.
121 ExtensionTestMessageListener listener("registered", false); 155 ExtensionTestMessageListener listener("registered", false);
122 const extensions::Extension* extension = 156 const extensions::Extension* extension =
123 LoadExtension(test_data_dir_.AppendASCII("api_test") 157 LoadExtension(test_data_dir_.AppendASCII("api_test")
124 .AppendASCII("proxy") 158 .AppendASCII("proxy")
125 .AppendASCII("register")); 159 .AppendASCII("register"));
126 // Wait for it to complete. 160 // Wait for it to complete.
127 listener.WaitUntilSatisfied(); 161 listener.WaitUntilSatisfied();
128 162
129 // Create a second browser with the extension installed - the bubble will be 163 // Create a second browser with the extension installed - the bubble will be
130 // set to show. 164 // set to show.
131 Browser* second_browser = new Browser(Browser::CreateParams(profile())); 165 Browser* second_browser = new Browser(Browser::CreateParams(profile()));
132 ASSERT_TRUE(second_browser); 166 ASSERT_TRUE(second_browser);
133 // Uninstall the extension before the bubble is shown. This should not crash, 167 // Uninstall the extension before the bubble is shown. This should not crash,
134 // and the bubble shouldn't be shown. 168 // and the bubble shouldn't be shown.
135 extension_service()->UninstallExtension( 169 extension_service()->UninstallExtension(
136 extension->id(), extensions::UNINSTALL_REASON_FOR_TESTING, 170 extension->id(), extensions::UNINSTALL_REASON_FOR_TESTING,
137 base::Bind(&base::DoNothing), nullptr); 171 base::Bind(&base::DoNothing), nullptr);
138 base::RunLoop().RunUntilIdle(); 172 base::RunLoop().RunUntilIdle();
139 CheckBubbleIsNotPresent(second_browser); 173 CheckBubbleIsNotPresent(second_browser, false, false);
140 } 174 }
141 175
142 void ExtensionMessageBubbleBrowserTest::PreBubbleShowsOnStartup() { 176 void ExtensionMessageBubbleBrowserTest::PreBubbleShowsOnStartup() {
143 LoadExtension(test_data_dir_.AppendASCII("good_unpacked")); 177 LoadExtension(test_data_dir_.AppendASCII("good_unpacked"));
144 } 178 }
145 179
146 void ExtensionMessageBubbleBrowserTest::TestBubbleShowsOnStartup() { 180 void ExtensionMessageBubbleBrowserTest::TestBubbleShowsOnStartup() {
147 base::RunLoop().RunUntilIdle(); 181 base::RunLoop().RunUntilIdle();
148 CheckBubble(browser(), ANCHOR_BROWSER_ACTION); 182 CheckBubble(browser(), ANCHOR_BROWSER_ACTION, true);
149 CloseBubble(browser()); 183 CloseBubble(browser());
150 } 184 }
151 185
152 void ExtensionMessageBubbleBrowserTest::TestDevModeBubbleIsntShownTwice() { 186 void ExtensionMessageBubbleBrowserTest::TestDevModeBubbleIsntShownTwice() {
153 scoped_refptr<const extensions::Extension> action_extension = 187 scoped_refptr<const extensions::Extension> action_extension =
154 extensions::extension_action_test_util::CreateActionExtension( 188 extensions::extension_action_test_util::CreateActionExtension(
155 "action_extension", 189 "action_extension",
156 extensions::extension_action_test_util::BROWSER_ACTION, 190 extensions::extension_action_test_util::BROWSER_ACTION,
157 extensions::Manifest::UNPACKED); 191 extensions::Manifest::UNPACKED);
158 extension_service()->AddExtension(action_extension.get()); 192 extension_service()->AddExtension(action_extension.get());
159 193
160 Browser* second_browser = new Browser(Browser::CreateParams(profile())); 194 Browser* second_browser = new Browser(Browser::CreateParams(profile()));
161 base::RunLoop().RunUntilIdle(); 195 base::RunLoop().RunUntilIdle();
162 196
163 CheckBubble(second_browser, ANCHOR_BROWSER_ACTION); 197 CheckBubble(second_browser, ANCHOR_BROWSER_ACTION, true);
164 CloseBubble(second_browser); 198 CloseBubble(second_browser);
165 base::RunLoop().RunUntilIdle(); 199 base::RunLoop().RunUntilIdle();
166 200
167 // The bubble was already shown, so it shouldn't be shown again. 201 // The bubble was already shown, so it shouldn't be shown again.
168 Browser* third_browser = new Browser(Browser::CreateParams(profile())); 202 Browser* third_browser = new Browser(Browser::CreateParams(profile()));
169 base::RunLoop().RunUntilIdle(); 203 base::RunLoop().RunUntilIdle();
170 CheckBubbleIsNotPresent(third_browser); 204 CheckBubbleIsNotPresent(third_browser, false, false);
171 } 205 }
172 206
173 void ExtensionMessageBubbleBrowserTest::TestControlledNewTabPageBubbleShown() { 207 void ExtensionMessageBubbleBrowserTest::TestControlledNewTabPageBubbleShown() {
174 ASSERT_TRUE(LoadExtension(test_data_dir_.AppendASCII("api_test") 208 ASSERT_TRUE(LoadExtension(test_data_dir_.AppendASCII("api_test")
175 .AppendASCII("override") 209 .AppendASCII("override")
176 .AppendASCII("newtab"))); 210 .AppendASCII("newtab")));
177 CheckBubbleIsNotPresent(browser()); 211 CheckBubbleIsNotPresent(browser(), false, false);
178 chrome::NewTab(browser()); 212 chrome::NewTab(browser());
179 base::RunLoop().RunUntilIdle(); 213 base::RunLoop().RunUntilIdle();
180 CheckBubble(browser(), ANCHOR_BROWSER_ACTION); 214 CheckBubble(browser(), ANCHOR_BROWSER_ACTION, false);
181 CloseBubble(browser()); 215 CloseBubble(browser());
182 } 216 }
183 217
184 void ExtensionMessageBubbleBrowserTest::TestControlledHomeBubbleShown() { 218 void ExtensionMessageBubbleBrowserTest::TestControlledHomeBubbleShown() {
185 browser()->profile()->GetPrefs()->SetBoolean(prefs::kShowHomeButton, true); 219 browser()->profile()->GetPrefs()->SetBoolean(prefs::kShowHomeButton, true);
186 220
187 const char kHomePage[] = "'homepage': 'https://www.google.com'\n"; 221 const char kHomePage[] = "'homepage': 'https://www.google.com'\n";
188 AddSettingsOverrideExtension(kHomePage); 222 AddSettingsOverrideExtension(kHomePage);
189 223
190 CheckBubbleIsNotPresent(browser()); 224 CheckBubbleIsNotPresent(browser(), false, false);
191 225
192 chrome::ExecuteCommandWithDisposition(browser(), 226 chrome::ExecuteCommandWithDisposition(browser(),
193 IDC_HOME, NEW_FOREGROUND_TAB); 227 IDC_HOME, NEW_FOREGROUND_TAB);
194 base::RunLoop().RunUntilIdle(); 228 base::RunLoop().RunUntilIdle();
195 229
196 CheckBubble(browser(), ANCHOR_BROWSER_ACTION); 230 CheckBubble(browser(), ANCHOR_BROWSER_ACTION, false);
197 CloseBubble(browser()); 231 CloseBubble(browser());
198 } 232 }
199 233
200 void ExtensionMessageBubbleBrowserTest::TestControlledSearchBubbleShown() { 234 void ExtensionMessageBubbleBrowserTest::TestControlledSearchBubbleShown() {
201 const char kSearchProvider[] = 235 const char kSearchProvider[] =
202 "'search_provider': {\n" 236 "'search_provider': {\n"
203 " 'search_url': 'https://www.google.com/search?q={searchTerms}',\n" 237 " 'search_url': 'https://www.google.com/search?q={searchTerms}',\n"
204 " 'is_default': true,\n" 238 " 'is_default': true,\n"
205 " 'favicon_url': 'https://www.google.com/favicon.icon',\n" 239 " 'favicon_url': 'https://www.google.com/favicon.icon',\n"
206 " 'keyword': 'TheGoogs',\n" 240 " 'keyword': 'TheGoogs',\n"
207 " 'name': 'Google',\n" 241 " 'name': 'Google',\n"
208 " 'encoding': 'UTF-8'\n" 242 " 'encoding': 'UTF-8'\n"
209 "}\n"; 243 "}\n";
210 AddSettingsOverrideExtension(kSearchProvider); 244 AddSettingsOverrideExtension(kSearchProvider);
211 245
212 CheckBubbleIsNotPresent(browser()); 246 CheckBubbleIsNotPresent(browser(), false, false);
213 247
214 OmniboxView* omnibox = 248 OmniboxView* omnibox =
215 browser()->window()->GetLocationBar()->GetOmniboxView(); 249 browser()->window()->GetLocationBar()->GetOmniboxView();
216 omnibox->OnBeforePossibleChange(); 250 omnibox->OnBeforePossibleChange();
217 omnibox->SetUserText(base::ASCIIToUTF16("search for this")); 251 omnibox->SetUserText(base::ASCIIToUTF16("search for this"));
218 omnibox->OnAfterPossibleChange(true); 252 omnibox->OnAfterPossibleChange(true);
219 omnibox->model()->AcceptInput(CURRENT_TAB, false); 253 omnibox->model()->AcceptInput(CURRENT_TAB, false);
220 base::RunLoop().RunUntilIdle(); 254 base::RunLoop().RunUntilIdle();
221 255
222 CheckBubble(browser(), ANCHOR_BROWSER_ACTION); 256 CheckBubble(browser(), ANCHOR_BROWSER_ACTION, false);
223 CloseBubble(browser()); 257 CloseBubble(browser());
224 } 258 }
259
260 void ExtensionMessageBubbleBrowserTest::TestBubbleWithMultipleWindows() {
261 CheckBubbleIsNotPresent(browser(), false, false);
262 LoadExtension(test_data_dir_.AppendASCII("good_unpacked"));
263 Browser* second_browser = new Browser(Browser::CreateParams(profile()));
264 Browser* third_browser = new Browser(Browser::CreateParams(profile()));
265 Browser* fourth_browser = new Browser(Browser::CreateParams(profile()));
266 base::RunLoop().RunUntilIdle();
267 CheckBubble(second_browser, ANCHOR_BROWSER_ACTION, true);
268 // Even though the bubble isn't present on these browser windows, highlighting
269 // is per-profile.
270 CheckBubbleIsNotPresent(browser(), true, true);
271 CheckBubbleIsNotPresent(third_browser, true, true);
272 CheckBubbleIsNotPresent(fourth_browser, true, true);
273 CloseBubble(second_browser);
274 }
275
276 void ExtensionMessageBubbleBrowserTest::TestClickingLearnMoreButton() {
277 CheckBubbleIsNotPresent(browser(), false, false);
278 LoadExtension(test_data_dir_.AppendASCII("good_unpacked"));
279 Browser* second_browser = new Browser(Browser::CreateParams(profile()));
280 base::RunLoop().RunUntilIdle();
281 CheckBubble(second_browser, ANCHOR_BROWSER_ACTION, true);
282 ClickLearnMoreButton(second_browser);
283 base::RunLoop().RunUntilIdle();
284 CheckBubbleIsNotPresent(second_browser, false, false);
285 // The learn more link goes to the chrome://extensions page, so it should be
286 // opened in the active tab.
287 content::WebContents* active_web_contents =
288 second_browser->tab_strip_model()->GetActiveWebContents();
289 content::WaitForLoadStop(active_web_contents);
290 EXPECT_EQ(GURL(chrome::kChromeUIExtensionsURL),
291 active_web_contents->GetLastCommittedURL());
292 }
293
294 void ExtensionMessageBubbleBrowserTest::TestClickingActionButton() {
295 CheckBubbleIsNotPresent(browser(), false, false);
296 const extensions::Extension* extension =
297 LoadExtension(test_data_dir_.AppendASCII("good_unpacked"));
298 extensions::ExtensionRegistry* registry =
299 extensions::ExtensionRegistry::Get(profile());
300 std::string id = extension->id();
301 EXPECT_TRUE(registry->enabled_extensions().GetByID(id));
302 Browser* second_browser = new Browser(Browser::CreateParams(profile()));
303 base::RunLoop().RunUntilIdle();
304 CheckBubble(second_browser, ANCHOR_BROWSER_ACTION, true);
305 ClickActionButton(second_browser);
306 base::RunLoop().RunUntilIdle();
307 CheckBubbleIsNotPresent(browser(), false, false);
308 // Clicking the action button disabled the extension.
309 EXPECT_FALSE(registry->enabled_extensions().GetByID(id));
310 }
311
312 void ExtensionMessageBubbleBrowserTest::TestClickingDismissButton() {
313 CheckBubbleIsNotPresent(browser(), false, false);
314 const extensions::Extension* extension =
315 LoadExtension(test_data_dir_.AppendASCII("good_unpacked"));
316 extensions::ExtensionRegistry* registry =
317 extensions::ExtensionRegistry::Get(profile());
318 std::string id = extension->id();
319 EXPECT_TRUE(registry->enabled_extensions().GetByID(id));
320 Browser* second_browser = new Browser(Browser::CreateParams(profile()));
321 base::RunLoop().RunUntilIdle();
322 CheckBubble(second_browser, ANCHOR_BROWSER_ACTION, true);
323 ClickDismissButton(second_browser);
324 base::RunLoop().RunUntilIdle();
325 CheckBubbleIsNotPresent(browser(), false, false);
326 // Clicking dismiss should have no affect, so the extension should still be
327 // active.
328 EXPECT_TRUE(registry->enabled_extensions().GetByID(id));
329 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698