blob: dd84e4792fd3ee64eb5a334d08d737497211ca8b [file] [log] [blame] [view]
hkamila843aed7d2017-09-01 18:34:131<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 in `components/`
14
Ali Jumafb3dc1f2020-01-07 17:33:4715To create a WebUI interface in `components/` you need to follow different steps from [Creating WebUI Interfaces in `chrome/`](https://www.chromium.org/developers/webui). This guide is specific to creating a WebUI interface in `src/components/`. It is based on the steps I went through to create the WebUI infrastructure for chrome://safe-browsing in 'src/components/safe_browsing/content/web_ui/'.
hkamila843aed7d2017-09-01 18:34:1316
17[TOC]
18
19<a name="creating_webui_page"></a>
20## Creating the WebUI page
21
22WebUI resources in `components/` will be added in your specific project folder. Create a project folder `src/components/hello_world/`. When 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:
23
24`src/components/hello_world/hello_world.html:`
25```html
26<!DOCTYPE HTML>
Dave Schuylera131ab12017-12-12 20:03:5827<html dir="$i18n{textdirection}">
hkamila843aed7d2017-09-01 18:34:1328<head>
29 <meta charset="utf-8">
Dave Schuylera131ab12017-12-12 20:03:5830 <title>$i18n{helloWorldTitle}</title>
hkamila843aed7d2017-09-01 18:34:1331 <link rel="stylesheet" href="hello_world.css">
32 <script src="chrome://resources/js/cr.js"></script>
33 <script src="chrome://resources/js/load_time_data.js"></script>
dpapade2575bf2020-11-02 21:52:0934 <script src="chrome://resources/js/assert.js"></script>
hkamila843aed7d2017-09-01 18:34:1335 <script src="chrome://resources/js/util.js"></script>
36 <script src="strings.js"></script>
37 <script src="hello_world.js"></script>
38</head>
Dave Schuylera131ab12017-12-12 20:03:5839<body style="font-family:$i18n{fontfamily};font-size:$i18n{fontSize}">
40 <h1>$i18n{helloWorldTitle}</h1>
hkamila843aed7d2017-09-01 18:34:1341 <p id="welcome-message"></p>
hkamila843aed7d2017-09-01 18:34:1342</body>
43</html>
44```
45
46`src/components/hello_world/hello_world.css:`
47```css
48p {
49 white-space: pre-wrap;
50}
51```
52
53`src/components/hello_world/hello_world.js:`
54```js
55cr.define('hello_world', function() {
56 'use strict';
57
58 /**
59 * Be polite and insert translated hello world strings for the user on loading.
60 */
61 function initialize() {
62 $('welcome-message').textContent = loadTimeData.getStringF('welcomeMessage',
63 loadTimeData.getString('userName'));
64 }
65
66 // Return an object with all of the exports.
67 return {
68 initialize: initialize,
69 };
70});
71
72document.addEventListener('DOMContentLoaded', hello_world.initialize);
73```
74
75## Adding the resources
76
77Resource files are specified in a `.grdp` file. Here's our
78`components/resources/hello_world_resources.grdp`:
79
80```xml
81<?xml version="1.0" encoding="utf-8"?>
82<grit-part>
83 <include name="IDR_HELLO_WORLD_HTML" file="../../components/hello_world/hello_world.html" type="BINDATA" />
84 <include name="IDR_HELLO_WORLD_CSS" file="../../components/hello_world/hello_world.css" type="BINDATA" />
85 <include name="IDR_HELLO_WORLD_JS" file="../../components/hello_world/hello_world.js" type="BINDATA" />
86</grit-part>
87```
88
Samuel Huang1b99a062020-01-08 22:20:3489Add the created file in `components/resources/dev_ui_components_resources.grd`:
90
91```xml
92+<part file="hello_world_resources.grdp" />
93```
94
hkamila843aed7d2017-09-01 18:34:1395## Adding URL constants for the new chrome URL
96
97Create the `constants.cc` and `constants.h` files to add the URL constants. This is where you will add the URL or URL's which will be directed to your new resources.
98
99`src/components/hello_world/constants.cc:`
100```c++
101const char kChromeUIHelloWorldURL[] = "chrome://hello-world/";
102const char kChromeUIHelloWorldHost[] = "hello-world";
103```
104
105`src/components/hello_world/constants.h:`
106```c++
107extern const char kChromeUIHelloWorldURL[];
108extern const char kChromeUIHelloWorldHost[];
109```
110
111## Adding localized strings
112
113We need a few string resources for translated strings to work on the new resource. The welcome message contains a variable with a sample value so that it can be accurately translated. Create a new file `components/hello_world_strings.grdp`. You can add the messages as follow:
114
115```xml
116<message name="IDS_HELLO_WORLD_TITLE" desc="A happy message saying hello to the world">
117 Hello World!
118</message>
119<message name="IDS_HELLO_WORLD_WELCOME_TEXT" desc="Message welcoming the user to the hello world page">
120 Welcome to this fancy Hello World page <ph name="WELCOME_NAME">$1<ex>Chromium User</ex></ph>!
121</message>
122```
123Add the created file in `components/components_strings.grd`:
124
125```xml
126+<part file="hello_world_strings.grdp" />
127```
128
129## Adding a WebUI class for handling requests to the chrome://hello-world/ URL
130
131Next we need a class to handle requests to this new resource URL. Typically this will subclass `ChromeWebUI` (but WebUI dialogs should subclass `HtmlDialogUI` instead).
132
133`src/components/hello_world/hello_world_ui.h:`
134```c++
135#ifndef COMPONENTS_HELLO_WORLD_HELLO_WORLD_UI_H_
136#define COMPONENTS_HELLO_WORLD_HELLO_WORLD_UI_H_
137#pragma once
138
139#include "base/macros.h"
140#include "content/public/browser/web_ui_controller.h"
141
142// The WebUI for chrome://hello-world
143class HelloWorldUI : public content::WebUIController {
144 public:
145 explicit HelloWorldUI(content::WebUI* web_ui);
Johann16cf06fd2020-09-14 22:55:55146 HelloWorldUI(const HelloWorldUI&) = delete;
147 HelloWorldUI& operator=(const HelloWorldUI&) = delete;
hkamila843aed7d2017-09-01 18:34:13148 ~HelloWorldUI() override;
149 private:
hkamila843aed7d2017-09-01 18:34:13150};
151
152#endif // COMPONENTS_HELLO_WORLD_HELLO_WORLD_UI_H_
153```
154
155`src/components/hello_world/hello_world_ui.cc:`
156```c++
157#include "components/hello_world/hello_world_ui.h"
158
hkamila843aed7d2017-09-01 18:34:13159#include "components/grit/components_scaled_resources.h"
Samuel Huang1b99a062020-01-08 22:20:34160#include "components/grit/dev_ui_components_resources.h"
hkamila843aed7d2017-09-01 18:34:13161#include "components/hello_world/constants.h"
162#include "components/strings/grit/components_strings.h"
163#include "content/public/browser/browser_context.h"
164#include "content/public/browser/web_contents.h"
165#include "content/public/browser/web_ui.h"
166#include "content/public/browser/web_ui_data_source.h"
167
168HelloWorldUI::HelloWorldUI(content::WebUI* web_ui)
169 : content::WebUIController(web_ui) {
170 // Set up the chrome://hello-world source.
171 content::WebUIDataSource* html_source =
172 content::WebUIDataSource::Create(chrome::kChromeUIHelloWorldHost);
173
174 // Localized strings.
175 html_source->AddLocalizedString("helloWorldTitle", IDS_HELLO_WORLD_TITLE);
176 html_source->AddLocalizedString("welcomeMessage", IDS_HELLO_WORLD_WELCOME_TEXT);
177
178 // As a demonstration of passing a variable for JS to use we pass in the name "Bob".
179 html_source->AddString("userName", "Bob");
Dan Beame9f4007f2019-08-17 00:59:10180 html_source->UseStringsJs();
hkamila843aed7d2017-09-01 18:34:13181
182 // Add required resources.
183 html_source->AddResourcePath("hello_world.css", IDR_HELLO_WORLD_CSS);
184 html_source->AddResourcePath("hello_world.js", IDR_HELLO_WORLD_JS);
185 html_source->SetDefaultResource(IDR_HELLO_WORLD_HTML);
186
187 content::BrowserContext* browser_context =
188 web_ui->GetWebContents()->GetBrowserContext();
189 content::WebUIDataSource::Add(browser_context, html_source);
190}
191
192HelloWorldUI::~HelloWorldUI() {
193}
194```
195
196## Adding new sources to Chrome
197
198In order for your new class to be built and linked, you need to update the `BUILD.gn` and DEPS files. Create
199
200`src/components/hello_world/BUILD.gn:`
201```
202sources = [
203 "hello_world_ui.cc",
204 "hello_world_ui.h",
205 ...
206```
207and `src/components/hello_world/DEPS:`
208```
209include_rules = [
hkamila843aed7d2017-09-01 18:34:13210 "+components/strings/grit/components_strings.h",
211 "+components/grit/components_scaled_resources.h"
Samuel Huang1b99a062020-01-08 22:20:34212 "+components/grit/dev_ui_components_resources.h",
hkamila843aed7d2017-09-01 18:34:13213]
214```
215
216## Adding your WebUI request handler to the Chrome WebUI factory
217
218The Chrome WebUI factory is where you setup your new request handler.
219
220`src/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc:`
221```c++
222+ #include "components/hello_world/hello_world_ui.h"
223+ #include "components/hello_world/constants.h"
224...
225+ if (url.host() == chrome::kChromeUIHelloWorldHost)
226+ return &NewWebUI<HelloWorldUI>;
227```
228
229## Testing
230
231You'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!
232
233## Adding a callback handler
234
235You probably want your new WebUI page to be able to do something or get information from the C++ world. For this, we use message callback handlers. Let's say that we don't trust the Javascript engine to be able to add two integers together (since we know that it uses floating point values internally). We could add a callback handler to perform integer arithmetic for us.
236
237`src/components/hello_world/hello_world_ui.h:`
238```c++
239#include "content/public/browser/web_ui.h"
240+
241+ namespace base {
242+ class ListValue;
243+ } // namespace base
244
245// The WebUI for chrome://hello-world
246...
247 // Set up the chrome://hello-world source.
248 content::WebUIDataSource* html_source = content::WebUIDataSource::Create(hello_world::kChromeUIHelloWorldHost);
249+
250+ // Register callback handler.
251+ RegisterMessageCallback("addNumbers",
Avi Drissman5e5875b2018-03-24 01:39:47252+ base::BindRepeating(&HelloWorldUI::AddNumbers,
253+ base::Unretained(this)));
hkamila843aed7d2017-09-01 18:34:13254
255 // Localized strings.
256...
257 virtual ~HelloWorldUI();
258+
259+ private:
260+ // Add two numbers together using integer arithmetic.
261+ void AddNumbers(const base::ListValue* args);
hkamila843aed7d2017-09-01 18:34:13262 };
263```
264
265`src/components/hello_world/hello_world_ui.cc:`
266```c++
267 #include "components/hello_world/hello_world_ui.h"
268+
269+ #include "base/values.h"
270 #include "content/public/browser/browser_context.h"
271...
272 HelloWorldUI::~HelloWorldUI() {
273 }
274+
275+ void HelloWorldUI::AddNumbers(const base::ListValue* args) {
276+ int term1, term2;
277+ if (!args->GetInteger(0, &term1) || !args->GetInteger(1, &term2))
278+ return;
279+ base::FundamentalValue result(term1 + term2);
280+ AllowJavascript();
281+ std::string callback_id;
282+ args->GetString(0, &callback_id);
283+ ResolveJavascriptCallback(base::Value(callback_id), result);
284+ }
285```
286
287`src/components/hello_world/hello_world.js:`
288```c++
289 function initialize() {
290+ cr.sendWithPromise('addNumbers', [2, 2]).then((result) =>
291+ addResult(result));
292 }
293+
294+ function addResult(result) {
295+ alert('The result of our C++ arithmetic: 2 + 2 = ' + result);
296+ }
297
298 return {
299 initialize: initialize,
300 };
301```
302
303You'll notice that the call is asynchronous. We must wait for the C++ side to call our Javascript function to get the result.
304
305## Creating a WebUI Dialog
306
307Some pages have many messages or share code that sends messages. To make possible message handling and/or to create a WebUI dialogue `c++->js` and `js->c++`, follow the guide in [WebUI Explainer](https://chromium.googlesource.com/chromium/src/+/master/docs/webui_explainer.md).
308
Samuel Huang1b99a062020-01-08 22:20:34309## DevUI Pages
310
311DevUI pages are WebUI pages intended for developers, and unlikely used by most users. An example is `chrome://bluetooth-internals`. On Android Chrome, these pages are moved to a separate [Dynamic Feature Module (DFM)](https://chromium.googlesource.com/chromium/src/+/master/docs/android_dynamic_feature_modules.md) to reduce binary size. Most WebUI pages are DevUI. This is why in this doc uses `dev_ui_components_resources.{grd, h}` in its examples.
312
313`components/` resources that are intended for end users are associated with `components_resources.{grd, h}` and `components_scaled_resorces.{grd, h}`. Use these in place of or inadditional to `dev_ui_components_resources.{grd, h}` if needed.
hkamila843aed7d2017-09-01 18:34:13314
315<script>
316let nameEls = Array.from(document.querySelectorAll('[id], a[name]'));
317let names = nameEls.map(nameEl => nameEl.name || nameEl.id);
318
319let localLinks = Array.from(document.querySelectorAll('a[href^="#"]'));
320let hrefs = localLinks.map(a => a.href.split('#')[1]);
321
322hrefs.forEach(href => {
323 if (names.includes(href))
324 console.info('found: ' + href);
325 else
326 console.error('broken href: ' + href);
327})
328</script>