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 @@