Persist session state and single-instance
Add session persistence and single-instance handling. Introduces browser/session_state.{h,cpp} to load/save a simple JSON session_state.json (limits restored tabs to 50, basic JSON parsing, atomic write via a .tmp rename). TabManager gains RestoreTabs and ActiveTabIndex to restore and track tabs. NebulaController now calls PersistSession on tab/title/activate/close events, flushes cookies on shutdown, and sets CEF runtime style to Alloy for embedded child browsers and devtools. run.cpp adds a named mutex to prevent multiple instances, enables persistent session cookies, and tweaks initial URL handling. Added GetSessionStatePath() to ui/paths and updated CMakeLists.txt to include the new source file.
This commit is contained in:
@@ -6,9 +6,11 @@
|
||||
#include <charconv>
|
||||
#include <system_error>
|
||||
|
||||
#include "browser/session_state.h"
|
||||
#include "browser/url_utils.h"
|
||||
#include "include/cef_app.h"
|
||||
#include "include/cef_browser.h"
|
||||
#include "include/cef_cookie.h"
|
||||
#include "include/wrapper/cef_helpers.h"
|
||||
#include "ui/paths.h"
|
||||
|
||||
@@ -37,6 +39,10 @@ CefWindowInfo ChildWindowInfo(HWND parent, const RECT& rect) {
|
||||
rect.top,
|
||||
rect.right - rect.left,
|
||||
rect.bottom - rect.top));
|
||||
// CEF defaults to the Chrome runtime style, which ignores the
|
||||
// SetAsChild hint and creates a top-level window per browser. Force the
|
||||
// Alloy runtime style so each browser embeds inside the Nebula HWND.
|
||||
info.runtime_style = CEF_RUNTIME_STYLE_ALLOY;
|
||||
return info;
|
||||
}
|
||||
|
||||
@@ -145,7 +151,17 @@ bool NebulaController::Create() {
|
||||
}
|
||||
|
||||
void NebulaController::OnWindowCreated() {
|
||||
tabs_.CreateInitialTab(initial_url_.empty() ? nebula::ui::GetHomeUrl() : initial_url_);
|
||||
if (initial_url_.empty()) {
|
||||
const auto session = nebula::browser::LoadSessionState();
|
||||
if (!session.tabs.empty()) {
|
||||
tabs_.RestoreTabs(session.tabs, session.active_tab_index);
|
||||
} else {
|
||||
tabs_.CreateInitialTab(nebula::ui::GetHomeUrl());
|
||||
}
|
||||
} else {
|
||||
tabs_.CreateInitialTab(initial_url_);
|
||||
}
|
||||
|
||||
CreateChromeBrowser();
|
||||
CreateContentBrowser();
|
||||
}
|
||||
@@ -157,11 +173,23 @@ void NebulaController::OnWindowResized(const nebula::window::BrowserLayout& layo
|
||||
|
||||
void NebulaController::OnWindowCloseRequested() {
|
||||
if (closing_) {
|
||||
// CEF re-sends WM_CLOSE to the top-level window after each Alloy
|
||||
// child browser finishes its JS unload + DoClose phase. Destroy the
|
||||
// Nebula window now so CEF can tear down the child browser HWNDs and
|
||||
// fire OnBeforeClose; MaybeFinishShutdown will then quit the loop.
|
||||
if (window_ && window_->hwnd()) {
|
||||
DestroyWindow(window_->hwnd());
|
||||
}
|
||||
MaybeFinishShutdown();
|
||||
return;
|
||||
}
|
||||
|
||||
closing_ = true;
|
||||
PersistSession();
|
||||
if (auto cookie_manager = CefCookieManager::GetGlobalManager(nullptr)) {
|
||||
cookie_manager->FlushStore(nullptr);
|
||||
}
|
||||
|
||||
if (chrome_browser_) {
|
||||
chrome_browser_->GetHost()->CloseBrowser(false);
|
||||
}
|
||||
@@ -173,7 +201,6 @@ void NebulaController::OnWindowCloseRequested() {
|
||||
tab.browser->GetHost()->CloseBrowser(false);
|
||||
}
|
||||
}
|
||||
MaybeFinishShutdown();
|
||||
}
|
||||
|
||||
void NebulaController::OnActiveTabChanged(const nebula::browser::NebulaTab& tab) {
|
||||
@@ -292,10 +319,12 @@ void NebulaController::OnContentAddressChanged(CefRefPtr<CefBrowser> browser, co
|
||||
nebula::ui::IsChromiumNewTabUrl(url)
|
||||
? nebula::ui::GetHomeUrl()
|
||||
: nebula::ui::ToInternalUrl(url));
|
||||
PersistSession();
|
||||
}
|
||||
|
||||
void NebulaController::OnContentTitleChanged(CefRefPtr<CefBrowser> browser, const std::string& title) {
|
||||
tabs_.UpdateTitle(browser, title);
|
||||
PersistSession();
|
||||
const auto* active_tab = tabs_.ActiveTab();
|
||||
if (window_ && active_tab && active_tab->browser && active_tab->browser->IsSame(browser)) {
|
||||
window_->SetTitle(Utf8ToWide(title.empty() ? "Nebula Browser" : title + " - Nebula"));
|
||||
@@ -353,6 +382,7 @@ void NebulaController::CreateNewTab() {
|
||||
}
|
||||
|
||||
tabs_.CreateTab(nebula::ui::GetHomeUrl());
|
||||
PersistSession();
|
||||
CreateContentBrowser();
|
||||
}
|
||||
|
||||
@@ -366,6 +396,7 @@ void NebulaController::ActivateTab(int tab_id) {
|
||||
if (!tabs_.ActivateTab(tab_id)) {
|
||||
return;
|
||||
}
|
||||
PersistSession();
|
||||
|
||||
SetBrowserVisible(previous_browser, false);
|
||||
if (auto* active_tab = tabs_.ActiveTab()) {
|
||||
@@ -385,12 +416,14 @@ void NebulaController::CloseTab(int tab_id) {
|
||||
}();
|
||||
|
||||
CefRefPtr<CefBrowser> closing_browser = tabs_.CloseTab(tab_id);
|
||||
PersistSession();
|
||||
if (closing_browser) {
|
||||
closing_browser->GetHost()->CloseBrowser(false);
|
||||
}
|
||||
|
||||
if (!tabs_.ActiveTab()) {
|
||||
tabs_.CreateTab(nebula::ui::GetHomeUrl());
|
||||
PersistSession();
|
||||
CreateContentBrowser();
|
||||
return;
|
||||
}
|
||||
@@ -485,6 +518,7 @@ void NebulaController::ToggleDevTools() {
|
||||
|
||||
CefWindowInfo window_info;
|
||||
window_info.SetAsPopup(window_->hwnd(), "Nebula Developer Tools");
|
||||
window_info.runtime_style = CEF_RUNTIME_STYLE_ALLOY;
|
||||
CefBrowserSettings browser_settings;
|
||||
host->ShowDevTools(window_info, content_client_, browser_settings, CefPoint());
|
||||
}
|
||||
@@ -580,6 +614,10 @@ void NebulaController::SendChromeState(const nebula::browser::NebulaTab& tab) {
|
||||
chrome_browser_->GetMainFrame()->ExecuteJavaScript(script, nebula::ui::GetChromeUrl(), 0);
|
||||
}
|
||||
|
||||
void NebulaController::PersistSession() const {
|
||||
nebula::browser::SaveSessionState(tabs_.Tabs(), tabs_.ActiveTabIndex());
|
||||
}
|
||||
|
||||
void NebulaController::MaybeFinishShutdown() {
|
||||
if (!closing_) {
|
||||
return;
|
||||
|
||||
@@ -54,6 +54,7 @@ private:
|
||||
void SetContentFullscreen(bool fullscreen);
|
||||
void ResizeBrowsers();
|
||||
void SendChromeState(const nebula::browser::NebulaTab& tab);
|
||||
void PersistSession() const;
|
||||
void MaybeFinishShutdown();
|
||||
|
||||
HINSTANCE instance_ = nullptr;
|
||||
|
||||
+27
-1
@@ -9,10 +9,30 @@
|
||||
namespace nebula::app {
|
||||
namespace {
|
||||
|
||||
constexpr wchar_t kMainInstanceMutexName[] = L"Local\\NebulaBrowserMainInstance";
|
||||
|
||||
void EnableDpiAwareness() {
|
||||
SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
|
||||
}
|
||||
|
||||
class ScopedHandle {
|
||||
public:
|
||||
explicit ScopedHandle(HANDLE handle) : handle_(handle) {}
|
||||
~ScopedHandle() {
|
||||
if (handle_) {
|
||||
CloseHandle(handle_);
|
||||
}
|
||||
}
|
||||
|
||||
ScopedHandle(const ScopedHandle&) = delete;
|
||||
ScopedHandle& operator=(const ScopedHandle&) = delete;
|
||||
|
||||
bool valid() const { return handle_ != nullptr; }
|
||||
|
||||
private:
|
||||
HANDLE handle_ = nullptr;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
int RunNebula(HINSTANCE instance, int show_command) {
|
||||
@@ -26,8 +46,14 @@ int RunNebula(HINSTANCE instance, int show_command) {
|
||||
return subprocess_exit_code;
|
||||
}
|
||||
|
||||
ScopedHandle main_instance_mutex(CreateMutexW(nullptr, TRUE, kMainInstanceMutexName));
|
||||
if (main_instance_mutex.valid() && GetLastError() == ERROR_ALREADY_EXISTS) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
CefSettings settings;
|
||||
settings.no_sandbox = true;
|
||||
settings.persist_session_cookies = true;
|
||||
|
||||
// A persistent profile is required for the GPU shader cache and several
|
||||
// hardware acceleration features. Without these Chromium silently falls
|
||||
@@ -50,7 +76,7 @@ int RunNebula(HINSTANCE instance, int show_command) {
|
||||
command_line->InitFromString(GetCommandLineW());
|
||||
|
||||
std::string initial_url = command_line->GetSwitchValue("url");
|
||||
if (nebula::ui::IsEmptyOrChromiumNewTabUrl(initial_url)) {
|
||||
if (!initial_url.empty() && nebula::ui::IsChromiumNewTabUrl(initial_url)) {
|
||||
initial_url = nebula::ui::GetHomeUrl();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user