Commit 027233cd authored by tapted's avatar tapted Committed by Commit bot

Mac: Hack around Sierra autolayout bug in the certificate viewer.

Currently on macOS 10.12 Sierra, invoking the OS-provided SSL
certificate viewer used by Chrome will show a window sheet with an
improperly constrained height. Tracing shows the autolayout algorithm
going berserk - it gets invoked a few thousand times and decides that
the NSScrollView containing the certificate details should be sized as
though details are expanded, and doesn't add scrollbars. This usually
results in an attached panel window much larger than the screen height,
which is mostly unusable.

This happens when linking to the 10.10 SDK, but not the 10.11 SDK, and
only when running against Sierra's 10.12 SDK.

To fix, append an override of -[NSWindow setFrame:display:animate] to
the certificate viewer's class implementation using the Objective C
runtime. This override prevents programmatic changes to the frame from
setting a height more than 400 pixels. User-initiated resizes function
as usual.

BUG=643123
TEST=On macOS 10.12 Sierra, go to a secure site (e.g.
https://www.google.com) and view the SSL certificate (click padlock,
click Details, click View Certificate). It should fit within the screen
area. On other OSX versions, there should be no change.

Review-Url: https://codereview.chromium.org/2356113002
Cr-Commit-Position: refs/heads/master@{#420038}
parent 7461a5ad
......@@ -4,13 +4,78 @@
#import "chrome/browser/ui/certificate_viewer_mac.h"
#import <objc/runtime.h>
#include "base/mac/foundation_util.h"
#include "base/mac/mac_util.h"
#include "base/mac/scoped_cftyperef.h"
#import "base/mac/scoped_nsobject.h"
#include "content/public/browser/web_contents.h"
#include "net/cert/x509_certificate.h"
#include "net/cert/x509_util_mac.h"
namespace {
// The maximum height of the certificate panel. Imposed whenever Sierra's buggy
// autolayout algorithm tries to change it. Doesn't affect user resizes.
const CGFloat kMaxPanelSetFrameHeight = 400;
// Pointer to the real implementation of -[SFCertificatePanel setFrame:..]. This
// is cached so a correct lookup is performed before we add the override.
IMP g_real_certificate_panel_setframe = nullptr;
// Provide a workaround for a buggy autolayout algorithm in macOS Sierra when
// running Chrome linked against the 10.10 SDK on macOS 10.12.
// See http://crbug.com/643123 for more details.
// Note it's not possible to inherit from SFCertificatePanel without triggering
// *** Assertion failure in -[BrowserCrApplication
// _commonBeginModalSessionForWindow:relativeToWindow:modalDelegate:
// didEndSelector:contextInfo:], .../AppKit.subproj/NSApplication.m:4077
// After that assertion, the sheet simply refuses to continue loading.
// It's also not possible to swizzle the -setFrame: method because
// SFCertificatePanel doesn't define it. Attempting to swizzle that would
// replace the implementation on NSWindow and constrain all dialogs.
// So, provide a regular C method and append it to the SFCertificatePanel
// implementation using the objc runtime.
// TODO(tapted): Remove all of this when Chrome's SDK target gets bumped.
id SFCertificatePanelSetFrameOverride(id self,
SEL _cmd,
NSRect frame,
BOOL display,
BOOL animate) {
if (frame.size.height > kMaxPanelSetFrameHeight)
frame.size.height = kMaxPanelSetFrameHeight;
DCHECK(g_real_certificate_panel_setframe);
return g_real_certificate_panel_setframe(self, _cmd, frame, display, animate);
}
void MaybeConstrainPanelSizeForSierraBug() {
#if defined(MAC_OS_X_VERSION_10_11) && \
MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_11
// This is known not to be required when linking against the 10.11 SDK. Early
// exit in that case but assume anything earlier is broken.
return;
#endif
// It's also not required when running on El Capitan or earlier.
if (base::mac::IsAtMostOS10_11() || g_real_certificate_panel_setframe)
return;
const SEL kSetFrame = @selector(setFrame:display:animate:);
Method real_method =
class_getInstanceMethod([SFCertificatePanel class], kSetFrame);
const char* type_encoding = method_getTypeEncoding(real_method);
g_real_certificate_panel_setframe = method_getImplementation(real_method);
DCHECK(g_real_certificate_panel_setframe);
IMP method = reinterpret_cast<IMP>(&SFCertificatePanelSetFrameOverride);
BOOL method_added = class_addMethod([SFCertificatePanel class], kSetFrame,
method, type_encoding);
DCHECK(method_added);
}
} // namespace
@interface SFCertificatePanel (SystemPrivate)
// A system-private interface that dismisses a panel whose sheet was started by
// -beginSheetForWindow:
......@@ -88,6 +153,8 @@
}
- (void)showCertificateSheet:(NSWindow*)window {
MaybeConstrainPanelSizeForSierraBug();
[panel_ beginSheetForWindow:window
modalDelegate:self
didEndSelector:@selector(sheetDidEnd:returnCode:context:)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment