Commit 74d2ba14 authored by Christopher Cameron's avatar Christopher Cameron Committed by Commit Bot

MacViews: Remove JavaScriptAppModalDialogCocoa

Move from using Cocoa to using Views. The Cocoa version was was causing
problems with app exit dialogs showing up in the wrong process in
RemoteMacViews. This issue was solved by the views version.

Bug: 898604
Change-Id: I30bd1e543fa98a1503d80b4ad3947f676eb5e0c6
Reviewed-on: https://chromium-review.googlesource.com/c/1298300
Commit-Queue: ccameron <ccameron@chromium.org>
Reviewed-by: default avatarElly Fong-Jones <ellyjones@chromium.org>
Cr-Commit-Position: refs/heads/master@{#605898}
parent cee3abc6
......@@ -1999,8 +1999,6 @@ jumbo_split_static_library("ui") {
"cocoa/history_menu_cocoa_controller.mm",
"cocoa/history_overlay_controller.h",
"cocoa/history_overlay_controller.mm",
"cocoa/javascript_app_modal_dialog_cocoa.h",
"cocoa/javascript_app_modal_dialog_cocoa.mm",
"cocoa/key_equivalent_constants.h",
"cocoa/key_equivalent_constants.mm",
"cocoa/keystone_infobar_delegate.h",
......@@ -2062,6 +2060,7 @@ jumbo_split_static_library("ui") {
"cocoa/window_size_autosaver.mm",
"views/apps/chrome_app_window_client_views_mac.mm",
"views/certificate_viewer_mac_views.mm",
"views/chrome_javascript_native_dialog_factory_views.cc",
"views/dropdown_bar_host_mac.mm",
"views/frame/browser_frame_mac.h",
"views/frame/browser_frame_mac.mm",
......
// Copyright (c) 2012 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.
#ifndef CHROME_BROWSER_UI_COCOA_JAVASCRIPT_APP_MODAL_DIALOG_COCOA_H_
#define CHROME_BROWSER_UI_COCOA_JAVASCRIPT_APP_MODAL_DIALOG_COCOA_H_
#include <memory>
#include "base/logging.h"
#include "base/mac/scoped_nsobject.h"
#include "base/macros.h"
#include "components/app_modal/native_app_modal_dialog.h"
class PopunderPreventer;
namespace app_modal {
class JavaScriptAppModalDialog;
}
#if __OBJC__
@class NSAlert;
@class JavaScriptAppModalDialogHelper;
#else
class NSAlert;
class JavaScriptAppModalDialogHelper;
#endif
class JavaScriptAppModalDialogCocoa : public app_modal::NativeAppModalDialog {
public:
explicit JavaScriptAppModalDialogCocoa(
app_modal::JavaScriptAppModalDialog* dialog);
~JavaScriptAppModalDialogCocoa() override;
// Overridden from NativeAppModalDialog:
int GetAppModalDialogButtons() const override;
void ShowAppModalDialog() override;
void ActivateAppModalDialog() override;
void CloseAppModalDialog() override;
void AcceptAppModalDialog() override;
void CancelAppModalDialog() override;
bool IsShowing() const override;
app_modal::JavaScriptAppModalDialog* dialog() const {
return dialog_.get();
}
private:
// Returns the NSAlert associated with the modal dialog.
NSAlert* GetAlert() const;
std::unique_ptr<app_modal::JavaScriptAppModalDialog> dialog_;
std::unique_ptr<PopunderPreventer> popunder_preventer_;
// Created in the constructor and destroyed in the destructor.
base::scoped_nsobject<JavaScriptAppModalDialogHelper> helper_;
bool is_showing_;
DISALLOW_COPY_AND_ASSIGN(JavaScriptAppModalDialogCocoa);
};
#endif // CHROME_BROWSER_UI_COCOA_JAVASCRIPT_APP_MODAL_DIALOG_COCOA_H_
// Copyright (c) 2012 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.
#include "chrome/browser/ui/cocoa/javascript_app_modal_dialog_cocoa.h"
#import <Cocoa/Cocoa.h>
#include <stddef.h>
#include "base/i18n/rtl.h"
#include "base/logging.h"
#import "base/mac/foundation_util.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/strings/sys_string_conversions.h"
#import "chrome/browser/chrome_browser_application_mac.h"
#include "chrome/browser/ui/blocked_content/popunder_preventer.h"
#include "chrome/browser/ui/javascript_dialogs/chrome_javascript_native_dialog_factory.h"
#include "components/app_modal/javascript_app_modal_dialog.h"
#include "components/app_modal/javascript_dialog_manager.h"
#include "components/app_modal/javascript_native_dialog_factory.h"
#include "components/strings/grit/components_strings.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_delegate.h"
#include "ui/base/l10n/l10n_util_mac.h"
#include "ui/base/ui_base_types.h"
#include "ui/gfx/text_elider.h"
#include "ui/strings/grit/ui_strings.h"
namespace {
const int kSlotsPerLine = 50;
const int kMessageTextMaxSlots = 2000;
} // namespace
// Helper object that receives the notification that the dialog/sheet is
// going away. Is responsible for cleaning itself up.
@interface JavaScriptAppModalDialogHelper : NSObject<NSAlertDelegate> {
@private
base::scoped_nsobject<NSAlert> alert_;
JavaScriptAppModalDialogCocoa* nativeDialog_; // Weak.
base::scoped_nsobject<NSTextField> textField_;
BOOL alertShown_;
}
// Creates an NSAlert if one does not already exist. Otherwise returns the
// existing NSAlert.
- (NSAlert*)alert;
- (void)addTextFieldWithPrompt:(NSString*)prompt;
// Presents an AppKit blocking dialog.
- (void)showAlert;
// Selects the first button of the alert, which should accept it.
- (void)acceptAlert;
// Selects the second button of the alert, which should cancel it.
- (void)cancelAlert;
// Closes the window, and the alert along with it.
- (void)closeWindow;
// Designated initializer.
- (instancetype)initWithNativeDialog:(JavaScriptAppModalDialogCocoa*)dialog;
@end
@implementation JavaScriptAppModalDialogHelper
- (instancetype)init {
NOTREACHED();
return nil;
}
- (instancetype)initWithNativeDialog:(JavaScriptAppModalDialogCocoa*)dialog {
DCHECK(dialog);
self = [super init];
if (self)
nativeDialog_ = dialog;
return self;
}
- (NSAlert*)alert {
if (!alert_) {
alert_.reset([[NSAlert alloc] init]);
if (!nativeDialog_->dialog()->is_before_unload_dialog()) {
// Set a blank icon for dialogs with text provided by the page.
// "onbeforeunload" dialogs don't have text provided by the page, so it's
// OK to use the app icon.
NSImage* image =
[[[NSImage alloc] initWithSize:NSMakeSize(1, 1)] autorelease];
[alert_ setIcon:image];
}
}
return alert_;
}
- (void)addTextFieldWithPrompt:(NSString*)prompt {
DCHECK(!textField_);
textField_.reset(
[[NSTextField alloc] initWithFrame:NSMakeRect(0, 0, 300, 22)]);
[[textField_ cell] setLineBreakMode:NSLineBreakByTruncatingTail];
[[self alert] setAccessoryView:textField_];
[[alert_ window] setInitialFirstResponder:textField_];
[textField_ setStringValue:prompt];
}
// |contextInfo| is the JavaScriptAppModalDialogCocoa that owns us.
- (void)alertDidEnd:(NSAlert*)alert
returnCode:(int)returnCode
contextInfo:(void*)contextInfo {
switch (returnCode) {
case NSAlertFirstButtonReturn: { // OK
[self sendAcceptToNativeDialog];
break;
}
case NSAlertSecondButtonReturn: { // Cancel
// If the user wants to stay on this page, stop quitting (if a quit is in
// progress).
[self sendCancelToNativeDialog];
break;
}
case NSRunStoppedResponse: { // Window was closed underneath us
// Need to call OnClose() because there is some cleanup that needs
// to be done. It won't call back to the javascript since the
// JavaScriptAppModalDialog knows that the WebContents was destroyed.
[self sendCloseToNativeDialog];
break;
}
default: {
NOTREACHED();
}
}
}
- (void)showAlert {
DCHECK(nativeDialog_);
DCHECK(!alertShown_);
alertShown_ = YES;
NSAlert* alert = [self alert];
[alert layout];
[[alert window] recalculateKeyViewLoop];
[alert beginSheetModalForWindow:nil // nil here makes it app-modal
modalDelegate:self
didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:)
contextInfo:NULL];
}
- (void)acceptAlert {
DCHECK(nativeDialog_);
if (!alertShown_) {
[self sendAcceptToNativeDialog];
return;
}
NSButton* first = [[[self alert] buttons] objectAtIndex:0];
[first performClick:nil];
}
- (void)cancelAlert {
DCHECK(nativeDialog_);
if (!alertShown_) {
[self sendCancelToNativeDialog];
return;
}
DCHECK_GE([[[self alert] buttons] count], 2U);
NSButton* second = [[[self alert] buttons] objectAtIndex:1];
[second performClick:nil];
}
- (void)closeWindow {
DCHECK(nativeDialog_);
if (!alertShown_) {
[self sendCloseToNativeDialog];
return;
}
[NSApp endSheet:[[self alert] window]];
}
- (void)sendAcceptToNativeDialog {
DCHECK(nativeDialog_);
nativeDialog_->dialog()->OnAccept([self input], [self shouldSuppress]);
[self destroyNativeDialog];
}
- (void)sendCancelToNativeDialog {
DCHECK(nativeDialog_);
// If the user wants to stay on this page, stop quitting (if a quit is in
// progress).
if (nativeDialog_->dialog()->is_before_unload_dialog())
chrome_browser_application_mac::CancelTerminate();
nativeDialog_->dialog()->OnCancel([self shouldSuppress]);
[self destroyNativeDialog];
}
- (void)sendCloseToNativeDialog {
DCHECK(nativeDialog_);
nativeDialog_->dialog()->OnClose();
[self destroyNativeDialog];
}
- (void)destroyNativeDialog {
DCHECK(nativeDialog_);
JavaScriptAppModalDialogCocoa* nativeDialog = nativeDialog_;
nativeDialog_ = nil; // Need to fail on DCHECK if something wrong happens.
delete nativeDialog; // Careful, this will delete us.
}
- (base::string16)input {
if (textField_)
return base::SysNSStringToUTF16([textField_ stringValue]);
return base::string16();
}
- (bool)shouldSuppress {
if ([[self alert] showsSuppressionButton])
return [[[self alert] suppressionButton] state] == NSOnState;
return false;
}
@end
////////////////////////////////////////////////////////////////////////////////
// JavaScriptAppModalDialogCocoa, public:
JavaScriptAppModalDialogCocoa::JavaScriptAppModalDialogCocoa(
app_modal::JavaScriptAppModalDialog* dialog)
: dialog_(dialog),
popunder_preventer_(new PopunderPreventer(dialog->web_contents())),
is_showing_(false) {
// Determine the names of the dialog buttons based on the flags. "Default"
// is the OK button. "Other" is the cancel button. We don't use the
// "Alternate" button in NSRunAlertPanel.
NSString* default_button = l10n_util::GetNSStringWithFixup(IDS_APP_OK);
NSString* other_button = l10n_util::GetNSStringWithFixup(IDS_APP_CANCEL);
bool text_field = false;
bool one_button = false;
switch (dialog_->javascript_dialog_type()) {
case content::JAVASCRIPT_DIALOG_TYPE_ALERT:
one_button = true;
break;
case content::JAVASCRIPT_DIALOG_TYPE_CONFIRM:
if (dialog_->is_before_unload_dialog()) {
if (dialog_->is_reload()) {
default_button = l10n_util::GetNSStringWithFixup(
IDS_BEFORERELOAD_MESSAGEBOX_OK_BUTTON_LABEL);
} else {
default_button = l10n_util::GetNSStringWithFixup(
IDS_BEFOREUNLOAD_MESSAGEBOX_OK_BUTTON_LABEL);
}
}
break;
case content::JAVASCRIPT_DIALOG_TYPE_PROMPT:
text_field = true;
break;
default:
NOTREACHED();
}
// Create a helper which will receive the sheet ended selector. It will
// delete itself when done.
helper_.reset(
[[JavaScriptAppModalDialogHelper alloc] initWithNativeDialog:this]);
// Show the modal dialog.
if (text_field) {
[helper_ addTextFieldWithPrompt:base::SysUTF16ToNSString(
dialog_->default_prompt_text())];
}
[GetAlert() setDelegate:helper_];
NSString* informative_text =
base::SysUTF16ToNSString(dialog_->message_text());
// Truncate long JS alerts - crbug.com/331219
NSCharacterSet* newline_char_set = [NSCharacterSet newlineCharacterSet];
for (size_t index = 0, slots_count = 0; index < informative_text.length;
++index) {
unichar current_char = [informative_text characterAtIndex:index];
if ([newline_char_set characterIsMember:current_char])
slots_count += kSlotsPerLine;
else
slots_count++;
if (slots_count > kMessageTextMaxSlots) {
base::string16 info_text = base::SysNSStringToUTF16(informative_text);
informative_text = base::SysUTF16ToNSString(
gfx::TruncateString(info_text, index, gfx::WORD_BREAK));
break;
}
}
[GetAlert() setInformativeText:informative_text];
NSString* message_text =
base::SysUTF16ToNSString(dialog_->title());
[GetAlert() setMessageText:message_text];
[GetAlert() addButtonWithTitle:default_button];
if (!one_button) {
NSButton* other = [GetAlert() addButtonWithTitle:other_button];
[other setKeyEquivalent:@"\e"];
}
if (dialog_->display_suppress_checkbox()) {
[GetAlert() setShowsSuppressionButton:YES];
NSString* suppression_title = l10n_util::GetNSStringWithFixup(
IDS_JAVASCRIPT_MESSAGEBOX_SUPPRESS_OPTION);
[[GetAlert() suppressionButton] setTitle:suppression_title];
}
// Fix RTL dialogs.
//
// Mac OS X will always display NSAlert strings as LTR. A workaround is to
// manually set the text as attributed strings in the implementing
// NSTextFields. This is a basic correctness issue.
//
// In addition, for readability, the overall alignment is set based on the
// directionality of the first strongly-directional character.
//
// If the dialog fields are selectable then they will scramble when clicked.
// Therefore, selectability is disabled.
//
// See http://crbug.com/70806 for more details.
bool message_has_rtl =
base::i18n::StringContainsStrongRTLChars(dialog_->title());
bool informative_has_rtl =
base::i18n::StringContainsStrongRTLChars(dialog_->message_text());
NSTextField* message_text_field = nil;
NSTextField* informative_text_field = nil;
if (message_has_rtl || informative_has_rtl) {
// Force layout of the dialog. NSAlert leaves its dialog alone once laid
// out; if this is not done then all the modifications that are to come will
// be un-done when the dialog is finally displayed.
[GetAlert() layout];
// Locate the NSTextFields that implement the text display. These are
// actually available as the ivars |_messageField| and |_informationField|
// of the NSAlert, but it is safer (and more forward-compatible) to search
// for them in the subviews.
for (NSView* view in [[[GetAlert() window] contentView] subviews]) {
NSTextField* text_field = base::mac::ObjCCast<NSTextField>(view);
if ([[text_field stringValue] isEqualTo:message_text])
message_text_field = text_field;
else if ([[text_field stringValue] isEqualTo:informative_text])
informative_text_field = text_field;
}
// This may fail in future OS releases, but it will still work for shipped
// versions of Chromium.
DCHECK(message_text_field);
DCHECK(informative_text_field);
}
if (message_has_rtl && message_text_field) {
base::scoped_nsobject<NSMutableParagraphStyle> alignment(
[[NSParagraphStyle defaultParagraphStyle] mutableCopy]);
[alignment setAlignment:NSRightTextAlignment];
NSDictionary* alignment_attributes =
@{ NSParagraphStyleAttributeName : alignment };
base::scoped_nsobject<NSAttributedString> attr_string(
[[NSAttributedString alloc] initWithString:message_text
attributes:alignment_attributes]);
[message_text_field setAttributedStringValue:attr_string];
[message_text_field setSelectable:NO];
}
if (informative_has_rtl && informative_text_field) {
base::i18n::TextDirection direction =
base::i18n::GetFirstStrongCharacterDirection(dialog_->message_text());
base::scoped_nsobject<NSMutableParagraphStyle> alignment(
[[NSParagraphStyle defaultParagraphStyle] mutableCopy]);
[alignment setAlignment:
(direction == base::i18n::RIGHT_TO_LEFT) ? NSRightTextAlignment
: NSLeftTextAlignment];
NSDictionary* alignment_attributes =
@{ NSParagraphStyleAttributeName : alignment };
base::scoped_nsobject<NSAttributedString> attr_string(
[[NSAttributedString alloc] initWithString:informative_text
attributes:alignment_attributes]);
[informative_text_field setAttributedStringValue:attr_string];
[informative_text_field setSelectable:NO];
}
}
JavaScriptAppModalDialogCocoa::~JavaScriptAppModalDialogCocoa() {
[NSObject cancelPreviousPerformRequestsWithTarget:helper_.get()];
}
////////////////////////////////////////////////////////////////////////////////
// JavaScriptAppModalDialogCocoa, private:
NSAlert* JavaScriptAppModalDialogCocoa::GetAlert() const {
return [helper_ alert];
}
////////////////////////////////////////////////////////////////////////////////
// JavaScriptAppModalDialogCocoa, NativeAppModalDialog implementation:
int JavaScriptAppModalDialogCocoa::GetAppModalDialogButtons() const {
// From the above, it is the case that if there is 1 button, it is always the
// OK button. The second button, if it exists, is always the Cancel button.
int num_buttons = [[GetAlert() buttons] count];
switch (num_buttons) {
case 1:
return ui::DIALOG_BUTTON_OK;
case 2:
return ui::DIALOG_BUTTON_OK | ui::DIALOG_BUTTON_CANCEL;
default:
NOTREACHED();
return 0;
}
}
void JavaScriptAppModalDialogCocoa::ShowAppModalDialog() {
is_showing_ = true;
// Dispatch the method to show the alert back to the top of the CFRunLoop.
// This fixes an interaction bug with NSSavePanel. http://crbug.com/375785
// When this object is destroyed, outstanding performSelector: requests
// should be cancelled.
[helper_.get() performSelector:@selector(showAlert)
withObject:nil
afterDelay:0];
}
void JavaScriptAppModalDialogCocoa::ActivateAppModalDialog() {
}
void JavaScriptAppModalDialogCocoa::CloseAppModalDialog() {
[helper_ closeWindow];
}
void JavaScriptAppModalDialogCocoa::AcceptAppModalDialog() {
[helper_ acceptAlert];
}
void JavaScriptAppModalDialogCocoa::CancelAppModalDialog() {
[helper_ cancelAlert];
}
bool JavaScriptAppModalDialogCocoa::IsShowing() const {
return is_showing_;
}
namespace {
class ChromeJavaScriptNativeDialogCocoaFactory
: public app_modal::JavaScriptNativeDialogFactory {
public:
ChromeJavaScriptNativeDialogCocoaFactory() {}
~ChromeJavaScriptNativeDialogCocoaFactory() override {}
private:
app_modal::NativeAppModalDialog* CreateNativeJavaScriptDialog(
app_modal::JavaScriptAppModalDialog* dialog) override {
app_modal::NativeAppModalDialog* d =
new JavaScriptAppModalDialogCocoa(dialog);
dialog->web_contents()->GetDelegate()->ActivateContents(
dialog->web_contents());
return d;
}
DISALLOW_COPY_AND_ASSIGN(ChromeJavaScriptNativeDialogCocoaFactory);
};
} // namespace
void InstallChromeJavaScriptNativeDialogFactory() {
app_modal::JavaScriptDialogManager::GetInstance()->SetNativeDialogFactory(
base::WrapUnique(new ChromeJavaScriptNativeDialogCocoaFactory));
}
......@@ -302,13 +302,11 @@ void NativeWidgetMac::InitModalType(ui::ModalType modal_type) {
if (modal_type == ui::MODAL_TYPE_NONE)
return;
// System modal windows not implemented (or used) on Mac.
DCHECK_NE(ui::MODAL_TYPE_SYSTEM, modal_type);
// A peculiarity of the constrained window framework is that it permits a
// dialog of MODAL_TYPE_WINDOW to have a null parent window; falling back to
// a non-modal window in this case.
DCHECK(bridge_host_->parent() || modal_type == ui::MODAL_TYPE_WINDOW);
DCHECK(bridge_host_->parent() || modal_type == ui::MODAL_TYPE_WINDOW ||
modal_type == ui::MODAL_TYPE_SYSTEM);
// Everything happens upon show.
}
......
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