diff --git a/third_party/blink/renderer/core/editing/frame_selection.cc b/third_party/blink/renderer/core/editing/frame_selection.cc index a322762432029..4b0dbb65c084a 100644 --- a/third_party/blink/renderer/core/editing/frame_selection.cc +++ b/third_party/blink/renderer/core/editing/frame_selection.cc @@ -154,6 +154,9 @@ Element* FrameSelection::RootEditableElementOrDocumentElement() const { // TODO(editing-dev): The use of UpdateStyleAndLayout // needs to be audited. See http://crbug.com/590369 for more details. GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kSelection); + if (!IsAvailable()) { + return nullptr; + } Element* selection_root = ComputeVisibleSelectionInDomTree().RootEditableElement(); 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 f788fedde71ad..6e279fac770ff 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" @@ -6194,6 +6195,96 @@ TEST_F(WebViewTest, DetachPluginInLayout) { web_view_helper_.Reset(); // Remove dependency on locally scoped client. } +// Tests that requesting composition info on a child local root whose plugin +// detaches the local root during the layout it forces does not crash. +TEST_F(WebViewTest, RequestCompositionUpdatesPluginDetachesLocalRoot) { + class DetachInDestroyPlugin : public FakeWebPlugin { + public: + DetachInDestroyPlugin(WebFrame** frame_to_detach, + const WebPluginParams& params) + : FakeWebPlugin(params), frame_to_detach_(frame_to_detach) {} + + // WebPlugin overrides: + void Destroy() override { + if (*frame_to_detach_) { + (*frame_to_detach_)->Detach(); + } + // Deletes this. + FakeWebPlugin::Destroy(); + } + + private: + WebFrame** frame_to_detach_; // Unowned. + }; + + class PluginCreatingWebFrameClient + : public frame_test_helpers::TestWebFrameClient { + public: + // WebLocalFrameClient overrides: + WebPlugin* CreatePlugin(const WebPluginParams& params) override { + return new DetachInDestroyPlugin(&frame_to_detach_, params); + } + + void SetFrameToDetach(WebFrame* frame) { frame_to_detach_ = frame; } + + private: + WebFrame* frame_to_detach_ = nullptr; + }; + + PluginCreatingWebFrameClient frame_client; + web_view_helper_.InitializeRemote(); + WebLocalFrameImpl* local_frame = web_view_helper_.CreateLocalChild( + *web_view_helper_.RemoteMainFrame(), WebString(), + WebFrameOwnerProperties(), nullptr, &frame_client); + WebFrameWidgetImpl* frame_widget = local_frame->FrameWidgetImpl(); + frame_widget->Resize(gfx::Size(200, 200)); + + WebURL base_url = url_test_helpers::ToKURL("https://example.com/"); + frame_test_helpers::LoadHTMLString( + local_frame, + "" + "
hello
" + "" + "", + base_url); + frame_widget->UpdateAllLifecyclePhases(DocumentUpdateReason::kTest); + + // Verify the plugin is loaded. + LocalFrame* frame = local_frame->GetFrame(); + auto* plugin_element = + To(frame->GetDocument()->body()->lastChild()); + ASSERT_TRUE(plugin_element->OwnedPlugin()); + + // Focus the editable element and start a composition. + frame->GetPage()->GetFocusController().SetFocusedFrame(frame); + frame->GetDocument()->getElementById(AtomicString("e"))->Focus(); + WebInputMethodController* controller = + frame_widget->GetActiveWebInputMethodController(); + ASSERT_TRUE(controller); + std::vector ime_text_spans; + controller->SetComposition(WebString::FromUtf8("abc"), ime_text_spans, + WebRange(), 3, 3); + ASSERT_FALSE(controller->CompositionRange().IsNull()); + + // Hide the plugin so that it is disposed during the next forced layout, and + // arrange for the local root to be detached at that time. + plugin_element->style()->setCSSText(frame->DomWindow(), "display: none", + ASSERT_NO_EXCEPTION); + ASSERT_TRUE(plugin_element->OwnedPlugin()); + frame_client.SetFrameToDetach(local_frame); + + WidgetBase* widget_base = frame_widget->widget_base_for_testing(); + base::WeakPtr weak_widget_base = widget_base->GetWeakPtr(); + // This forces a layout to compute the composition range. The plugin is + // disposed inside that layout and detaches the local root, which destroys + // |widget_base|. This should not crash. + widget_base->RequestCompositionUpdates(/*immediate_request=*/true, + /*monitor_updates=*/false); + EXPECT_FALSE(weak_widget_base); + + web_view_helper_.Reset(); // Remove dependency on locally scoped client. +} + // Check that first input delay is correctly reported to the document. TEST_F(WebViewTest, FirstInputDelayReported) { WebViewImpl* web_view = web_view_helper_.Initialize(); diff --git a/third_party/blink/renderer/platform/widget/widget_base.cc b/third_party/blink/renderer/platform/widget/widget_base.cc index fd4d3bb58621b..017e51006f7dc 100644 --- a/third_party/blink/renderer/platform/widget/widget_base.cc +++ b/third_party/blink/renderer/platform/widget/widget_base.cc @@ -1445,12 +1445,25 @@ void WidgetBase::UpdateCompositionInfo(bool immediate_request) { gfx::Range range; Vector character_bounds; - if (GetTextInputType() == ui::TextInputType::TEXT_INPUT_TYPE_NONE) { + // Determining the text input type and composition range may update style and + // layout, which can detach the local root and destroy |this|. + auto weak_this = weak_ptr_factory_.GetWeakPtr(); + ui::TextInputType text_input_type = GetTextInputType(); + if (!weak_this) { + return; + } + if (text_input_type == ui::TextInputType::TEXT_INPUT_TYPE_NONE) { // Composition information is only available on editable node. range = gfx::Range::InvalidRange(); } else { GetCompositionRange(&range); + if (!weak_this) { + return; + } GetCompositionCharacterBounds(&character_bounds); + if (!weak_this) { + return; + } } if (!immediate_request &&