xref: /haiku/src/apps/webpositive/BrowserApp.cpp (revision 8a6724a0ee3803f1e9f487d8111bb3f6cb8d16db)
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 
64 const char* kApplicationSignature = "application/x-vnd.Haiku-WebPositive";
65 const char* kApplicationName = B_TRANSLATE_SYSTEM_NAME("WebPositive");
66 static const uint32 PRELOAD_BROWSING_HISTORY = 'plbh';
67 
68 #define ENABLE_NATIVE_COOKIES 1
69 
70 
71 BrowserApp::BrowserApp()
72 	:
73 	BApplication(kApplicationSignature),
74 	fWindowCount(0),
75 	fLastWindowFrame(50, 50, 950, 750),
76 	fLaunchRefsMessage(0),
77 	fInitialized(false),
78 	fSettings(NULL),
79 	fCookies(NULL),
80 	fSession(NULL),
81 	fContext(NULL),
82 	fDownloadWindow(NULL),
83 	fSettingsWindow(NULL),
84 	fConsoleWindow(NULL),
85 	fCookieWindow(NULL)
86 {
87 #if ENABLE_NATIVE_COOKIES
88 	BString cookieStorePath = kApplicationName;
89 	cookieStorePath << "/Cookies";
90 	fCookies = new SettingsMessage(B_USER_SETTINGS_DIRECTORY,
91 		cookieStorePath.String());
92 	BMessage cookieArchive = fCookies->GetValue("cookies", cookieArchive);
93 	fContext = new BUrlContext();
94 	fContext->SetCookieJar(BNetworkCookieJar(&cookieArchive));
95 #endif
96 
97 	BString sessionStorePath = kApplicationName;
98 	sessionStorePath << "/Session";
99 	fSession = new SettingsMessage(B_USER_SETTINGS_DIRECTORY,
100 		sessionStorePath.String());
101 }
102 
103 
104 BrowserApp::~BrowserApp()
105 {
106 	delete fLaunchRefsMessage;
107 	delete fSettings;
108 	delete fCookies;
109 	delete fSession;
110 	delete fContext;
111 }
112 
113 
114 void
115 BrowserApp::AboutRequested()
116 {
117 	BAboutWindow* window = new BAboutWindow(kApplicationName,
118 		kApplicationSignature);
119 
120 	// create the about window
121 
122 	const char* authors[] = {
123 		"Andrea Anzani",
124 		"Stephan Aßmus",
125 		"Alexandre Deckner",
126 		"Rene Gollent",
127 		"Ryan Leavengood",
128 		"Michael Lotz",
129 		"Maxime Simon",
130 		NULL
131 	};
132 
133 	BString aboutText("");
134 	aboutText << "HaikuWebKit " << WebKitInfo::HaikuWebKitVersion();
135 	aboutText << "\nWebKit " << WebKitInfo::WebKitVersion();
136 
137 	window->AddCopyright(2007, "Haiku, Inc.");
138 	window->AddAuthors(authors);
139 	window->AddExtraInfo(aboutText.String());
140 
141 	window->Show();
142 }
143 
144 
145 void
146 BrowserApp::ArgvReceived(int32 argc, char** argv)
147 {
148 	BMessage message(B_REFS_RECEIVED);
149 	for (int i = 1; i < argc; i++) {
150 		if (strcmp("-f", argv[i]) == 0
151 			|| strcmp("--fullscreen", argv[i]) == 0) {
152 			message.AddBool("fullscreen", true);
153 			continue;
154 		}
155 		const char* url = argv[i];
156 		BEntry entry(argv[i], true);
157 		BPath path;
158 		if (entry.Exists() && entry.GetPath(&path) == B_OK)
159 			url = path.Path();
160 		message.AddString("url", url);
161 	}
162 	// Upon program launch, it will buffer a copy of the message, since
163 	// ArgReceived() is called before ReadyToRun().
164 	RefsReceived(&message);
165 }
166 
167 
168 void
169 BrowserApp::ReadyToRun()
170 {
171 	// Since we will essentially run the GUI...
172 	set_thread_priority(Thread(), B_DISPLAY_PRIORITY);
173 
174 	BWebPage::InitializeOnce();
175 	BWebPage::SetCacheModel(B_WEBKIT_CACHE_MODEL_WEB_BROWSER);
176 
177 	BPath path;
178 	if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) == B_OK
179 		&& path.Append(kApplicationName) == B_OK
180 		&& create_directory(path.Path(), 0777) == B_OK) {
181 
182 		BWebSettings::SetPersistentStoragePath(path.Path());
183 	}
184 
185 	BString mainSettingsPath(kApplicationName);
186 	mainSettingsPath << "/Application";
187 	fSettings = new SettingsMessage(B_USER_SETTINGS_DIRECTORY,
188 		mainSettingsPath.String());
189 
190 	fLastWindowFrame = fSettings->GetValue("window frame", fLastWindowFrame);
191 	BRect defaultDownloadWindowFrame(-10, -10, 365, 265);
192 	BRect downloadWindowFrame = fSettings->GetValue("downloads window frame",
193 		defaultDownloadWindowFrame);
194 	BRect settingsWindowFrame = fSettings->GetValue("settings window frame",
195 		BRect());
196 	BRect consoleWindowFrame = fSettings->GetValue("console window frame",
197 		BRect(50, 50, 400, 300));
198 	BRect cookieWindowFrame = fSettings->GetValue("cookie window frame",
199 		BRect(50, 50, 400, 300));
200 	bool showDownloads = fSettings->GetValue("show downloads", false);
201 
202 	fDownloadWindow = new DownloadWindow(downloadWindowFrame, showDownloads,
203 		fSettings);
204 	if (downloadWindowFrame == defaultDownloadWindowFrame) {
205 		// Initially put download window in lower right of screen.
206 		BRect screenFrame = BScreen().Frame();
207 		BMessage decoratorSettings;
208 		fDownloadWindow->GetDecoratorSettings(&decoratorSettings);
209 		float borderWidth = 0;
210 		if (decoratorSettings.FindFloat("border width", &borderWidth) != B_OK)
211 			borderWidth = 5;
212 		fDownloadWindow->MoveTo(screenFrame.Width()
213 			- fDownloadWindow->Frame().Width() - borderWidth,
214 			screenFrame.Height() - fDownloadWindow->Frame().Height()
215 			- borderWidth);
216 	}
217 	fSettingsWindow = new SettingsWindow(settingsWindowFrame, fSettings);
218 
219 	BWebPage::SetDownloadListener(BMessenger(fDownloadWindow));
220 
221 	fConsoleWindow = new ConsoleWindow(consoleWindowFrame);
222 	fCookieWindow = new CookieWindow(cookieWindowFrame, fContext->GetCookieJar());
223 
224 	fInitialized = true;
225 
226 	int32 pagesCreated = 0;
227 	bool fullscreen = false;
228 	if (fLaunchRefsMessage) {
229 		_RefsReceived(fLaunchRefsMessage, &pagesCreated, &fullscreen);
230 		delete fLaunchRefsMessage;
231 		fLaunchRefsMessage = NULL;
232 	}
233 
234 	// If no refs led to a new open page, restore previous session.
235 	if (pagesCreated == 0) {
236 		BMessage archivedWindow;
237 		for (int i = 0; fSession->FindMessage("window", i, &archivedWindow) == B_OK;
238 			i++) {
239 			BRect frame = archivedWindow.FindRect("window frame");
240 			BString url;
241 			archivedWindow.FindString("tab", 0, &url);
242 			BrowserWindow* window = new(std::nothrow) BrowserWindow(frame,
243 				fSettings, url, fContext);
244 
245 			if (window != NULL) {
246 				window->Show();
247 				pagesCreated++;
248 
249 				for (int j = 1; archivedWindow.FindString("tab", j, &url) == B_OK;
250 					j++) {
251 					printf("Create %d:%d\n", i, j);
252 					_CreateNewTab(window, url, false);
253 					pagesCreated++;
254 				}
255 			}
256 		}
257 	}
258 
259 	// If previous session did not contain any window, create a new empty one.
260 	if (pagesCreated == 0)
261 		_CreateNewWindow("", fullscreen);
262 
263 	PostMessage(PRELOAD_BROWSING_HISTORY);
264 }
265 
266 
267 void
268 BrowserApp::MessageReceived(BMessage* message)
269 {
270 	switch (message->what) {
271 	case PRELOAD_BROWSING_HISTORY:
272 		// Accessing the default instance will load the history from disk.
273 		BrowsingHistory::DefaultInstance();
274 		break;
275 	case B_SILENT_RELAUNCH:
276 		_CreateNewPage("");
277 		break;
278 	case NEW_WINDOW: {
279 		BString url;
280 		if (message->FindString("url", &url) != B_OK)
281 			break;
282 		_CreateNewWindow(url);
283 		break;
284 	}
285 	case NEW_TAB: {
286 		BrowserWindow* window;
287 		if (message->FindPointer("window",
288 			reinterpret_cast<void**>(&window)) != B_OK)
289 			break;
290 		BString url;
291 		message->FindString("url", &url);
292 		bool select = false;
293 		message->FindBool("select", &select);
294 		_CreateNewTab(window, url, select);
295 		break;
296 	}
297 	case WINDOW_OPENED:
298 		fWindowCount++;
299 		fDownloadWindow->SetMinimizeOnClose(false);
300 		break;
301 	case WINDOW_CLOSED:
302 		fWindowCount--;
303 		message->FindRect("window frame", &fLastWindowFrame);
304 		if (fWindowCount <= 0) {
305 			BMessage* message = new BMessage(B_QUIT_REQUESTED);
306 			message->AddMessage("window", DetachCurrentMessage());
307 			PostMessage(message);
308 		}
309 		break;
310 
311 	case SHOW_DOWNLOAD_WINDOW:
312 		_ShowWindow(message, fDownloadWindow);
313 		break;
314 	case SHOW_SETTINGS_WINDOW:
315 		_ShowWindow(message, fSettingsWindow);
316 		break;
317 	case SHOW_CONSOLE_WINDOW:
318 		_ShowWindow(message, fConsoleWindow);
319 		break;
320 	case SHOW_COOKIE_WINDOW:
321 		_ShowWindow(message, fCookieWindow);
322 		break;
323 	case ADD_CONSOLE_MESSAGE:
324 		fConsoleWindow->PostMessage(message);
325 		break;
326 
327 	default:
328 		BApplication::MessageReceived(message);
329 		break;
330 	}
331 }
332 
333 
334 void
335 BrowserApp::RefsReceived(BMessage* message)
336 {
337 	if (!fInitialized) {
338 		delete fLaunchRefsMessage;
339 		fLaunchRefsMessage = new BMessage(*message);
340 		return;
341 	}
342 
343 	_RefsReceived(message);
344 }
345 
346 
347 bool
348 BrowserApp::QuitRequested()
349 {
350 	if (fDownloadWindow->DownloadsInProgress()) {
351 		BAlert* alert = new BAlert(B_TRANSLATE("Downloads in progress"),
352 			B_TRANSLATE("There are still downloads in progress, do you really "
353 			"want to quit WebPositive now?"), B_TRANSLATE("Quit"),
354 			B_TRANSLATE("Continue downloads"));
355 		int32 choice = alert->Go();
356 		if (choice == 1) {
357 			if (fWindowCount == 0) {
358 				if (fDownloadWindow->Lock()) {
359 					fDownloadWindow->SetWorkspaces(1 << current_workspace());
360 					if (fDownloadWindow->IsHidden())
361 						fDownloadWindow->Show();
362 					else
363 						fDownloadWindow->Activate();
364 					fDownloadWindow->SetMinimizeOnClose(true);
365 					fDownloadWindow->Unlock();
366 					return false;
367 				}
368 			} else
369 				return false;
370 		}
371 	}
372 
373 	fSession->MakeEmpty();
374 
375 	/* See if we got here because the last window is already closed.
376 	 * In that case we only need to save that one, which is already archived */
377 	BMessage* message = CurrentMessage();
378 	BMessage windowMessage;
379 
380 	status_t ret = message->FindMessage("window", &windowMessage);
381 	if (ret == B_OK) {
382 		fSession->AddMessage("window", &windowMessage);
383 	} else {
384 		for (int i = 0; BWindow* window = WindowAt(i); i++) {
385 			BrowserWindow* webWindow = dynamic_cast<BrowserWindow*>(window);
386 			if (!webWindow)
387 				continue;
388 			if (!webWindow->Lock())
389 				continue;
390 
391 			BMessage windowArchive;
392 			webWindow->Archive(&windowArchive, true);
393 			fSession->AddMessage("window", &windowArchive);
394 
395 			if (webWindow->QuitRequested()) {
396 				fLastWindowFrame = webWindow->WindowFrame();
397 				webWindow->Quit();
398 				i--;
399 			} else {
400 				webWindow->Unlock();
401 				return false;
402 			}
403 		}
404 	}
405 
406 	BWebPage::ShutdownOnce();
407 
408 	fSettings->SetValue("window frame", fLastWindowFrame);
409 	if (fDownloadWindow->Lock()) {
410 		fSettings->SetValue("downloads window frame", fDownloadWindow->Frame());
411 		fSettings->SetValue("show downloads", !fDownloadWindow->IsHidden());
412 		fDownloadWindow->Unlock();
413 	}
414 	if (fSettingsWindow->Lock()) {
415 		fSettings->SetValue("settings window frame", fSettingsWindow->Frame());
416 		fSettingsWindow->Unlock();
417 	}
418 	if (fConsoleWindow->Lock()) {
419 		fSettings->SetValue("console window frame", fConsoleWindow->Frame());
420 		fConsoleWindow->Unlock();
421 	}
422 	if (fCookieWindow->Lock()) {
423 		fSettings->SetValue("cookie window frame", fCookieWindow->Frame());
424 		fCookieWindow->Unlock();
425 	}
426 
427 	BMessage cookieArchive;
428 	BNetworkCookieJar& cookieJar = fContext->GetCookieJar();
429 	cookieJar.PurgeForExit();
430 	if (cookieJar.Archive(&cookieArchive) == B_OK)
431 		fCookies->SetValue("cookies", cookieArchive);
432 
433 	return true;
434 }
435 
436 
437 void
438 BrowserApp::_RefsReceived(BMessage* message, int32* _pagesCreated,
439 	bool* _fullscreen)
440 {
441 	int32 pagesCreated = 0;
442 
443 	BrowserWindow* window = NULL;
444 	if (message->FindPointer("window", (void**)&window) != B_OK)
445 		window = NULL;
446 
447 	bool fullscreen;
448 	if (message->FindBool("fullscreen", &fullscreen) != B_OK)
449 		fullscreen = false;
450 
451 	entry_ref ref;
452 	for (int32 i = 0; message->FindRef("refs", i, &ref) == B_OK; i++) {
453 		BEntry entry(&ref, true);
454 		if (!entry.Exists())
455 			continue;
456 		BPath path;
457 		if (entry.GetPath(&path) != B_OK)
458 			continue;
459 		BUrl url(path);
460 		window = _CreateNewPage(url.UrlString(), window, fullscreen,
461 			pagesCreated == 0);
462 		pagesCreated++;
463 	}
464 
465 	BString url;
466 	for (int32 i = 0; message->FindString("url", i, &url) == B_OK; i++) {
467 		window = _CreateNewPage(url, window, fullscreen, pagesCreated == 0);
468 		pagesCreated++;
469 	}
470 
471 	if (_pagesCreated != NULL)
472 		*_pagesCreated = pagesCreated;
473 	if (_fullscreen != NULL)
474 		*_fullscreen = fullscreen;
475 }
476 
477 
478 BrowserWindow*
479 BrowserApp::_CreateNewPage(const BString& url, BrowserWindow* webWindow,
480 	bool fullscreen, bool useBlankTab)
481 {
482 	// Let's first see if we must target a specific window...
483 	if (webWindow && webWindow->Lock()) {
484 		if (useBlankTab && webWindow->IsBlankTab()) {
485 			if (url.Length() != 0)
486 				webWindow->CurrentWebView()->LoadURL(url);
487 		} else
488 			webWindow->CreateNewTab(url, true);
489 		webWindow->Activate();
490 		webWindow->CurrentWebView()->MakeFocus(true);
491 		webWindow->Unlock();
492 		return webWindow;
493 	}
494 
495 	// Otherwise, try to find one in the current workspace
496 	uint32 workspace = 1 << current_workspace();
497 
498 	bool loadedInWindowOnCurrentWorkspace = false;
499 	for (int i = 0; BWindow* window = WindowAt(i); i++) {
500 		webWindow = dynamic_cast<BrowserWindow*>(window);
501 		if (!webWindow)
502 			continue;
503 
504 		if (webWindow->Lock()) {
505 			if (webWindow->Workspaces() & workspace) {
506 				if (useBlankTab && webWindow->IsBlankTab()) {
507 					if (url.Length() != 0)
508 						webWindow->CurrentWebView()->LoadURL(url);
509 				} else
510 					webWindow->CreateNewTab(url, true);
511 				webWindow->Activate();
512 				webWindow->CurrentWebView()->MakeFocus(true);
513 				loadedInWindowOnCurrentWorkspace = true;
514 			}
515 			webWindow->Unlock();
516 		}
517 		if (loadedInWindowOnCurrentWorkspace)
518 			return webWindow;
519 	}
520 
521 	// Finally, if no window is available, let's create one.
522 	return _CreateNewWindow(url, fullscreen);
523 }
524 
525 
526 BrowserWindow*
527 BrowserApp::_CreateNewWindow(const BString& url, bool fullscreen)
528 {
529 	// Offset the window frame unless this is the first window created in the
530 	// session.
531 	if (fWindowCount > 0)
532 		fLastWindowFrame.OffsetBy(20, 20);
533 	if (!BScreen().Frame().Contains(fLastWindowFrame))
534 		fLastWindowFrame.OffsetTo(50, 50);
535 
536 	BrowserWindow* window = new BrowserWindow(fLastWindowFrame, fSettings,
537 		url, fContext);
538 	if (fullscreen)
539 		window->ToggleFullscreen();
540 	window->Show();
541 	return window;
542 }
543 
544 
545 void
546 BrowserApp::_CreateNewTab(BrowserWindow* window, const BString& url,
547 	bool select)
548 {
549 	if (!window->Lock())
550 		return;
551 	window->CreateNewTab(url, select);
552 	window->Unlock();
553 }
554 
555 
556 void
557 BrowserApp::_ShowWindow(const BMessage* message, BWindow* window)
558 {
559 	BAutolock _(window);
560 	uint32 workspaces;
561 	if (message->FindUInt32("workspaces", &workspaces) == B_OK)
562 		window->SetWorkspaces(workspaces);
563 	if (window->IsHidden())
564 		window->Show();
565 	else
566 		window->Activate();
567 }
568 
569 
570 // #pragma mark -
571 
572 
573 int
574 main(int, char**)
575 {
576 	try {
577 		new BrowserApp();
578 		be_app->Run();
579 		delete be_app;
580 	} catch (...) {
581 		debugger("Exception caught.");
582 	}
583 
584 	return 0;
585 }
586 
587