Add NebulaController, tab manager, and CEF clients

Introduce core application structure and browser management: add NebulaController and run entry (src/app/*) to centralize window, tab and CEF lifecycle logic; implement TabManager and NebulaTab (src/browser/*) for tab creation, navigation and state tracking; add URL utilities (NormalizeNavigationInput, JsonEscape) and CEF browser client glue (src/cef/browser_client.cpp/.h) to forward chrome commands and content events. Update app/main.cpp to delegate startup to nebula::app::RunNebula. Add UI assets (chrome.html, chrome.css, chrome.js, lucide, menu-popup updates) and remove obsolete nebot.html. Update CMakeLists to include new sources, add ${CMAKE_SOURCE_DIR}/src to includes and link dwmapi on Windows. Overall this refactors startup and splits responsibilities for cleaner tab and browser lifecycle handling.
This commit is contained in:
Andrew Zambazos
2026-05-14 10:18:51 +12:00
parent 207a849f06
commit a8786b4c1c
23 changed files with 2835 additions and 330 deletions
+79
View File
@@ -0,0 +1,79 @@
#include "cef/nebula_app.h"
#include "include/cef_process_message.h"
#include "include/wrapper/cef_helpers.h"
namespace nebula::cef {
namespace {
constexpr char kChromeCommandMessage[] = "NebulaChromeCommand";
class NativeBridgeHandler final : public CefV8Handler {
public:
bool Execute(const CefString& name,
CefRefPtr<CefV8Value> object,
const CefV8ValueList& arguments,
CefRefPtr<CefV8Value>& retval,
CefString& exception) override {
UNREFERENCED_PARAMETER(object);
UNREFERENCED_PARAMETER(retval);
if (name != "postMessage") {
return false;
}
if (arguments.empty() || !arguments[0]->IsString()) {
exception = "nebulaNative.postMessage requires a command string.";
return true;
}
CefRefPtr<CefV8Context> context = CefV8Context::GetCurrentContext();
CefRefPtr<CefBrowser> browser = context ? context->GetBrowser() : nullptr;
CefRefPtr<CefFrame> frame = context ? context->GetFrame() : nullptr;
if (!browser || !frame) {
exception = "No CEF frame is available for native messaging.";
return true;
}
CefRefPtr<CefProcessMessage> message = CefProcessMessage::Create(kChromeCommandMessage);
CefRefPtr<CefListValue> args = message->GetArgumentList();
args->SetString(0, arguments[0]->GetStringValue());
args->SetString(1, arguments.size() > 1 && arguments[1]->IsString()
? arguments[1]->GetStringValue()
: CefString());
frame->SendProcessMessage(PID_BROWSER, message);
return true;
}
private:
IMPLEMENT_REFCOUNTING(NativeBridgeHandler);
};
} // namespace
void NebulaApp::OnBeforeCommandLineProcessing(const CefString& process_type,
CefRefPtr<CefCommandLine> command_line) {
UNREFERENCED_PARAMETER(process_type);
// The bundled UI is loaded from file:// and uses ES modules.
command_line->AppendSwitch("allow-file-access-from-files");
}
void NebulaApp::OnContextCreated(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefRefPtr<CefV8Context> context) {
CEF_REQUIRE_RENDERER_THREAD();
UNREFERENCED_PARAMETER(browser);
UNREFERENCED_PARAMETER(frame);
CefRefPtr<CefV8Value> global = context->GetGlobal();
CefRefPtr<CefV8Value> native = CefV8Value::CreateObject(nullptr, nullptr);
native->SetValue(
"postMessage",
CefV8Value::CreateFunction("postMessage", new NativeBridgeHandler()),
V8_PROPERTY_ATTRIBUTE_NONE);
global->SetValue("nebulaNative", native, V8_PROPERTY_ATTRIBUTE_READONLY);
}
} // namespace nebula::cef