2013-04-03 20:20:59 +02:00
|
|
|
// Copyright (c) 2013 The Chromium Embedded Framework Authors.
|
2012-04-03 03:34:16 +02:00
|
|
|
// 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>
|
2024-04-30 17:45:07 +02:00
|
|
|
|
2012-04-03 03:34:16 +02:00
|
|
|
#include "include/cef_app.h"
|
|
|
|
#import "include/cef_application_mac.h"
|
2023-04-19 02:08:02 +02:00
|
|
|
#include "include/cef_command_ids.h"
|
2018-07-27 17:39:53 +02:00
|
|
|
#import "include/wrapper/cef_library_loader.h"
|
2016-11-18 00:52:42 +01:00
|
|
|
#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"
|
2012-04-03 03:34:16 +02:00
|
|
|
|
2015-01-23 20:09:34 +01:00
|
|
|
namespace {
|
|
|
|
|
2017-02-02 21:43:41 +01:00
|
|
|
// Returns the top menu bar with the specified |tag|.
|
|
|
|
NSMenuItem* GetMenuBarMenuWithTag(NSInteger tag) {
|
2017-04-28 23:19:29 +02:00
|
|
|
NSMenu* main_menu = [[NSApplication sharedApplication] mainMenu];
|
2017-02-02 21:43:41 +01:00
|
|
|
NSInteger found_index = [main_menu indexOfItemWithTag:tag];
|
2023-01-02 23:59:03 +01:00
|
|
|
if (found_index >= 0) {
|
2017-02-02 21:43:41 +01:00
|
|
|
return [main_menu itemAtIndex:found_index];
|
2023-01-02 23:59:03 +01:00
|
|
|
}
|
2017-02-02 21:43:41 +01:00
|
|
|
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];
|
2023-01-02 23:59:03 +01:00
|
|
|
if (item.action == action_selector) {
|
2017-02-02 21:43:41 +01:00
|
|
|
return item;
|
2023-01-02 23:59:03 +01:00
|
|
|
}
|
2017-02-02 21:43:41 +01:00
|
|
|
}
|
|
|
|
return nil;
|
2015-01-23 20:09:34 +01:00
|
|
|
}
|
|
|
|
|
2023-06-14 10:20:02 +02:00
|
|
|
void RemoveMenuItem(NSMenu* menu, SEL action_selector) {
|
|
|
|
NSMenuItem* item = GetMenuItemWithAction(menu, action_selector);
|
|
|
|
if (item) {
|
|
|
|
[menu removeItem:item];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-01-23 20:09:34 +01:00
|
|
|
} // namespace
|
|
|
|
|
2014-04-09 20:29:51 +02:00
|
|
|
// Receives notifications from the application. Will delete itself when done.
|
2019-02-26 17:44:17 +01:00
|
|
|
@interface ClientAppDelegate : NSObject <NSApplicationDelegate> {
|
2015-01-30 19:07:13 +01:00
|
|
|
@private
|
|
|
|
bool with_osr_;
|
|
|
|
}
|
|
|
|
|
2023-11-29 02:33:44 +01:00
|
|
|
- (id)initWithOsr:(bool)with_osr;
|
2014-04-09 20:29:51 +02:00
|
|
|
- (void)createApplication:(id)object;
|
|
|
|
- (void)tryToTerminateApplication:(NSApplication*)app;
|
2017-02-02 21:43:41 +01:00
|
|
|
- (void)testsItemSelected:(int)command_id;
|
|
|
|
- (IBAction)menuTestsGetText:(id)sender;
|
|
|
|
- (IBAction)menuTestsGetSource:(id)sender;
|
|
|
|
- (IBAction)menuTestsWindowNew:(id)sender;
|
|
|
|
- (IBAction)menuTestsWindowPopup:(id)sender;
|
2023-06-14 10:20:02 +02:00
|
|
|
- (IBAction)menuTestsWindowDialog:(id)sender;
|
2017-02-02 21:43:41 +01:00
|
|
|
- (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;
|
2019-02-26 17:44:17 +01:00
|
|
|
- (IBAction)menuTestsMuteAudio:(id)sender;
|
|
|
|
- (IBAction)menuTestsUnmuteAudio:(id)sender;
|
2017-02-02 21:43:41 +01:00
|
|
|
- (IBAction)menuTestsOtherTests:(id)sender;
|
2017-05-12 20:28:25 +02:00
|
|
|
- (void)enableAccessibility:(bool)bEnable;
|
2014-04-09 20:29:51 +02:00
|
|
|
@end
|
|
|
|
|
2012-04-03 03:34:16 +02:00
|
|
|
// Provide the CefAppProtocol implementation required by CEF.
|
2019-02-26 17:44:17 +01:00
|
|
|
@interface ClientApplication : NSApplication <CefAppProtocol> {
|
2015-01-30 19:07:13 +01:00
|
|
|
@private
|
2012-04-03 03:34:16 +02:00
|
|
|
BOOL handlingSendEvent_;
|
|
|
|
}
|
|
|
|
@end
|
|
|
|
|
|
|
|
@implementation ClientApplication
|
2015-01-30 19:07:13 +01:00
|
|
|
|
2012-04-03 03:34:16 +02:00
|
|
|
- (BOOL)isHandlingSendEvent {
|
|
|
|
return handlingSendEvent_;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)setHandlingSendEvent:(BOOL)handlingSendEvent {
|
|
|
|
handlingSendEvent_ = handlingSendEvent;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)sendEvent:(NSEvent*)event {
|
|
|
|
CefScopedSendingEvent sendingEventScoper;
|
|
|
|
[super sendEvent:event];
|
|
|
|
}
|
2014-04-09 20:29:51 +02:00
|
|
|
|
|
|
|
// |-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 {
|
2015-01-30 19:07:13 +01:00
|
|
|
ClientAppDelegate* delegate = static_cast<ClientAppDelegate*>(
|
|
|
|
[[NSApplication sharedApplication] delegate]);
|
2014-04-09 20:29:51 +02:00
|
|
|
[delegate tryToTerminateApplication:self];
|
|
|
|
// Return, don't exit. The application is responsible for exiting on its own.
|
|
|
|
}
|
2017-05-12 20:28:25 +02:00
|
|
|
|
|
|
|
// 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];
|
|
|
|
}
|
2012-04-03 03:34:16 +02:00
|
|
|
@end
|
|
|
|
|
|
|
|
@implementation ClientAppDelegate
|
|
|
|
|
2023-11-29 02:33:44 +01:00
|
|
|
- (id)initWithOsr:(bool)with_osr {
|
2015-01-30 19:13:39 +01:00
|
|
|
if (self = [super init]) {
|
|
|
|
with_osr_ = with_osr;
|
|
|
|
}
|
|
|
|
return self;
|
|
|
|
}
|
|
|
|
|
2012-04-03 03:34:16 +02:00
|
|
|
// Create the application on the UI thread.
|
2014-04-09 20:29:51 +02:00
|
|
|
- (void)createApplication:(id)object {
|
2015-01-30 19:07:13 +01:00
|
|
|
NSApplication* application = [NSApplication sharedApplication];
|
2017-02-02 21:43:41 +01:00
|
|
|
|
|
|
|
// 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.
|
2016-11-23 21:54:29 +01:00
|
|
|
[[NSBundle mainBundle] loadNibNamed:@"MainMenu"
|
2017-02-02 21:43:41 +01:00
|
|
|
owner:self
|
2016-11-23 21:54:29 +01:00
|
|
|
topLevelObjects:nil];
|
2013-04-16 21:23:00 +02:00
|
|
|
|
2012-04-03 03:34:16 +02:00
|
|
|
// Set the delegate for application events.
|
2015-01-30 19:07:13 +01:00
|
|
|
[application setDelegate:self];
|
2013-04-16 21:23:00 +02:00
|
|
|
|
2023-06-14 10:20:02 +02:00
|
|
|
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:));
|
|
|
|
}
|
2024-04-17 18:01:26 +02:00
|
|
|
if (!main_context->UseViewsGlobal()) {
|
2023-06-14 10:20:02 +02:00
|
|
|
// Remove the Views-related menu items when not using Views.
|
|
|
|
RemoveMenuItem(tests_menu.submenu, @selector(menuTestsWindowDialog:));
|
2017-02-02 21:43:41 +01:00
|
|
|
}
|
2015-05-13 17:43:50 +02:00
|
|
|
}
|
2013-04-16 21:23:00 +02:00
|
|
|
|
2021-06-19 21:54:45 +02:00
|
|
|
auto window_config = std::make_unique<client::RootWindowConfig>();
|
|
|
|
window_config->with_osr = with_osr_;
|
2017-08-04 00:55:19 +02:00
|
|
|
|
2015-01-30 19:07:13 +01:00
|
|
|
// Create the first window.
|
2023-06-14 10:20:02 +02:00
|
|
|
main_context->GetRootWindowManager()->CreateRootWindow(
|
2021-06-19 21:54:45 +02:00
|
|
|
std::move(window_config));
|
2012-04-03 03:34:16 +02:00
|
|
|
}
|
|
|
|
|
2014-04-09 20:29:51 +02:00
|
|
|
- (void)tryToTerminateApplication:(NSApplication*)app {
|
2015-01-30 19:07:13 +01:00
|
|
|
client::MainContext::Get()->GetRootWindowManager()->CloseAllWindows(false);
|
2014-04-09 20:29:51 +02:00
|
|
|
}
|
|
|
|
|
2017-02-02 21:43:41 +01:00
|
|
|
- (void)orderFrontStandardAboutPanel:(id)sender {
|
|
|
|
[[NSApplication sharedApplication] orderFrontStandardAboutPanel:nil];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)testsItemSelected:(int)command_id {
|
2023-04-19 02:08:02 +02:00
|
|
|
if (auto browser = [self getActiveBrowser]) {
|
2017-02-02 21:43:41 +01:00
|
|
|
client::test_runner::RunTest(browser, command_id);
|
2023-01-02 23:59:03 +01:00
|
|
|
}
|
2017-02-02 21:43:41 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
- (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];
|
|
|
|
}
|
|
|
|
|
2023-06-14 10:20:02 +02:00
|
|
|
- (IBAction)menuTestsWindowDialog:(id)sender {
|
|
|
|
[self testsItemSelected:ID_TESTS_WINDOW_DIALOG];
|
|
|
|
}
|
|
|
|
|
2017-02-02 21:43:41 +01:00
|
|
|
- (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];
|
|
|
|
}
|
|
|
|
|
2019-02-26 17:44:17 +01:00
|
|
|
- (IBAction)menuTestsMuteAudio:(id)sender {
|
|
|
|
[self testsItemSelected:ID_TESTS_MUTE_AUDIO];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (IBAction)menuTestsUnmuteAudio:(id)sender {
|
|
|
|
[self testsItemSelected:ID_TESTS_UNMUTE_AUDIO];
|
|
|
|
}
|
|
|
|
|
2017-02-02 21:43:41 +01:00
|
|
|
- (IBAction)menuTestsOtherTests:(id)sender {
|
|
|
|
[self testsItemSelected:ID_TESTS_OTHER_TESTS];
|
2013-04-03 20:20:59 +02:00
|
|
|
}
|
|
|
|
|
2024-01-29 19:38:25 +01:00
|
|
|
- (scoped_refptr<client::RootWindow>)getActiveRootWindow {
|
|
|
|
return client::MainContext::Get()
|
|
|
|
->GetRootWindowManager()
|
|
|
|
->GetActiveRootWindow();
|
|
|
|
}
|
|
|
|
|
2023-04-19 02:08:02 +02:00
|
|
|
- (CefRefPtr<CefBrowser>)getActiveBrowser {
|
2024-01-29 19:38:25 +01:00
|
|
|
if (auto root_window = [self getActiveRootWindow]) {
|
2023-04-19 02:08:02 +02:00
|
|
|
return root_window->GetBrowser();
|
2023-01-02 23:59:03 +01:00
|
|
|
}
|
2023-04-19 02:08:02 +02:00
|
|
|
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]) {
|
2017-05-17 11:29:28 +02:00
|
|
|
browser->GetHost()->SetAccessibilityState(bEnable ? STATE_ENABLED
|
|
|
|
: STATE_DISABLED);
|
2017-05-12 20:28:25 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-04-03 21:29:47 +02:00
|
|
|
- (NSApplicationTerminateReply)applicationShouldTerminate:
|
2017-05-17 11:29:28 +02:00
|
|
|
(NSApplication*)sender {
|
2014-04-09 20:29:51 +02:00
|
|
|
return NSTerminateNow;
|
2012-04-03 03:34:16 +02:00
|
|
|
}
|
|
|
|
|
2023-04-19 02:08:02 +02:00
|
|
|
// 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];
|
|
|
|
}
|
|
|
|
|
2024-01-29 19:38:25 +01:00
|
|
|
// Called when the user clicks the app dock icon while the application is
|
|
|
|
// already running.
|
|
|
|
- (BOOL)applicationShouldHandleReopen:(NSApplication*)theApplication
|
|
|
|
hasVisibleWindows:(BOOL)flag {
|
|
|
|
if (auto root_window = [self getActiveRootWindow]) {
|
|
|
|
root_window->Show(client::RootWindow::ShowNormal);
|
|
|
|
}
|
|
|
|
return NO;
|
|
|
|
}
|
2012-04-03 03:34:16 +02:00
|
|
|
@end
|
|
|
|
|
2015-01-23 20:09:34 +01:00
|
|
|
namespace client {
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
int RunMain(int argc, char* argv[]) {
|
2018-07-27 17:39:53 +02:00
|
|
|
// Load the CEF framework library at runtime instead of linking directly
|
|
|
|
// as required by the macOS sandbox implementation.
|
|
|
|
CefScopedLibraryLoader library_loader;
|
2023-01-02 23:59:03 +01:00
|
|
|
if (!library_loader.LoadInMain()) {
|
2018-07-27 17:39:53 +02:00
|
|
|
return 1;
|
2023-01-02 23:59:03 +01:00
|
|
|
}
|
2018-07-27 17:39:53 +02:00
|
|
|
|
2019-04-23 19:17:56 +02:00
|
|
|
int result = -1;
|
2012-04-03 03:34:16 +02:00
|
|
|
|
2019-04-23 19:17:56 +02:00
|
|
|
CefMainArgs main_args(argc, argv);
|
2012-04-03 03:34:16 +02:00
|
|
|
|
2019-04-23 19:17:56 +02:00
|
|
|
@autoreleasepool {
|
|
|
|
// Initialize the ClientApplication instance.
|
|
|
|
[ClientApplication sharedApplication];
|
2013-04-16 21:23:00 +02:00
|
|
|
|
2020-07-06 20:14:57 +02:00
|
|
|
// 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]]);
|
|
|
|
|
2019-04-23 19:17:56 +02:00
|
|
|
// Parse command-line arguments.
|
|
|
|
CefRefPtr<CefCommandLine> command_line =
|
|
|
|
CefCommandLine::CreateCommandLine();
|
|
|
|
command_line->InitFromArgv(argc, argv);
|
2015-01-31 05:41:36 +01:00
|
|
|
|
2019-04-23 19:17:56 +02:00
|
|
|
// Create a ClientApp of the correct type.
|
|
|
|
CefRefPtr<CefApp> app;
|
|
|
|
ClientApp::ProcessType process_type =
|
|
|
|
ClientApp::GetProcessType(command_line);
|
2023-01-02 23:59:03 +01:00
|
|
|
if (process_type == ClientApp::BrowserProcess) {
|
2019-04-23 19:17:56 +02:00
|
|
|
app = new ClientAppBrowser();
|
2023-01-02 23:59:03 +01:00
|
|
|
}
|
2015-01-31 05:41:36 +01:00
|
|
|
|
2019-04-23 19:17:56 +02:00
|
|
|
// Create the main context object.
|
2021-06-17 22:08:01 +02:00
|
|
|
std::unique_ptr<MainContextImpl> context(
|
2019-04-23 19:17:56 +02:00
|
|
|
new MainContextImpl(command_line, true));
|
2012-04-03 03:34:16 +02:00
|
|
|
|
2019-04-23 19:17:56 +02:00
|
|
|
CefSettings settings;
|
2012-04-03 03:34:16 +02:00
|
|
|
|
2018-07-27 23:28:12 +02:00
|
|
|
// 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)
|
2019-04-23 19:17:56 +02:00
|
|
|
settings.no_sandbox = true;
|
2018-07-27 23:28:12 +02:00
|
|
|
#endif
|
|
|
|
|
2019-04-23 19:17:56 +02:00
|
|
|
// Populate the settings based on command line arguments.
|
|
|
|
context->PopulateSettings(&settings);
|
|
|
|
|
|
|
|
// Create the main message loop object.
|
2021-06-17 22:08:01 +02:00
|
|
|
std::unique_ptr<MainMessageLoop> message_loop;
|
2023-01-02 23:59:03 +01:00
|
|
|
if (settings.external_message_pump) {
|
2019-04-23 19:17:56 +02:00
|
|
|
message_loop = MainMessageLoopExternalPump::Create();
|
2023-01-02 23:59:03 +01:00
|
|
|
} else {
|
2019-04-23 19:17:56 +02:00
|
|
|
message_loop.reset(new MainMessageLoopStd);
|
2023-01-02 23:59:03 +01:00
|
|
|
}
|
2019-04-23 19:17:56 +02:00
|
|
|
|
2023-11-29 02:33:44 +01:00
|
|
|
// Initialize the CEF browser process. May return false if initialization
|
|
|
|
// fails or if early exit is desired (for example, due to process singleton
|
|
|
|
// relaunch behavior).
|
|
|
|
if (!context->Initialize(main_args, settings, app, nullptr)) {
|
2024-03-12 20:47:10 +01:00
|
|
|
return CefGetExitCode();
|
2023-11-29 02:33:44 +01:00
|
|
|
}
|
2019-04-23 19:17:56 +02:00
|
|
|
|
|
|
|
// Register scheme handlers.
|
|
|
|
test_runner::RegisterSchemeHandlers();
|
|
|
|
|
|
|
|
// Create the application delegate and window.
|
|
|
|
ClientAppDelegate* delegate = [[ClientAppDelegate alloc]
|
2023-11-29 02:33:44 +01:00
|
|
|
initWithOsr:settings.windowless_rendering_enabled ? true : false];
|
2024-01-29 19:38:25 +01:00
|
|
|
// Set as the delegate for application events.
|
|
|
|
NSApp.delegate = delegate;
|
|
|
|
|
2019-04-23 19:17:56 +02:00
|
|
|
[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
|
2015-01-22 18:55:55 +01:00
|
|
|
|
|
|
|
return result;
|
2012-04-03 03:34:16 +02:00
|
|
|
}
|
2015-01-23 20:09:34 +01:00
|
|
|
|
|
|
|
} // namespace
|
|
|
|
} // namespace client
|
|
|
|
|
2018-07-27 17:39:53 +02:00
|
|
|
// Entry point function for the browser process.
|
2015-01-23 20:09:34 +01:00
|
|
|
int main(int argc, char* argv[]) {
|
|
|
|
return client::RunMain(argc, argv);
|
|
|
|
}
|