Skip to content

Commit 3ab4e06

Browse files
committed
Fix presentation and captions buttons for new Meet UI changes
1 parent 0e5390a commit 3ab4e06

7 files changed

Lines changed: 112 additions & 151 deletions

browser-extension/event_handlers/aria_pressed_based_toggle_event_handler.js

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,18 @@
44
*/
55
class AriaPressedBasedToggleEventHandler extends ToggleEventHandler {
66

7-
// Subclasses should set this to the `jsname` attribute value on the target toggle button in the Meet UI.
8-
static ButtonJsName;
7+
// Subclasses should set this to an array of `jsname` attribute value(s) on the target toggle button in the Meet UI.
8+
// We'll check all of them and take the first result we find.
9+
static ButtonJsNames;
910

1011
_controlElementSelector = () => {
11-
return document.querySelector(`button[jsname="${this.constructor.ButtonJsName}"]`);
12+
for (var i = 0; i < this.constructor.ButtonJsNames.length; i++) {
13+
const button = document.querySelector(`button[jsname="${this.constructor.ButtonJsNames[i]}"]`);
14+
if (button) {
15+
return button;
16+
}
17+
}
18+
return null;
1219
}
1320

1421
_isElementMuted = (element) => {
@@ -26,7 +33,7 @@ class AriaPressedBasedToggleEventHandler extends ToggleEventHandler {
2633
for (const mutation of mutationsList) {
2734
if (mutation.type === "attributes" && mutation.attributeName === "aria-pressed") {
2835
const jsName = mutation.target.attributes["jsname"]?.value;
29-
if (jsName === this.constructor.ButtonJsName) {
36+
if (this.constructor.ButtonJsNames.includes(jsName)) {
3037
this._sendMuteState();
3138
}
3239
}
Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,22 @@
1-
class CaptionsEventHandler extends AriaPressedBasedToggleEventHandler {
1+
class CaptionsEventHandler extends IconBasedEventHandler {
22

3-
static ButtonJsName = "r8qRAd";
3+
/**
4+
* When captions are off, we see:
5+
* <i class="quRWN-Bz112c google-symbols notranslate" aria-hidden="true" data-google-symbols-override="true">closed_caption_off</i>
6+
* When captions are on, we see:
7+
* <i class="quRWN-Bz112c google-symbols notranslate" aria-hidden="true" data-google-symbols-override="true">closed_caption</i>
8+
*/
9+
static MutedIconHTML = "closed_caption_off";
410

5-
_sendMuteState = () => {
6-
this._sendSimpleMuteStateUpdate("captionsMutedState");
7-
}
11+
static MuteStateUpdateEventName = "captionsMutedState";
12+
13+
static ToggleEventName = "toggleCaptions";
14+
15+
static GetMuteStateEventName = "getCaptionsState";
816

9-
handleStreamDeckEvent = (message) => {
10-
if (message.event === "toggleCaptions") {
11-
this._toggleMute();
12-
} else if (message.event === "getCaptionsState") {
13-
this._sendMuteState();
14-
}
17+
_controlElementSelector = () => {
18+
return document.querySelector('button[jsname="RrG0hf"]') // From approximately Feb 2026 onwards
19+
|| document.querySelector('button[jsname="r8qRAd"]'); // Before Feb 2026 redesign
1520
}
1621

1722
}

browser-extension/event_handlers/hand_event_handler.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
class HandEventHandler extends AriaPressedBasedToggleEventHandler {
22

3-
static ButtonJsName = "FpSaz";
3+
static ButtonJsNames = ["FpSaz"];
44

55
_sendMuteState = () => {
66
this._sendSimpleMuteStateUpdate("handMutedState");
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/**
2+
* An abstract base class used for toggle controls in the Meet UI that indicate whether or not
3+
* they're active using an icon that Meet puts in an <i> tag.
4+
*/
5+
class IconBasedEventHandler extends ToggleEventHandler {
6+
7+
// Subclasses should set this to the content of the <i> tag when the target is "off"/"muted".
8+
static MutedIconHTML;
9+
10+
// Subclasses should set this to the name of the event from our Stream Deck plugin
11+
// that's used to inform the plugin of the current mute state.
12+
static MuteStateUpdateEventName;
13+
14+
// Subclasses should set this to the name of the event from our Stream Deck plugin
15+
// that's used to instruct this browser extension to change the target button's state.
16+
static ToggleEventName;
17+
18+
// Subclasses should set this to the name of the event from our Stream Deck plugin
19+
// that's used by the plugin to request the current mute state.
20+
static GetMuteStateEventName;
21+
22+
_isElementMuted = (element) => {
23+
if (!element) {
24+
return true;
25+
}
26+
27+
const iTags = element.querySelectorAll('i');
28+
for (var i = 0; i < iTags.length; i++) {
29+
if (iTags[i].getHTML() == this.constructor.MutedIconHTML) {
30+
return true;
31+
}
32+
}
33+
return false;
34+
}
35+
36+
_getControlElement = () => {
37+
/**
38+
* These buttons are not visible under all circumstances (e.g. in the preview before joining
39+
* a call) so don't throw an error if the button is not found.
40+
*/
41+
return this._controlElementSelector();
42+
};
43+
44+
_sendMuteState = () => {
45+
this._sendSimpleMuteStateUpdate(this.constructor.MuteStateUpdateEventName);
46+
}
47+
48+
handleStreamDeckEvent = (message) => {
49+
if (message.event === this.constructor.ToggleEventName) {
50+
this._toggleMute();
51+
} else if (message.event === this.constructor.GetMuteStateEventName) {
52+
this._sendMuteState();
53+
}
54+
}
55+
56+
_registerMutationObserver = () => {
57+
const observer = new MutationObserver(this._handleControlChange);
58+
observer.observe(document.body, {
59+
childList: false,
60+
attributes: true,
61+
attributeFilter: ["aria-label"],
62+
attributeOldValue: true,
63+
subtree: true,
64+
});
65+
}
66+
67+
}

browser-extension/event_handlers/label_based_toggle_event_handler.js

Lines changed: 0 additions & 79 deletions
This file was deleted.
Lines changed: 16 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,18 @@
1-
class PinPresentationEventHandler extends ToggleEventHandler {
1+
class PinPresentationEventHandler extends IconBasedEventHandler {
2+
3+
/**
4+
* When the presentation is pinned, we see an Unpin icon:
5+
* <i class="google-material-icons VfPpkd-kBDsod" aria-hidden="true">keep_off</i>
6+
* When the presentation is not pinned, we see a Pin icon:
7+
* <i class="google-material-icons VfPpkd-kBDsod" aria-hidden="true">keep_outline</i>
8+
*/
9+
static MutedIconHTML = "keep_outline";
10+
11+
static MuteStateUpdateEventName = "pinPresentationMutedState";
12+
13+
static ToggleEventName = "togglePinPresentation";
14+
15+
static GetMuteStateEventName = "getPinPresentationState";
216

317
_controlElementSelector = () => {
418
/**
@@ -8,57 +22,4 @@ class PinPresentationEventHandler extends ToggleEventHandler {
822
return document.querySelector('button[jsname="fniDcc"]');
923
}
1024

11-
_isElementMuted = (element) => {
12-
// "muted" means there's no pinned presentation.
13-
if (!element) {
14-
return true;
15-
}
16-
17-
/**
18-
* When the presentation is pinned, we see an Unpin icon:
19-
* <i class="google-material-icons VfPpkd-kBDsod" aria-hidden="true">keep_off</i>
20-
* When the presentation is not pinned, we see a Pin icon:
21-
* <i class="google-material-icons VfPpkd-kBDsod" aria-hidden="true">keep_outline</i>
22-
*/
23-
const iTags = element.querySelectorAll('i');
24-
for (var i = 0; i < iTags.length; i++) {
25-
if (iTags[i].getHTML().includes('keep_outline')) {
26-
return true;
27-
}
28-
}
29-
return false;
30-
}
31-
32-
_getControlElement = () => {
33-
/**
34-
* Unlike most of our controls, the Pin Presentation button is _not_ expected
35-
* to be available in all meetings. There might be no one presenting. So unlike
36-
* for normal toggleables, don't throw an error here.
37-
*/
38-
return this._controlElementSelector();
39-
};
40-
41-
_sendMuteState = () => {
42-
this._sendSimpleMuteStateUpdate("pinPresentationMutedState");
43-
}
44-
45-
handleStreamDeckEvent = (message) => {
46-
if (message.event === "togglePinPresentation") {
47-
this._toggleMute();
48-
} else if (message.event === "getPinPresentationState") {
49-
this._sendMuteState();
50-
}
51-
}
52-
53-
_registerMutationObserver = () => {
54-
const observer = new MutationObserver(this._handleControlChange);
55-
observer.observe(document.body, {
56-
childList: false,
57-
attributes: true,
58-
attributeFilter: ["data-requested-participant-id"],
59-
attributeOldValue: true,
60-
subtree: true,
61-
});
62-
}
63-
64-
}
25+
}

browser-extension/manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@
1515
"js": [
1616
"event_handlers/base_event_handler.js",
1717
"event_handlers/toggle_event_handler.js",
18-
"event_handlers/label_based_toggle_event_handler.js",
1918
"event_handlers/aria_pressed_based_toggle_event_handler.js",
19+
"event_handlers/icon_based_toggle_event_handler.js",
2020
"event_handlers/side_panel_event_handler.js",
2121
"event_handlers/camera_event_handler.js",
2222
"event_handlers/chat_event_handler.js",

0 commit comments

Comments
 (0)