Skip to content

Commit

Permalink
Scope LanguageDetectionModel to a ChromeBrowserState
Browse files Browse the repository at this point in the history
TranslateModelService downloads the model and makes it
available as a file, but does not load it in a
LanguageDetectionModel (and in TFLite).
On other platforms, this done in the
ContentTranslateDriver in a content process.

But on iOS, tabs are in the same process, so it is not
needed to load the model multiple times and multiple
tabs trying to load the model file in the
LanguageDetectionModel can lead to conflicts.
Instead, create a new service that will handle the loading
once and provide the LanguageDetectionModel to every tabs.

Bug: 1285224
Change-Id: Ie4f936cedc53634ac7e8e88c1c5213f12068ba87
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3592978
Reviewed-by: Sylvain Defresne <sdefresne@chromium.org>
Reviewed-by: Megan Jablonski <megjablon@chromium.org>
Reviewed-by: Michael Crouse <mcrouse@chromium.org>
Reviewed-by: Colin Blundell <blundell@chromium.org>
Commit-Queue: Olivier Robin <olivierrobin@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1002571}
  • Loading branch information
robinolivier authored and Chromium LUCI CQ committed May 12, 2022
1 parent 0b4c87a commit 1d4b895
Show file tree
Hide file tree
Showing 11 changed files with 279 additions and 45 deletions.
2 changes: 2 additions & 0 deletions components/translate/core/browser/translate_model_service.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ namespace translate {
// Service that manages models required to support translation in the browser.
// Currently, the service should only be used in the browser as it relies on
// the Optimization Guide.
// TODO(crbug/1324530): TranslateModelService should own
// LanguageDetectionModel.
class TranslateModelService
: public KeyedService,
public optimization_guide::OptimizationTargetModelObserver {
Expand Down
3 changes: 3 additions & 0 deletions components/translate/ios/browser/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ source_set("browser") {
"js_translate_manager.mm",
"language_detection_controller.h",
"language_detection_controller.mm",
"language_detection_model_service.h",
"language_detection_model_service.mm",
"string_clipping_util.cc",
"string_clipping_util.h",
"translate_controller.h",
Expand All @@ -22,6 +24,7 @@ source_set("browser") {
deps = [
":injected_js",
"//base",
"//components/keyed_service/core",
"//components/language/ios/browser",
"//components/prefs",
"//components/translate/core/browser",
Expand Down
1 change: 1 addition & 0 deletions components/translate/ios/browser/DEPS
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
include_rules = [
"+components/keyed_service/core/keyed_service.h",
"+components/language/core/browser",
"+components/ukm/ios",
]
11 changes: 6 additions & 5 deletions components/translate/ios/browser/ios_translate_driver.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ class WebState;

namespace translate {

class LanguageDetectionModelService;
class TranslateManager;
class TranslateModelService;

// Content implementation of TranslateDriver.
class IOSTranslateDriver
Expand All @@ -32,9 +32,10 @@ class IOSTranslateDriver
public web::WebStateObserver,
public language::IOSLanguageDetectionTabHelper::Observer {
public:
IOSTranslateDriver(web::WebState* web_state,
TranslateManager* translate_manager,
TranslateModelService* translate_model_service);
IOSTranslateDriver(
web::WebState* web_state,
TranslateManager* translate_manager,
LanguageDetectionModelService* language_detection_model_service);

IOSTranslateDriver(const IOSTranslateDriver&) = delete;
IOSTranslateDriver& operator=(const IOSTranslateDriver&) = delete;
Expand Down Expand Up @@ -111,7 +112,7 @@ class IOSTranslateDriver
std::unique_ptr<TranslateController> translate_controller_;
std::unique_ptr<LanguageDetectionController> language_detection_controller_;

TranslateModelService* translate_model_service_ = nullptr;
LanguageDetectionModelService* language_detection_model_service_ = nullptr;

// An ever-increasing sequence number of the current page, used to match up
// translation requests with responses.
Expand Down
41 changes: 6 additions & 35 deletions components/translate/ios/browser/ios_translate_driver.mm
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include "components/translate/core/language_detection/language_detection_model.h"
#import "components/translate/ios/browser/js_translate_manager.h"
#import "components/translate/ios/browser/language_detection_controller.h"
#include "components/translate/ios/browser/language_detection_model_service.h"
#import "components/translate/ios/browser/translate_controller.h"
#include "components/ukm/ios/ukm_url_recorder.h"
#include "ios/web/public/browser_state.h"
Expand All @@ -43,42 +44,25 @@
// Language name passed to the Translate element for it to detect the language.
const char kAutoDetectionLanguage[] = "auto";

LanguageDetectionModel* GetLanguageDetectionModel() {
static base::NoDestructor<LanguageDetectionModel> instance;
return instance.get();
}

void SetLanguageDetectionModelModelFile(base::File model_file) {
LanguageDetectionModel* language_detection_model =
GetLanguageDetectionModel();
language_detection_model->UpdateWithFile(std::move(model_file));
}

} // namespace

IOSTranslateDriver::IOSTranslateDriver(
web::WebState* web_state,
TranslateManager* translate_manager,
TranslateModelService* translate_model_service)
LanguageDetectionModelService* language_detection_model_service)
: web_state_(web_state),
translate_manager_(translate_manager->GetWeakPtr()),
translate_model_service_(translate_model_service),
language_detection_model_service_(language_detection_model_service),
page_seq_no_(0),
pending_page_seq_no_(0) {
DCHECK(translate_manager_);
DCHECK(web_state_);

web_state_->AddObserver(this);
LanguageDetectionModel* language_detection_model = nullptr;
if (translate_model_service_ && IsTFLiteLanguageDetectionEnabled()) {
language_detection_model = GetLanguageDetectionModel();
if (!language_detection_model->IsAvailable()) {
translate_model_service_->NotifyOnModelFileAvailable(base::BindOnce(
&IOSTranslateDriver::OnLanguageModelFileAvailabilityChanged,
weak_ptr_factory_.GetWeakPtr()));
} else {
OnLanguageModelFileAvailabilityChanged(true);
}
if (language_detection_model_service_ && IsTFLiteLanguageDetectionEnabled()) {
language_detection_model =
language_detection_model_service_->GetLanguageDetectionModel();
}

language::IOSLanguageDetectionTabHelper* language_detection_tab_helper =
Expand Down Expand Up @@ -122,19 +106,6 @@ void SetLanguageDetectionModelModelFile(base::File model_file) {
observer.OnLanguageDetermined(details);
}

void IOSTranslateDriver::OnLanguageModelFileAvailabilityChanged(
bool available) {
if (available) {
DCHECK(translate_model_service_);
base::File model_file =
translate_model_service_->GetLanguageDetectionModelFile();
base::ThreadPool::PostTask(
FROM_HERE, {base::TaskPriority::USER_VISIBLE, base::MayBlock()},
base::BindOnce(SetLanguageDetectionModelModelFile,
std::move(model_file)));
}
}

void IOSTranslateDriver::IOSLanguageDetectionTabHelperWasDestroyed(
language::IOSLanguageDetectionTabHelper* tab_helper) {
// No-op. We stop observing the IOSLanguageDetectionTabHelper in
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Copyright 2022 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef COMPONENTS_TRANSLATE_IOS_BROWSER_LANGUAGE_DETECTION_MODEL_SERVICE_H_
#define COMPONENTS_TRANSLATE_IOS_BROWSER_LANGUAGE_DETECTION_MODEL_SERVICE_H_

#include "base/files/file.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/sequence_checker.h"
#include "components/keyed_service/core/keyed_service.h"
#include "components/translate/core/browser/translate_model_service.h"

namespace translate {

class LanguageDetectionModelContainer;
class LanguageDetectionModel;

// A service that contains the LanguageDetectionModel and handles its loading.
// This is a workaround for crbug/1324530 on iOS where it is mandatory to have
// LanguageDetectionModel scoped by BrowserState.
// TODO(crbug.com/1324530): remove this class once TranslateModelService does
// this.
class LanguageDetectionModelService : public KeyedService {
public:
LanguageDetectionModelService(
TranslateModelService* opt_guide,
const scoped_refptr<base::SequencedTaskRunner>& background_task_runner);
~LanguageDetectionModelService() override;

// Get for the actual TFLite language detection model.
LanguageDetectionModel* GetLanguageDetectionModel();

// Utility function to check if the model is already loaded.
// |GetLanguageDetectionModel| can be used even if this return false.
bool IsModelAvailable();

private:
// Notifies |this| that the translate model service is available for model
// requests or is invalidating existing requests specified by |is_available|.
void OnLanguageModelFileAvailabilityChanged(bool available);
// The TranslageModelService that will handle the downloading and provide
// the file containing the model.
raw_ptr<TranslateModelService> translate_model_service_;
scoped_refptr<base::SequencedTaskRunner> background_task_runner_;
scoped_refptr<LanguageDetectionModelContainer> language_detection_model_;
base::WeakPtrFactory<LanguageDetectionModelService> weak_ptr_factory_{this};
};

} // namespace translate

#endif // COMPONENTS_TRANSLATE_IOS_BROWSER_LANGUAGE_DETECTION_MODEL_SERVICE_H_
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// Copyright 2022 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/translate/ios/browser/language_detection_model_service.h"

#include <memory>

#include "base/task/sequenced_task_runner.h"
#include "components/translate/core/language_detection/language_detection_model.h"

#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif

namespace translate {

class LanguageDetectionModelContainer
: public base::RefCountedThreadSafe<LanguageDetectionModelContainer>,
public LanguageDetectionModel {
public:
LanguageDetectionModelContainer() {}

private:
// Allow destruction by RefCounted<>.
friend class RefCountedThreadSafe<LanguageDetectionModelContainer>;
// Destructor must be private/protected.
~LanguageDetectionModelContainer() = default;
};

namespace {
void SetLanguageDetectionModelModelFile(
scoped_refptr<translate::LanguageDetectionModelContainer>
language_detection_model,
base::File model_file) {
language_detection_model->UpdateWithFile(std::move(model_file));
}
} // namespace

LanguageDetectionModelService::LanguageDetectionModelService(
TranslateModelService* translate_model_service,
const scoped_refptr<base::SequencedTaskRunner>& background_task_runner)
: translate_model_service_(translate_model_service),
background_task_runner_(background_task_runner),
language_detection_model_(
base::MakeRefCounted<LanguageDetectionModelContainer>()) {
if (translate_model_service_) {
if (!language_detection_model_->IsAvailable()) {
translate_model_service_->NotifyOnModelFileAvailable(
base::BindOnce(&LanguageDetectionModelService::
OnLanguageModelFileAvailabilityChanged,
weak_ptr_factory_.GetWeakPtr()));
} else {
OnLanguageModelFileAvailabilityChanged(true);
}
}
}

LanguageDetectionModelService::~LanguageDetectionModelService() {}

LanguageDetectionModel*
LanguageDetectionModelService::GetLanguageDetectionModel() {
return language_detection_model_.get();
}

bool LanguageDetectionModelService::IsModelAvailable() {
return language_detection_model_->IsAvailable();
}

void LanguageDetectionModelService::OnLanguageModelFileAvailabilityChanged(
bool available) {
if (available) {
DCHECK(translate_model_service_);
base::File model_file =
translate_model_service_->GetLanguageDetectionModelFile();
background_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&SetLanguageDetectionModelModelFile,
language_detection_model_, std::move(model_file)));
}
}

} // namespace translate
3 changes: 3 additions & 0 deletions ios/chrome/browser/translate/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ source_set("translate") {
sources = [
"chrome_ios_translate_client.h",
"chrome_ios_translate_client.mm",
"language_detection_model_service_factory.h",
"language_detection_model_service_factory.mm",
"language_selection_context.h",
"language_selection_context.mm",
"translate_accept_languages_factory.cc",
Expand Down Expand Up @@ -49,6 +51,7 @@ source_set("translate") {
"//components/translate/core/browser",
"//components/translate/core/browser:translate_model_service",
"//components/translate/core/common",
"//components/translate/core/language_detection",
"//components/translate/ios/browser",
"//components/web_resource",
"//ios/chrome/app/strings",
Expand Down
12 changes: 7 additions & 5 deletions ios/chrome/browser/translate/chrome_ios_translate_client.mm
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include "ios/chrome/browser/infobars/infobar_ios.h"
#include "ios/chrome/browser/infobars/infobar_manager_impl.h"
#include "ios/chrome/browser/language/language_model_manager_factory.h"
#include "ios/chrome/browser/translate/language_detection_model_service_factory.h"
#include "ios/chrome/browser/translate/translate_accept_languages_factory.h"
#include "ios/chrome/browser/translate/translate_model_service_factory.h"
#include "ios/chrome/browser/translate/translate_ranker_factory.h"
Expand Down Expand Up @@ -60,11 +61,12 @@
ChromeBrowserState::FromBrowserState(
web_state->GetBrowserState()))
->GetPrimaryModel())),
translate_driver_(web_state,
translate_manager_.get(),
TranslateModelServiceFactory::GetForBrowserState(
ChromeBrowserState::FromBrowserState(
web_state->GetBrowserState()))) {
translate_driver_(
web_state,
translate_manager_.get(),
LanguageDetectionModelServiceFactory::GetForBrowserState(
ChromeBrowserState::FromBrowserState(
web_state->GetBrowserState()))) {
web_state_->AddObserver(this);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright 2022 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef IOS_CHROME_BROWSER_TRANSLATE_LANGUAGE_DETECTION_MODEL_SERVICE_FACTORY_H_
#define IOS_CHROME_BROWSER_TRANSLATE_LANGUAGE_DETECTION_MODEL_SERVICE_FACTORY_H_

#include <memory>

#include "base/no_destructor.h"
#include "components/keyed_service/ios/browser_state_keyed_service_factory.h"

class ChromeBrowserState;

namespace translate {
class LanguageDetectionModelService;
} // namespace translate

// This is a workaround for crbug/1324530 on iOS where it is mandatory to have
// LanguageDetectionModel scoped by BrowserState.
// TODO(crbug.com/1324530): remove this class once TranslateModelService does
// this.
class LanguageDetectionModelServiceFactory
: public BrowserStateKeyedServiceFactory {
public:
static translate::LanguageDetectionModelService* GetForBrowserState(
ChromeBrowserState* browser_state);
static LanguageDetectionModelServiceFactory* GetInstance();

LanguageDetectionModelServiceFactory(
const LanguageDetectionModelServiceFactory&) = delete;
LanguageDetectionModelServiceFactory& operator=(
const LanguageDetectionModelServiceFactory&) = delete;

private:
friend class base::NoDestructor<LanguageDetectionModelServiceFactory>;

LanguageDetectionModelServiceFactory();
~LanguageDetectionModelServiceFactory() override;

// BrowserStateKeyedServiceFactory implementation.
std::unique_ptr<KeyedService> BuildServiceInstanceFor(
web::BrowserState* context) const override;
web::BrowserState* GetBrowserStateToUse(
web::BrowserState* context) const override;
};

#endif // IOS_CHROME_BROWSER_TRANSLATE_LANGUAGE_DETECTION_MODEL_SERVICE_FACTORY_H_

0 comments on commit 1d4b895

Please sign in to comment.