index.rst (15421B)
1 Touch Bar 2 ========= 3 4 The Touch Bar is a hardware component on some MacBook Pros released from 2016. 5 It is a display above the keyboard that allows more flexible types of 6 input than is otherwise possible with a normal keyboard. Apple offers Touch Bar 7 APIs so developers can extend the Touch Bar to display inputs specific to their 8 application. Firefox consumes these APIs to offer a customizable row of inputs 9 in the Touch Bar. 10 11 In Apple's documentation, the term "the Touch Bar" refers to the hardware. 12 The term "a Touch Bar" refers not to the hardware but to a collection of inputs 13 shown on the Touch Bar. This means that there can be multiple "Touch Bars" that 14 switch out as the user switches contexts. The same naming convention is used in 15 this document. 16 17 In this document and in the code, the word "input" is used to refer to 18 an interactive element in the Touch Bar. It is often interchangeable with 19 "button", but "input" can also refer to any element displayed in the Touch Bar. 20 21 The Touch Bar should never offer functionality unavailable to Firefox users 22 without the Touch Bar. Most macOS Firefox users do not have the Touch Bar and 23 some choose to disable it. Apple's own `Human Interface Guidelines`_ (HIG) 24 forbids this kind of Touch Bar functionality. Please read the HIG for more 25 design considerations before you plan on implementing a new Touch Bar feature. 26 27 If you have questions about the Touch Bar that are not answered in this 28 document, feel free to reach out to `Harry Twyford`_ (:harry on Slack). 29 He wrote this document and Firefox's initial Touch Bar implementation. 30 31 .. _Human Interface Guidelines: https://developer.apple.com/design/human-interface-guidelines/macos/touch-bar/touch-bar-overview/ 32 33 .. _Harry Twyford: mailto:harry@mozilla.com 34 35 .. contents:: Table of Contents 36 37 Overview 38 ~~~~~~~~ 39 40 Firefox's Touch Bar implementation is equal parts JavaScript and Cocoa 41 (Objective-C++). The JavaScript code lives in ``browser/components/touchbar`` 42 and the Cocoa code lives in ``widget/cocoa``, mostly in ``nsTouchBar.mm``. The 43 Cocoa code is a consumer of Apple's Touch Bar APIs and defines what types of 44 Touch Bar inputs are available to its own consumers. The JS code in 45 ``browser/components/touchbar`` provides services to ``nsTouchBar.mm`` and 46 defines what inputs the user actually sees in the Touch Bar. There is two-way 47 communication between the JS and the Cocoa: the Cocoa code asks the JS what 48 inputs it should display, and the JS asks the Cocoa code to update those inputs 49 when needed. 50 51 JavaScript API 52 ~~~~~~~~~~~~~~ 53 54 ``browser/components/touchbar/MacTouchBar.sys.mjs`` defines what specific inputs are 55 available to the user, what icon they will have, what action they will perform, 56 and so on. Inputs are defined in the ``gBuiltInInputs`` object `in that file`_. 57 When creating a new object in ``gBuiltInInputs``, the available properties are 58 documented in the JSDoc for ``TouchBarInput``: 59 60 .. code:: JavaScript 61 62 /** 63 * A representation of a Touch Bar input. 64 * @param {string} input.title 65 * The lookup key for the button's localized text title. 66 * @param {string} input.image 67 * A URL pointing to an SVG internal to Firefox. 68 * @param {string} input.type 69 * The type of Touch Bar input represented by the object. 70 * Must be a value from kInputTypes. 71 * @param {Function} input.callback 72 * A callback invoked when a touchbar item is touched. 73 * @param {string} [input.color] 74 * A string in hex format specifying the button's background color. 75 * If omitted, the default background color is used. 76 * @param {bool} [input.disabled] 77 * If `true`, the Touch Bar input is greyed out and inoperable. 78 * @param {Array} [input.children] 79 * An array of input objects that will be displayed as children of 80 * this input. Available only for types KInputTypes.POPOVER and 81 * kInputTypes.SCROLLVIEW. 82 */ 83 84 Clarification on some of these properties is warranted. 85 86 * ``title`` is the key to a Fluent translation defined in ``browser/locales/<LOCALE>/browser/touchbar/touchbar.ftl``. 87 * ``type`` must be a value from the ``kInputTypes`` enum in ``MacTouchBar.sys.mjs``. 88 For example, ``kInputTypes.BUTTON``. More information on input types follows 89 below. 90 * ``callback`` points to a JavaScript function. Any chrome-level JavaScript can 91 be executed. ``execCommand`` is a convenience method in ``MacTouchBar.sys.mjs`` 92 that takes a XUL command as a string and executes that command. For instance, 93 one input sets ``callback`` to ``execCommand("Browser:Back")``. 94 * ``children`` is an array of objects with the same properties as members of 95 ``gBuiltInInputs``. When used with an input of type 96 ``kInputTypes.SCROLLVIEW``, ``children`` can only contain inputs of type 97 ``kInputTypes.BUTTON``. When used with an input of type 98 ``kInputTypes.POPOVER``, any input type except another ``kInputTypes.POPOVER`` 99 can be used. 100 101 .. _in that file: https://searchfox.org/mozilla-central/rev/669fac9888b173c02baa4c036e980c0c204dfe02/browser/components/touchbar/MacTouchBar.sys.mjs#76 102 103 Input types 104 ----------- 105 106 Button 107 A simple button. If ``image`` is not specified, the buttons displays the text 108 label from ``title``. If both ``image`` and ``title`` are specified, only the 109 ``image`` is shown. The action specified in ``callback`` is executed when the 110 button is pressed. 111 112 .. caution:: 113 114 Even if the ``title`` will not be shown in the Touch Bar, you must still 115 define a ``title`` property. 116 117 Main Button 118 Similar to a button, but displayed at double the width. A main button 119 displays both the string in ``title`` and the icon in ``image``. Only one 120 main button should be shown in the Touch Bar at any time, although this is 121 not enforced. 122 123 Label 124 A non-interactive text label. This input takes only the attributes ``title`` 125 and ``type``. 126 127 Popover 128 Initially represented in the Touch Bar as a button, a popover will display an 129 entirely different set of inputs when pressed. These different inputs should 130 be defined in the ``children`` property of the parent. Popovers can also be 131 shown and hidden programmatically, by calling 132 133 .. code:: JavaScript 134 135 gTouchBarUpdater.showPopover( 136 TouchBarHelper.baseWindow, 137 [POPOVER], 138 {true | false} 139 ); 140 141 where the second argument is a reference to a popover TouchBarInput and 142 the third argument is whether the popover should be shown or hidden. 143 144 Scroll View 145 A Scroll View is a scrolling list of buttons. The buttons should be defined 146 in the Scroll View's ``children`` array. 147 148 .. note:: 149 150 In Firefox, a list of search shortcuts appears in the Touch Bar when the 151 address bar is focused. This is an example of a ScrollView contained within 152 a popover. The popover is opened programmatically with 153 ``gTouchBarUpdater.showPopover`` when the address bar is focused and it is 154 hidden when the address bar is blurred. 155 156 Examples 157 -------- 158 Some examples of ``gBuiltInInputs`` objects follow. 159 160 A simple button 161 162 .. code:: JavaScript 163 164 Back: { 165 title: "back", 166 image: "chrome://browser/skin/back.svg", 167 type: kInputTypes.BUTTON, 168 callback: () => execCommand("Browser:Back", "Back"), 169 }, 170 171 A button is defined with a title, icon, type, and a callback. The callback 172 simply calls the XUL command to go back. 173 174 The search popover 175 This is the input that occupies the Touch Bar when the address bar is focused. 176 177 .. code:: JavaScript 178 179 SearchPopover: { 180 title: "search-popover", 181 image: "chrome://global/skin/icons/search-glass.svg", 182 type: kInputTypes.POPOVER, 183 children: { 184 SearchScrollViewLabel: { 185 title: "search-search-in", 186 type: kInputTypes.LABEL, 187 }, 188 SearchScrollView: { 189 key: "search-scrollview", 190 type: kInputTypes.SCROLLVIEW, 191 children: { 192 Bookmarks: { 193 title: "search-bookmarks", 194 type: kInputTypes.BUTTON, 195 callback: () => 196 gTouchBarHelper.insertRestrictionInUrlbar( 197 UrlbarTokenizer.RESTRICT.BOOKMARK 198 ), 199 }, 200 History: { 201 title: "search-history", 202 type: kInputTypes.BUTTON, 203 callback: () => 204 gTouchBarHelper.insertRestrictionInUrlbar( 205 UrlbarTokenizer.RESTRICT.HISTORY 206 ), 207 }, 208 OpenTabs: { 209 title: "search-opentabs", 210 type: kInputTypes.BUTTON, 211 callback: () => 212 gTouchBarHelper.insertRestrictionInUrlbar( 213 UrlbarTokenizer.RESTRICT.OPENPAGE 214 ), 215 }, 216 Tags: { 217 title: "search-tags", 218 type: kInputTypes.BUTTON, 219 callback: () => 220 gTouchBarHelper.insertRestrictionInUrlbar( 221 UrlbarTokenizer.RESTRICT.TAG 222 ), 223 }, 224 }, 225 }, 226 }, 227 }, 228 229 At the top level, a Popover is defined. This allows a collection of children 230 to be shown in a separate Touch Bar. The Popover has two children: a Label, 231 and a Scroll View. The Scroll View displays five similar buttons that call a 232 helper method to insert search shortcut symbols into the address bar. 233 234 Adding a new input 235 ------------------ 236 Adding a new input is easy: just add a new object to ``gBuiltInInputs``. This 237 will make the input available in the Touch Bar customization window (accessible 238 from the Firefox menu bar item). 239 240 If you want to to add your new input to the default set, add its identifier 241 here_, where ``type`` is a value from ``kAllowedInputTypes`` in that 242 file and ``key`` is the value you set for ``title`` in ``gBuiltInInputs``. 243 You should request approval from UX before changing the default set of inputs. 244 245 .. _here: https://searchfox.org/mozilla-central/rev/ebe492edacc75bb122a2b380e4cafcca3470864c/widget/cocoa/nsTouchBar.mm#100 246 247 If you are interested in adding new features to Firefox's implementation of the 248 Touch Bar API, read on! 249 250 251 Cocoa API 252 ~~~~~~~~~ 253 Firefox implements Apple's Touch Bar API in its Widget: Cocoa code with an 254 ``nsTouchBar`` class. ``nsTouchBar`` interfaces between Apple's Touch Bar API 255 and the ``TouchBarHelper`` JavaScript API. 256 257 The best resource to understand the Touch Bar API is Apple's 258 `official documentation`_. This documentation will cover how Firefox implements 259 these APIs and how one might extend ``nsTouchBar`` to enable new Touch Bar 260 features. 261 262 Every new Firefox window initializes ``nsTouchBar`` (link_). The function 263 ``makeTouchBar`` is looked for automatically on every new instance of an 264 ``NSWindow*``. If ``makeTouchBar`` is defined, that window will own a new 265 instance of ``nsTouchBar``. 266 267 At the time of this writing, every window initializes ``nsTouchBar`` with a 268 default set of inputs. In the future, Firefox windows other than the main 269 browser window (such as the Library window or DevTools) may initialize 270 ``nsTouchBar`` with a different set of inputs. 271 272 ``nsTouchBar`` has two different initialization methods: ``init`` and 273 ``initWithInputs``. The former is a convenience method for the latter, calling 274 ``initWithInputs`` with a nil argument. When that happens, a Touch Bar is 275 created containing a default set of inputs. ``initWithInputs`` can also take an 276 ``NSArray<TouchBarInput*>*``. In that case, a non-customizable Touch Bar will be 277 initialized with only those inputs available. 278 279 .. _official documentation: https://developer.apple.com/documentation/appkit/nstouchbar?language=objc 280 .. _link: https://searchfox.org/mozilla-central/rev/ebe492edacc75bb122a2b380e4cafcca3470864c/widget/cocoa/nsCocoaWindow.mm#2877 281 282 NSTouchBarItemIdentifiers 283 ------------------------- 284 The architecture of the Touch Bar is based largely around an ``NSString*`` 285 wrapper class called ``NSTouchBarItemIdentifier``. Every input in the Touch Bar 286 has a unique ``NSTouchBarItemIdentifier``. They are structured in reverse-URI 287 format like so: 288 289 ``com.mozilla.firefox.touchbar.[TYPE].[KEY]`` 290 291 [TYPE] is a string indicating the type of the input, e.g. "button". If an 292 input is a child of another input, the parent's type is prepended to the child's 293 type, e.g. "scrubber.button" indicates a button contained in a scrubber. 294 295 [KEY] is the ``title`` attribute defined for that input on the JS side. 296 297 If you need to generate an identifier, use the convenience method 298 ``[TouchBarInput nativeIdentifierWithType:withKey:]``. 299 300 .. caution:: 301 302 Do not create a new input that would have the same identifier as any other 303 input. All identifiers must be unique. 304 305 .. warning:: 306 307 ``NSTouchBarItemIdentifier`` `is used in one other place`_: setting 308 ``customizationIdentifier``. Do not ever change this string. If it is changed, 309 any customizations users have made to the layout of their Touch Bar in Firefox 310 will be erased. 311 312 Each identifier is tied to a ``TouchBarInput``. ``TouchBarInput`` is a class 313 that holds the properties specified for each input in ``gBuiltInInputs``. 314 ``nsTouchBar`` uses them to create instances of ``NSTouchBarItem`` 315 which are the actual objects used by Apple's Touch Bar API and displayed in the 316 Touch Bar. It is important to understand the difference between 317 ``TouchBarInput`` and ``NSTouchBarItem``! 318 319 .. _is used in one other place: https://searchfox.org/mozilla-central/rev/ebe492edacc75bb122a2b380e4cafcca3470864c/widget/cocoa/nsTouchBar.mm#71 320 321 TouchBarInput creation flow 322 --------------------------- 323 Creating a Touch Bar and its ``TouchBarInputs`` flows as follows: 324 325 #. ``[nsTouchBar init]`` is called from ``[NSWindow makeTouchBar]``. 326 327 #. ``init`` populates two NSArrays: ``customizationAllowedItemIdentifiers`` and 328 ``defaultItemIdentifiers``. It also initializes a ``TouchBarInput`` object 329 for every element in the union of the two arrays and stores them in 330 ``NSMutableDictionary<NSTouchBarItemIdentifier, TouchBarInput*>* mappedLayoutItems``. 331 332 #. ``touchBar:makeItemForIdentifier:`` is called for every element in the union 333 of the two arrays of identifiers. This method retrieves the ``TouchBarInput`` 334 for the given identifier and uses it to initialize a ``NSTouchBarItem``. 335 ``touchBar:makeItemForIdentifier:`` reads the ``type`` attribute from the 336 ``TouchBarInput`` to determine what ``NSTouchBarItem`` subclass should be 337 initialized. Our Touch Bar code currently supports ``NSCustomTouchBarItem`` 338 (buttons, main buttons); ``NSPopoverTouchBarItem`` (popovers); 339 ``NSTextField`` (labels); and ``NSScrollView`` (ScrollViews). 340 341 #. Once the ``NSTouchBarItem`` is initialized, its properties are populated with 342 an assortment of "update" methods. These include ``updateButton``, 343 ``updateMainButton``, ``updateLabel``, ``updatePopover``, and 344 ``updateScrollView``. 345 346 #. Since the localization of ``TouchBarInput`` titles happens asynchronously in 347 JavaScript code, the l10n callback executes 348 ``[nsTouchBarUpdater updateTouchBarInputs:]``. This method reads the 349 identifier of the input(s) that need to be updated and calls their respective 350 "update" methods. This method is most often used to update ``title`` after 351 l10n is complete. It can also be used to update any property of a 352 ``TouchBarInput``; for instance, one might wish to change ``color`` 353 when a specific event occurs in the browser.