Project/embedded browser - #6195
Open
callumlinden wants to merge 78 commits into
Open
Conversation
…lled llembedded browser. Creates a floater than consumes it. Currently does next to nothing - next up is to push the llembeddedbrowser::update() calls into their own thread and then flesh out the interface so we can create, destroy, update etc. different 'browser tabs'
…this appears to work fine (might need to consider double buffering the output to avoid flicker) - then the next step is to fill in more of the browser manager and browser tab stuff
… less CPU time spent in rand()
…ddedbrowser to make/destroy tabs
…re swatch by creating a next-POT texture and drawing the output ourselves with some UV scaling
…dBrowser debug setting, defaulted on for this experimental viewer. Makes llembeddedbrowser multi-instance (one tab/thread/buffer per media source instead of a single global one), wires LLViewerMediaImpl's create/ destroy/update/resize/navigate lifecycle to it alongside the existing CEF path, and adds backend-agnostic width/height/texture-size accessors so LLMediaCtrl's draw/layout code doesn't need to reach into LLPluginClassMedia directly. Also: fast row-based checkerboard fill and size-scaled update rate to bound CPU/lock cost on large (up to 4096x4096, EmbeddedBrowserMax Width/Height) buffers, and thread-safety fixes (shared_ptr-held tabs, an atomic pixel+size snapshot handed to the async GL upload, per-tab RNG) found in review. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…r, proving out real CEF frames over shared memory in place of the checkerboard placeholder. LLEmbeddedBrowserTab now claims a view from an external cefshm_producer over the control channel (see the sibling llcefshm-example/llshmframe/ llCefBrowser repos) and pumps read_latest() frames into its pixel buffer each tick instead of generating a checkerboard; navigate()/resize() send kSetUrl/kResize over the same channel. cefshm_protocol.h is a deliberate byte-compatible copy of that repo's own protocol header. Fixes found getting this working end-to-end: - Source pixel format is BGRA (matching CEF's native OnPaint, same as the legacy plugin), not RGBA -- llviewermedia.cpp's embedded-browser texture setup had it hardcoded wrong. - Prim-face rendering has no orientation compensation anywhere (unlike LLMediaCtrl's floater quad, which picks its UV winding based on the media source's self-reported coordinate convention) -- it just trusts the raw buffer's row order, which the legacy plugin has always supplied bottom-up. The shm pipeline hands back top-down rows, so LLEmbeddedBrowserTab::update() now flips on copy, and getMediaTextureCoordsOpenGL() now reports true for the embedded-browser backend to keep LLMediaCtrl's own UV winding in sync with that. - LLViewerMediaImpl::navigateTo() could lose a race against the priority-driven idle pass: a UI-driven LLMediaCtrl calling navigateTo() before createMediaSource() ever ran for that impl would fall through to the legacy plugin path and permanently lock UseEmbeddedBrowser out via createMediaSource()'s own idempotency guard. navigateTo() now resolves the backend up front when neither has been decided yet. - data: URIs need LLURI::escapePathAndData()'s payload re-escaping to parse correctly, same as the legacy loadURI() path already applies -- the embedded-browser entry points were passing the raw string through unescaped. Also repoints llfloaterembeddedbrowsertest.cpp's three test buttons (and its initial create() call) at real URLs instead of the "red"/"green"/ "blue" tokens the checkerboard generator used to special-case, since those have no meaning to a real CEF page. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…browser. destinations/avatar_welcome_pack/search/marketplace used to be force-instantiated on login specifically to warm up their CEF instances ahead of time -- worth revisiting whether that's still needed now that those floaters can run on the embedded-browser backend instead of the memory-heavier plugin.
…kend is wired into real LLMediaCtrl/prim media paths. llfloaterembeddedbrowsertest predates LLViewerMediaImpl routing through UseEmbeddedBrowser and was the only way to exercise create/destroy/ navigate/getPixels directly; that's now covered more realistically by the Media Settings preview, the login screen, and real prim media, and its own hand-rolled swatch-drawing code (unlike LLMediaCtrl) has no UV compensation for the row-flip added in the previous commit, so it was displaying upside-down. Removes the source files, XUI floater definition, its two "Embedded Browser Test" menu entries, and the CMake/registration references. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…e entries, and menu items. The previous commit deleted the floater's source/XUI files but missed staging its LLFloaterReg::add() call, CMakeLists.txt entries, and the two "Embedded Browser Test" menu items (menu_viewer.xml, menu_login.xml) -- completing that removal here. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…vents to the embedded browser. Extends cefshm_protocol.h (this repo's copy and llcefshm-example's canonical one) with kScrollWheel/kKeyEvent (consumer->producer) and kEventLoadStart/LoadEnd/TitleChanged/AddressChanged/CursorChanged (producer->consumer). Keyboard rides LLWindowWin32::getNativeKeyData()'s raw msg/wParam/lParam straight into llCefBrowserManager::SendKeyEvent(), a clean fit since that's exactly the shape it already expects. LLEmbeddedBrowserTab gains mouseMove/mouseButton/scrollWheel/keyEvent senders and a popEvent() FIFO; LLViewerMediaImpl wires those into every mouse/keyboard entry point and drains events each update() via emitEvent(nullptr, ...) -- nullptr standing in for "no real LLPluginClassMedia behind this," since a genuine plugin callback never passes one. That nullptr choice forced a real audit: grepped every LLViewerMediaObserver::handleMediaEvent override for the 5 events now emitted, and found genuine unconditional (non-debug-log) self-></ dereferences in llfloaterhelpbrowser.cpp, llfloaterwebcontent.cpp (x3), llpaneldirweb.cpp, and llfloater360capture.cpp -- real crash risks once embedded-browser events start flowing, not hypothetical. Patched with fallbacks to cached state (LLMediaCtrl::getCurrentNavUrl(), and a new backend-agnostic LLViewerMediaImpl::getMediaName()/LLMediaCtrl:: getMediaName() pair for title, mirroring the width/height/textureCoords accessors already added). Also defensively guarded the debug-log-only cases in llmediactrl.cpp and llviewerparcelmedia.cpp. Two more bugs surfaced through actual interactive testing, both in pre-existing code this session never touched before today: - LLMediaCtrl::convertInputCoords() computed its OpenGL-coords flag via mMediaSource->getMediaPlugin()->getTextureCoordsOpenGL(), bypassing the backend-agnostic getMediaTextureCoordsOpenGL() accessor added earlier this session -- getMediaPlugin() is null for embedded-browser by construction, so this always read false, inverting mouse Y for every embedded-browser click/move/scroll regardless of window size. - Once that was fixed, Y was still wrong whenever the widget was larger than its aspect-corrected content (mStretchToFill/mMaintainAspectRatio default true for every LLMediaCtrl): the flip used getRect().getHeight() (the full widget) instead of the locally computed, aspect-corrected height from calcOffsetsAndSize(), throwing Y off by exactly the centering/letterbox offset. Likely a longstanding latent bug for the legacy plugin path too whenever centering was actually active; just never reported. Also raised LLEmbeddedBrowserUpdateThread::run()'s size-scaled fps floor (10->30) and widened its full-60fps zone (512x512->1280x720): those numbers were tuned for bounding the CPU cost of painting the old checkerboard placeholder, not for real interactive latency, and made real mouse-move feedback feel sluggish on anything larger than a small thumbnail. Scroll wheel needed one more fix on top: LLMediaCtrl::handleScrollWheel/ handleScrollHWheel gate on hasMedia(), which is hardcoded to "is there a real plugin" (mMediaSource != NULL) and always false for embedded-browser, silently dropping every scroll event before it could reach any of the above. Added a narrow LLViewerMediaImpl::isUsingEmbeddedBrowser() accessor rather than widening hasMedia() itself, which has ~60 call sites and at least one (LLMediaCtrl::handleToolTip) that immediately follows it with an unchecked getMediaPlugin()->getHoverText() -- widening hasMedia() would have traded a silent scroll-wheel bug for a tooltip crash. Known gaps, out of scope for this pass: prim-face media has no scroll wheel support at all today, for either backend (not a regression); mouse double-click is sent as a plain click, relying on CEF's own consecutive-click timing rather than an explicit wire signal; horizontal scroll is dropped (SendMouseWheelEvent has no deltaX equivalent); and input-to-display latency still feels sluggish to the user even after the fps-floor fix, not yet root-caused further. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Reverses 1537870 -- keeping destinations/avatar_welcome_pack/search/ marketplace preloaded on login after all, a deliberate call independent of the embedded-browser work.
…lifeviewer, not secondlife-bin. secondlife-bin is this experimental build's own binary name; the actual release viewer (used as the legacy comparison baseline) runs as secondlifeviewer. Also trims the example DurationMinutes from 10 to 5.
…ugin behavior. CEF's SendMouseWheelEvent expects wheel-delta units (~120/notch), not SL's raw per-notch click value, and its sign convention for scroll direction is the opposite of Dullahan's for the legacy plugin path. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Forwards MEDIA_EVENT_CLICK_LINK_HREF/CLICK_LINK_NOFOLLOW so links that want a new window/tab or navigate to a custom URL scheme (e.g. secondlife://) get handled the same way as the legacy CEF plugin, and forwards focus/blur to the embedded browser so CEF drives caret blink and focus/blur page JS on click, matching the plugin path's existing behavior. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Forwards MEDIA_EVENT_PICK_FILE_REQUEST/FILE_DOWNLOAD via a new LLEmbeddedMediaFilePicker (mirrors LLMediaFilePicker but keyed to LLViewerMediaImpl instead of LLPluginClassMedia, since embedded browser has no plugin pointer) so native OS open/save dialogs work the same way as the legacy CEF plugin -- this is what llfloater360capture.cpp's image-save flow needs once it moves to the embedded backend. Also forwards MEDIA_EVENT_STATUS_TEXT_CHANGED (e.g. showing a hovered link's URL), guarding every LLViewerMediaObserver::handleMediaEvent override across the viewer that previously dereferenced self unconditionally for that event. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
LLViewerMediaImpl::executeJavaScript() had no embedded-browser branch, and LLMediaCtrl::executeJavaScript()'s hasMedia() gate is plugin-only-false, so neither backend-agnostic path actually worked for embedded browser -- this is what silently broke both the WebGL preview (init(...) never ran) and the Save button (saveAsEqrImage(...) never ran) in llfloater360capture.cpp, which bypassed both and called getMediaPlugin()->executeJavaScript() directly. Wires the new kExecuteJavaScript command through LLEmbeddedBrowser/Tab and the producer, widens LLMediaCtrl's gate, and simplifies llfloater360capture.cpp to the backend-agnostic LLMediaCtrl::executeJavaScript() call it should have used all along. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Forwards console.log/warn/error calls via a new kEventConsoleMessage command, composed into the same "Console message: <msg> in file(<source>) at line <n>" text MediaPluginCEF::onConsoleMessageCallback produces, so LLMediaCtrlListener::getMediaText()'s PAGE_TEXT_EXTRACT_MARKER search keeps working unmodified. Also stops getMediaText() hard-failing when there's no LLPluginClassMedia, since executeJavaScript() already gates correctly for either backend. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
LLEmbeddedBrowserTab gains a mHadDisconnected flag making disconnect/ reconnect edge-triggered (fires once per outage, not once per retry tick): update()'s existing LLReadResult::Disconnected handling pushes one ProducerDisconnected event, connectToProducer() pushes one ProducerReconnected event the next time it succeeds. LLViewerMediaImpl treats ProducerDisconnected like its own MEDIA_EVENT_PLUGIN_FAILED handling (mMediaSourceFailed, resetPreviousMediaState) but with the notification left enabled -- unlike that case, whose notification is disabled after a past "fires every frame" spam incident -- since edge-triggering guarantees this one fires at most once. The popup names "Embedded Browser Provider" rather than the legacy plugin's mime-type-derived name, since no such plugin is involved in this failure path. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Replaces the untracked local-path hack with proper use_prebuilt_binary() installables (llshmframe, llcefbrowser), modeled on the existing dullahan/CEFPlugin.cmake pattern. EmbeddedBrowser.cmake now defines real ll::shmframe and ll::cefbrowser interface targets, each with an optional LLSHMFRAME_LOCAL_BUILD_DIR/LLCEFBROWSER_LOCAL_BUILD_DIR override that skips the autobuild fetch and links straight against a local `stage` build, to keep fast local iteration on those repos. llembeddedbrowser now links ll::shmframe explicitly instead of relying on whatever was previously injecting the raw include path. Note: the two installables currently point at local file:// archives for testing, not a real published package -- publishing is a separate, explicit follow-up step.
Real autobuild publishing hit a wall: both repos are private, so their GitHub Release assets 404 for a plain unauthenticated autobuild install, unlike cef-bin's public S3-hosted package. Until that's resolved (pending confirmation on making the repos public), stay on local file:// packages for both -- refreshed to match current source (the llcefbrowser cygpath fix, llshmframe's canonical_repo correction). canonical_repo for llshmframe now points at its real current location (secondlife/llshmframe, moved from a personal account) regardless of where its package is actually hosted.
Adds kEventVersionInfo to the cefshm wire protocol: cefshm_producer now sends its CEF/Chromium version string once per slot right after allocation, since the Viewer doesn't link CEF/llcefbrowser directly for this path and has no other way to know which build is actually in play. LLEmbeddedBrowser stores the most recently reported version and exposes it alongside llshmframe's own (build-time) version. Both now appear in the About box's Libraries section as "Embedded Browser llShmFrame Version" / "Embedded Browser CEF Version", falling back to "Not connected" if no producer has completed the handshake yet.
The old legacy-plugin CEF/Chromium version block (Dullahan-based) is no longer relevant now that the embedded-browser path exists, so drop it entirely (including the now-unused dullahan_version.h include) and show llCefBrowser's/CEF's/Chromium's versions from the wire message in its place instead. Also: getShmFrameVersion() drops its redundant "llshmframe " prefix (the About-box label already says that), and the CEF version block now includes llCefBrowser's own version, formatted like the block it replaces. Note: only the English strings.xml was updated -- other locales still reference [LIBCEF_VERSION], which will now render as a literal unsubstituted placeholder. Pre-existing localization drift in this fork, not something this change fixes.
Adds indra/llcefproducer (SLCefProducer.exe), a direct port of llcefshm-example's cefshm_producer.cpp: windowless by default via a WinMain wrapper (forwarding the MSVC CRT's __argc/__argv, since CEF's subprocess re-exec model needs real argc/argv and WinMain's own lpCmdLine can't provide them), with an opt-in --console debug console toggleable via the new CefProducerShowConsole setting. LLEmbeddedBrowser::init()/reset() (previously vestigial -- nothing called them) now launch/kill SLCefProducer via LLProcess, gated on UseEmbeddedBrowser; called from LLAppViewer::init()/cleanup(). The launch is eager, not lazy, since the login screen itself renders through this same path and needs a live producer immediately. connectToProducer()'s existing "no producer reachable" branch now triggers a debounced, capped auto-relaunch, reusing the disconnect detection already built and tested on llshmframe's own heartbeat protocol rather than adding a second liveness mechanism. SLCefProducer.exe is packaged into llplugin/ alongside libcef.dll and its other CEF runtime files (new LLDir::getSLCefProducerLauncher(), mirroring getLLPluginLauncher()) rather than next to secondlife-bin.exe -- unlike a DLL loaded from there, a standalone .exe only checks its own directory in the default DLL search order, so it needs to actually live where those files are. Also refreshes the local llshmframe package registration in autobuild.xml: a clean rebuild had fallen back to a stale local package predating yesterday's versioning work, since the LLSHMFRAME_LOCAL_BUILD_DIR cache override doesn't survive a clean build-dir wipe.
LLMediaCtrl::calcOffsetsAndSize() letterboxed/pillarboxed whenever getMediaWidth()/getMediaHeight() didn't match the widget's own aspect ratio -- correct for a video plugin's fixed intrinsic resolution, but for the embedded-browser (CEF) backend that value is just "the size of the last frame that happened to arrive," not a property of the content: a CEF browser always renders at exactly the size it was last told to. This showed up as the login screen's HTML widget intermittently not filling its panel: getMediaWidth()/getMediaHeight() only updates once a frame at a newly-requested size round-trips through the producer, and now that the producer starts asynchronously at Viewer launch instead of being pre-launched by hand, that round trip can take several real seconds -- long enough for a post-construction reshape (e.g. the window settling to its restored size) to be visibly letterboxed against the stale reported size until the next frame lands. Skip the aspect-ratio branch entirely for this backend and always fill the rect; the in-flight resize converges the actual browser size to match regardless.
Replaces duckduckgo.com with an S3-hosted page listing bookmarks useful for testing the embedded-browser work, in both the login-screen and main-viewer copies of this debug menu item.
New log_info()/log_connect()/log_disconnect() helpers (yellow/green/red, via ANSI escape codes -- explicitly enabling ENABLE_VIRTUAL_TERMINAL_ PROCESSING, since a freshly allocated Windows console doesn't interpret them by default). Deliberately ad hoc rather than a logging framework: enough structure that adding another call site later is a one-line thing. Logs: slot connect (green), URL navigation (green), disconnect with a reason -- "crashed consumer" or "idle timeout" (red), plus the existing startup/shutdown banners now go through log_info() for consistency. Also adds an edge-triggered "viewer detached" log using the had_subscriber field (previously written but never read) at the moment a subscriber's has_subscriber() flips false, separate from the slot's actual teardown below it -- which deliberately still waits out the idle grace period in case the same consumer reconnects shortly. CefProducerShowConsole now defaults to true so this is visible during active development; flip back to false once this settles down.
The embedded-browser build's producer moved in-tree as SLCefProducer a few days ago; this script's own examples still named the old standalone cefshm_producer.exe, which would silently sample zero processes for anyone following them literally.
mStatus was a plain EThreadStatus, written by the worker thread as its
last act before returning and polled by shutdown() on the destroying
thread, with the code's own comment already flagging the gap ("we
really need memory barriers here"). LLThread::start() detaches its
underlying std::thread immediately, so isStopped() becoming true is
the *only* signal the destroying thread ever gets that it's safe to
free anything -- there is no real join() anywhere in this lifecycle.
A plain, non-atomic flag gives no real cross-thread visibility
guarantee for that decision. Costs nothing (a plain enum load/store is
already lock-free) and closes a real, if narrow, race that mostly
would only show up under heavy thread create/destroy churn.
Root cause of a run of hard-to-diagnose Viewer crashes reported from a region with many concurrent media sources. LLEmbeddedBrowser::destroy() (and reset()) extracted a tab's shared_ptr under mTabsMutex, erased the map entry, and let the tab's destructor run after releasing the lock -- deliberately, to avoid an already-known deadlock hazard (see that function's own existing comment). But that destructor call could land on *any* thread holding the last reference, including the tab's own LLEmbeddedBrowserUpdateThread: a concurrent findTab() (called from that same thread's own LLEmbeddedBrowser::update(id)) can race the erase() and end up holding the final shared_ptr. When that thread's own copy went out of scope, ~LLEmbeddedBrowserTab() ran *on that update thread*, which then called shutdown() and blocked waiting for itself to stop -- a self-referential deadlock that either hangs or eventually hits LLThread::shutdown()'s own 60s force-kill fallback, which calls TerminateThread() on the currently-executing thread. Killing a thread mid-heap-operation like that leaves the process's C-runtime heap locked/corrupted, which is why every crash this session showed different, seemingly-unrelated garbage in memory that should have been untouched -- it wasn't one consistent overwrite, it was heap-wide damage from the aftermath of a violent self-kill. Confirmed via two independent diagnostics before fixing: - A canary pair on the LLEmbeddedBrowser singleton itself, to rule out (and ultimately rule in favor of a different mechanism than) the singleton's own memory being corrupted or freed early. - isInsideRunLoop(), an atomic flag proving a tab's update thread was still genuinely inside run()'s loop body at the exact moment its own shutdown() call believed it was safe to proceed -- confirmed firing in practice, pinpointing this exact mechanism. Fix: LLEmbeddedBrowserTab::stopUpdateThread() is now called explicitly, by destroy()/reset(), on the thread that's actually dropping the map's own reference, before any shared_ptr copy (anyone's) can go out of scope. This guarantees the update thread has already, genuinely exited run() by the time any destructor can possibly run, regardless of which thread ends up holding the last reference -- so the destructor's own shutdown() call becomes a guaranteed no-op no matter what. Both diagnostics are left in deliberately for now (not yet fully soak-tested) rather than removed immediately -- cheap safety nets that turn any recurrence into a clear signal instead of another blind investigation.
allocate_slot()'s own per-index retry (10x/10ms) paid that full wait cost for every stuck index encountered along the way, even when some other index was actually free right now -- with more than one index stuck at once, that cost compounds and can itself exceed a caller's own patience (e.g. the Viewer's disconnect-alert grace period). Restructured so the outer loop does one fast pass over every free index per attempt (a stuck one costs nothing beyond moving to the next), only retrying the whole pass, with a brief wait, if every free index failed on it.
…aling outages ProducerDisconnected fired the "Embedded Media has failed" alert immediately, every time -- but a producer briefly (and wrongly) concluding a tab's own thread had crashed under heavy system load, then reconnecting a moment later via the existing ProducerReconnected path, would still interrupt the user with a failure dialog for something that was already resolved by the time they saw it. Now waits a 2s grace period before actually raising the alert; a ProducerReconnected arriving within that window cancels it entirely, matching what actually happened from the user's point of view. A genuine, lasting outage still alerts exactly as before, just slightly later.
…efault it off Matches this project's other EmbeddedBrowser* debug settings naming. Defaulting to off keeps production quiet; still a persisted Debug Setting, so it can be flipped back on for debugging with no rebuild.
Every connect/disconnect/navigate line now appends "(N/32 active)", so watching the console doesn't require manually counting colored lines to see how close the producer is to its concurrent-instance ceiling. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…nce cap staying exhausted after a priority-based unload Embedded-browser media never received any volume/mute signal at all: LLViewerMediaImpl::updateVolume() (distance rolloff, the global media- volume slider, "mute all but this one") was entirely gated behind mMediaSource, which is always null for this backend. That's why parcel/ prim media kept playing indefinitely after leaving its region or parcel -- legacy plugin media went silent instantly (its own updateVolume() call reacts to the same enormous post-teleport distance), embedded browser had no equivalent call at all. CEF only exposes a binary SetAudioMuted()/IsAudioMuted() pair, not a continuous per-browser volume level, so updateVolume() now collapses its existing distance/global-volume computation to a mute/unmute decision for this backend, sent through a new kSetMuted opcode: LLViewerMediaImpl -> LLEmbeddedBrowser -> SLCefProducer -> llCefBrowserManager -> CefBrowserHost::SetAudioMuted (llcefbrowser repo, committed separately). Investigating why media still didn't reliably reappear after a teleport turned up a second, unrelated bug in tonight's earlier SLIDESHOW/HIDDEN auto-unload fix: LLViewerMedia::updateMedia()'s instance-cap accounting (PluginInstancesTotal) counts impls by their *priority state*, not by whether they still hold a live resource. That unload fix tore down the CEF tab but left the impl's priority at SLIDESHOW/HIDDEN rather than UNLOADED, so it kept counting against the cap forever even with no resource behind it -- once every slot had ever been touched by "closest N" churn, the cap could never free up again, blocking new-region media from loading at all. Shared wouldUnloadEmbeddedBrowserMedia() between setPriority() (tears the resource down) and updateMedia()'s own counting step fixes this. Also added an immediate fast-path in removeObject(): once a media impl's last owning object is gone, don't wait for the generic priority loop's texture-interest stat (which decays over many seconds, not instantly) to notice -- destroy the embedded-browser tab right away. Finally, fixed a spurious SLCefProducer relaunch found while testing: LLEmbeddedBrowser::reset() kills the producer before stopping every tab's update thread, and a thread still alive in that window could notice the producer is gone and relaunch a brand new one right as the Viewer exits. Gated maybeRelaunchProducer() on LLApp::isExiting(). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…e the concurrent-instance headroom Diagnosed why a lighter region's own media could take 60-90s (or fail outright) to appear after leaving a media-heavy one, even with last night's cap-accounting fix in place: - setPriority()'s SLIDESHOW/HIDDEN auto-unload debounce (added this morning) only guarded that one branch. The cap-accounting fix excludes an already-pending impl from impl_count_total, which lets other candidates fill the cap and can then push that same impl into literal PRIORITY_UNLOADED on a later frame -- a separate, unconditional branch with no debounce at all, silently bypassing the protection. Unified both routes (literal UNLOADED and SLIDESHOW/HIDDEN-active) into the same debounced path. - Even once individual impls stopped being destroyed prematurely, the "closest N" sort itself had no memory: two same-interest candidates (the common case right after a region change, when a crowd of stale not-yet-culled impls from the old region and freshly-noticed impls from the new one are mostly tied at zero interest) were treated as fully interchangeable, tie-broken only by distance. That let the rotation keep reshuffling which ~N candidates counted as loadable every time distances shifted slightly, bumping an already-loading tab out before its own page ever finished rendering -- a different impl winning the slot, not the same one being torn down repeatedly. Added stickiness to that tie-break in priorityComparitor(): prefer whichever side already holds a live embedded-browser resource. - Raised PluginInstancesTotal's default from 8 to 12. The test region used for this investigation turned out to be a genuine outlier -- even at a raised cap, 24+ distinct media-bearing candidates were still found competing for slots simultaneously, roughly double any reasonable cap size. The fixes above make the rotation itself well- behaved; this just gives it more headroom on top, comfortably within SLCefProducer's own 32-slot ceiling. Net result, confirmed by hand across several region-transition runs: media that previously took 60-90+ seconds (or never appeared) now loads within a few seconds at worst, immediately in the common case. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…tely on teardown LLViewerMediaFocus::setFocusFace() only skipped its re-navigate when hasMedia() was true -- but hasMedia() is deliberately mMediaSource != NULL only (see its own comment), which is always false for embedded- browser media. So clicking off an already-loaded embedded-browser face and back onto it (the only path that re-enters setFocusFace() at all -- repeat clicks on an already-focused face are a no-op higher up) always re-navigated to the same URL from scratch, restarting it (losing scroll position, replaying audio/video, etc.) where the legacy plugin backend just resumed. Fixed by using the already- documented `hasMedia() || isUsingEmbeddedBrowser()` pattern. Also deduplicated: last night's cap-accounting/sort-stickiness work added a second, differently-named getter (getUseEmbeddedBrowser()) for the same mUseEmbeddedBrowser flag isUsingEmbeddedBrowser() already exposes. Switched those call sites onto the existing getter and removed the redundant one. Separately: destroyMediaSource() now mutes the tab immediately, before asking LLEmbeddedBrowser to actually tear it down. Closing the media debug floater (or anything else that unloads embedded-browser media) left audio audibly playing for a few seconds while the real teardown (closing the CEF browser, then the producer's own slot-release handling) ran in the background. Muting is one cheap opcode that takes effect immediately, independent of how long that actual teardown takes. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ture Covers why the new CEF approach was needed, the four-piece producer/ consumer design, repo locations, the codec-enabled CEF build, cookie isolation, page preloading, debugging, and remote DevTools. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Calls out the all-or-nothing volume control, the open LibVLC-vs-CEF codec coverage question, the current Windows-only scope, and that the legacy media plugin stays in place until this system is closer to feature-complete. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds kGoBack/kGoForward/kStopLoad commands and a kEventNavStateChanged event to the shm protocol, dispatches them through llcefbrowser's already-existing GoBack/GoForward/StopLoad, and threads canGoBack/ canGoForward state back through llembeddedbrowser into LLViewerMediaImpl::navigateBack/Forward/Stop and canNavigateBack/Forward. Also fixes LLMediaCtrl::navigateBack/Forward/Stop, which were bypassing LLViewerMediaImpl and calling into the legacy plugin pointer directly (always null for embedded-browser media, so those buttons silently did nothing). Fixes the same dead buttons on in-world prim media controls as a side effect, since those already called LLViewerMediaImpl directly. Confirmed working live: floater back/forward/stop and button enable/ disable state, plus prim media controls. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds a kSetRenderRate wire command that caps how often SLCefProducer calls SendExternalBeginFrame() for a given tab, and threads the same priority value LLViewerMedia::updateMedia() already computes through LLViewerMediaImpl::setPriority() into a target fps for non-UI, non- parcel prim media (NORMAL/HIGH unthrottled, LOW 15fps, SLIDESHOW 2fps, HIDDEN 1fps). This is the embedded-browser equivalent of the legacy plugin's own setPriority()/setLowPrioritySizeLimit() throttle, which embedded-browser media never had until now. UI and parcel media always send a target rate of 0 regardless of what priority they're assigned, deliberately not keyed off the raw priority value alone -- the shared priority computation can still push UI media down to HIDDEN/LOW when the Viewer window is minimized or loses focus, and priority mis-assignment was suspected as a possible cause of the legacy plugin's own reputation for feeling slow and clumsy. Also documents this in doc/Embedded_Browser.md. Confirmed building clean end-to-end (SLCefProducer, llembeddedbrowser, secondlife-bin). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds URL/slot/priority to kSetRenderRate's payload so both the Viewer log (tag PluginPriority) and SLCefProducer's own console (new purple log_priority(), gated on EmbeddedBrowserProducerConsole like every other console line, always written to slcefproducer_log.txt regardless) report every render-rate tier change for QA. Also fixes a real UX issue found during testing: LLViewerMediaFocus's auto-zoom moves the camera in on media focus and back out on defocus, so clicking off a media face demoted it from PRIORITY_HIGH straight to PRIORITY_LOW purely from the camera position change, not because the resident actually moved or stopped watching. Demotions (not promotions) now debounce for EMBEDDED_BROWSER_RENDER_RATE_DEMOTION_GRACE_PERIOD (2s) before applying, and EMBEDDED_BROWSER_FPS_LOW is raised 15->30 so that specific, very common transition is far less visible. Both are starting points for the product team to tune further. Documents all of this in doc/Embedded_Browser.md. Confirmed working live: purple producer-console lines and Viewer log lines appear on tier changes, and defocusing a media face no longer visibly drops its frame rate unless the demotion actually holds. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
kRequestSlot now carries the consumer's own current max-dimension ceiling alongside the existing UI/prim flag. The producer clamps it to its own absolute maximum (1920x1080, unchanged) and floor (960x540, its own default browser creation size) and sizes that slot's shared- memory segment to the result, instead of always reserving the full 1920x1080x3-buffers regardless of what the Viewer will ever request. Lowering EmbeddedBrowserMaxWidth/Height now genuinely reduces memory per tab, not just the resolution it's allowed to request -- previously it only did the latter. Each slot remembers its own negotiated ceiling so the kResize handler can clamp against it instead of the old global constant, which matters for correctness: a resize bigger than what a slot actually reserved would otherwise write past its end. Documents this, including the 960x540 floor's effect on very low settings, in doc/Embedded_Browser.md. Confirmed working live, including a floater resized past a lowered EmbeddedBrowserMaxWidth/Height stretching the smaller rendered texture to fill the larger rect, as expected. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Both repos are public now and their release CI (secondlife/action- autobuild-release) has produced real, downloadable packages: llcefbrowser v1.31.0 and llshmframe v1.19.0. Replaces the two file:/// local-override URLs with the real release URLs and their SHA1 hashes. Verified end-to-end: autobuild install fetches and unpacks both real packages cleanly, and SLCefProducer/llembeddedbrowser rebuild against them with no changes needed elsewhere. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
ENABLE_MEDIA_PLUGINS now defaults off (it already gated the whole media_plugins subdirectory, just needed flipping). SLPlugin.exe's own add_subdirectory() is now gated by the same flag: the only caller of LLPluginProcessParent::create() in the whole codebase is LLPluginClassMedia::init(), so with no media plugins to host it has nothing left to do. Source for both is untouched -- this is build/ deploy configuration only, easily reverted by flipping the flag back. Also stops copying, into the deployed package: media_plugin_cef/ libvlc/example (path_optional(), a no-op once they're not built), dullahan_host.exe (SLCefProducer.exe re-execs itself instead via ExecuteSubProcess(), so it never needed this) and the LibVLC runtime (libvlc.dll/libvlccore.dll/plugins/, 367 files). The latter two ship inside the CEF/VLC autobuild packages themselves regardless of ENABLE_MEDIA_PLUGINS, so path_optional() alone doesn't skip them -- had to comment out the copy calls outright, confirmed by testing. SLCefProducer.exe (and the CEF runtime files it needs, now the sole consumer of them) moves from the now sometimes-empty llplugin\ into its own SLCefProducer\ directory, matching the executable's own name. Confirmed via live build: media_plugin_cef/libvlc/example and SLPlugin.exe no longer build; llplugin\ no longer exists in the deployed package when media plugins are off; SLCefProducer\ contains only SLCefProducer.exe and its actual CEF runtime dependencies, no VLC or dullahan_host.exe. Deployed package footprint for this one directory alone: 526MB -> 390MB. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…libvlc header llappviewer.cpp unconditionally includes vlc/libvlc_version.h (guarded only by !LL_LINUX) to report the LibVLC version in the About-box info dump -- unrelated to whether media_plugin_libvlc itself gets built. That header's include path previously only existed as a side effect of media_plugin_libvlc's own CMakeLists.txt running (include(LibVLCPlugin)), which the last commit's ENABLE_MEDIA_PLUGINS=OFF default now skips, breaking the build with a missing-include error on a from-scratch configure. Fixed by including LibVLCPlugin and linking ll::libvlc directly for the Viewer binary target itself, independent of ENABLE_MEDIA_PLUGINS. Confirmed via a real rebuild from a fresh CMake configure. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…r\ move The media-plugin pruning work moved SLCefProducer.exe's deployed location from llplugin\ to its own SLCefProducer\ directory in viewer_manifest.py, but missed two runtime call sites that independently built the old path: LLDir_Win32::getSLCefProducerLauncher() still pointed at getLLPluginDir(), and LLEmbeddedBrowser::launchProducer() separately re-derived the same (now wrong) working directory instead of using the launcher path it already had. Result: SLCefProducer.exe failed to launch at all, so no media (web or otherwise) rendered. getSLCefProducerLauncher() now builds its own path directly. The launch working directory is now derived from that same path (getDirName()), not a second independent lookup -- so this can't drift out of sync again the way it just did. Confirmed fixed by the user on a real clean-build Viewer: embedded browser media loads normally again. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Resolved conflicts: - indra/llmediactrl.cpp: kept our null-safety fix for handleMediaEvent's self pointer (always null for embedded-browser media). - indra/llplugin/CMakeLists.txt: kept both additions (develop's precompiled-headers support, our ENABLE_MEDIA_PLUGINS gate around add_subdirectory(slplugin)). - indra/newview/CMakeLists.txt: kept both additions (our libvlc header fix, develop's DISABLE_WEBRTC conditional). - indra/newview/llappviewer.cpp: kept both watchdog-shutdown additions; for the About-box info dump, report both the embedded-browser (SHMFRAME_VERSION/EMBEDDED_LLCEFBROWSER_VERSION) and legacy Dullahan (LIBCEF_VERSION) version blocks, since dullahan_version.h's macros are available either way and this stays meaningful if ENABLE_MEDIA_PLUGINS is ever turned back on. - autobuild.xml (21 hunks): mechanical package-list staleness resolved by taking develop's side; kept our side on the dullahan/CEF version specifically (ours is CEF 150, develop's is still CEF 139 -- taking theirs would reintroduce the version mismatch this project already solved between the legacy plugin and llcefbrowser/SLCefProducer); the 6 self-build command hunks (devenv/MSBuild-style flags -> develop's modernized plain `cmake --build`) were resolved by replacing our entire windows/linux64 self-build sections with develop's verified real content rather than hand-patching, after finding stale unconflicted content the raw diff hunks alone would have missed. Verified: well-formed XML, and `autobuild install --list` parses it cleanly with llcefbrowser/llshmframe/dullahan all present. This is a local sync merge (develop into our branch), not a request to land embedded-browser in develop -- see PR secondlife#6195's own description for the actual intent.
- llmediactrl.cpp: mHidingInitialLoad no longer exists -- develop removed it (commit bd3a7c9, "Fix uninitialized member variables reported by ubsan": it was never initialized in the constructor on either side, and used nowhere else in the codebase). My earlier conflict resolution kept a reference to it without noticing the header declaration was gone. Removed the dead block, matching develop's own conclusion that this mechanism should go away. - llappviewer.cpp: mismatched #if/#endif. develop actually removed the !LL_LINUX guard entirely around both dullahan_version.h and vlc/libvlc_version.h (both now unconditional there), not just moved it -- my earlier resolution only captured part of that change and left a dangling #if with no matching #endif. Now matches develop's real structure exactly. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…acOS/Linux A macOS or Linux failure was cancelling the Windows job via GitHub's default fail-fast matrix behavior (seen firsthand: a macOS Tests failure on PR secondlife#6195 cancelled the Windows Viewer/Tests jobs before they ever ran). This project is Windows-only for now -- macOS/Linux results aren't a current priority, but they shouldn't be able to hide a real Windows result either way. Deliberate divergence from develop's own workflow file. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Indirect way to push the Embedded Viewer repo to a Viewer branch: Go via my own Linden repository and avoid the pre-commit branch protection rules.