// 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 "cefclient/cefclient.h" #include "include/cef_app.h" #import "include/cef_application_mac.h" #include "include/cef_browser.h" #include "include/cef_frame.h" #include "include/cef_runnable.h" #include "cefclient/client_handler.h" #include "cefclient/resource_util.h" #include "cefclient/scheme_test.h" #include "cefclient/string_util.h" // The global ClientHandler reference. extern CefRefPtr g_handler; char szWorkingDir[512]; // The current working directory // 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; // 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]; } @end // Receives notifications from controls and the browser window. Will delete // itself when done. @interface ClientWindowDelegate : NSObject - (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; - (void)notifyConsoleMessage:(id)object; - (void)notifyDownloadComplete:(id)object; - (void)notifyDownloadError:(id)object; @end @implementation ClientWindowDelegate - (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]; } - (void)notifyConsoleMessage:(id)object { std::stringstream ss; ss << "Console messages will be written to " << g_handler->GetLogFile(); NSString* str = [NSString stringWithUTF8String:(ss.str().c_str())]; [self alert:@"Console Messages" withMessage:str]; } - (void)notifyDownloadComplete:(id)object { std::stringstream ss; ss << "File \"" << g_handler->GetLastDownloadFile() << "\" downloaded successfully."; NSString* str = [NSString stringWithUTF8String:(ss.str().c_str())]; [self alert:@"File Download" withMessage:str]; } - (void)notifyDownloadError:(id)object { std::stringstream ss; ss << "File \"" << g_handler->GetLastDownloadFile() << "\" failed to download."; NSString* str = [NSString stringWithUTF8String:(ss.str().c_str())]; [self alert:@"File Download" withMessage:str]; } - (void)windowDidBecomeKey:(NSNotification*)notification { if (g_handler.get() && g_handler->GetBrowserId()) { // Give focus to the browser window. g_handler->GetBrowser()->GetHost()->SetFocus(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 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; } // Receives notifications from the application. Will delete itself when done. @interface ClientAppDelegate : NSObject - (void)createApp:(id)object; - (IBAction)testGetSource:(id)sender; - (IBAction)testGetText:(id)sender; - (IBAction)testPopupWindow:(id)sender; - (IBAction)testRequest:(id)sender; - (IBAction)testPluginInfo:(id)sender; - (IBAction)testZoomIn:(id)sender; - (IBAction)testZoomOut:(id)sender; - (IBAction)testZoomReset:(id)sender; - (IBAction)testBeginTracing:(id)sender; - (IBAction)testEndTracing:(id)sender; - (IBAction)testOtherTests:(id)sender; @end @implementation ClientAppDelegate // Create the application on the UI thread. - (void)createApp:(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]; [testMenu addItemWithTitle:@"Get Source" action:@selector(testGetSource:) keyEquivalent:@""]; [testMenu addItemWithTitle:@"Get Text" action:@selector(testGetText:) keyEquivalent:@""]; [testMenu addItemWithTitle:@"Popup Window" action:@selector(testPopupWindow:) keyEquivalent:@""]; [testMenu addItemWithTitle:@"Request" action:@selector(testRequest:) keyEquivalent:@""]; [testMenu addItemWithTitle:@"Plugin Info" action:@selector(testPluginInfo:) keyEquivalent:@""]; [testMenu addItemWithTitle:@"Zoom In" action:@selector(testZoomIn:) keyEquivalent:@""]; [testMenu addItemWithTitle:@"Zoom Out" action:@selector(testZoomOut:) keyEquivalent:@""]; [testMenu addItemWithTitle:@"Zoom Reset" action:@selector(testZoomReset:) keyEquivalent:@""]; [testMenu addItemWithTitle:@"Begin Tracing" action:@selector(testBeginTracing:) keyEquivalent:@""]; [testMenu addItemWithTitle:@"End Tracing" action:@selector(testEndTracing:) keyEquivalent:@""]; [testMenu addItemWithTitle:@"Other Tests" action:@selector(testOtherTests:) keyEquivalent:@""]; [testItem setSubmenu:testMenu]; [menubar addItem:testItem]; // Create the delegate for control and browser window events. ClientWindowDelegate* delegate = [[ClientWindowDelegate alloc] init]; // 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"]; [mainWnd setDelegate:delegate]; // 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* button = MakeButton(&button_rect, @"Back", contentView); [button setTarget:delegate]; [button setAction:@selector(goBack:)]; button = MakeButton(&button_rect, @"Forward", contentView); [button setTarget:delegate]; [button setAction:@selector(goForward:)]; button = MakeButton(&button_rect, @"Reload", contentView); [button setTarget:delegate]; [button setAction:@selector(reload:)]; button = MakeButton(&button_rect, @"Stop", contentView); [button setTarget:delegate]; [button setAction:@selector(stopLoading:)]; // 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 cell] setWraps:NO]; [[editWnd cell] setScrollable:YES]; // Create the handler. g_handler = new ClientHandler(); g_handler->SetMainHwnd(contentView); g_handler->SetEditHwnd(editWnd); // Create the browser view. CefWindowInfo window_info; CefBrowserSettings settings; window_info.SetAsChild(contentView, 0, 0, kWindowWidth, kWindowHeight); CefBrowserHost::CreateBrowser(window_info, g_handler.get(), g_handler->GetStartupURL(), settings); // 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]; } - (IBAction)testGetSource:(id)sender { if (g_handler.get() && g_handler->GetBrowserId()) RunGetSourceTest(g_handler->GetBrowser()); } - (IBAction)testGetText:(id)sender { if (g_handler.get() && g_handler->GetBrowserId()) RunGetTextTest(g_handler->GetBrowser()); } - (IBAction)testPopupWindow:(id)sender { if (g_handler.get() && g_handler->GetBrowserId()) RunPopupTest(g_handler->GetBrowser()); } - (IBAction)testRequest:(id)sender { if (g_handler.get() && g_handler->GetBrowserId()) RunRequestTest(g_handler->GetBrowser()); } - (IBAction)testPluginInfo:(id)sender { if (g_handler.get() && g_handler->GetBrowserId()) RunPluginInfoTest(g_handler->GetBrowser()); } - (IBAction)testZoomIn:(id)sender { if (g_handler.get() && g_handler->GetBrowserId()) { CefRefPtr browser = g_handler->GetBrowser(); browser->GetHost()->SetZoomLevel(browser->GetHost()->GetZoomLevel() + 0.5); } } - (IBAction)testZoomOut:(id)sender { if (g_handler.get() && g_handler->GetBrowserId()) { CefRefPtr browser = g_handler->GetBrowser(); browser->GetHost()->SetZoomLevel(browser->GetHost()->GetZoomLevel() - 0.5); } } - (IBAction)testZoomReset:(id)sender { if (g_handler.get() && g_handler->GetBrowserId()) { CefRefPtr browser = g_handler->GetBrowser(); browser->GetHost()->SetZoomLevel(0.0); } } - (IBAction)testBeginTracing:(id)sender { if (g_handler.get()) g_handler->BeginTracing(); } - (IBAction)testEndTracing:(id)sender { if (g_handler.get()) g_handler->EndTracing(); } - (IBAction)testOtherTests:(id)sender { if (g_handler.get() && g_handler->GetBrowserId()) RunOtherTests(g_handler->GetBrowser()); } // Called when the application’s Quit menu item is selected. - (NSApplicationTerminateReply)applicationShouldTerminate: (NSApplication *)sender { // Request that all browser windows close. if (g_handler.get()) g_handler->CloseAllBrowsers(false); // Cancel the termination. The application will exit after all windows have // closed. return NSTerminateCancel; } // Sent immediately before the application terminates. This signal should not // be called because we cancel the termination. - (void)applicationWillTerminate:(NSNotification *)aNotification { ASSERT(false); // Not reached. } @end int main(int argc, char* argv[]) { CefMainArgs main_args(argc, argv); CefRefPtr app(new ClientApp); // Execute the secondary process, if any. int exit_code = CefExecuteProcess(main_args, app.get()); if (exit_code >= 0) return exit_code; // Retrieve the current working directory. getcwd(szWorkingDir, sizeof(szWorkingDir)); // Initialize the AutoRelease pool. NSAutoreleasePool* autopool = [[NSAutoreleasePool alloc] init]; // Initialize the ClientApplication instance. [ClientApplication sharedApplication]; // Parse command line arguments. AppInitCommandLine(argc, argv); CefSettings settings; // Populate the settings based on command line arguments. AppGetSettings(settings); // Initialize CEF. CefInitialize(main_args, settings, app.get()); // Register the scheme handler. scheme_test::InitTest(); // Create the application delegate and window. NSObject* delegate = [[ClientAppDelegate alloc] init]; [delegate performSelectorOnMainThread:@selector(createApp:) withObject:nil waitUntilDone:NO]; // Run the application message loop. CefRunMessageLoop(); // Shut down CEF. CefShutdown(); // Release the handler. g_handler = NULL; // Release the delegate. [delegate release]; // Release the AutoRelease pool. [autopool release]; return 0; } // Global functions std::string AppGetWorkingDirectory() { return szWorkingDir; } void AppQuitMessageLoop() { CefQuitMessageLoop(); }