michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: // System headers (alphabetical) michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: michael@0: // Mozilla headers (alphabetical) michael@0: #include "nsIFile.h" michael@0: #include "nsINIParser.h" michael@0: #include "nsWindowsWMain.cpp" // we want a wmain entry point michael@0: #include "nsXPCOMGlue.h" michael@0: #include "nsXPCOMPrivate.h" // for MAXPATHLEN and XPCOM_DLL michael@0: #include "nsXULAppAPI.h" michael@0: #include "mozilla/AppData.h" michael@0: michael@0: using namespace mozilla; michael@0: michael@0: XRE_GetFileFromPathType XRE_GetFileFromPath; michael@0: XRE_CreateAppDataType XRE_CreateAppData; michael@0: XRE_FreeAppDataType XRE_FreeAppData; michael@0: XRE_mainType XRE_main; michael@0: michael@0: namespace { michael@0: const char kAPP_INI[] = "application.ini"; michael@0: const char kWEBAPP_INI[] = "webapp.ini"; michael@0: const char kWEBAPPRT_INI[] = "webapprt.ini"; michael@0: const char kWEBAPPRT_PATH[] = "webapprt"; michael@0: const char kAPP_ENV_PREFIX[] = "XUL_APP_FILE="; michael@0: const char kAPP_RT[] = "webapprt-stub.exe"; michael@0: michael@0: const wchar_t kAPP_RT_BACKUP[] = L"webapprt.old"; michael@0: michael@0: wchar_t curExePath[MAXPATHLEN]; michael@0: wchar_t backupFilePath[MAXPATHLEN]; michael@0: wchar_t iconPath[MAXPATHLEN]; michael@0: char profile[MAXPATHLEN]; michael@0: bool isProfileOverridden = false; michael@0: int* pargc; michael@0: char*** pargv; michael@0: michael@0: nsresult michael@0: joinPath(char* const dest, michael@0: char const* const dir, michael@0: char const* const leaf, michael@0: size_t bufferSize) michael@0: { michael@0: size_t dirLen = strlen(dir); michael@0: size_t leafLen = strlen(leaf); michael@0: bool needsSeparator = (dirLen != 0 michael@0: && dir[dirLen-1] != '\\' michael@0: && leafLen != 0 michael@0: && leaf[0] != '\\'); michael@0: michael@0: if (dirLen + (needsSeparator? 1 : 0) + leafLen >= bufferSize) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: strncpy(dest, dir, bufferSize); michael@0: char* destEnd = dest + dirLen; michael@0: if (needsSeparator) { michael@0: *(destEnd++) = '\\'; michael@0: } michael@0: michael@0: strncpy(destEnd, leaf, leafLen); michael@0: return NS_OK; michael@0: } michael@0: michael@0: /** michael@0: * A helper class which calls NS_LogInit/NS_LogTerm in its scope. michael@0: */ michael@0: class ScopedLogging michael@0: { michael@0: public: michael@0: ScopedLogging() { NS_LogInit(); } michael@0: ~ScopedLogging() { NS_LogTerm(); } michael@0: }; michael@0: michael@0: /** michael@0: * A helper class for scope-guarding nsXREAppData. michael@0: */ michael@0: class ScopedXREAppData michael@0: { michael@0: public: michael@0: ScopedXREAppData() michael@0: : mAppData(nullptr) { } michael@0: michael@0: nsresult michael@0: create(nsIFile* aINIFile) michael@0: { michael@0: return XRE_CreateAppData(aINIFile, &mAppData); michael@0: } michael@0: michael@0: ~ScopedXREAppData() michael@0: { michael@0: if (nullptr != mAppData) { michael@0: XRE_FreeAppData(mAppData); michael@0: } michael@0: } michael@0: michael@0: nsXREAppData* const michael@0: operator->() michael@0: { michael@0: return get(); michael@0: } michael@0: michael@0: nsXREAppData michael@0: operator*() michael@0: { michael@0: return *get(); michael@0: } michael@0: michael@0: operator michael@0: nsXREAppData*() michael@0: { michael@0: return get(); michael@0: } michael@0: private: michael@0: nsXREAppData* mAppData; michael@0: nsXREAppData* const get() { return mAppData; } michael@0: }; michael@0: michael@0: void michael@0: Output(const wchar_t *fmt, ... ) michael@0: { michael@0: va_list ap; michael@0: va_start(ap, fmt); michael@0: michael@0: wchar_t msg[1024]; michael@0: _vsnwprintf_s(msg, _countof(msg), _countof(msg), fmt, ap); michael@0: michael@0: MessageBoxW(nullptr, msg, L"Web Runtime", MB_OK); michael@0: michael@0: va_end(ap); michael@0: } michael@0: michael@0: void michael@0: Output(const char *fmt, ... ) michael@0: { michael@0: va_list ap; michael@0: va_start(ap, fmt); michael@0: michael@0: char msg[1024]; michael@0: vsnprintf(msg, sizeof(msg), fmt, ap); michael@0: michael@0: wchar_t wide_msg[1024]; michael@0: MultiByteToWideChar(CP_UTF8, michael@0: 0, michael@0: msg, michael@0: -1, michael@0: wide_msg, michael@0: _countof(wide_msg)); michael@0: Output(wide_msg); michael@0: michael@0: va_end(ap); michael@0: } michael@0: michael@0: const nsDynamicFunctionLoad kXULFuncs[] = { michael@0: { "XRE_GetFileFromPath", (NSFuncPtr*) &XRE_GetFileFromPath }, michael@0: { "XRE_CreateAppData", (NSFuncPtr*) &XRE_CreateAppData }, michael@0: { "XRE_FreeAppData", (NSFuncPtr*) &XRE_FreeAppData }, michael@0: { "XRE_main", (NSFuncPtr*) &XRE_main }, michael@0: { nullptr, nullptr } michael@0: }; michael@0: michael@0: bool michael@0: AttemptCopyAndLaunch(wchar_t* src) michael@0: { michael@0: // Rename the old app executable michael@0: if (FALSE == ::MoveFileExW(curExePath, michael@0: backupFilePath, michael@0: MOVEFILE_REPLACE_EXISTING)) { michael@0: return false; michael@0: } michael@0: michael@0: // Copy webapprt-stub.exe from the Firefox dir to the app's dir michael@0: if (FALSE == ::CopyFileW(src, michael@0: curExePath, michael@0: TRUE)) { michael@0: // Try to move the old file back to its original location michael@0: ::MoveFileW(backupFilePath, michael@0: curExePath); michael@0: return false; michael@0: } michael@0: michael@0: // XXX: We will soon embed the app's icon in the EXE here michael@0: michael@0: STARTUPINFOW si; michael@0: PROCESS_INFORMATION pi; michael@0: michael@0: ::ZeroMemory(&si, sizeof(si)); michael@0: si.cb = sizeof(si); michael@0: ::ZeroMemory(&pi, sizeof(pi)); michael@0: michael@0: if (!CreateProcessW(curExePath, // Module name michael@0: nullptr, // Command line michael@0: nullptr, // Process handle not inheritable michael@0: nullptr, // Thread handle not inheritable michael@0: FALSE, // Set handle inheritance to FALSE michael@0: 0, // No creation flags michael@0: nullptr, // Use parent's environment block michael@0: nullptr, // Use parent's starting directory michael@0: &si, michael@0: &pi)) { michael@0: return false; michael@0: } michael@0: michael@0: // Close process and thread handles. michael@0: CloseHandle( pi.hProcess ); michael@0: CloseHandle( pi.hThread ); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: AttemptCopyAndLaunch(char* srcUtf8) michael@0: { michael@0: wchar_t src[MAXPATHLEN]; michael@0: if (0 == MultiByteToWideChar(CP_UTF8, michael@0: 0, michael@0: srcUtf8, michael@0: -1, michael@0: src, michael@0: MAXPATHLEN)) { michael@0: return false; michael@0: } michael@0: michael@0: return AttemptCopyAndLaunch(src); michael@0: } michael@0: michael@0: bool michael@0: AttemptGRELoadAndLaunch(char* greDir) michael@0: { michael@0: nsresult rv; michael@0: michael@0: char xpcomDllPath[MAXPATHLEN]; michael@0: rv = joinPath(xpcomDllPath, greDir, XPCOM_DLL, MAXPATHLEN); michael@0: NS_ENSURE_SUCCESS(rv, false); michael@0: michael@0: rv = XPCOMGlueStartup(xpcomDllPath); michael@0: NS_ENSURE_SUCCESS(rv, false); michael@0: michael@0: rv = XPCOMGlueLoadXULFunctions(kXULFuncs); michael@0: NS_ENSURE_SUCCESS(rv, false); michael@0: michael@0: // NOTE: The GRE has successfully loaded, so we can use XPCOM now michael@0: { // Scope for any XPCOM stuff we create michael@0: michael@0: ScopedLogging log; michael@0: michael@0: // Get the path to the runtime. michael@0: char rtPath[MAXPATHLEN]; michael@0: rv = joinPath(rtPath, greDir, kWEBAPPRT_PATH, MAXPATHLEN); michael@0: NS_ENSURE_SUCCESS(rv, false); michael@0: michael@0: // Get the path to the runtime's INI file. michael@0: char rtIniPath[MAXPATHLEN]; michael@0: rv = joinPath(rtIniPath, rtPath, kWEBAPPRT_INI, MAXPATHLEN); michael@0: NS_ENSURE_SUCCESS(rv, false); michael@0: michael@0: // Load the runtime's INI from its path. michael@0: nsCOMPtr rtINI; michael@0: rv = XRE_GetFileFromPath(rtIniPath, getter_AddRefs(rtINI)); michael@0: NS_ENSURE_SUCCESS(rv, false); michael@0: michael@0: bool exists; michael@0: rv = rtINI->Exists(&exists); michael@0: if (NS_FAILED(rv) || !exists) michael@0: return false; michael@0: michael@0: ScopedXREAppData webShellAppData; michael@0: rv = webShellAppData.create(rtINI); michael@0: NS_ENSURE_SUCCESS(rv, false); michael@0: michael@0: if (!isProfileOverridden) { michael@0: SetAllocatedString(webShellAppData->profile, profile); michael@0: SetAllocatedString(webShellAppData->name, profile); michael@0: } michael@0: michael@0: nsCOMPtr directory; michael@0: rv = XRE_GetFileFromPath(rtPath, getter_AddRefs(directory)); michael@0: NS_ENSURE_SUCCESS(rv, false); michael@0: michael@0: nsCOMPtr xreDir; michael@0: rv = XRE_GetFileFromPath(greDir, getter_AddRefs(xreDir)); michael@0: NS_ENSURE_SUCCESS(rv, false); michael@0: michael@0: xreDir.forget(&webShellAppData->xreDirectory); michael@0: NS_IF_RELEASE(webShellAppData->directory); michael@0: directory.forget(&webShellAppData->directory); michael@0: michael@0: // There is only XUL. michael@0: XRE_main(*pargc, *pargv, webShellAppData, 0); michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: AttemptLoadFromDir(char* firefoxDir) michael@0: { michael@0: nsresult rv; michael@0: michael@0: // Here we're going to open Firefox's application.ini michael@0: char appIniPath[MAXPATHLEN]; michael@0: rv = joinPath(appIniPath, firefoxDir, kAPP_INI, MAXPATHLEN); michael@0: NS_ENSURE_SUCCESS(rv, false); michael@0: michael@0: nsINIParser parser; michael@0: rv = parser.Init(appIniPath); michael@0: NS_ENSURE_SUCCESS(rv, false); michael@0: michael@0: // Get buildid of FF we're trying to load michael@0: char buildid[MAXPATHLEN]; // This isn't a path, so MAXPATHLEN doesn't michael@0: // necessarily make sense, but it's a michael@0: // convenient number to use. michael@0: rv = parser.GetString("App", michael@0: "BuildID", michael@0: buildid, michael@0: MAXPATHLEN); michael@0: NS_ENSURE_SUCCESS(rv, false); michael@0: michael@0: if (0 == strcmp(buildid, NS_STRINGIFY(GRE_BUILDID))) { michael@0: return AttemptGRELoadAndLaunch(firefoxDir); michael@0: } michael@0: michael@0: char webAppRTExe[MAXPATHLEN]; michael@0: rv = joinPath(webAppRTExe, firefoxDir, kAPP_RT, MAXPATHLEN); michael@0: NS_ENSURE_SUCCESS(rv, false); michael@0: michael@0: return AttemptCopyAndLaunch(webAppRTExe); michael@0: } michael@0: michael@0: bool michael@0: GetFirefoxDirFromRegistry(char* firefoxDir) michael@0: { michael@0: HKEY key; michael@0: wchar_t wideGreDir[MAXPATHLEN]; michael@0: michael@0: if (ERROR_SUCCESS != michael@0: RegOpenKeyExW(HKEY_LOCAL_MACHINE, michael@0: L"SOFTWARE\\Microsoft\\Windows" michael@0: L"\\CurrentVersion\\App paths\\firefox.exe", michael@0: 0, michael@0: KEY_READ, michael@0: &key)) { michael@0: return false; michael@0: } michael@0: michael@0: DWORD length = MAXPATHLEN * sizeof(wchar_t); michael@0: // XXX: When Vista/XP64 become our minimum supported client, we can use michael@0: // RegGetValue instead michael@0: if (ERROR_SUCCESS != RegQueryValueExW(key, michael@0: L"Path", michael@0: nullptr, michael@0: nullptr, michael@0: reinterpret_cast(wideGreDir), michael@0: &length)) { michael@0: RegCloseKey(key); michael@0: return false; michael@0: }; michael@0: RegCloseKey(key); michael@0: michael@0: // According to this article, we need to write our own null terminator: michael@0: // http://msdn.microsoft.com/en-us/library/ms724911%28v=vs.85%29.aspx michael@0: length = length / sizeof(wchar_t); michael@0: if (wideGreDir[length] != L'\0') { michael@0: if (length >= MAXPATHLEN) { michael@0: return false; michael@0: } michael@0: wideGreDir[length] = L'\0'; michael@0: } michael@0: michael@0: if (0 == WideCharToMultiByte(CP_UTF8, michael@0: 0, michael@0: wideGreDir, michael@0: -1, michael@0: firefoxDir, michael@0: MAXPATHLEN, michael@0: nullptr, michael@0: nullptr)) { michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: }; michael@0: michael@0: michael@0: michael@0: ////////////////////////////////////////////////////////////////////////////// michael@0: // main michael@0: // michael@0: // Note: XPCOM cannot be used until AttemptGRELoad has returned successfully. michael@0: int michael@0: main(int argc, char* argv[]) michael@0: { michael@0: pargc = &argc; michael@0: pargv = &argv; michael@0: nsresult rv; michael@0: char buffer[MAXPATHLEN]; michael@0: wchar_t wbuffer[MAXPATHLEN]; michael@0: michael@0: // Set up curEXEPath michael@0: if (!GetModuleFileNameW(0, wbuffer, MAXPATHLEN)) { michael@0: Output("Couldn't calculate the application directory."); michael@0: return 255; michael@0: } michael@0: wcsncpy(curExePath, wbuffer, MAXPATHLEN); michael@0: michael@0: // Get the current directory into wbuffer michael@0: wchar_t* lastSlash = wcsrchr(wbuffer, L'\\'); michael@0: if (!lastSlash) { michael@0: Output("Application directory format not understood."); michael@0: return 255; michael@0: } michael@0: *(++lastSlash) = L'\0'; michael@0: michael@0: // Set up backup file path michael@0: if (wcslen(wbuffer) + _countof(kAPP_RT_BACKUP) >= MAXPATHLEN) { michael@0: Output("Application directory path is too long (couldn't set up backup file path)."); michael@0: } michael@0: wcsncpy(lastSlash, kAPP_RT_BACKUP, _countof(kAPP_RT_BACKUP)); michael@0: wcsncpy(backupFilePath, wbuffer, MAXPATHLEN); michael@0: michael@0: *lastSlash = L'\0'; michael@0: michael@0: // Convert current directory to utf8 and stuff it in buffer michael@0: if (0 == WideCharToMultiByte(CP_UTF8, michael@0: 0, michael@0: wbuffer, michael@0: -1, michael@0: buffer, michael@0: MAXPATHLEN, michael@0: nullptr, michael@0: nullptr)) { michael@0: Output("Application directory could not be processed."); michael@0: return 255; michael@0: } michael@0: michael@0: // Check if the runtime was executed with the "-profile" argument michael@0: for (int i = 1; i < argc; i++) { michael@0: if (!strcmp(argv[i], "-profile")) { michael@0: isProfileOverridden = true; michael@0: break; michael@0: } michael@0: } michael@0: michael@0: // First attempt at loading Firefox binaries: michael@0: // Check if the webapprt is in the same directory as the Firefox binary. michael@0: // This is the case during WebappRT chrome and content tests. michael@0: if (AttemptLoadFromDir(buffer)) { michael@0: return 0; michael@0: } michael@0: michael@0: // Set up appIniPath with path to webapp.ini. michael@0: // This should be in the same directory as the running executable. michael@0: char appIniPath[MAXPATHLEN]; michael@0: if (NS_FAILED(joinPath(appIniPath, buffer, kWEBAPP_INI, MAXPATHLEN))) { michael@0: Output("Path to webapp.ini could not be processed."); michael@0: return 255; michael@0: } michael@0: michael@0: // Open webapp.ini as an INI file (as opposed to using the michael@0: // XRE webapp.ini-specific processing we do later) michael@0: nsINIParser parser; michael@0: if (NS_FAILED(parser.Init(appIniPath))) { michael@0: Output("Could not open webapp.ini"); michael@0: return 255; michael@0: } michael@0: michael@0: // Set up our environment to know where webapp.ini was loaded from. michael@0: char appEnv[MAXPATHLEN + _countof(kAPP_ENV_PREFIX)]; michael@0: strcpy(appEnv, kAPP_ENV_PREFIX); michael@0: strcpy(appEnv + _countof(kAPP_ENV_PREFIX) - 1, appIniPath); michael@0: if (putenv(appEnv)) { michael@0: Output("Couldn't set up app environment"); michael@0: return 255; michael@0: } michael@0: michael@0: if (!isProfileOverridden) { michael@0: // Get profile dir from webapp.ini michael@0: if (NS_FAILED(parser.GetString("Webapp", michael@0: "Profile", michael@0: profile, michael@0: MAXPATHLEN))) { michael@0: Output("Unable to retrieve profile from web app INI file"); michael@0: return 255; michael@0: } michael@0: } michael@0: michael@0: char firefoxDir[MAXPATHLEN]; michael@0: michael@0: // Second attempt at loading Firefox binaries: michael@0: // Get the location of Firefox from our webapp.ini michael@0: michael@0: // XXX: This string better be UTF-8... michael@0: rv = parser.GetString("WebappRT", michael@0: "InstallDir", michael@0: firefoxDir, michael@0: MAXPATHLEN); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: if (AttemptLoadFromDir(firefoxDir)) { michael@0: return 0; michael@0: } michael@0: } michael@0: michael@0: // Third attempt at loading Firefox binaries: michael@0: // Get the location of Firefox from the registry michael@0: if (GetFirefoxDirFromRegistry(firefoxDir)) { michael@0: if (AttemptLoadFromDir(firefoxDir)) { michael@0: // XXX: Write gre dir location to webapp.ini michael@0: return 0; michael@0: } michael@0: } michael@0: michael@0: // We've done all we know how to do to try to find and launch FF michael@0: Output("This app requires that Firefox version 16 or above is installed." michael@0: " Firefox 16+ has not been detected."); michael@0: return 255; michael@0: }