diff --git a/third_party/blink/renderer/core/exported/web_view_test.cc b/third_party/blink/renderer/core/exported/web_view_test.cc index a59f682536d47..a9383da43b370 100644 --- a/third_party/blink/renderer/core/exported/web_view_test.cc +++ b/third_party/blink/renderer/core/exported/web_view_test.cc @@ -159,6 +159,7 @@ #include "third_party/blink/renderer/platform/testing/unit_test_helpers.h" #include "third_party/blink/renderer/platform/testing/url_loader_mock_factory.h" #include "third_party/blink/renderer/platform/testing/url_test_helpers.h" +#include "third_party/blink/renderer/platform/widget/widget_base.h" #include "third_party/blink/renderer/platform/wtf/casting.h" #include "third_party/blink/renderer/platform/wtf/functional.h" #include "third_party/skia/include/core/SkBitmap.h" @@ -6145,6 +6146,136 @@ TEST_F(WebViewTest, DetachPluginInLayout) { web_view_helper_.Reset(); // Remove dependency on locally scoped client. } +// Tests that WidgetBase::UpdateCompositionInfo handles destruction of the +// WidgetBase during the synchronous layout it triggers. CompositionRange() +// forces UpdateStyleAndLayout, whose PluginDisposeSuspendScope destructor can +// dispose plugins which are explicitly permitted to run script (see +// ScriptForbiddenScope::AllowUserAgentScript in +// WebPluginContainerImpl::Dispose). Such script may detach a local-root +// subframe, synchronously invoking WebFrameWidgetImpl::Close which resets +// widget_base_ while UpdateCompositionInfo is still on the stack. +TEST_F(WebViewTest, UpdateCompositionInfoWidgetDestroyedDuringLayout) { + // A plugin that runs a callback during Destroy(), simulating a plugin + // implementation that runs script on destruction. + class CallbackInDestroyPlugin : public FakeWebPlugin { + public: + explicit CallbackInDestroyPlugin(const WebPluginParams& params) + : FakeWebPlugin(params) {} + void Destroy() override { + if (destroy_callback_) { + std::move(destroy_callback_).Run(); + } + FakeWebPlugin::Destroy(); + } + void SetDestroyCallback(base::OnceClosure callback) { + destroy_callback_ = std::move(callback); + } + + private: + base::OnceClosure destroy_callback_; + }; + + class PluginCreatingWebFrameClient + : public frame_test_helpers::TestWebFrameClient { + public: + WebPlugin* CreatePlugin(const WebPluginParams& params) override { + last_plugin_ = new CallbackInDestroyPlugin(params); + return last_plugin_; + } + CallbackInDestroyPlugin* last_plugin() { return last_plugin_; } + void clear_last_plugin() { last_plugin_ = nullptr; } + + private: + CallbackInDestroyPlugin* last_plugin_ = nullptr; + }; + + // Set up: Remote main frame -> Local child frame (a local root with its own + // WidgetBase). This mirrors an OOPIF scenario where a subframe has its own + // widget that can be destroyed independently of the page. + PluginCreatingWebFrameClient frame_client; + frame_test_helpers::WebViewHelper helper; + helper.InitializeRemote(); + WebLocalFrameImpl* local_frame = + helper.CreateLocalChild(*helper.RemoteMainFrame(), "child", + WebFrameOwnerProperties(), nullptr, &frame_client); + + WebURL base_url = url_test_helpers::ToKURL("http://example.com/"); + frame_test_helpers::LoadHTMLString( + local_frame, + "
" + "" + "" + "", + base_url); + local_frame->FrameWidget()->Resize(gfx::Size(300, 300)); + local_frame->GetFrame()->View()->UpdateAllLifecyclePhasesForTest(); + + Document* document = local_frame->GetFrame()->GetDocument(); + Element* input = document->getElementById(AtomicString("input")); + auto* plugin_element = + To