blob: b65e886889ecb026fde1157ee67646f11befb889 [file] [log] [blame] [view]
John Palmerd3225c22020-12-17 00:33:171<style>
2.note::before {
3 content: 'Note: ';
4 font-variant: small-caps;
5 font-style: italic;
6}
7
8.doc h1 {
9 margin: 0;
10}
11</style>
12
13# Creating WebUI Interfaces outside components/
Toby Huangd7752f92021-05-06 16:37:3414This guide is based on [Creating WebUI Interfaces in components](webui_in_components.md), and comments from reviewers when creating the ChromeOS emoji picker.
John Palmerd3225c22020-12-17 00:33:1715
16[TOC]
17
John Palmerd3225c22020-12-17 00:33:1718WebUI pages live in `chrome/browser/resources`. You should create a folder for your project `chrome/browser/resources/hello_world`.
John Palmer046f9872021-05-24 01:24:5619When 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:
John Palmerd3225c22020-12-17 00:33:1720
Ryan Lesterbb992602021-07-15 15:36:0321`chrome/browser/resources/hello_world/hello_world_container.html`
John Palmerd3225c22020-12-17 00:33:1722```html
23<!DOCTYPE HTML>
24<html>
Ryan Lesterbb992602021-07-15 15:36:0325 <meta charset="utf-8">
26 <link rel="stylesheet" href="hello_world.css">
27 <hello-world></hello-world>
28 <script type="module" src="hello_world.js"></script>
John Palmerd3225c22020-12-17 00:33:1729</html>
30```
31
32`chrome/browser/resources/hello_world/hello_world.css`
33```css
34body {
35 margin: 0;
36}
37```
38
Ryan Lesterbb992602021-07-15 15:36:0339`chrome/browser/resources/hello_world/hello_world.html`
40```html
41<h1>Hello World</h1>
42<div id="example-div">[[message_]]</div>
43```
44
rbpotteracc480cd2022-03-04 08:42:1945`chrome/browser/resources/hello_world/hello_world.ts`
John Palmerd3225c22020-12-17 00:33:1746```js
47import './strings.m.js';
48
49import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
rbpotteracc480cd2022-03-04 08:42:1950import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
51import {getTemplate} from './hello_world.html.js';
John Palmerd3225c22020-12-17 00:33:1752
Ryan Lesterbb992602021-07-15 15:36:0353export class HelloWorldElement extends PolymerElement {
54 static get is() {
55 return 'hello-world';
56 }
57
58 static get template() {
rbpotteracc480cd2022-03-04 08:42:1959 return getTemplate();
Ryan Lesterbb992602021-07-15 15:36:0360 }
61
62 static get properties() {
63 return {
64 message_: {
65 type: String,
66 value: () => loadTimeData.getString('message'),
67 },
68 };
69 }
rbpotteracc480cd2022-03-04 08:42:1970
71 private message_: string;
John Palmerd3225c22020-12-17 00:33:1772}
73
Ryan Lesterbb992602021-07-15 15:36:0374customElements.define(HelloWorldElement.is, HelloWorldElement);
John Palmerd3225c22020-12-17 00:33:1775```
76
rbpotteracc480cd2022-03-04 08:42:1977Add a 'tsconfig_base.json' file to configure TypeScript options. Typical options
78needed by Polymer UIs include noUncheckedIndexAccess, noUnusedLocals, and
79strictPropertyInitialization, all set to false.
80
81`chrome/browser/resources/hello_world/tsconfig_base.gn`
82```
83{
84 "extends": "../../../../tools/typescript/tsconfig_base.json",
85 "compilerOptions": {
86 "noUncheckedIndexedAccess": false,
87 "noUnusedLocals": false,
88 "strictPropertyInitialization": false
89 }
90}
91```
92
93Add a `BUILD.gn` file to get TypeScript compilation and to generate the JS file
94from which the template will be imported.
John Palmerd3225c22020-12-17 00:33:1795
96`chrome/browser/resources/hello_world/BUILD.gn`
97```
rbpotteracc480cd2022-03-04 08:42:1998import("//tools/polymer/html_to_wrapper.gni")
99import("//tools/grit/preprocess_if_expr.gni")
100import("//tools/typescript/ts_library.gni")
Ryan Lesterbb992602021-07-15 15:36:03101
rbpotteracc480cd2022-03-04 08:42:19102html_to_wrapper("html_wrapper_files") {
103 in_files = [ "hello_world.html" ]
Ryan Lesterbb992602021-07-15 15:36:03104}
John Palmerd3225c22020-12-17 00:33:17105
rbpotteracc480cd2022-03-04 08:42:19106# Move everything to one folder using preprocess_if_expr.
107preprocess_folder = "preprocessed"
108
109preprocess_if_expr("preprocess_generated") {
110 # This file is generated by html_to_wrapper().
111 in_files = [ "hello_world.html.ts" ]
112 in_folder = target_gen_dir
113 out_folder = "$target_gen_dir/$preprocess_folder"
114 deps = [ ":html_wrapper_files" ]
115}
116
117preprocess_if_expr("preprocess") {
118 in_files = [ "hello_world.ts" ]
119 in_folder = "."
120 out_folder = "$target_gen_dir/$preprocess_folder"
121}
122
123ts_library("build_ts") {
124 root_dir = "$target_gen_dir/$preprocess_folder"
125 out_dir = "$target_gen_dir/tsc"
126 tsconfig_base = "tsconfig_base.json"
127 in_files = [
128 "hello_world.ts",
129 "hello_world.html.ts"
130 ]
John Palmerd3225c22020-12-17 00:33:17131 deps = [
rbpotteracc480cd2022-03-04 08:42:19132 "//third_party/polymer/v3_0:library,"
133 "//ui/webui/resources:library",
134 ]
135 extra_deps = [
136 ":preprocess",
137 ":preprocess_generated",
John Palmerd3225c22020-12-17 00:33:17138 ]
139}
John Palmerd3225c22020-12-17 00:33:17140```
141
142Finally, create an `OWNERS` file for the new folder.
143
144## Adding the resources
Ryan Lesterbb992602021-07-15 15:36:03145Resources for the browser are stored in `grd` files. Current best practice is to autogenerate a grd file for your
rbpotteracc480cd2022-03-04 08:42:19146component in the `BUILD` file we created earlier. See new content below:
John Palmerd3225c22020-12-17 00:33:17147
rbpotteracc480cd2022-03-04 08:42:19148`chrome/browser/resources/hello_world/BUILD.gn new additions`
John Palmerd3225c22020-12-17 00:33:17149```
150import("//tools/grit/grit_rule.gni")
151import("//ui/webui/resources/tools/generate_grd.gni")
152
153resources_grd_file = "$target_gen_dir/resources.grd"
Ryan Lesterbb992602021-07-15 15:36:03154
John Palmerd3225c22020-12-17 00:33:17155generate_grd("build_grd") {
156 grd_prefix = "hello_world"
157 out_grd = resources_grd_file
158 input_files = [
159 "hello_world.css",
Ryan Lesterbb992602021-07-15 15:36:03160 "hello_world_container.html",
John Palmerd3225c22020-12-17 00:33:17161 ]
162 input_files_base_dir = rebase_path(".", "//")
rbpotteracc480cd2022-03-04 08:42:19163 deps = [ ":build_ts" ]
164 manifest_files = [ "$target_gen_dir/$tsconfig.manifest" ]
John Palmerd3225c22020-12-17 00:33:17165}
166
167grit("resources") {
168 enable_input_discovery_for_gn_analyze = false
169 source = resources_grd_file
170 deps = [ ":build_grd" ]
171 outputs = [
172 "grit/hello_world_resources.h",
173 "grit/hello_world_resources_map.cc",
174 "grit/hello_world_resources_map.h",
175 "hello_world_resources.pak",
176 ]
177 output_dir = "$root_gen_dir/chrome"
178}
179```
180
181Then add the new resource target to `chrome/browser/resources/BUILD.gn`
182```
183group("resources") {
184 public_deps += [
185 ...
186 "hello_world:resources"
187 ...
188 ]
189}
190```
191
Toby Huangd7752f92021-05-06 16:37:34192Also add to `chrome/chrome_paks.gni`
193
194```
195template("chrome_extra_paks") {
196 ... (lots)
197 sources += [
198 ...
199 "$root_gen_dir/chrome/hello_world_resources.pak",
200 ...
201 ]
202 deps += [
203 ...
204 "//chrome/browser/resources/hello_world:resources",
205 ...
206 ]
207}
208```
209
John Palmerd3225c22020-12-17 00:33:17210## Adding URL constants for the new chrome URL
211
212`chrome/common/webui_url_constants.cc:`
213```c++
214const char kChromeUIHelloWorldURL[] = "chrome://hello-world/";
215const char kChromeUIHelloWorldHost[] = "hello-world";
216```
217
218`chrome/common/webui_url_constants.h:`
219```c++
220extern const char kChromeUIHelloWorldURL[];
221extern const char kChromeUIHelloWorldHost[];
222```
223
224## Adding a WebUI class for handling requests to the chrome://hello-world/ URL
225Next we need a class to handle requests to this new resource URL. Typically this will subclass `WebUIController` (WebUI
226dialogs will also need another class which will subclass `WebDialogDelegate`, this is shown later).
227
228`chrome/browser/ui/webui/hello_world_ui.h`
229```c++
230#ifndef CHROME_BROWSER_UI_WEBUI_HELLO_WORLD_HELLO_WORLD_H_
231#define CHROME_BROWSER_UI_WEBUI_HELLO_WORLD_HELLO_WORLD_H_
232
John Palmerd3225c22020-12-17 00:33:17233#include "content/public/browser/web_ui_controller.h"
234
235// The WebUI for chrome://hello-world
236class HelloWorldUI : public content::WebUIController {
237 public:
238 explicit HelloWorldUI(content::WebUI* web_ui);
239 ~HelloWorldUI() override;
John Palmerd3225c22020-12-17 00:33:17240};
241
242#endif // CHROME_BROWSER_UI_WEBUI_HELLO_WORLD_HELLO_WORLD_H_
243```
244
245`chrome/browser/ui/webui/hello_world_ui.cc`
246```c++
247#include "chrome/browser/ui/webui/hello_world_ui.h"
248
249#include "chrome/browser/ui/webui/webui_util.h"
Toby Huangd7752f92021-05-06 16:37:34250#include "chrome/common/webui_url_constants.h"
John Palmerd3225c22020-12-17 00:33:17251#include "content/public/browser/browser_context.h"
252#include "content/public/browser/web_contents.h"
Toby Huangd7752f92021-05-06 16:37:34253#include "chrome/grit/hello_world_resources.h"
254#include "chrome/grit/hello_world_resources_map.h"
John Palmerd3225c22020-12-17 00:33:17255#include "content/public/browser/web_ui.h"
256#include "content/public/browser/web_ui_data_source.h"
257
258
259HelloWorldUI::HelloWorldUI(content::WebUI* web_ui)
260 : content::WebUIController(web_ui) {
261 // Set up the chrome://hello-world source.
262 content::WebUIDataSource* html_source =
263 content::WebUIDataSource::Create(chrome::kChromeUIHelloWorldHost);
264
265 // As a demonstration of passing a variable for JS to use we pass in some
266 // a simple message.
267 html_source->AddString("message", "Hello World!");
268 html_source->UseStringsJs();
269
270 // Add required resources.
Lei Zhang6b01c7c2021-11-16 16:39:41271 webui::SetupWebUIDataSource(
272 html_source,
273 base::make_span(kHelloWorldResources, kHelloWorldResourcesSize),
274 IDR_HELLO_WORLD_HELLO_WORLD_CONTAINER_HTML);
John Palmerd3225c22020-12-17 00:33:17275
276 content::BrowserContext* browser_context =
277 web_ui->GetWebContents()->GetBrowserContext();
278 content::WebUIDataSource::Add(browser_context, html_source);
279}
280
Toby Huangd7752f92021-05-06 16:37:34281HelloWorldUI::~HelloWorldUI() = default;
John Palmerd3225c22020-12-17 00:33:17282```
283
284To ensure that your code actually gets compiled, you need to add it to `chrome/browser/ui/BUILD.gn`:
285
286```
287static_library("ui") {
288 sources = [
289 ... (lots)
Toby Huangd7752f92021-05-06 16:37:34290 "webui/hello_world_ui.cc",
291 "webui/hello_world_ui.h",
John Palmerd3225c22020-12-17 00:33:17292```
293
294## Adding your WebUI request handler to the Chrome WebUI factory
295
296The Chrome WebUI factory is where you setup your new request handler.
297
298`chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc:`
299```c++
300+ #include "chrome/browser/ui/webui/hello_world_ui.h"
301...
302+ if (url.host() == chrome::kChromeUIHelloWorldHost)
303+ return &NewWebUI<HelloWorldUI>;
304```
305
Toby Huangd7752f92021-05-06 16:37:34306## Add an entry to resource_ids.spec
Ryan Lesterdcf8f422021-07-07 15:36:18307This file is for automatically generating resource ids. Ensure that your entry
308has a unique ID and preserves numerical ordering.
Toby Huangd7752f92021-05-06 16:37:34309
310`tools/gritsettings/resource_ids.spec`
311
312```
313 # START chrome/ WebUI resources section
314 ... (lots)
315 "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/hello_world/resources.grd": {
316 "META": {"sizes": {"includes": [5]}},
317 "includes": [2085],
318 },
319```
320
John Palmerd3225c22020-12-17 00:33:17321## Check everything works
322
323You're done! Assuming no errors (because everyone gets their code perfect the first time) you should be able to compile
324and run chrome and navigate to `chrome://hello-world/` and see your nifty welcome text!
325
326
327## Making a WebUI Dialog
328
329Instead of having a full page for your WebUI, you might want a dialog in order to have a fully independent window. To
330do that, some small changes are needed to your code. First, we need to add a new class which inherits from
331`ui::WebDialogDelegate`. The easiest way to do that is to edit the `hello_world_ui.*` files
332
333
334`chrome/browser/ui/webui/hello_world_ui.h`
335```c++
336 // Leave the old content, but add this new code
337 class HelloWorldDialog : public ui::WebDialogDelegate {
338 public:
339 static void Show();
340 ~HelloWorldDialog() override;
Toby Huangd7752f92021-05-06 16:37:34341 HelloWorldDialog(const HelloWorldDialog&) = delete;
342 HelloWorldDialog& operator=(const HelloWorldDialog&) = delete;
John Palmerd3225c22020-12-17 00:33:17343
344 private:
345 HelloWorldDialog();
346 // ui::WebDialogDelegate:
347 ui::ModalType GetDialogModalType() const override;
Jan Wilken Dörrie85285b02021-03-11 23:38:47348 std::u16string GetDialogTitle() const override;
John Palmerd3225c22020-12-17 00:33:17349 GURL GetDialogContentURL() const override;
350 void GetWebUIMessageHandlers(
351 std::vector<content::WebUIMessageHandler*>* handlers) const override;
352 void GetDialogSize(gfx::Size* size) const override;
353 std::string GetDialogArgs() const override;
354 void OnDialogShown(content::WebUI* webui) override;
355 void OnDialogClosed(const std::string& json_retval) override;
356 void OnCloseContents(content::WebContents* source,
357 bool* out_close_dialog) override;
358 bool ShouldShowDialogTitle() const override;
359
360 content::WebUI* webui_ = nullptr;
John Palmerd3225c22020-12-17 00:33:17361};
362```
363
364`chrome/browser/ui/webui/hello_world_ui.cc`
365```c++
366 // Leave the old content, but add this new stuff
367
Toby Huangd7752f92021-05-06 16:37:34368HelloWorldDialog::HelloWorldDialog() = default;
John Palmerd3225c22020-12-17 00:33:17369
370void HelloWorldDialog::Show() {
Lei Zhang6b01c7c2021-11-16 16:39:41371 // HelloWorldDialog is self-deleting via OnDialogClosed().
John Palmerd3225c22020-12-17 00:33:17372 chrome::ShowWebDialog(nullptr, ProfileManager::GetActiveUserProfile(),
373 new HelloWorldDialog());
374}
375
376ui::ModalType HelloWorldDialog::GetDialogModalType() const {
377 return ui::MODAL_TYPE_NONE;
378}
379
Jan Wilken Dörrie85285b02021-03-11 23:38:47380std::u16string HelloWorldDialog::GetDialogTitle() const {
Jan Wilken Dörrie2c470ea2021-03-22 22:26:24381 return u"Hello world";
John Palmerd3225c22020-12-17 00:33:17382}
383
384GURL HelloWorldDialog::GetDialogContentURL() const {
385 return GURL(chrome::kChromeUIHelloWorldURL[);
386}
387
388void HelloWorldDialog::GetWebUIMessageHandlers(
389 std::vector<content::WebUIMessageHandler*>* handlers) const {}
390
391void HelloWorldDialog::GetDialogSize(gfx::Size* size) const {
392 const int kDefaultWidth = 544;
393 const int kDefaultHeight = 628;
394 size->SetSize(kDefaultWidth, kDefaultHeight);
395}
396
397std::string HelloWorldDialog::GetDialogArgs() const {
398 return "";
399}
400
401void HelloWorldDialog::OnDialogShown(content::WebUI* webui) {
402 webui_ = webui;
403}
404
405void HelloWorldDialog::OnDialogClosed(const std::string& json_retval) {
406 delete this;
407}
408
409void HelloWorldDialog::OnCloseContents(content::WebContents* source,
410 bool* out_close_dialog) {
411 *out_close_dialog = true;
412}
413
414bool HelloWorldDialog::ShouldShowDialogTitle() const {
415 return true;
416}
417
418HelloWorldDialog::~HelloWorldDialog() = default;
419```
420
421Finally, you will need to do something to actually show your dialog, which can be done by calling `HelloWorldDialog::Show()`.