Skip to content
Merged
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
17 changes: 17 additions & 0 deletions locales/en-US/app.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,18 @@ Details--close-sidebar-button =
Details--error-boundary-message =
.message = Uh oh, some unknown error happened in this panel.

## ErrorBoundary
## This component is shown when an unexpected error is encountered in the application.
## Note that the localization won't be always applied in this component.

# This message will always be displayed after another context-specific message.
ErrorBoundary--report-error-to-developers-description =
Please report this issue to the developers, including the full
error as displayed in the Developer Tools’ Web Console.

# This is used in a call to action button, displayed inside the error box.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added some more comments here as well.

ErrorBoundary--report-error-on-github = Report the error on GitHub

## Footer Links

FooterLinks--legal = Legal
Expand Down Expand Up @@ -668,6 +680,11 @@ ProfileLoaderAnimation--loading-view-not-found = View not found
ProfileRootMessage--title = { -profiler-brand-name }
ProfileRootMessage--additional = Back to home

## Root

Root--error-boundary-message =
.message = Uh oh, some unknown error happened in profiler.firefox.com.

## ServiceWorkerManager
## This is the component responsible for handling the service worker installation
## and update. It appears at the top of the UI.
Expand Down
6 changes: 6 additions & 0 deletions res/css/photon/button.css
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@

/* See https://design.firefox.com/photon/components/buttons.html for the spec */
.photon-button {
/* These two flex options aren't necessary when a real <button> is used, but they are when a <a> element is used as abutton */
display: flex;
align-items: center;
padding: 0 8px;
border: none;
border-radius: 2px;
Expand All @@ -15,7 +18,10 @@
/* photon styles */
background-color: var(--grey-90-a10);
color: var(--grey-90);
cursor: initial; /* reset the defaut link style when used as a button */
font: inherit;
text-decoration: none; /* reset the defaut link style when used as a button */
user-select: none;
}

/* This is a Firefox-specific style because Firefox adds a focusring at a bad
Expand Down
3 changes: 0 additions & 3 deletions res/css/photon/message-bar.css
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,6 @@
}

.photon-message-bar-inner-text {
display: flex;
align-items: center;

Comment on lines -49 to -51

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I looked closely at our example page, and I think that this wasn't useful after all, and this was in the way when I tried to add more than 1 child inside this element. So I removed them. But we will need to take care at regressions in some of our notification bars.

/* This padding is carefully computed so that multi-line message bars have the
* same top padding than single-line message bars. Having this property
* shouldn't change anything for single-line message bars.
Expand Down
8 changes: 4 additions & 4 deletions src/components/app/Details.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { Localized } from '@fluent/react';

import explicitConnect from 'firefox-profiler/utils/connect';
import { TabBar } from './TabBar';
import { ErrorBoundary } from './ErrorBoundary';
import { LocalizedErrorBoundary } from './ErrorBoundary';
import { ProfileCallTreeView } from 'firefox-profiler/components/calltree/ProfileCallTreeView';
import { MarkerTable } from 'firefox-profiler/components/marker-table';
import { StackChart } from 'firefox-profiler/components/stack-chart/';
Expand Down Expand Up @@ -106,9 +106,9 @@ class ProfileViewerImpl extends PureComponent<Props> {
id="Details--error-boundary-message"
attrs={{ message: true }}
>
<ErrorBoundary
<LocalizedErrorBoundary
key={selectedTab}
message="Uh oh, some unknown error happened in this panel."
message="Uh oh, some unknown error happened in this panel"
>
{
{
Expand All @@ -121,7 +121,7 @@ class ProfileViewerImpl extends PureComponent<Props> {
'js-tracer': <JsTracer />,
}[selectedTab]
}
</ErrorBoundary>
</LocalizedErrorBoundary>
</Localized>
<CallNodeContextMenu />
<MaybeMarkerContextMenu />
Expand Down
38 changes: 9 additions & 29 deletions src/components/app/ErrorBoundary.css
Original file line number Diff line number Diff line change
Expand Up @@ -13,39 +13,19 @@
.appErrorBoundaryContents {
display: flex;
width: 100%;
max-width: 600px;
max-height: calc(100%);
max-width: 800px;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I gave some more space to the notice.

max-height: 100%;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and remove the calc which didn't seem useful (?).

box-sizing: border-box;
flex-direction: column;
}

.appErrorBoundaryMessage {
flex: none;
}

.appErrorBoundaryDetails {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I forgot to remove those in the first version of the patch.

padding: 25px;
border-radius: 4px;
margin: 12px 0;
background: #fff;
box-shadow: 0 0 10px rgb(0 0 0 / 0.1);
color: var(--grey-70);
font-family: monospace;
line-height: 1.3;
opacity: 1;
overflow-y: auto;

/* Transition opacity, but do not delay changing the visibility. */
transition: opacity 150ms, visibility 0s;
visibility: visible;
white-space: pre-wrap;
.appErrorBoundaryInnerText {
display: flex;
flex-direction: column;
gap: 4px;
}

