1
0
mirror of https://github.com/excalidraw/excalidraw.git synced 2024-11-02 03:25:53 +01:00

fix: show error message when measureText API breaks in brave (#6336)

* fix: show error message when measureText API breaks in brave

* Add docs

* Add assets

* tweak message

* fix

* tweak message

* add translations

* lint

* fix

* fix

* lint

* lint

* lint please work now

* tweak doc

* fix

* split error component to new file

* add specs

* tweaks

Co-authored-by: David Luzar <luzar.david@gmail.com>

* wrap in div with a width of 30rem

* fix spec

* fix

* Fix typo

---------

Co-authored-by: David Luzar <luzar.david@gmail.com>
Co-authored-by: Daniel J. Geiger <1852529+DanielJGeiger@users.noreply.github.com>
This commit is contained in:
Aakansha Doshi 2023-03-13 19:46:09 +05:30 committed by GitHub
parent 1eee488dab
commit 023313e92f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 244 additions and 23 deletions

1
.gitignore vendored

@ -25,3 +25,4 @@ src/packages/excalidraw/types
src/packages/excalidraw/example/public/bundle.js
src/packages/excalidraw/example/public/excalidraw-assets-dev
src/packages/excalidraw/example/public/excalidraw.development.js
coverage

@ -4,6 +4,34 @@
No, Excalidraw package doesn't come with collaboration built in, since the implementation is specific to each host app. We expose APIs which you can use to communicate with Excalidraw which you can use to implement it. You can check our own implementation [here](https://github.com/excalidraw/excalidraw/blob/master/src/excalidraw-app/index.tsx). Here is a [detailed answer](https://github.com/excalidraw/excalidraw/discussions/3879#discussioncomment-1110524) on how you can achieve the same.
### Turning off Aggressive Anti-Fingerprinting in Brave browser
When *Aggressive Anti-Fingerprinting* is turned on, the `measureText` API breaks which in turn breaks the Text Elements in your drawings. Here is more [info](https://github.com/excalidraw/excalidraw/pull/6336) on the same.
We strongly recommend turning it off. You can follow the steps below on how to do so.
1. Open [excalidraw.com](https://excalidraw.com) in Brave and click on the **Shield** button
![Shield button](../../assets/brave-shield.png)
<div style={{width:'30rem'}}>
2. Once opened, look for **Aggressively Block Fingerprinting**
![Aggresive block fingerprinting](../../assets/aggressive-block-fingerprint.png)
3. Switch to **Block Fingerprinting**
![Block filtering](../../assets/block-fingerprint.png)
4. Thats all. All text elements should be fixed now 🎉
</div>
If disabling this setting doesn't fix the display of text elements, please consider opening an [issue](https://github.com/excalidraw/excalidraw/issues/new) on our GitHub, or message us on [Discord](https://discord.gg/UexuTaE).
## Need help?
Check out the existing [Q&A](https://github.com/excalidraw/excalidraw/discussions?discussions_q=label%3Apackage%3Aexcalidraw). If you have any queries or need help, ask us [here](https://github.com/excalidraw/excalidraw/discussions?discussions_q=label%3Apackage%3Aexcalidraw).

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 266 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

@ -0,0 +1,45 @@
import ReactDOM from "react-dom";
import * as Renderer from "../renderer/renderScene";
import { reseed } from "../random";
import { render, queryByTestId } from "../tests/test-utils";
import ExcalidrawApp from "../excalidraw-app";
const renderScene = jest.spyOn(Renderer, "renderScene");
describe("Test <App/>", () => {
beforeEach(async () => {
// Unmount ReactDOM from root
ReactDOM.unmountComponentAtNode(document.getElementById("root")!);
localStorage.clear();
renderScene.mockClear();
reseed(7);
});
it("should show error modal when using brave and measureText API is not working", async () => {
(global.navigator as any).brave = {
isBrave: {
name: "isBrave",
},
};
const originalContext = global.HTMLCanvasElement.prototype.getContext("2d");
//@ts-ignore
global.HTMLCanvasElement.prototype.getContext = (contextId) => {
return {
...originalContext,
measureText: () => ({
width: 0,
}),
};
};
await render(<ExcalidrawApp />);
expect(
queryByTestId(
document.querySelector(".excalidraw-modal-container")!,
"brave-measure-text-error",
),
).toMatchSnapshot();
});
});

@ -62,6 +62,7 @@ import {
GRID_SIZE,
IMAGE_RENDER_TIMEOUT,
isAndroid,
isBrave,
LINE_CONFIRM_THRESHOLD,
MAX_ALLOWED_FILE_BYTES,
MIME_TYPES,
@ -267,6 +268,7 @@ import {
getContainerDims,
getContainerElement,
getTextBindableContainerAtPosition,
isMeasureTextSupported,
isValidTextContainer,
} from "../element/textElement";
import { isHittingElementNotConsideringBoundingBox } from "../element/collision";
@ -285,6 +287,7 @@ import { actionToggleHandTool } from "../actions/actionCanvas";
import { jotaiStore } from "../jotai";
import { activeConfirmDialogAtom } from "./ActiveConfirmDialog";
import { actionCreateContainerFromText } from "../actions/actionBoundText";
import BraveMeasureTextError from "./BraveMeasureTextError";
const deviceContextInitialValue = {
isSmScreen: false,
@ -429,7 +432,6 @@ class App extends React.Component<AppProps, AppState> {
};
this.id = nanoid();
this.library = new Library(this);
if (excalidrawRef) {
const readyPromise =
@ -711,6 +713,8 @@ class App extends React.Component<AppProps, AppState> {
const theme =
actionResult?.appState?.theme || this.props.theme || THEME.LIGHT;
let name = actionResult?.appState?.name ?? this.state.name;
const errorMessage =
actionResult?.appState?.errorMessage ?? this.state.errorMessage;
if (typeof this.props.viewModeEnabled !== "undefined") {
viewModeEnabled = this.props.viewModeEnabled;
}
@ -726,7 +730,6 @@ class App extends React.Component<AppProps, AppState> {
if (typeof this.props.name !== "undefined") {
name = this.props.name;
}
this.setState(
(state) => {
// using Object.assign instead of spread to fool TS 4.2.2+ into
@ -744,6 +747,7 @@ class App extends React.Component<AppProps, AppState> {
gridSize,
theme,
name,
errorMessage,
});
},
() => {
@ -872,7 +876,6 @@ class App extends React.Component<AppProps, AppState> {
),
};
}
// FontFaceSet loadingdone event we listen on may not always fire
// (looking at you Safari), so on init we manually load fonts for current
// text elements on canvas, and rerender them once done. This also
@ -1000,6 +1003,13 @@ class App extends React.Component<AppProps, AppState> {
} else {
this.updateDOMRect(this.initializeScene);
}
// note that this check seems to always pass in localhost
if (isBrave() && !isMeasureTextSupported()) {
this.setState({
errorMessage: <BraveMeasureTextError />,
});
}
}
public componentWillUnmount() {

@ -0,0 +1,42 @@
import { t } from "../i18n";
const BraveMeasureTextError = () => {
return (
<div data-testid="brave-measure-text-error">
<p>
{t("errors.brave_measure_text_error.start")} &nbsp;
<span style={{ fontWeight: 600 }}>
{t("errors.brave_measure_text_error.aggressive_block_fingerprint")}
</span>{" "}
{t("errors.brave_measure_text_error.setting_enabled")}.
<br />
<br />
{t("errors.brave_measure_text_error.break")}{" "}
<span style={{ fontWeight: 600 }}>
{t("errors.brave_measure_text_error.text_elements")}
</span>{" "}
{t("errors.brave_measure_text_error.in_your_drawings")}.
</p>
<p>
{t("errors.brave_measure_text_error.strongly_recommend")}{" "}
<a href="http://docs.excalidraw.com/docs/@excalidraw/excalidraw/faq#turning-off-aggresive-block-fingerprinting-in-brave-browser">
{" "}
{t("errors.brave_measure_text_error.steps")}
</a>{" "}
{t("errors.brave_measure_text_error.how")}.
</p>
<p>
{t("errors.brave_measure_text_error.disable_setting")}{" "}
<a href="https://github.com/excalidraw/excalidraw/issues/new">
{t("errors.brave_measure_text_error.issue")}
</a>{" "}
{t("errors.brave_measure_text_error.write")}{" "}
<a href="https://discord.gg/UexuTaE">
{t("errors.brave_measure_text_error.discord")}
</a>
.
</p>
</div>
);
};
export default BraveMeasureTextError;

@ -5,13 +5,13 @@ import { Dialog } from "./Dialog";
import { useExcalidrawContainer } from "./App";
export const ErrorDialog = ({
message,
children,
onClose,
}: {
message: string;
children?: React.ReactNode;
onClose?: () => void;
}) => {
const [modalIsShown, setModalIsShown] = useState(!!message);
const [modalIsShown, setModalIsShown] = useState(!!children);
const { container: excalidrawContainer } = useExcalidrawContainer();
const handleClose = React.useCallback(() => {
@ -32,7 +32,7 @@ export const ErrorDialog = ({
onCloseRequest={handleClose}
title={t("errorDialog.title")}
>
<div style={{ whiteSpace: "pre-wrap" }}>{message}</div>
<div style={{ whiteSpace: "pre-wrap" }}>{children}</div>
</Dialog>
)}
</>

@ -364,10 +364,9 @@ const LayerUI = ({
{appState.isLoading && <LoadingMessage delay={250} />}
{appState.errorMessage && (
<ErrorDialog
message={appState.errorMessage}
onClose={() => setAppState({ errorMessage: null })}
/>
<ErrorDialog onClose={() => setAppState({ errorMessage: null })}>
{appState.errorMessage}
</ErrorDialog>
)}
{appState.openDialog === "help" && (
<HelpDialog

@ -0,0 +1,63 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Test <App/> should show error modal when using brave and measureText API is not working 1`] = `
<div
data-testid="brave-measure-text-error"
>
<p>
Looks like you are using Brave browser with the
 
<span
style="font-weight: 600;"
>
Aggressively Block Fingerprinting
</span>
setting enabled
.
<br />
<br />
This could result in breaking the
<span
style="font-weight: 600;"
>
Text Elements
</span>
in your drawings
.
</p>
<p>
We strongly recommend disabling this setting. You can follow
<a
href="http://docs.excalidraw.com/docs/@excalidraw/excalidraw/faq#turning-off-aggresive-block-fingerprinting-in-brave-browser"
>
these steps
</a>
on how to do so
.
</p>
<p>
If disabling this setting doesn't fix the display of text elements, please open an
<a
href="https://github.com/excalidraw/excalidraw/issues/new"
>
issue
</a>
on our GitHub, or write us on
<a
href="https://discord.gg/UexuTaE"
>
Discord
</a>
.
</p>
</div>
`;

@ -12,6 +12,9 @@ export const isFirefox =
export const isChrome = navigator.userAgent.indexOf("Chrome") !== -1;
export const isSafari =
!isChrome && navigator.userAgent.indexOf("Safari") !== -1;
// keeping function so it can be mocked in test
export const isBrave = () =>
(navigator as any).brave?.isBrave?.name === "isBrave";
export const APP_NAME = "Excalidraw";

@ -8,7 +8,13 @@ import {
NonDeletedExcalidrawElement,
} from "./types";
import { mutateElement } from "./mutateElement";
import { BOUND_TEXT_PADDING, TEXT_ALIGN, VERTICAL_ALIGN } from "../constants";
import {
BOUND_TEXT_PADDING,
DEFAULT_FONT_FAMILY,
DEFAULT_FONT_SIZE,
TEXT_ALIGN,
VERTICAL_ALIGN,
} from "../constants";
import { MaybeTransformHandleType } from "./transformHandles";
import Scene from "../scene/Scene";
import { isTextElement } from ".";
@ -794,3 +800,14 @@ export const getMaxContainerHeight = (container: ExcalidrawElement) => {
}
return height - BOUND_TEXT_PADDING * 2;
};
export const isMeasureTextSupported = () => {
const width = getTextWidth(
DUMMY_TEXT,
getFontString({
fontSize: DEFAULT_FONT_SIZE,
fontFamily: DEFAULT_FONT_FAMILY,
}),
);
return width > 0;
};

@ -838,10 +838,9 @@ class Collab extends PureComponent<Props, CollabState> {
/>
)}
{errorMessage && (
<ErrorDialog
message={errorMessage}
onClose={() => this.setState({ errorMessage: "" })}
/>
<ErrorDialog onClose={() => this.setState({ errorMessage: "" })}>
{errorMessage}
</ErrorDialog>
)}
</>
);

@ -673,10 +673,9 @@ const ExcalidrawWrapper = () => {
</Excalidraw>
{excalidrawAPI && <Collab excalidrawAPI={excalidrawAPI} />}
{errorMessage && (
<ErrorDialog
message={errorMessage}
onClose={() => setErrorMessage("")}
/>
<ErrorDialog onClose={() => setErrorMessage("")}>
{errorMessage}
</ErrorDialog>
)}
</div>
);

@ -120,7 +120,6 @@
"edit": "Edit line",
"exit": "Exit line editor"
},
"elementLock": {
"lock": "Lock",
"unlock": "Unlock",
@ -206,7 +205,22 @@
"cannotResolveCollabServer": "Couldn't connect to the collab server. Please reload the page and try again.",
"importLibraryError": "Couldn't load library",
"collabSaveFailed": "Couldn't save to the backend database. If problems persist, you should save your file locally to ensure you don't lose your work.",
"collabSaveFailed_sizeExceeded": "Couldn't save to the backend database, the canvas seems to be too big. You should save the file locally to ensure you don't lose your work."
"collabSaveFailed_sizeExceeded": "Couldn't save to the backend database, the canvas seems to be too big. You should save the file locally to ensure you don't lose your work.",
"brave_measure_text_error": {
"start": "Looks like you are using Brave browser with the",
"aggressive_block_fingerprint": "Aggressively Block Fingerprinting",
"setting_enabled": "setting enabled",
"break": "This could result in breaking the",
"text_elements": "Text Elements",
"in_your_drawings": "in your drawings",
"strongly_recommend": "We strongly recommend disabling this setting. You can follow",
"steps": "these steps",
"how": "on how to do so",
"disable_setting": " If disabling this setting doesn't fix the display of text elements, please open an",
"issue": "issue",
"write": "on our GitHub, or write us on",
"discord": "Discord"
}
},
"toolBar": {
"selection": "Selection",

@ -32,6 +32,7 @@ import type { FileSystemHandle } from "./data/filesystem";
import type { ALLOWED_IMAGE_MIME_TYPES, MIME_TYPES } from "./constants";
import { ContextMenuItems } from "./components/ContextMenu";
import { Merge, ForwardRef } from "./utility-types";
import React from "react";
export type Point = Readonly<RoughPoint>;
@ -101,7 +102,7 @@ export type AppState = {
} | null;
showWelcomeScreen: boolean;
isLoading: boolean;
errorMessage: string | null;
errorMessage: React.ReactNode;
draggingElement: NonDeletedExcalidrawElement | null;
resizingElement: NonDeletedExcalidrawElement | null;
multiElement: NonDeleted<ExcalidrawLinearElement> | null;