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

Side by Side Diff: chrome/browser/ui/cocoa/toolbar/toolbar_controller.mm

Issue 1305143008: [Mac] Implement LocationBarViewMac::UpdateLocationBarVisibility() (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Addressing nits Created 5 years, 1 month 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 (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 #import "chrome/browser/ui/cocoa/toolbar/toolbar_controller.h" 5 #import "chrome/browser/ui/cocoa/toolbar/toolbar_controller.h"
6 6
7 #include <algorithm> 7 #include <algorithm>
8 8
9 #include "base/mac/bundle_locations.h" 9 #include "base/mac/bundle_locations.h"
10 #include "base/mac/foundation_util.h" 10 #include "base/mac/foundation_util.h"
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after
63 #include "ui/base/l10n/l10n_util_mac.h" 63 #include "ui/base/l10n/l10n_util_mac.h"
64 #include "ui/gfx/geometry/rect.h" 64 #include "ui/gfx/geometry/rect.h"
65 #include "ui/gfx/image/image.h" 65 #include "ui/gfx/image/image.h"
66 66
67 using content::OpenURLParams; 67 using content::OpenURLParams;
68 using content::Referrer; 68 using content::Referrer;
69 using content::WebContents; 69 using content::WebContents;
70 70
71 namespace { 71 namespace {
72 72
73 // Duration of the toolbar animation.
74 const NSTimeInterval kToolBarAnimationDuration = 0.12;
75
73 // Height of the toolbar in pixels when the bookmark bar is closed. 76 // Height of the toolbar in pixels when the bookmark bar is closed.
74 const CGFloat kBaseToolbarHeightNormal = 35.0; 77 const CGFloat kBaseToolbarHeightNormal = 35.0;
75 78
79 // Height of the location bar. Used for animating the toolbar in and out when
80 // the location bar is displayed stand-alone for bookmark apps.
81 const CGFloat kLocationBarHeight = 29.0;
82
76 // The padding above the toolbar elements. This is calculated from the values 83 // The padding above the toolbar elements. This is calculated from the values
77 // in Toolbar.xib: the height of the toolbar (35) minus the height of the child 84 // in Toolbar.xib: the height of the toolbar (35) minus the height of the child
78 // elements (29) minus the y-origin of the elements (4). 85 // elements (29) minus the y-origin of the elements (4).
79 const CGFloat kToolbarElementTopPadding = 2.0; 86 const CGFloat kToolbarElementTopPadding = 2.0;
80 87
81 // The minimum width of the location bar in pixels. 88 // The minimum width of the location bar in pixels.
82 const CGFloat kMinimumLocationBarWidth = 100.0; 89 const CGFloat kMinimumLocationBarWidth = 100.0;
83 90
84 // The amount of left padding that the wrench menu should have. 91 // The amount of left padding that the wrench menu should have.
85 const CGFloat kWrenchMenuLeftPadding = 3.0; 92 const CGFloat kWrenchMenuLeftPadding = 3.0;
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after
121 } 128 }
122 129
123 } // namespace 130 } // namespace
124 131
125 @interface ToolbarController() 132 @interface ToolbarController()
126 @property(assign, nonatomic) Browser* browser; 133 @property(assign, nonatomic) Browser* browser;
127 - (void)cleanUp; 134 - (void)cleanUp;
128 - (void)addAccessibilityDescriptions; 135 - (void)addAccessibilityDescriptions;
129 - (void)initCommandStatus:(CommandUpdater*)commands; 136 - (void)initCommandStatus:(CommandUpdater*)commands;
130 - (void)prefChanged:(const std::string&)prefName; 137 - (void)prefChanged:(const std::string&)prefName;
131 - (BackgroundGradientView*)backgroundGradientView; 138 - (ToolbarView*)toolbarView;
132 - (void)toolbarFrameChanged; 139 - (void)toolbarFrameChanged;
140 - (void)showLocationBarOnly;
133 - (void)pinLocationBarToLeftOfBrowserActionsContainerAndAnimate:(BOOL)animate; 141 - (void)pinLocationBarToLeftOfBrowserActionsContainerAndAnimate:(BOOL)animate;
134 - (void)maintainMinimumLocationBarWidth; 142 - (void)maintainMinimumLocationBarWidth;
135 - (void)adjustBrowserActionsContainerForNewWindow:(NSNotification*)notification; 143 - (void)adjustBrowserActionsContainerForNewWindow:(NSNotification*)notification;
136 - (void)browserActionsContainerDragged:(NSNotification*)notification; 144 - (void)browserActionsContainerDragged:(NSNotification*)notification;
137 - (void)browserActionsVisibilityChanged:(NSNotification*)notification; 145 - (void)browserActionsVisibilityChanged:(NSNotification*)notification;
138 - (void)browserActionsContainerWillAnimate:(NSNotification*)notification; 146 - (void)browserActionsContainerWillAnimate:(NSNotification*)notification;
139 - (void)adjustLocationSizeBy:(CGFloat)dX animate:(BOOL)animate; 147 - (void)adjustLocationSizeBy:(CGFloat)dX animate:(BOOL)animate;
140 - (void)updateWrenchButtonSeverity:(WrenchIconPainter::Severity)severity 148 - (void)updateWrenchButtonSeverity:(WrenchIconPainter::Severity)severity
141 animate:(BOOL)animate; 149 animate:(BOOL)animate;
142 @end 150 @end
(...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after
202 210
203 } // namespace ToolbarControllerInternal 211 } // namespace ToolbarControllerInternal
204 212
205 @implementation ToolbarController 213 @implementation ToolbarController
206 214
207 @synthesize browser = browser_; 215 @synthesize browser = browser_;
208 216
209 - (id)initWithCommands:(CommandUpdater*)commands 217 - (id)initWithCommands:(CommandUpdater*)commands
210 profile:(Profile*)profile 218 profile:(Profile*)profile
211 browser:(Browser*)browser 219 browser:(Browser*)browser
212 nibFileNamed:(NSString*)nibName { 220 resizeDelegate:(id<ViewResizer>)resizeDelegate {
213 DCHECK(commands && profile && [nibName length]); 221 DCHECK(commands && profile);
214 if ((self = [super initWithNibName:nibName 222 if ((self = [super initWithNibName:@"Toolbar"
215 bundle:base::mac::FrameworkBundle()])) { 223 bundle:base::mac::FrameworkBundle()])) {
216 commands_ = commands; 224 commands_ = commands;
217 profile_ = profile; 225 profile_ = profile;
218 browser_ = browser; 226 browser_ = browser;
219 hasToolbar_ = YES; 227 hasToolbar_ = YES;
220 hasLocationBar_ = YES; 228 hasLocationBar_ = YES;
221 229
222 // Register for notifications about state changes for the toolbar buttons 230 // Register for notifications about state changes for the toolbar buttons
223 commandObserver_.reset( 231 commandObserver_.reset(
224 new ToolbarControllerInternal::CommandObserverBridge(self)); 232 new ToolbarControllerInternal::CommandObserverBridge(self));
225 233
226 commands->AddCommandObserver(IDC_BACK, commandObserver_.get()); 234 commands->AddCommandObserver(IDC_BACK, commandObserver_.get());
227 commands->AddCommandObserver(IDC_FORWARD, commandObserver_.get()); 235 commands->AddCommandObserver(IDC_FORWARD, commandObserver_.get());
228 commands->AddCommandObserver(IDC_RELOAD, commandObserver_.get()); 236 commands->AddCommandObserver(IDC_RELOAD, commandObserver_.get());
229 commands->AddCommandObserver(IDC_HOME, commandObserver_.get()); 237 commands->AddCommandObserver(IDC_HOME, commandObserver_.get());
230 commands->AddCommandObserver(IDC_BOOKMARK_PAGE, commandObserver_.get()); 238 commands->AddCommandObserver(IDC_BOOKMARK_PAGE, commandObserver_.get());
231 // NOTE: Don't remove the command observers. ToolbarController is 239 // NOTE: Don't remove the command observers. ToolbarController is
232 // autoreleased at about the same time as the CommandUpdater (owned by the 240 // autoreleased at about the same time as the CommandUpdater (owned by the
233 // Browser), so |commands_| may not be valid any more. 241 // Browser), so |commands_| may not be valid any more.
234 }
235 return self;
236 }
237 242
238 - (id)initWithCommands:(CommandUpdater*)commands 243 [[self toolbarView] setResizeDelegate:resizeDelegate];
239 profile:(Profile*)profile 244
240 browser:(Browser*)browser {
241 if ((self = [self initWithCommands:commands
242 profile:profile
243 browser:browser
244 nibFileNamed:@"Toolbar"])) {
245 // Start global error services now so we badge the menu correctly. 245 // Start global error services now so we badge the menu correctly.
246 SyncGlobalErrorFactory::GetForProfile(profile); 246 SyncGlobalErrorFactory::GetForProfile(profile);
247 } 247 }
248 return self; 248 return self;
249 } 249 }
250 250
251 // Called after the view is done loading and the outlets have been hooked up. 251 // Called after the view is done loading and the outlets have been hooked up.
252 // Now we can hook up bridges that rely on UI objects such as the location bar 252 // Now we can hook up bridges that rely on UI objects such as the location bar
253 // and button state. -viewDidLoad is the recommended way to do this in 10.10 253 // and button state. -viewDidLoad is the recommended way to do this in 10.10
254 // SDK. When running on 10.10 or above -awakeFromNib still works but for some 254 // SDK. When running on 10.10 or above -awakeFromNib still works but for some
(...skipping 87 matching lines...) Expand 10 before | Expand all | Expand 10 after
342 // Create the controllers for the back/forward menus. 342 // Create the controllers for the back/forward menus.
343 backMenuController_.reset([[BackForwardMenuController alloc] 343 backMenuController_.reset([[BackForwardMenuController alloc]
344 initWithBrowser:browser_ 344 initWithBrowser:browser_
345 modelType:BACK_FORWARD_MENU_TYPE_BACK 345 modelType:BACK_FORWARD_MENU_TYPE_BACK
346 button:backButton_]); 346 button:backButton_]);
347 forwardMenuController_.reset([[BackForwardMenuController alloc] 347 forwardMenuController_.reset([[BackForwardMenuController alloc]
348 initWithBrowser:browser_ 348 initWithBrowser:browser_
349 modelType:BACK_FORWARD_MENU_TYPE_FORWARD 349 modelType:BACK_FORWARD_MENU_TYPE_FORWARD
350 button:forwardButton_]); 350 button:forwardButton_]);
351 351
352 // For a popup window, the toolbar is really just a location bar
353 // (see override for [ToolbarController view], below). When going
354 // fullscreen, we remove the toolbar controller's view from the view
355 // hierarchy. Calling [locationBar_ removeFromSuperview] when going
356 // fullscreen causes it to get released, making us unhappy
357 // (http://crbug.com/18551). We avoid the problem by incrementing
358 // the retain count of the location bar; use of the scoped object
359 // helps us remember to release it.
360 locationBarRetainer_.reset([locationBar_ retain]);
361 trackingArea_.reset( 352 trackingArea_.reset(
362 [[CrTrackingArea alloc] initWithRect:NSZeroRect // Ignored 353 [[CrTrackingArea alloc] initWithRect:NSZeroRect // Ignored
363 options:NSTrackingMouseMoved | 354 options:NSTrackingMouseMoved |
364 NSTrackingInVisibleRect | 355 NSTrackingInVisibleRect |
365 NSTrackingMouseEnteredAndExited | 356 NSTrackingMouseEnteredAndExited |
366 NSTrackingActiveAlways 357 NSTrackingActiveAlways
367 owner:self 358 owner:self
368 userInfo:nil]); 359 userInfo:nil]);
369 NSView* toolbarView = [self view]; 360 NSView* toolbarView = [self view];
370 [toolbarView addTrackingArea:trackingArea_.get()]; 361 [toolbarView addTrackingArea:trackingArea_.get()];
(...skipping 17 matching lines...) Expand all
388 379
389 [self addAccessibilityDescriptions]; 380 [self addAccessibilityDescriptions];
390 } 381 }
391 382
392 - (void)dealloc { 383 - (void)dealloc {
393 [self cleanUp]; 384 [self cleanUp];
394 [super dealloc]; 385 [super dealloc];
395 } 386 }
396 387
397 - (void)browserWillBeDestroyed { 388 - (void)browserWillBeDestroyed {
389 // Clear resize delegate so it doesn't get called during stopAnimation, and
390 // stop any in-flight animation.
391 [[self toolbarView] setResizeDelegate:nil];
392 [[self toolbarView] stopAnimation];
393
398 // Pass this call onto other reference counted objects. 394 // Pass this call onto other reference counted objects.
399 [backMenuController_ browserWillBeDestroyed]; 395 [backMenuController_ browserWillBeDestroyed];
400 [forwardMenuController_ browserWillBeDestroyed]; 396 [forwardMenuController_ browserWillBeDestroyed];
401 [browserActionsController_ browserWillBeDestroyed]; 397 [browserActionsController_ browserWillBeDestroyed];
402 [wrenchMenuController_ browserWillBeDestroyed]; 398 [wrenchMenuController_ browserWillBeDestroyed];
403 399
404 [self cleanUp]; 400 [self cleanUp];
405 } 401 }
406 402
407 - (void)cleanUp { 403 - (void)cleanUp {
(...skipping 172 matching lines...) Expand 10 before | Expand all | Expand 10 after
580 576
581 hasToolbar_ = toolbar; 577 hasToolbar_ = toolbar;
582 578
583 // If there's a toolbar, there must be a location bar. 579 // If there's a toolbar, there must be a location bar.
584 DCHECK((toolbar && locBar) || !toolbar); 580 DCHECK((toolbar && locBar) || !toolbar);
585 hasLocationBar_ = toolbar ? YES : locBar; 581 hasLocationBar_ = toolbar ? YES : locBar;
586 582
587 // Decide whether to hide/show based on whether there's a location bar. 583 // Decide whether to hide/show based on whether there's a location bar.
588 [[self view] setHidden:!hasLocationBar_]; 584 [[self view] setHidden:!hasLocationBar_];
589 585
590 // Make location bar not editable when in a pop-up. 586 // Make location bar not editable when in a pop-up or an app window.
591 locationBarView_->SetEditable(toolbar); 587 locationBarView_->SetEditable(toolbar);
588
589 // If necessary, resize the location bar and hide the toolbar icons to display
590 // the toolbar with only the location bar inside it.
591 if (!hasToolbar_ && hasLocationBar_)
592 [self showLocationBarOnly];
592 } 593 }
593 594
594 - (NSView*)view { 595 // (Private) Returns the backdrop to the toolbar as a ToolbarView.
595 if (hasToolbar_) 596 - (ToolbarView*)toolbarView{
596 return [super view]; 597 return base::mac::ObjCCastStrict<ToolbarView>([self view]);
597 return locationBar_;
598 }
599
600 // (Private) Returns the backdrop to the toolbar.
601 - (BackgroundGradientView*)backgroundGradientView {
602 // We really do mean |[super view]|; see our override of |-view|.
603 DCHECK([[super view] isKindOfClass:[BackgroundGradientView class]]);
604 return (BackgroundGradientView*)[super view];
605 } 598 }
606 599
607 - (id)customFieldEditorForObject:(id)obj { 600 - (id)customFieldEditorForObject:(id)obj {
608 if (obj == locationBar_) { 601 if (obj == locationBar_) {
609 // Lazilly construct Field editor, Cocoa UI code always runs on the 602 // Lazilly construct Field editor, Cocoa UI code always runs on the
610 // same thread, so there shoudn't be a race condition here. 603 // same thread, so there shoudn't be a race condition here.
611 if (autocompleteTextFieldEditor_.get() == nil) { 604 if (autocompleteTextFieldEditor_.get() == nil) {
612 autocompleteTextFieldEditor_.reset( 605 autocompleteTextFieldEditor_.reset(
613 [[AutocompleteTextFieldEditor alloc] init]); 606 [[AutocompleteTextFieldEditor alloc] init]);
614 } 607 }
(...skipping 104 matching lines...) Expand 10 before | Expand all | Expand 10 after
719 [[NSNotificationCenter defaultCenter] 712 [[NSNotificationCenter defaultCenter]
720 addObserver:self 713 addObserver:self
721 selector:@selector(adjustBrowserActionsContainerForNewWindow:) 714 selector:@selector(adjustBrowserActionsContainerForNewWindow:)
722 name:NSWindowDidBecomeKeyNotification 715 name:NSWindowDidBecomeKeyNotification
723 object:[[self view] window]]; 716 object:[[self view] window]];
724 } 717 }
725 if (![browserActionsContainerView_ isHidden]) 718 if (![browserActionsContainerView_ isHidden])
726 [self pinLocationBarToLeftOfBrowserActionsContainerAndAnimate:NO]; 719 [self pinLocationBarToLeftOfBrowserActionsContainerAndAnimate:NO];
727 } 720 }
728 721
722 - (void)updateVisibility:(BOOL)visible withAnimation:(BOOL)animate {
723 CGFloat newHeight = visible ? kLocationBarHeight : 0;
724
725 // Perform the animation, which will cause the BrowserWindowController to
726 // resize this view in the browser layout as required.
727 if (animate) {
728 [[self toolbarView] animateToNewHeight:newHeight
729 duration:kToolBarAnimationDuration];
730 } else {
731 [[self toolbarView] setHeight:newHeight];
732 }
733 }
734
729 - (void)adjustBrowserActionsContainerForNewWindow: 735 - (void)adjustBrowserActionsContainerForNewWindow:
730 (NSNotification*)notification { 736 (NSNotification*)notification {
731 [self toolbarFrameChanged]; 737 [self toolbarFrameChanged];
732 [[NSNotificationCenter defaultCenter] 738 [[NSNotificationCenter defaultCenter]
733 removeObserver:self 739 removeObserver:self
734 name:NSWindowDidBecomeKeyNotification 740 name:NSWindowDidBecomeKeyNotification
735 object:[[self view] window]]; 741 object:[[self view] window]];
736 } 742 }
737 743
738 - (void)browserActionsContainerDragged:(NSNotification*)notification { 744 - (void)browserActionsContainerDragged:(NSNotification*)notification {
(...skipping 83 matching lines...) Expand 10 before | Expand all | Expand 10 after
822 containerFrame = NSOffsetRect(containerFrame, 828 containerFrame = NSOffsetRect(containerFrame,
823 NSWidth(containerFrame) - savedContainerWidth, 0); 829 NSWidth(containerFrame) - savedContainerWidth, 0);
824 containerFrame.size.width = savedContainerWidth; 830 containerFrame.size.width = savedContainerWidth;
825 [browserActionsContainerView_ setGrippyPinned:NO]; 831 [browserActionsContainerView_ setGrippyPinned:NO];
826 } 832 }
827 [browserActionsContainerView_ setFrame:containerFrame]; 833 [browserActionsContainerView_ setFrame:containerFrame];
828 [self pinLocationBarToLeftOfBrowserActionsContainerAndAnimate:NO]; 834 [self pinLocationBarToLeftOfBrowserActionsContainerAndAnimate:NO];
829 } 835 }
830 } 836 }
831 837
838 // Hide the back, forward, reload, home, and wrench buttons of the toolbar.
839 // This allows the location bar to occupy the entire width. There is no way to
840 // undo this operation, and once it is called, no other programmatic changes
841 // to the toolbar or location bar width should be made. This message is
842 // invalid if the toolbar is shown or the location bar is hidden.
843 - (void)showLocationBarOnly {
844 // -showLocationBarOnly is only ever called once, shortly after
845 // initialization, so the regular buttons should all be visible.
846 DCHECK(!hasToolbar_ && hasLocationBar_);
847 DCHECK(![backButton_ isHidden]);
848
849 // Ensure the location bar fills the toolbar.
850 NSRect toolbarFrame = [[self view] frame];
851 toolbarFrame.size.height = kLocationBarHeight;
852 [[self view] setFrame:toolbarFrame];
853
854 [locationBar_ setFrame:NSMakeRect(0, 0, NSWidth([[self view] frame]),
855 kLocationBarHeight)];
856
857 [backButton_ setHidden:YES];
858 [forwardButton_ setHidden:YES];
859 [reloadButton_ setHidden:YES];
860 [wrenchButton_ setHidden:YES];
861 [homeButton_ setHidden:YES];
862 }
863
832 - (void)adjustLocationSizeBy:(CGFloat)dX animate:(BOOL)animate { 864 - (void)adjustLocationSizeBy:(CGFloat)dX animate:(BOOL)animate {
833 // Ensure that the location bar is in its proper place. 865 // Ensure that the location bar is in its proper place.
834 NSRect locationFrame = [locationBar_ frame]; 866 NSRect locationFrame = [locationBar_ frame];
835 locationFrame.size.width += dX; 867 locationFrame.size.width += dX;
836 868
837 [locationBar_ stopAnimation]; 869 [locationBar_ stopAnimation];
838 870
839 if (animate) 871 if (animate)
840 [locationBar_ animateToFrame:locationFrame]; 872 [locationBar_ animateToFrame:locationFrame];
841 else 873 else
(...skipping 22 matching lines...) Expand all
864 896
865 - (CGFloat)desiredHeightForCompression:(CGFloat)compressByHeight { 897 - (CGFloat)desiredHeightForCompression:(CGFloat)compressByHeight {
866 // With no toolbar, just ignore the compression. 898 // With no toolbar, just ignore the compression.
867 if (!hasToolbar_) 899 if (!hasToolbar_)
868 return NSHeight([locationBar_ frame]); 900 return NSHeight([locationBar_ frame]);
869 901
870 return kBaseToolbarHeightNormal - compressByHeight; 902 return kBaseToolbarHeightNormal - compressByHeight;
871 } 903 }
872 904
873 - (void)setDividerOpacity:(CGFloat)opacity { 905 - (void)setDividerOpacity:(CGFloat)opacity {
874 BackgroundGradientView* view = [self backgroundGradientView]; 906 ToolbarView* toolbarView = [self toolbarView];
875 [view setShowsDivider:(opacity > 0 ? YES : NO)]; 907 [toolbarView setShowsDivider:(opacity > 0 ? YES : NO)];
876 908 [toolbarView setDividerOpacity:opacity];
877 // We may not have a toolbar view (e.g., popup windows only have a location 909 [toolbarView setNeedsDisplay:YES];
878 // bar).
879 if ([view isKindOfClass:[ToolbarView class]]) {
880 ToolbarView* toolbarView = (ToolbarView*)view;
881 [toolbarView setDividerOpacity:opacity];
882 }
883
884 [view setNeedsDisplay:YES];
885 } 910 }
886 911
887 - (BrowserActionsController*)browserActionsController { 912 - (BrowserActionsController*)browserActionsController {
888 return browserActionsController_.get(); 913 return browserActionsController_.get();
889 } 914 }
890 915
891 - (NSView*)wrenchButton { 916 - (NSView*)wrenchButton {
892 return wrenchButton_; 917 return wrenchButton_;
893 } 918 }
894 919
(...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after
950 - (void)hideDropURLsIndicatorInView:(NSView*)view { 975 - (void)hideDropURLsIndicatorInView:(NSView*)view {
951 // Do nothing. 976 // Do nothing.
952 } 977 }
953 978
954 // (URLDropTargetController protocol) 979 // (URLDropTargetController protocol)
955 - (BOOL)isUnsupportedDropData:(id<NSDraggingInfo>)info { 980 - (BOOL)isUnsupportedDropData:(id<NSDraggingInfo>)info {
956 return drag_util::IsUnsupportedDropData(profile_, info); 981 return drag_util::IsUnsupportedDropData(profile_, info);
957 } 982 }
958 983
959 @end 984 @end
OLDNEW
« no previous file with comments | « chrome/browser/ui/cocoa/toolbar/toolbar_controller.h ('k') | chrome/browser/ui/cocoa/toolbar/toolbar_controller_unittest.mm » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698