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