Skip to content

Feature: Basic Firefox compatibility #4479

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

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
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
31 changes: 21 additions & 10 deletions src/js/protocols/WebSerial.js
Original file line number Diff line number Diff line change
Expand Up @@ -333,18 +333,26 @@ class WebSerial extends EventTarget {
// AT32 on macOS requires smaller chunks (63 bytes) to work correctly due to
// USB buffer size limitations in the macOS implementation
const batchWriteSize = 63;
let remainingData = data;
while (remainingData.byteLength > batchWriteSize) {
const sliceData = remainingData.slice(0, batchWriteSize);
remainingData = remainingData.slice(batchWriteSize);

// Ensure data is a Uint8Array for proper slicing
const dataArray = data instanceof Uint8Array ? data : new Uint8Array(data);

let offset = 0;
while (offset + batchWriteSize < dataArray.byteLength) {
const chunk = dataArray.slice(offset, offset + batchWriteSize);
offset += batchWriteSize;
try {
await this.writer.write(sliceData);
await this.writer.write(chunk);
} catch (error) {
console.error(`${logHead} Error writing batch chunk:`, error);
throw error; // Re-throw to be caught by the send method
}
}
await this.writer.write(remainingData);

// Write the remaining data
if (offset < dataArray.byteLength) {
await this.writer.write(dataArray.slice(offset));
}
}

async send(data, callback) {
Expand All @@ -357,14 +365,17 @@ class WebSerial extends EventTarget {
}

try {
// Create a buffer from the data
const buffer = data instanceof ArrayBuffer ? data : new Uint8Array(data).buffer;

if (this.isNeedBatchWrite) {
await this.batchWrite(data);
await this.batchWrite(buffer);
} else {
await this.writer.write(data);
await this.writer.write(new Uint8Array(buffer));
}
this.bytesSent += data.byteLength;
this.bytesSent += buffer.byteLength;

const result = { bytesSent: data.byteLength };
const result = { bytesSent: buffer.byteLength };
if (callback) {
callback(result);
}
Expand Down
46 changes: 25 additions & 21 deletions src/js/tabs/setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import $ from "jquery";
import { ispConnected } from "../utils/connection";
import { sensorTypes } from "../sensor_types";
import { addArrayElementsAfter, replaceArrayElement } from "../utils/array";
import { isFirefoxBrowser } from "../utils/checkBrowserCompatibility";

const setup = {
yaw_fix: 0.0,
Expand Down Expand Up @@ -387,17 +388,19 @@ setup.initialize = function (callback) {
const buildConfig = `<span class="buildInfoBtn" title="${i18n.getMessage(
"initialSetupInfoBuildConfig",
)}: ${buildRoot}/json">
<a href="${buildRoot}/json" target="_blank"><strong>${i18n.getMessage(
"initialSetupInfoBuildConfig",
)}</strong></a></span>`;
<a href="${buildRoot}/json" target="_blank">
<strong>${i18n.getMessage("initialSetupInfoBuildConfig")}</strong>
</a>
</span>`;

// Creates the "Log" button
const buildLog = `<span class="buildInfoBtn" title="${i18n.getMessage(
"initialSetupInfoBuildLog",
)}: ${buildRoot}/log">
<a href="${buildRoot}/log" target="_blank"><strong>${i18n.getMessage(
"initialSetupInfoBuildLog",
)}</strong></a></span>`;
<a href="${buildRoot}/log" target="_blank">
<strong>${i18n.getMessage("initialSetupInfoBuildLog")}</strong>
</a>
</span>`;

// Shows the "Config" and "Log" buttons
build_info_e.html(`${buildConfig} ${buildLog}`);
Expand Down Expand Up @@ -429,19 +432,19 @@ setup.initialize = function (callback) {
// Creates the "Options" button (if possible)
const buildOptions = buildOptionsValid
? `<span class="buildInfoBtn" title="${i18n.getMessage("initialSetupInfoBuildOptionList")}">
<a class="buildOptions" href="#"><strong>${i18n.getMessage(
"initialSetupInfoBuildOptions",
)}</strong></a></span>`
<a class="buildOptions" href="#">
<strong>${i18n.getMessage("initialSetupInfoBuildOptions")}</strong>
</a>
</span>`
: "";

// Creates the "Download" button (if possible)
const buildDownload = buildKeyValid
? `<span class="buildInfoBtn" title="${i18n.getMessage(
"initialSetupInfoBuildDownload",
)}: ${buildRoot}/hex">
<a href="${buildRoot}/hex" target="_blank"><strong>${i18n.getMessage(
"initialSetupInfoBuildDownload",
)}</strong></a></span>`
? `<span class="buildInfoBtn" title="${i18n.getMessage("initialSetupInfoBuildDownload")}: ${buildRoot}/hex">
<a href="${buildRoot}/hex" target="_blank">
<strong>${i18n.getMessage("initialSetupInfoBuildDownload")}</strong>
</a>
</span>`
: "";

// Shows the "Options" and/or "Download" buttons
Expand Down Expand Up @@ -493,13 +496,14 @@ setup.initialize = function (callback) {
}

function showNetworkStatus() {
const isFirefox = isFirefoxBrowser();
const networkStatus = ispConnected();

let statusText = "";

const type = navigator.connection.effectiveType;
const downlink = navigator.connection.downlink;
const rtt = navigator.connection.rtt;
const type = isFirefox ? "NA" : navigator.connection.effectiveType;
const downlink = isFirefox ? "NA" : navigator.connection.downlink;
const rtt = isFirefox ? "NA" : navigator.connection.rtt;

if (!networkStatus || !navigator.onLine || type === "none") {
statusText = i18n.getMessage("initialSetupNetworkInfoStatusOffline");
Expand All @@ -510,9 +514,9 @@ setup.initialize = function (callback) {
}

$(".network-status").text(statusText);
$(".network-type").text(navigator.connection.effectiveType);
$(".network-downlink").text(`${navigator.connection.downlink} Mbps`);
$(".network-rtt").text(navigator.connection.rtt);
$(".network-type").text(type);
$(".network-downlink").text(`${downlink} Mbps`);
$(".network-rtt").text(rtt);
}

prepareDisarmFlags();
Expand Down
31 changes: 23 additions & 8 deletions src/js/utils/checkBrowserCompatibility.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,26 @@ export function getOS() {
}

export function isChromiumBrowser() {
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Browser_detection_using_the_user_agent
if (!navigator.userAgentData) {
// Fallback to traditional userAgent string check
return /Chrome/.test(navigator.userAgent) && /Google Inc/.test(navigator.vendor);
}

// https://learn.microsoft.com/en-us/microsoft-edge/web-platform/user-agent-guidance
return navigator.userAgentData.brands.some((brand) => {
return brand.brand == "Chromium";
});
}

export function isFirefoxBrowser() {
if (navigator.userAgentData) {
return navigator.userAgentData.brands.some((brand) => {
return brand.brand == "Chromium";
return brand.brand == "Firefox";
});
}

// Fallback for older browsers/Android
const ua = navigator.userAgent.toLowerCase();
return ua.includes("chrom") || ua.includes("edg");
// Fallback to traditional userAgent string check
return navigator.userAgent.includes("Firefox");
}

export function isAndroid() {
Expand Down Expand Up @@ -65,15 +76,18 @@ export function checkBrowserCompatibility() {
const isWebBluetooth = checkWebBluetoothSupport();
const isWebUSB = checkWebUSBSupport();
const isChromium = isChromiumBrowser();

const isFirefox = isFirefoxBrowser();
const isNative = Capacitor.isNativePlatform();

const compatible = isNative || (isChromium && (isWebSerial || isWebBluetooth || isWebUSB));
const compatible = isNative || ((isChromium || isFirefox) && (isWebSerial || isWebBluetooth || isWebUSB));

console.log("User Agent: ", navigator.userAgentData);
console.log("Native: ", isNative);
console.log("Chromium: ", isChromium);
console.log("Firefox: ", isFirefox);
console.log("Web Serial: ", isWebSerial);
console.log("Web Bluetooth: ", isWebBluetooth);
console.log("Web USB: ", isWebUSB);
console.log("OS: ", getOS());

console.log("Android: ", isAndroid());
Expand All @@ -86,7 +100,8 @@ export function checkBrowserCompatibility() {

let errorMessage = "";
if (!isChromium) {
errorMessage = "Betaflight app requires a Chromium based browser (Chrome, Chromium, Edge).<br/>";
errorMessage =
"Betaflight app requires a Chromium based browser (Chrome, Chromium, Edge),<br> or Firefox based browser running the <a href='https://addons.mozilla.org/firefox/addon/webserial-for-firefox/'>WebSerial extension</a>.<br/>";
}

if (!isWebBluetooth) {
Expand Down