Home | History | Annotate | Download | only in cocoa
      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