Update WebUI recommendations based on the latest conventions
Bug: None Change-Id: Ie67e40000e23846cfc13d2f63dec33158e5a9bda Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3793848 Commit-Queue: Nicolas Dossou-Gbété <dgn@chromium.org> Reviewed-by: Demetrios Papadopoulos <dpapad@chromium.org> Cr-Commit-Position: refs/heads/main@{#1031914}
This commit is contained in:

committed by
Chromium LUCI CQ

parent
c149277d94
commit
7f953e5bc9
@ -11,20 +11,31 @@
|
||||
</style>
|
||||
|
||||
# Creating WebUI Interfaces outside components/
|
||||
This guide is based on [Creating WebUI Interfaces in components](webui_in_components.md), and comments from reviewers when creating the ChromeOS emoji picker.
|
||||
This guide is based on
|
||||
[Creating WebUI Interfaces in components](webui_in_components.md).
|
||||
|
||||
[TOC]
|
||||
|
||||
WebUI pages live in `chrome/browser/resources`. You should create a folder for your project `chrome/browser/resources/hello_world`.
|
||||
When creating WebUI resources, follow the [Web Development Style Guide](https://chromium.googlesource.com/chromium/src/+/main/styleguide/web/web.md). For a sample WebUI page you could start with the following files:
|
||||
A WebUI page is made of a Polymer single-page application, which communicates
|
||||
with a C++ UI controller, as explained [here](webui_explainer.md).
|
||||
|
||||
`chrome/browser/resources/hello_world/hello_world_container.html`
|
||||
WebUI pages live in `chrome/browser/resources` and their native counterpart in
|
||||
`chrome/browser/ui/webui/`. We will start by creating folders for the new page
|
||||
in `chrome/browser/[resources|ui/webui]/hello_world`. When creating WebUI
|
||||
resources, follow the
|
||||
[Web Development Style Guide](https://chromium.googlesource.com/chromium/src/+/main/styleguide/web/web.md).
|
||||
|
||||
## Making a basic WebUI page
|
||||
|
||||
For a sample WebUI page you could start with the following files:
|
||||
|
||||
`chrome/browser/resources/hello_world/hello_world.html`
|
||||
```html
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<meta charset="utf-8">
|
||||
<link rel="stylesheet" href="hello_world.css">
|
||||
<hello-world></hello-world>
|
||||
<hello-world-app></hello-world-app>
|
||||
<script type="module" src="hello_world.js"></script>
|
||||
</html>
|
||||
```
|
||||
@ -36,23 +47,23 @@ body {
|
||||
}
|
||||
```
|
||||
|
||||
`chrome/browser/resources/hello_world/hello_world.html`
|
||||
`chrome/browser/resources/hello_world/app.html`
|
||||
```html
|
||||
<h1>Hello World</h1>
|
||||
<div id="example-div">[[message_]]</div>
|
||||
```
|
||||
|
||||
`chrome/browser/resources/hello_world/hello_world.ts`
|
||||
`chrome/browser/resources/hello_world/app.ts`
|
||||
```js
|
||||
import './strings.m.js';
|
||||
|
||||
import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
|
||||
import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
|
||||
import {getTemplate} from './hello_world.html.js';
|
||||
import {getTemplate} from './app.html.js';
|
||||
|
||||
export class HelloWorldElement extends PolymerElement {
|
||||
export class HelloWorldAppElement extends PolymerElement {
|
||||
static get is() {
|
||||
return 'hello-world';
|
||||
return 'hello-world-app';
|
||||
}
|
||||
|
||||
static get template() {
|
||||
@ -67,19 +78,27 @@ export class HelloWorldElement extends PolymerElement {
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private message_: string;
|
||||
}
|
||||
|
||||
customElements.define(HelloWorldElement.is, HelloWorldElement);
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'hello-world-app': HelloWorldAppElement;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define(HelloWorldAppElement.is, HelloWorldAppElement);
|
||||
```
|
||||
|
||||
Add a 'tsconfig_base.json' file to configure TypeScript options. Typical options
|
||||
needed by Polymer UIs include noUncheckedIndexAccess, noUnusedLocals, and
|
||||
strictPropertyInitialization, all set to false.
|
||||
Add a `tsconfig_base.json` file to configure TypeScript options. Typical
|
||||
options needed by Polymer UIs include:
|
||||
- disabling `noUncheckedIndexAccess`
|
||||
- disabling `noUnusedLocals`: private members from Elements are accessed from
|
||||
their HTML template
|
||||
- disabling `strictPropertyInitialization`: Element properties can be
|
||||
initialized via the `property` map.
|
||||
|
||||
`chrome/browser/resources/hello_world/tsconfig_base.json`
|
||||
```
|
||||
```json
|
||||
{
|
||||
"extends": "../../../../tools/typescript/tsconfig_base.json",
|
||||
"compilerOptions": {
|
||||
@ -94,93 +113,40 @@ Add a `BUILD.gn` file to get TypeScript compilation and to generate the JS file
|
||||
from which the template will be imported.
|
||||
|
||||
`chrome/browser/resources/hello_world/BUILD.gn`
|
||||
```
|
||||
import("//tools/polymer/html_to_wrapper.gni")
|
||||
import("//tools/grit/preprocess_if_expr.gni")
|
||||
import("//tools/typescript/ts_library.gni")
|
||||
```py
|
||||
import("//chrome/browser/resources/tools/build_webui.gni")
|
||||
|
||||
html_to_wrapper("html_wrapper_files") {
|
||||
in_files = [ "hello_world.html" ]
|
||||
}
|
||||
build_webui("build") {
|
||||
grd_prefix = "hello_world"
|
||||
|
||||
# Move everything to one folder using preprocess_if_expr.
|
||||
preprocess_folder = "preprocessed"
|
||||
static_files = [ "hello_world.html", "hello_world.css" ]
|
||||
|
||||
preprocess_if_expr("preprocess_generated") {
|
||||
# This file is generated by html_to_wrapper().
|
||||
in_files = [ "hello_world.html.ts" ]
|
||||
in_folder = target_gen_dir
|
||||
out_folder = "$target_gen_dir/$preprocess_folder"
|
||||
deps = [ ":html_wrapper_files" ]
|
||||
}
|
||||
web_component_files = [ "app.ts" ]
|
||||
|
||||
preprocess_if_expr("preprocess") {
|
||||
in_files = [ "hello_world.ts" ]
|
||||
in_folder = "."
|
||||
out_folder = "$target_gen_dir/$preprocess_folder"
|
||||
}
|
||||
|
||||
ts_library("build_ts") {
|
||||
root_dir = "$target_gen_dir/$preprocess_folder"
|
||||
out_dir = "$target_gen_dir/tsc"
|
||||
tsconfig_base = "tsconfig_base.json"
|
||||
in_files = [
|
||||
"hello_world.ts",
|
||||
"hello_world.html.ts"
|
||||
non_web_component_files = [
|
||||
# For example the BrowserProxy file would go here.
|
||||
]
|
||||
deps = [
|
||||
|
||||
ts_deps = [
|
||||
"//third_party/polymer/v3_0:library",
|
||||
"//ui/webui/resources:library",
|
||||
]
|
||||
extra_deps = [
|
||||
":preprocess",
|
||||
":preprocess_generated",
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
> Note: See [the build config docs for more examples](webui_build_configuration.md#example-build-configurations)
|
||||
of how the build could be configured.
|
||||
|
||||
Finally, create an `OWNERS` file for the new folder.
|
||||
|
||||
## Adding the resources
|
||||
Resources for the browser are stored in `grd` files. Current best practice is to autogenerate a grd file for your
|
||||
component in the `BUILD` file we created earlier. See new content below:
|
||||
### Adding the resources
|
||||
|
||||
`chrome/browser/resources/hello_world/BUILD.gn new additions`
|
||||
```
|
||||
import("//tools/grit/grit_rule.gni")
|
||||
import("//ui/webui/resources/tools/generate_grd.gni")
|
||||
The `build_webui` target in `BUILD.gn` autogenerates some targets and files
|
||||
that need to be linked from the binary-wide resource targets:
|
||||
|
||||
resources_grd_file = "$target_gen_dir/resources.grd"
|
||||
Add the new resource target to `chrome/browser/resources/BUILD.gn`
|
||||
|
||||
generate_grd("build_grd") {
|
||||
grd_prefix = "hello_world"
|
||||
out_grd = resources_grd_file
|
||||
input_files = [
|
||||
"hello_world.css",
|
||||
"hello_world_container.html",
|
||||
]
|
||||
input_files_base_dir = rebase_path(".", "//")
|
||||
deps = [ ":build_ts" ]
|
||||
manifest_files = filter_include(
|
||||
get_target_outputs(":build_ts"), [ "*.manifest" ])
|
||||
}
|
||||
|
||||
grit("resources") {
|
||||
enable_input_discovery_for_gn_analyze = false
|
||||
source = resources_grd_file
|
||||
deps = [ ":build_grd" ]
|
||||
outputs = [
|
||||
"grit/hello_world_resources.h",
|
||||
"grit/hello_world_resources_map.cc",
|
||||
"grit/hello_world_resources_map.h",
|
||||
"hello_world_resources.pak",
|
||||
]
|
||||
output_dir = "$root_gen_dir/chrome"
|
||||
}
|
||||
```
|
||||
|
||||
Then add the new resource target to `chrome/browser/resources/BUILD.gn`
|
||||
```
|
||||
```py
|
||||
group("resources") {
|
||||
public_deps += [
|
||||
...
|
||||
@ -190,9 +156,25 @@ group("resources") {
|
||||
}
|
||||
```
|
||||
|
||||
Also add to `chrome/chrome_paks.gni`
|
||||
Add an entry to resource_ids.spec
|
||||
|
||||
This file is for automatically generating resource ids. Ensure that your entry
|
||||
has a unique ID and preserves numerical ordering.
|
||||
|
||||
`tools/gritsettings/resource_ids.spec`
|
||||
|
||||
```
|
||||
# START chrome/ WebUI resources section
|
||||
... (lots)
|
||||
"<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/hello_world/resources.grd": {
|
||||
"META": {"sizes": {"includes": [5]}},
|
||||
"includes": [2085],
|
||||
},
|
||||
```
|
||||
|
||||
Also add to `chrome/chrome_paks.gni`
|
||||
|
||||
```py
|
||||
template("chrome_extra_paks") {
|
||||
... (lots)
|
||||
sources += [
|
||||
@ -208,7 +190,7 @@ template("chrome_extra_paks") {
|
||||
}
|
||||
```
|
||||
|
||||
## Adding URL constants for the new chrome URL
|
||||
### Adding URL constants for the new chrome URL
|
||||
|
||||
`chrome/common/webui_url_constants.cc:`
|
||||
```c++
|
||||
@ -222,11 +204,12 @@ extern const char kChromeUIHelloWorldURL[];
|
||||
extern const char kChromeUIHelloWorldHost[];
|
||||
```
|
||||
|
||||
## Adding a WebUI class for handling requests to the chrome://hello-world/ URL
|
||||
### Adding a WebUI class for handling requests to the `chrome://hello-world/` URL
|
||||
|
||||
Next we need a class to handle requests to this new resource URL. Typically this will subclass `WebUIController` (WebUI
|
||||
dialogs will also need another class which will subclass `WebDialogDelegate`, this is shown later).
|
||||
|
||||
`chrome/browser/ui/webui/hello_world_ui.h`
|
||||
`chrome/browser/ui/webui/hello_world/hello_world_ui.h`
|
||||
```c++
|
||||
#ifndef CHROME_BROWSER_UI_WEBUI_HELLO_WORLD_HELLO_WORLD_H_
|
||||
#define CHROME_BROWSER_UI_WEBUI_HELLO_WORLD_HELLO_WORLD_H_
|
||||
@ -243,7 +226,7 @@ class HelloWorldUI : public content::WebUIController {
|
||||
#endif // CHROME_BROWSER_UI_WEBUI_HELLO_WORLD_HELLO_WORLD_H_
|
||||
```
|
||||
|
||||
`chrome/browser/ui/webui/hello_world_ui.cc`
|
||||
`chrome/browser/ui/webui/hello_world/hello_world_ui.cc`
|
||||
```c++
|
||||
#include "chrome/browser/ui/webui/hello_world_ui.h"
|
||||
|
||||
@ -260,23 +243,19 @@ class HelloWorldUI : public content::WebUIController {
|
||||
HelloWorldUI::HelloWorldUI(content::WebUI* web_ui)
|
||||
: content::WebUIController(web_ui) {
|
||||
// Set up the chrome://hello-world source.
|
||||
content::WebUIDataSource* html_source =
|
||||
content::WebUIDataSource::Create(chrome::kChromeUIHelloWorldHost);
|
||||
|
||||
// As a demonstration of passing a variable for JS to use we pass in some
|
||||
// a simple message.
|
||||
html_source->AddString("message", "Hello World!");
|
||||
html_source->UseStringsJs();
|
||||
content::WebUIDataSource* source = content::WebUIDataSource::CreateAndAdd(
|
||||
web_ui->GetWebContents()->GetBrowserContext(),
|
||||
chrome::kChromeUIHelloWorldHost);
|
||||
|
||||
// Add required resources.
|
||||
webui::SetupWebUIDataSource(
|
||||
html_source,
|
||||
source,
|
||||
base::make_span(kHelloWorldResources, kHelloWorldResourcesSize),
|
||||
IDR_HELLO_WORLD_HELLO_WORLD_CONTAINER_HTML);
|
||||
|
||||
content::BrowserContext* browser_context =
|
||||
web_ui->GetWebContents()->GetBrowserContext();
|
||||
content::WebUIDataSource::Add(browser_context, html_source);
|
||||
// As a demonstration of passing a variable for JS to use we pass in some
|
||||
// a simple message.
|
||||
source->AddString("message", "Hello World!");
|
||||
}
|
||||
|
||||
HelloWorldUI::~HelloWorldUI() = default;
|
||||
@ -284,42 +263,30 @@ HelloWorldUI::~HelloWorldUI() = default;
|
||||
|
||||
To ensure that your code actually gets compiled, you need to add it to `chrome/browser/ui/BUILD.gn`:
|
||||
|
||||
```
|
||||
```py
|
||||
static_library("ui") {
|
||||
sources = [
|
||||
... (lots)
|
||||
"webui/hello_world_ui.cc",
|
||||
"webui/hello_world_ui.h",
|
||||
"webui/hello_world/hello_world_ui.cc",
|
||||
"webui/hello_world/hello_world_ui.h",
|
||||
...
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Adding your WebUI request handler to the Chrome WebUI factory
|
||||
### Adding your WebUI request handler to the Chrome WebUI factory
|
||||
|
||||
The Chrome WebUI factory is where you setup your new request handler.
|
||||
|
||||
`chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc:`
|
||||
```c++
|
||||
+ #include "chrome/browser/ui/webui/hello_world_ui.h"
|
||||
+ #include "chrome/browser/ui/webui/hello_world/hello_world_ui.h"
|
||||
...
|
||||
+ if (url.host() == chrome::kChromeUIHelloWorldHost)
|
||||
+ return &NewWebUI<HelloWorldUI>;
|
||||
```
|
||||
|
||||
## Add an entry to resource_ids.spec
|
||||
This file is for automatically generating resource ids. Ensure that your entry
|
||||
has a unique ID and preserves numerical ordering.
|
||||
|
||||
`tools/gritsettings/resource_ids.spec`
|
||||
|
||||
```
|
||||
# START chrome/ WebUI resources section
|
||||
... (lots)
|
||||
"<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/hello_world/resources.grd": {
|
||||
"META": {"sizes": {"includes": [5]}},
|
||||
"includes": [2085],
|
||||
},
|
||||
```
|
||||
|
||||
## Check everything works
|
||||
### Check everything works
|
||||
|
||||
You're done! Assuming no errors (because everyone gets their code perfect the first time) you should be able to compile
|
||||
and run chrome and navigate to `chrome://hello-world/` and see your nifty welcome text!
|
||||
@ -332,7 +299,7 @@ do that, some small changes are needed to your code. First, we need to add a ne
|
||||
`ui::WebDialogDelegate`. The easiest way to do that is to edit the `hello_world_ui.*` files
|
||||
|
||||
|
||||
`chrome/browser/ui/webui/hello_world_ui.h`
|
||||
`chrome/browser/ui/webui/hello_world/hello_world_ui.h`
|
||||
```c++
|
||||
// Leave the old content, but add this new code
|
||||
class HelloWorldDialog : public ui::WebDialogDelegate {
|
||||
@ -362,7 +329,7 @@ do that, some small changes are needed to your code. First, we need to add a ne
|
||||
};
|
||||
```
|
||||
|
||||
`chrome/browser/ui/webui/hello_world_ui.cc`
|
||||
`chrome/browser/ui/webui/hello_world/hello_world_ui.cc`
|
||||
```c++
|
||||
// Leave the old content, but add this new stuff
|
||||
|
||||
@ -420,3 +387,60 @@ HelloWorldDialog::~HelloWorldDialog() = default;
|
||||
```
|
||||
|
||||
Finally, you will need to do something to actually show your dialog, which can be done by calling `HelloWorldDialog::Show()`.
|
||||
|
||||
## More elaborate configurations
|
||||
|
||||
### Referencing resources from another webui page
|
||||
|
||||
There are already mechanisms to make resources available chrome-wide, by
|
||||
publishing them under `chrome://resources`. If this is not appropriate, there
|
||||
are some ways to serve a file from some other webui page directly through
|
||||
another host.
|
||||
|
||||
First, a few explanations. The configuration based on the `build_webui()` BUILD
|
||||
target as presented above generates a few helpers that hide the complexity of
|
||||
the page's configuration. For example, considering the snippet below:
|
||||
|
||||
```cpp
|
||||
//...
|
||||
#include "chrome/grit/hello_world_resources.h"
|
||||
#include "chrome/grit/hello_world_resources_map.h"
|
||||
|
||||
HelloWorldUI::HelloWorldUI(content::WebUI* web_ui)
|
||||
: content::WebUIController(web_ui) {
|
||||
// ...
|
||||
webui::SetupWebUIDataSource(
|
||||
source,
|
||||
base::make_span(kHelloWorldResources, kHelloWorldResourcesSize),
|
||||
IDR_HELLO_WORLD_HELLO_WORLD_CONTAINER_HTML);
|
||||
}
|
||||
```
|
||||
|
||||
`kHelloWorldResources` and `kHelloWorldResourcesSize` come from from the
|
||||
imported grit-generated files, as configured by the build target, and reference
|
||||
the files listed in it so they can be served out of the given host name.
|
||||
For example, they would contain values like:
|
||||
|
||||
```cpp
|
||||
const webui::ResourcePath kHelloWorldResources[] = {
|
||||
{"hello_world.html", IDR_CHROME_BROWSER_RESOURCES_HELLO_WORLD_HELLO_WORLD_HTML},
|
||||
{"hello_world.css", IDR_CHROME_BROWSER_RESOURCES_HELLO_WORLD_HELLO_WORLD_CSS},
|
||||
};
|
||||
```
|
||||
|
||||
Using `WebUIDataSource::AddResourcePaths()` we can add other resources,
|
||||
looking for the right way to declare them by looking through the generated
|
||||
grit files (e.g. via codesearch), or manual registrations if they exist.
|
||||
|
||||
```cpp
|
||||
#include "chrome/grit/signin_resources.h"
|
||||
// ...
|
||||
HelloWorldUI::HelloWorldUI(content::WebUI* web_ui) {
|
||||
// ...
|
||||
static constexpr webui::ResourcePath kResources[] = {
|
||||
{"signin_shared.css.js", IDR_SIGNIN_SIGNIN_SHARED_CSS_JS},
|
||||
{"signin_vars.css.js", IDR_SIGNIN_SIGNIN_VARS_CSS_JS},
|
||||
};
|
||||
source->AddResourcePaths(kResources);
|
||||
}
|
||||
```
|
||||
|
Reference in New Issue
Block a user