| // 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" |
| #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; |
| } |
| |
| } // 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)menuTestsRequest:(id)sender; |
| - (IBAction)menuTestsPluginInfo:(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]; |
| |
| if (!with_osr_) { |
| // Remove the OSR-related menu items when OSR is disabled. |
| NSMenuItem* tests_menu = GetMenuBarMenuWithTag(8); |
| if (tests_menu) { |
| NSMenuItem* set_fps_item = GetMenuItemWithAction( |
| tests_menu.submenu, @selector(menuTestsSetFPS:)); |
| if (set_fps_item) |
| [tests_menu.submenu removeItem:set_fps_item]; |
| NSMenuItem* set_scale_factor_item = GetMenuItemWithAction( |
| tests_menu.submenu, @selector(menuTestsSetScaleFactor:)); |
| if (set_scale_factor_item) |
| [tests_menu.submenu removeItem:set_scale_factor_item]; |
| } |
| } |
| |
| client::RootWindowConfig window_config; |
| window_config.with_controls = with_controls_; |
| window_config.with_osr = with_osr_; |
| |
| // Create the first window. |
| client::MainContext::Get()->GetRootWindowManager()->CreateRootWindow( |
| 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 { |
| // Retrieve the active RootWindow. |
| NSWindow* key_window = [[NSApplication sharedApplication] keyWindow]; |
| if (!key_window) |
| return; |
| |
| scoped_refptr<client::RootWindow> root_window = |
| client::RootWindow::GetForNSWindow(key_window); |
| |
| CefRefPtr<CefBrowser> browser = root_window->GetBrowser(); |
| if (browser.get()) |
| 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)menuTestsRequest:(id)sender { |
| [self testsItemSelected:ID_TESTS_REQUEST]; |
| } |
| |
| - (IBAction)menuTestsPluginInfo:(id)sender { |
| [self testsItemSelected:ID_TESTS_PLUGIN_INFO]; |
| } |
| |
| - (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]; |
| } |
| |
| - (void)enableAccessibility:(bool)bEnable { |
| // Retrieve the active RootWindow. |
| NSWindow* key_window = [[NSApplication sharedApplication] keyWindow]; |
| if (!key_window) |
| return; |
| |
| scoped_refptr<client::RootWindow> root_window = |
| client::RootWindow::GetForNSWindow(key_window); |
| |
| CefRefPtr<CefBrowser> browser = root_window->GetBrowser(); |
| if (browser.get()) { |
| browser->GetHost()->SetAccessibilityState(bEnable ? STATE_ENABLED |
| : STATE_DISABLED); |
| } |
| } |
| |
| - (NSApplicationTerminateReply)applicationShouldTerminate: |
| (NSApplication*)sender { |
| return NSTerminateNow; |
| } |
| |
| @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]; |
| |
| // 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. |
| scoped_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. |
| scoped_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, NULL); |
| |
| // 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); |
| } |