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 #import "chrome/browser/ui/cocoa/custom_frame_view.h" 6 7 #import <Carbon/Carbon.h> 8 #include <crt_externs.h> 9 #import <objc/runtime.h> 10 #include <string.h> 11 12 #include "base/logging.h" 13 #include "base/mac/mac_util.h" 14 #include "base/mac/scoped_nsautorelease_pool.h" 15 16 namespace { 17 BOOL gCanDrawTitle = NO; 18 BOOL gCanGetCornerRadius = NO; 19 } // namespace 20 21 @interface NSView (Swizzles) 22 - (void)drawRectOriginal:(NSRect)rect; 23 - (NSPoint)_fullScreenButtonOriginOriginal; 24 @end 25 26 @interface NSWindow (FramedBrowserWindow) 27 - (NSPoint)fullScreenButtonOriginAdjustment; 28 @end 29 30 @implementation NSWindow (CustomFrameView) 31 - (void)drawCustomFrameRect:(NSRect)rect forView:(NSView*)view { 32 [view drawRectOriginal:rect]; 33 } 34 @end 35 36 @interface CustomFrameView : NSView 37 38 @end 39 40 @implementation CustomFrameView 41 42 // This is where we swizzle drawRect, and add in two methods that we 43 // need. If any of these fail it shouldn't affect the functionality of the 44 // others. If they all fail, we will lose window frame theming and 45 // roll overs for our close widgets, but things should still function 46 // correctly. 47 + (void)load { 48 // Swizzling should only happen in the browser process. Interacting with 49 // AppKit will run +[borderViewClass initialize] in the renderer, which 50 // may establish Mach IPC with com.apple.windowserver. 51 // Note that CommandLine has not been initialized yet, since this is running 52 // as a module initializer. 53 const char* const* const argv = *_NSGetArgv(); 54 const int argc = *_NSGetArgc(); 55 const char kType[] = "--type="; 56 for (int i = 1; i < argc; ++i) { 57 const char* arg = argv[i]; 58 if (strncmp(arg, kType, strlen(kType)) == 0) 59 return; 60 } 61 62 base::mac::ScopedNSAutoreleasePool pool; 63 64 // On 10.8+ the background for textured windows are no longer drawn by 65 // NSGrayFrame, and NSThemeFrame is used instead <http://crbug.com/114745>. 66 Class borderViewClass = NSClassFromString( 67 base::mac::IsOSMountainLionOrLater() ? @"NSThemeFrame" : @"NSGrayFrame"); 68 DCHECK(borderViewClass); 69 if (!borderViewClass) return; 70 71 // Exchange draw rect. 72 Method m0 = class_getInstanceMethod([self class], @selector(drawRect:)); 73 DCHECK(m0); 74 if (m0) { 75 BOOL didAdd = class_addMethod(borderViewClass, 76 @selector(drawRectOriginal:), 77 method_getImplementation(m0), 78 method_getTypeEncoding(m0)); 79 DCHECK(didAdd); 80 if (didAdd) { 81 Method m1 = class_getInstanceMethod(borderViewClass, 82 @selector(drawRect:)); 83 Method m2 = class_getInstanceMethod(borderViewClass, 84 @selector(drawRectOriginal:)); 85 DCHECK(m1 && m2); 86 if (m1 && m2) { 87 method_exchangeImplementations(m1, m2); 88 } 89 } 90 } 91 92 // In Yosemite, the fullscreen button replaces the zoom button. We no longer 93 // need to swizzle out this AppKit private method. 94 if (base::mac::IsOSMavericksOrEarlier()) { 95 // Swizzle the method that sets the origin for the Lion fullscreen button. 96 // Do nothing if it cannot be found. 97 m0 = class_getInstanceMethod([self class], 98 @selector(_fullScreenButtonOrigin)); 99 if (m0) { 100 BOOL didAdd = class_addMethod(borderViewClass, 101 @selector(_fullScreenButtonOriginOriginal), 102 method_getImplementation(m0), 103 method_getTypeEncoding(m0)); 104 if (didAdd) { 105 Method m1 = class_getInstanceMethod(borderViewClass, 106 @selector(_fullScreenButtonOrigin)); 107 Method m2 = class_getInstanceMethod( 108 borderViewClass, @selector(_fullScreenButtonOriginOriginal)); 109 if (m1 && m2) { 110 method_exchangeImplementations(m1, m2); 111 } 112 } 113 } 114 } 115 } 116 117 + (BOOL)canDrawTitle { 118 return gCanDrawTitle; 119 } 120 121 + (BOOL)canGetCornerRadius { 122 return gCanGetCornerRadius; 123 } 124 125 - (id)initWithFrame:(NSRect)frame { 126 // This class is not for instantiating. 127 [self doesNotRecognizeSelector:_cmd]; 128 return nil; 129 } 130 131 - (id)initWithCoder:(NSCoder*)coder { 132 // This class is not for instantiating. 133 [self doesNotRecognizeSelector:_cmd]; 134 return nil; 135 } 136 137 // Here is our custom drawing for our frame. 138 - (void)drawRect:(NSRect)rect { 139 // Delegate drawing to the window, whose default implementation (above) is to 140 // call into the original implementation. 141 [[self window] drawCustomFrameRect:rect forView:self]; 142 } 143 144 // Override to move the fullscreen button to the left of the profile avatar. 145 - (NSPoint)_fullScreenButtonOrigin { 146 NSWindow* window = [self window]; 147 NSPoint offset = NSZeroPoint; 148 149 if ([window respondsToSelector:@selector(fullScreenButtonOriginAdjustment)]) 150 offset = [window fullScreenButtonOriginAdjustment]; 151 152 NSPoint origin = [self _fullScreenButtonOriginOriginal]; 153 origin.x += offset.x; 154 origin.y += offset.y; 155 return origin; 156 } 157 158 @end 159