diff --git a/appshell.gyp b/appshell.gyp index 7f78d66b5..84f9ee897 100755 --- a/appshell.gyp +++ b/appshell.gyp @@ -1,4 +1,4 @@ -# Copyright (c) 2011 The Chromium Embedded Framework Authors. All rights + # Copyright (c) 2011 The Chromium Embedded Framework Authors. All rights # reserved. Use of this source code is governed by a BSD-style license that # can be found in the LICENSE file. @@ -204,6 +204,8 @@ 'link_settings': { 'libraries': [ '$(SDKROOT)/System/Library/Frameworks/AppKit.framework', + '$(SDKROOT)/System/Library/Frameworks/Foundation.framework', + '$(SDKROOT)/System/Library/Frameworks/ScriptingBridge.framework', '$(CONFIGURATION)/libcef.dylib', ], }, @@ -359,6 +361,8 @@ 'link_settings': { 'libraries': [ '$(SDKROOT)/System/Library/Frameworks/AppKit.framework', + '$(SDKROOT)/System/Library/Frameworks/Foundation.framework', + '$(SDKROOT)/System/Library/Frameworks/ScriptingBridge.framework', '$(CONFIGURATION)/libcef.dylib', ], }, diff --git a/appshell/GoogleChrome.h b/appshell/GoogleChrome.h new file mode 100644 index 000000000..24f51aecb --- /dev/null +++ b/appshell/GoogleChrome.h @@ -0,0 +1,191 @@ +/* + * GoogleChrome.h + */ + +#import +#import + + +@class GoogleChromeApplication, GoogleChromeWindow, GoogleChromeTab, GoogleChromeBookmarkFolder, GoogleChromeBookmarkItem; + + + +/* + * Standard Suite + */ + +// The application's top-level scripting object. +@interface GoogleChromeApplication : SBApplication + +- (SBElementArray *) windows; + +@property (copy, readonly) NSString *name; // The name of the application. +@property (readonly) BOOL frontmost; // Is this the frontmost (active) application? +@property (copy, readonly) NSString *version; // The version of the application. + +- (void) open:(NSArray *)x; // Open a document. +- (void) quit; // Quit the application. +- (BOOL) exists:(id)x; // Verify if an object exists. + +@end + +// A window. +@interface GoogleChromeWindow : SBObject + +- (SBElementArray *) tabs; + +@property (copy, readonly) NSString *name; // The full title of the window. +- (NSInteger) id; // The unique identifier of the window. +@property NSInteger index; // The index of the window, ordered front to back. +@property NSRect bounds; // The bounding rectangle of the window. +@property (readonly) BOOL closeable; // Whether the window has a close box. +@property (readonly) BOOL minimizable; // Whether the window can be minimized. +@property BOOL minimized; // Whether the window is currently minimized. +@property (readonly) BOOL resizable; // Whether the window can be resized. +@property BOOL visible; // Whether the window is currently visible. +@property (readonly) BOOL zoomable; // Whether the window can be zoomed. +@property BOOL zoomed; // Whether the window is currently zoomed. +@property (copy, readonly) GoogleChromeTab *activeTab; // Returns the currently selected tab +@property (copy) NSString *mode; // Represents the mode of the window which can be 'normal' or 'incognito', can be set only once during creation of the window. +@property NSInteger activeTabIndex; // The index of the active tab. + +- (void) saveIn:(NSURL *)in_ as:(NSString *)as; // Save an object. +- (void) close; // Close a window. +- (void) delete; // Delete an object. +- (SBObject *) duplicateTo:(SBObject *)to withProperties:(NSDictionary *)withProperties; // Copy object(s) and put the copies at a new location. +- (SBObject *) moveTo:(SBObject *)to; // Move object(s) to a new location. +- (void) print; // Print an object. +- (void) reload; // Reload a tab. +- (void) goBack; // Go Back (If Possible). +- (void) goForward; // Go Forward (If Possible). +- (void) selectAll; // Select all. +- (void) cutSelection; // Cut selected text (If Possible). +- (void) copySelection; // Copy text. +- (void) pasteSelection; // Paste text (If Possible). +- (void) undo; // Undo the last change. +- (void) redo; // Redo the last change. +- (void) stop; // Stop the current tab from loading. +- (void) viewSource; // View the HTML source of the tab. +- (id) executeJavascript:(NSString *)javascript; // Execute a piece of javascript. +- (void) enterPresentationMode; // Enter presentation mode in window. +- (void) exitPresentationMode; // Exit presentation mode in window. + +@end + + + +/* + * Chromium Suite + */ + +// The application's top-level scripting object. +@interface GoogleChromeApplication (ChromiumSuite) + +- (SBElementArray *) bookmarkFolders; + +@property (copy, readonly) GoogleChromeBookmarkFolder *bookmarksBar; // The bookmarks bar bookmark folder. +@property (copy, readonly) GoogleChromeBookmarkFolder *otherBookmarks; // The other bookmarks bookmark folder. + +@end + +// A tab. +@interface GoogleChromeTab : SBObject + +- (NSInteger) id; // Unique ID of the tab. +@property (copy, readonly) NSString *title; // The title of the tab. +@property (copy) NSString *URL; // The url visible to the user. +@property (readonly) BOOL loading; // Is loading? + +- (void) saveIn:(NSURL *)in_ as:(NSString *)as; // Save an object. +- (void) close; // Close a window. +- (void) delete; // Delete an object. +- (SBObject *) duplicateTo:(SBObject *)to withProperties:(NSDictionary *)withProperties; // Copy object(s) and put the copies at a new location. +- (SBObject *) moveTo:(SBObject *)to; // Move object(s) to a new location. +- (void) print; // Print an object. +- (void) reload; // Reload a tab. +- (void) goBack; // Go Back (If Possible). +- (void) goForward; // Go Forward (If Possible). +- (void) selectAll; // Select all. +- (void) cutSelection; // Cut selected text (If Possible). +- (void) copySelection; // Copy text. +- (void) pasteSelection; // Paste text (If Possible). +- (void) undo; // Undo the last change. +- (void) redo; // Redo the last change. +- (void) stop; // Stop the current tab from loading. +- (void) viewSource; // View the HTML source of the tab. +- (id) executeJavascript:(NSString *)javascript; // Execute a piece of javascript. +- (void) enterPresentationMode; // Enter presentation mode in window. +- (void) exitPresentationMode; // Exit presentation mode in window. + +@end + +// A bookmarks folder that contains other bookmarks folder and bookmark items. +@interface GoogleChromeBookmarkFolder : SBObject + +- (SBElementArray *) bookmarkFolders; +- (SBElementArray *) bookmarkItems; + +- (NSNumber *) id; // Unique ID of the bookmark folder. +@property (copy) NSString *title; // The title of the folder. +@property (copy, readonly) NSNumber *index; // Returns the index with respect to its parent bookmark folder + +- (void) saveIn:(NSURL *)in_ as:(NSString *)as; // Save an object. +- (void) close; // Close a window. +- (void) delete; // Delete an object. +- (SBObject *) duplicateTo:(SBObject *)to withProperties:(NSDictionary *)withProperties; // Copy object(s) and put the copies at a new location. +- (SBObject *) moveTo:(SBObject *)to; // Move object(s) to a new location. +- (void) print; // Print an object. +- (void) reload; // Reload a tab. +- (void) goBack; // Go Back (If Possible). +- (void) goForward; // Go Forward (If Possible). +- (void) selectAll; // Select all. +- (void) cutSelection; // Cut selected text (If Possible). +- (void) copySelection; // Copy text. +- (void) pasteSelection; // Paste text (If Possible). +- (void) undo; // Undo the last change. +- (void) redo; // Redo the last change. +- (void) stop; // Stop the current tab from loading. +- (void) viewSource; // View the HTML source of the tab. +- (id) executeJavascript:(NSString *)javascript; // Execute a piece of javascript. +- (void) enterPresentationMode; // Enter presentation mode in window. +- (void) exitPresentationMode; // Exit presentation mode in window. + +@end + +// An item consists of an URL and the title of a bookmark +@interface GoogleChromeBookmarkItem : SBObject + +- (NSInteger) id; // Unique ID of the bookmark item. +@property (copy) NSString *title; // The title of the bookmark item. +@property (copy) NSString *URL; // The URL of the bookmark. +@property (copy, readonly) NSNumber *index; // Returns the index with respect to its parent bookmark folder + +- (void) saveIn:(NSURL *)in_ as:(NSString *)as; // Save an object. +- (void) close; // Close a window. +- (void) delete; // Delete an object. +- (SBObject *) duplicateTo:(SBObject *)to withProperties:(NSDictionary *)withProperties; // Copy object(s) and put the copies at a new location. +- (SBObject *) moveTo:(SBObject *)to; // Move object(s) to a new location. +- (void) print; // Print an object. +- (void) reload; // Reload a tab. +- (void) goBack; // Go Back (If Possible). +- (void) goForward; // Go Forward (If Possible). +- (void) selectAll; // Select all. +- (void) cutSelection; // Cut selected text (If Possible). +- (void) copySelection; // Copy text. +- (void) pasteSelection; // Paste text (If Possible). +- (void) undo; // Undo the last change. +- (void) redo; // Redo the last change. +- (void) stop; // Stop the current tab from loading. +- (void) viewSource; // View the HTML source of the tab. +- (id) executeJavascript:(NSString *)javascript; // Execute a piece of javascript. +- (void) enterPresentationMode; // Enter presentation mode in window. +- (void) exitPresentationMode; // Exit presentation mode in window. + +@end + +@interface GoogleChromeWindow (ChromiumSuite) + +@property (readonly) BOOL presenting; // Whether the window is in presentation mode. + +@end + diff --git a/appshell/appshell_extensions_mac.mm b/appshell/appshell_extensions_mac.mm index 6c16d1c1e..c88172c4e 100644 --- a/appshell/appshell_extensions_mac.mm +++ b/appshell/appshell_extensions_mac.mm @@ -25,7 +25,10 @@ #include "appshell_extensions.h" #include "native_menu_model.h" +#include "GoogleChrome.h" + #include +#include NSMutableArray* pendingOpenFiles; @@ -34,10 +37,20 @@ - (void)appTerminated:(NSNotification *)note; - (void)timeoutTimer:(NSTimer*)timer; @end +// LiveBrowser helper functions +NSRunningApplication* GetLiveBrowserApp(NSString *bundleId, int debugPort); +NSString* GetUserProfilePath() { + return [NSString stringWithFormat:@"%s%@", ClientApp::AppGetSupportDirectory().ToString().c_str(), @"/live-dev-profile"]; +} + // App ID for either Chrome or Chrome Canary (commented out) NSString *const appId = @"com.google.Chrome"; //NSString *const appId = @"com.google.Chrome.canary"; +// Live Development debug port +int const debugPort = 9222; +NSString* debugPortCommandlineArguments = [NSString stringWithFormat:@"--remote-debugging-port=%d", debugPort]; + /////////////////////////////////////////////////////////////////////////////// // LiveBrowserMgrMac @@ -51,11 +64,19 @@ - (void)timeoutTimer:(NSTimer*)timer; void CheckForChromeRunning(); void CheckForChromeRunningTimeout(); + void SetWorkspaceNotifications(int pid); + void RemoveWorkspaceNotifications(); + void CloseLiveBrowserKillTimers(); void CloseLiveBrowserFireCallback(int valToSend); + int IncrementOpenRetryCount() { return m_openLiveBrowserRetryCount++; } + void ResetOpenRetryCount() { m_openLiveBrowserRetryCount = 0; } + ChromeWindowsTerminatedObserver* GetTerminateObserver() { return m_chromeTerminateObserver; } CefRefPtr GetCloseCallback() { return m_closeLiveBrowserCallback; } + NSRunningApplication* GetLiveBrowser() { return GetLiveBrowserApp(appId, debugPort); } + int GetLiveBrowserPid() { return m_liveBrowserPid; } void SetCloseTimeoutTimer(NSTimer* closeLiveBrowserTimeoutTimer) { m_closeLiveBrowserTimeoutTimer = closeLiveBrowserTimeoutTimer; } @@ -65,25 +86,30 @@ void SetCloseCallback(CefRefPtr response) { m_closeLiveBrowserCallback = response; } void SetBrowser(CefRefPtr browser) { m_browser = browser; } - + void SetLiveBrowserPid(int pid) + { m_liveBrowserPid = pid; } private: // private so this class cannot be instantiated externally LiveBrowserMgrMac(); virtual ~LiveBrowserMgrMac(); + int m_openLiveBrowserRetryCount; NSTimer* m_closeLiveBrowserTimeoutTimer; CefRefPtr m_closeLiveBrowserCallback; CefRefPtr m_browser; ChromeWindowsTerminatedObserver* m_chromeTerminateObserver; + int m_liveBrowserPid; - static LiveBrowserMgrMac* s_instance; + static LiveBrowserMgrMac* s_instance; }; LiveBrowserMgrMac::LiveBrowserMgrMac() - : m_closeLiveBrowserTimeoutTimer(nil) - , m_chromeTerminateObserver(nil) +: m_openLiveBrowserRetryCount(0) +, m_closeLiveBrowserTimeoutTimer(nil) +, m_chromeTerminateObserver(nil) +, m_liveBrowserPid(ERR_PID_NOT_FOUND) { } @@ -91,12 +117,15 @@ void SetBrowser(CefRefPtr browser) { if (s_instance) s_instance->CloseLiveBrowserKillTimers(); + + RemoveWorkspaceNotifications(); } LiveBrowserMgrMac* LiveBrowserMgrMac::GetInstance() { if (!s_instance) s_instance = new LiveBrowserMgrMac(); + return s_instance; } @@ -108,14 +137,7 @@ void SetBrowser(CefRefPtr browser) bool LiveBrowserMgrMac::IsChromeRunning() { - NSArray *apps = [NSRunningApplication runningApplicationsWithBundleIdentifier:appId]; - for (NSUInteger i = 0; i < apps.count; i++) { - NSRunningApplication* curApp = [apps objectAtIndex:i]; - if( curApp && !curApp.terminated ) { - return true; - } - } - return false; + return GetLiveBrowser() ? true : false; } void LiveBrowserMgrMac::CloseLiveBrowserKillTimers() @@ -125,12 +147,6 @@ void SetBrowser(CefRefPtr browser) [m_closeLiveBrowserTimeoutTimer release]; m_closeLiveBrowserTimeoutTimer = nil; } - - if (m_chromeTerminateObserver) { - [[[NSWorkspace sharedWorkspace] notificationCenter] removeObserver:m_chromeTerminateObserver]; - [m_chromeTerminateObserver release]; - m_chromeTerminateObserver = nil; - } } void LiveBrowserMgrMac::CloseLiveBrowserFireCallback(int valToSend) @@ -139,7 +155,10 @@ void SetBrowser(CefRefPtr browser) // kill the timers CloseLiveBrowserKillTimers(); - + + // Stop listening for ws shutdown notifications + RemoveWorkspaceNotifications(); + // Set common response args (callbackId and error) responseArgs->SetInt(1, valToSend); @@ -156,6 +175,10 @@ void SetBrowser(CefRefPtr browser) if (IsChromeRunning()) return; + // Unset the LiveBrowser pid + m_liveBrowserPid = ERR_PID_NOT_FOUND; + + // Fire callback to browser CloseLiveBrowserFireCallback(NO_ERROR); } @@ -167,55 +190,143 @@ void SetBrowser(CefRefPtr browser) CloseLiveBrowserFireCallback(retVal); } +void LiveBrowserMgrMac::SetWorkspaceNotifications(int PID) +{ + + // Cache the LiveBrowser pid for fast lookups + matching during termination + SetLiveBrowserPid(PID); + + // Set up workspace notifications for asynchronous Browser shutdowns + if (!GetTerminateObserver()) { + //register an observer to watch for the app terminations + SetTerminateObserver([[ChromeWindowsTerminatedObserver alloc] init]); + + [[[NSWorkspace sharedWorkspace] notificationCenter] + addObserver:GetTerminateObserver() + selector:@selector(appTerminated:) + name:NSWorkspaceDidTerminateApplicationNotification + object:nil + ]; + } +} + +void LiveBrowserMgrMac::RemoveWorkspaceNotifications() +{ + if (m_chromeTerminateObserver) { + [[[NSWorkspace sharedWorkspace] notificationCenter] removeObserver:m_chromeTerminateObserver]; + [m_chromeTerminateObserver release]; + m_chromeTerminateObserver = nil; + } +} + LiveBrowserMgrMac* LiveBrowserMgrMac::s_instance = NULL; // Forward declarations for functions defined later in this file void NSArrayToCefList(NSArray* array, CefRefPtr& list); int32 ConvertNSErrorCode(NSError* error, bool isReading); +GoogleChromeApplication* GetGoogleChromeApplicationWithPid(int PID) +{ + try { + return [SBApplication applicationWithProcessIdentifier:PID]; + } + catch (...) { + return nil; + } +} int32 OpenLiveBrowser(ExtensionString argURL, bool enableRemoteDebugging) { + LiveBrowserMgrMac* liveBrowserMgr = LiveBrowserMgrMac::GetInstance(); + // Parse the arguments NSString *urlString = [NSString stringWithUTF8String:argURL.c_str()]; - NSURL *url = [NSURL URLWithString:urlString]; // Find instances of the Browser - NSArray *apps = [NSRunningApplication runningApplicationsWithBundleIdentifier:appId]; - NSWorkspace * ws = [NSWorkspace sharedWorkspace]; - NSUInteger launchOptions = NSWorkspaceLaunchDefault | NSWorkspaceLaunchWithoutActivation; + NSRunningApplication* liveBrowser = liveBrowserMgr->GetLiveBrowser(); - // Launch Browser - if(apps.count == 0) { + // Launch Browser (if not running) + if (!liveBrowser) { - // Create the configuration dictionary for launching with custom parameters. - NSArray *parameters = nil; - if (enableRemoteDebugging) { - parameters = [NSArray arrayWithObjects: - @"--remote-debugging-port=9222", - @"--allow-file-access-from-files", - nil]; - } - else { - parameters = [NSArray arrayWithObjects: - @"--allow-file-access-from-files", - nil]; - } - - NSMutableDictionary* appConfig = [NSDictionary dictionaryWithObject:parameters forKey:NSWorkspaceLaunchConfigurationArguments]; - + NSWorkspace * ws = [NSWorkspace sharedWorkspace]; NSURL *appURL = [ws URLForApplicationWithBundleIdentifier:appId]; if( !appURL ) { return ERR_NOT_FOUND; //Chrome not installed } + + // Create the configuration dictionary for launching with custom parameters. + NSArray *parameters = nil; + NSString *profilePath = [NSString stringWithFormat:@"--user-data-dir=%@", GetUserProfilePath()]; + + parameters = [NSArray arrayWithObjects: + debugPortCommandlineArguments, + @"--allow-file-access-from-files", + @"--no-first-run", + @"--no-default-browser-check", + @"--temp-profile", + profilePath, + urlString, + nil]; + + NSDictionary* appConfig = [NSDictionary dictionaryWithObject:parameters forKey:NSWorkspaceLaunchConfigurationArguments]; + NSError *error = nil; - if( ![ws launchApplicationAtURL:appURL options:launchOptions configuration:appConfig error:&error] ) { + NSUInteger launchOptions = NSWorkspaceLaunchDefault | NSWorkspaceLaunchWithoutActivation | NSWorkspaceLaunchNewInstance; + liveBrowser = [ws launchApplicationAtURL:appURL options:launchOptions configuration:appConfig error:&error]; + + // Set up workspace notifications for asynchronous Browser shutdowns + if (liveBrowser) { + liveBrowserMgr->SetWorkspaceNotifications([liveBrowser processIdentifier]); + } + + return liveBrowser ? NO_ERROR : ERR_UNKNOWN; + } + + // A running instance of LiveBrowser was found. Let's get the corresponding GoogleChromeApplication object + GoogleChromeApplication *chromeApp = GetGoogleChromeApplicationWithPid([liveBrowser processIdentifier]); + + // Sanity check + if (!chromeApp) { + // Failed to retrieve the LiveBrowser's instance as a GoogleChromeApplication object. + // It's very likely that Chrome has been shutdown asynchronously. + + // So let's try to open a new instance recursively, first making sure we limit the + // number of retries in order to prevent infinite looping... + if (liveBrowserMgr->IncrementOpenRetryCount() > 3) { + liveBrowserMgr->ResetOpenRetryCount(); return ERR_UNKNOWN; } + + return OpenLiveBrowser(argURL, enableRemoteDebugging); + } + + liveBrowserMgr->ResetOpenRetryCount(); + + // Set up workspace notifications for asynchronous Browser shutdowns + liveBrowserMgr->SetWorkspaceNotifications([liveBrowser processIdentifier]); + + // Check for existing tab (with interstitial page) in all open Chrome windows + for (GoogleChromeWindow* chromeWindow in [chromeApp windows]){ + for (GoogleChromeTab* tab in [chromeWindow tabs]) { + if ([tab.URL isEqualToString:urlString]) { + // Found and open tab with interstitial page loaded + return NO_ERROR; + } + } + } + + GoogleChromeWindow* chromeWindow = [[chromeApp windows] objectAtIndex:0]; + if(!chromeWindow){ + // Create new LiveBrowser Window + GoogleChromeWindow* chromeWindow = [[[chromeApp classForScriptingClass:@"window"] alloc] init]; + [[chromeApp windows] addObject:chromeWindow]; + chromeWindow.activeTab.URL = urlString; + [chromeWindow release]; + } else { + // Create new Tab in LiveBrowser window + GoogleChromeTab* chromeTab = [[[chromeApp classForScriptingClass:@"tab"] alloc] initWithProperties:@{@"URL": urlString}]; + [[chromeWindow tabs] addObject:chromeTab]; + [chromeTab release]; } - - // Tell the Browser to load the url - [ws openURLs:[NSArray arrayWithObject:url] withAppBundleIdentifier:appId options:launchOptions additionalEventParamDescriptor:nil launchIdentifiers:nil]; - return NO_ERROR; } @@ -229,40 +340,45 @@ void CloseLiveBrowser(CefRefPtr browser, CefRefPtrCloseLiveBrowserFireCallback(ERR_UNKNOWN); } + // Set up new Brackets CloseLiveBrowser callbacks liveBrowserMgr->SetBrowser(browser); liveBrowserMgr->SetCloseCallback(response); - // Find instances of the Browser and terminate them - NSArray *apps = [NSRunningApplication runningApplicationsWithBundleIdentifier:appId]; + // Set up workspace shutdown notifications (in case they are not currently setup + // or have been since disabled by a previous browser shutdown notification) + liveBrowserMgr->SetWorkspaceNotifications(liveBrowserMgr->GetLiveBrowserPid()); - if (apps.count == 0) { - // No instances of Chrome found. Fire callback immediately. + // Check chrome + if (!liveBrowserMgr->IsChromeRunning()) { liveBrowserMgr->CloseLiveBrowserFireCallback(NO_ERROR); return; - } else if (apps.count > 0 && !LiveBrowserMgrMac::GetInstance()->GetTerminateObserver()) { - //register an observer to watch for the app terminations - LiveBrowserMgrMac::GetInstance()->SetTerminateObserver([[ChromeWindowsTerminatedObserver alloc] init]); - - [[[NSWorkspace sharedWorkspace] notificationCenter] - addObserver:LiveBrowserMgrMac::GetInstance()->GetTerminateObserver() - selector:@selector(appTerminated:) - name:NSWorkspaceDidTerminateApplicationNotification - object:nil - ]; } - // Iterate over open browser intances and terminate - for (NSUInteger i = 0; i < apps.count; i++) { - NSRunningApplication* curApp = [apps objectAtIndex:i]; - if( curApp && !curApp.terminated ) { - [curApp terminate]; - } + // Get the currently active LiveBrowser session + GoogleChromeApplication* chromeApp = GetGoogleChromeApplicationWithPid(liveBrowserMgr->GetLiveBrowserPid()); + if (!chromeApp) { + // No active LiveBrowser found + liveBrowserMgr->CloseLiveBrowserFireCallback(NO_ERROR); + return; + } + + // Technically at this point we would locate the LiveBrowser window and + // close all tabs; however, the LiveDocument tab already closed by Inspector! + // and there is no way to find which window to close. + + // Do not close other windows + if ([[chromeApp windows] count] > 0 || [[[[chromeApp windows] objectAtIndex:0] tabs] count] > 0) { + liveBrowserMgr->CloseLiveBrowserFireCallback(NO_ERROR); + return; } - //start a timeout timer + // No more open windows found, so quit Chrome + [chromeApp quit]; + + // Set timeout timer liveBrowserMgr->SetCloseTimeoutTimer([[NSTimer scheduledTimerWithTimeInterval:(3 * 60) - target:LiveBrowserMgrMac::GetInstance()->GetTerminateObserver() + target:liveBrowserMgr->GetTerminateObserver() selector:@selector(timeoutTimer:) userInfo:nil repeats:NO] retain] ); @@ -666,6 +782,16 @@ @implementation ChromeWindowsTerminatedObserver - (void) appTerminated:(NSNotification *)note { + // Not Chrome? Not interested. + if ( ![[[note userInfo] objectForKey:@"NSApplicationBundleIdentifier"] isEqualToString:appId] ) { + return; + } + + // Not LiveBrowser instance? Not interested. + if ( ![[[note userInfo] objectForKey:@"NSApplicationProcessIdentifier"] isEqualToNumber:[NSNumber numberWithInt:LiveBrowserMgrMac::GetInstance()->GetLiveBrowserPid()]] ) { + return; + } + LiveBrowserMgrMac::GetInstance()->CheckForChromeRunning(); } @@ -1136,3 +1262,184 @@ void DragWindow(CefRefPtr browser) [win displayIfNeeded]; } } + +int32 GetArgvFromProcessID(int pid, NSString **argv); +NSRunningApplication* GetLiveBrowserApp(NSString *bundleId, int debugPort) +{ + + NSArray* appList = [NSRunningApplication runningApplicationsWithBundleIdentifier: bundleId]; + + // Search list of running apps with bundleId + debug port + for (NSRunningApplication* currApp in appList) { + + int PID = [currApp processIdentifier]; + NSString* args = nil; + + // Check for process arguments + if (GetArgvFromProcessID(PID, &args) != NO_ERROR) { + continue; + } + + // Check debug port (e.g. --remote-debugging-port=9222) + if ([args rangeOfString:debugPortCommandlineArguments].location != NSNotFound) { + return currApp; + } + } + return nil; +} + +// Extracted & Modified from https://gist.github.com/nonowarn/770696 +int32 GetArgvFromProcessID(int pid, NSString **argv) +{ + int mib[3], argmax, nargs, c = 0; + size_t size; + char *procargs, *sp, *np, *cp; + int show_args = 1; + + mib[0] = CTL_KERN; + mib[1] = KERN_ARGMAX; + + size = sizeof(argmax); + if (sysctl(mib, 2, &argmax, &size, NULL, 0) == -1) { + goto ERROR_A; + } + + /* Allocate space for the arguments. */ + procargs = (char *)malloc(argmax); + if (procargs == NULL) { + goto ERROR_A; + } + + + /* + * Make a sysctl() call to get the raw argument space of the process. + * The layout is documented in start.s, which is part of the Csu + * project. In summary, it looks like: + * + * /---------------\ 0x00000000 + * : : + * : : + * |---------------| + * | argc | + * |---------------| + * | arg[0] | + * |---------------| + * : : + * : : + * |---------------| + * | arg[argc - 1] | + * |---------------| + * | 0 | + * |---------------| + * | env[0] | + * |---------------| + * : : + * : : + * |---------------| + * | env[n] | + * |---------------| + * | 0 | + * |---------------| <-- Beginning of data returned by sysctl() is here. + * | argc | + * |---------------| + * | exec_path | + * |:::::::::::::::| + * | | + * | String area. | + * | | + * |---------------| <-- Top of stack. + * : : + * : : + * \---------------/ 0xffffffff + */ + mib[0] = CTL_KERN; + mib[1] = KERN_PROCARGS2; + mib[2] = pid; + + size = (size_t)argmax; + if (sysctl(mib, 3, procargs, &size, NULL, 0) == -1) { + goto ERROR_B; + } + + memcpy(&nargs, procargs, sizeof(nargs)); + cp = procargs + sizeof(nargs); + + /* Skip the saved exec_path. */ + for (; cp < &procargs[size]; cp++) { + if (*cp == '\0') { + /* End of exec_path reached. */ + break; + } + } + if (cp == &procargs[size]) { + goto ERROR_B; + } + + /* Skip trailing '\0' characters. */ + for (; cp < &procargs[size]; cp++) { + if (*cp != '\0') { + /* Beginning of first argument reached. */ + break; + } + } + if (cp == &procargs[size]) { + goto ERROR_B; + } + /* Save where the argv[0] string starts. */ + sp = cp; + + /* + * Iterate through the '\0'-terminated strings and convert '\0' to ' ' + * until a string is found that has a '=' character in it (or there are + * no more strings in procargs). There is no way to deterministically + * know where the command arguments end and the environment strings + * start, which is why the '=' character is searched for as a heuristic. + */ + for (np = NULL; c < nargs && cp < &procargs[size]; cp++) { + if (*cp == '\0') { + c++; + if (np != NULL) { + /* Convert previous '\0'. */ + *np = ' '; + } else { + /* *argv0len = cp - sp; */ + } + /* Note location of current '\0'. */ + np = cp; + + if (!show_args) { + /* + * Don't convert '\0' characters to ' '. + * However, we needed to know that the + * command name was terminated, which we + * now know. + */ + break; + } + } + } + + /* + * sp points to the beginning of the arguments/environment string, and + * np should point to the '\0' terminator for the string. + */ + if (np == NULL || np == sp) { + /* Empty or unterminated string. */ + goto ERROR_B; + } + + /* Make a copy of the string. */ + //printf("From function: %s\n", sp); + + /* Clean up. */ + free(procargs); + + *argv = [NSString stringWithCString:sp encoding:NSUTF8StringEncoding]; + return NO_ERROR; + +ERROR_B: + free(procargs); +ERROR_A: + //fprintf(stderr, "Sorry, failed\n"); + return ERR_UNKNOWN; +} \ No newline at end of file diff --git a/appshell/appshell_extensions_platform.h b/appshell/appshell_extensions_platform.h index 5603ca6f5..9d6faf0ff 100644 --- a/appshell/appshell_extensions_platform.h +++ b/appshell/appshell_extensions_platform.h @@ -44,6 +44,7 @@ static const int ERR_NOT_FILE = 8; static const int ERR_NOT_DIRECTORY = 9; static const int ERR_FILE_EXISTS = 10; static const int ERR_BROWSER_NOT_INSTALLED = 11; +static const int ERR_PID_NOT_FOUND = -9999; // negative int to avoid confusion with real PIDs #if defined(OS_WIN) typedef std::wstring ExtensionString;