From 10180b7109ca900d0f1a623050213b9a9bde4cd3 Mon Sep 17 00:00:00 2001 From: Andrew Zambazos Date: Thu, 14 May 2026 19:11:06 +1200 Subject: [PATCH] Support nebula:// internal pages & GPU tools Introduce support for an internal nebula:// URL scheme and internal page routing (ResolveInternalUrl / ToInternalUrl), including dedicated slugs for home, settings, downloads, big-picture, gpu-diagnostics, insecure and a 404 fallback. Wire internal resolution into browser creation and tab navigation so internal pages load from local UI files. Add an insecure-warning interstitial flow with a navigate-insecure command and a one-shot bypass set (ShouldBypassInsecureWarning) so content can request navigating to an HTTP target after user confirmation. Harden BrowserClient handling to resolve Chromium new-tab and nebula internal URLs, redirect HTTP to the insecure warning when appropriate, and handle 404 responses by loading the internal 404 page. Update chrome UI behavior to hide internal home URLs, accept nebula:// in navigation input checks, and add a GPU Diagnostics page (revamped UI + diagnostic scripts) plus menu entry. Misc: improve URL utilities (scheme checks, percent-encoding, decorations), fix 404 display text, adjust menu popup size, tweak window frame styling (DWM attributes) and remove branding block from chrome UI CSS. --- src/app/nebula_controller.cpp | 40 ++- src/app/nebula_controller.h | 3 + src/browser/tab_manager.cpp | 3 +- src/browser/url_utils.cpp | 9 +- src/cef/browser_client.cpp | 51 ++- src/cef/browser_client.h | 1 + src/ui/paths.cpp | 158 ++++++++- src/ui/paths.h | 8 + src/window/nebula_window.cpp | 30 +- ui/css/chrome.css | 20 -- ui/js/chrome.js | 2 +- ui/js/home.js | 2 +- ui/pages/404.html | 2 +- ui/pages/chrome.html | 5 - ui/pages/gpu-diagnostics.html | 603 +++++++++++++++++++++++----------- ui/pages/insecure.html | 4 + ui/pages/menu-popup.html | 1 + 17 files changed, 704 insertions(+), 238 deletions(-) diff --git a/src/app/nebula_controller.cpp b/src/app/nebula_controller.cpp index 2680100..fa2d99f 100644 --- a/src/app/nebula_controller.cpp +++ b/src/app/nebula_controller.cpp @@ -55,7 +55,7 @@ RECT MenuPopupRect(HWND hwnd, const nebula::window::BrowserLayout& layout) { GetClientRect(hwnd, &client); const int width = ScaleForWindow(hwnd, 260); - const int height = ScaleForWindow(hwnd, 218); + const int height = ScaleForWindow(hwnd, 258); const int margin = ScaleForWindow(hwnd, 12); const int overlap = ScaleForWindow(hwnd, 2); @@ -103,6 +103,10 @@ std::string WithCacheBuster(std::string url) { return url + separator + "nebula_cache_bust=" + std::to_string(GetTickCount64()) + fragment; } +std::string GetChromeDisplayUrl(const std::string& url) { + return nebula::ui::IsInternalHomeUrl(url) ? std::string{} : url; +} + void SetBrowserVisible(CefRefPtr browser, bool visible) { if (!browser) { return; @@ -209,6 +213,12 @@ void NebulaController::OnBrowserClosing(nebula::cef::BrowserRole role, CefRefPtr void NebulaController::OnChromeCommand(const std::string& command, const std::string& payload) { if (command == "navigate") { tabs_.LoadURL(payload); + } else if (command == "navigate-insecure") { + const std::string target = nebula::browser::NormalizeNavigationInput(payload); + if (nebula::ui::IsHttpUrl(target)) { + insecure_warning_bypasses_.insert(target); + tabs_.LoadURL(target); + } } else if (command == "new-tab") { CreateNewTab(); } else if (command == "activate-tab") { @@ -233,6 +243,9 @@ void NebulaController::OnChromeCommand(const std::string& command, const std::st } else if (command == "big-picture") { CloseMenuPopup(); tabs_.LoadURL(nebula::ui::GetBigPictureUrl()); + } else if (command == "gpu-diagnostics") { + CloseMenuPopup(); + tabs_.LoadURL(nebula::ui::GetGpuDiagnosticsUrl()); } else if (command == "toggle-devtools") { ToggleDevTools(); } else if (command == "zoom-out") { @@ -263,7 +276,10 @@ void NebulaController::OnChromeCommand(const std::string& command, const std::st } void NebulaController::OnContentAddressChanged(CefRefPtr browser, const std::string& url) { - tabs_.UpdateURL(browser, nebula::ui::IsChromiumNewTabUrl(url) ? nebula::ui::GetHomeUrl() : url); + tabs_.UpdateURL(browser, + nebula::ui::IsChromiumNewTabUrl(url) + ? nebula::ui::GetHomeUrl() + : nebula::ui::ToInternalUrl(url)); } void NebulaController::OnContentTitleChanged(CefRefPtr browser, const std::string& title) { @@ -296,6 +312,20 @@ void NebulaController::OnPopupRequested(CefRefPtr browser, const std : target_url); } +bool NebulaController::ShouldBypassInsecureWarning(CefRefPtr browser, const std::string& target_url) { + if (!tabs_.OwnsBrowser(browser)) { + return false; + } + + const auto bypass = insecure_warning_bypasses_.find(target_url); + if (bypass == insecure_warning_bypasses_.end()) { + return false; + } + + insecure_warning_bypasses_.erase(bypass); + return true; +} + void NebulaController::CreateNewTab() { if (auto* tab = tabs_.ActiveTab()) { SetBrowserVisible(tab->browser, false); @@ -376,7 +406,8 @@ void NebulaController::CreateContentBrowser() { CefBrowserSettings browser_settings; content_client_ = new nebula::cef::NebulaBrowserClient(nebula::cef::BrowserRole::Content, this); CefWindowInfo window_info = ChildWindowInfo(window_->hwnd(), layout.content); - CefBrowserHost::CreateBrowser(window_info, content_client_, url, browser_settings, nullptr, nullptr); + CefBrowserHost::CreateBrowser( + window_info, content_client_, nebula::ui::ResolveInternalUrl(url), browser_settings, nullptr, nullptr); } void NebulaController::ToggleMenuPopup() { @@ -476,6 +507,7 @@ void NebulaController::SendChromeState(const nebula::browser::NebulaTab& tab) { return; } + const std::string display_url = GetChromeDisplayUrl(tab.url); std::string tabs_json = "["; const auto& tabs = tabs_.Tabs(); for (size_t i = 0; i < tabs.size(); ++i) { @@ -495,7 +527,7 @@ void NebulaController::SendChromeState(const nebula::browser::NebulaTab& tab) { const std::string script = "window.NebulaChrome && window.NebulaChrome.applyState({" "\"id\":" + std::to_string(tab.id) + - ",\"url\":\"" + nebula::browser::JsonEscape(tab.url) + "\"" + ",\"url\":\"" + nebula::browser::JsonEscape(display_url) + "\"" ",\"title\":\"" + nebula::browser::JsonEscape(tab.title) + "\"" ",\"isLoading\":" + std::string(tab.is_loading ? "true" : "false") + ",\"progress\":" + std::to_string(tab.load_progress) + diff --git a/src/app/nebula_controller.h b/src/app/nebula_controller.h index 9a4a899..05d14ab 100644 --- a/src/app/nebula_controller.h +++ b/src/app/nebula_controller.h @@ -2,6 +2,7 @@ #include #include +#include #include #include "browser/tab_manager.h" @@ -34,6 +35,7 @@ public: void OnContentLoadProgressChanged(CefRefPtr browser, double progress) override; void OnContentFaviconChanged(CefRefPtr browser, const std::vector& urls) override; void OnPopupRequested(CefRefPtr browser, const std::string& target_url) override; + bool ShouldBypassInsecureWarning(CefRefPtr browser, const std::string& target_url) override; private: void CreateNewTab(); @@ -65,6 +67,7 @@ private: CefRefPtr chrome_client_; CefRefPtr content_client_; CefRefPtr menu_popup_client_; + std::unordered_set insecure_warning_bypasses_; }; } // namespace nebula::app diff --git a/src/browser/tab_manager.cpp b/src/browser/tab_manager.cpp index f052a43..f621723 100644 --- a/src/browser/tab_manager.cpp +++ b/src/browser/tab_manager.cpp @@ -1,6 +1,7 @@ #include "browser/tab_manager.h" #include "browser/url_utils.h" +#include "ui/paths.h" namespace nebula::browser { @@ -134,7 +135,7 @@ void TabManager::LoadURL(const std::string& input) { tab->url = target; tab->favicon_url.clear(); - tab->browser->GetMainFrame()->LoadURL(target); + tab->browser->GetMainFrame()->LoadURL(nebula::ui::ResolveInternalUrl(target)); Notify(); } diff --git a/src/browser/url_utils.cpp b/src/browser/url_utils.cpp index 3338587..67b9ee4 100644 --- a/src/browser/url_utils.cpp +++ b/src/browser/url_utils.cpp @@ -1,5 +1,6 @@ #include "browser/url_utils.h" +#include #include #include #include @@ -19,13 +20,17 @@ std::string Trim(std::string value) { return value; } -bool StartsWithScheme(const std::string& value) { +bool StartsWithScheme(std::string value) { + std::transform(value.begin(), value.end(), value.begin(), [](unsigned char ch) { + return static_cast(std::tolower(ch)); + }); return value.starts_with("http://") || value.starts_with("https://") || value.starts_with("file:") || value.starts_with("data:") || value.starts_with("blob:") || - value.starts_with("chrome:"); + value.starts_with("chrome:") || + value.starts_with("nebula://"); } bool LooksLikeHostName(const std::string& value) { diff --git a/src/cef/browser_client.cpp b/src/cef/browser_client.cpp index 6effe3b..f1453b8 100644 --- a/src/cef/browser_client.cpp +++ b/src/cef/browser_client.cpp @@ -9,6 +9,14 @@ namespace { constexpr char kChromeCommandMessage[] = "NebulaChromeCommand"; +bool IsInsecureInterstitialFrame(CefRefPtr frame) { + if (!frame) { + return false; + } + + return nebula::ui::ToInternalUrl(frame->GetURL().ToString()).starts_with("nebula://insecure"); +} + std::vector ToStringVector(const std::vector& values) { std::vector result; result.reserve(values.size()); @@ -29,17 +37,25 @@ bool NebulaBrowserClient::OnProcessMessageReceived(CefRefPtr browser CefRefPtr message) { CEF_REQUIRE_UI_THREAD(); UNREFERENCED_PARAMETER(browser); - UNREFERENCED_PARAMETER(frame); UNREFERENCED_PARAMETER(source_process); - if ((role_ != BrowserRole::Chrome && role_ != BrowserRole::MenuPopup) || !message || - message->GetName().ToString() != kChromeCommandMessage) { + if (!message || message->GetName().ToString() != kChromeCommandMessage) { + return false; + } + + if (role_ != BrowserRole::Chrome && role_ != BrowserRole::MenuPopup && + role_ != BrowserRole::Content) { return false; } CefRefPtr args = message->GetArgumentList(); const std::string command = args && args->GetSize() > 0 ? args->GetString(0).ToString() : ""; const std::string payload = args && args->GetSize() > 1 ? args->GetString(1).ToString() : ""; + if (role_ == BrowserRole::Content && + (command != "navigate-insecure" || !IsInsecureInterstitialFrame(frame))) { + return false; + } + if (delegate_ && !command.empty()) { delegate_->OnChromeCommand(command, payload); return true; @@ -172,9 +188,14 @@ void NebulaBrowserClient::OnLoadEnd(CefRefPtr browser, CefRefPtr frame, int httpStatusCode) { CEF_REQUIRE_UI_THREAD(); - UNREFERENCED_PARAMETER(httpStatusCode); if (role_ == BrowserRole::Content && delegate_ && frame && frame->IsMain()) { + if (httpStatusCode == 404) { + frame->LoadURL(nebula::ui::ResolveInternalUrl( + nebula::ui::GetNotFoundUrl(frame->GetURL().ToString()))); + return; + } + delegate_->OnContentLoadProgressChanged(browser, 1.0); } } @@ -189,10 +210,24 @@ bool NebulaBrowserClient::OnBeforeBrowse(CefRefPtr browser, UNREFERENCED_PARAMETER(user_gesture); UNREFERENCED_PARAMETER(is_redirect); - if (role_ == BrowserRole::Content && frame && frame->IsMain() && request && - nebula::ui::IsChromiumNewTabUrl(request->GetURL().ToString())) { - frame->LoadURL(nebula::ui::GetHomeUrl()); - return true; + if (role_ == BrowserRole::Content && frame && frame->IsMain() && request) { + const std::string url = request->GetURL().ToString(); + if (nebula::ui::IsChromiumNewTabUrl(url)) { + frame->LoadURL(nebula::ui::ResolveInternalUrl(nebula::ui::GetHomeUrl())); + return true; + } + + if (nebula::ui::IsNebulaInternalUrl(url)) { + frame->LoadURL(nebula::ui::ResolveInternalUrl(url)); + return true; + } + + if (nebula::ui::IsHttpUrl(url) && + (!delegate_ || !delegate_->ShouldBypassInsecureWarning(browser, url))) { + frame->LoadURL(nebula::ui::ResolveInternalUrl( + nebula::ui::GetInsecureWarningUrl(url))); + return true; + } } return false; diff --git a/src/cef/browser_client.h b/src/cef/browser_client.h index f53da04..6347433 100644 --- a/src/cef/browser_client.h +++ b/src/cef/browser_client.h @@ -31,6 +31,7 @@ public: virtual void OnContentLoadProgressChanged(CefRefPtr browser, double progress) = 0; virtual void OnContentFaviconChanged(CefRefPtr browser, const std::vector& urls) = 0; virtual void OnPopupRequested(CefRefPtr browser, const std::string& target_url) = 0; + virtual bool ShouldBypassInsecureWarning(CefRefPtr browser, const std::string& target_url) = 0; }; class NebulaBrowserClient final : public CefClient, diff --git a/src/ui/paths.cpp b/src/ui/paths.cpp index d071255..8ef4b23 100644 --- a/src/ui/paths.cpp +++ b/src/ui/paths.cpp @@ -4,10 +4,31 @@ #include #include +#include namespace nebula::ui { namespace { +constexpr std::string_view kNebulaScheme = "nebula://"; +constexpr std::wstring_view kInternalFallbackPage = L"404.html"; + +struct InternalPage { + std::string_view slug; + std::wstring_view file_name; +}; + +constexpr InternalPage kInternalPages[] = { + {"home", L"home.html"}, + {"settings", L"settings.html"}, + {"downloads", L"downloads.html"}, + {"bigpicture", L"bigpicture.html"}, + {"big-picture", L"bigpicture.html"}, + {"gpu-diagnostics", L"gpu-diagnostics.html"}, + {"setup", L"setup.html"}, + {"404", L"404.html"}, + {"insecure", L"insecure.html"}, +}; + std::string WideToUtf8(const std::wstring& value) { if (value.empty()) { return {}; @@ -31,6 +52,11 @@ std::string GetUrlWithoutDecoration(std::string url) { return url; } +std::string GetUrlDecoration(const std::string& url) { + const size_t split = url.find_first_of("?#"); + return split == std::string::npos ? std::string{} : url.substr(split); +} + std::string ToLowerAscii(std::string value) { std::transform(value.begin(), value.end(), value.begin(), [](unsigned char ch) { return static_cast(std::tolower(ch)); @@ -38,6 +64,66 @@ std::string ToLowerAscii(std::string value) { return value; } +std::string PageFileUrl(std::wstring_view page_name) { + const auto path = GetUiPagePath(std::wstring(page_name)); + return path.empty() ? std::string{} : FilePathToUrl(path); +} + +std::string PercentEncode(const std::string& value) { + constexpr char kHex[] = "0123456789ABCDEF"; + std::string encoded; + encoded.reserve(value.size()); + for (unsigned char ch : value) { + if (std::isalnum(ch) || ch == '-' || ch == '_' || ch == '.' || ch == '~') { + encoded += static_cast(ch); + } else { + encoded += '%'; + encoded += kHex[ch >> 4]; + encoded += kHex[ch & 0x0F]; + } + } + return encoded; +} + +std::string InternalPageName(const std::string& url) { + std::string target = ToLowerAscii(GetUrlWithoutDecoration(url)); + if (!target.starts_with(kNebulaScheme)) { + return {}; + } + + target.erase(0, kNebulaScheme.size()); + while (!target.empty() && target.front() == '/') { + target.erase(target.begin()); + } + while (!target.empty() && target.back() == '/') { + target.pop_back(); + } + return target.empty() ? "home" : target; +} + +std::string InternalUrlForSlug(std::string_view slug) { + return std::string(kNebulaScheme) + std::string(slug); +} + +const InternalPage* FindInternalPageBySlug(std::string_view slug) { + for (const auto& page : kInternalPages) { + if (page.slug == slug) { + return &page; + } + } + return nullptr; +} + +const InternalPage* FindInternalPageByFileUrl(const std::string& url) { + const std::string base_url = GetUrlWithoutDecoration(url); + for (const auto& page : kInternalPages) { + if (PageFileUrl(page.file_name) == base_url) { + return &page; + } + } + return nullptr; +} + } // namespace std::filesystem::path GetExecutableDirectory() { @@ -77,31 +163,85 @@ std::string FilePathToUrl(std::filesystem::path path) { std::string GetChromeUrl() { const auto path = GetUiPagePath(L"chrome.html"); - return path.empty() ? GetHomeUrl() : FilePathToUrl(path); + const std::string fallback = PageFileUrl(L"home.html"); + return path.empty() ? (fallback.empty() ? "https://www.google.com" : fallback) : FilePathToUrl(path); } std::string GetHomeUrl() { - const auto path = GetUiPagePath(L"home.html"); - return path.empty() ? "https://www.google.com" : FilePathToUrl(path); + return InternalUrlForSlug("home"); } std::string GetSettingsUrl() { - const auto path = GetUiPagePath(L"settings.html"); - return path.empty() ? GetHomeUrl() : FilePathToUrl(path); + return InternalUrlForSlug("settings"); +} + +std::string GetDownloadsUrl() { + return InternalUrlForSlug("downloads"); } std::string GetBigPictureUrl() { - const auto path = GetUiPagePath(L"bigpicture.html"); - return path.empty() ? GetHomeUrl() : FilePathToUrl(path); + return InternalUrlForSlug("bigpicture"); +} + +std::string GetGpuDiagnosticsUrl() { + return InternalUrlForSlug("gpu-diagnostics"); } std::string GetMenuPopupUrl() { const auto path = GetUiPagePath(L"menu-popup.html"); - return path.empty() ? GetHomeUrl() : FilePathToUrl(path); + const std::string fallback = PageFileUrl(L"home.html"); + return path.empty() ? (fallback.empty() ? "https://www.google.com" : fallback) : FilePathToUrl(path); +} + +std::string GetInsecureWarningUrl(const std::string& target_url) { + return InternalUrlForSlug("insecure") + "?target=" + PercentEncode(target_url); +} + +std::string GetNotFoundUrl(const std::string& target_url) { + return InternalUrlForSlug("404") + "?url=" + PercentEncode(target_url); +} + +std::string ResolveInternalUrl(const std::string& url) { + const std::string page_name = InternalPageName(url); + if (page_name.empty()) { + return url; + } + + if (const auto* page = FindInternalPageBySlug(page_name)) { + const std::string file_url = PageFileUrl(page->file_name); + return file_url.empty() ? url : file_url + GetUrlDecoration(url); + } + + const std::string fallback_url = PageFileUrl(kInternalFallbackPage); + return fallback_url.empty() ? url : fallback_url + "?url=" + PercentEncode(url); +} + +std::string ToInternalUrl(const std::string& url) { + const std::string page_name = InternalPageName(url); + if (!page_name.empty()) { + if (const auto* page = FindInternalPageBySlug(page_name)) { + return InternalUrlForSlug(page->slug) + GetUrlDecoration(url); + } + return url; + } + + if (const auto* page = FindInternalPageByFileUrl(url)) { + return InternalUrlForSlug(page->slug) + GetUrlDecoration(url); + } + + return url; } bool IsInternalHomeUrl(const std::string& url) { - return GetUrlWithoutDecoration(url) == GetHomeUrl(); + return GetUrlWithoutDecoration(ToInternalUrl(url)) == GetHomeUrl(); +} + +bool IsNebulaInternalUrl(const std::string& url) { + return ToLowerAscii(url).starts_with(kNebulaScheme); +} + +bool IsHttpUrl(const std::string& url) { + return ToLowerAscii(url).starts_with("http://"); } bool IsChromiumNewTabUrl(const std::string& url) { diff --git a/src/ui/paths.h b/src/ui/paths.h index 58348bd..caebd74 100644 --- a/src/ui/paths.h +++ b/src/ui/paths.h @@ -11,10 +11,18 @@ std::string FilePathToUrl(std::filesystem::path path); std::string GetChromeUrl(); std::string GetHomeUrl(); std::string GetSettingsUrl(); +std::string GetDownloadsUrl(); std::string GetBigPictureUrl(); +std::string GetGpuDiagnosticsUrl(); std::string GetMenuPopupUrl(); +std::string GetInsecureWarningUrl(const std::string& target_url); +std::string GetNotFoundUrl(const std::string& target_url); +std::string ResolveInternalUrl(const std::string& url); +std::string ToInternalUrl(const std::string& url); bool IsInternalHomeUrl(const std::string& url); +bool IsNebulaInternalUrl(const std::string& url); +bool IsHttpUrl(const std::string& url); bool IsChromiumNewTabUrl(const std::string& url); bool IsEmptyOrChromiumNewTabUrl(const std::string& url); diff --git a/src/window/nebula_window.cpp b/src/window/nebula_window.cpp index f6e57d3..8384bc0 100644 --- a/src/window/nebula_window.cpp +++ b/src/window/nebula_window.cpp @@ -15,6 +15,7 @@ constexpr wchar_t kChildFrameHitTestParentProp[] = L"NebulaChildFrameHitTestPare constexpr int kTitleRowHeightDip = 42; constexpr int kWindowControlWidthDip = 46; constexpr int kWindowControlCount = 3; +constexpr COLORREF kNoWindowBorderColor = 0xFFFFFFFE; RECT GetWorkArea() { RECT work_area = {}; @@ -56,6 +57,28 @@ bool SetResizeCursor(LRESULT hit) { return true; } +void ApplyWindowFrameStyle(HWND hwnd) { + const BOOL dark_mode = TRUE; + const DWM_WINDOW_CORNER_PREFERENCE corner_preference = DWMWCP_ROUND; + DwmSetWindowAttribute( + hwnd, + DWMWA_USE_IMMERSIVE_DARK_MODE, + &dark_mode, + sizeof(dark_mode)); + + DwmSetWindowAttribute( + hwnd, + DWMWA_WINDOW_CORNER_PREFERENCE, + &corner_preference, + sizeof(corner_preference)); + + DwmSetWindowAttribute( + hwnd, + DWMWA_BORDER_COLOR, + &kNoWindowBorderColor, + sizeof(kNoWindowBorderColor)); +} + } // namespace NebulaWindow::NebulaWindow(WindowDelegate* delegate) : delegate_(delegate) {} @@ -92,8 +115,9 @@ bool NebulaWindow::Create(HINSTANCE instance, int show_command) { } UpdateDpi(); + ApplyWindowFrameStyle(hwnd_); - const MARGINS margins = {1, 1, 1, 1}; + const MARGINS margins = {0, 0, 0, 0}; DwmExtendFrameIntoClientArea(hwnd_, &margins); ShowWindow(hwnd_, show_command); @@ -265,6 +289,10 @@ LRESULT NebulaWindow::WndProc(UINT message, WPARAM wparam, LPARAM lparam) { } break; + case WM_NCACTIVATE: + ApplyWindowFrameStyle(hwnd_); + return TRUE; + case WM_ERASEBKGND: return 1; diff --git a/ui/css/chrome.css b/ui/css/chrome.css index 5970cfa..6fbe84e 100644 --- a/ui/css/chrome.css +++ b/ui/css/chrome.css @@ -78,26 +78,6 @@ button:disabled { background: var(--bg); } -/* ── Brand ──────────────────────────────────────────────────── */ - -.brand { - display: flex; - align-items: center; - gap: 7px; - min-width: 104px; - color: var(--accent-2); - font-size: 0.73rem; - font-weight: 800; - letter-spacing: 0.15em; - text-transform: uppercase; - opacity: 0.85; -} - -.brand-icon { - width: 17px; - height: 17px; -} - /* ── Tabs ───────────────────────────────────────────────────── */ .tabs { diff --git a/ui/js/chrome.js b/ui/js/chrome.js index ee5a47b..56d19da 100644 --- a/ui/js/chrome.js +++ b/ui/js/chrome.js @@ -15,7 +15,7 @@ const state = { function toNavigationUrl(input) { const value = (input || '').trim(); if (!value) return null; - if (/^(https?:|file:|data:|blob:|chrome:)/i.test(value)) return value; + if (/^(https?:|file:|data:|blob:|chrome:|nebula:\/\/)/i.test(value)) return value; if (value.includes('.') && !/\s/.test(value)) return `https://${value}`; return `${SEARCH_URL}${encodeURIComponent(value)}`; } diff --git a/ui/js/home.js b/ui/js/home.js index d185a7a..647f5e1 100644 --- a/ui/js/home.js +++ b/ui/js/home.js @@ -88,7 +88,7 @@ function applySelectedSearchEngine(engine) { function normalizeNavigationUrl(input) { const value = (input || '').trim(); if (!value) return null; - if (/^(https?:|file:|data:|blob:)/i.test(value)) return value; + if (/^(https?:|file:|data:|blob:|nebula:\/\/)/i.test(value)) return value; if (value.includes('.') && !/\s/.test(value)) return `https://${value}`; return `${searchEngines[selectedSearchEngine]}${encodeURIComponent(value)}`; } diff --git a/ui/pages/404.html b/ui/pages/404.html index d9fc547..08a6b46 100644 --- a/ui/pages/404.html +++ b/ui/pages/404.html @@ -61,7 +61,7 @@ const attemptedUrl = params.get('url'); const box = document.getElementById('targetBox'); if (attemptedUrl) { - box.textContent = decodeURIComponent(attemptedUrl); + box.textContent = attemptedUrl; } else { box.textContent = 'Unknown URL'; } diff --git a/ui/pages/chrome.html b/ui/pages/chrome.html index c12d969..e4c437c 100644 --- a/ui/pages/chrome.html +++ b/ui/pages/chrome.html @@ -9,11 +9,6 @@
-
- - Nebula -
-
+ -
-

