// 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 #include #include "include/cef_app.h" #import "include/cef_application_mac.h" #include "include/cef_browser.h" #include "include/cef_frame.h" #include "cefclient/client_app.h" #include "cefclient/client_handler.h" #include "cefclient/client_switches.h" #include "cefclient/main_context_impl.h" #include "cefclient/main_message_loop_std.h" #include "cefclient/osr_widget_mac.h" #include "cefclient/resource.h" #include "cefclient/resource_util.h" #include "cefclient/test_runner.h" namespace { // The global ClientHandlerShared reference. CefRefPtr g_handler; // Used by off-screen rendering to find the associated CefBrowser. class MainBrowserProvider : public client::OSRBrowserProvider { virtual CefRefPtr GetBrowser() { if (g_handler.get()) return g_handler->GetBrowser(); return NULL; } } g_main_browser_provider; // Sizes for URL bar layout #define BUTTON_HEIGHT 22 #define BUTTON_WIDTH 72 #define BUTTON_MARGIN 8 #define URLBAR_HEIGHT 32 // Content area size for newly created windows. const int kWindowWidth = 800; const int kWindowHeight = 600; NSButton* MakeButton(NSRect* rect, NSString* title, NSView* parent) { NSButton* button = [[[NSButton alloc] initWithFrame:*rect] autorelease]; [button setTitle:title]; [button setBezelStyle:NSSmallSquareBezelStyle]; [button setAutoresizingMask:(NSViewMaxXMargin | NSViewMinYMargin)]; [parent addSubview:button]; rect->origin.x += BUTTON_WIDTH; return button; } void AddMenuItem(NSMenu *menu, NSString* label, int idval) { NSMenuItem* item = [menu addItemWithTitle:label action:@selector(menuItemSelected:) keyEquivalent:@""]; [item setTag:idval]; } } // namespace // Receives notifications from the application. Will delete itself when done. @interface ClientAppDelegate : NSObject - (void)createApplication:(id)object; - (void)tryToTerminateApplication:(NSApplication*)app; - (IBAction)menuItemSelected:(id)sender; @end // Provide the CefAppProtocol implementation required by CEF. @interface ClientApplication : NSApplication { @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([NSApp delegate]); [delegate tryToTerminateApplication:self]; // Return, don't exit. The application is responsible for exiting on its own. } @end // Receives notifications from controls and the browser window. Will delete // itself when done. @interface ClientWindowDelegate : NSObject { @private NSWindow* window_; } - (id)initWithWindow:(NSWindow*)window; - (IBAction)goBack:(id)sender; - (IBAction)goForward:(id)sender; - (IBAction)reload:(id)sender; - (IBAction)stopLoading:(id)sender; - (IBAction)takeURLStringValueFrom:(NSTextField *)sender; - (void)alert:(NSString*)title withMessage:(NSString*)message; @end @implementation ClientWindowDelegate - (id)initWithWindow:(NSWindow*)window { if (self = [super init]) { window_ = window; [window_ setDelegate:self]; // Register for application hide/unhide notifications. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidHide:) name:NSApplicationDidHideNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidUnhide:) name:NSApplicationDidUnhideNotification object:nil]; } return self; } - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; [super dealloc]; } - (IBAction)goBack:(id)sender { if (g_handler.get() && g_handler->GetBrowserId()) g_handler->GetBrowser()->GoBack(); } - (IBAction)goForward:(id)sender { if (g_handler.get() && g_handler->GetBrowserId()) g_handler->GetBrowser()->GoForward(); } - (IBAction)reload:(id)sender { if (g_handler.get() && g_handler->GetBrowserId()) g_handler->GetBrowser()->Reload(); } - (IBAction)stopLoading:(id)sender { if (g_handler.get() && g_handler->GetBrowserId()) g_handler->GetBrowser()->StopLoad(); } - (IBAction)takeURLStringValueFrom:(NSTextField *)sender { if (!g_handler.get() || !g_handler->GetBrowserId()) return; NSString *url = [sender stringValue]; // if it doesn't already have a prefix, add http. If we can't parse it, // just don't bother rather than making things worse. NSURL* tempUrl = [NSURL URLWithString:url]; if (tempUrl && ![tempUrl scheme]) url = [@"http://" stringByAppendingString:url]; std::string urlStr = [url UTF8String]; g_handler->GetBrowser()->GetMainFrame()->LoadURL(urlStr); } - (void)alert:(NSString*)title withMessage:(NSString*)message { NSAlert *alert = [NSAlert alertWithMessageText:title defaultButton:@"OK" alternateButton:nil otherButton:nil informativeTextWithFormat:@"%@", message]; [alert runModal]; } // Called when we are activated (when we gain focus). - (void)windowDidBecomeKey:(NSNotification*)notification { if (g_handler.get()) { CefRefPtr browser = g_handler->GetBrowser(); if (browser.get()) { if (g_handler->is_osr()) { browser->GetHost()->SendFocusEvent(true); } else { browser->GetHost()->SetFocus(true); } } } } // Called when we are deactivated (when we lose focus). - (void)windowDidResignKey:(NSNotification*)notification { if (g_handler.get()) { CefRefPtr browser = g_handler->GetBrowser(); if (browser.get()) { if (g_handler->is_osr()) { browser->GetHost()->SendFocusEvent(false); } else { browser->GetHost()->SetFocus(false); } } } } // Called when we have been minimized. - (void)windowDidMiniaturize:(NSNotification *)notification { if (g_handler.get()) { CefRefPtr browser = g_handler->GetBrowser(); if (browser.get()) browser->GetHost()->SetWindowVisibility(false); } } // Called when we have been unminimized. - (void)windowDidDeminiaturize:(NSNotification *)notification { if (g_handler.get()) { CefRefPtr browser = g_handler->GetBrowser(); if (browser.get()) browser->GetHost()->SetWindowVisibility(true); } } // Called when the application has been hidden. - (void)applicationDidHide:(NSNotification *)notification { // If the window is miniaturized then nothing has really changed. if (![window_ isMiniaturized]) { if (g_handler.get()) { CefRefPtr browser = g_handler->GetBrowser(); if (browser.get()) browser->GetHost()->SetWindowVisibility(false); } } } // Called when the application has been unhidden. - (void)applicationDidUnhide:(NSNotification *)notification { // If the window is miniaturized then nothing has really changed. if (![window_ isMiniaturized]) { if (g_handler.get()) { CefRefPtr browser = g_handler->GetBrowser(); if (browser.get()) browser->GetHost()->SetWindowVisibility(true); } } } // Called when the window is about to close. Perform the self-destruction // sequence by getting rid of the window. By returning YES, we allow the window // to be removed from the screen. - (BOOL)windowShouldClose:(id)window { if (g_handler.get() && !g_handler->IsClosing()) { CefRefPtr browser = g_handler->GetBrowser(); if (browser.get()) { // Notify the browser window that we would like to close it. This // will result in a call to ClientHandler::DoClose() if the // JavaScript 'onbeforeunload' event handler allows it. browser->GetHost()->CloseBrowser(false); // Cancel the close. return NO; } } // Try to make the window go away. [window autorelease]; // Clean ourselves up after clearing the stack of anything that might have the // window on it. [self performSelectorOnMainThread:@selector(cleanup:) withObject:window waitUntilDone:NO]; // Allow the close. return YES; } // Deletes itself. - (void)cleanup:(id)window { [self release]; } @end @implementation ClientAppDelegate // Create the application on the UI thread. - (void)createApplication:(id)object { [NSApplication sharedApplication]; [NSBundle loadNibNamed:@"MainMenu" owner:NSApp]; // Set the delegate for application events. [NSApp setDelegate:self]; // Add the Tests menu. NSMenu* menubar = [NSApp mainMenu]; NSMenuItem *testItem = [[[NSMenuItem alloc] initWithTitle:@"Tests" action:nil keyEquivalent:@""] autorelease]; NSMenu *testMenu = [[[NSMenu alloc] initWithTitle:@"Tests"] autorelease]; AddMenuItem(testMenu, @"Get Text", ID_TESTS_GETSOURCE); AddMenuItem(testMenu, @"Get Source", ID_TESTS_GETTEXT); AddMenuItem(testMenu, @"Popup Window", ID_TESTS_POPUP); AddMenuItem(testMenu, @"Request", ID_TESTS_REQUEST); AddMenuItem(testMenu, @"Plugin Info", ID_TESTS_PLUGIN_INFO); AddMenuItem(testMenu, @"Zoom In", ID_TESTS_ZOOM_IN); AddMenuItem(testMenu, @"Zoom Out", ID_TESTS_ZOOM_OUT); AddMenuItem(testMenu, @"Zoom Reset", ID_TESTS_ZOOM_RESET); AddMenuItem(testMenu, @"Begin Tracing", ID_TESTS_TRACING_BEGIN); AddMenuItem(testMenu, @"End Tracing", ID_TESTS_TRACING_END); AddMenuItem(testMenu, @"Print", ID_TESTS_PRINT); AddMenuItem(testMenu, @"Other Tests", ID_TESTS_OTHER_TESTS); [testItem setSubmenu:testMenu]; [menubar addItem:testItem]; // Create the main application window. NSRect screen_rect = [[NSScreen mainScreen] visibleFrame]; NSRect window_rect = { {0, screen_rect.size.height - kWindowHeight}, {kWindowWidth, kWindowHeight} }; NSWindow* mainWnd = [[UnderlayOpenGLHostingWindow alloc] initWithContentRect:window_rect styleMask:(NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask ) backing:NSBackingStoreBuffered defer:NO]; [mainWnd setTitle:@"cefclient"]; // Create the delegate for control and browser window events. ClientWindowDelegate* delegate = [[ClientWindowDelegate alloc] initWithWindow:mainWnd]; // Rely on the window delegate to clean us up rather than immediately // releasing when the window gets closed. We use the delegate to do // everything from the autorelease pool so the window isn't on the stack // during cleanup (ie, a window close from javascript). [mainWnd setReleasedWhenClosed:NO]; NSView* contentView = [mainWnd contentView]; // Create the buttons. NSRect button_rect = [contentView bounds]; button_rect.origin.y = window_rect.size.height - URLBAR_HEIGHT + (URLBAR_HEIGHT - BUTTON_HEIGHT) / 2; button_rect.size.height = BUTTON_HEIGHT; button_rect.origin.x += BUTTON_MARGIN; button_rect.size.width = BUTTON_WIDTH; NSButton* backButton = MakeButton(&button_rect, @"Back", contentView); [backButton setTarget:delegate]; [backButton setAction:@selector(goBack:)]; [backButton setEnabled:NO]; NSButton* forwardButton = MakeButton(&button_rect, @"Forward", contentView); [forwardButton setTarget:delegate]; [forwardButton setAction:@selector(goForward:)]; [forwardButton setEnabled:NO]; NSButton* reloadButton = MakeButton(&button_rect, @"Reload", contentView); [reloadButton setTarget:delegate]; [reloadButton setAction:@selector(reload:)]; [reloadButton setEnabled:NO]; NSButton* stopButton = MakeButton(&button_rect, @"Stop", contentView); [stopButton setTarget:delegate]; [stopButton setAction:@selector(stopLoading:)]; [stopButton setEnabled:NO]; // Create the URL text field. button_rect.origin.x += BUTTON_MARGIN; button_rect.size.width = [contentView bounds].size.width - button_rect.origin.x - BUTTON_MARGIN; NSTextField* editWnd = [[NSTextField alloc] initWithFrame:button_rect]; [contentView addSubview:editWnd]; [editWnd setAutoresizingMask:(NSViewWidthSizable | NSViewMinYMargin)]; [editWnd setTarget:delegate]; [editWnd setAction:@selector(takeURLStringValueFrom:)]; [editWnd setEnabled:NO]; [[editWnd cell] setWraps:NO]; [[editWnd cell] setScrollable:YES]; // Create the handler. g_handler = new client::ClientHandlerShared(); g_handler->SetMainWindowHandle(contentView); g_handler->SetUXWindowHandles(editWnd, backButton, forwardButton, reloadButton, stopButton); // Create the browser view. CefWindowInfo window_info; CefBrowserSettings settings; // Populate the browser settings based on command line arguments. client::MainContext::Get()->PopulateBrowserSettings(&settings); if (g_handler->is_osr()) { CefRefPtr command_line = CefCommandLine::GetGlobalCommandLine(); const bool transparent = command_line->HasSwitch(client::switches::kTransparentPaintingEnabled); const bool show_update_rect = command_line->HasSwitch(client::switches::kShowUpdateRect); CefRefPtr osr_window = client::OSRWindow::Create(&g_main_browser_provider, transparent, show_update_rect, contentView, CefRect(0, 0, kWindowWidth, kWindowHeight)); window_info.SetAsWindowless(osr_window->GetWindowHandle(), transparent); g_handler->SetOSRHandler(osr_window->GetRenderHandler().get()); } else { // Initialize window info to the defaults for a child window. window_info.SetAsChild(contentView, 0, 0, kWindowWidth, kWindowHeight); } CefBrowserHost::CreateBrowser(window_info, g_handler.get(), g_handler->startup_url(), settings, NULL); // Show the window. [mainWnd makeKeyAndOrderFront: nil]; // Size the window. NSRect r = [mainWnd contentRectForFrameRect:[mainWnd frame]]; r.size.width = kWindowWidth; r.size.height = kWindowHeight + URLBAR_HEIGHT; [mainWnd setFrame:[mainWnd frameRectForContentRect:r] display:YES]; } - (void)tryToTerminateApplication:(NSApplication*)app { if (g_handler.get() && !g_handler->IsClosing()) g_handler->CloseAllBrowsers(false); } - (IBAction)menuItemSelected:(id)sender { NSMenuItem *item = (NSMenuItem*)sender; if (g_handler.get() && g_handler->GetBrowserId()) client::test_runner::RunTest(g_handler->GetBrowser(), [item tag]); } - (NSApplicationTerminateReply)applicationShouldTerminate: (NSApplication *)sender { return NSTerminateNow; } @end namespace client { namespace { int RunMain(int argc, char* argv[]) { CefMainArgs main_args(argc, argv); CefRefPtr app(new ClientApp); // Initialize the AutoRelease pool. NSAutoreleasePool* autopool = [[NSAutoreleasePool alloc] init]; // Initialize the ClientApplication instance. [ClientApplication sharedApplication]; // Create the main context object. scoped_ptr context(new MainContextImpl(argc, argv, true)); CefSettings settings; // Populate the settings based on command line arguments. context->PopulateSettings(&settings); // Create the main message loop object. scoped_ptr message_loop(new MainMessageLoopStd); // Initialize CEF. context->Initialize(main_args, settings, app.get(), NULL); // Register scheme handlers. test_runner::RegisterSchemeHandlers(); // Create the application delegate and window. NSObject* delegate = [[ClientAppDelegate alloc] init]; [delegate performSelectorOnMainThread:@selector(createApplication:) withObject:nil waitUntilDone:NO]; // Run the message loop. This will block until Quit() is called. int result = message_loop->Run(); // Shut down CEF. context->Shutdown(); // Release objects in reverse order of creation. g_handler = NULL; [delegate release]; message_loop.reset(); context.reset(); [autopool release]; return result; } } // namespace } // namespace client // Program entry point function. int main(int argc, char* argv[]) { return client::RunMain(argc, argv); }