blob: 39e3483733def07ec13398ac3ab37d0ad52585ed [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
18<a name="creating_web_ui_page"></a>
19WebUI pages live in `chrome/browser/resources`. You should create a folder for your project `chrome/browser/resources/hello_world`.
20When creating WebUI resources, follow the [Web Development Style Guide](https://chromium.googlesource.com/chromium/src/+/master/styleguide/web/web.md). For a sample WebUI page you could start with the following files:
21
22`chrome/browser/resources/hello_world/hello_world.html`
23```html
24<!DOCTYPE HTML>
25<html>
26<head>
27 <meta charset="utf-8">
28 <link rel="stylesheet" href="hello_world.css">
29 <script type="module" src="hello_world.js"></script>
30</head>
31<body>
32 <h1>Hello World</h1>
33 <div id="example-div"></div>
34</body>
35</html>
36```
37
38`chrome/browser/resources/hello_world/hello_world.css`
39```css
40body {
41 margin: 0;
42}
43```
44
45`chrome/browser/resources/hello_world/hello_world.js`
46```js
47import './strings.m.js';
48
49import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
50import {$} from 'chrome://resources/js/util.m.js';
51
52function initialize() {
53 const block = document.createElement('div');
54 block.innerText = loadTimeData.getString('message');
55 $('example-div').appendChild(block);
56}
57
58document.addEventListener('DOMContentLoaded', initialize);
59```
60
61Add a `BUILD.gn` file to get Javascript type checking:
62
63`chrome/browser/resources/hello_world/BUILD.gn`
64```
65import("//third_party/closure_compiler/compile_js.gni")
66
67js_library("hello_world") {
68 deps = [
69 "//ui/webui/resources/js:load_time_data.m",
70 "//ui/webui/resources/js:util.m",
71 ]
72}
73
74js_type_check("closure_compile") {
John Palmerd3225c22020-12-17 00:33:1775 deps = [ ":hello_world" ]
76}
77```
78
79Then refer to the new `:closure_compile` target from `chrome/browser/resources/BUILD.gn`:
80
81```
82group("closure_compile) {
83 deps = [
84 ...
85 "hello_world:closure_compile"
86 ...
87 ]
88```
89
90Finally, create an `OWNERS` file for the new folder.
91
92## Adding the resources
93Resources for the browser are stored in `grd` files. Current best practice is to autogenerate a grd file for your
94component in the `BUILD` file we created earlier
95
96`chrome/browser/resources/hello_world/BUILD.gn`
97```
98import("//tools/grit/grit_rule.gni")
99import("//ui/webui/resources/tools/generate_grd.gni")
100
101resources_grd_file = "$target_gen_dir/resources.grd"
102generate_grd("build_grd") {
103 grd_prefix = "hello_world"
104 out_grd = resources_grd_file
105 input_files = [
106 "hello_world.css",
107 "hello_world.html",
108 "hello_world.js",
109 ]
110 input_files_base_dir = rebase_path(".", "//")
111}
112
113grit("resources") {
114 enable_input_discovery_for_gn_analyze = false
115 source = resources_grd_file
116 deps = [ ":build_grd" ]
117 outputs = [
118 "grit/hello_world_resources.h",
119 "grit/hello_world_resources_map.cc",
120 "grit/hello_world_resources_map.h",
121 "hello_world_resources.pak",
122 ]
123 output_dir = "$root_gen_dir/chrome"
124}
125```
126
127Then add the new resource target to `chrome/browser/resources/BUILD.gn`
128```
129group("resources") {
130 public_deps += [
131 ...
132 "hello_world:resources"
133 ...
134 ]
135}
136```
137
Toby Huangd7752f92021-05-06 16:37:34138Also add to `chrome/chrome_paks.gni`
139
140```
141template("chrome_extra_paks") {
142 ... (lots)
143 sources += [
144 ...
145 "$root_gen_dir/chrome/hello_world_resources.pak",
146 ...
147 ]
148 deps += [
149 ...
150 "//chrome/browser/resources/hello_world:resources",
151 ...
152 ]
153}
154```
155
John Palmerd3225c22020-12-17 00:33:17156## Adding URL constants for the new chrome URL
157
158`chrome/common/webui_url_constants.cc:`
159```c++
160const char kChromeUIHelloWorldURL[] = "chrome://hello-world/";
161const char kChromeUIHelloWorldHost[] = "hello-world";
162```
163
164`chrome/common/webui_url_constants.h:`
165```c++
166extern const char kChromeUIHelloWorldURL[];
167extern const char kChromeUIHelloWorldHost[];
168```
169
170## Adding a WebUI class for handling requests to the chrome://hello-world/ URL
171Next we need a class to handle requests to this new resource URL. Typically this will subclass `WebUIController` (WebUI
172dialogs will also need another class which will subclass `WebDialogDelegate`, this is shown later).
173
174`chrome/browser/ui/webui/hello_world_ui.h`
175```c++
176#ifndef CHROME_BROWSER_UI_WEBUI_HELLO_WORLD_HELLO_WORLD_H_
177#define CHROME_BROWSER_UI_WEBUI_HELLO_WORLD_HELLO_WORLD_H_
178
John Palmerd3225c22020-12-17 00:33:17179#include "content/public/browser/web_ui_controller.h"
180
181// The WebUI for chrome://hello-world
182class HelloWorldUI : public content::WebUIController {
183 public:
184 explicit HelloWorldUI(content::WebUI* web_ui);
185 ~HelloWorldUI() override;
John Palmerd3225c22020-12-17 00:33:17186};
187
188#endif // CHROME_BROWSER_UI_WEBUI_HELLO_WORLD_HELLO_WORLD_H_
189```
190
191`chrome/browser/ui/webui/hello_world_ui.cc`
192```c++
193#include "chrome/browser/ui/webui/hello_world_ui.h"
194
195#include "chrome/browser/ui/webui/webui_util.h"
Toby Huangd7752f92021-05-06 16:37:34196#include "chrome/common/webui_url_constants.h"
John Palmerd3225c22020-12-17 00:33:17197#include "content/public/browser/browser_context.h"
198#include "content/public/browser/web_contents.h"
Toby Huangd7752f92021-05-06 16:37:34199#include "chrome/grit/hello_world_resources.h"
200#include "chrome/grit/hello_world_resources_map.h"
John Palmerd3225c22020-12-17 00:33:17201#include "content/public/browser/web_ui.h"
202#include "content/public/browser/web_ui_data_source.h"
203
204
205HelloWorldUI::HelloWorldUI(content::WebUI* web_ui)
206 : content::WebUIController(web_ui) {
207 // Set up the chrome://hello-world source.
208 content::WebUIDataSource* html_source =
209 content::WebUIDataSource::Create(chrome::kChromeUIHelloWorldHost);
210
211 // As a demonstration of passing a variable for JS to use we pass in some
212 // a simple message.
213 html_source->AddString("message", "Hello World!");
214 html_source->UseStringsJs();
215
216 // Add required resources.
Toby Huangd7752f92021-05-06 16:37:34217 webui::SetupWebUIDataSource(html_source, base::make_span(kHelloWorldResources, kHelloWorldResourcesSize), IDR_HELLO_WORLD_HELLO_WORLD_HTML);
John Palmerd3225c22020-12-17 00:33:17218
219 content::BrowserContext* browser_context =
220 web_ui->GetWebContents()->GetBrowserContext();
221 content::WebUIDataSource::Add(browser_context, html_source);
222}
223
Toby Huangd7752f92021-05-06 16:37:34224HelloWorldUI::~HelloWorldUI() = default;
John Palmerd3225c22020-12-17 00:33:17225```
226
227To ensure that your code actually gets compiled, you need to add it to `chrome/browser/ui/BUILD.gn`:
228
229```
230static_library("ui") {
231 sources = [
232 ... (lots)
Toby Huangd7752f92021-05-06 16:37:34233 "webui/hello_world_ui.cc",
234 "webui/hello_world_ui.h",
John Palmerd3225c22020-12-17 00:33:17235```
236
237## Adding your WebUI request handler to the Chrome WebUI factory
238
239The Chrome WebUI factory is where you setup your new request handler.
240
241`chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc:`
242```c++
243+ #include "chrome/browser/ui/webui/hello_world_ui.h"
244...
245+ if (url.host() == chrome::kChromeUIHelloWorldHost)
246+ return &NewWebUI<HelloWorldUI>;
247```
248
Toby Huangd7752f92021-05-06 16:37:34249## Add an entry to resource_ids.spec
250This file is for automatically generating resource ids. Ensure that the previous
251entry number plus the size equals the next entry number.
252
253`tools/gritsettings/resource_ids.spec`
254
255```
256 # START chrome/ WebUI resources section
257 ... (lots)
258 "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/hello_world/resources.grd": {
259 "META": {"sizes": {"includes": [5]}},
260 "includes": [2085],
261 },
262```
263
John Palmerd3225c22020-12-17 00:33:17264## Check everything works
265
266You're done! Assuming no errors (because everyone gets their code perfect the first time) you should be able to compile
267and run chrome and navigate to `chrome://hello-world/` and see your nifty welcome text!
268
269
270## Making a WebUI Dialog
271
272Instead of having a full page for your WebUI, you might want a dialog in order to have a fully independent window. To
273do that, some small changes are needed to your code. First, we need to add a new class which inherits from
274`ui::WebDialogDelegate`. The easiest way to do that is to edit the `hello_world_ui.*` files
275
276
277`chrome/browser/ui/webui/hello_world_ui.h`
278```c++
279 // Leave the old content, but add this new code
280 class HelloWorldDialog : public ui::WebDialogDelegate {
281 public:
282 static void Show();
283 ~HelloWorldDialog() override;
Toby Huangd7752f92021-05-06 16:37:34284 HelloWorldDialog(const HelloWorldDialog&) = delete;
285 HelloWorldDialog& operator=(const HelloWorldDialog&) = delete;
John Palmerd3225c22020-12-17 00:33:17286
287 private:
288 HelloWorldDialog();
289 // ui::WebDialogDelegate:
290 ui::ModalType GetDialogModalType() const override;
Jan Wilken Dörrie85285b02021-03-11 23:38:47291 std::u16string GetDialogTitle() const override;
John Palmerd3225c22020-12-17 00:33:17292 GURL GetDialogContentURL() const override;
293 void GetWebUIMessageHandlers(
294 std::vector<content::WebUIMessageHandler*>* handlers) const override;
295 void GetDialogSize(gfx::Size* size) const override;
296 std::string GetDialogArgs() const override;
297 void OnDialogShown(content::WebUI* webui) override;
298 void OnDialogClosed(const std::string& json_retval) override;
299 void OnCloseContents(content::WebContents* source,
300 bool* out_close_dialog) override;
301 bool ShouldShowDialogTitle() const override;
302
303 content::WebUI* webui_ = nullptr;
John Palmerd3225c22020-12-17 00:33:17304};
305```
306
307`chrome/browser/ui/webui/hello_world_ui.cc`
308```c++
309 // Leave the old content, but add this new stuff
310
Toby Huangd7752f92021-05-06 16:37:34311HelloWorldDialog::HelloWorldDialog() = default;
John Palmerd3225c22020-12-17 00:33:17312
313void HelloWorldDialog::Show() {
314 chrome::ShowWebDialog(nullptr, ProfileManager::GetActiveUserProfile(),
315 new HelloWorldDialog());
316}
317
318ui::ModalType HelloWorldDialog::GetDialogModalType() const {
319 return ui::MODAL_TYPE_NONE;
320}
321
Jan Wilken Dörrie85285b02021-03-11 23:38:47322std::u16string HelloWorldDialog::GetDialogTitle() const {
Jan Wilken Dörrie2c470ea2021-03-22 22:26:24323 return u"Hello world";
John Palmerd3225c22020-12-17 00:33:17324}
325
326GURL HelloWorldDialog::GetDialogContentURL() const {
327 return GURL(chrome::kChromeUIHelloWorldURL[);
328}
329
330void HelloWorldDialog::GetWebUIMessageHandlers(
331 std::vector<content::WebUIMessageHandler*>* handlers) const {}
332
333void HelloWorldDialog::GetDialogSize(gfx::Size* size) const {
334 const int kDefaultWidth = 544;
335 const int kDefaultHeight = 628;
336 size->SetSize(kDefaultWidth, kDefaultHeight);
337}
338
339std::string HelloWorldDialog::GetDialogArgs() const {
340 return "";
341}
342
343void HelloWorldDialog::OnDialogShown(content::WebUI* webui) {
344 webui_ = webui;
345}
346
347void HelloWorldDialog::OnDialogClosed(const std::string& json_retval) {
348 delete this;
349}
350
351void HelloWorldDialog::OnCloseContents(content::WebContents* source,
352 bool* out_close_dialog) {
353 *out_close_dialog = true;
354}
355
356bool HelloWorldDialog::ShouldShowDialogTitle() const {
357 return true;
358}
359
360HelloWorldDialog::~HelloWorldDialog() = default;
361```
362
363Finally, you will need to do something to actually show your dialog, which can be done by calling `HelloWorldDialog::Show()`.