Wed, 31 Dec 2014 06:55:46 +0100
Added tag TORBROWSER_REPLICA for changeset 6474c204b198
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
7 // GTK headers
8 #include <gtk/gtk.h>
10 // Linux headers
11 #include <fcntl.h>
12 #include <unistd.h>
13 #include <dlfcn.h>
15 // Mozilla headers
16 #include "nsIFile.h"
17 #include "nsINIParser.h"
18 #include "nsXPCOMGlue.h"
19 #include "nsXPCOMPrivate.h" // for MAXPATHLEN and XPCOM_DLL
20 #include "nsXULAppAPI.h"
21 #include "BinaryPath.h"
23 const char kAPP_INI[] = "application.ini";
24 const char kWEBAPP_INI[] = "webapp.ini";
25 const char kWEBAPP_JSON[] = "webapp.json";
26 const char kWEBAPP_PACKAGE[] = "application.zip";
27 const char kWEBAPPRT_INI[] = "webapprt.ini";
28 const char kWEBAPPRT_PATH[] = "webapprt";
29 const char kAPP_ENV_VAR[] = "XUL_APP_FILE";
30 const char kAPP_RT[] = "webapprt-stub";
32 int* pargc;
33 char*** pargv;
34 char profile[MAXPATHLEN];
35 bool isProfileOverridden = false;
37 XRE_GetFileFromPathType XRE_GetFileFromPath;
38 XRE_CreateAppDataType XRE_CreateAppData;
39 XRE_FreeAppDataType XRE_FreeAppData;
40 XRE_mainType XRE_main;
42 const nsDynamicFunctionLoad kXULFuncs[] = {
43 { "XRE_GetFileFromPath", (NSFuncPtr*) &XRE_GetFileFromPath },
44 { "XRE_CreateAppData", (NSFuncPtr*) &XRE_CreateAppData },
45 { "XRE_FreeAppData", (NSFuncPtr*) &XRE_FreeAppData },
46 { "XRE_main", (NSFuncPtr*) &XRE_main },
47 { nullptr, nullptr }
48 };
50 class ScopedLogging
51 {
52 public:
53 ScopedLogging() { NS_LogInit(); }
54 ~ScopedLogging() { NS_LogTerm(); }
55 };
57 // Copied from toolkit/xre/nsAppData.cpp.
58 void SetAllocatedString(const char *&str, const char *newvalue)
59 {
60 NS_Free(const_cast<char*>(str));
61 if (newvalue) {
62 str = NS_strdup(newvalue);
63 }
64 else {
65 str = nullptr;
66 }
67 }
69 // Function to open a dialog box displaying the message provided
70 void ErrorDialog(const char* message)
71 {
72 gtk_init(pargc, pargv);
74 GtkWidget* dialog = gtk_message_dialog_new(nullptr, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, "%s", message);
75 gtk_window_set_title(GTK_WINDOW(dialog), "Error launching webapp");
76 gtk_window_set_skip_taskbar_hint(GTK_WINDOW(dialog), false);
77 gtk_dialog_run(GTK_DIALOG(dialog));
78 gtk_widget_destroy(dialog);
79 }
81 // Function to get the parent dir of a file
82 void GetDirFromPath(char* parentDir, char* filePath)
83 {
84 char* base = strrchr(filePath, '/');
86 if (!base) {
87 strcpy(parentDir, ".");
88 }
89 else {
90 while (base > filePath && *base == '/')
91 base--;
93 int len = 1 + base - filePath;
94 strncpy(parentDir, filePath, len);
95 parentDir[len] = '\0';
96 }
97 }
99 bool CopyFile(const char* inputFile, const char* outputFile)
100 {
101 // Open input file
102 int inputFD = open(inputFile, O_RDONLY);
103 if (!inputFD)
104 return false;
106 // Open output file
107 int outputFD = creat(outputFile, S_IRWXU);
108 if (!outputFD)
109 return false;
111 // Copy file
112 char buf[BUFSIZ];
113 ssize_t bytesRead;
115 while ((bytesRead = read(inputFD, buf, BUFSIZ)) > 0) {
116 ssize_t bytesWritten = write(outputFD, buf, bytesRead);
117 if (bytesWritten < 0) {
118 bytesRead = -1;
119 break;
120 }
121 }
123 // Close file descriptors
124 close(inputFD);
125 close(outputFD);
127 return (bytesRead >= 0);
128 }
130 bool GRELoadAndLaunch(const char* firefoxDir, bool silentFail)
131 {
132 char xpcomDllPath[MAXPATHLEN];
133 snprintf(xpcomDllPath, MAXPATHLEN, "%s/%s", firefoxDir, XPCOM_DLL);
135 if (silentFail && access(xpcomDllPath, F_OK) != 0)
136 return false;
138 if (NS_FAILED(XPCOMGlueStartup(xpcomDllPath))) {
139 ErrorDialog("Couldn't load the XPCOM library");
140 return false;
141 }
143 if (NS_FAILED(XPCOMGlueLoadXULFunctions(kXULFuncs))) {
144 ErrorDialog("Couldn't load libxul");
145 return false;
146 }
148 // NOTE: The GRE has successfully loaded, so we can use XPCOM now
149 { // Scope for any XPCOM stuff we create
150 ScopedLogging log;
152 // Get the path to the runtime
153 char rtPath[MAXPATHLEN];
154 snprintf(rtPath, MAXPATHLEN, "%s/%s", firefoxDir, kWEBAPPRT_PATH);
156 // Get the path to the runtime's INI file
157 char rtIniPath[MAXPATHLEN];
158 snprintf(rtIniPath, MAXPATHLEN, "%s/%s", rtPath, kWEBAPPRT_INI);
160 // Load the runtime's INI from its path
161 nsCOMPtr<nsIFile> rtINI;
162 if (NS_FAILED(XRE_GetFileFromPath(rtIniPath, getter_AddRefs(rtINI)))) {
163 ErrorDialog("Couldn't load the runtime INI");
164 return false;
165 }
167 bool exists;
168 nsresult rv = rtINI->Exists(&exists);
169 if (NS_FAILED(rv) || !exists) {
170 ErrorDialog("The runtime INI doesn't exist");
171 return false;
172 }
174 nsXREAppData *webShellAppData;
175 if (NS_FAILED(XRE_CreateAppData(rtINI, &webShellAppData))) {
176 ErrorDialog("Couldn't read WebappRT application.ini");
177 return false;
178 }
180 if (!isProfileOverridden) {
181 SetAllocatedString(webShellAppData->profile, profile);
182 // nsXREAppData::name is used for the class name part of the WM_CLASS
183 // property. Set it so that the DE can match our window to the correct
184 // launcher.
185 char programClass[MAXPATHLEN];
186 snprintf(programClass, MAXPATHLEN, "owa-%s", profile);
187 SetAllocatedString(webShellAppData->name, programClass);
188 }
190 nsCOMPtr<nsIFile> directory;
191 if (NS_FAILED(XRE_GetFileFromPath(rtPath, getter_AddRefs(directory)))) {
192 ErrorDialog("Couldn't open runtime directory");
193 return false;
194 }
196 nsCOMPtr<nsIFile> xreDir;
197 if (NS_FAILED(XRE_GetFileFromPath(firefoxDir, getter_AddRefs(xreDir)))) {
198 ErrorDialog("Couldn't open XRE directory");
199 return false;
200 }
202 xreDir.forget(&webShellAppData->xreDirectory);
203 NS_IF_RELEASE(webShellAppData->directory);
204 directory.forget(&webShellAppData->directory);
206 XRE_main(*pargc, *pargv, webShellAppData, 0);
208 XRE_FreeAppData(webShellAppData);
209 }
211 return true;
212 }
214 void CopyAndRelaunch(const char* firefoxDir, const char* curExePath)
215 {
216 char newExePath[MAXPATHLEN];
217 snprintf(newExePath, MAXPATHLEN, "%s/%s", firefoxDir, kAPP_RT);
219 if (unlink(curExePath) == -1) {
220 ErrorDialog("Couldn't remove the old webapprt-stub executable");
221 return;
222 }
224 if (!CopyFile(newExePath, curExePath)) {
225 ErrorDialog("Couldn't copy the new webapprt-stub executable");
226 return;
227 }
229 execv(curExePath, *pargv);
231 ErrorDialog("Couldn't execute the new webapprt-stub executable");
232 }
234 void RemoveApplication(nsINIParser& parser, const char* curExeDir, const char* profile) {
235 if (!isProfileOverridden) {
236 // Remove the desktop entry file.
237 char desktopEntryFilePath[MAXPATHLEN];
239 char* dataDir = getenv("XDG_DATA_HOME");
241 if (dataDir && *dataDir) {
242 snprintf(desktopEntryFilePath, MAXPATHLEN, "%s/applications/owa-%s.desktop", dataDir, profile);
243 } else {
244 char* home = getenv("HOME");
245 snprintf(desktopEntryFilePath, MAXPATHLEN, "%s/.local/share/applications/owa-%s.desktop", home, profile);
246 }
248 unlink(desktopEntryFilePath);
249 }
251 // Remove the files from the installation directory.
252 char webAppIniPath[MAXPATHLEN];
253 snprintf(webAppIniPath, MAXPATHLEN, "%s/%s", curExeDir, kWEBAPP_INI);
254 unlink(webAppIniPath);
256 char curExePath[MAXPATHLEN];
257 snprintf(curExePath, MAXPATHLEN, "%s/%s", curExeDir, kAPP_RT);
258 unlink(curExePath);
260 char webAppJsonPath[MAXPATHLEN];
261 snprintf(webAppJsonPath, MAXPATHLEN, "%s/%s", curExeDir, kWEBAPP_JSON);
262 unlink(webAppJsonPath);
264 char iconPath[MAXPATHLEN];
265 snprintf(iconPath, MAXPATHLEN, "%s/icon.png", curExeDir);
266 unlink(iconPath);
268 char packagePath[MAXPATHLEN];
269 snprintf(packagePath, MAXPATHLEN, "%s/%s", curExeDir, kWEBAPP_PACKAGE);
270 unlink(packagePath);
272 char appName[MAXPATHLEN];
273 if (NS_FAILED(parser.GetString("Webapp", "Name", appName, MAXPATHLEN))) {
274 strcpy(appName, profile);
275 }
277 char uninstallMsg[MAXPATHLEN];
278 if (NS_SUCCEEDED(parser.GetString("Webapp", "UninstallMsg", uninstallMsg, MAXPATHLEN))) {
279 /**
280 * The only difference between libnotify.so.4 and libnotify.so.1 for these symbols
281 * is that notify_notification_new takes three arguments in libnotify.so.4 and
282 * four in libnotify.so.1.
283 * Passing the fourth argument as nullptr is binary compatible.
284 */
285 typedef void (*notify_init_t)(const char*);
286 typedef void* (*notify_notification_new_t)(const char*, const char*, const char*, const char*);
287 typedef void (*notify_notification_show_t)(void*, void**);
289 void *handle = dlopen("libnotify.so.4", RTLD_LAZY);
290 if (!handle) {
291 handle = dlopen("libnotify.so.1", RTLD_LAZY);
292 if (!handle)
293 return;
294 }
296 notify_init_t nn_init = (notify_init_t)(uintptr_t)dlsym(handle, "notify_init");
297 notify_notification_new_t nn_new = (notify_notification_new_t)(uintptr_t)dlsym(handle, "notify_notification_new");
298 notify_notification_show_t nn_show = (notify_notification_show_t)(uintptr_t)dlsym(handle, "notify_notification_show");
299 if (!nn_init || !nn_new || !nn_show) {
300 dlclose(handle);
301 return;
302 }
304 nn_init(appName);
306 void* n = nn_new(uninstallMsg, nullptr, "dialog-information", nullptr);
308 nn_show(n, nullptr);
310 dlclose(handle);
311 }
312 }
314 int main(int argc, char *argv[])
315 {
316 pargc = &argc;
317 pargv = &argv;
319 // Get current executable path
320 char curExePath[MAXPATHLEN];
321 if (NS_FAILED(mozilla::BinaryPath::Get(argv[0], curExePath))) {
322 ErrorDialog("Couldn't read current executable path");
323 return 255;
324 }
325 char curExeDir[MAXPATHLEN];
326 GetDirFromPath(curExeDir, curExePath);
328 bool removeApp = false;
329 for (int i = 1; i < argc; i++) {
330 if (!strcmp(argv[i], "-profile")) {
331 isProfileOverridden = true;
332 }
333 else if (!strcmp(argv[i], "-remove")) {
334 removeApp = true;
335 }
336 }
338 char firefoxDir[MAXPATHLEN];
340 // Check if Firefox is in the same directory as the webapp runtime.
341 // This is the case for webapprt chrome and content tests.
342 if (GRELoadAndLaunch(curExeDir, true)) {
343 return 0;
344 }
346 // Set up webAppIniPath with path to webapp.ini
347 char webAppIniPath[MAXPATHLEN];
348 snprintf(webAppIniPath, MAXPATHLEN, "%s/%s", curExeDir, kWEBAPP_INI);
350 // Open webapp.ini as an INI file
351 nsINIParser parser;
352 if (NS_FAILED(parser.Init(webAppIniPath))) {
353 ErrorDialog("Couldn't open webapp.ini");
354 return 255;
355 }
357 // Set up our environment to know where webapp.ini was loaded from
358 if (setenv(kAPP_ENV_VAR, webAppIniPath, 1) == -1) {
359 ErrorDialog("Couldn't set up app environment");
360 return 255;
361 }
363 // Get profile dir from webapp.ini
364 if (NS_FAILED(parser.GetString("Webapp", "Profile", profile, MAXPATHLEN))) {
365 ErrorDialog("Couldn't retrieve profile from web app INI file");
366 return 255;
367 }
369 if (removeApp) {
370 RemoveApplication(parser, curExeDir, profile);
371 return 0;
372 }
374 // Get the location of Firefox from our webapp.ini
375 if (NS_FAILED(parser.GetString("WebappRT", "InstallDir", firefoxDir, MAXPATHLEN))) {
376 ErrorDialog("Couldn't find your Firefox install directory.");
377 return 255;
378 }
380 // Set up appIniPath with path to application.ini.
381 // This is in the Firefox installation directory.
382 char appIniPath[MAXPATHLEN];
383 snprintf(appIniPath, MAXPATHLEN, "%s/%s", firefoxDir, kAPP_INI);
385 if (NS_FAILED(parser.Init(appIniPath))) {
386 ErrorDialog("This app requires that Firefox version 16 or above is installed."
387 " Firefox 16+ has not been detected.");
388 return 255;
389 }
391 // Get buildid of Firefox we're trying to load (MAXPATHLEN only for convenience)
392 char buildid[MAXPATHLEN];
393 if (NS_FAILED(parser.GetString("App", "BuildID", buildid, MAXPATHLEN))) {
394 ErrorDialog("Couldn't read BuildID from Firefox application.ini");
395 return 255;
396 }
398 // If WebAppRT version == Firefox version, load XUL and execute the application
399 if (!strcmp(buildid, NS_STRINGIFY(GRE_BUILDID))) {
400 if (GRELoadAndLaunch(firefoxDir, false))
401 return 0;
402 }
403 // Else, copy WebAppRT from Firefox installation and re-execute the process
404 else
405 CopyAndRelaunch(firefoxDir, curExePath);
407 return 255;
408 }