OLD | NEW |
1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2013 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/views/extensions/extension_message_bubble_view.h" | 5 #include "chrome/browser/ui/views/extensions/extension_message_bubble_view.h" |
6 | 6 |
7 #include "base/strings/string_number_conversions.h" | 7 #include "base/strings/string_number_conversions.h" |
8 #include "base/strings/string_util.h" | 8 #include "base/strings/string_util.h" |
9 #include "base/strings/utf_string_conversions.h" | 9 #include "base/strings/utf_string_conversions.h" |
10 #include "chrome/browser/extensions/dev_mode_bubble_controller.h" | 10 #include "chrome/browser/extensions/dev_mode_bubble_controller.h" |
11 #include "chrome/browser/extensions/extension_action_manager.h" | 11 #include "chrome/browser/extensions/extension_action_manager.h" |
12 #include "chrome/browser/extensions/extension_message_bubble_controller.h" | 12 #include "chrome/browser/extensions/extension_message_bubble_controller.h" |
13 #include "chrome/browser/extensions/extension_service.h" | 13 #include "chrome/browser/extensions/extension_service.h" |
| 14 #include "chrome/browser/extensions/proxy_overridden_bubble_controller.h" |
14 #include "chrome/browser/extensions/settings_api_bubble_controller.h" | 15 #include "chrome/browser/extensions/settings_api_bubble_controller.h" |
15 #include "chrome/browser/extensions/settings_api_helpers.h" | 16 #include "chrome/browser/extensions/settings_api_helpers.h" |
16 #include "chrome/browser/extensions/suspicious_extension_bubble_controller.h" | 17 #include "chrome/browser/extensions/suspicious_extension_bubble_controller.h" |
17 #include "chrome/browser/profiles/profile.h" | 18 #include "chrome/browser/profiles/profile.h" |
| 19 #include "chrome/browser/ui/view_ids.h" |
18 #include "chrome/browser/ui/views/frame/browser_view.h" | 20 #include "chrome/browser/ui/views/frame/browser_view.h" |
19 #include "chrome/browser/ui/views/toolbar/browser_actions_container.h" | 21 #include "chrome/browser/ui/views/toolbar/browser_actions_container.h" |
20 #include "chrome/browser/ui/views/toolbar/browser_actions_container_observer.h" | 22 #include "chrome/browser/ui/views/toolbar/browser_actions_container_observer.h" |
21 #include "chrome/browser/ui/views/toolbar/toolbar_view.h" | 23 #include "chrome/browser/ui/views/toolbar/toolbar_view.h" |
22 #include "extensions/browser/extension_prefs.h" | 24 #include "extensions/browser/extension_prefs.h" |
23 #include "extensions/browser/extension_system.h" | 25 #include "extensions/browser/extension_system.h" |
24 #include "grit/locale_settings.h" | 26 #include "grit/locale_settings.h" |
25 #include "ui/accessibility/ax_view_state.h" | 27 #include "ui/accessibility/ax_view_state.h" |
26 #include "ui/base/resource/resource_bundle.h" | 28 #include "ui/base/resource/resource_bundle.h" |
27 #include "ui/views/controls/button/label_button.h" | 29 #include "ui/views/controls/button/label_button.h" |
(...skipping 27 matching lines...) Expand all Loading... |
55 | 57 |
56 namespace extensions { | 58 namespace extensions { |
57 | 59 |
58 ExtensionMessageBubbleView::ExtensionMessageBubbleView( | 60 ExtensionMessageBubbleView::ExtensionMessageBubbleView( |
59 views::View* anchor_view, | 61 views::View* anchor_view, |
60 views::BubbleBorder::Arrow arrow_location, | 62 views::BubbleBorder::Arrow arrow_location, |
61 scoped_ptr<extensions::ExtensionMessageBubbleController> controller) | 63 scoped_ptr<extensions::ExtensionMessageBubbleController> controller) |
62 : BubbleDelegateView(anchor_view, arrow_location), | 64 : BubbleDelegateView(anchor_view, arrow_location), |
63 weak_factory_(this), | 65 weak_factory_(this), |
64 controller_(controller.Pass()), | 66 controller_(controller.Pass()), |
| 67 anchor_view_(anchor_view), |
65 headline_(NULL), | 68 headline_(NULL), |
66 learn_more_(NULL), | 69 learn_more_(NULL), |
67 dismiss_button_(NULL), | 70 dismiss_button_(NULL), |
68 link_clicked_(false), | 71 link_clicked_(false), |
69 action_taken_(false) { | 72 action_taken_(false) { |
70 DCHECK(anchor_view->GetWidget()); | 73 DCHECK(anchor_view->GetWidget()); |
71 set_close_on_deactivate(controller_->CloseOnDeactivate()); | 74 set_close_on_deactivate(controller_->CloseOnDeactivate()); |
72 set_close_on_esc(true); | 75 set_close_on_esc(true); |
73 | 76 |
74 // Compensate for built-in vertical padding in the anchor view's image. | 77 // Compensate for built-in vertical padding in the anchor view's image. |
(...skipping 74 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
149 const int text_column_set_id = 1; | 152 const int text_column_set_id = 1; |
150 views::ColumnSet* upper_columns = layout->AddColumnSet(text_column_set_id); | 153 views::ColumnSet* upper_columns = layout->AddColumnSet(text_column_set_id); |
151 upper_columns->AddColumn( | 154 upper_columns->AddColumn( |
152 views::GridLayout::LEADING, views::GridLayout::LEADING, | 155 views::GridLayout::LEADING, views::GridLayout::LEADING, |
153 0, views::GridLayout::USE_PREF, 0, 0); | 156 0, views::GridLayout::USE_PREF, 0, 0); |
154 layout->StartRow(0, text_column_set_id); | 157 layout->StartRow(0, text_column_set_id); |
155 | 158 |
156 views::Label* message = new views::Label(); | 159 views::Label* message = new views::Label(); |
157 message->SetMultiLine(true); | 160 message->SetMultiLine(true); |
158 message->SetHorizontalAlignment(gfx::ALIGN_LEFT); | 161 message->SetHorizontalAlignment(gfx::ALIGN_LEFT); |
159 message->SetText(delegate->GetMessageBody()); | 162 message->SetText(delegate->GetMessageBody( |
| 163 anchor_view_->id() == VIEW_ID_BROWSER_ACTION)); |
160 message->SizeToFit(views::Widget::GetLocalizedContentsWidth( | 164 message->SizeToFit(views::Widget::GetLocalizedContentsWidth( |
161 IDS_EXTENSION_WIPEOUT_BUBBLE_WIDTH_CHARS)); | 165 IDS_EXTENSION_WIPEOUT_BUBBLE_WIDTH_CHARS)); |
162 layout->AddView(message); | 166 layout->AddView(message); |
163 | 167 |
164 if (delegate->ShouldShowExtensionList()) { | 168 if (delegate->ShouldShowExtensionList()) { |
165 const int extension_list_column_set_id = 2; | 169 const int extension_list_column_set_id = 2; |
166 views::ColumnSet* middle_columns = | 170 views::ColumnSet* middle_columns = |
167 layout->AddColumnSet(extension_list_column_set_id); | 171 layout->AddColumnSet(extension_list_column_set_id); |
168 middle_columns->AddPaddingColumn(0, kExtensionListPadding); | 172 middle_columns->AddPaddingColumn(0, kExtensionListPadding); |
169 middle_columns->AddColumn( | 173 middle_columns->AddColumn( |
(...skipping 95 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
265 //////////////////////////////////////////////////////////////////////////////// | 269 //////////////////////////////////////////////////////////////////////////////// |
266 // ExtensionMessageBubbleFactory | 270 // ExtensionMessageBubbleFactory |
267 | 271 |
268 ExtensionMessageBubbleFactory::ExtensionMessageBubbleFactory( | 272 ExtensionMessageBubbleFactory::ExtensionMessageBubbleFactory( |
269 Profile* profile, | 273 Profile* profile, |
270 ToolbarView* toolbar_view) | 274 ToolbarView* toolbar_view) |
271 : profile_(profile), | 275 : profile_(profile), |
272 toolbar_view_(toolbar_view), | 276 toolbar_view_(toolbar_view), |
273 shown_suspicious_extensions_bubble_(false), | 277 shown_suspicious_extensions_bubble_(false), |
274 shown_startup_override_extensions_bubble_(false), | 278 shown_startup_override_extensions_bubble_(false), |
| 279 shown_proxy_override_extensions_bubble_(false), |
275 shown_dev_mode_extensions_bubble_(false), | 280 shown_dev_mode_extensions_bubble_(false), |
276 is_observing_(false), | 281 is_observing_(false), |
277 stage_(STAGE_START), | 282 stage_(STAGE_START), |
278 container_(NULL), | 283 container_(NULL), |
279 anchor_view_(NULL) {} | 284 anchor_view_(NULL) {} |
280 | 285 |
281 ExtensionMessageBubbleFactory::~ExtensionMessageBubbleFactory() { | 286 ExtensionMessageBubbleFactory::~ExtensionMessageBubbleFactory() { |
282 MaybeStopObserving(); | 287 MaybeStopObserving(); |
283 } | 288 } |
284 | 289 |
(...skipping 12 matching lines...) Expand all Loading... |
297 // way, we're not too spammy with the bubbles. | 302 // way, we're not too spammy with the bubbles. |
298 if (!shown_suspicious_extensions_bubble_ && | 303 if (!shown_suspicious_extensions_bubble_ && |
299 MaybeShowSuspiciousExtensionsBubble(anchor_view)) | 304 MaybeShowSuspiciousExtensionsBubble(anchor_view)) |
300 return; | 305 return; |
301 | 306 |
302 if (!shown_startup_override_extensions_bubble_ && | 307 if (!shown_startup_override_extensions_bubble_ && |
303 is_initial_check && | 308 is_initial_check && |
304 MaybeShowStartupOverrideExtensionsBubble(anchor_view)) | 309 MaybeShowStartupOverrideExtensionsBubble(anchor_view)) |
305 return; | 310 return; |
306 | 311 |
| 312 if (!shown_proxy_override_extensions_bubble_ && |
| 313 MaybeShowProxyOverrideExtensionsBubble(anchor_view)) |
| 314 return; |
| 315 |
307 if (!shown_dev_mode_extensions_bubble_) | 316 if (!shown_dev_mode_extensions_bubble_) |
308 MaybeShowDevModeExtensionsBubble(anchor_view); | 317 MaybeShowDevModeExtensionsBubble(anchor_view); |
309 #endif // OS_WIN | 318 #endif // OS_WIN |
310 } | 319 } |
311 | 320 |
312 bool ExtensionMessageBubbleFactory::MaybeShowSuspiciousExtensionsBubble( | 321 bool ExtensionMessageBubbleFactory::MaybeShowSuspiciousExtensionsBubble( |
313 views::View* anchor_view) { | 322 views::View* anchor_view) { |
314 DCHECK(!shown_suspicious_extensions_bubble_); | 323 DCHECK(!shown_suspicious_extensions_bubble_); |
315 | 324 |
316 scoped_ptr<SuspiciousExtensionBubbleController> suspicious_extensions( | 325 scoped_ptr<SuspiciousExtensionBubbleController> suspicious_extensions( |
(...skipping 12 matching lines...) Expand all Loading... |
329 views::BubbleDelegateView::CreateBubble(bubble_delegate); | 338 views::BubbleDelegateView::CreateBubble(bubble_delegate); |
330 weak_controller->Show(bubble_delegate); | 339 weak_controller->Show(bubble_delegate); |
331 | 340 |
332 return true; | 341 return true; |
333 } | 342 } |
334 | 343 |
335 bool ExtensionMessageBubbleFactory::MaybeShowStartupOverrideExtensionsBubble( | 344 bool ExtensionMessageBubbleFactory::MaybeShowStartupOverrideExtensionsBubble( |
336 views::View* anchor_view) { | 345 views::View* anchor_view) { |
337 #if !defined(OS_WIN) | 346 #if !defined(OS_WIN) |
338 return false; | 347 return false; |
339 #endif | 348 #else |
340 | |
341 DCHECK(!shown_startup_override_extensions_bubble_); | 349 DCHECK(!shown_startup_override_extensions_bubble_); |
342 | 350 |
343 const Extension* extension = OverridesStartupPages(profile_, NULL); | 351 const Extension* extension = |
| 352 GetExtensionOverridingStartupPages(profile_, NULL); |
344 if (!extension) | 353 if (!extension) |
345 return false; | 354 return false; |
346 | 355 |
347 scoped_ptr<SettingsApiBubbleController> settings_api_bubble( | 356 scoped_ptr<SettingsApiBubbleController> settings_api_bubble( |
348 new SettingsApiBubbleController(profile_, | 357 new SettingsApiBubbleController(profile_, |
349 BUBBLE_TYPE_STARTUP_PAGES)); | 358 BUBBLE_TYPE_STARTUP_PAGES)); |
350 if (!settings_api_bubble->ShouldShow(extension->id())) | 359 if (!settings_api_bubble->ShouldShow(extension->id())) |
351 return false; | 360 return false; |
352 | 361 |
353 shown_startup_override_extensions_bubble_ = true; | 362 shown_startup_override_extensions_bubble_ = true; |
354 SettingsApiBubbleController* weak_controller = settings_api_bubble.get(); | 363 PrepareToHighlightExtensions( |
355 ExtensionMessageBubbleView* bubble_delegate = new ExtensionMessageBubbleView( | 364 settings_api_bubble.PassAs<ExtensionMessageBubbleController>(), |
356 anchor_view, | 365 anchor_view); |
357 views::BubbleBorder::TOP_RIGHT, | 366 return true; |
358 settings_api_bubble.PassAs<ExtensionMessageBubbleController>()); | 367 #endif |
359 views::BubbleDelegateView::CreateBubble(bubble_delegate); | 368 } |
360 weak_controller->Show(bubble_delegate); | |
361 | 369 |
| 370 bool ExtensionMessageBubbleFactory::MaybeShowProxyOverrideExtensionsBubble( |
| 371 views::View* anchor_view) { |
| 372 #if !defined(OS_WIN) |
| 373 return false; |
| 374 #else |
| 375 DCHECK(!shown_proxy_override_extensions_bubble_); |
| 376 |
| 377 const Extension* extension = GetExtensionOverridingProxy(profile_); |
| 378 if (!extension) |
| 379 return false; |
| 380 |
| 381 scoped_ptr<ProxyOverriddenBubbleController> proxy_bubble( |
| 382 new ProxyOverriddenBubbleController(profile_)); |
| 383 if (!proxy_bubble->ShouldShow(extension->id())) |
| 384 return false; |
| 385 |
| 386 shown_proxy_override_extensions_bubble_ = true; |
| 387 PrepareToHighlightExtensions( |
| 388 proxy_bubble.PassAs<ExtensionMessageBubbleController>(), anchor_view); |
362 return true; | 389 return true; |
| 390 #endif |
363 } | 391 } |
364 | 392 |
365 bool ExtensionMessageBubbleFactory::MaybeShowDevModeExtensionsBubble( | 393 bool ExtensionMessageBubbleFactory::MaybeShowDevModeExtensionsBubble( |
366 views::View* anchor_view) { | 394 views::View* anchor_view) { |
367 DCHECK(!shown_dev_mode_extensions_bubble_); | 395 DCHECK(!shown_dev_mode_extensions_bubble_); |
368 | 396 |
369 // Check the Developer Mode extensions. | 397 // Check the Developer Mode extensions. |
370 scoped_ptr<DevModeBubbleController> dev_mode_extensions( | 398 scoped_ptr<DevModeBubbleController> dev_mode_extensions( |
371 new DevModeBubbleController(profile_)); | 399 new DevModeBubbleController(profile_)); |
372 | 400 |
373 // Return early if we have none to show. | 401 // Return early if we have none to show. |
374 if (!dev_mode_extensions->ShouldShow()) | 402 if (!dev_mode_extensions->ShouldShow()) |
375 return false; | 403 return false; |
376 | 404 |
377 shown_dev_mode_extensions_bubble_ = true; | 405 shown_dev_mode_extensions_bubble_ = true; |
378 | 406 PrepareToHighlightExtensions( |
379 // We should be in the start stage (i.e., should not have a pending attempt to | 407 dev_mode_extensions.PassAs<ExtensionMessageBubbleController>(), |
380 // show a bubble). | 408 anchor_view); |
381 DCHECK_EQ(stage_, STAGE_START); | |
382 | |
383 // Prepare to display and highlight the developer mode extensions before | |
384 // showing the bubble. Since this is an asynchronous process, set member | |
385 // variables for later use. | |
386 controller_ = dev_mode_extensions.Pass(); | |
387 anchor_view_ = anchor_view; | |
388 container_ = toolbar_view_->browser_actions(); | |
389 | |
390 if (container_->animating()) | |
391 MaybeObserve(); | |
392 else | |
393 HighlightDevModeExtensions(); | |
394 | |
395 return true; | 409 return true; |
396 } | 410 } |
397 | 411 |
398 void ExtensionMessageBubbleFactory::MaybeObserve() { | 412 void ExtensionMessageBubbleFactory::MaybeObserve() { |
399 if (!is_observing_) { | 413 if (!is_observing_) { |
400 is_observing_ = true; | 414 is_observing_ = true; |
401 container_->AddObserver(this); | 415 container_->AddObserver(this); |
402 } | 416 } |
403 } | 417 } |
404 | 418 |
405 void ExtensionMessageBubbleFactory::MaybeStopObserving() { | 419 void ExtensionMessageBubbleFactory::MaybeStopObserving() { |
406 if (is_observing_) { | 420 if (is_observing_) { |
407 is_observing_ = false; | 421 is_observing_ = false; |
408 container_->RemoveObserver(this); | 422 container_->RemoveObserver(this); |
409 } | 423 } |
410 } | 424 } |
411 | 425 |
412 void ExtensionMessageBubbleFactory::RecordProfileCheck(Profile* profile) { | 426 void ExtensionMessageBubbleFactory::RecordProfileCheck(Profile* profile) { |
413 g_profiles_evaluated.Get().insert(profile); | 427 g_profiles_evaluated.Get().insert(profile); |
414 } | 428 } |
415 | 429 |
416 bool ExtensionMessageBubbleFactory::IsInitialProfileCheck(Profile* profile) { | 430 bool ExtensionMessageBubbleFactory::IsInitialProfileCheck(Profile* profile) { |
417 return g_profiles_evaluated.Get().count(profile) == 0; | 431 return g_profiles_evaluated.Get().count(profile) == 0; |
418 } | 432 } |
419 | 433 |
420 void ExtensionMessageBubbleFactory::OnBrowserActionsContainerAnimationEnded() { | 434 void ExtensionMessageBubbleFactory::OnBrowserActionsContainerAnimationEnded() { |
421 MaybeStopObserving(); | 435 MaybeStopObserving(); |
422 if (stage_ == STAGE_START) { | 436 if (stage_ == STAGE_START) { |
423 HighlightDevModeExtensions(); | 437 HighlightExtensions(); |
424 } else if (stage_ == STAGE_HIGHLIGHTED) { | 438 } else if (stage_ == STAGE_HIGHLIGHTED) { |
425 ShowDevModeBubble(); | 439 ShowHighlightingBubble(); |
426 } else { // We shouldn't be observing if we've completed the process. | 440 } else { // We shouldn't be observing if we've completed the process. |
427 NOTREACHED(); | 441 NOTREACHED(); |
428 Finish(); | 442 Finish(); |
429 } | 443 } |
430 } | 444 } |
431 | 445 |
432 void ExtensionMessageBubbleFactory::OnBrowserActionsContainerDestroyed() { | 446 void ExtensionMessageBubbleFactory::OnBrowserActionsContainerDestroyed() { |
433 // If the container associated with the bubble is destroyed, abandon the | 447 // If the container associated with the bubble is destroyed, abandon the |
434 // process. | 448 // process. |
435 Finish(); | 449 Finish(); |
436 } | 450 } |
437 | 451 |
438 void ExtensionMessageBubbleFactory::HighlightDevModeExtensions() { | 452 void ExtensionMessageBubbleFactory::PrepareToHighlightExtensions( |
| 453 scoped_ptr<ExtensionMessageBubbleController> controller, |
| 454 views::View* anchor_view) { |
| 455 // We should be in the start stage (i.e., should not have a pending attempt to |
| 456 // show a bubble). |
| 457 DCHECK_EQ(stage_, STAGE_START); |
| 458 |
| 459 // Prepare to display and highlight the extensions before showing the bubble. |
| 460 // Since this is an asynchronous process, set member variables for later use. |
| 461 controller_ = controller.Pass(); |
| 462 anchor_view_ = anchor_view; |
| 463 container_ = toolbar_view_->browser_actions(); |
| 464 |
| 465 if (container_->animating()) |
| 466 MaybeObserve(); |
| 467 else |
| 468 HighlightExtensions(); |
| 469 } |
| 470 |
| 471 void ExtensionMessageBubbleFactory::HighlightExtensions() { |
439 DCHECK_EQ(STAGE_START, stage_); | 472 DCHECK_EQ(STAGE_START, stage_); |
440 stage_ = STAGE_HIGHLIGHTED; | 473 stage_ = STAGE_HIGHLIGHTED; |
441 | 474 |
442 const ExtensionIdList extension_list = controller_->GetExtensionIdList(); | 475 const ExtensionIdList extension_list = controller_->GetExtensionIdList(); |
443 DCHECK(!extension_list.empty()); | 476 DCHECK(!extension_list.empty()); |
444 ExtensionToolbarModel::Get(profile_)->HighlightExtensions(extension_list); | 477 ExtensionToolbarModel::Get(profile_)->HighlightExtensions(extension_list); |
445 if (container_->animating()) | 478 if (container_->animating()) |
446 MaybeObserve(); | 479 MaybeObserve(); |
447 else | 480 else |
448 ShowDevModeBubble(); | 481 ShowHighlightingBubble(); |
449 } | 482 } |
450 | 483 |
451 void ExtensionMessageBubbleFactory::ShowDevModeBubble() { | 484 void ExtensionMessageBubbleFactory::ShowHighlightingBubble() { |
452 DCHECK_EQ(stage_, STAGE_HIGHLIGHTED); | 485 DCHECK_EQ(stage_, STAGE_HIGHLIGHTED); |
453 stage_ = STAGE_COMPLETE; | 486 stage_ = STAGE_COMPLETE; |
454 | 487 |
455 views::View* reference_view = NULL; | 488 views::View* reference_view = NULL; |
456 if (container_->num_browser_actions() > 0) | 489 if (container_->num_browser_actions() > 0) |
457 reference_view = container_->GetBrowserActionViewAt(0); | 490 reference_view = container_->GetBrowserActionViewAt(0); |
458 if (reference_view && reference_view->visible()) | 491 if (reference_view && reference_view->visible()) |
459 anchor_view_ = reference_view; | 492 anchor_view_ = reference_view; |
460 | 493 |
461 DevModeBubbleController* weak_controller = controller_.get(); | 494 ExtensionMessageBubbleController* weak_controller = controller_.get(); |
462 ExtensionMessageBubbleView* bubble_delegate = new ExtensionMessageBubbleView( | 495 ExtensionMessageBubbleView* bubble_delegate = |
463 anchor_view_, | 496 new ExtensionMessageBubbleView( |
464 views::BubbleBorder::TOP_RIGHT, | 497 anchor_view_, |
465 scoped_ptr<ExtensionMessageBubbleController>(controller_.release())); | 498 views::BubbleBorder::TOP_RIGHT, |
| 499 scoped_ptr<ExtensionMessageBubbleController>( |
| 500 controller_.release())); |
466 views::BubbleDelegateView::CreateBubble(bubble_delegate); | 501 views::BubbleDelegateView::CreateBubble(bubble_delegate); |
467 weak_controller->Show(bubble_delegate); | 502 weak_controller->Show(bubble_delegate); |
468 | 503 |
469 Finish(); | 504 Finish(); |
470 } | 505 } |
471 | 506 |
472 void ExtensionMessageBubbleFactory::Finish() { | 507 void ExtensionMessageBubbleFactory::Finish() { |
473 MaybeStopObserving(); | 508 MaybeStopObserving(); |
474 controller_.reset(); | 509 controller_.reset(); |
475 anchor_view_ = NULL; | 510 anchor_view_ = NULL; |
476 container_ = NULL; | 511 container_ = NULL; |
477 } | 512 } |
478 | 513 |
479 } // namespace extensions | 514 } // namespace extensions |
OLD | NEW |