/*
 * Copyright (C) 2007 Ryan Leavengood <leavengood@gmail.com>
 * Copyright (C) 2010 Stephan Aßmus <superstippi@gmx.de>
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *	notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *	notice, this list of conditions and the following disclaimer in the
 *	documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
 
#include "BrowserApp.h"
 
#include <AboutWindow.h>
#include <Alert.h>
#include <Autolock.h>
#include <Catalog.h>
#include <Directory.h>
#include <Entry.h>
#include <FindDirectory.h>
#include <Locale.h>
#include <Path.h>
#include <Screen.h>
#include <UrlContext.h>
#include <debugger.h>
 
#include <stdio.h>
 
#include "BrowserWindow.h"
#include "BrowsingHistory.h"
#include "DownloadWindow.h"
#include "SettingsMessage.h"
#include "SettingsWindow.h"
#include "ConsoleWindow.h"
#include "CookieWindow.h"
#include "NetworkCookieJar.h"
#include "WebKitInfo.h"
#include "WebPage.h"
#include "WebSettings.h"
#include "WebView.h"
#include "WebViewConstants.h"
 
 
#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "WebPositive"
 
const char* kApplicationSignature = "application/x-vnd.Haiku-WebPositive";
const char* kApplicationName = B_TRANSLATE_SYSTEM_NAME("WebPositive");
static const uint32 PRELOAD_BROWSING_HISTORY = 'plbh';
 
#define ENABLE_NATIVE_COOKIES 1
 
 
BrowserApp::BrowserApp()
	:
	BApplication(kApplicationSignature),
	fWindowCount(0),
	fLastWindowFrame(50, 50, 950, 750),
	fLaunchRefsMessage(0),
	fInitialized(false),
	fSettings(NULL),
	fCookies(NULL),
	fSession(NULL),
	fContext(NULL),
	fDownloadWindow(NULL),
	fSettingsWindow(NULL),
	fConsoleWindow(NULL),
	fCookieWindow(NULL)
{
#ifdef __i386__
	// First let's check SSE2 is available
	cpuid_info info;
	get_cpuid(&info, 1, 0);
 
	if ((info.eax_1.features & (1 << 26)) == 0) {
		BAlert alert(B_TRANSLATE("No SSE2 support"), B_TRANSLATE("Your CPU is "
			"too old and does not support the SSE2 extensions, without which "
			"WebPositive cannot run. We recommend installing NetSurf instead."),
			B_TRANSLATE("Darn!"));
		alert.Go();
		exit(-1);
	}
#endif
 
#if ENABLE_NATIVE_COOKIES
	BString cookieStorePath = kApplicationName;
	cookieStorePath << "/Cookies";
	fCookies = new SettingsMessage(B_USER_SETTINGS_DIRECTORY,
		cookieStorePath.String());
	fContext = new BUrlContext();
	if (fCookies->InitCheck() == B_OK) {
		BMessage cookieArchive = fCookies->GetValue("cookies", cookieArchive);
		fContext->SetCookieJar(BNetworkCookieJar(&cookieArchive));
	}
#endif
 
	BString sessionStorePath = kApplicationName;
	sessionStorePath << "/Session";
	fSession = new SettingsMessage(B_USER_SETTINGS_DIRECTORY,
		sessionStorePath.String());
}
 
 
BrowserApp::~BrowserApp()
{
	delete fLaunchRefsMessage;
	delete fSettings;
	delete fCookies;
	delete fSession;
	delete fContext;
}
 
 
void
BrowserApp::AboutRequested()
{
	BAboutWindow* window = new BAboutWindow(kApplicationName,
		kApplicationSignature);
 
	// create the about window
 
	const char* authors[] = {
		"Andrea Anzani",
		"Stephan Aßmus",
		"Alexandre Deckner",
		"Rene Gollent",
		"Ryan Leavengood",
		"Michael Lotz",
		"Maxime Simon",
		NULL
	};
 
	BString aboutText("");
	aboutText << "HaikuWebKit " << WebKitInfo::HaikuWebKitVersion();
	aboutText << "\nWebKit " << WebKitInfo::WebKitVersion();
 
	window->AddCopyright(2007, "Haiku, Inc.");
	window->AddAuthors(authors);
	window->AddExtraInfo(aboutText.String());
 
	window->Show();
}
 
 
void
BrowserApp::ArgvReceived(int32 argc, char** argv)
{
	BMessage message(B_REFS_RECEIVED);
	for (int i = 1; i < argc; i++) {
		if (strcmp("-f", argv[i]) == 0
			|| strcmp("--fullscreen", argv[i]) == 0) {
			message.AddBool("fullscreen", true);
			continue;
		}
		const char* url = argv[i];
		BEntry entry(argv[i], true);
		BPath path;
		if (entry.Exists() && entry.GetPath(&path) == B_OK)
			url = path.Path();
		message.AddString("url", url);
	}
	// Upon program launch, it will buffer a copy of the message, since
	// ArgReceived() is called before ReadyToRun().
	RefsReceived(&message);
}
 
 
void
BrowserApp::ReadyToRun()
{
	// Since we will essentially run the GUI...
	set_thread_priority(Thread(), B_DISPLAY_PRIORITY);
 
	BWebPage::InitializeOnce();
	BWebPage::SetCacheModel(B_WEBKIT_CACHE_MODEL_WEB_BROWSER);
 
	BPath path;
	if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) == B_OK
		&& path.Append(kApplicationName) == B_OK
		&& create_directory(path.Path(), 0777) == B_OK) {
 
		BWebSettings::SetPersistentStoragePath(path.Path());
	}
 
	BString mainSettingsPath(kApplicationName);
	mainSettingsPath << "/Application";
	fSettings = new SettingsMessage(B_USER_SETTINGS_DIRECTORY,
		mainSettingsPath.String());
 
	fLastWindowFrame = fSettings->GetValue("window frame", fLastWindowFrame);
	BRect defaultDownloadWindowFrame(-10, -10, 365, 265);
	BRect downloadWindowFrame = fSettings->GetValue("downloads window frame",
		defaultDownloadWindowFrame);
	BRect settingsWindowFrame = fSettings->GetValue("settings window frame",
		BRect());
	BRect consoleWindowFrame = fSettings->GetValue("console window frame",
		BRect(50, 50, 400, 300));
	BRect cookieWindowFrame = fSettings->GetValue("cookie window frame",
		BRect(50, 50, 400, 300));
	bool showDownloads = fSettings->GetValue("show downloads", false);
 
	fDownloadWindow = new DownloadWindow(downloadWindowFrame, showDownloads,
		fSettings);
	if (downloadWindowFrame == defaultDownloadWindowFrame) {
		// Initially put download window in lower right of screen.
		BRect screenFrame = BScreen().Frame();
		BMessage decoratorSettings;
		fDownloadWindow->GetDecoratorSettings(&decoratorSettings);
		float borderWidth = 0;
		if (decoratorSettings.FindFloat("border width", &borderWidth) != B_OK)
			borderWidth = 5;
		fDownloadWindow->MoveTo(screenFrame.Width()
			- fDownloadWindow->Frame().Width() - borderWidth,
			screenFrame.Height() - fDownloadWindow->Frame().Height()
			- borderWidth);
	}
	fSettingsWindow = new SettingsWindow(settingsWindowFrame, fSettings);
 
	BWebPage::SetDownloadListener(BMessenger(fDownloadWindow));
 
	fConsoleWindow = new ConsoleWindow(consoleWindowFrame);
	fCookieWindow = new CookieWindow(cookieWindowFrame, fContext->GetCookieJar());
 
	fInitialized = true;
 
	int32 pagesCreated = 0;
	bool fullscreen = false;
	if (fLaunchRefsMessage) {
		_RefsReceived(fLaunchRefsMessage, &pagesCreated, &fullscreen);
		delete fLaunchRefsMessage;
		fLaunchRefsMessage = NULL;
	}
 
	// If no refs led to a new open page, open new session if set
	if (fSession->InitCheck() == B_OK && pagesCreated == 0) {
		const char* kSettingsKeyStartUpPolicy = "start up policy";
		uint32 fStartUpPolicy = fSettings->GetValue(kSettingsKeyStartUpPolicy,
			(uint32)ResumePriorSession);
		if (fStartUpPolicy == StartNewSession) {
			PostMessage(NEW_WINDOW);
		} else {
			// otherwise, restore previous session
			BMessage archivedWindow;
			for (int i = 0; fSession->FindMessage("window", i, &archivedWindow)
				== B_OK; i++) {
				BRect frame = archivedWindow.FindRect("window frame");
				BString url;
				archivedWindow.FindString("tab", 0, &url);
				BrowserWindow* window = new(std::nothrow) BrowserWindow(frame,
					fSettings, url, fContext);
 
				if (window != NULL) {
					window->Show();
					pagesCreated++;
 
					for (int j = 1; archivedWindow.FindString("tab", j, &url)
						== B_OK; j++) {
						printf("Create %d:%d\n", i, j);
						_CreateNewTab(window, url, false);
						pagesCreated++;
					}
				}
			}
		}
	}
 
	// If previous session did not contain any window, create a new empty one.
	if (pagesCreated == 0)
		_CreateNewWindow("", fullscreen);
 
	PostMessage(PRELOAD_BROWSING_HISTORY);
}
 
 
void
BrowserApp::MessageReceived(BMessage* message)
{
	switch (message->what) {
	case PRELOAD_BROWSING_HISTORY:
		// Accessing the default instance will load the history from disk.
		BrowsingHistory::DefaultInstance();
		break;
	case B_SILENT_RELAUNCH:
		_CreateNewPage("");
		break;
	case NEW_WINDOW: {
		BString url;
		if (message->FindString("url", &url) != B_OK)
			break;
		_CreateNewWindow(url);
		break;
	}
	case NEW_TAB: {
		BrowserWindow* window;
		if (message->FindPointer("window",
			reinterpret_cast<void**>(&window)) != B_OK)
			break;
		BString url;
		message->FindString("url", &url);
		bool select = false;
		message->FindBool("select", &select);
		_CreateNewTab(window, url, select);
		break;
	}
	case WINDOW_OPENED:
		fWindowCount++;
		fDownloadWindow->SetMinimizeOnClose(false);
		break;
	case WINDOW_CLOSED:
		fWindowCount--;
		message->FindRect("window frame", &fLastWindowFrame);
		if (fWindowCount <= 0) {
			BMessage* message = new BMessage(B_QUIT_REQUESTED);
			message->AddMessage("window", DetachCurrentMessage());
			PostMessage(message);
		}
		break;
 
	case SHOW_DOWNLOAD_WINDOW:
		_ShowWindow(message, fDownloadWindow);
		break;
	case SHOW_SETTINGS_WINDOW:
		_ShowWindow(message, fSettingsWindow);
		break;
	case SHOW_CONSOLE_WINDOW:
		_ShowWindow(message, fConsoleWindow);
		break;
	case SHOW_COOKIE_WINDOW:
		_ShowWindow(message, fCookieWindow);
		break;
	case ADD_CONSOLE_MESSAGE:
		fConsoleWindow->PostMessage(message);
		break;
 
	default:
		BApplication::MessageReceived(message);
		break;
	}
}
 
 
void
BrowserApp::RefsReceived(BMessage* message)
{
	if (!fInitialized) {
		delete fLaunchRefsMessage;
		fLaunchRefsMessage = new BMessage(*message);
		return;
	}
 
	_RefsReceived(message);
}
 
 
bool
BrowserApp::QuitRequested()
{
	if (fDownloadWindow->DownloadsInProgress()) {
		BAlert* alert = new BAlert(B_TRANSLATE("Downloads in progress"),
			B_TRANSLATE("There are still downloads in progress, do you really "
			"want to quit WebPositive now?"), B_TRANSLATE("Quit"),
			B_TRANSLATE("Continue downloads"));
		int32 choice = alert->Go();
		if (choice == 1) {
			if (fWindowCount == 0) {
				if (fDownloadWindow->Lock()) {
					fDownloadWindow->SetWorkspaces(1 << current_workspace());
					if (fDownloadWindow->IsHidden())
						fDownloadWindow->Show();
					else
						fDownloadWindow->Activate();
					fDownloadWindow->SetMinimizeOnClose(true);
					fDownloadWindow->Unlock();
					return false;
				}
			} else
				return false;
		}
	}
 
	fSession->MakeEmpty();
 
	/* See if we got here because the last window is already closed.
	 * In that case we only need to save that one, which is already archived */
	BMessage* message = CurrentMessage();
	BMessage windowMessage;
 
	status_t ret = message->FindMessage("window", &windowMessage);
	if (ret == B_OK) {
		fSession->AddMessage("window", &windowMessage);
	} else {
		for (int i = 0; BWindow* window = WindowAt(i); i++) {
			BrowserWindow* webWindow = dynamic_cast<BrowserWindow*>(window);
			if (!webWindow)
				continue;
			if (!webWindow->Lock())
				continue;
 
			BMessage windowArchive;
			webWindow->Archive(&windowArchive, true);
			fSession->AddMessage("window", &windowArchive);
 
			if (webWindow->QuitRequested()) {
				fLastWindowFrame = webWindow->WindowFrame();
				webWindow->Quit();
				i--;
			} else {
				webWindow->Unlock();
				return false;
			}
		}
	}
 
	BWebPage::ShutdownOnce();
 
	fSettings->SetValue("window frame", fLastWindowFrame);
	if (fDownloadWindow->Lock()) {
		fSettings->SetValue("downloads window frame", fDownloadWindow->Frame());
		fSettings->SetValue("show downloads", !fDownloadWindow->IsHidden());
		fDownloadWindow->Unlock();
	}
	if (fSettingsWindow->Lock()) {
		fSettings->SetValue("settings window frame", fSettingsWindow->Frame());
		fSettingsWindow->Unlock();
	}
	if (fConsoleWindow->Lock()) {
		fSettings->SetValue("console window frame", fConsoleWindow->Frame());
		fConsoleWindow->Unlock();
	}
	if (fCookieWindow->Lock()) {
		fSettings->SetValue("cookie window frame", fCookieWindow->Frame());
		fCookieWindow->Unlock();
	}
 
	BMessage cookieArchive;
	BNetworkCookieJar& cookieJar = fContext->GetCookieJar();
	cookieJar.PurgeForExit();
	if (cookieJar.Archive(&cookieArchive) == B_OK)
		fCookies->SetValue("cookies", cookieArchive);
 
	return true;
}
 
 
void
BrowserApp::_RefsReceived(BMessage* message, int32* _pagesCreated,
	bool* _fullscreen)
{
	int32 pagesCreated = 0;
 
	BrowserWindow* window = NULL;
	if (message->FindPointer("window", (void**)&window) != B_OK)
		window = NULL;
 
	bool fullscreen;
	if (message->FindBool("fullscreen", &fullscreen) != B_OK)
		fullscreen = false;
 
	entry_ref ref;
	for (int32 i = 0; message->FindRef("refs", i, &ref) == B_OK; i++) {
		BEntry entry(&ref, true);
		if (!entry.Exists())
			continue;
		BPath path;
		if (entry.GetPath(&path) != B_OK)
			continue;
		BUrl url(path);
		window = _CreateNewPage(url.UrlString(), window, fullscreen,
			pagesCreated == 0);
		pagesCreated++;
	}
 
	BString url;
	for (int32 i = 0; message->FindString("url", i, &url) == B_OK; i++) {
		window = _CreateNewPage(url, window, fullscreen, pagesCreated == 0);
		pagesCreated++;
	}
 
	if (_pagesCreated != NULL)
		*_pagesCreated = pagesCreated;
	if (_fullscreen != NULL)
		*_fullscreen = fullscreen;
}
 
 
BrowserWindow*
BrowserApp::_CreateNewPage(const BString& url, BrowserWindow* webWindow,
	bool fullscreen, bool useBlankTab)
{
	// Let's first see if we must target a specific window...
	if (webWindow && webWindow->Lock()) {
		if (useBlankTab && webWindow->IsBlankTab()) {
			if (url.Length() != 0)
				webWindow->CurrentWebView()->LoadURL(url);
		} else
			webWindow->CreateNewTab(url, true);
		webWindow->Activate();
		webWindow->CurrentWebView()->MakeFocus(true);
		webWindow->Unlock();
		return webWindow;
	}
 
	// Otherwise, try to find one in the current workspace
	uint32 workspace = 1 << current_workspace();
 
	bool loadedInWindowOnCurrentWorkspace = false;
	for (int i = 0; BWindow* window = WindowAt(i); i++) {
		webWindow = dynamic_cast<BrowserWindow*>(window);
		if (!webWindow)
			continue;
 
		if (webWindow->Lock()) {
			if (webWindow->Workspaces() & workspace) {
				if (useBlankTab && webWindow->IsBlankTab()) {
					if (url.Length() != 0)
						webWindow->CurrentWebView()->LoadURL(url);
				} else
					webWindow->CreateNewTab(url, true);
				webWindow->Activate();
				webWindow->CurrentWebView()->MakeFocus(true);
				loadedInWindowOnCurrentWorkspace = true;
			}
			webWindow->Unlock();
		}
		if (loadedInWindowOnCurrentWorkspace)
			return webWindow;
	}
 
	// Finally, if no window is available, let's create one.
	return _CreateNewWindow(url, fullscreen);
}
 
 
BrowserWindow*
BrowserApp::_CreateNewWindow(const BString& url, bool fullscreen)
{
	// Offset the window frame unless this is the first window created in the
	// session.
	if (fWindowCount > 0)
		fLastWindowFrame.OffsetBy(20, 20);
	if (!BScreen().Frame().Contains(fLastWindowFrame))
		fLastWindowFrame.OffsetTo(50, 50);
 
	BrowserWindow* window = new BrowserWindow(fLastWindowFrame, fSettings,
		url, fContext);
	if (fullscreen)
		window->ToggleFullscreen();
	window->Show();
	return window;
}
 
 
void
BrowserApp::_CreateNewTab(BrowserWindow* window, const BString& url,
	bool select)
{
	if (!window->Lock())
		return;
	window->CreateNewTab(url, select);
	window->Unlock();
}
 
 
void
BrowserApp::_ShowWindow(const BMessage* message, BWindow* window)
{
	BAutolock _(window);
	uint32 workspaces;
	if (message->FindUInt32("workspaces", &workspaces) == B_OK)
		window->SetWorkspaces(workspaces);
	if (window->IsHidden())
		window->Show();
	else
		window->Activate();
}
 
 
// #pragma mark -
 
 
int
main(int, char**)
{
	try {
		new BrowserApp();
		be_app->Run();
		delete be_app;
	} catch (...) {
		debugger("Exception caught.");
	}
 
	return 0;
}
 

V773 The function was exited without releasing the 'alert' pointer. A memory leak is possible.

V573 Uninitialized variable 'cookieArchive' was used. The variable was used to initialize itself.