Search code examples
objective-cxcodemacoscocoachromium-embedded

application:openFile: did report error when trying to open own file type


My application has it's own unique file type. When the file is double-clicked by the user and the application was not running, the application starts but reports the following error:

Error dialog on dbl-click

My application base on Chromium (CEF). I neither use application:applicationWillFinishLaunching nor application:applicationDidFinishLaunching for any setup parts but it seems that application:openFile did get called before the application is fully launched.

Questions

  • Is there a way to wait until the application is fully initialized to be able to open/handle the file correctly
  • Is there another method which can be called after the application is fully initialized

My AppDelegate implementation is as follow:

// ****************************************************************************
// application:openFile
// ****************************************************************************

- (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename
{
  return [self processFile:filename];
}

// ****************************************************************************
// processFile
// ****************************************************************************

- (BOOL)processFile:(NSString *)file
{
  std::string fileName([file UTF8String]);
  au::arcwork::Handler* handler = au::arcwork::Handler::GetInstance();
  handler->OnOpenFile(fileName);
  return YES;
}

Solution

  • After lot of research and debugging I came up with a solution how to implement application:openFile for Chromium based applications on OS X. First of all, there are 3 parts/layers which have to be solved.

    1. Cocoa part with application:openFile
    2. Chromium part with browser initialization code
    3. JavaScript part with its initialization part

    Beginning with 1:

    The Apple documentation for application:openFile already describes in the Discussion part, that application:openFile is called before applicationDidFinishLaunching. This means, that If you rely on a full initialized client (I can't imagine how's not?), you have to store the URL of the file somewhere, e.g. ivar/property in application:openFile or like in my example below in a std::vector of my Chromium handler. Only if the application if fully initialized and a browser window is displayed, then you're able to directly call the Chromium handler method that in turn calls the appropriate JavaScript function!

    // ***
    // application:openFile
    // ***
    
    - (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename {
      AUApplication * clientApp = (AUApplication *)theApplication;
      NSWindow* targetWindow = [clientApp findTargetWindow];
      //Check if browser window is up and running
      if (targetWindow) {
        [self processFile:filename];
      }
      else {
        //This method saves the file URL to open the file 
        //when the application+JavaScript is fully initialized
        au::test::Handler* handler = au::test::Handler::GetInstance();
        handler->AddPendingFile([filename UTF8String]); 
      }
      return YES;
    }
    
    // ***
    // processFile
    // ***
    
    - (BOOL)processFile:(NSString *)file
    {
      //This method calls the JavaScript function to open the file
      std::string fileName([file UTF8String]);
      au::test::Handler* handler = au::test::Handler::GetInstance();
      handler->OnOpenFile(fileName); 
      return YES;
    }
    

    The 2 part:

    On Chromium side you have to store the file URL in a appropriate structure. I'm using std::vector for that. The file URL is saved in the Chromium method OnLoadEnd. Here Chromium have loaded your HTML+JavaScript parts in the browser. But be careful. The initialization of JavaScript is not done yet! In my example below I have to assign a JavaScript property to store the file URL.

    // ***
    // Handler::OnLoadEnd
    // ***
    
    void Handler::OnLoadEnd(CefRefPtr<CefBrowser> browser,
                   CefRefPtr<CefFrame> frame,
                   int httpStatusCode) {
      if (!m_pendingOpenFiles.empty()) {
        std::string file(m_pendingOpenFiles[0]);
        std::cout << "Pending file for later opening: " << file << std::endl;
        //This method below is calling a JavaScript function to assign
        //a property `pendingFile`
        OnPendingFile(file);
        //Don't forget to pop the file URL afterwards 
        //or use another store container instead of `std::vector`
        //if you don't plan to implement `application:openFiles` as well!!!
        m_pendingOpenFiles.pop_back();
      }
    }
    

    The 3 part:

    On JavaScript side you have to check after the application initialization if the property pendingFile is assigned or not to open the file appropriately.