Extension Popup Sized Incorrectly After Content Changes

#40391360 Filed 2014-10-28 P3 / New

Bug

Extension popup opens at 25x25 (minimum size) instead of the correct content size, then flashes to the right dimensions. Also fails to resize when content changes dynamically after load. Open since 2014.

Without fix (broken)

x

Opens at 25x25, flashes

With fix (correct)

380 x auto
Content fits

Opens at correct size

Demo Extension

⬇ Download Demo Extension (.zip)

To test: Unzip, go to chrome://extensions, enable Developer Mode, click "Load unpacked", select the folder, then click the extension icon in the toolbar.

What the demo tests

Test 1 - Initial Size: Captures innerWidth/Height at script parse time. On buggy builds this is 25x25 instead of 380xN. Shown as red FAIL.

Test 2 - Dynamic Resize: After 500ms the popup reveals a hidden card (simulating API data loading). Measures whether the popup height grew to fit the new content. On buggy builds the popup stays clipped.

Root Cause

Async auto-resize vs. sync load events

The renderer reports content size changes via the compositor Mojo channel (ResizeDueToAutoResize). This is a different channel from the FrameHost notifications (DocumentOnLoadCompleted, DidStopLoading). The auto-resize IPC may arrive before or after the load events -- non-deterministic ordering.

Widget resize is async (PostTask)

When the preferred size changes, Widget::OnRootViewLayoutInvalidated posts ResizeToDelegateDesiredBounds as an async task. If this task runs before ShowBubble, the widget appears at stale minimum size. If it runs between show and the test check, you see a flash.

Fix

Two changes in extension_popup.cc:

// 1. Override ChildPreferredSizeChanged -- when the renderer
//    reports a new content size via auto-resize, resize the
//    bubble widget synchronously instead of waiting for the
//    async PostTask(ResizeToDelegateDesiredBounds).
void ExtensionPopup::ChildPreferredSizeChanged(View* child) {
  if (GetWidget() && GetWidget()->IsVisible())
    SizeToContents();
}

// 2. In ShowBubble(), after Show(), apply the preferred size
//    that may already have been set before the widget was
//    shown (e.g. single-process mode, fast compositor).
GetWidget()->Show();
SizeToContents();  // <-- new

Tests

PopupHasCorrectInitialBounds

Opens a popup with a 300x200 div. Verifies bounds are correct after load. Without the fix this shows 39x39 ~80% of the time.

PopupResizesAfterScriptTriggeredContentChange

Opens a popup with a 50x50 div, then uses ExecJs to resize to 300x200. Verifies the widget grows to match.

# Build & run
autoninja -C out/Default interactive_ui_tests
./out/Default/interactive_ui_tests \
  --gtest_filter="ExtensionPopupInteractiveUiTest.Popup*"