// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#import "chrome/browser/ui/cocoa/full_size_content_window.h"

#include <crt_externs.h>

#include "base/auto_reset.h"
#include "base/logging.h"
#include "base/mac/foundation_util.h"
#include "base/mac/scoped_objc_class_swizzler.h"

@interface FullSizeContentWindow ()

+ (BOOL)shouldUseFullSizeContentViewForStyle:(NSUInteger)windowStyle;

@end

// This view always takes the size of its superview. It is intended to be used
// as a NSWindow's contentView.  It is needed because NSWindow's implementation
// explicitly resizes the contentView at inopportune times.
@interface FullSizeContentView : NSView {
  BOOL forceFrameFlag_;
}

// This method allows us to set the content view size since setFrameSize is
// overridden to prevent the view from shrinking.
- (void)forceFrame:(NSRect)frame;

@end

@implementation FullSizeContentView

// This method is directly called by AppKit during a live window resize.
// Override it to prevent the content view from shrinking.
- (void)setFrameSize:(NSSize)size {
  if ([self superview] && !forceFrameFlag_)
    size = [[self superview] bounds].size;
  [super setFrameSize:size];
}

- (void)forceFrame:(NSRect)frame {
  forceFrameFlag_ = YES;
  [super setFrame:frame];
  forceFrameFlag_ = NO;
}

@end

static bool g_disable_callstacksymbols = false;
static IMP g_original_callstacksymbols_implementation;

@interface FullSizeContentWindowSwizzlingSupport : NSObject
@end

@implementation FullSizeContentWindowSwizzlingSupport

// This method replaces [NSThread callStackSymbols] via swizzling - see +load
// below.
+ (NSArray*)callStackSymbols {
  return g_disable_callstacksymbols ?
      @[@"+callStackSymbols disabled for performance reasons"] :
      g_original_callstacksymbols_implementation(
          self, @selector(callStackSymbols));
}

@end

@implementation FullSizeContentWindow

#pragma mark - Lifecycle

// In initWithContentRect:styleMask:backing:defer:, the call to
// [NSView addSubview:positioned:relativeTo:] causes NSWindow to complain that
// an unknown view is being added to it, and to generate a stack trace.
// Not only does this stack trace pollute the console, it can also take hundreds
// of milliseconds to generate (because of symbolication). By swizzling
// [NSThread callStackSymbols] we can prevent the stack trace output.
// See crbug.com/520373 .
+ (void)load {
  // Swizzling should only happen in the browser process.
  const char* const* const argv = *_NSGetArgv();
  const int argc = *_NSGetArgc();
  const char kType[] = "--type=";
  for (int i = 1; i < argc; ++i) {
    const char* arg = argv[i];
    if (strncmp(arg, kType, strlen(kType)) == 0) {
      return;
    }
  }

  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    Class targetClass = [NSThread class];
    Class swizzleClass = [FullSizeContentWindowSwizzlingSupport class];
    SEL targetSelector = @selector(callStackSymbols);

    CR_DEFINE_STATIC_LOCAL(base::mac::ScopedObjCClassSwizzler,
                           callStackSymbolsSuppressor, (targetClass,
                           swizzleClass, targetSelector));
    g_original_callstacksymbols_implementation =
        callStackSymbolsSuppressor.GetOriginalImplementation();
  });
}

- (instancetype)init {
  NOTREACHED();
  return nil;
}

- (instancetype)initWithContentRect:(NSRect)contentRect
                          styleMask:(NSUInteger)windowStyle
                            backing:(NSBackingStoreType)bufferingType
                              defer:(BOOL)deferCreation {
  return [self initWithContentRect:contentRect
                         styleMask:windowStyle
                           backing:bufferingType
                             defer:deferCreation
            wantsViewsOverTitlebar:NO];
}

- (instancetype)initWithContentRect:(NSRect)contentRect
                          styleMask:(NSUInteger)windowStyle
                            backing:(NSBackingStoreType)bufferingType
                              defer:(BOOL)deferCreation
             wantsViewsOverTitlebar:(BOOL)wantsViewsOverTitlebar {
  self = [super initWithContentRect:contentRect
                          styleMask:windowStyle
                            backing:bufferingType
                              defer:deferCreation];
  if (self) {
    if (wantsViewsOverTitlebar &&
        [FullSizeContentWindow
            shouldUseFullSizeContentViewForStyle:windowStyle]) {
      chromeWindowView_.reset([[FullSizeContentView alloc] init]);
      [chromeWindowView_
          setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
      [self setContentView:chromeWindowView_];
      [chromeWindowView_ setFrame:[[chromeWindowView_ superview] bounds]];

      // Our content view overlaps the window control buttons, so we must ensure
      // it is positioned below the buttons.
      NSView* superview = [chromeWindowView_ superview];
      [chromeWindowView_ removeFromSuperview];

      // Prevent the AppKit from generating a backtrace to include in it's
      // complaint about our upcoming call to addSubview:positioned:relativeTo:.
      // See +load for more info.
      base::AutoReset<bool> disable_symbolication(&g_disable_callstacksymbols,
                                                  true);

      [superview addSubview:chromeWindowView_
                 positioned:NSWindowBelow
                 relativeTo:nil];
    }
  }
  return self;
}

- (void)forceContentViewFrame:(NSRect)frame {
  if ([chromeWindowView_ isKindOfClass:[FullSizeContentView class]]) {
    FullSizeContentView* contentView =
        base::mac::ObjCCast<FullSizeContentView>(chromeWindowView_);
    [contentView forceFrame:frame];
  } else if (chromeWindowView_) {
    [chromeWindowView_ setFrame:frame];
  } else {
    [self.contentView setFrame:frame];
  }
}

#pragma mark - Private Methods

+ (BOOL)shouldUseFullSizeContentViewForStyle:(NSUInteger)windowStyle {
  return windowStyle & NSTitledWindowMask;
}

#pragma mark - NSWindow Overrides

+ (NSRect)frameRectForContentRect:(NSRect)cRect styleMask:(NSUInteger)aStyle {
  if ([self shouldUseFullSizeContentViewForStyle:aStyle])
    return cRect;
  return [super frameRectForContentRect:cRect styleMask:aStyle];
}

- (NSRect)frameRectForContentRect:(NSRect)contentRect {
  if (chromeWindowView_)
    return contentRect;
  return [super frameRectForContentRect:contentRect];
}

+ (NSRect)contentRectForFrameRect:(NSRect)fRect styleMask:(NSUInteger)aStyle {
  if ([self shouldUseFullSizeContentViewForStyle:aStyle])
    return fRect;
  return [super contentRectForFrameRect:fRect styleMask:aStyle];
}

- (NSRect)contentRectForFrameRect:(NSRect)frameRect {
  if (chromeWindowView_)
    return frameRect;
  return [super contentRectForFrameRect:frameRect];
}

@end