-
Notifications
You must be signed in to change notification settings - Fork 390
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Retrieve the page favicons from the browser and display it on the tab selector #5166
base: main
Are you sure you want to change the base?
Conversation
In the following commit, we are going to be using the sha1 code that we have that relies on window.crypto. We have to add it to jest as well to make sure that we don't break the tests.
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.
Thanks for the patch!
I haven't tried it, but I trust you that you tested it.
I think this should be improved now, because it impacts the web channel protocol:
- make the web channel return binary data instead of the data url. I left these comments in the phabricator patch as well. See below for more specific comments about that.
(+ some nits)
Possibly later, but my preference is that it should be done now:
- I think the icons from pages don't need to go through all the machinery in actions/icons. This machinery was made so that we wouldn't try to display the favicons that are inexistant, which was unavoidable because we were trying to guess a URL. Without this machinery, some browsers would display the "broken image" icon instead of a blank image.
So I think you could have 2 different paths for these 2 types of icons: the ones where we have a data-url, and the ones where we have a normal URL. We know that the data-url works, so we don't need to check for it.
By having these 2 different paths, you could also batch adding all the icons from pages to the state in just one operation, avoiding the cost of regenerating the map for each icon (like mentioned below).
This way you could also avoid the sha1 operation and instead use a counter like mentioned below.
Tell me what you think!
Please add new commits on top of these ones and do not change the current ones (except for rebase if necessary of cousre).
@@ -1017,6 +1022,36 @@ export async function doSymbolicateProfile( | |||
dispatch(doneSymbolicating()); | |||
} | |||
|
|||
export async function retrievePageFaviconsFromBrowser( | |||
dispatch: Dispatch, |
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.
nit: instead of passing dispatch
as a parameter, I'd make this a thunk action, so that dispatch
is injected by the thunk action middleware.
Then in the caller, you'd do await dispatch(retrievePageFaviconsFromBrowser(...))
I think it's better because it makes it clearer in the caller that this will ultimately dispatch an action.
Currently it feels like await retrievePageFaviconsFromBrowser
ought to return something.
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 wanted to do similarly to what we already do for doSymbolicateProfile
above:
profiler/src/actions/receive-profile.js
Lines 986 to 1018 in 1170042
export async function doSymbolicateProfile( | |
dispatch: Dispatch, | |
profile: Profile, | |
symbolStore: SymbolStore | |
) { | |
dispatch(startSymbolicating()); | |
const completionPromises = []; | |
await symbolicateProfile( | |
profile, | |
symbolStore, | |
( | |
threadIndex: ThreadIndex, | |
symbolicationStepInfo: SymbolicationStepInfo | |
) => { | |
completionPromises.push( | |
new Promise((resolve) => { | |
_symbolicationStepQueueSingleton.enqueueSingleSymbolicationStep( | |
dispatch, | |
threadIndex, | |
symbolicationStepInfo, | |
resolve | |
); | |
}) | |
); | |
} | |
); | |
await Promise.all(completionPromises); | |
dispatch(doneSymbolicating()); | |
} |
Both of them are called right after each other. So I would prefer to keep their implementation similar to keep them uniform in the function where we call them:
profiler/src/actions/receive-profile.js
Lines 280 to 285 in 40e3068
await doSymbolicateProfile(dispatch, profile, symbolStore); | |
} | |
} | |
if (browserConnection && pages && pages.length > 0) { | |
await retrievePageFaviconsFromBrowser(dispatch, pages, browserConnection); |
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.
mmm I see (and I was the one who implemented doSymbolicateProfile
initially so I can't blame anyone else :-) )
The difference I see though, is that doSymbolicateProfile
starts the symbolication proces which can take long, while retrievePageFaviconsFromBrowser
is a more direct process. But I admit the difference is thin.
I guess seeing that we pass dispatch
is good enough to know that actions may be dispatched.
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 makes me think that we might want to run doSymbolicateProfile
and retrievePageFaviconsFromBrowser
in parallel. Or at least do retrievePageFaviconsFromBrowser
first, because it's likely much faster than the symbolication, and it may happen that users share their profile before the symbolication ends.
src/actions/receive-profile.js
Outdated
// It appears that an error occurred since the pages and favicons arrays | ||
// have different lengths. Return early without doing anything. |
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.
if that's not normal, it would be good to output a warning or an error to the console so that it's not silently swallowed.
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 could also happen in the case where the backend doesn't support the new webchannel request yet and we fallback to an empty array in the webchannel logic. I would prefer to not pollute the console for now. But we might want to add for later.
src/actions/icons.js
Outdated
async function _classNameFromUrl(url): Promise<string> { | ||
return url.startsWith('data:image/') | ||
? 'dataUrl' + (await sha1(url)) | ||
: url.replace(/[/:.+>< ~()#,]/g, '_'); | ||
} |
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.
(optional)
The goal of this function was to provide always the same class name for the same URL, so that we wouldn't try to insert a lot of identical CSS classes. The other goal was an idempotent operation, so that calling it with the same parameter in 2 different places would generate the same result.
But now that you have this Map, we have a way to ensure this unicity without this trick.
So I wonder if we could do a simple counter with a common prefix instead of a possibly expensive sha1 (expensive depending on the size of the input).
return `favicon-${++faviconCounter}`
What do you think?
The problem with a static counter is for testing, you need to provide a function to reset it for the tests only. Also it might need to change more than just this because of how the class is used currently (but I'm not sure).
(not optional) IMO you can do the sha1 operation for both data urls and and normal urls. Also as a comment you could check for data:
only (but you don't need this check anyway).
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.
Also I believe you moved how the classname was generated here just becase of the fact you made it async. But I don't mind, I feel like it's better to generate the classnames once rather than regenerating them always. And it makes things easier with the counter.
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.
Hmm yeah, you are right about the favicon class name. We don't really need to sha1 the whole url and just can have a counter. I changed it to that one, thanks!
src/actions/receive-profile.js
Outdated
for (let index = 0; index < favicons.length; index++) { | ||
newPages[index] = { | ||
...newPages[index], | ||
favicon: favicons[index], | ||
}; |
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.
(optional)
for (let index = 0; index < favicons.length; index++) { | |
newPages[index] = { | |
...newPages[index], | |
favicon: favicons[index], | |
}; | |
for (let index = 0; index < favicons.length; index++) { | |
if (favicons[index]){ | |
newPages[index] = { | |
...newPages[index], | |
favicon: favicons[index], | |
}; | |
} |
so that we don't add unneeded content to the profile.
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.
If you follow my suggestion from https://phabricator.services.mozilla.com/D225197, that is the WebChannel should return the binary data + the mimetype, then here is the location where you'll generate base64 data using the FIleReader.
I was also wondering it we should store the data url, or a pair (base64 + mimetype), in the profile JSON. But I think the data url makes sense as it contains both these information.
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.
Yeah it think it's easier/simpler to keep a single data url string that contains the both information.
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.
Thanks for the review @julienw! You can find the new commits after the [DO NOT LAND] Enable the tab selector commit.
I changed the frontend to handle the binary data and convert that into the data url once we get them. Also batched the icon/classname logic so we don't have to update the map one by one.
Let me know what you think!
@@ -1017,6 +1022,36 @@ export async function doSymbolicateProfile( | |||
dispatch(doneSymbolicating()); | |||
} | |||
|
|||
export async function retrievePageFaviconsFromBrowser( | |||
dispatch: Dispatch, |
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 wanted to do similarly to what we already do for doSymbolicateProfile
above:
profiler/src/actions/receive-profile.js
Lines 986 to 1018 in 1170042
export async function doSymbolicateProfile( | |
dispatch: Dispatch, | |
profile: Profile, | |
symbolStore: SymbolStore | |
) { | |
dispatch(startSymbolicating()); | |
const completionPromises = []; | |
await symbolicateProfile( | |
profile, | |
symbolStore, | |
( | |
threadIndex: ThreadIndex, | |
symbolicationStepInfo: SymbolicationStepInfo | |
) => { | |
completionPromises.push( | |
new Promise((resolve) => { | |
_symbolicationStepQueueSingleton.enqueueSingleSymbolicationStep( | |
dispatch, | |
threadIndex, | |
symbolicationStepInfo, | |
resolve | |
); | |
}) | |
); | |
} | |
); | |
await Promise.all(completionPromises); | |
dispatch(doneSymbolicating()); | |
} |
Both of them are called right after each other. So I would prefer to keep their implementation similar to keep them uniform in the function where we call them:
profiler/src/actions/receive-profile.js
Lines 280 to 285 in 40e3068
await doSymbolicateProfile(dispatch, profile, symbolStore); | |
} | |
} | |
if (browserConnection && pages && pages.length > 0) { | |
await retrievePageFaviconsFromBrowser(dispatch, pages, browserConnection); |
src/actions/receive-profile.js
Outdated
// It appears that an error occurred since the pages and favicons arrays | ||
// have different lengths. Return early without doing anything. |
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 could also happen in the case where the backend doesn't support the new webchannel request yet and we fallback to an empty array in the webchannel logic. I would prefer to not pollute the console for now. But we might want to add for later.
@@ -71,6 +76,7 @@ class TabSelectorMenuImpl extends React.PureComponent<Props> { | |||
'aria-checked': tabFilter === tabID ? 'false' : 'true', | |||
}} | |||
> | |||
{hasSomeIcons ? <Icon iconUrl={pageData.favicon} /> : null} |
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.
Yeah, I would like to have a placeholder as well. I can work on it as a followup.
Do you have any ideas for possible icons that we can put it here?
Maybe "Globe" for the websites: https://icons.design.firefox.com/viewer/#globe
and "Extensions" icon for the webextensions: https://icons.design.firefox.com/viewer/#extensions
What do you think?
src/actions/receive-profile.js
Outdated
for (let index = 0; index < favicons.length; index++) { | ||
newPages[index] = { | ||
...newPages[index], | ||
favicon: favicons[index], | ||
}; |
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.
Yeah it think it's easier/simpler to keep a single data url string that contains the both information.
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #5166 +/- ##
==========================================
- Coverage 88.56% 88.45% -0.11%
==========================================
Files 307 308 +1
Lines 27936 27997 +61
Branches 7560 7572 +12
==========================================
+ Hits 24741 24766 +25
- Misses 2978 3013 +35
- Partials 217 218 +1 ☔ View full report in Codecov by Sentry. |
This requires changes from Bug 1921778 since it adds the webchannel changes in the backend.
This PR adds the ability to retrieve the favicons for pages as a data url, so we can display them easily in the frontend without needing to fetch them from their url every time we open a profile.
This also changes how we use the
Icon
component and how we compute the css class names for them. Let me know what you think!Note that this doesn't change the other icons that we have in places like call tree. I would prefer to do it as a followup as there are some questions to answer there.
Deploy preview with the page data retrieved already