# 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. [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: `chrome/browser/resources/hello_world/hello_world_container.html` ```html ``` `chrome/browser/resources/hello_world/hello_world.css` ```css body { margin: 0; } ``` `chrome/browser/resources/hello_world/hello_world.html` ```html

Hello World

[[message_]]
``` `chrome/browser/resources/hello_world/hello_world.js` ```js import './strings.m.js'; import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js'; import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; /** @polymer */ export class HelloWorldElement extends PolymerElement { static get is() { return 'hello-world'; } static get template() { return html`{__html_template__}`; } static get properties() { return { message_: { type: String, value: () => loadTimeData.getString('message'), }, }; } } customElements.define(HelloWorldElement.is, HelloWorldElement); ``` Add a `BUILD.gn` file to get Javascript type checking and Polymer compilation: `chrome/browser/resources/hello_world/BUILD.gn` ``` import("//third_party/closure_compiler/compile_js.gni") import("//tools/polymer/html_to_js.gni") html_to_js("web_components") { js_files = [ "hello_world.js" ] } js_library("hello_world") { deps = [ "//ui/webui/resources/js:load_time_data.m", "//ui/webui/resources/js:util.m", ] } js_type_check("closure_compile") { deps = [ ":hello_world" ] } ``` Add the new `:closure_compile` target to `chrome/browser/resources/BUILD.gn` to include it in coverage: ``` group("closure_compile) { deps = [ ... "hello_world:closure_compile" ... ] ``` 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 `chrome/browser/resources/hello_world/BUILD.gn` ``` import("//tools/grit/grit_rule.gni") import("//tools/grit/preprocess_if_expr.gni") import("//ui/webui/resources/tools/generate_grd.gni") preprocess_folder = "preprocessed" preprocess_gen_manifest = "preprocessed_gen_manifest.json" resources_grd_file = "$target_gen_dir/resources.grd" preprocess_if_expr("preprocess_generated") { deps = [ ":web_components" ] in_folder = target_gen_dir out_folder = "$target_gen_dir/$preprocess_folder" out_manifest = "$target_gen_dir/$preprocess_gen_manifest" in_files = [ "hello_world.js" ] } 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 = [ ":preprocess_generated" ] manifest_files = [ "$target_gen_dir/$preprocess_gen_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` ``` group("resources") { public_deps += [ ... "hello_world:resources" ... ] } ``` Also add to `chrome/chrome_paks.gni` ``` template("chrome_extra_paks") { ... (lots) sources += [ ... "$root_gen_dir/chrome/hello_world_resources.pak", ... ] deps += [ ... "//chrome/browser/resources/hello_world:resources", ... ] } ``` ## Adding URL constants for the new chrome URL `chrome/common/webui_url_constants.cc:` ```c++ const char kChromeUIHelloWorldURL[] = "chrome://hello-world/"; const char kChromeUIHelloWorldHost[] = "hello-world"; ``` `chrome/common/webui_url_constants.h:` ```c++ extern const char kChromeUIHelloWorldURL[]; extern const char kChromeUIHelloWorldHost[]; ``` ## 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` ```c++ #ifndef CHROME_BROWSER_UI_WEBUI_HELLO_WORLD_HELLO_WORLD_H_ #define CHROME_BROWSER_UI_WEBUI_HELLO_WORLD_HELLO_WORLD_H_ #include "content/public/browser/web_ui_controller.h" // The WebUI for chrome://hello-world class HelloWorldUI : public content::WebUIController { public: explicit HelloWorldUI(content::WebUI* web_ui); ~HelloWorldUI() override; }; #endif // CHROME_BROWSER_UI_WEBUI_HELLO_WORLD_HELLO_WORLD_H_ ``` `chrome/browser/ui/webui/hello_world_ui.cc` ```c++ #include "chrome/browser/ui/webui/hello_world_ui.h" #include "chrome/browser/ui/webui/webui_util.h" #include "chrome/common/webui_url_constants.h" #include "content/public/browser/browser_context.h" #include "content/public/browser/web_contents.h" #include "chrome/grit/hello_world_resources.h" #include "chrome/grit/hello_world_resources_map.h" #include "content/public/browser/web_ui.h" #include "content/public/browser/web_ui_data_source.h" 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(); // Add required resources. webui::SetupWebUIDataSource(html_source, base::make_span(kHelloWorldResources, kHelloWorldResourcesSize), IDR_HELLO_WORLD_HELLO_WORLD_HTML); content::BrowserContext* browser_context = web_ui->GetWebContents()->GetBrowserContext(); content::WebUIDataSource::Add(browser_context, html_source); } HelloWorldUI::~HelloWorldUI() = default; ``` To ensure that your code actually gets compiled, you need to add it to `chrome/browser/ui/BUILD.gn`: ``` static_library("ui") { sources = [ ... (lots) "webui/hello_world_ui.cc", "webui/hello_world_ui.h", ``` ## 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" ... + if (url.host() == chrome::kChromeUIHelloWorldHost) + return &NewWebUI; ``` ## 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 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! ## Making a WebUI Dialog Instead of having a full page for your WebUI, you might want a dialog in order to have a fully independent window. To do that, some small changes are needed to your code. First, we need to add a new class which inherits from `ui::WebDialogDelegate`. The easiest way to do that is to edit the `hello_world_ui.*` files `chrome/browser/ui/webui/hello_world_ui.h` ```c++ // Leave the old content, but add this new code class HelloWorldDialog : public ui::WebDialogDelegate { public: static void Show(); ~HelloWorldDialog() override; HelloWorldDialog(const HelloWorldDialog&) = delete; HelloWorldDialog& operator=(const HelloWorldDialog&) = delete; private: HelloWorldDialog(); // ui::WebDialogDelegate: ui::ModalType GetDialogModalType() const override; std::u16string GetDialogTitle() const override; GURL GetDialogContentURL() const override; void GetWebUIMessageHandlers( std::vector* handlers) const override; void GetDialogSize(gfx::Size* size) const override; std::string GetDialogArgs() const override; void OnDialogShown(content::WebUI* webui) override; void OnDialogClosed(const std::string& json_retval) override; void OnCloseContents(content::WebContents* source, bool* out_close_dialog) override; bool ShouldShowDialogTitle() const override; content::WebUI* webui_ = nullptr; }; ``` `chrome/browser/ui/webui/hello_world_ui.cc` ```c++ // Leave the old content, but add this new stuff HelloWorldDialog::HelloWorldDialog() = default; void HelloWorldDialog::Show() { chrome::ShowWebDialog(nullptr, ProfileManager::GetActiveUserProfile(), new HelloWorldDialog()); } ui::ModalType HelloWorldDialog::GetDialogModalType() const { return ui::MODAL_TYPE_NONE; } std::u16string HelloWorldDialog::GetDialogTitle() const { return u"Hello world"; } GURL HelloWorldDialog::GetDialogContentURL() const { return GURL(chrome::kChromeUIHelloWorldURL[); } void HelloWorldDialog::GetWebUIMessageHandlers( std::vector* handlers) const {} void HelloWorldDialog::GetDialogSize(gfx::Size* size) const { const int kDefaultWidth = 544; const int kDefaultHeight = 628; size->SetSize(kDefaultWidth, kDefaultHeight); } std::string HelloWorldDialog::GetDialogArgs() const { return ""; } void HelloWorldDialog::OnDialogShown(content::WebUI* webui) { webui_ = webui; } void HelloWorldDialog::OnDialogClosed(const std::string& json_retval) { delete this; } void HelloWorldDialog::OnCloseContents(content::WebContents* source, bool* out_close_dialog) { *out_close_dialog = true; } bool HelloWorldDialog::ShouldShowDialogTitle() const { return true; } HelloWorldDialog::~HelloWorldDialog() = default; ``` Finally, you will need to do something to actually show your dialog, which can be done by calling `HelloWorldDialog::Show()`.