• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright (c) 2013 The Chromium Embedded Framework Authors.
2// Portions copyright (c) 2010 The Chromium Authors. All rights reserved.
3// Use of this source code is governed by a BSD-style license that can be
4// found in the LICENSE file.
5
6#import <Cocoa/Cocoa.h>
7#include "include/cef_app.h"
8#import "include/cef_application_mac.h"
9#import "include/wrapper/cef_library_loader.h"
10#include "tests/cefclient/browser/main_context_impl.h"
11#include "tests/cefclient/browser/resource.h"
12#include "tests/cefclient/browser/root_window.h"
13#include "tests/cefclient/browser/test_runner.h"
14#include "tests/shared/browser/client_app_browser.h"
15#include "tests/shared/browser/main_message_loop_external_pump.h"
16#include "tests/shared/browser/main_message_loop_std.h"
17#include "tests/shared/common/client_switches.h"
18
19namespace {
20
21// Returns the top menu bar with the specified |tag|.
22NSMenuItem* GetMenuBarMenuWithTag(NSInteger tag) {
23  NSMenu* main_menu = [[NSApplication sharedApplication] mainMenu];
24  NSInteger found_index = [main_menu indexOfItemWithTag:tag];
25  if (found_index >= 0)
26    return [main_menu itemAtIndex:found_index];
27  return nil;
28}
29
30// Returns the item in |menu| that has the specified |action_selector|.
31NSMenuItem* GetMenuItemWithAction(NSMenu* menu, SEL action_selector) {
32  for (NSInteger i = 0; i < menu.numberOfItems; ++i) {
33    NSMenuItem* item = [menu itemAtIndex:i];
34    if (item.action == action_selector)
35      return item;
36  }
37  return nil;
38}
39
40}  // namespace
41
42// Receives notifications from the application. Will delete itself when done.
43@interface ClientAppDelegate : NSObject <NSApplicationDelegate> {
44 @private
45  bool with_controls_;
46  bool with_osr_;
47}
48
49- (id)initWithControls:(bool)with_controls andOsr:(bool)with_osr;
50- (void)createApplication:(id)object;
51- (void)tryToTerminateApplication:(NSApplication*)app;
52- (void)testsItemSelected:(int)command_id;
53- (IBAction)menuTestsGetText:(id)sender;
54- (IBAction)menuTestsGetSource:(id)sender;
55- (IBAction)menuTestsWindowNew:(id)sender;
56- (IBAction)menuTestsWindowPopup:(id)sender;
57- (IBAction)menuTestsRequest:(id)sender;
58- (IBAction)menuTestsPluginInfo:(id)sender;
59- (IBAction)menuTestsZoomIn:(id)sender;
60- (IBAction)menuTestsZoomOut:(id)sender;
61- (IBAction)menuTestsZoomReset:(id)sender;
62- (IBAction)menuTestsSetFPS:(id)sender;
63- (IBAction)menuTestsSetScaleFactor:(id)sender;
64- (IBAction)menuTestsTracingBegin:(id)sender;
65- (IBAction)menuTestsTracingEnd:(id)sender;
66- (IBAction)menuTestsPrint:(id)sender;
67- (IBAction)menuTestsPrintToPdf:(id)sender;
68- (IBAction)menuTestsMuteAudio:(id)sender;
69- (IBAction)menuTestsUnmuteAudio:(id)sender;
70- (IBAction)menuTestsOtherTests:(id)sender;
71- (void)enableAccessibility:(bool)bEnable;
72@end
73
74// Provide the CefAppProtocol implementation required by CEF.
75@interface ClientApplication : NSApplication <CefAppProtocol> {
76 @private
77  BOOL handlingSendEvent_;
78}
79@end
80
81@implementation ClientApplication
82
83- (BOOL)isHandlingSendEvent {
84  return handlingSendEvent_;
85}
86
87- (void)setHandlingSendEvent:(BOOL)handlingSendEvent {
88  handlingSendEvent_ = handlingSendEvent;
89}
90
91- (void)sendEvent:(NSEvent*)event {
92  CefScopedSendingEvent sendingEventScoper;
93  [super sendEvent:event];
94}
95
96// |-terminate:| is the entry point for orderly "quit" operations in Cocoa. This
97// includes the application menu's quit menu item and keyboard equivalent, the
98// application's dock icon menu's quit menu item, "quit" (not "force quit") in
99// the Activity Monitor, and quits triggered by user logout and system restart
100// and shutdown.
101//
102// The default |-terminate:| implementation ends the process by calling exit(),
103// and thus never leaves the main run loop. This is unsuitable for Chromium
104// since Chromium depends on leaving the main run loop to perform an orderly
105// shutdown. We support the normal |-terminate:| interface by overriding the
106// default implementation. Our implementation, which is very specific to the
107// needs of Chromium, works by asking the application delegate to terminate
108// using its |-tryToTerminateApplication:| method.
109//
110// |-tryToTerminateApplication:| differs from the standard
111// |-applicationShouldTerminate:| in that no special event loop is run in the
112// case that immediate termination is not possible (e.g., if dialog boxes
113// allowing the user to cancel have to be shown). Instead, this method tries to
114// close all browsers by calling CloseBrowser(false) via
115// ClientHandler::CloseAllBrowsers. Calling CloseBrowser will result in a call
116// to ClientHandler::DoClose and execution of |-performClose:| on the NSWindow.
117// DoClose sets a flag that is used to differentiate between new close events
118// (e.g., user clicked the window close button) and in-progress close events
119// (e.g., user approved the close window dialog). The NSWindowDelegate
120// |-windowShouldClose:| method checks this flag and either calls
121// CloseBrowser(false) in the case of a new close event or destructs the
122// NSWindow in the case of an in-progress close event.
123// ClientHandler::OnBeforeClose will be called after the CEF NSView hosted in
124// the NSWindow is dealloc'ed.
125//
126// After the final browser window has closed ClientHandler::OnBeforeClose will
127// begin actual tear-down of the application by calling CefQuitMessageLoop.
128// This ends the NSApplication event loop and execution then returns to the
129// main() function for cleanup before application termination.
130//
131// The standard |-applicationShouldTerminate:| is not supported, and code paths
132// leading to it must be redirected.
133- (void)terminate:(id)sender {
134  ClientAppDelegate* delegate = static_cast<ClientAppDelegate*>(
135      [[NSApplication sharedApplication] delegate]);
136  [delegate tryToTerminateApplication:self];
137  // Return, don't exit. The application is responsible for exiting on its own.
138}
139
140// Detect dynamically if VoiceOver is running. Like Chromium, rely upon the
141// undocumented accessibility attribute @"AXEnhancedUserInterface" which is set
142// when VoiceOver is launched and unset when VoiceOver is closed.
143- (void)accessibilitySetValue:(id)value forAttribute:(NSString*)attribute {
144  if ([attribute isEqualToString:@"AXEnhancedUserInterface"]) {
145    ClientAppDelegate* delegate = static_cast<ClientAppDelegate*>(
146        [[NSApplication sharedApplication] delegate]);
147    [delegate enableAccessibility:([value intValue] == 1)];
148  }
149  return [super accessibilitySetValue:value forAttribute:attribute];
150}
151@end
152
153@implementation ClientAppDelegate
154
155- (id)initWithControls:(bool)with_controls andOsr:(bool)with_osr {
156  if (self = [super init]) {
157    with_controls_ = with_controls;
158    with_osr_ = with_osr;
159  }
160  return self;
161}
162
163// Create the application on the UI thread.
164- (void)createApplication:(id)object {
165  NSApplication* application = [NSApplication sharedApplication];
166
167  // The top menu is configured using Interface Builder (IB). To modify the menu
168  // start by loading MainMenu.xib in IB.
169  //
170  // To associate MainMenu.xib with ClientAppDelegate:
171  // 1. Select "File's Owner" from the "Placeholders" section in the left side
172  //    pane.
173  // 2. Load the "Identity inspector" tab in the top-right side pane.
174  // 3. In the "Custom Class" section set the "Class" value to
175  //    "ClientAppDelegate".
176  // 4. Pass an instance of ClientAppDelegate as the |owner| parameter to
177  //    loadNibNamed:.
178  //
179  // To create a new top menu:
180  // 1. Load the "Object library" tab in the bottom-right side pane.
181  // 2. Drag a "Submenu Menu Item" widget from the Object library to the desired
182  //    location in the menu bar shown in the center pane.
183  // 3. Select the newly created top menu by left clicking on it.
184  // 4. Load the "Attributes inspector" tab in the top-right side pane.
185  // 5. Under the "Menu Item" section set the "Tag" value to a unique integer.
186  //    This is necessary for the GetMenuBarMenuWithTag function to work
187  //    properly.
188  //
189  // To create a new menu item in a top menu:
190  // 1. Add a new receiver method in ClientAppDelegate (e.g. menuTestsDoStuff:).
191  // 2. Load the "Object library" tab in the bottom-right side pane.
192  // 3. Drag a "Menu Item" widget from the Object library to the desired
193  //    location in the menu bar shown in the center pane.
194  // 4. Double-click on the new menu item to set the label.
195  // 5. Right click on the new menu item to show the "Get Source" dialog.
196  // 6. In the "Sent Actions" section drag from the circle icon and drop on the
197  //    new receiver method in the ClientAppDelegate source code file.
198  //
199  // Load the top menu from MainMenu.xib.
200  [[NSBundle mainBundle] loadNibNamed:@"MainMenu"
201                                owner:self
202                      topLevelObjects:nil];
203
204  // Set the delegate for application events.
205  [application setDelegate:self];
206
207  if (!with_osr_) {
208    // Remove the OSR-related menu items when OSR is disabled.
209    NSMenuItem* tests_menu = GetMenuBarMenuWithTag(8);
210    if (tests_menu) {
211      NSMenuItem* set_fps_item = GetMenuItemWithAction(
212          tests_menu.submenu, @selector(menuTestsSetFPS:));
213      if (set_fps_item)
214        [tests_menu.submenu removeItem:set_fps_item];
215      NSMenuItem* set_scale_factor_item = GetMenuItemWithAction(
216          tests_menu.submenu, @selector(menuTestsSetScaleFactor:));
217      if (set_scale_factor_item)
218        [tests_menu.submenu removeItem:set_scale_factor_item];
219    }
220  }
221
222  auto window_config = std::make_unique<client::RootWindowConfig>();
223  window_config->with_controls = with_controls_;
224  window_config->with_osr = with_osr_;
225
226  // Create the first window.
227  client::MainContext::Get()->GetRootWindowManager()->CreateRootWindow(
228      std::move(window_config));
229}
230
231- (void)tryToTerminateApplication:(NSApplication*)app {
232  client::MainContext::Get()->GetRootWindowManager()->CloseAllWindows(false);
233}
234
235- (void)orderFrontStandardAboutPanel:(id)sender {
236  [[NSApplication sharedApplication] orderFrontStandardAboutPanel:nil];
237}
238
239- (void)testsItemSelected:(int)command_id {
240  // Retrieve the active RootWindow.
241  NSWindow* key_window = [[NSApplication sharedApplication] keyWindow];
242  if (!key_window)
243    return;
244
245  scoped_refptr<client::RootWindow> root_window =
246      client::RootWindow::GetForNSWindow(key_window);
247
248  CefRefPtr<CefBrowser> browser = root_window->GetBrowser();
249  if (browser.get())
250    client::test_runner::RunTest(browser, command_id);
251}
252
253- (IBAction)menuTestsGetText:(id)sender {
254  [self testsItemSelected:ID_TESTS_GETTEXT];
255}
256
257- (IBAction)menuTestsGetSource:(id)sender {
258  [self testsItemSelected:ID_TESTS_GETSOURCE];
259}
260
261- (IBAction)menuTestsWindowNew:(id)sender {
262  [self testsItemSelected:ID_TESTS_WINDOW_NEW];
263}
264
265- (IBAction)menuTestsWindowPopup:(id)sender {
266  [self testsItemSelected:ID_TESTS_WINDOW_POPUP];
267}
268
269- (IBAction)menuTestsRequest:(id)sender {
270  [self testsItemSelected:ID_TESTS_REQUEST];
271}
272
273- (IBAction)menuTestsPluginInfo:(id)sender {
274  [self testsItemSelected:ID_TESTS_PLUGIN_INFO];
275}
276
277- (IBAction)menuTestsZoomIn:(id)sender {
278  [self testsItemSelected:ID_TESTS_ZOOM_IN];
279}
280
281- (IBAction)menuTestsZoomOut:(id)sender {
282  [self testsItemSelected:ID_TESTS_ZOOM_OUT];
283}
284
285- (IBAction)menuTestsZoomReset:(id)sender {
286  [self testsItemSelected:ID_TESTS_ZOOM_RESET];
287}
288
289- (IBAction)menuTestsSetFPS:(id)sender {
290  [self testsItemSelected:ID_TESTS_OSR_FPS];
291}
292
293- (IBAction)menuTestsSetScaleFactor:(id)sender {
294  [self testsItemSelected:ID_TESTS_OSR_DSF];
295}
296
297- (IBAction)menuTestsTracingBegin:(id)sender {
298  [self testsItemSelected:ID_TESTS_TRACING_BEGIN];
299}
300
301- (IBAction)menuTestsTracingEnd:(id)sender {
302  [self testsItemSelected:ID_TESTS_TRACING_END];
303}
304
305- (IBAction)menuTestsPrint:(id)sender {
306  [self testsItemSelected:ID_TESTS_PRINT];
307}
308
309- (IBAction)menuTestsPrintToPdf:(id)sender {
310  [self testsItemSelected:ID_TESTS_PRINT_TO_PDF];
311}
312
313- (IBAction)menuTestsMuteAudio:(id)sender {
314  [self testsItemSelected:ID_TESTS_MUTE_AUDIO];
315}
316
317- (IBAction)menuTestsUnmuteAudio:(id)sender {
318  [self testsItemSelected:ID_TESTS_UNMUTE_AUDIO];
319}
320
321- (IBAction)menuTestsOtherTests:(id)sender {
322  [self testsItemSelected:ID_TESTS_OTHER_TESTS];
323}
324
325- (void)enableAccessibility:(bool)bEnable {
326  // Retrieve the active RootWindow.
327  NSWindow* key_window = [[NSApplication sharedApplication] keyWindow];
328  if (!key_window)
329    return;
330
331  scoped_refptr<client::RootWindow> root_window =
332      client::RootWindow::GetForNSWindow(key_window);
333
334  CefRefPtr<CefBrowser> browser = root_window->GetBrowser();
335  if (browser.get()) {
336    browser->GetHost()->SetAccessibilityState(bEnable ? STATE_ENABLED
337                                                      : STATE_DISABLED);
338  }
339}
340
341- (NSApplicationTerminateReply)applicationShouldTerminate:
342    (NSApplication*)sender {
343  return NSTerminateNow;
344}
345
346@end
347
348namespace client {
349namespace {
350
351int RunMain(int argc, char* argv[]) {
352  // Load the CEF framework library at runtime instead of linking directly
353  // as required by the macOS sandbox implementation.
354  CefScopedLibraryLoader library_loader;
355  if (!library_loader.LoadInMain())
356    return 1;
357
358  int result = -1;
359
360  CefMainArgs main_args(argc, argv);
361
362  @autoreleasepool {
363    // Initialize the ClientApplication instance.
364    [ClientApplication sharedApplication];
365
366    // If there was an invocation to NSApp prior to this method, then the NSApp
367    // will not be a ClientApplication, but will instead be an NSApplication.
368    // This is undesirable and we must enforce that this doesn't happen.
369    CHECK([NSApp isKindOfClass:[ClientApplication class]]);
370
371    // Parse command-line arguments.
372    CefRefPtr<CefCommandLine> command_line =
373        CefCommandLine::CreateCommandLine();
374    command_line->InitFromArgv(argc, argv);
375
376    // Create a ClientApp of the correct type.
377    CefRefPtr<CefApp> app;
378    ClientApp::ProcessType process_type =
379        ClientApp::GetProcessType(command_line);
380    if (process_type == ClientApp::BrowserProcess)
381      app = new ClientAppBrowser();
382
383    // Create the main context object.
384    std::unique_ptr<MainContextImpl> context(
385        new MainContextImpl(command_line, true));
386
387    CefSettings settings;
388
389// When generating projects with CMake the CEF_USE_SANDBOX value will be defined
390// automatically. Pass -DUSE_SANDBOX=OFF to the CMake command-line to disable
391// use of the sandbox.
392#if !defined(CEF_USE_SANDBOX)
393    settings.no_sandbox = true;
394#endif
395
396    // Populate the settings based on command line arguments.
397    context->PopulateSettings(&settings);
398
399    // Create the main message loop object.
400    std::unique_ptr<MainMessageLoop> message_loop;
401    if (settings.external_message_pump)
402      message_loop = MainMessageLoopExternalPump::Create();
403    else
404      message_loop.reset(new MainMessageLoopStd);
405
406    // Initialize CEF.
407    context->Initialize(main_args, settings, app, nullptr);
408
409    // Register scheme handlers.
410    test_runner::RegisterSchemeHandlers();
411
412    // Create the application delegate and window.
413    ClientAppDelegate* delegate = [[ClientAppDelegate alloc]
414        initWithControls:!command_line->HasSwitch(switches::kHideControls)
415                  andOsr:settings.windowless_rendering_enabled ? true : false];
416    [delegate performSelectorOnMainThread:@selector(createApplication:)
417                               withObject:nil
418                            waitUntilDone:NO];
419
420    // Run the message loop. This will block until Quit() is called.
421    result = message_loop->Run();
422
423    // Shut down CEF.
424    context->Shutdown();
425
426    // Release objects in reverse order of creation.
427#if !__has_feature(objc_arc)
428    [delegate release];
429#endif  // !__has_feature(objc_arc)
430    delegate = nil;
431    message_loop.reset();
432    context.reset();
433  }  // @autoreleasepool
434
435  return result;
436}
437
438}  // namespace
439}  // namespace client
440
441// Entry point function for the browser process.
442int main(int argc, char* argv[]) {
443  return client::RunMain(argc, argv);
444}
445