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

Side by Side Diff: chrome/browser/ui/views/website_settings_popup_view.cc

Issue 10456017: Add WebsiteSettingsUI for Windows (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Created 8 years, 6 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
(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/ui/views/website_settings_popup_view.h"
6
7 #include "base/string_number_conversions.h"
8 #include "base/utf_string_conversions.h"
9 #include "chrome/browser/profiles/profile.h"
10 #include "chrome/browser/website_settings.h"
11 #include "chrome/browser/ui/views/collected_cookies_views.h"
12 #include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h"
13 #include "chrome/common/content_settings_types.h"
14 #include "grit/generated_resources.h"
15 #include "ui/base/l10n/l10n_util.h"
16 #include "ui/gfx/font.h"
17 #include "ui/views/controls/combobox/combobox.h"
18 #include "ui/views/controls/label.h"
19 #include "ui/views/controls/link.h"
20 #include "ui/views/controls/tabbed_pane/tabbed_pane.h"
21 #include "ui/views/layout/box_layout.h"
22 #include "ui/views/layout/grid_layout.h"
23 #include "ui/views/layout/layout_manager.h"
24
25 namespace {
26
27 string16 PermissionTypeToString(ContentSettingsType type) {
28 switch (type) {
29 case CONTENT_SETTINGS_TYPE_POPUPS:
30 return l10n_util::GetStringUTF16(IDS_WEBSITE_SETTINGS_TYPE_POPUPS);
31 case CONTENT_SETTINGS_TYPE_PLUGINS:
32 return l10n_util::GetStringUTF16(IDS_WEBSITE_SETTINGS_TYPE_PLUGINS);
33 case CONTENT_SETTINGS_TYPE_GEOLOCATION:
34 return l10n_util::GetStringUTF16(IDS_WEBSITE_SETTINGS_TYPE_LOCATION);
35 case CONTENT_SETTINGS_TYPE_NOTIFICATIONS:
36 return l10n_util::GetStringUTF16(IDS_WEBSITE_SETTINGS_TYPE_NOTIFICATIONS);
37 default:
38 NOTREACHED();
39 return ASCIIToUTF16("");
40 }
41 }
42
43 string16 PermissionValueToString(ContentSetting value) {
44 switch (value) {
45 case CONTENT_SETTING_ALLOW:
46 return l10n_util::GetStringUTF16(IDS_WEBSITE_SETTINGS_PERMISSION_ALLOW);
47 case CONTENT_SETTING_BLOCK:
48 return l10n_util::GetStringUTF16(IDS_WEBSITE_SETTINGS_PERMISSION_BLOCK);
49 case CONTENT_SETTING_ASK:
50 return l10n_util::GetStringUTF16(IDS_WEBSITE_SETTINGS_PERMISSION_ASK);
51 default:
52 NOTREACHED();
53 return ASCIIToUTF16("");
54 }
55 }
Finnur 2012/05/30 11:20:04 These two functions are a bit unfortunate since th
markusheintz_ 2012/06/05 17:02:12 I created two methods for returning the ressource
56
57 } // namespace
58
59 // ////////////////////////////////////////////////////////////////////////////
60 // Popup Header
61 // ////////////////////////////////////////////////////////////////////////////
Finnur 2012/05/30 11:20:04 nit: To be consistent with other Chrome code, you
markusheintz_ 2012/06/05 17:02:12 Done.
62
63 PopupHeader::PopupHeader() : name_(NULL), status_(NULL) {
64 views::GridLayout* layout = new views::GridLayout(this);
65 SetLayoutManager(layout);
66 const int label_column = 0;
67 views::ColumnSet* column_set = layout->AddColumnSet(label_column);
68 column_set->AddColumn(views::GridLayout::FILL,
69 views::GridLayout::FILL,
70 1,
71 views::GridLayout::USE_PREF,
72 0,
73 0);
74
75 layout->AddPaddingRow(0, 8);
76
77 layout->StartRow(0, label_column);
78 name_ = new views::Label(string16());
79 name_->SetFont(name_->font().DeriveFont(0, gfx::Font::BOLD));
80 layout->AddView(name_, 1, 1, views::GridLayout::LEADING,
81 views::GridLayout::CENTER);
82
83 layout->AddPaddingRow(0, 4);
84
85 status_ = new views::Label(string16());
86 layout->StartRow(0, label_column);
87 layout->AddView(status_,
88 1,
89 1,
90 views::GridLayout::LEADING,
91 views::GridLayout::CENTER);
92 }
93
94 PopupHeader::~PopupHeader() {
95 }
96
97 gfx::Size PopupHeader::GetPreferredSize() {
98 return gfx::Size(300, 80);
Finnur 2012/05/30 11:20:04 Do you mean to reserve space (80 pixels) even if n
markusheintz_ 2012/06/05 17:02:12 The width 300 was meant prevent flickering. But it
99 }
100
101 void PopupHeader::SetIdentityName(string16 name) {
Finnur 2012/05/30 11:20:04 const string16&, same on line 105
markusheintz_ 2012/06/05 17:02:12 Done (Damn it :) )
102 name_->SetText(name);
103 }
104
105 void PopupHeader::SetIdentityStatus(string16 status) {
106 status_->SetText(status);
107 }
108
109
Finnur 2012/05/30 11:20:04 nit: Extra line break.
markusheintz_ 2012/06/05 17:02:12 Done.
110 // ////////////////////////////////////////////////////////////////////////////
111 // PermissionComboboxModel
112 // ////////////////////////////////////////////////////////////////////////////
113
114 PermissionComboboxModel::PermissionComboboxModel(
115 ContentSettingsType site_permission, ContentSetting default_setting)
116 : site_permission_(site_permission),
117 default_setting_(default_setting){
118 settings_.push_back(CONTENT_SETTING_DEFAULT);
119 settings_.push_back(CONTENT_SETTING_ALLOW);
120 settings_.push_back(CONTENT_SETTING_BLOCK);
121 if (site_permission == CONTENT_SETTINGS_TYPE_GEOLOCATION ||
122 site_permission == CONTENT_SETTINGS_TYPE_NOTIFICATIONS) {
123 settings_.push_back(CONTENT_SETTING_ASK);
124 }
125 }
126
127 PermissionComboboxModel::~PermissionComboboxModel() {
128 }
129
130 ContentSetting PermissionComboboxModel::GetSettingAt(int index) const {
131 if (index < static_cast<int>(settings_.size()))
132 return settings_[index];
133 NOTREACHED();
134 return CONTENT_SETTING_DEFAULT;
135 }
136
137 int PermissionComboboxModel::GetItemCount() const {
138 return settings_.size();
139 }
140
141 string16 PermissionComboboxModel::GetItemAt(int index) {
142 if (index == 0)
143 return l10n_util::GetStringUTF16(IDS_WEBSITE_SETTINGS_PERMISSION_DEFAULT)
144 + ASCIIToUTF16(" (") + PermissionValueToString(default_setting_)
145 + ASCIIToUTF16(")");
Finnur 2012/05/30 11:20:04 String concatenations are generally a bad idea in
markusheintz_ 2012/06/05 17:02:12 Done.
146 if (index < static_cast<int>(settings_.size()))
147 return PermissionValueToString(settings_[index]);
148 NOTREACHED();
149 return string16();
150 }
151
152 // ////////////////////////////////////////////////////////////////////////////
153 // WebsiteSettingsPopupView
154 // ////////////////////////////////////////////////////////////////////////////
155
156 WebsiteSettingsPopupView::WebsiteSettingsPopupView(
157 views::View* anchor_view,
158 Profile* profile,
Finnur 2012/05/30 11:20:04 Why do you need the |profile|?
markusheintz_ 2012/06/05 17:02:12 I need it now to create the presenter (WebsiteSett
159 TabContentsWrapper* wrapper)
160 : BubbleDelegateView(anchor_view, views::BubbleBorder::TOP_LEFT),
161 tab_contents_wrapper_(wrapper),
162 presenter_(NULL),
163 header_(NULL),
164 tabbed_pane_(NULL),
165 site_data_content_(NULL),
166 cookie_dialog_link_(NULL),
167 permissions_content_(NULL),
168 identity_info_text_(NULL),
169 connection_info_text_(NULL),
170 page_info_text_(NULL) {
171 views::GridLayout* layout = new views::GridLayout(this);
172 SetLayoutManager(layout);
173 const int content_column = 0;
174 views::ColumnSet* column_set = layout->AddColumnSet(content_column);
175 column_set->AddColumn(views::GridLayout::FILL,
176 views::GridLayout::FILL,
177 1,
178 views::GridLayout::USE_PREF,
179 0,
180 0);
181
182 header_ = new PopupHeader();
183 layout->StartRow(1, content_column);
184 layout->AddView(header_);
185
186 layout->AddPaddingRow(1, 10);
187
188 tabbed_pane_ = new views::TabbedPane();
189 layout->StartRow(1, content_column);
190 layout->AddView(tabbed_pane_);
191 // Tabs must be added after the tabbed_pane_ was added to the views hierachy.
192 // Adding the |tabbed_pane_| to the views hierachy triggers the
193 // initialization of the native tab UI element. If the native tab UI element
194 // is not initalized adding a tab will result in a NULL pointer excetion.
195 tabbed_pane_->AddTab(
196 l10n_util::GetStringUTF16(IDS_WEBSITE_SETTINGS_TAB_LABEL_PERMISSIONS),
197 CreatePermissionsTab());
198 tabbed_pane_->AddTab(
199 l10n_util::GetStringUTF16(IDS_WEBSITE_SETTINGS_TAB_LABEL_IDENTITY),
200 CreateIdentityTab());
201 tabbed_pane_->SelectTabAt(0);
202 tabbed_pane_->set_listener(this);
203
204 views::BubbleDelegateView::CreateBubble(this);
205 this->Show();
206 SizeToContents();
207 }
208
209 WebsiteSettingsPopupView::~WebsiteSettingsPopupView() {
210 if (presenter_)
211 presenter_->OnUIClosing();
212 }
213
214 void WebsiteSettingsPopupView::SetPresenter(WebsiteSettings* presenter) {
215 presenter_ = presenter;
216 }
217
218 void WebsiteSettingsPopupView::SetCookieInfo(
219 const CookieInfoList& cookie_info_list) {
220 site_data_content_->RemoveAllChildViews(true);
221
222 views::GridLayout* layout =
223 new views::GridLayout(site_data_content_);
Finnur 2012/05/30 11:20:04 nit: This fits within one line.
markusheintz_ 2012/06/05 17:02:12 Done.
224 site_data_content_->SetLayoutManager(layout);
225
226 const int site_data_content_column = 0;
227 views::ColumnSet* column_set =
228 layout->AddColumnSet(site_data_content_column);
229 column_set->AddColumn(views::GridLayout::FILL,
230 views::GridLayout::FILL,
231 1,
232 views::GridLayout::USE_PREF,
233 0,
234 0);
235
236 for (CookieInfoList::const_iterator it = cookie_info_list.begin();
237 it != cookie_info_list.end();
238 ++it) {
239 // TODO(markusheintz): Add L10N strings for "allowed" and "blocked" once we
240 // decided about the wording.
241 std::string label_text = it->cookie_source + " (";
242 label_text += base::IntToString(it->allowed);
243 label_text += " allowed / ";
244 label_text += base::IntToString(it->blocked);
245 label_text += " blocked)";
Finnur 2012/05/30 11:20:04 Same i18n argument here. It is easy to add a strin
markusheintz_ 2012/06/05 17:02:12 Done.
246 layout->StartRow(1, site_data_content_column);
247 layout->AddView(new views::Label(UTF8ToUTF16(label_text)),
248 1,
249 1,
250 views::GridLayout::LEADING,
251 views::GridLayout::CENTER);
252
253 layout->AddPaddingRow(1, 4);
254 }
255
256 layout->Layout(site_data_content_);
257 SizeToContents();
258 }
259
260 void WebsiteSettingsPopupView::SetPermissionInfo(
261 const PermissionInfoList& permission_info_list) {
262 permissions_content_->RemoveAllChildViews(true);
263
264 views::GridLayout* layout =
265 new views::GridLayout(permissions_content_);
266 permissions_content_->SetLayoutManager(layout);
267 const int content_column = 0;
268 views::ColumnSet* column_set =
269 layout->AddColumnSet(content_column);
270 column_set->AddColumn(views::GridLayout::FILL,
271 views::GridLayout::FILL,
272 1,
273 views::GridLayout::USE_PREF,
274 0,
275 0);
276 for (PermissionInfoList::const_iterator permission =
277 permission_info_list.begin();
Finnur 2012/05/30 11:20:04 nit: You could move the initialization of |permiss
markusheintz_ 2012/06/05 17:02:12 I don't know. Does this really improve the readabi
Finnur 2012/06/06 11:55:50 Like I said, up to you. :)
markusheintz_ 2012/06/06 13:47:03 :)
278 permission != permission_info_list.end();
279 ++permission) {
280 views::Label* label =
281 new views::Label(PermissionTypeToString(permission->type));
282 views::Combobox* combobox = new views::Combobox(
283 new PermissionComboboxModel(permission->type,
284 permission->default_setting));
Finnur 2012/05/30 11:20:04 nit: align indentation of type with default_settin
markusheintz_ 2012/06/05 17:02:12 Done.
285 switch (permission->setting) {
286 case CONTENT_SETTING_DEFAULT: combobox->SetSelectedIndex(0);
287 break;
288 case CONTENT_SETTING_ALLOW: combobox->SetSelectedIndex(1);
289 break;
290 case CONTENT_SETTING_BLOCK: combobox->SetSelectedIndex(2);
291 break;
292 default: combobox->SetSelectedIndex(4);
293 break;
294 }
295 combobox->set_listener(this);
296
297 layout->StartRow(1, content_column);
298 layout->AddView(CreatePermissionRow(label, combobox),
299 1,
300 1,
301 views::GridLayout::LEADING,
302 views::GridLayout::CENTER);
303
304 }
305
306 SizeToContents();
307 }
308
309 void WebsiteSettingsPopupView::SetIdentityInfo(
310 const IdentityInfo& identity_info) {
311 string16 identity_status_text;
312 switch (identity_info.identity_status) {
313 case WebsiteSettings::SITE_IDENTITY_STATUS_CERT:
314 case WebsiteSettings::SITE_IDENTITY_STATUS_DNSSEC_CERT:
315 case WebsiteSettings::SITE_IDENTITY_STATUS_EV_CERT:
316 identity_status_text =
317 l10n_util::GetStringUTF16(IDS_WEBSITE_SETTINGS_IDENTITY_VERIFIED);
318 break;
319 default:
320 identity_status_text =
321 l10n_util::GetStringUTF16(IDS_WEBSITE_SETTINGS_IDENTITY_NOT_VERIFIED);
322 break;
323 }
324 header_->SetIdentityName(UTF8ToUTF16(identity_info.site_identity));
325 header_->SetIdentityStatus(identity_status_text);
326
327 identity_info_text_->SetText(
328 UTF8ToUTF16(identity_info.identity_status_description));
329
330 connection_info_text_->SetText(
331 UTF8ToUTF16(identity_info.connection_status_description));
332
333 GetLayoutManager()->Layout(this);
334 SizeToContents();
335 }
336
337 gfx::Size WebsiteSettingsPopupView::GetPreferredSize() {
338 int height = 0;
339 int width = 300;
340 if (header_)
341 height += header_->GetPreferredSize().height();
342 if (tabbed_pane_)
343 height += tabbed_pane_->GetPreferredSize().height();
344
345 gfx::Size size(width, height);
346 return size;
347 }
348
349 void WebsiteSettingsPopupView::SetFirstVisit(const string16& first_visit) {
350 page_info_text_->SetText(first_visit);
351 }
352
353 gfx::Rect WebsiteSettingsPopupView::GetAnchorRect() {
354 // Compensate for some built-in padding in the icon. This will make the arrow
355 // point to the mittle of the icon.
Finnur 2012/05/30 11:20:04 Englisch, bitte! s/mittle/middle/. :)
markusheintz_ 2012/06/05 17:02:12 Done. You won't believe it. But this is a copy and
356 gfx::Rect anchor(BubbleDelegateView::GetAnchorRect());
357 anchor.Inset(0, anchor_view() ? 5 : 0);
Finnur 2012/05/30 11:20:04 Can you create a variable for this magic constant
markusheintz_ 2012/06/05 17:02:12 Done.
358 return anchor;
359 }
360
361 void WebsiteSettingsPopupView::OnSelectedIndexChanged(
362 views::Combobox* combobox) {
363 PermissionComboboxModel* model =
364 static_cast<PermissionComboboxModel*>(combobox->model());
365 DCHECK(model);
366 presenter_->OnSitePermissionChanged(
367 model->site_permission(),
368 model->GetSettingAt(combobox->selected_index()));
369 }
370
371 void WebsiteSettingsPopupView::LinkClicked(views::Link* source,
372 int event_flags) {
373 if (source == cookie_dialog_link_) {
374 new CollectedCookiesViews(tab_contents_wrapper_);
375 GetWidget()->CloseNow();
376 }
Finnur 2012/05/30 11:20:04 else NOTREACHED?
markusheintz_ 2012/06/05 17:02:12 Done.
377 }
378
379 void WebsiteSettingsPopupView::TabSelectedAt(int index) {
380 tabbed_pane_->GetSelectedTab()->Layout();
381 SizeToContents();
382 }
383
384 views::View* WebsiteSettingsPopupView::CreatePermissionsTab() {
385 views::View* pane = new views::View();
386 pane->SetLayoutManager(
387 new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 1));
388
389 cookie_dialog_link_ = new views::Link(
390 l10n_util::GetStringUTF16(IDS_WEBSITE_SETTINGS_SHOW_SITE_DATA));
391 cookie_dialog_link_->set_listener(this);
392 site_data_content_ = new views::View();
393 views::View* site_data_section =
394 CreateSection(l10n_util::GetStringUTF16(
395 IDS_WEBSITE_SETTINGS_TITLE_SITE_DATA),
396 site_data_content_,
397 cookie_dialog_link_);
398 pane->AddChildView(site_data_section);
399
400 permissions_content_ = new views::View();
401 views::View* permissions_section =
402 CreateSection(l10n_util::GetStringUTF16(
403 IDS_WEBSITE_SETTINGS_TITLE_SITE_PERMISSIONS),
404 permissions_content_,
405 NULL);
406 pane->AddChildView(permissions_section);
407 return pane;
408 }
409
410 views::View* WebsiteSettingsPopupView::CreateIdentityTab() {
411 views::View* pane = new views::View();
412 pane->SetLayoutManager(
413 new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 1));
414
415 // Add Identity section
Finnur 2012/05/30 11:20:04 nit: End comments in period.
markusheintz_ 2012/06/05 17:02:12 Done.
416 views::View* section_content = new views::View();
417 views::GridLayout* layout =
418 new views::GridLayout(section_content);
419 section_content->SetLayoutManager(layout);
420 const int content_column = 0;
421 views::ColumnSet* column_set =
422 layout->AddColumnSet(content_column);
423 column_set->AddColumn(views::GridLayout::FILL,
424 views::GridLayout::FILL,
425 1,
426 views::GridLayout::USE_PREF,
427 0,
428 0);
429 identity_info_text_ = CreateTextLabel(string16());
430 layout->StartRow(1, content_column);
431 layout->AddView(identity_info_text_, 1, 1, views::GridLayout::LEADING,
432 views::GridLayout::CENTER);
433 views::View* section =
434 CreateSection(l10n_util::GetStringUTF16(
435 IDS_WEBSITE_SETTINGS_TITEL_IDENTITY),
436 section_content,
437 NULL);
438 pane->AddChildView(section);
439
440 // Add connection section.
441 section_content = new views::View();
442 layout = new views::GridLayout(section_content);
443 section_content->SetLayoutManager(layout);
444 column_set = layout->AddColumnSet(content_column);
445 column_set->AddColumn(views::GridLayout::FILL,
446 views::GridLayout::FILL,
447 1,
448 views::GridLayout::USE_PREF,
449 0,
450 0);
451 connection_info_text_ = CreateTextLabel(string16());
452 layout->StartRow(1, content_column);
453 layout->AddView(connection_info_text_, 1, 1, views::GridLayout::LEADING,
454 views::GridLayout::CENTER);
455 section = CreateSection(l10n_util::GetStringUTF16(
456 IDS_WEBSITE_SETTINGS_TITEL_CONNECTION),
457 section_content,
458 NULL);
459 pane->AddChildView(section);
460
461 // Add page info section.
462 section_content = new views::View();
463 layout = new views::GridLayout(section_content);
464 section_content->SetLayoutManager(layout);
465 column_set = layout->AddColumnSet(content_column);
466 column_set->AddColumn(views::GridLayout::FILL,
467 views::GridLayout::FILL,
468 1,
469 views::GridLayout::USE_PREF,
470 0,
471 0);
472 page_info_text_ = CreateTextLabel(string16());
473 layout->StartRow(1, content_column);
474 layout->AddView(page_info_text_, 1, 1, views::GridLayout::LEADING,
475 views::GridLayout::CENTER);
476 section = CreateSection(l10n_util::GetStringUTF16(
477 IDS_PAGE_INFO_SITE_INFO_TITLE),
478 section_content,
479 NULL);
480 pane->AddChildView(section);
481
482 return pane;
483 }
484
485 views::View* WebsiteSettingsPopupView::CreateSection(
486 string16 headline_text,
487 views::View* content,
488 views::Link* link) {
489 views::View* container = new views::View();
490 views::GridLayout* layout = new views::GridLayout(container);
491 container->SetLayoutManager(layout);
492 const int content_column = 0;
493 views::ColumnSet* column_set = layout->AddColumnSet(content_column);
494 column_set->AddColumn(views::GridLayout::FILL,
495 views::GridLayout::FILL,
496 1,
497 views::GridLayout::USE_PREF,
498 0,
499 0);
500
501 layout->AddPaddingRow(1, 4);
502 layout->StartRow(1, content_column);
503 views::Label* headline = new views::Label(headline_text);
504 headline->SetFont(headline->font().DeriveFont(0, gfx::Font::BOLD));
505 layout->AddView(headline, 1, 1, views::GridLayout::LEADING,
506 views::GridLayout::CENTER);
507
508 layout->AddPaddingRow(1, 4);
509 layout->StartRow(1, content_column);
510 layout->AddView(content, 1, 1, views::GridLayout::LEADING,
511 views::GridLayout::CENTER);
512
513 if (link) {
514 layout->AddPaddingRow(1, 4);
515 layout->StartRow(1, content_column);
516 layout->AddView(link, 1, 1, views::GridLayout::LEADING,
517 views::GridLayout::CENTER);
518 }
519
520 return container;
521 }
522
523 views::View* WebsiteSettingsPopupView::CreatePermissionRow(
524 views::Label* label,
525 views::Combobox* combobox) {
526 views::View* container = new views::View();
527 views::GridLayout* layout = new views::GridLayout(container);
528 container->SetLayoutManager(layout);
529 const int two_column_layout = 0;
530 views::ColumnSet* column_set = layout->AddColumnSet(two_column_layout);
531 column_set->AddColumn(views::GridLayout::FILL,
532 views::GridLayout::FILL,
533 1,
534 views::GridLayout::USE_PREF,
535 0,
536 0);
537 column_set->AddPaddingColumn(0, 8);
538 column_set->AddColumn(views::GridLayout::FILL,
539 views::GridLayout::FILL,
540 1,
541 views::GridLayout::USE_PREF,
542 0,
543 0);
544
545 layout->StartRow(1, two_column_layout);
546 layout->AddView(label);
547 layout->AddView(combobox);
548 return container;
549 }
550
551 views::Label* WebsiteSettingsPopupView::CreateTextLabel(string16 text) {
552 views::Label* label = new views::Label(text);
553 label->SetMultiLine(true);
554 label->SetAllowCharacterBreak(true);
555 label->SetHorizontalAlignment(views::Label::ALIGN_LEFT);
556 return label;
557 }
558
Finnur 2012/05/30 11:20:04 Is there an extra space here at the end?
markusheintz_ 2012/06/05 17:02:12 Removed.
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698