Introduce top level Error boundary Component
Categories
(DevTools :: Netmonitor, enhancement, P3)
Tracking
(firefox84 fixed)
Tracking | Status | |
---|---|---|
firefox84 | --- | fixed |
People
(Reporter: Honza, Assigned: codywelsh)
References
(Blocks 2 open bugs)
Details
Attachments
(2 files)
From: https://reactjs.org/docs/error-boundaries.html
Error boundaries are React components that catch JavaScript errors anywhere in their child component tree, log those errors, and display a fallback UI instead of the component tree that crashed. Error boundaries catch errors during rendering, in lifecycle methods, and in constructors of the whole tree below them.
It would be great to have such component in the Network panel. It could help to with bugs where the entire panel goes blank, like e.g. this one:
- Bug 1659127 - Network tab goes blank
The component could render:
- The error stack + description
- Some instructions about how to file a bug (and copy the error)
- Link to bugzilla new bug (in the Network component) page
Honza
Reporter | ||
Updated•4 years ago
|
Assignee | ||
Comment 1•4 years ago
|
||
This would be cool to work on - will start looking into this, if you're confident in assigning it to me. :)
Reporter | ||
Comment 2•4 years ago
|
||
Assigned to you, thanks for the help!
Honza
Assignee | ||
Comment 3•4 years ago
|
||
Thanks! My thoughts so far:
-
Assuming we only want a single
ErrorBoundary
component (instead of multiple components for various different portions of the Network Monitor), I'm thinking we should place it around the encapsulatingApp
to catch any errors thrown by the entire panel. -
For testing, I'm not yet totally familiar with Mozilla's tooling - however, I'm thinking that we probably want to force all child components to throw an error programmatically (if possible), and then verify that our ErrorBoundary component is rendering as expected in each case. I'm open to any suggestions, here, though. Maybe we don't need to be quite that exhaustive?
In any case, I'm glad to be of help! I'll ask questions if I get stuck.
Reporter | ||
Comment 4•4 years ago
|
||
Thanks for the update!
(In reply to Cody Welsh from comment #3)
Thanks! My thoughts so far:
- Assuming we only want a single
ErrorBoundary
component (instead of multiple components for various different portions of the Network Monitor), I'm thinking we should place it around the encapsulatingApp
to catch any errors thrown by the entire panel.
Yes, this makes sense.
The name of the component could be AppErrorBoundary
It could live in the same directory
And we should update the src/app.js
file to render the new error boundary component.
The UI rendered by AppErrorBoundary
could:
- render some info message + link to file a new network panel bug: https://bugzilla.mozilla.org/enter_bug.cgi?product=DevTools&component=Netmonitor
- There could also be a button allowing the user to copy and data we have (e.g. stack trace) and paste it into the bug report.
- For testing, I'm not yet totally familiar with Mozilla's tooling - however, I'm thinking that we probably want to force all child components to throw an error programmatically (if possible), and then verify that our ErrorBoundary component is rendering as expected in each case. I'm open to any suggestions, here, though. Maybe we don't need to be quite that exhaustive?
What about programmatically damage data in a reducer e.g. set requests
in the requests
reducer to null and rerender, this should cause an exception - and make the entire panel blank. You can use it for testing as well as for automated test eventually.
Thanks for working on this, this will be great architecture improvement!
Honza
Assignee | ||
Comment 5•4 years ago
|
||
What about programmatically damage data in a reducer e.g. set requests in the requests reducer to null and rerender, this should cause an exception - and make the entire panel blank. You can use it for testing as well as for automated test eventually.
Sounds good to me - should propagate through and cause theErrorBoundary
to catch, so this would be a good case.
Maybe we should also allow users to save logs of the errors? Might be helpful for future debugging efforts, but I don't know if this would be difficult to implement in this context. Might be something for another filed bug.
Assignee | ||
Comment 6•4 years ago
|
||
One additional query:
Is it standard within DevTools to keep the implementation of the class in a given file, and then import a component from devtools/client/shared/components
to bring in the presentation? I took a glance at the available components, and I don't think that there's an appropriate one we can reuse here, so I'm assuming we'll need to add some ErrorWrapper
that's defined in that directory (along with its styles), unless we intend to use bare elements in the error pane itself.
Reporter | ||
Comment 7•4 years ago
|
||
(In reply to Cody Welsh from comment #5)
Maybe we should also allow users to save logs of the errors? Might be helpful for future debugging efforts, but I don't know if this would be difficult to implement in this context. Might be something for another filed bug.
Yes, this would be great. The user should be able to copy anything that could help us to solve/debug the issue: errors, stack trace, etc.
And, yes, might be done in a follow up.
Is it standard within DevTools to keep the implementation of the class in a given file, and then import a component from
devtools/client/shared/components
to bring in the presentation? I took a glance at the available components, and I don't think that there's an appropriate one we can reuse here, so I'm assuming we'll need to add someErrorWrapper
that's defined in that directory (along with its styles), unless we intend to use bare elements in the error pane itself.
Yes, there is no component we could reuse (from the shared directory)
So, as I mentioned in comment #4 we should do the following:
- Introduce a new
AppErrorBoundary
component - wrapping the top level App component (i.e. this component will become new root component) - Add it into the
netmonitor/src
dir (along side the App component) - Introduce a
AppErrorBoundary.css
file withinnetmonitor/src/assets/styles
dir (we are maintaining all *.css styles in this dir atm) - Include the new CSS file in
netmonitor/src/assets/styles/netmonitor.css
Honza
Assignee | ||
Comment 8•4 years ago
|
||
(In reply to Jan Honza Odvarko [:Honza] (always need-info? me) from comment #7)
Thanks for the insightful comments! This should be all I need for now, but if I stumble at any point, I'll add more.
Assignee | ||
Comment 9•4 years ago
|
||
Just a quick note that I am actually still working on this - had a while there where I couldn't do anything; sorry for that! Response time will be significantly shorter, now. :)
Reporter | ||
Comment 10•4 years ago
|
||
Great, thanks for the update!
Honza
Assignee | ||
Comment 11•4 years ago
|
||
I seem to be having some trouble with base-loader
loading the module I've created - is there anything I should look out for? (The module path and name are correct, so my mind is pointing me to maybe a Redux connection requirement I'm unaware of; I'm currently just attempting to do a simple export to render the error boundary before proceeding.)
In the App component, I've added:
loader.lazyGetter(this, "AppErrorBoundary", function() {
return createFactory(
require("devtools/client/netmonitor/src/components/AppErrorBoundary")
);
});
..and the export is just a simple module.exports = AppErrorBoundary;
, which is currently just a simple class, similar to the React example (with prop type definitions).
The end result is:
console.error: (new Error("Module `resource://devtools/client/netmonitor/src/components/AppErrorBoundary.js` is not found at resource://devtools/client/netmonitor/src/components/AppErrorBoundary.js", "resource://devtools/shared/base-loader.js", 169))
In any case, I'll be continuing to plug at it. :) My next step is just connecting the export to the Redux store, and seeing what happens next.
Reporter | ||
Comment 12•4 years ago
|
||
Can you please attach a patch, so I can try it on my machine?
Btw. Have you appended the new file into the moz.build
file located in the same dir?
Thanks for working on this! There is another bug where we'd benefit from having the error boundary implemented.
See Bug 1665574
Honza
Assignee | ||
Comment 13•4 years ago
|
||
(In reply to Jan Honza Odvarko [:Honza] (always need-info? me) from comment #12)
Can you please attach a patch, so I can try it on my machine?
Btw. Have you appended the new file into the
moz.build
file located in the same dir?Thanks for working on this! There is another bug where we'd benefit from having the error boundary implemented.
See Bug 1665574Honza
The missing moz.build
info was totally the issue; thanks! (I had no idea!) I can now continue working on the bug. :) Patch to come soon.
Assignee | ||
Comment 14•4 years ago
|
||
Just a quick line of inquiry, which I'm sure has been asked before (I can't seem to find anything on it):
Is it currently impossible to inspect elements of DevTools itself with React DevTools inside of a remote Browser Toolbox? I can inspect the panels themselves with a Toolbox, but not with any development-specific addons - just wanted to make sure I wasn't missing anything. (I suppose one could always attempt rendering the DevTools panels as individual projects, but it seems like this is something that may already have a solution!)
Thanks!
Reporter | ||
Comment 15•4 years ago
|
||
(In reply to Cody Welsh from comment #13)
The missing
moz.build
info was totally the issue; thanks! (I had no idea!)
@bomsy thanks! (it was his idea, when we triaged this :-)
I can now continue working on the bug. :) Patch to come soon.
Awesome, thanks!
(In reply to Cody Welsh from comment #14)
Is it currently impossible to inspect elements of DevTools itself with React DevTools inside of a remote Browser Toolbox? I can inspect the panels themselves with a Toolbox, but not with any development-specific addons - just wanted to make sure I wasn't missing anything. (I suppose one could always attempt rendering the DevTools panels as individual projects, but it seems like this is something that may already have a solution!)
Good question, I don't know. But, you can ask in our public channel: https://chat.mozilla.org/#/room/#devtools:mozilla.org
Honza
Assignee | ||
Comment 16•4 years ago
|
||
Add a needed top-level error boundary component to the Net Monitor panel
in DevTools, to catch errors that would otherwise render the pane blank.
(incomplete; this message to be amended)
Assignee | ||
Comment 17•4 years ago
|
||
(In reply to Jan Honza Odvarko [:Honza] (always need-info? me) from comment #15)
Awesome - I've pushed a WIP commit while I work out the testing and styling, but it appears to be functionally adequate.
As for testing - in what way do you believe we should progress with programmatic damage for reducers? Is it possible to instantiate a "test-only reducer" for our purposes that mirrors the structure of our actual reducer? Certainly some poking around to do here. :)
Reporter | ||
Comment 18•4 years ago
|
||
(In reply to Cody Welsh from comment #17)
(In reply to Jan Honza Odvarko [:Honza] (always need-info? me) from comment #15)
As for testing - in what way do you believe we should progress with programmatic damage for reducers? Is it possible to instantiate a "test-only reducer" for our purposes that mirrors the structure of our actual reducer? Certainly some poking around to do here. :)
See my comment #4:
What about programmatically damage data in a reducer e.g. set requests in the requests reducer to null and reender, this should cause an
exception - and make the entire panel blank. You can use it for testing as well as for automated test eventually.¨
I recall that it worked for me when I tried it. The test should directly access the reducer and set store.requests.requests
to null to damage it.
Honza
Assignee | ||
Comment 19•4 years ago
|
||
(In reply to Jan Honza Odvarko [:Honza] (always need-info? me) from comment #18)
I recall that it worked for me when I tried it. The test should directly access the reducer and set store.requests.requests to null to damage it.
Okay, cool. It hadn't actually occurred to me that I could just modify the store like that, but definitely makes sense for this context. Haha. Will try that and update the differential as soon as I can.
Other considerations: As per the React docs, if we get this to a good state, we may even be able to consider shared top-level error boundaries for all other panels, but that's something for down the line. It shouldn't be too difficult to abstract this, though.
Reporter | ||
Comment 20•4 years ago
|
||
(In reply to Cody Welsh from comment #19)
Other considerations: As per the React docs, if we get this to a good state, we may even be able to consider shared top-level error boundaries for all other panels, but that's something for down the line. It shouldn't be too difficult to abstract this, though.
Yes, I agree, but let's do this as part of a follow up bug.
Assignee | ||
Comment 21•4 years ago
|
||
In trying to write a test that works for this, it seems like I can't get the pane to rerender after setting store.requests.requests
to null. I may be missing something - do I need to dispatch a special action with a mock reducer, here? I assume that the update logic is contained within that, but it sounds like you arrived at another solution, so I wanted to make sure. (Apologies, I'm probably glossing over something obvious!)
Reporter | ||
Comment 22•4 years ago
|
||
(In reply to Cody Welsh from comment #21)
In trying to write a test that works for this, it seems like I can't get the pane to rerender after setting
store.requests.requests
to null. I may be missing something - do I need to dispatch a special action with a mock reducer, here? I assume that the update logic is contained within that, but it sounds like you arrived at another solution, so I wanted to make sure. (Apologies, I'm probably glossing over something obvious!)
Yes, you need to fire an action. Actions should be the only way the application state is changed (except of our little hack in this test). So, you should fire an action (something innocent e.g. openNetworkAction? - opens the left sidebar, https://searchfox.org/mozilla-central/rev/4352fb7b0d17c1febff9569ed311e0e42c93093e/devtools/client/netmonitor/src/actions/ui.js#55) to change the state and cause React to rerender.
Assignee | ||
Comment 23•4 years ago
|
||
(In reply to Jan Honza Odvarko [:Honza] (always need-info? me) from comment #22)
Yes, you need to fire an action. Actions should be the only way the application state is changed (except of our little hack in this test). So, you should fire an action (something innocent e.g. openNetworkAction? - opens the left sidebar, https://searchfox.org/mozilla-central/rev/4352fb7b0d17c1febff9569ed311e0e42c93093e/devtools/client/netmonitor/src/actions/ui.js#55) to change the state and cause React to rerender.
Okay, that definitely causes the pane to rerender, but now it looks like simply setting store.requests.requests = null
isn't sufficient. From some early searching, it looks like I can't modify the store in this context (I believe this is intentional, granted, but makes testing this potentially a little more annoying).
Is there anything in particular you've done to allow that operation? The only way I can see moving forward is to use store.replaceReducer
if we want to damage the store, but am happy to hear any other options.
Reporter | ||
Comment 24•4 years ago
|
||
(In reply to Cody Welsh from comment #23)
(In reply to Jan Honza Odvarko [:Honza] (always need-info? me) from comment #22)
Is there anything in particular you've done to allow that operation? The only way I can see moving forward is to use
store.replaceReducer
if we want to damage the store, but am happy to hear any other options.
I like the idea with store.replaceReducer
it feels less hacky. Definitely worth of trying.
Honza
Assignee | ||
Comment 25•4 years ago
|
||
(In reply to Jan Honza Odvarko [:Honza] (always need-info? me) from comment #24)
(In reply to Cody Welsh from comment #23)
(In reply to Jan Honza Odvarko [:Honza] (always need-info? me) from comment #22)
Is there anything in particular you've done to allow that operation? The only way I can see moving forward is to use
store.replaceReducer
if we want to damage the store, but am happy to hear any other options.I like the idea with
store.replaceReducer
it feels less hacky. Definitely worth of trying.Honza
Well, it looks like this function replaces every reducer with the one passed in. I would normally expect this to work, but the problem is that the panel freezes up instead of erroring out - imagine this is because it's not getting the data it expects for each action (such as recording pause/unpause, etc), so nothing happens. I guess you'd have to pass in a reducer that is identical to the one passed in, except for one specific portion, but that sounds unwieldy (and duplicates a lot of code).
My next approach might be to see if we can manually cause one of the children components to error out. Perhaps instantiate a version of the monitor that will error out? Definitely looking into more options.
Assignee | ||
Comment 26•4 years ago
|
||
Additional info: This question makes me think that maybe we could reduce the test size to just rendering the monitor and passing ...{ hasError: true, errorMsg: "..." }
or similar to AppErrorBoundary
, thus forcing it to think that there is an error. Hmm..
Reporter | ||
Comment 27•4 years ago
|
||
Thanks for working on this!
The following test works for me:
add_task(async function() {
const { tab, monitor } = await initNetMonitor(API_CALLS_URL, {
requestCount: 1,
});
info("Starting test... ");
const { document, store, windowRequire } = monitor.panelWin;
const Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
const { getDisplayedRequests, getSortedRequests } = windowRequire(
"devtools/client/netmonitor/src/selectors/index"
);
store.dispatch(Actions.batchEnable(false));
const state = store.getState();
state.ui.columns = null;
tab.linkedBrowser.reload();
// TODO: verify the UI
await teardown(monitor);
});
See also my review comments in Phab
https://phabricator.services.mozilla.com/D91511
Honza
Assignee | ||
Comment 28•4 years ago
|
||
Awesome! I'll address everything else after verifying the test works (and adding the UI state test at the end). Thanks for the help with this.
Assignee | ||
Comment 29•4 years ago
|
||
(This should be pretty much ready for review.) I styled it as best I could according to some of the recommendations in the Photon style guide, but I'm sure that it could be polished more. Will keep an eye on things. :)
Reporter | ||
Comment 30•4 years ago
|
||
Victoria, this bug is introducing a new UI that informs the user about an error happening in the Network panel (it should later be applied on other panels).
If there is an error today there is just an empty screen, which results in bug reports like "The panel went blank ...".
We want to show some useful information if an error happens (a stack trace + error message) and encourage users to file a bug (by providing a link to bugzilla) and copying the data to the bug report.
What do you think about the UI (I am attaching a screenshot of the current state)
I think there could also be a button for copying the error data into the clipboard...
Honza
Comment 31•4 years ago
•
|
||
Great to see this work happening!
Some thoughts:
- The red is a bit hard to read, so I'd use the "Console Error Red" variable which is much darker. Alternatively, it would look less alarming if we make the text black, and add an error icon next to the error heading.
- Instead of the two boxes for the error, what about just one that contains both the error title and error text?
- We could shorten the top heading to just "The Network panel has crashed" (I think this is similar to the language that Firefox uses when a tab crashes. No reason to hide that it's a crash, I think :))
- I'd replace the file a bug section with just a single button in a paragraph entitled "File Bug Report". (We could potentially pre-fill the bugzilla textarea with their error data, if that isn't too intrusive. If we can't do that, then clibpboard button makes sense.) Could use the blue button from Perf panel.
- Could add a second button in gray that says "Reload the Page", because I think that's the next step, correct? It could be helpful in case the user doesn't immediately know to do that and feels stuck.
Assignee | ||
Comment 32•4 years ago
|
||
Great! I'll go ahead and implement the aforementioned suggestions, and possibly provide another screenshot of the resulting interface. Thanks.
Assignee | ||
Comment 33•4 years ago
|
||
Couple comments I might need to explore more:
I'd replace the file a bug section with just a single button in a paragraph entitled "File Bug Report". (We could potentially pre-fill the bugzilla textarea with their error data, if that isn't too intrusive. If we can't do that, then clibpboard button makes sense.)
I'm not sure that this is possible to do on Bugzilla, but someone else might know more. The clipboard button certainly is, of course. :)
Could add a second button in gray that says "Reload the Page", because I think that's the next step, correct? It could be helpful in case the user doesn't immediately know to do that and feels stuck.
Honza, is this possible inside of the monitor? We would have to close and reload the toolbox itself, I think.
Otherwise, everything else is done locally; just need to figure out these two points.
Assignee | ||
Comment 34•4 years ago
|
||
(Oops, can't edit my comment, but the last lines in the quotes are my questions)
Reporter | ||
Comment 35•4 years ago
|
||
(In reply to Cody Welsh from comment #33)
Couple comments I might need to explore more:
I'd replace the file a bug section with just a single button in a paragraph entitled "File Bug Report". (We could potentially pre-fill the bugzilla textarea with their error data, if that isn't too intrusive. If we can't do that, then clibpboard button makes sense.)
I'm not sure that this is possible to do on Bugzilla, but someone else might know more. The clipboard button certainly is, of course. :)
You can provide default description through comment
field as follows:
https://bugzilla.mozilla.org/enter_bug.cgi?product=DevTools&component=Netmonitor&comment=some%20text
Could add a second button in gray that says "Reload the Page", because I think that's the next step, correct? It could be helpful in case the user doesn't immediately know to do that and feels stuck.
Honza, is this possible inside of the monitor? We would have to close and reload the toolbox itself, I think.Otherwise, everything else is done locally; just need to figure out these two points.
We can do the same as the "Reload" button displayed when the Network panel is empty.
See this code
https://searchfox.org/mozilla-central/rev/e0eb861a187f0bb6d994228f2e0e49b2c9ee455e/devtools/client/netmonitor/src/components/request-list/RequestListEmptyNotice.js#90
Thanks this will be great feature!
Honza
Assignee | ||
Comment 36•4 years ago
|
||
(In reply to Jan Honza Odvarko [:Honza] (always need-info? me) from comment #35)
We can do the same as the "Reload" button displayed when the Network panel is empty.
See this code
https://searchfox.org/mozilla-central/rev/e0eb861a187f0bb6d994228f2e0e49b2c9ee455e/devtools/client/netmonitor/src/components/request-list/RequestListEmptyNotice.js#90Thanks this will be great feature!
Honza
This works, but only updates the browser page. Is that what we're going for, here? (I'd assumed the goal was to reload the network monitor, but if not, my bad!)
Assignee | ||
Comment 37•4 years ago
|
||
(Also, I think that the only way to resolve the error boundary "normally" is to cause a total remount of the component, hence the need to close/reopen the toolbox. Not sure if there's another way to invoke that.)
Assignee | ||
Comment 38•4 years ago
|
||
Okay, did some research on this.
Firstly, this comment thread suggests that we can set the key
of AppErrorBoundary
upon clicking the "reload" button, and alternatively call this.forceUpdate()
, though the first solution is preferred. There is also a decent example on this.
If we decide to go this route, we'd have to create an additional component to hold the buttons, and update the key from there. Might take some reshuffling around of a bunch of the logic.
Reporter | ||
Comment 39•4 years ago
|
||
Thanks for the update!
(In reply to Cody Welsh from comment #38)
Firstly, this comment thread suggests that we can set the
key
ofAppErrorBoundary
upon clicking the "reload" button,
Sounds good to me.
If we decide to go this route, we'd have to create an additional component to hold the buttons, and update the key from there. Might take some reshuffling around of a bunch of the logic.
ok
Also note that the content of the Network panel is rendered within an iframe, so re-rendering just that could be enough.
Honza
Assignee | ||
Comment 40•4 years ago
|
||
Well, looks like simply remounting AppErrorBoundary
is insufficient (it simply re-renders, errors out again, and we're back at the same view. It's hard to say exactly why, but I'd guess it's because the store doesn't change in the interim.).
Past that, since the rendering of the Network Panel essentially happens in the src/app.js
file within the bootstrap process, I'm having a bit of trouble working out how we should approach re-rendering the iframe
there from our reset button. (I figure we might somehow pass a local method down, but I'm not sure.) Will be continuing to try anything I can think of, though.
Reporter | ||
Comment 41•4 years ago
|
||
Julian, do we have some API that allows to re-open the Toolbox?
It would help to recover from unexpected error in this case.
Honza
Comment 42•4 years ago
|
||
The only thing I can think about is our logic to handle a localTab remoteness change, ie what we used to do before we had target switching:
await toolbox.destroy();
// Fetch the new target for this tab
const newTarget = await TargetFactory.forTab(this.localTab, null);
gDevTools.showToolbox(newTarget);
So 3 steps: close the toolbox, recreate the target, reopen the toolbox. The challenge is to be able to recreate the correct target. It's easy for local tabs, because the TabDescriptorFront keeps a pointer on the actual XULTab of the target and can easily recreate a new target. I would suggest exposing a new method on TabDescriptorFront, called something like reloadLocalTabToolbox
(I suggest this front, because that's the only place that should handle specifics about local tabs).
An easier option would be to tell the user to close and reopen the toolbox?
Reporter | ||
Comment 43•4 years ago
|
||
Thanks for quick response Julian!
(In reply to Julian Descottes [:jdescottes] from comment #42)
An easier option would be to tell the user to close and reopen the toolbox?
Yes, sounds like the option for now. We can automated that in a follow up if needed.
@Cody - let's just update the description and instruct the user to reopen the Toolbox to workaround the issue.
Honza
Assignee | ||
Comment 44•4 years ago
|
||
(In reply to Jan Honza Odvarko [:Honza] (always need-info? me) from comment #43)
@Cody - let's just update the description and instruct the user to reopen the Toolbox to workaround the issue.
Sounds good; it would certainly be easier to do this for now. Will go ahead and implement it. :)
Assignee | ||
Comment 45•4 years ago
|
||
Diff should be updated; will keep an eye out for any other change requests!
Comment 47•4 years ago
|
||
Pushed by hmanilla@mozilla.com: https://hg.mozilla.org/integration/autoland/rev/5129f2522092 Introduce top level Error boundary Component. r=Honza,bomsy
Comment 48•4 years ago
|
||
bugherder |
Description
•