Using delegates to Resolve Dependency Issues

Chromium Repo Dependency Checks

The Chromium repository has strict restrictions on which modules are able to depend on the top level //ui, //chrome, or //content directories.

Directly from the //chromeos README:

These limitations are enforced using "-chrome" in the DEPS file and apply to the //chromeos, //ash/webui, and other low-level directories in the repository.

When these rules are violated (by including a file from //chrome/…), attempts to upload the offending CL will display the following message:

Running Python 3 presubmit upload checks ...
** Presubmit ERRORS: 1 **
You added one or more #includes that violate checkdeps rules.
  ash/webui/file_with_violation.cc
      Illegal include: "chrome/browser/utils.h"
    Because of "-chrome" from ash/webui's include_rules.

Presubmit checks took 5.4s to calculate.
There were Python 3 presubmit errors.

The diagram below gives a rough visualization of the separation of components in ChromeOS.

ChromeOS Dependency Chart ChromeOS architecture pre-LaCrOs

Delegate pattern to use Chrome from Ash

There will be numerous occasions where services in //ash/webui will need access to functions and data only available in //chrome (to access the logged-in user’s Profile for example).

To avoid repo dependency violations, in these situations we can utilize the delegate pattern to get the data/invoke the actions needed. The delegate pattern (summary) in the diagram below shows a simplified version of a function in //ash/webui using a delegate to invoke a function in //chrome.

Sample delegate pattern diagram Sample delegate pattern diagram

Creating a delegate

The below steps will use the existing Scan App implementation in //ash/webui to demonstrate examples of the delegate pattern.

Define the functions in the header

Here the functions are defined which will be invoked by our //ash/webui service. This header file will be created in //ash/webui so the service’s .cc file will be allowed to call/depend on it.

class ServiceDelegate {
 public:
  virtual ~ServiceDelegate() = default;

  virtual std::string GetStringFromChromeBrowser() = 0;
};

Scan app example

Create the implementation class

Since functions in //chrome can’t be called from //ash/webui, the functions defined in the delegate header need to be implemented by a file located in //chrome.

.h file

class ChromeServiceDelegate : public ServiceDelegate {
 public:
  ChromeServiceDelegate() override;

  std::string GetStringFromChromeBrowser() override;
};

Scan app example

.cc file

ChromeServiceDelegate::ChromeServiceDelegate() = default;

std::string ChromeServiceDelegate::GetStringFromChromeBrowser() {
  return chrome::ChromeBrowserFunction();
}

Scan app example

Pass the delegate implementation to the service

Probably the most difficult part in the process, the service in //ash/webui needs to be given access to an instance of the delegate implementation created in //chrome. This requires the app to be created/launched from //chrome. Here are two examples of correctly setting up and passing the impl: Scan app example via chrome_web_ui_controller_factory.cc Diagnostics Log Controller example via chrome_browser_main_parts_ash.cc

Invoke the delegate function from the service

Once the delegate impl is passed to the service and stored as a private local variable, any function defined in the delegate header can now be invoked without worry of dependency conflicts.

.h file

class Service {
 private:
  std::string UseStringFromChrome();

  // Provides browser functionality from //chrome to the Service UI.
  std::unique_ptr<ServiceDelegate> service_delegate_;
};

Scan app example

.cc file

std::string Service::GetStringFromChromeBrowser() {
  service_delegate_->GetStringFromChromeBrowser();
}

Scan app example

Example CL