Your first characterization is a special case of your second characterization where the routine for the low-priority interrupt that happens "at the same time" is interrupted by the routine for the high-priority interrupt at the very beginning before it starts. Everything else is much as you said it is.
There are several things to note. (1) The registers R0-R7 are not fixed locations but are pointers to one of 4 register windows, which is determined by PSW. So, the simplest way to avoid register conflicts for R0-R7 and/or to do context-switching is to stack the PSW and reset the register window in PSW. (2) You can do a lot of things with priority. If a service routine at high priority is long, in the context of other time-critical high-priority routines, then you do the time-critical part of it first, drop the priority, and return from the service routine without clearing the interrupt flag. When it comes back, you can do the low-priority handling and then reset the flag for high priority when done. (3) To handle the cases where high-priority events can - during a burst most - temporarily outrun the low-priority handlers, you'd, instead, spool the requests for low-priority action and use a sleeping barber protocol.
That's essentially what happens with the handlers for the 5 data clock timestamps for the 8051FA in this example application multi-threaded data collection example (meant to be assembled using the CAS assembler), from the early 1990's, where everything is context-switching and threads (but where suggestion (1) was not used ... which would have simplified things a bit). The application cited is set up as the top-level foreground thread, which is outside of all ISR's and which self-terminates, since there is no polling, the low-priority background threads, which are all inside ISR's and the high-priority background threads, which are also all inside ISR's. This provides a natural, organic implementation of thread priority.