diff --git a/components/local_ai/core/local_ai.mojom b/components/local_ai/core/local_ai.mojom index 155472cb4fb..f08f495b19c 100644 --- a/components/local_ai/core/local_ai.mojom +++ b/components/local_ai/core/local_ai.mojom @@ -33,4 +33,8 @@ interface LocalAIService { // Creates the background WebContents if needed. If the factory is // not yet registered, the callback is deferred until it is. GetPassageEmbedder() => (pending_remote? embedder); + + // Called by the renderer when all PassageEmbedder receivers have + // disconnected. The browser may close the BackgroundWebContents. + NotifyPassageEmbedderIdle(); }; diff --git a/components/local_ai/core/local_ai_service.cc b/components/local_ai/core/local_ai_service.cc index 0fce33a2632..32dbe6fc117 100644 --- a/components/local_ai/core/local_ai_service.cc +++ b/components/local_ai/core/local_ai_service.cc @@ -56,19 +56,21 @@ void LocalAIService::GetPassageEmbedder(GetPassageEmbedderCallback callback) { } } +void LocalAIService::NotifyPassageEmbedderIdle() { + DVLOG(3) << "LocalAIService: PassageEmbedder idle"; + CloseBackgroundContents(); +} + void LocalAIService::OnBackgroundContentsDestroyed( BackgroundWebContents::DestroyReason reason) { DVLOG(1) << "LocalAIService: Background contents destroyed"; - factory_.reset(); - CancelPendingCallbacks(); CloseBackgroundContents(); } void LocalAIService::Shutdown() { DVLOG(3) << "LocalAIService: Shutting down"; receivers_.Clear(); - factory_.reset(); - CancelPendingCallbacks(); + weak_ptr_factory_.InvalidateWeakPtrs(); CloseBackgroundContents(); } @@ -83,6 +85,8 @@ void LocalAIService::MaybeCreateBackgroundContents() { void LocalAIService::CloseBackgroundContents() { DVLOG(3) << "LocalAIService: Closing background contents to free memory"; + factory_.reset(); + CancelPendingCallbacks(); background_web_contents_.reset(); } diff --git a/components/local_ai/core/local_ai_service.h b/components/local_ai/core/local_ai_service.h index a8cbb974e49..5edac104fdf 100644 --- a/components/local_ai/core/local_ai_service.h +++ b/components/local_ai/core/local_ai_service.h @@ -52,6 +52,7 @@ class LocalAIService : public KeyedService, void RegisterPassageEmbedderFactory( mojo::PendingRemote factory) override; void GetPassageEmbedder(GetPassageEmbedderCallback callback) override; + void NotifyPassageEmbedderIdle() override; private: // KeyedService: diff --git a/components/local_ai/core/local_ai_service_unittest.cc b/components/local_ai/core/local_ai_service_unittest.cc index 8373466111e..5a6bc1cebdb 100644 --- a/components/local_ai/core/local_ai_service_unittest.cc +++ b/components/local_ai/core/local_ai_service_unittest.cc @@ -267,6 +267,24 @@ TEST_F(LocalAIServiceTest, DoubleShutdownIsIdempotent) { keyed_service->Shutdown(); } +TEST_F(LocalAIServiceTest, NotifyPassageEmbedderIdleClosesBackgroundContents) { + // Create background contents and register factory. + base::test::TestFuture> future; + service_->GetPassageEmbedder(future.GetCallback()); + RegisterFactory(); + ASSERT_TRUE(future.Get().is_valid()); + ASSERT_TRUE(last_created_web_contents_); + + // Worker reports idle — should close background contents. + service_->NotifyPassageEmbedderIdle(); + EXPECT_FALSE(last_created_web_contents_); + + // Getting a new embedder should recreate background contents. + base::test::TestFuture> future2; + service_->GetPassageEmbedder(future2.GetCallback()); + EXPECT_TRUE(last_created_web_contents_); +} + TEST_F(LocalAIServiceTest, FactoryDisconnectCancelsPendingCallbacks) { // Queue a callback, register factory, then disconnect. base::test::TestFuture>