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

Side by Side Diff: chrome/browser/ui/webui/options/content_settings_handler.cc

Issue 7713034: HostContentSettingsMap refactoring. (Closed) Base URL: http://git.chromium.org/git/chromium.git@trunk
Patch Set: Keeping up to date with trunk. Created 9 years, 3 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 | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2011 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/webui/options/content_settings_handler.h" 5 #include "chrome/browser/ui/webui/options/content_settings_handler.h"
6 6
7 #include <map> 7 #include <map>
8 #include <string> 8 #include <string>
9 #include <vector> 9 #include <vector>
10 10
11 #include "base/callback.h" 11 #include "base/callback.h"
12 #include "base/command_line.h" 12 #include "base/command_line.h"
13 #include "base/utf_string_conversions.h" 13 #include "base/utf_string_conversions.h"
14 #include "base/values.h" 14 #include "base/values.h"
15 #include "chrome/browser/browser_process.h" 15 #include "chrome/browser/browser_process.h"
16 #include "chrome/browser/content_settings/content_settings_details.h" 16 #include "chrome/browser/content_settings/content_settings_details.h"
17 #include "chrome/browser/content_settings/content_settings_pattern.h" 17 #include "chrome/browser/content_settings/content_settings_pattern.h"
18 #include "chrome/browser/content_settings/content_settings_utils.h" 18 #include "chrome/browser/content_settings/content_settings_utils.h"
19 #include "chrome/browser/content_settings/cookie_settings.h"
19 #include "chrome/browser/content_settings/host_content_settings_map.h" 20 #include "chrome/browser/content_settings/host_content_settings_map.h"
20 #include "chrome/browser/custom_handlers/protocol_handler_registry.h" 21 #include "chrome/browser/custom_handlers/protocol_handler_registry.h"
21 #include "chrome/browser/notifications/desktop_notification_service.h" 22 #include "chrome/browser/notifications/desktop_notification_service.h"
22 #include "chrome/browser/notifications/desktop_notification_service_factory.h" 23 #include "chrome/browser/notifications/desktop_notification_service_factory.h"
23 #include "chrome/browser/profiles/profile.h" 24 #include "chrome/browser/profiles/profile.h"
24 #include "chrome/browser/ui/browser_list.h" 25 #include "chrome/browser/ui/browser_list.h"
25 #include "chrome/common/chrome_notification_types.h" 26 #include "chrome/common/chrome_notification_types.h"
26 #include "chrome/common/chrome_switches.h" 27 #include "chrome/common/chrome_switches.h"
27 #include "chrome/common/pref_names.h" 28 #include "chrome/common/pref_names.h"
28 #include "chrome/common/url_constants.h" 29 #include "chrome/common/url_constants.h"
(...skipping 26 matching lines...) Expand all
55 const ContentSettingsTypeNameEntry kContentSettingsTypeGroupNames[] = { 56 const ContentSettingsTypeNameEntry kContentSettingsTypeGroupNames[] = {
56 {CONTENT_SETTINGS_TYPE_COOKIES, "cookies"}, 57 {CONTENT_SETTINGS_TYPE_COOKIES, "cookies"},
57 {CONTENT_SETTINGS_TYPE_IMAGES, "images"}, 58 {CONTENT_SETTINGS_TYPE_IMAGES, "images"},
58 {CONTENT_SETTINGS_TYPE_JAVASCRIPT, "javascript"}, 59 {CONTENT_SETTINGS_TYPE_JAVASCRIPT, "javascript"},
59 {CONTENT_SETTINGS_TYPE_PLUGINS, "plugins"}, 60 {CONTENT_SETTINGS_TYPE_PLUGINS, "plugins"},
60 {CONTENT_SETTINGS_TYPE_POPUPS, "popups"}, 61 {CONTENT_SETTINGS_TYPE_POPUPS, "popups"},
61 {CONTENT_SETTINGS_TYPE_GEOLOCATION, "location"}, 62 {CONTENT_SETTINGS_TYPE_GEOLOCATION, "location"},
62 {CONTENT_SETTINGS_TYPE_NOTIFICATIONS, "notifications"}, 63 {CONTENT_SETTINGS_TYPE_NOTIFICATIONS, "notifications"},
63 {CONTENT_SETTINGS_TYPE_INTENTS, "intents"}, 64 {CONTENT_SETTINGS_TYPE_INTENTS, "intents"},
64 {CONTENT_SETTINGS_TYPE_AUTO_SELECT_CERTIFICATE, "auto-select-certificate"}, 65 {CONTENT_SETTINGS_TYPE_AUTO_SELECT_CERTIFICATE, "auto-select-certificate"},
66 {CONTENT_SETTINGS_TYPE_PERMANENT_COOKIES, "permanent-cookies"},
65 }; 67 };
66 COMPILE_ASSERT(arraysize(kContentSettingsTypeGroupNames) == 68 COMPILE_ASSERT(arraysize(kContentSettingsTypeGroupNames) ==
67 CONTENT_SETTINGS_NUM_TYPES, 69 CONTENT_SETTINGS_NUM_TYPES,
68 MISSING_CONTENT_SETTINGS_TYPE); 70 MISSING_CONTENT_SETTINGS_TYPE);
69 71
70 ContentSettingsType ContentSettingsTypeFromGroupName(const std::string& name) { 72 ContentSettingsType ContentSettingsTypeFromGroupName(const std::string& name) {
71 for (size_t i = 0; i < arraysize(kContentSettingsTypeGroupNames); ++i) { 73 for (size_t i = 0; i < arraysize(kContentSettingsTypeGroupNames); ++i) {
72 if (name == kContentSettingsTypeGroupNames[i].name) 74 if (name == kContentSettingsTypeGroupNames[i].name)
73 return kContentSettingsTypeGroupNames[i].type; 75 return kContentSettingsTypeGroupNames[i].type;
74 } 76 }
(...skipping 289 matching lines...) Expand 10 before | Expand all | Expand 10 after
364 } 366 }
365 367
366 std::string ContentSettingsHandler::GetSettingDefaultFromModel( 368 std::string ContentSettingsHandler::GetSettingDefaultFromModel(
367 ContentSettingsType type) { 369 ContentSettingsType type) {
368 Profile* profile = Profile::FromWebUI(web_ui_); 370 Profile* profile = Profile::FromWebUI(web_ui_);
369 ContentSetting default_setting; 371 ContentSetting default_setting;
370 if (type == CONTENT_SETTINGS_TYPE_NOTIFICATIONS) { 372 if (type == CONTENT_SETTINGS_TYPE_NOTIFICATIONS) {
371 default_setting = 373 default_setting =
372 DesktopNotificationServiceFactory::GetForProfile(profile)-> 374 DesktopNotificationServiceFactory::GetForProfile(profile)->
373 GetDefaultContentSetting(); 375 GetDefaultContentSetting();
376 } else if (type == CONTENT_SETTINGS_TYPE_COOKIES) {
377 default_setting =
378 CookieSettings::GetForProfile(profile)->GetDefaultCookieSetting();
374 } else { 379 } else {
375 default_setting = 380 default_setting =
376 profile->GetHostContentSettingsMap()->GetDefaultContentSetting(type); 381 profile->GetHostContentSettingsMap()->GetDefaultContentSetting(type);
377 } 382 }
378 383
379 return ContentSettingToString(default_setting); 384 return ContentSettingToString(default_setting);
380 } 385 }
381 386
382 bool ContentSettingsHandler::GetDefaultSettingManagedFromModel( 387 bool ContentSettingsHandler::GetDefaultSettingManagedFromModel(
383 ContentSettingsType type) { 388 ContentSettingsType type) {
(...skipping 18 matching lines...) Expand all
402 } 407 }
403 408
404 void ContentSettingsHandler::UpdateAllExceptionsViewsFromModel() { 409 void ContentSettingsHandler::UpdateAllExceptionsViewsFromModel() {
405 for (int type = CONTENT_SETTINGS_TYPE_DEFAULT + 1; 410 for (int type = CONTENT_SETTINGS_TYPE_DEFAULT + 1;
406 type < CONTENT_SETTINGS_NUM_TYPES; ++type) { 411 type < CONTENT_SETTINGS_NUM_TYPES; ++type) {
407 // The content settings type CONTENT_SETTINGS_TYPE_AUTO_SELECT_CERTIFICATE 412 // The content settings type CONTENT_SETTINGS_TYPE_AUTO_SELECT_CERTIFICATE
408 // is supposed to be set by policy only. Hence there is no user facing UI 413 // is supposed to be set by policy only. Hence there is no user facing UI
409 // for this content type and we skip it here. 414 // for this content type and we skip it here.
410 if (type == CONTENT_SETTINGS_TYPE_AUTO_SELECT_CERTIFICATE) 415 if (type == CONTENT_SETTINGS_TYPE_AUTO_SELECT_CERTIFICATE)
411 continue; 416 continue;
417 // No UI for permanent cookies; cookie settings are handled by
418 // CONTENT_SETTINGS_TYPE_COOKIES.
419 if (type == CONTENT_SETTINGS_TYPE_PERMANENT_COOKIES)
420 continue;
412 UpdateExceptionsViewFromModel(static_cast<ContentSettingsType>(type)); 421 UpdateExceptionsViewFromModel(static_cast<ContentSettingsType>(type));
413 } 422 }
414 } 423 }
415 424
416 void ContentSettingsHandler::UpdateAllOTRExceptionsViewsFromModel() { 425 void ContentSettingsHandler::UpdateAllOTRExceptionsViewsFromModel() {
417 for (int type = CONTENT_SETTINGS_TYPE_DEFAULT + 1; 426 for (int type = CONTENT_SETTINGS_TYPE_DEFAULT + 1;
418 type < CONTENT_SETTINGS_NUM_TYPES; ++type) { 427 type < CONTENT_SETTINGS_NUM_TYPES; ++type) {
428 // No UI for permanent cookies; cookie settings are handled by
429 // CONTENT_SETTINGS_TYPE_COOKIES.
430 if (type == CONTENT_SETTINGS_TYPE_PERMANENT_COOKIES)
431 continue;
419 UpdateOTRExceptionsViewFromModel(static_cast<ContentSettingsType>(type)); 432 UpdateOTRExceptionsViewFromModel(static_cast<ContentSettingsType>(type));
420 } 433 }
421 } 434 }
422 435
423 void ContentSettingsHandler::UpdateExceptionsViewFromModel( 436 void ContentSettingsHandler::UpdateExceptionsViewFromModel(
424 ContentSettingsType type) { 437 ContentSettingsType type) {
425 switch (type) { 438 switch (type) {
426 case CONTENT_SETTINGS_TYPE_GEOLOCATION: 439 case CONTENT_SETTINGS_TYPE_GEOLOCATION:
427 UpdateGeolocationExceptionsView(); 440 UpdateGeolocationExceptionsView();
428 break; 441 break;
429 case CONTENT_SETTINGS_TYPE_NOTIFICATIONS: 442 case CONTENT_SETTINGS_TYPE_NOTIFICATIONS:
430 UpdateNotificationExceptionsView(); 443 UpdateNotificationExceptionsView();
431 break; 444 break;
445 case CONTENT_SETTINGS_TYPE_COOKIES:
446 UpdateCookieExceptionsView();
447 break;
432 default: 448 default:
433 UpdateExceptionsViewFromHostContentSettingsMap(type); 449 UpdateExceptionsViewFromHostContentSettingsMap(type);
434 break; 450 break;
435 } 451 }
436 } 452 }
437 453
438 void ContentSettingsHandler::UpdateOTRExceptionsViewFromModel( 454 void ContentSettingsHandler::UpdateOTRExceptionsViewFromModel(
439 ContentSettingsType type) { 455 ContentSettingsType type) {
440 switch (type) { 456 switch (type) {
441 case CONTENT_SETTINGS_TYPE_GEOLOCATION: 457 case CONTENT_SETTINGS_TYPE_GEOLOCATION:
(...skipping 85 matching lines...) Expand 10 before | Expand all | Expand 10 after
527 StringValue type_string( 543 StringValue type_string(
528 ContentSettingsTypeToGroupName(CONTENT_SETTINGS_TYPE_NOTIFICATIONS)); 544 ContentSettingsTypeToGroupName(CONTENT_SETTINGS_TYPE_NOTIFICATIONS));
529 web_ui_->CallJavascriptFunction("ContentSettings.setExceptions", 545 web_ui_->CallJavascriptFunction("ContentSettings.setExceptions",
530 type_string, exceptions); 546 type_string, exceptions);
531 547
532 // This is mainly here to keep this function ideologically parallel to 548 // This is mainly here to keep this function ideologically parallel to
533 // UpdateExceptionsViewFromHostContentSettingsMap(). 549 // UpdateExceptionsViewFromHostContentSettingsMap().
534 UpdateSettingDefaultFromModel(CONTENT_SETTINGS_TYPE_NOTIFICATIONS); 550 UpdateSettingDefaultFromModel(CONTENT_SETTINGS_TYPE_NOTIFICATIONS);
535 } 551 }
536 552
553 void ContentSettingsHandler::UpdateCookieExceptionsView() {
554 UpdateCookieExceptionsViewFromCookieSettings(
555 GetCookieSettings(), "ContentSettings.setExceptions");
556 UpdateCookieExceptionsViewFromCookieSettings(
557 GetOTRCookieSettings(), "ContentSettings.setOTRExceptions");
558 // This is mainly here to keep this function ideologically parallel to
559 // UpdateExceptionsViewFromHostContentSettingsMap().
560 UpdateSettingDefaultFromModel(CONTENT_SETTINGS_TYPE_COOKIES);
561 }
562
563 void ContentSettingsHandler::UpdateCookieExceptionsViewFromCookieSettings(
564 CookieSettings* cookie_settings,
565 const std::string& function_name) {
566 if (!cookie_settings)
567 return;
568
569 HostContentSettingsMap::SettingsForOneType entries;
570 cookie_settings->GetCookieSettings(&entries);
571
572 ListValue exceptions;
573 for (size_t i = 0; i < entries.size(); ++i) {
574 exceptions.Append(
575 GetExceptionForPage(entries[i].a, entries[i].c, entries[i].d));
576 }
577
578 StringValue type_string(
579 ContentSettingsTypeToGroupName(CONTENT_SETTINGS_TYPE_COOKIES));
580 web_ui_->CallJavascriptFunction(function_name, type_string, exceptions);
581 }
582
537 void ContentSettingsHandler::UpdateExceptionsViewFromHostContentSettingsMap( 583 void ContentSettingsHandler::UpdateExceptionsViewFromHostContentSettingsMap(
538 ContentSettingsType type) { 584 ContentSettingsType type) {
539 HostContentSettingsMap::SettingsForOneType entries; 585 HostContentSettingsMap::SettingsForOneType entries;
540 GetContentSettingsMap()->GetSettingsForOneType(type, "", &entries); 586 GetContentSettingsMap()->GetSettingsForOneType(type, "", &entries);
541 587
542 ListValue exceptions; 588 ListValue exceptions;
543 for (size_t i = 0; i < entries.size(); ++i) { 589 for (size_t i = 0; i < entries.size(); ++i) {
544 // The content settings UI does not support secondary content settings 590 // The content settings UI does not support secondary content settings
545 // pattern yet. For content settings set through the content settings UI the 591 // pattern yet. For content settings set through the content settings UI the
546 // secondary pattern is by default a wildcard pattern. Hence users are not 592 // secondary pattern is by default a wildcard pattern. Hence users are not
(...skipping 63 matching lines...) Expand 10 before | Expand all | Expand 10 after
610 NOTREACHED(); 656 NOTREACHED();
611 return; 657 return;
612 } 658 }
613 659
614 ContentSetting default_setting = ContentSettingFromString(setting); 660 ContentSetting default_setting = ContentSettingFromString(setting);
615 ContentSettingsType content_type = ContentSettingsTypeFromGroupName(group); 661 ContentSettingsType content_type = ContentSettingsTypeFromGroupName(group);
616 if (content_type == CONTENT_SETTINGS_TYPE_NOTIFICATIONS) { 662 if (content_type == CONTENT_SETTINGS_TYPE_NOTIFICATIONS) {
617 Profile* profile = Profile::FromWebUI(web_ui_); 663 Profile* profile = Profile::FromWebUI(web_ui_);
618 DesktopNotificationServiceFactory::GetForProfile(profile)-> 664 DesktopNotificationServiceFactory::GetForProfile(profile)->
619 SetDefaultContentSetting(default_setting); 665 SetDefaultContentSetting(default_setting);
666 } else if (content_type == CONTENT_SETTINGS_TYPE_COOKIES) {
667 GetCookieSettings()->SetDefaultCookieSetting(default_setting);
620 } else { 668 } else {
621 GetContentSettingsMap()-> 669 GetContentSettingsMap()->
622 SetDefaultContentSetting(content_type, default_setting); 670 SetDefaultContentSetting(content_type, default_setting);
623 } 671 }
624 } 672 }
625 673
626 void ContentSettingsHandler::RemoveException(const ListValue* args) { 674 void ContentSettingsHandler::RemoveException(const ListValue* args) {
627 size_t arg_i = 0; 675 size_t arg_i = 0;
628 std::string type_string; 676 std::string type_string;
629 CHECK(args->GetString(arg_i++, &type_string)); 677 CHECK(args->GetString(arg_i++, &type_string));
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after
665 std::string pattern; 713 std::string pattern;
666 rv = args->GetString(arg_i++, &pattern); 714 rv = args->GetString(arg_i++, &pattern);
667 DCHECK(rv); 715 DCHECK(rv);
668 716
669 HostContentSettingsMap* settings_map = 717 HostContentSettingsMap* settings_map =
670 mode == "normal" ? GetContentSettingsMap() : 718 mode == "normal" ? GetContentSettingsMap() :
671 GetOTRContentSettingsMap(); 719 GetOTRContentSettingsMap();
672 // The settings map could be null if the mode was OTR but the OTR profile 720 // The settings map could be null if the mode was OTR but the OTR profile
673 // got destroyed before we received this message. 721 // got destroyed before we received this message.
674 if (settings_map) { 722 if (settings_map) {
675 settings_map->SetContentSetting( 723 if (type == CONTENT_SETTINGS_TYPE_COOKIES) {
676 ContentSettingsPattern::FromString(pattern), 724 CookieSettings* cookie_settings =
677 ContentSettingsPattern::Wildcard(), 725 mode == "normal" ? GetCookieSettings() : GetOTRCookieSettings();
678 ContentSettingsTypeFromGroupName(type_string), 726 if (cookie_settings == NULL) {
679 "", 727 NOTREACHED();
680 CONTENT_SETTING_DEFAULT); 728 return;
729 }
730 cookie_settings->ResetCookieSetting(
731 ContentSettingsPattern::FromString(pattern));
732 } else {
733 settings_map->SetContentSetting(
734 ContentSettingsPattern::FromString(pattern),
735 ContentSettingsPattern::Wildcard(),
736 ContentSettingsTypeFromGroupName(type_string),
737 "",
738 CONTENT_SETTING_DEFAULT);
739 }
681 } 740 }
682 } 741 }
683 } 742 }
684 743
685 void ContentSettingsHandler::SetException(const ListValue* args) { 744 void ContentSettingsHandler::SetException(const ListValue* args) {
686 size_t arg_i = 0; 745 size_t arg_i = 0;
687 std::string type_string; 746 std::string type_string;
688 CHECK(args->GetString(arg_i++, &type_string)); 747 CHECK(args->GetString(arg_i++, &type_string));
689 std::string mode; 748 std::string mode;
690 CHECK(args->GetString(arg_i++, &mode)); 749 CHECK(args->GetString(arg_i++, &mode));
691 std::string pattern; 750 std::string pattern;
692 CHECK(args->GetString(arg_i++, &pattern)); 751 CHECK(args->GetString(arg_i++, &pattern));
693 std::string setting; 752 std::string setting;
694 CHECK(args->GetString(arg_i++, &setting)); 753 CHECK(args->GetString(arg_i++, &setting));
695 754
696 ContentSettingsType type = ContentSettingsTypeFromGroupName(type_string); 755 ContentSettingsType type = ContentSettingsTypeFromGroupName(type_string);
697 if (type == CONTENT_SETTINGS_TYPE_GEOLOCATION || 756 if (type == CONTENT_SETTINGS_TYPE_GEOLOCATION ||
698 type == CONTENT_SETTINGS_TYPE_NOTIFICATIONS) { 757 type == CONTENT_SETTINGS_TYPE_NOTIFICATIONS) {
699 NOTREACHED(); 758 NOTREACHED();
700 return; 759 return;
701 } 760 }
702 761
703 HostContentSettingsMap* settings_map = 762 HostContentSettingsMap* settings_map =
704 mode == "normal" ? GetContentSettingsMap() : 763 mode == "normal" ? GetContentSettingsMap() :
705 GetOTRContentSettingsMap(); 764 GetOTRContentSettingsMap();
706
707 // The settings map could be null if the mode was OTR but the OTR profile 765 // The settings map could be null if the mode was OTR but the OTR profile
708 // got destroyed before we received this message. 766 // got destroyed before we received this message.
709 if (!settings_map) 767 if (!settings_map)
710 return; 768 return;
711 settings_map->SetContentSetting(ContentSettingsPattern::FromString(pattern), 769 if (type == CONTENT_SETTINGS_TYPE_COOKIES) {
712 ContentSettingsPattern::Wildcard(), 770 CookieSettings* cookie_settings =
713 type, 771 mode == "normal" ? GetCookieSettings() : GetOTRCookieSettings();
714 "", 772 DCHECK(cookie_settings != NULL);
715 ContentSettingFromString(setting)); 773 cookie_settings->SetCookieSetting(
774 ContentSettingsPattern::FromString(pattern),
775 ContentSettingFromString(setting));
776 } else {
777 settings_map->SetContentSetting(ContentSettingsPattern::FromString(pattern),
778 ContentSettingsPattern::Wildcard(),
779 type,
780 "",
781 ContentSettingFromString(setting));
782 }
716 } 783 }
717 784
718 void ContentSettingsHandler::CheckExceptionPatternValidity( 785 void ContentSettingsHandler::CheckExceptionPatternValidity(
719 const ListValue* args) { 786 const ListValue* args) {
720 size_t arg_i = 0; 787 size_t arg_i = 0;
721 Value* type; 788 Value* type;
722 CHECK(args->Get(arg_i++, &type)); 789 CHECK(args->Get(arg_i++, &type));
723 std::string mode_string; 790 std::string mode_string;
724 CHECK(args->GetString(arg_i++, &mode_string)); 791 CHECK(args->GetString(arg_i++, &mode_string));
725 std::string pattern_string; 792 std::string pattern_string;
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after
760 return Profile::FromWebUI(web_ui_)->GetProtocolHandlerRegistry(); 827 return Profile::FromWebUI(web_ui_)->GetProtocolHandlerRegistry();
761 } 828 }
762 829
763 HostContentSettingsMap* 830 HostContentSettingsMap*
764 ContentSettingsHandler::GetOTRContentSettingsMap() { 831 ContentSettingsHandler::GetOTRContentSettingsMap() {
765 Profile* profile = Profile::FromWebUI(web_ui_); 832 Profile* profile = Profile::FromWebUI(web_ui_);
766 if (profile->HasOffTheRecordProfile()) 833 if (profile->HasOffTheRecordProfile())
767 return profile->GetOffTheRecordProfile()->GetHostContentSettingsMap(); 834 return profile->GetOffTheRecordProfile()->GetHostContentSettingsMap();
768 return NULL; 835 return NULL;
769 } 836 }
837
838 CookieSettings* ContentSettingsHandler::GetCookieSettings() {
839 return CookieSettings::GetForProfile(Profile::FromWebUI(web_ui_));
840 }
841
842 CookieSettings* ContentSettingsHandler::GetOTRCookieSettings() {
843 Profile* profile = Profile::FromWebUI(web_ui_);
844 if (profile->HasOffTheRecordProfile())
845 return CookieSettings::GetForProfile(
846 profile->GetOffTheRecordProfile());
847 return NULL;
848 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698