mirror of
https://bitbucket.org/chromiumembedded/cef
synced 2024-12-13 01:56:20 +01:00
0b99f92e97
A modal dialog is a child CefWindow that implements some special behaviors relative to a parent CefWindow. Like any CefWindow it can be framed with titlebar or frameless, and optionally contain draggable regions (subject to platform limitations described below). Modal dialogs are shown centered on the parent window (inside a single display) and always stay on top of the parent window in z-order. Sizing behavior and available window buttons are controlled via the usual CefWindowDelegate callbacks. For example, the dialog can have a preferred size with resize, minimize and maximize disabled (via GetPreferredSize, CanResize, CanMinimize and CanMaximize respectively). This change adds support for two modality modes. With window modality all controls in the parent window are disabled. With browser modality only the browser view in the parent window is disabled. Both modality modes require that a valid parent window be returned via GetParentWindow. For window modality return true from IsWindowModalDialog and call CefWindow::Show. For browser modality return false from IsWindowModalDialog (the default value) and call CefWindow::ShowAsBrowserModalDialog with a reference to the parent window's browser view. Window modal dialog behavior depends on the platform. On Windows and Linux these dialogs have a titlebar and can be moved independent of the parent window. On macOS these dialogs do not have a titlebar, move with the parent window, and do not support draggable regions (because they are implemented using sheets). On Linux disabling the parent window controls requires a window manager the supports _NET_WM_STATE_MODAL. Browser modal dialog behavior is similar on all platforms. The dialog will be automatically sized and positioned relative to the parent window's browser view. Closing the parent window or navigating the parent browser view will dismiss the dialog. The dialog can also be moved independent of the parent window though it will be recentered when the parent window itself is resized or redisplayed. On MacOS the dialog will move along with the parent window while on Windows and Linux the parent window can be moved independently. To test: Use the Tests > Dialog Window menu option in cefclient with Views enabled (`--use-views` or `--enable-chrome-runtime` command-line flag). Browser modal dialog is the default behavior. For window modal dialog add the `--use-window-modal-dialog` command-line flag.
612 lines
21 KiB
Plaintext
612 lines
21 KiB
Plaintext
// Copyright (c) 2013 The Chromium Embedded Framework Authors.
|
|
// Portions copyright (c) 2010 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 <Cocoa/Cocoa.h>
|
|
#include "include/cef_app.h"
|
|
#import "include/cef_application_mac.h"
|
|
#include "include/cef_command_ids.h"
|
|
#import "include/wrapper/cef_library_loader.h"
|
|
#include "tests/cefclient/browser/main_context_impl.h"
|
|
#include "tests/cefclient/browser/resource.h"
|
|
#include "tests/cefclient/browser/root_window.h"
|
|
#include "tests/cefclient/browser/test_runner.h"
|
|
#include "tests/shared/browser/client_app_browser.h"
|
|
#include "tests/shared/browser/main_message_loop_external_pump.h"
|
|
#include "tests/shared/browser/main_message_loop_std.h"
|
|
#include "tests/shared/common/client_switches.h"
|
|
|
|
namespace {
|
|
|
|
// Returns the top menu bar with the specified |tag|.
|
|
NSMenuItem* GetMenuBarMenuWithTag(NSInteger tag) {
|
|
NSMenu* main_menu = [[NSApplication sharedApplication] mainMenu];
|
|
NSInteger found_index = [main_menu indexOfItemWithTag:tag];
|
|
if (found_index >= 0) {
|
|
return [main_menu itemAtIndex:found_index];
|
|
}
|
|
return nil;
|
|
}
|
|
|
|
// Returns the item in |menu| that has the specified |action_selector|.
|
|
NSMenuItem* GetMenuItemWithAction(NSMenu* menu, SEL action_selector) {
|
|
for (NSInteger i = 0; i < menu.numberOfItems; ++i) {
|
|
NSMenuItem* item = [menu itemAtIndex:i];
|
|
if (item.action == action_selector) {
|
|
return item;
|
|
}
|
|
}
|
|
return nil;
|
|
}
|
|
|
|
void RemoveMenuItem(NSMenu* menu, SEL action_selector) {
|
|
NSMenuItem* item = GetMenuItemWithAction(menu, action_selector);
|
|
if (item) {
|
|
[menu removeItem:item];
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
// Receives notifications from the application. Will delete itself when done.
|
|
@interface ClientAppDelegate : NSObject <NSApplicationDelegate> {
|
|
@private
|
|
bool with_controls_;
|
|
bool with_osr_;
|
|
}
|
|
|
|
- (id)initWithControls:(bool)with_controls andOsr:(bool)with_osr;
|
|
- (void)createApplication:(id)object;
|
|
- (void)tryToTerminateApplication:(NSApplication*)app;
|
|
- (void)testsItemSelected:(int)command_id;
|
|
- (IBAction)menuTestsGetText:(id)sender;
|
|
- (IBAction)menuTestsGetSource:(id)sender;
|
|
- (IBAction)menuTestsWindowNew:(id)sender;
|
|
- (IBAction)menuTestsWindowPopup:(id)sender;
|
|
- (IBAction)menuTestsWindowDialog:(id)sender;
|
|
- (IBAction)menuTestsRequest:(id)sender;
|
|
- (IBAction)menuTestsZoomIn:(id)sender;
|
|
- (IBAction)menuTestsZoomOut:(id)sender;
|
|
- (IBAction)menuTestsZoomReset:(id)sender;
|
|
- (IBAction)menuTestsSetFPS:(id)sender;
|
|
- (IBAction)menuTestsSetScaleFactor:(id)sender;
|
|
- (IBAction)menuTestsTracingBegin:(id)sender;
|
|
- (IBAction)menuTestsTracingEnd:(id)sender;
|
|
- (IBAction)menuTestsPrint:(id)sender;
|
|
- (IBAction)menuTestsPrintToPdf:(id)sender;
|
|
- (IBAction)menuTestsMuteAudio:(id)sender;
|
|
- (IBAction)menuTestsUnmuteAudio:(id)sender;
|
|
- (IBAction)menuTestsOtherTests:(id)sender;
|
|
- (void)enableAccessibility:(bool)bEnable;
|
|
@end
|
|
|
|
// Provide the CefAppProtocol implementation required by CEF.
|
|
@interface ClientApplication : NSApplication <CefAppProtocol> {
|
|
@private
|
|
BOOL handlingSendEvent_;
|
|
}
|
|
@end
|
|
|
|
@implementation ClientApplication
|
|
|
|
- (BOOL)isHandlingSendEvent {
|
|
return handlingSendEvent_;
|
|
}
|
|
|
|
- (void)setHandlingSendEvent:(BOOL)handlingSendEvent {
|
|
handlingSendEvent_ = handlingSendEvent;
|
|
}
|
|
|
|
- (void)sendEvent:(NSEvent*)event {
|
|
CefScopedSendingEvent sendingEventScoper;
|
|
[super sendEvent:event];
|
|
}
|
|
|
|
// |-terminate:| is the entry point for orderly "quit" operations in Cocoa. This
|
|
// includes the application menu's quit menu item and keyboard equivalent, the
|
|
// application's dock icon menu's quit menu item, "quit" (not "force quit") in
|
|
// the Activity Monitor, and quits triggered by user logout and system restart
|
|
// and shutdown.
|
|
//
|
|
// The default |-terminate:| implementation ends the process by calling exit(),
|
|
// and thus never leaves the main run loop. This is unsuitable for Chromium
|
|
// since Chromium depends on leaving the main run loop to perform an orderly
|
|
// shutdown. We support the normal |-terminate:| interface by overriding the
|
|
// default implementation. Our implementation, which is very specific to the
|
|
// needs of Chromium, works by asking the application delegate to terminate
|
|
// using its |-tryToTerminateApplication:| method.
|
|
//
|
|
// |-tryToTerminateApplication:| differs from the standard
|
|
// |-applicationShouldTerminate:| in that no special event loop is run in the
|
|
// case that immediate termination is not possible (e.g., if dialog boxes
|
|
// allowing the user to cancel have to be shown). Instead, this method tries to
|
|
// close all browsers by calling CloseBrowser(false) via
|
|
// ClientHandler::CloseAllBrowsers. Calling CloseBrowser will result in a call
|
|
// to ClientHandler::DoClose and execution of |-performClose:| on the NSWindow.
|
|
// DoClose sets a flag that is used to differentiate between new close events
|
|
// (e.g., user clicked the window close button) and in-progress close events
|
|
// (e.g., user approved the close window dialog). The NSWindowDelegate
|
|
// |-windowShouldClose:| method checks this flag and either calls
|
|
// CloseBrowser(false) in the case of a new close event or destructs the
|
|
// NSWindow in the case of an in-progress close event.
|
|
// ClientHandler::OnBeforeClose will be called after the CEF NSView hosted in
|
|
// the NSWindow is dealloc'ed.
|
|
//
|
|
// After the final browser window has closed ClientHandler::OnBeforeClose will
|
|
// begin actual tear-down of the application by calling CefQuitMessageLoop.
|
|
// This ends the NSApplication event loop and execution then returns to the
|
|
// main() function for cleanup before application termination.
|
|
//
|
|
// The standard |-applicationShouldTerminate:| is not supported, and code paths
|
|
// leading to it must be redirected.
|
|
- (void)terminate:(id)sender {
|
|
ClientAppDelegate* delegate = static_cast<ClientAppDelegate*>(
|
|
[[NSApplication sharedApplication] delegate]);
|
|
[delegate tryToTerminateApplication:self];
|
|
// Return, don't exit. The application is responsible for exiting on its own.
|
|
}
|
|
|
|
// Detect dynamically if VoiceOver is running. Like Chromium, rely upon the
|
|
// undocumented accessibility attribute @"AXEnhancedUserInterface" which is set
|
|
// when VoiceOver is launched and unset when VoiceOver is closed.
|
|
- (void)accessibilitySetValue:(id)value forAttribute:(NSString*)attribute {
|
|
if ([attribute isEqualToString:@"AXEnhancedUserInterface"]) {
|
|
ClientAppDelegate* delegate = static_cast<ClientAppDelegate*>(
|
|
[[NSApplication sharedApplication] delegate]);
|
|
[delegate enableAccessibility:([value intValue] == 1)];
|
|
}
|
|
return [super accessibilitySetValue:value forAttribute:attribute];
|
|
}
|
|
@end
|
|
|
|
@implementation ClientAppDelegate
|
|
|
|
- (id)initWithControls:(bool)with_controls andOsr:(bool)with_osr {
|
|
if (self = [super init]) {
|
|
with_controls_ = with_controls;
|
|
with_osr_ = with_osr;
|
|
}
|
|
return self;
|
|
}
|
|
|
|
// Create the application on the UI thread.
|
|
- (void)createApplication:(id)object {
|
|
NSApplication* application = [NSApplication sharedApplication];
|
|
|
|
// The top menu is configured using Interface Builder (IB). To modify the menu
|
|
// start by loading MainMenu.xib in IB.
|
|
//
|
|
// To associate MainMenu.xib with ClientAppDelegate:
|
|
// 1. Select "File's Owner" from the "Placeholders" section in the left side
|
|
// pane.
|
|
// 2. Load the "Identity inspector" tab in the top-right side pane.
|
|
// 3. In the "Custom Class" section set the "Class" value to
|
|
// "ClientAppDelegate".
|
|
// 4. Pass an instance of ClientAppDelegate as the |owner| parameter to
|
|
// loadNibNamed:.
|
|
//
|
|
// To create a new top menu:
|
|
// 1. Load the "Object library" tab in the bottom-right side pane.
|
|
// 2. Drag a "Submenu Menu Item" widget from the Object library to the desired
|
|
// location in the menu bar shown in the center pane.
|
|
// 3. Select the newly created top menu by left clicking on it.
|
|
// 4. Load the "Attributes inspector" tab in the top-right side pane.
|
|
// 5. Under the "Menu Item" section set the "Tag" value to a unique integer.
|
|
// This is necessary for the GetMenuBarMenuWithTag function to work
|
|
// properly.
|
|
//
|
|
// To create a new menu item in a top menu:
|
|
// 1. Add a new receiver method in ClientAppDelegate (e.g. menuTestsDoStuff:).
|
|
// 2. Load the "Object library" tab in the bottom-right side pane.
|
|
// 3. Drag a "Menu Item" widget from the Object library to the desired
|
|
// location in the menu bar shown in the center pane.
|
|
// 4. Double-click on the new menu item to set the label.
|
|
// 5. Right click on the new menu item to show the "Get Source" dialog.
|
|
// 6. In the "Sent Actions" section drag from the circle icon and drop on the
|
|
// new receiver method in the ClientAppDelegate source code file.
|
|
//
|
|
// Load the top menu from MainMenu.xib.
|
|
[[NSBundle mainBundle] loadNibNamed:@"MainMenu"
|
|
owner:self
|
|
topLevelObjects:nil];
|
|
|
|
// Set the delegate for application events.
|
|
[application setDelegate:self];
|
|
|
|
auto* main_context = client::MainContext::Get();
|
|
|
|
NSMenuItem* tests_menu = GetMenuBarMenuWithTag(8);
|
|
if (tests_menu) {
|
|
if (!with_osr_) {
|
|
// Remove the OSR-related menu items when not using OSR.
|
|
RemoveMenuItem(tests_menu.submenu, @selector(menuTestsSetFPS:));
|
|
RemoveMenuItem(tests_menu.submenu, @selector(menuTestsSetScaleFactor:));
|
|
}
|
|
if (!main_context->UseViews()) {
|
|
// Remove the Views-related menu items when not using Views.
|
|
RemoveMenuItem(tests_menu.submenu, @selector(menuTestsWindowDialog:));
|
|
}
|
|
}
|
|
|
|
auto window_config = std::make_unique<client::RootWindowConfig>();
|
|
window_config->with_controls = with_controls_;
|
|
window_config->with_osr = with_osr_;
|
|
|
|
// Create the first window.
|
|
main_context->GetRootWindowManager()->CreateRootWindow(
|
|
std::move(window_config));
|
|
}
|
|
|
|
- (void)tryToTerminateApplication:(NSApplication*)app {
|
|
client::MainContext::Get()->GetRootWindowManager()->CloseAllWindows(false);
|
|
}
|
|
|
|
- (void)orderFrontStandardAboutPanel:(id)sender {
|
|
[[NSApplication sharedApplication] orderFrontStandardAboutPanel:nil];
|
|
}
|
|
|
|
- (void)testsItemSelected:(int)command_id {
|
|
if (auto browser = [self getActiveBrowser]) {
|
|
client::test_runner::RunTest(browser, command_id);
|
|
}
|
|
}
|
|
|
|
- (IBAction)menuTestsGetText:(id)sender {
|
|
[self testsItemSelected:ID_TESTS_GETTEXT];
|
|
}
|
|
|
|
- (IBAction)menuTestsGetSource:(id)sender {
|
|
[self testsItemSelected:ID_TESTS_GETSOURCE];
|
|
}
|
|
|
|
- (IBAction)menuTestsWindowNew:(id)sender {
|
|
[self testsItemSelected:ID_TESTS_WINDOW_NEW];
|
|
}
|
|
|
|
- (IBAction)menuTestsWindowPopup:(id)sender {
|
|
[self testsItemSelected:ID_TESTS_WINDOW_POPUP];
|
|
}
|
|
|
|
- (IBAction)menuTestsWindowDialog:(id)sender {
|
|
[self testsItemSelected:ID_TESTS_WINDOW_DIALOG];
|
|
}
|
|
|
|
- (IBAction)menuTestsRequest:(id)sender {
|
|
[self testsItemSelected:ID_TESTS_REQUEST];
|
|
}
|
|
|
|
- (IBAction)menuTestsZoomIn:(id)sender {
|
|
[self testsItemSelected:ID_TESTS_ZOOM_IN];
|
|
}
|
|
|
|
- (IBAction)menuTestsZoomOut:(id)sender {
|
|
[self testsItemSelected:ID_TESTS_ZOOM_OUT];
|
|
}
|
|
|
|
- (IBAction)menuTestsZoomReset:(id)sender {
|
|
[self testsItemSelected:ID_TESTS_ZOOM_RESET];
|
|
}
|
|
|
|
- (IBAction)menuTestsSetFPS:(id)sender {
|
|
[self testsItemSelected:ID_TESTS_OSR_FPS];
|
|
}
|
|
|
|
- (IBAction)menuTestsSetScaleFactor:(id)sender {
|
|
[self testsItemSelected:ID_TESTS_OSR_DSF];
|
|
}
|
|
|
|
- (IBAction)menuTestsTracingBegin:(id)sender {
|
|
[self testsItemSelected:ID_TESTS_TRACING_BEGIN];
|
|
}
|
|
|
|
- (IBAction)menuTestsTracingEnd:(id)sender {
|
|
[self testsItemSelected:ID_TESTS_TRACING_END];
|
|
}
|
|
|
|
- (IBAction)menuTestsPrint:(id)sender {
|
|
[self testsItemSelected:ID_TESTS_PRINT];
|
|
}
|
|
|
|
- (IBAction)menuTestsPrintToPdf:(id)sender {
|
|
[self testsItemSelected:ID_TESTS_PRINT_TO_PDF];
|
|
}
|
|
|
|
- (IBAction)menuTestsMuteAudio:(id)sender {
|
|
[self testsItemSelected:ID_TESTS_MUTE_AUDIO];
|
|
}
|
|
|
|
- (IBAction)menuTestsUnmuteAudio:(id)sender {
|
|
[self testsItemSelected:ID_TESTS_UNMUTE_AUDIO];
|
|
}
|
|
|
|
- (IBAction)menuTestsOtherTests:(id)sender {
|
|
[self testsItemSelected:ID_TESTS_OTHER_TESTS];
|
|
}
|
|
|
|
- (CefRefPtr<CefBrowser>)getActiveBrowser {
|
|
auto root_window =
|
|
client::MainContext::Get()->GetRootWindowManager()->GetActiveRootWindow();
|
|
if (root_window) {
|
|
return root_window->GetBrowser();
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
- (NSWindow*)getActiveBrowserNSWindow {
|
|
if (auto browser = [self getActiveBrowser]) {
|
|
if (auto view = CAST_CEF_WINDOW_HANDLE_TO_NSVIEW(
|
|
browser->GetHost()->GetWindowHandle())) {
|
|
return [view window];
|
|
}
|
|
}
|
|
return nil;
|
|
}
|
|
|
|
- (void)enableAccessibility:(bool)bEnable {
|
|
if (auto browser = [self getActiveBrowser]) {
|
|
browser->GetHost()->SetAccessibilityState(bEnable ? STATE_ENABLED
|
|
: STATE_DISABLED);
|
|
}
|
|
}
|
|
|
|
- (NSApplicationTerminateReply)applicationShouldTerminate:
|
|
(NSApplication*)sender {
|
|
return NSTerminateNow;
|
|
}
|
|
|
|
// Returns true if there is a modal window (either window- or application-
|
|
// modal) blocking the active browser. Note that tab modal dialogs (HTTP auth
|
|
// sheets) will not count as blocking the browser. But things like open/save
|
|
// dialogs that are window modal will block the browser.
|
|
- (BOOL)keyWindowIsModal {
|
|
if ([NSApp modalWindow]) {
|
|
return YES;
|
|
}
|
|
|
|
if (auto window = [self getActiveBrowserNSWindow]) {
|
|
return [[window attachedSheet] isKindOfClass:[NSWindow class]];
|
|
}
|
|
|
|
return NO;
|
|
}
|
|
|
|
// AppKit will call -[NSUserInterfaceValidations validateUserInterfaceItem:] to
|
|
// validate UI items. Any item whose target is FirstResponder, or nil, will
|
|
// traverse the responder chain looking for a responder that implements the
|
|
// item's selector. The top menu (configured in MainMenu.xib) can contain menu
|
|
// items with selectors that are implemented by Chromium's
|
|
// RenderWidgetHostViewCocoa or NativeWidgetMacNSWindow classes. These classes
|
|
// live in the Cocoa view hierarchy and will be triggered only if the browser
|
|
// window is focused. When the browser window is not focused these selectors
|
|
// will be forwarded (by Chromium's CommandDispatcher class) to `[NSApp
|
|
// delegate]` (this class). The particular selectors of interest here are
|
|
// |-commandDispatch:| and |-commandDispatchUsingKeyModifiers:| which will have
|
|
// a tag value from include/cef_command_ids.h. For example, 37000 is IDC_FIND
|
|
// and can be triggered via the "Find..." menu item or the Cmd+g keyboard
|
|
// shortcut:
|
|
//
|
|
// <menuItem title="Find..." tag="37000" keyEquivalent="g" id="209">
|
|
// <connections>
|
|
// <action selector="commandDispatch:" target="-1" id="241"/>
|
|
// </connections>
|
|
// </menuItem>
|
|
//
|
|
// If |-validateUserInterfaceItem:| returns YES then the menu item will be
|
|
// enabled and execution will trigger the associated selector.
|
|
//
|
|
// This implementation is based on Chromium's AppController class.
|
|
- (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)item {
|
|
SEL action = [item action];
|
|
BOOL enable = NO;
|
|
// Whether opening a new browser window is allowed.
|
|
BOOL canOpenNewBrowser = YES;
|
|
|
|
// Commands from the menu bar are only handled by commandDispatch: if there is
|
|
// no key window.
|
|
if (action == @selector(commandDispatch:) ||
|
|
action == @selector(commandDispatchUsingKeyModifiers:)) {
|
|
switch ([item tag]) {
|
|
// Browser-level items that open in new tabs or perform an action in a
|
|
// current tab should not open if there's a window- or app-modal dialog.
|
|
case IDC_OPEN_FILE:
|
|
case IDC_NEW_TAB:
|
|
case IDC_FOCUS_LOCATION:
|
|
case IDC_FOCUS_SEARCH:
|
|
case IDC_SHOW_HISTORY:
|
|
case IDC_SHOW_BOOKMARK_MANAGER:
|
|
case IDC_CLEAR_BROWSING_DATA:
|
|
case IDC_SHOW_DOWNLOADS:
|
|
case IDC_IMPORT_SETTINGS:
|
|
case IDC_MANAGE_EXTENSIONS:
|
|
case IDC_HELP_PAGE_VIA_MENU:
|
|
case IDC_OPTIONS:
|
|
enable = canOpenNewBrowser && ![self keyWindowIsModal];
|
|
break;
|
|
// Browser-level items that open in new windows: allow the user to open
|
|
// a new window even if there's a window-modal dialog.
|
|
case IDC_NEW_WINDOW:
|
|
enable = canOpenNewBrowser;
|
|
break;
|
|
case IDC_TASK_MANAGER:
|
|
enable = YES;
|
|
break;
|
|
case IDC_NEW_INCOGNITO_WINDOW:
|
|
enable = canOpenNewBrowser;
|
|
break;
|
|
default:
|
|
enable = ![self keyWindowIsModal];
|
|
break;
|
|
}
|
|
} else if ([self respondsToSelector:action]) {
|
|
// All other selectors that this class implements.
|
|
enable = YES;
|
|
}
|
|
|
|
return enable;
|
|
}
|
|
|
|
// This will get called in the case where the frontmost window is not a browser
|
|
// window, and the user has command-clicked a button in a background browser
|
|
// window whose action is |-commandDispatch:|
|
|
- (void)commandDispatch:(id)sender {
|
|
// Handle the case where we're dispatching a command from a sender that's in a
|
|
// browser window. This means that the command came from a background window
|
|
// and is getting here because the foreground window is not a browser window.
|
|
DCHECK(sender);
|
|
if ([sender respondsToSelector:@selector(window)]) {
|
|
id delegate = [[sender window] windowController];
|
|
if ([delegate respondsToSelector:@selector(commandDispatch:)]) {
|
|
[delegate commandDispatch:sender];
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Handle specific commands where we want to make the last active browser
|
|
// frontmost and then re-execute the command.
|
|
switch ([sender tag]) {
|
|
case IDC_FIND:
|
|
case IDC_FIND_NEXT:
|
|
case IDC_FIND_PREVIOUS:
|
|
if (id window = [self getActiveBrowserNSWindow]) {
|
|
[window makeKeyAndOrderFront:nil];
|
|
if ([window respondsToSelector:@selector(commandDispatch:)]) {
|
|
[window commandDispatch:sender];
|
|
return;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
LOG(INFO) << "Unhandled commandDispatch: for tag " << [sender tag];
|
|
}
|
|
|
|
// Same as |-commandDispatch:|, but executes commands using a disposition
|
|
// determined by the key flags. This will get called in the case where the
|
|
// frontmost window is not a browser window, and the user has command-clicked
|
|
// a button in a background browser window whose action is
|
|
// |-commandDispatchUsingKeyModifiers:|
|
|
- (void)commandDispatchUsingKeyModifiers:(id)sender {
|
|
// Handle the case where we're dispatching a command from a sender that's in a
|
|
// browser window. This means that the command came from a background window
|
|
// and is getting here because the foreground window is not a browser window.
|
|
DCHECK(sender);
|
|
if ([sender respondsToSelector:@selector(window)]) {
|
|
id delegate = [[sender window] windowController];
|
|
if ([delegate
|
|
respondsToSelector:@selector(commandDispatchUsingKeyModifiers:)]) {
|
|
[delegate commandDispatchUsingKeyModifiers:sender];
|
|
return;
|
|
}
|
|
}
|
|
|
|
LOG(INFO) << "Unhandled commandDispatchUsingKeyModifiers: for tag "
|
|
<< [sender tag];
|
|
}
|
|
|
|
@end
|
|
|
|
namespace client {
|
|
namespace {
|
|
|
|
int RunMain(int argc, char* argv[]) {
|
|
// Load the CEF framework library at runtime instead of linking directly
|
|
// as required by the macOS sandbox implementation.
|
|
CefScopedLibraryLoader library_loader;
|
|
if (!library_loader.LoadInMain()) {
|
|
return 1;
|
|
}
|
|
|
|
int result = -1;
|
|
|
|
CefMainArgs main_args(argc, argv);
|
|
|
|
@autoreleasepool {
|
|
// Initialize the ClientApplication instance.
|
|
[ClientApplication sharedApplication];
|
|
|
|
// If there was an invocation to NSApp prior to this method, then the NSApp
|
|
// will not be a ClientApplication, but will instead be an NSApplication.
|
|
// This is undesirable and we must enforce that this doesn't happen.
|
|
CHECK([NSApp isKindOfClass:[ClientApplication class]]);
|
|
|
|
// Parse command-line arguments.
|
|
CefRefPtr<CefCommandLine> command_line =
|
|
CefCommandLine::CreateCommandLine();
|
|
command_line->InitFromArgv(argc, argv);
|
|
|
|
// Create a ClientApp of the correct type.
|
|
CefRefPtr<CefApp> app;
|
|
ClientApp::ProcessType process_type =
|
|
ClientApp::GetProcessType(command_line);
|
|
if (process_type == ClientApp::BrowserProcess) {
|
|
app = new ClientAppBrowser();
|
|
}
|
|
|
|
// Create the main context object.
|
|
std::unique_ptr<MainContextImpl> context(
|
|
new MainContextImpl(command_line, true));
|
|
|
|
CefSettings settings;
|
|
|
|
// When generating projects with CMake the CEF_USE_SANDBOX value will be defined
|
|
// automatically. Pass -DUSE_SANDBOX=OFF to the CMake command-line to disable
|
|
// use of the sandbox.
|
|
#if !defined(CEF_USE_SANDBOX)
|
|
settings.no_sandbox = true;
|
|
#endif
|
|
|
|
// Populate the settings based on command line arguments.
|
|
context->PopulateSettings(&settings);
|
|
|
|
// Create the main message loop object.
|
|
std::unique_ptr<MainMessageLoop> message_loop;
|
|
if (settings.external_message_pump) {
|
|
message_loop = MainMessageLoopExternalPump::Create();
|
|
} else {
|
|
message_loop.reset(new MainMessageLoopStd);
|
|
}
|
|
|
|
// Initialize CEF.
|
|
context->Initialize(main_args, settings, app, nullptr);
|
|
|
|
// Register scheme handlers.
|
|
test_runner::RegisterSchemeHandlers();
|
|
|
|
// Create the application delegate and window.
|
|
ClientAppDelegate* delegate = [[ClientAppDelegate alloc]
|
|
initWithControls:!command_line->HasSwitch(switches::kHideControls)
|
|
andOsr:settings.windowless_rendering_enabled ? true : false];
|
|
[delegate performSelectorOnMainThread:@selector(createApplication:)
|
|
withObject:nil
|
|
waitUntilDone:NO];
|
|
|
|
// Run the message loop. This will block until Quit() is called.
|
|
result = message_loop->Run();
|
|
|
|
// Shut down CEF.
|
|
context->Shutdown();
|
|
|
|
// Release objects in reverse order of creation.
|
|
#if !__has_feature(objc_arc)
|
|
[delegate release];
|
|
#endif // !__has_feature(objc_arc)
|
|
delegate = nil;
|
|
message_loop.reset();
|
|
context.reset();
|
|
} // @autoreleasepool
|
|
|
|
return result;
|
|
}
|
|
|
|
} // namespace
|
|
} // namespace client
|
|
|
|
// Entry point function for the browser process.
|
|
int main(int argc, char* argv[]) {
|
|
return client::RunMain(argc, argv);
|
|
}
|