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