tenfourfox/webapprt/mac/webapprt.mm

429 lines
16 KiB
Plaintext

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
//PLAN:
// open my bundle, check for an override binary signature
// find the newest firefox. open its bundle, get the version number.
// if the firefox version is different than ours:
// delete our own binary,
// copy the newer webapprt binary from Firefox
// exec it, and quit
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <dirent.h>
#include <sys/stat.h>
#include <Cocoa/Cocoa.h>
#include <Foundation/Foundation.h>
#include "nsXPCOMGlue.h"
#include "nsINIParser.h"
#include "nsXPCOMPrivate.h" // for MAXPATHLEN and XPCOM_DLL
#include "nsXULAppAPI.h"
#include "nsComponentManagerUtils.h"
#include "nsCOMPtr.h"
#include "nsIFile.h"
#include "nsStringGlue.h"
#include "mozilla/AppData.h"
using namespace mozilla;
const char WEBAPPRT_EXECUTABLE[] = "webapprt-stub";
const char FXAPPINI_NAME[] = "application.ini";
const char WEBAPPINI_NAME[] = "webapp.ini";
const char WEBRTINI_NAME[] = "webapprt.ini";
//need the correct relative path here
const char APP_MACOS_PATH[] = "/Contents/MacOS/";
const char APP_RESOURCES_PATH[] = "/Contents/Resources/";
//the path to the WebappRT subdir within the Firefox app contents dir
const char WEBAPPRT_PATH[] = "webapprt/";
void ExecNewBinary(NSString* launchPath, NSDictionary* args);
NSString *PathToWebRT(NSString* alternateBinaryID);
NSException* MakeException(NSString* name, NSString* message);
void DisplayErrorAlert(NSString* title, NSString* message);
XRE_GetFileFromPathType XRE_GetFileFromPath;
XRE_CreateAppDataType XRE_CreateAppData;
XRE_FreeAppDataType XRE_FreeAppData;
XRE_mainType XRE_main;
const nsDynamicFunctionLoad kXULFuncs[] = {
{ "XRE_GetFileFromPath", (NSFuncPtr*) &XRE_GetFileFromPath },
{ "XRE_CreateAppData", (NSFuncPtr*) &XRE_CreateAppData },
{ "XRE_FreeAppData", (NSFuncPtr*) &XRE_FreeAppData },
{ "XRE_main", (NSFuncPtr*) &XRE_main },
{ nullptr, nullptr }
};
nsresult
AttemptGRELoad(char *greDir)
{
nsresult rv;
char xpcomDLLPath[MAXPATHLEN];
snprintf(xpcomDLLPath, MAXPATHLEN, "%s%s", greDir, XPCOM_DLL);
rv = XPCOMGlueStartup(xpcomDLLPath);
if (NS_FAILED(rv)) {
return rv;
}
rv = XPCOMGlueLoadXULFunctions(kXULFuncs);
if (NS_FAILED(rv)) {
return rv;
}
return rv;
}
int
main(int argc, char **argv)
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSDictionary *args = [[NSUserDefaults standardUserDefaults]
volatileDomainForName:NSArgumentDomain];
NSString *firefoxPath = nil;
NSString *alternateBinaryID = nil;
//this is our version, to be compared with the version of the binary we are asked to use
NSString* myVersion = [NSString stringWithFormat:@"%s", NS_STRINGIFY(GRE_BUILDID)];
NSLog(@"MY WEBAPPRT BUILDID: %@", myVersion);
//I need to look in our bundle first, before deciding what firefox binary to use
NSBundle* myBundle = [NSBundle mainBundle];
NSString* myBundlePath = [myBundle bundlePath];
alternateBinaryID = [myBundle objectForInfoDictionaryKey:@"FirefoxBinary"];
NSLog(@"found override firefox binary: %@", alternateBinaryID);
@try {
// Determine the runtime with which to run the application.
// Throws an exception with an error dialog if it can't find one.
firefoxPath = [args objectForKey:@"runtime"];
if (firefoxPath) {
NSLog(@"Runtime specified with -runtime flag: %@", firefoxPath);
} else {
firefoxPath = PathToWebRT(alternateBinaryID);
NSLog(@"Found runtime: %@", firefoxPath);
}
NSString* myWebRTPath = [myBundle pathForResource:@"webapprt"
ofType:nil];
if (!myWebRTPath) {
myWebRTPath = [myBundlePath stringByAppendingPathComponent:@"Contents"];
myWebRTPath = [myWebRTPath stringByAppendingPathComponent:@"MacOS"];
myWebRTPath = [myWebRTPath stringByAppendingPathComponent:@"webapprt"];
if ([[NSFileManager defaultManager] fileExistsAtPath:myWebRTPath] == NO) {
@throw MakeException(@"Missing Web Runtime Files",
@"Cannot locate binary for this App");
}
}
//GET FIREFOX BUILD ID
NSString *firefoxINIFilePath =
[NSString stringWithFormat:@"%@%s%s", firefoxPath, APP_RESOURCES_PATH,
FXAPPINI_NAME];
nsINIParser ffparser;
NSLog(@"Looking for firefox ini file here: %@", firefoxINIFilePath);
if (NS_FAILED(ffparser.Init([firefoxINIFilePath UTF8String]))) {
firefoxINIFilePath = [NSString stringWithFormat:@"%@%s%s", firefoxPath,
APP_MACOS_PATH,
FXAPPINI_NAME];
NSLog(@"Looking for firefox ini file here: %@", firefoxINIFilePath);
if (NS_FAILED(ffparser.Init([firefoxINIFilePath UTF8String]))) {
NSLog(@"Unable to locate Firefox application.ini");
@throw MakeException(@"Error",
@"Unable to parse environment files for application startup");
}
}
char ffVersChars[MAXPATHLEN];
if (NS_FAILED(ffparser.GetString("App", "BuildID", ffVersChars, MAXPATHLEN))) {
NSLog(@"Unable to retrieve Firefox BuildID");
@throw MakeException(@"Error", @"Unable to determine Firefox version.");
}
NSString* firefoxVersion = [NSString stringWithFormat:@"%s", ffVersChars];
NSLog(@"FIREFOX WEBAPPRT BUILDID: %@", firefoxVersion);
//GOT FIREFOX BUILD ID
//COMPARE MY BUILD ID AND FIREFOX BUILD ID
if ([myVersion compare: firefoxVersion] != NSOrderedSame) {
//we are going to assume that if they are different, we need to re-copy the webapprt, regardless of whether
// it is newer or older. If we don't find a webapprt, then the current Firefox must not be new enough to run webapps.
NSLog(@"### This Application has an old webrt. Updating it.");
NSLog(@"### My webapprt path: %@", myWebRTPath);
NSFileManager* fileClerk = [[NSFileManager alloc] init];
NSError *errorDesc = nil;
//we know the firefox path, so copy the new webapprt here
NSString *newWebRTPath =
[NSString stringWithFormat: @"%@%s%s", firefoxPath, APP_RESOURCES_PATH,
WEBAPPRT_EXECUTABLE];
NSLog(@"### Trying Firefox webapprt path: %@", newWebRTPath);
if (![fileClerk fileExistsAtPath:newWebRTPath]) {
newWebRTPath =
[NSString stringWithFormat: @"%@%s%s", firefoxPath, APP_MACOS_PATH,
WEBAPPRT_EXECUTABLE];
NSLog(@"### Trying Firefox webapprt path: %@", newWebRTPath);
if (![fileClerk fileExistsAtPath:newWebRTPath]) {
NSString* msg =
[NSString stringWithFormat:
@"This version of Firefox (%@) cannot run web applications, because it is not recent enough or damaged", firefoxVersion];
@throw MakeException(@"Missing Web Runtime Files", msg);
}
}
#if (MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_4)
[fileClerk removeFileAtPath: myWebRTPath handler:nil];
#else
[fileClerk removeItemAtPath: myWebRTPath error: &errorDesc];
#endif
if (errorDesc != nil) {
NSLog(@"failed to unlink old binary file at path: %@ with error: %@", myWebRTPath, errorDesc);
@throw MakeException(@"Unable To Update", @"Failed preparation for Web Runtime update");
}
#if (MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_4)
[fileClerk copyPath: newWebRTPath toPath: myWebRTPath handler:nil];
#else
[fileClerk copyItemAtPath: newWebRTPath toPath: myWebRTPath error: &errorDesc];
#endif
[fileClerk release];
if (errorDesc != nil) {
NSLog(@"failed to copy new webrt file: %@", errorDesc);
@throw MakeException(@"Unable To Update Web Runtime", @"Failed to update Web Runtime");
} else {
NSLog(@"### Successfully updated webapprt, relaunching");
}
//execv the new binary, and ride off into the sunset
ExecNewBinary(myWebRTPath, args);
} else {
//we are ready to load XUL and such, and go go go
NSLog(@"This Application has the newest webrt. Launching!");
int result = 0;
char rtINIPath[MAXPATHLEN];
// Set up our environment to know where webapp.ini was loaded from.
char appEnv[MAXPATHLEN];
snprintf(appEnv, MAXPATHLEN, "%s%s%s", [myBundlePath UTF8String],
APP_MACOS_PATH, WEBAPPINI_NAME);
if (setenv("XUL_APP_FILE", appEnv, 1)) {
NSLog(@"Couldn't set XUL_APP_FILE to: %s", appEnv);
@throw MakeException(@"Error", @"Unable to set Web Runtime INI file.");
}
NSLog(@"Set XUL_APP_FILE to: %s", appEnv);
//CONSTRUCT GREDIR AND CALL XPCOMGLUE WITH IT
char greDir[MAXPATHLEN];
snprintf(greDir, MAXPATHLEN, "%s%s", [firefoxPath UTF8String],
APP_RESOURCES_PATH);
if (!NS_SUCCEEDED(AttemptGRELoad(greDir))) {
@throw MakeException(@"Error", @"Unable to load XUL files for application startup");
}
// NOTE: The GRE has successfully loaded, so we can use XPCOM now
NS_LogInit();
{ // Scope for any XPCOM stuff we create
// Get the path to the runtime directory.
char rtDir[MAXPATHLEN];
snprintf(rtDir, MAXPATHLEN, "%s%s%s", [firefoxPath UTF8String],
APP_RESOURCES_PATH,
WEBAPPRT_PATH);
// Get the path to the runtime's INI file. This is in the runtime
// directory.
snprintf(rtINIPath, MAXPATHLEN, "%s%s%s%s", [firefoxPath UTF8String],
APP_RESOURCES_PATH,
WEBAPPRT_PATH,
WEBRTINI_NAME);
NSLog(@"WebappRT application.ini path: %s", rtINIPath);
// Load the runtime's INI from its path.
nsCOMPtr<nsIFile> rtINI;
if (NS_FAILED(XRE_GetFileFromPath(rtINIPath, getter_AddRefs(rtINI)))) {
NSLog(@"Runtime INI path not recognized: '%s'\n", rtINIPath);
@throw MakeException(@"Error", @"Incorrect path to base INI file.");
}
bool exists;
nsresult rv = rtINI->Exists(&exists);
if (NS_FAILED(rv) || !exists) {
NSString* msg = [NSString stringWithFormat: @"This copy of Firefox (%@) cannot run Apps, because it is missing the Web Runtime's application.ini file", firefoxVersion];
@throw MakeException(@"Missing Web Runtime application.ini", msg);
}
nsXREAppData *webShellAppData;
if (NS_FAILED(XRE_CreateAppData(rtINI, &webShellAppData))) {
NSLog(@"Couldn't read WebappRT application.ini: %s", rtINIPath);
@throw MakeException(@"Error", @"Unable to parse base INI file.");
}
NSString *profile = [args objectForKey:@"profile"];
if (profile) {
NSLog(@"Profile specified with -profile flag: %@", profile);
}
else {
nsINIParser parser;
if (NS_FAILED(parser.Init(appEnv))) {
NSLog(@"%s was not found\n", appEnv);
@throw MakeException(@"Error", @"Unable to parse environment files for application startup");
}
char profile[MAXPATHLEN];
if (NS_FAILED(parser.GetString("Webapp", "Profile", profile, MAXPATHLEN))) {
NSLog(@"Unable to retrieve profile from App INI file");
@throw MakeException(@"Error", @"Unable to retrieve installation profile.");
}
NSLog(@"setting app profile: %s", profile);
SetAllocatedString(webShellAppData->profile, profile);
}
nsCOMPtr<nsIFile> directory;
if (NS_FAILED(XRE_GetFileFromPath(rtDir, getter_AddRefs(directory)))) {
NSLog(@"Unable to open app dir");
@throw MakeException(@"Error", @"Unable to open App directory.");
}
nsCOMPtr<nsIFile> xreDir;
if (NS_FAILED(XRE_GetFileFromPath(greDir, getter_AddRefs(xreDir)))) {
NSLog(@"Unable to open XRE dir");
@throw MakeException(@"Error", @"Unable to open App XRE directory.");
}
xreDir.forget(&webShellAppData->xreDirectory);
NS_IF_RELEASE(webShellAppData->directory);
directory.forget(&webShellAppData->directory);
// There is only XUL.
result = XRE_main(argc, argv, webShellAppData, 0);
XRE_FreeAppData(webShellAppData);
}
NS_LogTerm();
return result;
}
}
@catch (NSException *e) {
NSLog(@"got exception: %@", e);
DisplayErrorAlert([e name], [e reason]);
}
@finally {
[pool drain];
}
return 0;
} //end main
NSException*
MakeException(NSString* name, NSString* message)
{
NSException* myException = [NSException
exceptionWithName:name
reason:message
userInfo:nil];
return myException;
}
void
DisplayErrorAlert(NSString* title, NSString* message)
{
CFUserNotificationDisplayNotice(0, kCFUserNotificationNoteAlertLevel,
NULL, NULL, NULL,
(CFStringRef)title,
(CFStringRef)message,
CFSTR("Quit")
);
}
/* Find the currently installed Firefox, if any, and return
* an absolute path to it. may return nil */
NSString
*PathToWebRT(NSString* alternateBinaryID)
{
//default is firefox
NSString *binaryPath = nil;
//we look for these flavors of Firefox, in this order
/*
NSArray* launchBinarySearchList = [NSArray arrayWithObjects: @"org.mozilla.nightly",
@"org.mozilla.firefoxdeveloperedition",
@"org.mozilla.firefox", nil];
*/
// If they provided a binary ID, use that.
if (alternateBinaryID != nil && ([alternateBinaryID length] > 0)) {
binaryPath = [[NSWorkspace sharedWorkspace] absolutePathForAppBundleWithIdentifier:alternateBinaryID];
if (binaryPath && [binaryPath length] > 0) {
return binaryPath;
}
}
//No override found, loop through the various flavors of firefox we have
// for (NSString* signature in launchBinarySearchList) {
// We only support this for TenFourFox.
NSString *signature = @"com.floodgap.tenfourfox";
NSLog(@"SEARCHING for webapprt, trying: %@", signature);
binaryPath = [[NSWorkspace sharedWorkspace] absolutePathForAppBundleWithIdentifier:signature];
if (binaryPath && [binaryPath length] > 0) {
return binaryPath;
}
// }
NSLog(@"unable to find a valid webrt path");
@throw MakeException(@"This App requires that Firefox version 16 or above is installed.", @"Firefox 16+ has not been detected.");
return nil;
}
void
ExecNewBinary(NSString* launchPath, NSDictionary* args)
{
#if(0)
NSLog(@" launching webrt at path: %@\n", launchPath);
NSUInteger numArgs = [args count];
const char *newargv[numArgs + 2];
NSMutableString *commandLine = [NSMutableString string];
newargv[0] = [launchPath UTF8String];
[commandLine appendString:launchPath];
NSUInteger i = 1;
for (id key in args) {
NSString *name = [@"-" stringByAppendingString:key];
NSString *value = [args objectForKey:key];
newargv[i++] = [name UTF8String];
newargv[i++] = [value UTF8String];
[commandLine appendFormat:@" %@ %@", name, value];
}
newargv[i] = NULL;
NSLog(@"Command line: '%@'", commandLine);
execv([launchPath UTF8String], (char **)newargv);
#else
NSLog(@"TenFourFoxBox is better than this");
#endif
}