avi | a41f7af | 2015-08-31 19:46:58 | [diff] [blame] | 1 | # Tab Helpers |
| 2 | |
| 3 | The `content/` layer of Chromium has a class called `WebContents`, which is one |
| 4 | of the most basic building blocks of all of Chromium. This document describes |
| 5 | how `WebContents`es are used to build tabs in browser windows. |
| 6 | |
Erik Chen | 50f7634b | 2024-04-29 21:14:22 | [diff] [blame] | 7 | Tab Helpers are deprecated for Desktop Chrome. Use TabFeatures instead. See |
| 8 | [design principles](chrome_browser_design_principles.md). |
| 9 | |
avi | a41f7af | 2015-08-31 19:46:58 | [diff] [blame] | 10 | [TOC] |
| 11 | |
| 12 | ## Introduction |
| 13 | |
| 14 | What is a "tab helper"? It is a `WebContentsObserver` owned by the `WebContents` |
| 15 | itself. Let's break that down. |
| 16 | |
Elly Fong-Jones | 8ea0fc76 | 2021-01-06 19:14:23 | [diff] [blame] | 17 | ## WebContentsObserver |
avi | a41f7af | 2015-08-31 19:46:58 | [diff] [blame] | 18 | |
| 19 | `WebContentsObserver` is a |
Elly Fong-Jones | 8ea0fc76 | 2021-01-06 19:14:23 | [diff] [blame] | 20 | [simple interface](https://source.chromium.org/chromium/chromium/src/+/HEAD:content/public/browser/web_contents_observer.h) |
avi | a41f7af | 2015-08-31 19:46:58 | [diff] [blame] | 21 | that allows an object to observe events in the life of a `WebContents`. As an |
| 22 | example, if we look at the `TabStripModel`, there are times when it need to |
| 23 | watch out for WebContents being deleted. So it creates a |
Elly Fong-Jones | 8ea0fc76 | 2021-01-06 19:14:23 | [diff] [blame] | 24 | [TabStripModel::WebContentsData](https://source.chromium.org/chromium/chromium/src/+/HEAD:chrome/browser/ui/tabs/tab_strip_model.cc). |
Suman Nelson Kancherla | 1d0cc35 | 2018-12-06 20:26:28 | [diff] [blame] | 25 | That object overrides `WebContentsDestroyed()`, and when a |
| 26 | `WebContents` gets destroyed, the callback is called and the object |
| 27 | processes the message. Note that `TabStripModel::WebContentsData` object is not owned by the |
avi | a41f7af | 2015-08-31 19:46:58 | [diff] [blame] | 28 | `WebContents`. It is owned indirectly by the `TabStripModel`. |
| 29 | |
Elly Fong-Jones | 8ea0fc76 | 2021-01-06 19:14:23 | [diff] [blame] | 30 | ## SupportsUserData and WebContentsUserData |
avi | a41f7af | 2015-08-31 19:46:58 | [diff] [blame] | 31 | |
| 32 | There is a mechanism used in Chromium called |
Josip Sokcevic | ba14441 | 2020-09-09 20:57:05 | [diff] [blame] | 33 | [`SupportsUserData`](https://source.chromium.org/chromium/chromium/src/+/HEAD:base/supports_user_data.h) |
avi | a41f7af | 2015-08-31 19:46:58 | [diff] [blame] | 34 | that allows attaching of arbitrary objects to an object. The mechanism is |
| 35 | simple: host objects derive from `SupportsUserData`, and owned objects derive |
| 36 | from `SupportsUserData::Data`. There are three calls to attach and detach the |
| 37 | data. |
| 38 | |
| 39 | `WebContents` derives from `SupportsUserData`, so that mechanism works for |
| 40 | attaching objects to a `WebContents`, but the `SupportsUserData` mechanism is a |
| 41 | bit low-level. A higher level abstraction is |
Josip Sokcevic | ba14441 | 2020-09-09 20:57:05 | [diff] [blame] | 42 | [`WebContentsUserData`](https://source.chromium.org/chromium/chromium/src/+/HEAD:content/public/browser/web_contents_user_data.h), |
avi | a41f7af | 2015-08-31 19:46:58 | [diff] [blame] | 43 | which is easy to derive from and has easy-to-use functionality in |
| 44 | `CreateForWebContents()` and `FromWebContents()`. |
| 45 | |
| 46 | ## Adding a feature to a browser tab |
| 47 | |
| 48 | Let's combine `WebContentsObserver` and `WebContentsUserData` together, to log |
| 49 | whenever the title of a tab changes. |
| 50 | |
| 51 | ``` |
| 52 | class TitleLoggerTabHelper |
| 53 | : public content::WebContentsObserver, |
| 54 | public content::WebContentsUserData<TitleLoggerTabHelper> { |
| 55 | public: |
Johann | 16cf06fd | 2020-09-14 22:55:55 | [diff] [blame] | 56 | TitleLoggerTabHelper(const TitleLoggerTabHelper&) = delete; |
| 57 | TitleLoggerTabHelper& operator=(const TitleLoggerTabHelper&) = delete; |
avi | a41f7af | 2015-08-31 19:46:58 | [diff] [blame] | 58 | ~TitleLoggerTabHelper() override; |
| 59 | |
| 60 | // content::WebContentsObserver |
Avi Drissman | 9300221 | 2017-09-27 03:20:52 | [diff] [blame] | 61 | void TitleWasSet(NavigationEntry* entry) override { |
avi | a41f7af | 2015-08-31 19:46:58 | [diff] [blame] | 62 | LOG(INFO) << "Title: " << entry->GetTitle(); |
| 63 | } |
| 64 | |
| 65 | private: |
| 66 | explicit TitleLoggerTabHelper(content::WebContents* web_contents); |
| 67 | friend class content::WebContentsUserData<TitleLoggerTabHelper>; |
avi | a41f7af | 2015-08-31 19:46:58 | [diff] [blame] | 68 | }; |
avi | a41f7af | 2015-08-31 19:46:58 | [diff] [blame] | 69 | ``` |
| 70 | |
| 71 | We want each tab to have this `WebContentsObserver` attached to it, so that it |
| 72 | will properly handle the events it's looking for, and when the tab goes away, |
| 73 | then this tab helper will go away too. |
| 74 | |
| 75 | But how do you hook in to browser tab creation? How can we attach this tab |
| 76 | helper to the `WebContents`es that are used for the browser tabs? |
| 77 | |
| 78 | ## AttachTabHelpers |
| 79 | |
| 80 | There is a function called |
Josip Sokcevic | ba14441 | 2020-09-09 20:57:05 | [diff] [blame] | 81 | [`AttachTabHelpers()`](https://source.chromium.org/chromium/chromium/src/+/HEAD:chrome/browser/ui/tab_helpers.cc;). |
avi | a41f7af | 2015-08-31 19:46:58 | [diff] [blame] | 82 | Whenever a `WebContents` is created for use as a browser tab, |
| 83 | `AttachTabHelpers()` is called. Every tab helper from around Chromium, |
| 84 | from ContentSettings to Favicons to History to Prefs, all take this opportunity |
| 85 | to hook into those `WebContents` used as tabs. |
| 86 | |
| 87 | If you are writing a feature that needs to deal with browser tabs, this is where |
| 88 | you go. Create a tab helper, and add it (in alphabetical order, please!) to |
| 89 | `AttachTabHelpers()`. Note, though, that you are _never_ allowed to call |
| 90 | `AttachTabHelpers()` yourself. `AttachTabHelpers()` is only for `WebContents` |
| 91 | that are in browser tabs, and all of those code paths are already written. |
| 92 | |
Elly Fong-Jones | 8ea0fc76 | 2021-01-06 19:14:23 | [diff] [blame] | 93 | ## Reusing tab helpers with non-browser tab WebContentses |
avi | a41f7af | 2015-08-31 19:46:58 | [diff] [blame] | 94 | |
| 95 | Sometimes it's useful to re-use tab helpers for `WebContents`es that aren't |
| 96 | browser tabs. For example, the Chrome Apps code wants to be able to print, and |
| 97 | wants to use the printing code that browser tabs use. So in |
Josip Sokcevic | ba14441 | 2020-09-09 20:57:05 | [diff] [blame] | 98 | [`ChromeAppDelegate::InitWebContents()`](https://source.chromium.org/chromium/chromium/src/+/HEAD:chrome/browser/ui/apps/chrome_app_delegate.cc) |
avi | a41f7af | 2015-08-31 19:46:58 | [diff] [blame] | 99 | we see that whenever the Apps code creates a new `WebContents`, it attaches a |
| 100 | carefully-chosen subset of tab helpers, including two printing ones. |
| 101 | |
| 102 | You can do that too. If you are creating a `WebContents`, make a very deliberate |
| 103 | decision about which tab helpers you need. Chances are, you don't need them all; |
| 104 | you probably only need a handful. In fact, most tab helpers assume they are |
| 105 | attached to browser tabs, so only add the bare minimum. |
| 106 | |
Elly Fong-Jones | 8ea0fc76 | 2021-01-06 19:14:23 | [diff] [blame] | 107 | ## Not every WebContents has every tab helper |
avi | a41f7af | 2015-08-31 19:46:58 | [diff] [blame] | 108 | |
| 109 | The other consequence of this design is that you can't make the assumption that |
| 110 | an arbitrary `WebContents` will have an arbitrary tab helper. The |
| 111 | `WebContents`es used as browser tabs likely will have most tab helpers (though |
| 112 | not necessarily all of them!) but a `WebContents` only has a tab helper if it is |
| 113 | installed on it. |
| 114 | |
| 115 | The deeper (false and dangerous) assumption is that every `WebContents` is a |
| 116 | browser tab. Do not assume that either! |
| 117 | |
| 118 | If your code handles `WebContents`es, be aware of their source. It is extremely |
| 119 | rare to have to be able to handle arbitrary `WebContents`es. Know where they |
| 120 | come from and what tab helpers are on them, and you'll be fine. |