-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Change browser extension to use a single MutationObserver #2909
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome start 👍
Also not touched other code hosts than GitHub yet.
It seems like this change would be code host-agnostic. What do you have in mind?
The functions that take MutationRecords can now be unit tested.
🎉
? null | ||
: originalDOM.getCodeElementFromTarget(target), | ||
} | ||
selectionsChanges.subscribe(selections => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't look right, it assumes that all visible view components have the same selections.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But wasn't that the behaviour before too? The selections are a property of the code host, so I don't see a way to distinguish which selections belong to which code view
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The behaviour wasn't exactly the same before - because we only considered the latest code view as relevant, when selections changed they were only updated on the latest code view.
This explicitly sets the same selections on all code views, which is knowingly wrong and I'd like us to avoid that. PTAL https://github.com/sourcegraph/sourcegraph/pull/2937 which offers a solution.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But only considering the "latest" code view was not correct, since there really isn't such a thing as the "latest" code view (the order in which code views are emitted is random as it depends on MutationObserver and the API requests to fetch contents). I think the previous code would still have the previous selections on the non-"latest" code view so selections did not just apply to the latest
// TODO optimize | ||
// Maybe getMount() would have to be replaced by a function that determines if a new mount was added from a given mutation record | ||
|
||
// injectCommandPalette({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What happened to command palette injection?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just commented out things I hadn't vetted for idempotency yet
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
vetted for idempotency
IMO, idempotency should be provided by the implementor of CodeHost
, not the user. This will allow for more logic to stay out of this file. It also seems like the correct place since the code host knows what the DOM looks like but this file should be ignorant of anything code host specific.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ijsnow could you clarify what you mean by that?
) | ||
// .pipe(emitWhenIntersecting(250)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's your thinking on doing away with emitWithIntersecting()
? I assume it was put in place for performance reasons when we used to scrape document contents from the DOM, but now that we're fetching the file contents instead, we're probably OK without.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just commented it out to exclude it as a possible source of why code views would not be detected properly. So far I've not noticed a perf problem. Maybe @ijsnow can comment on the reasoning behind it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Emitting only when code views are visible or about to be visible was ported over from the old way of handling code hosts. It was added by Kingy in that implementation. It was indeed for performance problems. While the need for it may be reduced, I don't think it would hurt to keep it in.
@@ -156,16 +157,18 @@ export interface CodeHost { | |||
*/ | |||
getOverlayMount?: () => HTMLElement | null | |||
|
|||
// Code views and code view resolver form a union |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know this is probably a "note to self", but it's probably worth being reworded & moved to the docstrings of codeViewSpecs
and codeViewSpecResolver
:
The set of code views tracked on a page is the union of all code views found using codeViewSpecs and codeViewResolver
(or similar)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am dissatisfied by the redundancy of codeViewSpecs
and codeViewSpecResolver
. It causes redundant code to handle both, but every codeViewSpec
could be expressed by a codeViewResolver
. However, multiple codeViewSpecs
are allowed, but only one codeViewSpecResolver
. I would like to unify them somehow. We could also think about the usefullness of specifying a selector vs simply having (container: Element) => CodeViewWithoutSelector[]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could also think about the usefullness of specifying a selector vs simply having
(container: Element) => CodeViewWithoutSelector[]
Why would the resolve function return an array?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ijsnow that would not be a resolve function, it would just return all the code views including their elements given a container.
client/browser/src/libs/code_intelligence/code_intelligence.tsx
Outdated
Show resolved
Hide resolved
Having selections as a property of `CodeHost` is a problem, because [it is incorrect to assume that all code views on the page have the same selections](https://github.com/sourcegraph/sourcegraph/pull/2909#discussion_r268261523). This adds `getSelections` and `observeSelections as optional properties of `CodeViewSpec`, and changes the code to make sure that when selections change, we don't update _all visible view components_ with the same spec of selection. This assumes that only adding `getSelections()` and `observeSelections()` to single file code views is "good enough", which is still an imperfect assumption, but at least avoids having code that explicitly sets the same selections on a bunch of code views.
There is a compile error in |
@@ -156,16 +157,18 @@ export interface CodeHost { | |||
*/ | |||
getOverlayMount?: () => HTMLElement | null | |||
|
|||
// Code views and code view resolver form a union |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could also think about the usefullness of specifying a selector vs simply having
(container: Element) => CodeViewWithoutSelector[]
Why would the resolve function return an array?
// TODO optimize | ||
// Maybe getMount() would have to be replaced by a function that determines if a new mount was added from a given mutation record | ||
|
||
// injectCommandPalette({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
vetted for idempotency
IMO, idempotency should be provided by the implementor of CodeHost
, not the user. This will allow for more logic to stay out of this file. It also seems like the correct place since the code host knows what the DOM looks like but this file should be ignorant of anything code host specific.
) | ||
// .pipe(emitWhenIntersecting(250)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Emitting only when code views are visible or about to be visible was ported over from the old way of handling code hosts. It was added by Kingy in that implementation. It was indeed for performance problems. While the need for it may be reduced, I don't think it would hurt to keep it in.
async function injectModules(): Promise<void> { | ||
// This is added so that the browser extension doesn't | ||
// interfere with the native Phabricator integration. | ||
// TODO is this racy? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is not intended to be racy. The logic is that the browser extension code runs after all other code is parsed/ran in the document. The native integration runs via a script tag added to the document by Phabricator. However, I just realized that we don't account for async code. I believe that it generally works but we should add the global class name that we look for to an element in the function that loads the phabricator bundle.
Codecov Report
|
)" This reverts commit 9475593.
Refactors browser extension to use a single MutationObserver whose events are passed as an Observable to functions, instead of only injecting things once and never removing them.
Code views are then detected inside added and removed nodes. When a node with a code view got removed, its state gets properly cleaned up.
It fixes a bunch of bugs/wrong assumptions I found - for example, switchMap was used on the codeViews Observable which could race to randomly drop code views, and the selections and view components code assumed only the latest code view emitted was relevant. Hoverify was never unsubscribed from.
Also does a few renames that made things less confusing for me, e.g.
CodeView
->CodeViewSpec
,CodeView.codeView
->CodeView.codeViewElement
Also removes a hacky MutationObserver workaround in favor of the real fix sourcegraph/codeintellify#100
I've not touched unit tests yet. The functions that take MutationRecords can now be unit tested.
Also not touched other code hosts than GitHub yet.
Fixes #2162
Fixes https://github.com/sourcegraph/sourcegraph/issues/2958
Fixes #3103
Fixes #955
Tested on