Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove legacy Safari/Firefox/IE compatibility aids #29010

Draft
wants to merge 4 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 0 additions & 37 deletions src/@types/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,19 +80,10 @@ declare global {
mxMatrixClientPeg: IMatrixClientPeg;
mxReactSdkConfig: DeepReadonly<IConfigOptions>;

// Needed for Safari, unknown to TypeScript
webkitAudioContext: typeof AudioContext;

// https://docs.microsoft.com/en-us/previous-versions/hh772328(v=vs.85)
// we only ever check for its existence, so we can ignore its actual type
MSStream?: unknown;

// https://github.com/microsoft/TypeScript-DOM-lib-generator/issues/1029#issuecomment-869224737
// https://developer.mozilla.org/en-US/docs/Web/API/OffscreenCanvas
OffscreenCanvas?: {
new (width: number, height: number): OffscreenCanvas;
};

mxContentMessages: ContentMessages;
mxToastStore: ToastStore;
mxDeviceListener: DeviceListener;
Expand Down Expand Up @@ -152,31 +143,10 @@ declare global {
fetchWindowIcons?: boolean;
}

interface Document {
// Safari & IE11 only have this prefixed: we used prefixed versions
// previously so let's continue to support them for now
webkitExitFullscreen(): Promise<void>;
msExitFullscreen(): Promise<void>;
readonly webkitFullscreenElement: Element | null;
readonly msFullscreenElement: Element | null;
}

interface Navigator {
userLanguage?: string;
}

interface StorageEstimate {
usageDetails?: { [key: string]: number };
}

interface Element {
// Safari & IE11 only have this prefixed: we used prefixed versions
// previously so let's continue to support them for now
webkitRequestFullScreen(options?: FullscreenOptions): Promise<void>;
msRequestFullscreen(options?: FullscreenOptions): Promise<void>;
// scrollIntoView(arg?: boolean | _ScrollIntoViewOptions): void;
}

// https://github.com/microsoft/TypeScript/issues/28308#issuecomment-650802278
interface AudioWorkletProcessor {
readonly port: MessagePort;
Expand Down Expand Up @@ -235,11 +205,4 @@ declare global {
var mx_rage_store: IndexedDBLogStore;
}

// add method which is missing from the node typing
declare module "url" {
interface Url {
format(): string;
}
}

/* eslint-enable @typescript-eslint/naming-convention */
45 changes: 15 additions & 30 deletions src/audio/Playback.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,36 +164,21 @@ export class Playback extends EventEmitter implements IDestroyable, PlaybackInte
this.element.src = URL.createObjectURL(new Blob([this.buf]));
await deferred.promise; // make sure the audio element is ready for us
} else {
// Safari compat: promise API not supported on this function
this.audioBuf = await new Promise((resolve, reject) => {
this.context.decodeAudioData(
this.buf,
(b) => resolve(b),
async (e): Promise<void> => {
try {
// This error handler is largely for Safari as well, which doesn't support Opus/Ogg
// very well.
logger.error("Error decoding recording: ", e);
logger.warn("Trying to re-encode to WAV instead...");

const wav = await decodeOgg(this.buf);

// noinspection ES6MissingAwait - not needed when using callbacks
this.context.decodeAudioData(
wav,
(b) => resolve(b),
(e) => {
logger.error("Still failed to decode recording: ", e);
reject(e);
},
);
} catch (e) {
logger.error("Caught decoding error:", e);
reject(e);
}
},
);
});
try {
this.audioBuf = await this.context.decodeAudioData(this.buf);
} catch (e) {
logger.error("Error decoding recording:", e);
logger.warn("Trying to re-encode to WAV instead...");

try {
// This error handler is largely for Safari, which doesn't support Opus/Ogg very well.
const wav = await decodeOgg(this.buf);
this.audioBuf = await this.context.decodeAudioData(wav);
} catch (e) {
logger.error("Error decoding recording:", e);
throw e;
}
}

// Update the waveform to the real waveform once we have channel data to use. We don't
// exactly trust the user-provided waveform to be accurate...
Expand Down
5 changes: 0 additions & 5 deletions src/audio/compat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,6 @@ import { SAMPLE_RATE } from "./VoiceRecording";
export function createAudioContext(opts?: AudioContextOptions): AudioContext {
if (window.AudioContext) {
return new AudioContext(opts);
} else if (window.webkitAudioContext) {
// While the linter is correct that "a constructor name should not start with
// a lowercase letter", it's also wrong to think that we have control over this.
// eslint-disable-next-line new-cap
return new window.webkitAudioContext(opts);
} else {
throw new Error("Unsupported browser");
}
Expand Down
10 changes: 2 additions & 8 deletions src/components/views/elements/LanguageDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -105,14 +105,8 @@ export default class LanguageDropdown extends React.Component<IProps, IState> {

// default value here too, otherwise we need to handle null / undefined
// values between mounting and the initial value propagating
let language = SettingsStore.getValue("language", null, /*excludeDefault:*/ true);
let value: string | undefined;
if (language) {
value = this.props.value || language;
} else {
language = navigator.language || navigator.userLanguage;
value = this.props.value || language;
}
const language = SettingsStore.getValue("language", null, /*excludeDefault:*/ true);
const value = this.props.value ?? language ?? navigator.language;

return (
<Dropdown
Expand Down
10 changes: 2 additions & 8 deletions src/components/views/elements/SpellCheckLanguagesDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -107,14 +107,8 @@ export default class SpellCheckLanguagesDropdown extends React.Component<

// default value here too, otherwise we need to handle null / undefined;
// values between mounting and the initial value propagating
let language = SettingsStore.getValue("language", null, /*excludeDefault:*/ true);
let value: string | undefined;
if (language) {
value = this.props.value || language;
} else {
language = navigator.language || navigator.userLanguage;
value = this.props.value || language;
}
const language = SettingsStore.getValue("language", null, /*excludeDefault:*/ true);
const value = this.props.value ?? language ?? navigator.language;

return (
<Dropdown
Expand Down
33 changes: 5 additions & 28 deletions src/components/views/voip/LegacyCallView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,29 +65,6 @@ interface IState {
sidebarShown: boolean;
}

function getFullScreenElement(): Element | null {
return (
document.fullscreenElement ||
// moz omitted because firefox supports this unprefixed now (webkit here for safari)
document.webkitFullscreenElement ||
document.msFullscreenElement
);
}

function requestFullscreen(element: Element): void {
const method =
element.requestFullscreen ||
// moz omitted since firefox supports unprefixed now
element.webkitRequestFullScreen ||
element.msRequestFullscreen;
if (method) method.call(element);
}

function exitFullscreen(): void {
const exitMethod = document.exitFullscreen || document.webkitExitFullscreen || document.msExitFullscreen;
if (exitMethod) exitMethod.call(document);
}

export default class LegacyCallView extends React.Component<IProps, IState> {
private dispatcherRef?: string;
private contentWrapperRef = createRef<HTMLDivElement>();
Expand Down Expand Up @@ -119,8 +96,8 @@ export default class LegacyCallView extends React.Component<IProps, IState> {
}

public componentWillUnmount(): void {
if (getFullScreenElement()) {
exitFullscreen();
if (document.fullscreenElement) {
document.exitFullscreen();
}

document.removeEventListener("keydown", this.onNativeKeyDown);
Expand Down Expand Up @@ -159,9 +136,9 @@ export default class LegacyCallView extends React.Component<IProps, IState> {
return;
}
if (payload.fullscreen) {
requestFullscreen(this.contentWrapperRef.current);
} else if (getFullScreenElement()) {
exitFullscreen();
this.contentWrapperRef.current?.requestFullscreen();
} else if (document.fullscreenElement) {
document.exitFullscreen();
}
break;
}
Expand Down
3 changes: 1 addition & 2 deletions src/languageHandler.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -529,8 +529,7 @@ export async function getAllLanguagesWithLabels(): Promise<Language[]> {

export function getLanguagesFromBrowser(): readonly string[] {
if (navigator.languages && navigator.languages.length) return navigator.languages;
if (navigator.language) return [navigator.language];
return [navigator.userLanguage || "en"];
return [navigator.language ?? "en"];
}

export function getLanguageFromBrowser(): string {
Expand Down
6 changes: 3 additions & 3 deletions test/unit-tests/audio/Playback-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ describe("Playback", () => {
beforeEach(() => {
jest.spyOn(logger, "error").mockRestore();
mockAudioBuffer.getChannelData.mockClear().mockReturnValue(mockChannelData);
mockAudioContext.decodeAudioData.mockReset().mockImplementation((_b, callback) => callback(mockAudioBuffer));
mockAudioContext.decodeAudioData.mockReset().mockResolvedValue(mockAudioBuffer);
mockAudioContext.resume.mockClear().mockResolvedValue(undefined);
mockAudioContext.suspend.mockClear().mockResolvedValue(undefined);
mocked(decodeOgg).mockClear().mockResolvedValue(new ArrayBuffer(1));
Expand Down Expand Up @@ -131,8 +131,8 @@ describe("Playback", () => {
const buffer = new ArrayBuffer(8);
const decodingError = new Error("test");
mockAudioContext.decodeAudioData
.mockImplementationOnce((_b, _callback, error) => error(decodingError))
.mockImplementationOnce((_b, callback) => callback(mockAudioBuffer));
.mockRejectedValueOnce(decodingError)
.mockResolvedValueOnce(mockAudioBuffer);

const playback = new Playback(buffer);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ describe("<RecordingPlayback />", () => {
beforeEach(() => {
jest.spyOn(logger, "error").mockRestore();
mockAudioBuffer.getChannelData.mockClear().mockReturnValue(mockChannelData);
mockAudioContext.decodeAudioData.mockReset().mockImplementation((_b, callback) => callback(mockAudioBuffer));
mockAudioContext.decodeAudioData.mockReset().mockResolvedValue(mockAudioBuffer);
mocked(createAudioContext).mockReturnValue(mockAudioContext as unknown as AudioContext);
});

Expand Down
Loading