1/*
2 * Copyright (C) 2007 Ryan Leavengood <leavengood@gmail.com>
3 * Copyright (C) 2010 Stephan A��mus <superstippi@gmx.de>
4 *
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *	notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *	notice, this list of conditions and the following disclaimer in the
14 *	documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
17 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
20 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
21 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
22 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
23 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
24 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29#include "BrowserApp.h"
30
31#include <AboutWindow.h>
32#include <Alert.h>
33#include <Autolock.h>
34#include <Catalog.h>
35#include <Directory.h>
36#include <Entry.h>
37#include <FindDirectory.h>
38#include <Locale.h>
39#include <Path.h>
40#include <Screen.h>
41#include <UrlContext.h>
42#include <debugger.h>
43
44#include <stdio.h>
45
46#include "BrowserWindow.h"
47#include "BrowsingHistory.h"
48#include "DownloadWindow.h"
49#include "SettingsMessage.h"
50#include "SettingsWindow.h"
51#include "ConsoleWindow.h"
52#include "CookieWindow.h"
53#include "NetworkCookieJar.h"
54#include "WebKitInfo.h"
55#include "WebPage.h"
56#include "WebSettings.h"
57#include "WebView.h"
58#include "WebViewConstants.h"
59
60
61#undef B_TRANSLATION_CONTEXT
62#define B_TRANSLATION_CONTEXT "WebPositive"
63
64const char* kApplicationSignature = "application/x-vnd.Haiku-WebPositive";
65const char* kApplicationName = B_TRANSLATE_SYSTEM_NAME("WebPositive");
66static const uint32 PRELOAD_BROWSING_HISTORY = 'plbh';
67
68
69BrowserApp::BrowserApp()
70	:
71	BApplication(kApplicationSignature),
72	fWindowCount(0),
73	fLastWindowFrame(50, 50, 950, 750),
74	fLaunchRefsMessage(0),
75	fInitialized(false),
76	fSettings(NULL),
77	fCookies(NULL),
78	fSession(NULL),
79	fContext(NULL),
80	fDownloadWindow(NULL),
81	fSettingsWindow(NULL),
82	fConsoleWindow(NULL),
83	fCookieWindow(NULL)
84{
85#ifdef __i386__
86	// First let's check SSE2 is available
87	cpuid_info info;
88	get_cpuid(&info, 1, 0);
89
90	if ((info.eax_1.features & (1 << 26)) == 0) {
91		BAlert alert(B_TRANSLATE("No SSE2 support"), B_TRANSLATE("Your CPU is "
92			"too old and does not support the SSE2 extensions, without which "
93			"WebPositive cannot run. We recommend installing NetSurf instead."),
94			B_TRANSLATE("Darn!"));
95		alert.Go();
96		exit(-1);
97	}
98#endif
99
100	BString cookieStorePath = kApplicationName;
101	cookieStorePath << "/Cookies";
102	fCookies = new SettingsMessage(B_USER_SETTINGS_DIRECTORY,
103		cookieStorePath.String());
104	fContext = new BPrivate::Network::BUrlContext();
105	if (fCookies->InitCheck() == B_OK) {
106		BMessage cookieArchive = fCookies->GetValue("cookies", cookieArchive);
107		fContext->SetCookieJar(
108			BPrivate::Network::BNetworkCookieJar(&cookieArchive));
109	}
110
111	BPath curlCookies;
112	if (find_directory(B_USER_SETTINGS_DIRECTORY, &curlCookies) == B_OK
113		&& curlCookies.Append(kApplicationName) == B_OK
114		&& curlCookies.Append("cookie.jar.db") == B_OK) {
115
116		setenv("CURL_COOKIE_JAR_PATH", curlCookies.Path(), 0);
117	}
118
119	BString sessionStorePath = kApplicationName;
120	sessionStorePath << "/Session";
121	fSession = new SettingsMessage(B_USER_SETTINGS_DIRECTORY,
122		sessionStorePath.String());
123}
124
125
126BrowserApp::~BrowserApp()
127{
128	delete fLaunchRefsMessage;
129	delete fSettings;
130	delete fCookies;
131	delete fSession;
132}
133
134
135void
136BrowserApp::AboutRequested()
137{
138	BAboutWindow* window = new BAboutWindow(kApplicationName,
139		kApplicationSignature);
140
141	// create the about window
142
143	const char* authors[] = {
144		"Andrea Anzani",
145		"Stephan A��mus",
146		"Alexandre Deckner",
147		"Adrien Destugues",
148		"Rajagopalan Gangadharan",
149		"Rene Gollent",
150		"Ryan Leavengood",
151		"Michael Lotz",
152		"Maxime Simon",
153		NULL
154	};
155
156	BString aboutText("");
157	aboutText << "HaikuWebKit " << WebKitInfo::HaikuWebKitVersion();
158	aboutText << "\nWebKit " << WebKitInfo::WebKitVersion();
159
160	window->AddCopyright(2007, "Haiku, Inc.");
161	window->AddAuthors(authors);
162	window->AddExtraInfo(aboutText.String());
163
164	window->Show();
165}
166
167
168void
169BrowserApp::ArgvReceived(int32 argc, char** argv)
170{
171	BMessage message(B_REFS_RECEIVED);
172	for (int i = 1; i < argc; i++) {
173		if (strcmp("-f", argv[i]) == 0
174			|| strcmp("--fullscreen", argv[i]) == 0) {
175			message.AddBool("fullscreen", true);
176			continue;
177		}
178		const char* url = argv[i];
179		BEntry entry(argv[i], true);
180		BPath path;
181		if (entry.Exists() && entry.GetPath(&path) == B_OK)
182			url = path.Path();
183		message.AddString("url", url);
184	}
185	// Upon program launch, it will buffer a copy of the message, since
186	// ArgReceived() is called before ReadyToRun().
187	RefsReceived(&message);
188}
189
190
191void
192BrowserApp::ReadyToRun()
193{
194	// Since we will essentially run the GUI...
195	set_thread_priority(Thread(), B_DISPLAY_PRIORITY);
196
197	BWebPage::InitializeOnce();
198	BWebPage::SetCacheModel(B_WEBKIT_CACHE_MODEL_WEB_BROWSER);
199
200	BPath path;
201	if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) == B_OK
202		&& path.Append(kApplicationName) == B_OK
203		&& create_directory(path.Path(), 0777) == B_OK) {
204
205		BWebSettings::SetPersistentStoragePath(path.Path());
206	}
207
208	BString mainSettingsPath(kApplicationName);
209	mainSettingsPath << "/Application";
210	fSettings = new SettingsMessage(B_USER_SETTINGS_DIRECTORY,
211		mainSettingsPath.String());
212
213	fLastWindowFrame = fSettings->GetValue("window frame", fLastWindowFrame);
214	BRect defaultDownloadWindowFrame(-10, -10, 365, 265);
215	BRect downloadWindowFrame = fSettings->GetValue("downloads window frame",
216		defaultDownloadWindowFrame);
217	BRect settingsWindowFrame = fSettings->GetValue("settings window frame",
218		BRect());
219	BRect consoleWindowFrame = fSettings->GetValue("console window frame",
220		BRect(50, 50, 400, 300));
221	BRect cookieWindowFrame = fSettings->GetValue("cookie window frame",
222		BRect(50, 50, 400, 300));
223	bool showDownloads = fSettings->GetValue("show downloads", false);
224
225	fDownloadWindow = new DownloadWindow(downloadWindowFrame, showDownloads,
226		fSettings);
227	if (downloadWindowFrame == defaultDownloadWindowFrame) {
228		// Initially put download window in lower right of screen.
229		BRect screenFrame = BScreen().Frame();
230		BMessage decoratorSettings;
231		fDownloadWindow->GetDecoratorSettings(&decoratorSettings);
232		float borderWidth = 0;
233		if (decoratorSettings.FindFloat("border width", &borderWidth) != B_OK)
234			borderWidth = 5;
235		fDownloadWindow->MoveTo(screenFrame.Width()
236			- fDownloadWindow->Frame().Width() - borderWidth,
237			screenFrame.Height() - fDownloadWindow->Frame().Height()
238			- borderWidth);
239	}
240	fSettingsWindow = new SettingsWindow(settingsWindowFrame, fSettings);
241
242	BWebPage::SetDownloadListener(BMessenger(fDownloadWindow));
243
244	fConsoleWindow = new ConsoleWindow(consoleWindowFrame);
245	fCookieWindow = new CookieWindow(cookieWindowFrame, fContext->GetCookieJar());
246
247	fInitialized = true;
248
249	int32 pagesCreated = 0;
250	bool fullscreen = false;
251	if (fLaunchRefsMessage) {
252		_RefsReceived(fLaunchRefsMessage, &pagesCreated, &fullscreen);
253		delete fLaunchRefsMessage;
254		fLaunchRefsMessage = NULL;
255	}
256
257	// If no refs led to a new open page, open new session if set
258	if (fSession->InitCheck() == B_OK && pagesCreated == 0) {
259		const char* kSettingsKeyStartUpPolicy = "start up policy";
260		uint32 fStartUpPolicy = fSettings->GetValue(kSettingsKeyStartUpPolicy,
261			(uint32)ResumePriorSession);
262		if (fStartUpPolicy == StartNewSession) {
263			PostMessage(NEW_WINDOW);
264		} else {
265			// otherwise, restore previous session
266			BMessage archivedWindow;
267			for (int i = 0; fSession->FindMessage("window", i, &archivedWindow)
268				== B_OK; i++) {
269				BRect frame = archivedWindow.FindRect("window frame");
270				BString url;
271				archivedWindow.FindString("tab", 0, &url);
272				BrowserWindow* window = new(std::nothrow) BrowserWindow(frame,
273					fSettings, url, fContext);
274
275				if (window != NULL) {
276					window->Show();
277					pagesCreated++;
278
279					for (int j = 1; archivedWindow.FindString("tab", j, &url)
280						== B_OK; j++) {
281						printf("Create %d:%d\n", i, j);
282						_CreateNewTab(window, url, false);
283						pagesCreated++;
284					}
285				}
286			}
287		}
288	}
289
290	// If previous session did not contain any window, create a new empty one.
291	if (pagesCreated == 0)
292		_CreateNewWindow("", fullscreen);
293
294	PostMessage(PRELOAD_BROWSING_HISTORY);
295}
296
297
298void
299BrowserApp::MessageReceived(BMessage* message)
300{
301	switch (message->what) {
302	case PRELOAD_BROWSING_HISTORY:
303		// Accessing the default instance will load the history from disk.
304		BrowsingHistory::DefaultInstance();
305		break;
306	case B_SILENT_RELAUNCH:
307		_CreateNewPage("");
308		break;
309	case NEW_WINDOW: {
310		BString url;
311		if (message->FindString("url", &url) != B_OK)
312			break;
313		_CreateNewWindow(url);
314		break;
315	}
316	case NEW_TAB: {
317		BrowserWindow* window;
318		if (message->FindPointer("window",
319			reinterpret_cast<void**>(&window)) != B_OK)
320			break;
321		BString url;
322		message->FindString("url", &url);
323		bool select = false;
324		message->FindBool("select", &select);
325		_CreateNewTab(window, url, select);
326		break;
327	}
328	case WINDOW_OPENED:
329		fWindowCount++;
330		fDownloadWindow->SetMinimizeOnClose(false);
331		break;
332	case WINDOW_CLOSED:
333		fWindowCount--;
334		message->FindRect("window frame", &fLastWindowFrame);
335		if (fWindowCount <= 0) {
336			BMessage* message = new BMessage(B_QUIT_REQUESTED);
337			message->AddMessage("window", DetachCurrentMessage());
338			PostMessage(message);
339		}
340		break;
341
342	case SHOW_DOWNLOAD_WINDOW:
343		_ShowWindow(message, fDownloadWindow);
344		break;
345	case SHOW_SETTINGS_WINDOW:
346		_ShowWindow(message, fSettingsWindow);
347		break;
348	case SHOW_CONSOLE_WINDOW:
349		_ShowWindow(message, fConsoleWindow);
350		break;
351	case SHOW_COOKIE_WINDOW:
352		_ShowWindow(message, fCookieWindow);
353		break;
354	case ADD_CONSOLE_MESSAGE:
355		fConsoleWindow->PostMessage(message);
356		break;
357
358	default:
359		BApplication::MessageReceived(message);
360		break;
361	}
362}
363
364
365void
366BrowserApp::RefsReceived(BMessage* message)
367{
368	if (!fInitialized) {
369		delete fLaunchRefsMessage;
370		fLaunchRefsMessage = new BMessage(*message);
371		return;
372	}
373
374	_RefsReceived(message);
375}
376
377
378bool
379BrowserApp::QuitRequested()
380{
381	if (fDownloadWindow->DownloadsInProgress()) {
382		BAlert* alert = new BAlert(B_TRANSLATE("Downloads in progress"),
383			B_TRANSLATE("There are still downloads in progress, do you really "
384			"want to quit WebPositive now?"), B_TRANSLATE("Quit"),
385			B_TRANSLATE("Continue downloads"));
386		int32 choice = alert->Go();
387		if (choice == 1) {
388			if (fWindowCount == 0) {
389				if (fDownloadWindow->Lock()) {
390					fDownloadWindow->SetWorkspaces(1 << current_workspace());
391					if (fDownloadWindow->IsHidden())
392						fDownloadWindow->Show();
393					else
394						fDownloadWindow->Activate();
395					fDownloadWindow->SetMinimizeOnClose(true);
396					fDownloadWindow->Unlock();
397					return false;
398				}
399			} else
400				return false;
401		}
402	}
403
404	fSession->MakeEmpty();
405
406	/* See if we got here because the last window is already closed.
407	 * In that case we only need to save that one, which is already archived */
408	BMessage* message = CurrentMessage();
409	BMessage windowMessage;
410
411	status_t ret = message->FindMessage("window", &windowMessage);
412	if (ret == B_OK) {
413		fSession->AddMessage("window", &windowMessage);
414	} else {
415		for (int i = 0; BWindow* window = WindowAt(i); i++) {
416			BrowserWindow* webWindow = dynamic_cast<BrowserWindow*>(window);
417			if (!webWindow)
418				continue;
419			if (!webWindow->Lock())
420				continue;
421
422			BMessage windowArchive;
423			webWindow->Archive(&windowArchive, true);
424			fSession->AddMessage("window", &windowArchive);
425
426			if (webWindow->QuitRequested()) {
427				fLastWindowFrame = webWindow->WindowFrame();
428				webWindow->Quit();
429				i--;
430			} else {
431				webWindow->Unlock();
432				return false;
433			}
434		}
435	}
436
437	BWebPage::ShutdownOnce();
438
439	fSettings->SetValue("window frame", fLastWindowFrame);
440	if (fDownloadWindow->Lock()) {
441		fSettings->SetValue("downloads window frame", fDownloadWindow->Frame());
442		fSettings->SetValue("show downloads", !fDownloadWindow->IsHidden());
443		fDownloadWindow->Unlock();
444	}
445	if (fSettingsWindow->Lock()) {
446		fSettings->SetValue("settings window frame", fSettingsWindow->Frame());
447		fSettingsWindow->Unlock();
448	}
449	if (fConsoleWindow->Lock()) {
450		fSettings->SetValue("console window frame", fConsoleWindow->Frame());
451		fConsoleWindow->Unlock();
452	}
453	if (fCookieWindow->Lock()) {
454		fSettings->SetValue("cookie window frame", fCookieWindow->Frame());
455		fCookieWindow->Unlock();
456	}
457
458	BMessage cookieArchive;
459	BPrivate::Network::BNetworkCookieJar& cookieJar = fContext->GetCookieJar();
460	cookieJar.PurgeForExit();
461	if (cookieJar.Archive(&cookieArchive) == B_OK)
462		fCookies->SetValue("cookies", cookieArchive);
463
464	return true;
465}
466
467
468void
469BrowserApp::_RefsReceived(BMessage* message, int32* _pagesCreated,
470	bool* _fullscreen)
471{
472	int32 pagesCreated = 0;
473
474	BrowserWindow* window = NULL;
475	if (message->FindPointer("window", (void**)&window) != B_OK)
476		window = NULL;
477
478	bool fullscreen;
479	if (message->FindBool("fullscreen", &fullscreen) != B_OK)
480		fullscreen = false;
481
482	entry_ref ref;
483	for (int32 i = 0; message->FindRef("refs", i, &ref) == B_OK; i++) {
484		BEntry entry(&ref, true);
485		if (!entry.Exists())
486			continue;
487		BPath path;
488		if (entry.GetPath(&path) != B_OK)
489			continue;
490		BUrl url(path);
491		window = _CreateNewPage(url.UrlString(), window, fullscreen,
492			pagesCreated == 0);
493		pagesCreated++;
494	}
495
496	BString url;
497	for (int32 i = 0; message->FindString("url", i, &url) == B_OK; i++) {
498		window = _CreateNewPage(url, window, fullscreen, pagesCreated == 0);
499		pagesCreated++;
500	}
501
502	if (_pagesCreated != NULL)
503		*_pagesCreated = pagesCreated;
504	if (_fullscreen != NULL)
505		*_fullscreen = fullscreen;
506}
507
508
509BrowserWindow*
510BrowserApp::_CreateNewPage(const BString& url, BrowserWindow* webWindow,
511	bool fullscreen, bool useBlankTab)
512{
513	// Let's first see if we must target a specific window...
514	if (webWindow && webWindow->Lock()) {
515		if (useBlankTab && webWindow->IsBlankTab()) {
516			if (url.Length() != 0)
517				webWindow->CurrentWebView()->LoadURL(url);
518		} else
519			webWindow->CreateNewTab(url, true);
520		webWindow->Activate();
521		webWindow->CurrentWebView()->MakeFocus(true);
522		webWindow->Unlock();
523		return webWindow;
524	}
525
526	// Otherwise, try to find one in the current workspace
527	uint32 workspace = 1 << current_workspace();
528
529	bool loadedInWindowOnCurrentWorkspace = false;
530	for (int i = 0; BWindow* window = WindowAt(i); i++) {
531		webWindow = dynamic_cast<BrowserWindow*>(window);
532		if (!webWindow)
533			continue;
534
535		if (webWindow->Lock()) {
536			if (webWindow->Workspaces() & workspace) {
537				if (useBlankTab && webWindow->IsBlankTab()) {
538					if (url.Length() != 0)
539						webWindow->CurrentWebView()->LoadURL(url);
540				} else
541					webWindow->CreateNewTab(url, true);
542				webWindow->Activate();
543				webWindow->CurrentWebView()->MakeFocus(true);
544				loadedInWindowOnCurrentWorkspace = true;
545			}
546			webWindow->Unlock();
547		}
548		if (loadedInWindowOnCurrentWorkspace)
549			return webWindow;
550	}
551
552	// Finally, if no window is available, let's create one.
553	return _CreateNewWindow(url, fullscreen);
554}
555
556
557BrowserWindow*
558BrowserApp::_CreateNewWindow(const BString& url, bool fullscreen)
559{
560	// Offset the window frame unless this is the first window created in the
561	// session.
562	if (fWindowCount > 0)
563		fLastWindowFrame.OffsetBy(20, 20);
564	if (!BScreen().Frame().Contains(fLastWindowFrame))
565		fLastWindowFrame.OffsetTo(50, 50);
566
567	BrowserWindow* window = new BrowserWindow(fLastWindowFrame, fSettings,
568		url, fContext);
569	if (fullscreen)
570		window->ToggleFullscreen();
571	window->Show();
572	return window;
573}
574
575
576void
577BrowserApp::_CreateNewTab(BrowserWindow* window, const BString& url,
578	bool select)
579{
580	if (!window->Lock())
581		return;
582	window->CreateNewTab(url, select);
583	window->Unlock();
584}
585
586
587void
588BrowserApp::_ShowWindow(const BMessage* message, BWindow* window)
589{
590	BAutolock _(window);
591	uint32 workspaces;
592	if (message->FindUInt32("workspaces", &workspaces) == B_OK)
593		window->SetWorkspaces(workspaces);
594	if (window->IsHidden())
595		window->Show();
596	else
597		window->Activate();
598}
599
600
601// #pragma mark -
602
603
604int
605main(int, char**)
606{
607	try {
608		new BrowserApp();
609		be_app->Run();
610		delete be_app;
611	} catch (...) {
612		debugger("Exception caught.");
613	}
614
615	return 0;
616}
617
618