WebGL Test

- -

Testing WebGL...

-
+
+
+
Waiting
+

Summary

+
+
-
-

Canvas 2D Acceleration Test

- -

Testing Canvas 2D...

-
+
+
Waiting
+

WebGL

+ +

+
-
-

Actions

- - - - -
+
+
Waiting
+

WebGL2

+ +

+
-
-

Detailed GPU Information

-
Loading...
-
-
+
+
Waiting
+

Canvas 2D

+ +

+
+ + +
+

Detailed Information

+
Waiting for diagnostics...
+
+ diff --git a/ui/pages/insecure.html b/ui/pages/insecure.html index 20fb75b..4d621ce 100644 --- a/ui/pages/insecure.html +++ b/ui/pages/insecure.html @@ -62,6 +62,10 @@ const box = document.getElementById('targetBox'); if (target) box.textContent = target; function sendNavigate(url, opts){ + if (opts && opts.insecureBypass && window.nebulaNative && window.nebulaNative.postMessage){ + window.nebulaNative.postMessage('navigate-insecure', url); + return; + } if (window.electronAPI && window.electronAPI.sendToHost){ window.electronAPI.sendToHost('navigate', url, opts||{}); } else if (window.parent && window.parent !== window) { diff --git a/ui/pages/menu-popup.html b/ui/pages/menu-popup.html index 76b5770..a55ee53 100644 --- a/ui/pages/menu-popup.html +++ b/ui/pages/menu-popup.html @@ -9,6 +9,7 @@