https://bugs.documentfoundation.org/show_bug.cgi?id=172207

            Bug ID: 172207
           Summary: Addon toolbars: status listeners not re-registered
                    after ToolBarManager dispose
           Product: LibreOffice
           Version: 26.2.2.2 release
          Hardware: All
                OS: Linux (All)
            Status: UNCONFIRMED
          Severity: normal
          Priority: medium
         Component: Calc
          Assignee: [email protected]
          Reporter: [email protected]

When LibreOffice disposes the ToolBarManager of an `addon_*`-prefixed
toolbar (registered by an extension via Addons.xcu), the toolbar item
controllers are destroyed and `removeStatusListener` is called on the
extension's dispatch object. LibreOffice **does not re-create the
controllers** afterwards: no `OnViewCreated` follows the `OnUnload`
event, and there is no `addStatusListener` burst to restore the
controller bindings.

The toolbar remains visible in the frame but is "frozen": its buttons
keep the enabled/disabled state they had at the moment of disposal and
no longer react to subsequent state updates. The extension has no LO
API path to recover, because `XLayoutManager.requestElement` is a no-op
for addon-toolbars whose cached `m_xUIElement` is non-null.

This affects any extension that registers an `addon_*` toolbar plus an
own XStatusListener-based command state mechanism. Reproduced with
Petanque-Turnier-Manager
(https://github.com/michaelmassee/Petanque-Turnier-Manager) on
LibreOffice 26.2.2.2 / Ubuntu 24.04 LTS / Wayland.

STEPS TO REPRODUCE:
1. Install an OXT extension that
   - registers an addon-toolbar via Addons.xcu
   - implements its own XDispatchProvider/ProtocolHandler
   - registers dynamic XStatusListener-based button state
2. Open TWO empty Calc documents (so the addon toolbar lives in both
   frames).
3. From doc2, trigger an extension action that runs a sheet-modifying
   background job and performs an XStorable.storeToURL (autoSave path).
4. Wait ~15-20 s after the job completes.

ACTUAL RESULT:
Without any user interaction, LO fires GlobalEvents in this order:
  - OnPrepareViewClosing
  - OnPrepareUnload
  - 24+ removeStatusListener calls (all addon-toolbar URLs of doc1's
    ProtocolHandler instance)
  - OnViewClosed
  - OnUnload (doc1 view)
  - 24+ removeStatusListener calls (doc2's handler)
  - OnUnload (doc2 view)

No corresponding OnViewCreated. No new addStatusListener calls.

Both Calc documents remain visible and editable; sheets are intact;
menu and sidebar still work. The addon toolbar in BOTH documents is
visible but frozen on its last status snapshot.

EXPECTED RESULT:
Either (a) `XLayoutManager.requestElement(addon_url)` triggered by the
extension after this state should rebuild the toolbar controllers and
result in fresh `addStatusListener` calls, OR (b) LibreOffice should
clear `m_xUIElement` for the addon-toolbar in `ToolbarLayoutManager`
when its underlying `ToolBarManager` is disposed, so that the next
`requestToolbar` enters the `createToolbar` branch.

ROOT CAUSE (source code):

`framework/source/layoutmanager/toolbarlayoutmanager.cxx:570`
(ToolbarLayoutManager::destroyToolbar):

    bool bMustBeDestroyed(
        !o3tl::starts_with(rResourceURL,
                           u"private:resource/toolbar/addon_") );
    if (bMustBeDestroyed)
        elem.m_xUIElement.clear();     // standard toolbars: real destroy
    else
        elem.m_bVisible = false;       // addon toolbars: only hide

`framework/source/layoutmanager/toolbarlayoutmanager.cxx:404`
(ToolbarLayoutManager::requestToolbar):

    UIElement aRequestedToolbar = impl_findToolbar(rResourceURL);
    if (aRequestedToolbar.m_aName != rResourceURL)
        bMustCallCreate = true;
    xUIElement = aRequestedToolbar.m_xUIElement;
    if (!xUIElement.is())
        bMustCallCreate = true;
    if (bCreateOrShowToolbar)
        bNotify = bMustCallCreate ? createToolbar(...) : showToolbar(...);

Effect: for addon_* toolbars whose ToolBarManager was disposed
(`framework/source/uielement/toolbarmanager.cxx:771`,
ToolBarManager::disposing → RemoveControllers → xComponent->dispose →
controllers call removeStatusListener), the LayoutManager cache still
holds the m_xUIElement reference pointing at a disposed component.
The next `requestElement` sees `xUIElement.is() == true` and takes the
`showToolbar` branch, never rebuilding controllers.

SUGGESTED FIX:
In `ToolBarManager::disposing(EventObject)`, notify the parent
`ToolbarLayoutManager` to clear `m_xUIElement` for the resource URL.
The next `requestToolbar` would then take the `createToolbar` branch.

Alternative: in `ToolbarLayoutManager::requestToolbar`, check whether
the cached `xUIElement` points to a disposed component (via
XComponent.isDisposed or by catching DisposedException on a probe
query) and force the rebuild branch in that case.

EXTENSION WORKAROUND ATTEMPTED (failed):
The Petanque-Turnier-Manager extension detected the empty status
listener map on `onFocus` and tried `hideElement` + `showElement` +
`requestElement` for each addon URL. None of these recover the
toolbar — `requestElement` returns early because cached `xUIElement`
is non-null, and `hideElement`/`showElement` toggles only visibility.

The only known user-side recovery: close and reopen the affected
document.

-- 
You are receiving this mail because:
You are the assignee for the bug.

Reply via email to