.appErrorBoundaryDetails.hide {
opacity: 0;

/* Transition opacity, and create a delay on hiding the visibility to allow
time for the fade out. */
transition: opacity 150ms, visibility 0s 150ms;
visibility: hidden;
.appErrorBoundaryInnerText > p {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wanted to use p which is semantically better, but it comes with default margin... One day we'll probably want to include some version (or our version) of normalize.css...

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it would be good to have a normalize.css.

padding: 0;
margin: 0;
}
87 changes: 60 additions & 27 deletions src/components/app/ErrorBoundary.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,35 @@
// @flow

import * as React from 'react';
import { Localized } from '@fluent/react';
import { reportError } from 'firefox-profiler/utils/analytics';
import './ErrorBoundary.css';

type State = {|
hasError: boolean,
showDetails: boolean,
errorString: string | null,
componentStack?: string,
|};

type Props = {|
type ExternalProps = {|
+children: React.Node,
+message: string,
|};

type InternalProps = {|
...ExternalProps,
buttonContent: React.Node,
reportExplanationMessage: React.Node,
|};

/**
* This component will catch errors in components, and display a more friendly error
* message. This stops the entire app from unmounting and displaying an empty screen.
*
* See: https://reactjs.org/docs/error-boundaries.html
*/
export class ErrorBoundary extends React.Component<Props, State> {
class ErrorBoundaryInternal extends React.Component<InternalProps, State> {
state = {
hasError: false,
showDetails: false,
errorString: null,
};

Expand All @@ -52,7 +56,10 @@ export class ErrorBoundary extends React.Component<Props, State> {
errorString = result;
}
}
this.setState({ hasError: true, errorString, componentStack });
this.setState({
hasError: true,
errorString,
});
reportError({
exDescription: errorString
? errorString + '\n' + componentStack
Expand All @@ -61,35 +68,26 @@ export class ErrorBoundary extends React.Component<Props, State> {
});
}

_toggleErrorDetails = () => {
this.setState((state) => ({ showDetails: !state.showDetails }));
};

render() {
if (this.state.hasError) {
const { errorString, componentStack, showDetails } = this.state;
const { errorString } = this.state;
return (
<div className="appErrorBoundary">
<div className="appErrorBoundaryContents">
<div className="photon-message-bar photon-message-bar-error photon-message-bar-inner-content appErrorBoundaryMessage">
<div className="photon-message-bar-inner-text">
{this.props.message}
<div className="photon-message-bar photon-message-bar-error photon-message-bar-inner-content">
<div className="photon-message-bar-inner-text appErrorBoundaryInnerText">
<p>{this.props.message}</p>
{errorString ? <p>{errorString}.</p> : null}
<p>{this.props.reportExplanationMessage}</p>
</div>
<button
<a
className="photon-button photon-button-micro photon-message-bar-action-button"
type="button"
onClick={this._toggleErrorDetails}
aria-expanded={showDetails ? 'true' : 'false'}
href="https://github.com/firefox-devtools/profiler/issues/new?title=An%20error%20occurred%20in%20Firefox%20Profiler"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can even populate the content of the bug to include the error message etc. But we can do that later.

target="_blank"
rel="noreferrer"
>
{showDetails ? 'Hide error details' : 'View full error details'}
</button>
</div>
<div
data-testid="error-technical-details"
className={`appErrorBoundaryDetails ${showDetails ? '' : 'hide'}`}
>
{errorString ? <div>{errorString}</div> : null}
{componentStack ? <div>{componentStack}</div> : null}
{this.props.buttonContent}
</a>
</div>
</div>
</div>
Expand All @@ -99,3 +97,38 @@ export class ErrorBoundary extends React.Component<Props, State> {
return this.props.children;
}
}

/**
* Use this error boundary when outside of the AppLocalizationProvider hierarchy
*/
export function NonLocalizedErrorBoundary(props: ExternalProps) {
return (
<ErrorBoundaryInternal
{...props}
buttonContent="Report the error on GitHub"
reportExplanationMessage="Please report this error to the developers, including the full error as displayed in the Developer Tools’ Web Console."
/>
);
}

/**
* Use this error boundary when inside of the AppLocalizationProvider hierarchy
*/
export function LocalizedErrorBoundary(props: ExternalProps) {
return (
<ErrorBoundaryInternal
{...props}
buttonContent={
<Localized id="ErrorBoundary--report-error-on-github">
Report the error on GitHub
</Localized>
}
reportExplanationMessage={
<Localized id="ErrorBoundary--report-error-to-developers-description">
Please report this issue to the developers, including the full error
as displayed in the Developer Tools’ Web Console.
</Localized>
}
/>
);
}
35 changes: 23 additions & 12 deletions src/components/app/Root.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,14 @@
// @flow

import React, { PureComponent } from 'react';
import { Localized } from '@fluent/react';
import { Provider } from 'react-redux';
import { UrlManager } from './UrlManager';
import { FooterLinks } from './FooterLinks';
import { ErrorBoundary } from './ErrorBoundary';
import {
NonLocalizedErrorBoundary,
LocalizedErrorBoundary,
} from './ErrorBoundary';
import { AppViewRouter } from './AppViewRouter';
import { ProfileLoader } from './ProfileLoader';
import { ServiceWorkerManager } from './ServiceWorkerManager';
Expand All @@ -28,21 +32,28 @@ export class Root extends PureComponent<RootProps> {
render() {
const { store } = this.props;
return (
<ErrorBoundary message="Uh oh, some error happened in profiler.firefox.com.">
<NonLocalizedErrorBoundary message="Uh oh, some unknown error happened in profiler.firefox.com.">
<Provider store={store}>
<AppLocalizationProvider>
<DragAndDrop>
<UrlManager>
<ServiceWorkerManager />
<ProfileLoader />
<AppViewRouter />
<FooterLinks />
<WindowTitle />
</UrlManager>
</DragAndDrop>
<Localized
id="Root--error-boundary-message"
attrs={{ message: true }}
>
<LocalizedErrorBoundary message="Uh oh, some unknown error happened in profiler.firefox.com.">
<DragAndDrop>
<UrlManager>
<ServiceWorkerManager />
<ProfileLoader />
<AppViewRouter />
<FooterLinks />
<WindowTitle />
</UrlManager>
</DragAndDrop>
</LocalizedErrorBoundary>
</Localized>
</AppLocalizationProvider>
</Provider>
</ErrorBoundary>
</NonLocalizedErrorBoundary>
);
}
}
Loading