Use mutation events for injections mounts too#3191
Conversation
d546786 to
7f5c004
Compare
lguychard
left a comment
There was a problem hiding this comment.
Code changes LGTM.
Could we add a unit test in code_intelligence.test.tsx that reproduces the issue we ran into? Something like this:
test('only renders elements if their mount locations have been emitted in mutations', async () => {
const { services } = await integrationTestContext()
const mutations = new Subject<MutationRecordLike[]>()
const globalDebugMount = createTestElement()
const getGlobalDebugMount: MountGetter = container => (container === globalDebugMount ? globalDebugMount : null)
subscriptions.add(
handleCodeHost({
mutations: mutations.pipe(startWith([{ addedNodes: [document.body], removedNodes: [] }])),
codeHost: {
name: 'test',
check: () => true,
getGlobalDebugMount,
},
extensionsController: createMockController(services),
showGlobalDebug: true,
...createMockPlatformContext(),
})
)
// Simulate the MutationObserver firing after each React render (this happens on FF)
RENDER.mockImplementation(() => {
mutations.next([{ addedNodes: [], removedNodes: [] }])
})
// Simulate mount being added to the DOM
mutations.next([{ addedNodes: [globalDebugMount], removedNodes: [] }])
expect(elementRenderedAtMount(globalDebugMount)).not.toBeUndefined()
})
client/browser/src/libs/code_intelligence/code_intelligence.tsx
Outdated
Show resolved
Hide resolved
client/browser/src/libs/code_intelligence/code_intelligence.tsx
Outdated
Show resolved
Hide resolved
| }) | ||
| ) | ||
| // Render command palette | ||
| if (codeHost.getCommandPaletteMount) { |
There was a problem hiding this comment.
Potential bug here: if the MountGetter is not precise enough (for example if its selector is too generic) and returns non-null results for several addedNodes, we could render several elements.
This is hypothetical, I'd estimate the likelihood of it actually happening to be pretty low.
There was a problem hiding this comment.
Can you give an example? Which selector do you mean, the one that gets the container or the one that checks the preexisting element?
There was a problem hiding this comment.
Consider this:
const getCommandPaletteMount: MountGetter = (container: HTMLElement): HTMLElement | null => {
const headerElement = querySelectorOrSelf(container, '.aui-header-primary .aui-nav')
if (!headerElement) {
return null
}
...
}If '.aui-header-primary .aui-nav' matches for n separate added nodes, we will inject the command palette n times.
There was a problem hiding this comment.
Yeah, the functions will have to ensure that doesn't happen 👍
faac923 to
1c039a3
Compare
Co-Authored-By: felixfbecker <felix.b@outlook.com>
Co-Authored-By: felixfbecker <felix.b@outlook.com>
|
Sentry issue: BROWSER-EXTENSION-Q5 |
|
Sentry issue: BROWSER-EXTENSION-NB |
|
Sentry issue: BROWSER-EXTENSION-PZ |
#2909 used a mutation observer for code view detection, but still called the
inject*functions on every mutation event. It turns outReactDOM.render()is not idempotent and triggered mutation events in Firefox, resulting in an infinite loop of mutation events.The solution is to only re-render the mounts when a node was added to the DOM that should contain a mount, which means the render only happens once.
To accomplish that, this PR changes all
get*Mountfunctions to receive a container. The mount getter needs to check if the mount belongs into the container, if yes get or create the mount inside, otherwise returnnull.The mount getters are then called with
addedNodesfrom mutation events.This necessitates other changes:
eventLoggertotelemetryServicefor consistency / easier passing).HoverOverlayContainercomponent had unneeded code in it (portals, hover overlay mount container container) that I removedcomponentWillUnmount()and handling Subscriptions was missing in some placesFixes #3180
Fixes #3166
Test plan:
x (Chrome, Firefox)