-
Notifications
You must be signed in to change notification settings - Fork 5
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
Correctly set check mark after accepting a new permission #33
Conversation
Oops, fixing CI now. |
@@ -119,7 +116,7 @@ async function handleClick( | |||
|
|||
throw error; | |||
} finally { | |||
void updateItem(); | |||
void updateItem(tab?.id ? await getTabUrl(tab?.id) : ''); |
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 this only needs to be called on error, then it should be inside the catch statement right? Also I presume that if there's an error the status is "unchecked", which doesn't need a URL check.
On extension with limited permissions, you can't get the tab URL (e.g. I think this fails on chrome://extensions for example)
There's a demo extension in this repo you can use for testing. Use npm run demo:watch
to build 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.
Rather, I suppose:
- restore the code on line 76
- also add the same exact code before
throw error;
right?
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 this only needs to be called on error, then it should be inside the catch statement right?
I did think about that, but I wanted to be able to add handling for other scenarios like this one:
and so it felt like a cleaner separation of concerns to only update the context menu from updateItem()
and then have that function be smart enough to handle all scenarios, rather than having multiple parts of the code updating the menu which could potentially interact in unpredictable and undesirable ways (e.g. race conditions from having multiple event handlers collide).
That said, this is just a preference rather than a strong opinion.
Also I presume that if there's an error the status is "unchecked", which doesn't need a URL check.
I wasn't sure about that. Couldn't the extension already be enabled, in which case if there's an error it should stay checked?
On extension with limited permissions, you can't get the tab URL (e.g. I think this fails on chrome://extensions for example)
There are so many pitfalls in this area it's crazy 🤣
There's a demo extension in this repo you can use for testing. Use
npm run demo:watch
to build it.
Oh thanks, I'll check it out!
Rather, I suppose:
- restore the code on line 76
- also add the same exact code before
throw error;
right?
You mean in addition to removing the finally
clause? Otherwise there could be duplicate menu updates: in both the catch
and finally
clauses.
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 wasn't sure about that. Couldn't the extension already be enabled, in which case if there's an error it should stay checked?
There shouldn't be an error when removing a permission. However… you can't remove a wide permission like *://*/*
this way. If that optional permission is granted, the item should become "✔️ Enabled on all sites". Clicking it would remove that wider permission.
Edit: Opened:
For the intents of this PR, it should assume that removal is successful.
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.
OK, new version coming shortly.
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.
TL;DR: after investigation, I recommend keeping the approach in this PR, but the gory details follow.
So I tried this approach:
and it results in several regressions where the check mark doesn't get set correctly in multiple cases.
For example, switch site access to on click, then enable for a site in the manifest via content menu toggle. Because we're relying on Chrome to update the menu item, it doesn't spot that the site is in the manifest, therefore it (correctly) enables the checkmark but fails to disable (grey out) the menu item. This means that it's now possible to attempt to remove the same permission from the still enabled menu item. In that case, this statement:
There shouldn't be an error when removing a permission.
turns out to be not true, because it results in:
Uncaught (in promise) Error: You cannot remove required permissions.
Even if we add some logic to deal with that case, it feels to me like a very brittle approach to assume that we can ensure chrome.permissions.remove()
will only ever be called in a way to ensure its success, because there are so many different combinations of scenarios which we don't fully understand yet - even in Chrome, let alone other browsers.
As well as being brittle, it would basically require either:
- copying logic into
togglePermission()
fromupdateItem()
which tests URLs against the manifest, which would be a violation of DRY and separation of concerns, or - calling
updateItem()
fromtogglePermissions()
, in which case it's not really any different to just keepingupdateItem()
in thefinally
block.
IMHO this affirms the point I was making earlier about the benefit of separation of concerns: if we have one function which handles interactions with chrome.permissions
(i.e. togglePermission()
, and then a separate function which takes care of ensuring that the menu item is always configured correctly (updateItem()
), then we don't really need to worry about all the ways they can interact with each other. All we need to do is ensure that updateItem()
is called at the appropriate points.
Revisiting your original comments with the benefit of hindsight:
If this only needs to be called on error,
We now know this isn't true, because of the need to disable the menu item in certain cases.
Also I presume that if there's an error the status is "unchecked", which doesn't need a URL check.
We now know this isn't true, since if something prior to this goes wrong, it can allow a misguided attempt to disable a site in the manifest would fail, in which case the status should remain checked.
On extension with limited permissions, you can't get the tab URL (e.g. I think this fails on chrome://extensions for example)
I don't see a problem here, because the code in this PR should still work if the tab URL can't be retrieved. The only minor issue might be that updateItem()
will cause the menu item to be enabled, which doesn't make sense for things like chrome://extensions
. But that could be easily fixed, e.g. by changing the default to enabled: false
.
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 that could be easily fixed, e.g. by changing the default to
enabled: false
.
Nothing is "easily" fixed here obviously haha
If this only needs to be called on error,
We now know this isn't true, because of the need to disable the menu item in certain cases.
I'm only talking about updateItem
calls in action.onClicked
, not tabs.onActivated
and it results in several regressions where the check mark doesn't get set correctly in multiple cases.
As mentioned elsewhere, I really don't want to get into allow access on click
and other Chrome UI stuff. Let's fix #38 first.
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'll open a PR
…nte#29) When the user requests the extension to be given access to the site and confirms this in the consequent browser dialog, the context menu item wasn't being immediately changed to checked. We can fix this by ensuring that the updateItem() function which is responsible for updating the checkmark is called with the correct URL for the tab in question, so that it can detect whether the permission is now granted to the site and update the context menu item accordingly. Note that the browser will automatically enable the checkmark simply by virtue of the context menu item having been clicked. So if the user denies access in the dialog (despite previously requesting it to be given via a user-initiated "gesture", which is the only way the chrome.permissions API allows access to be given), then chrome.contextMenus.update() needs to be called to reverse that enabling of the checkmark. However by calling `updateItem()` within the `finally` block with the correct tab URL, we can ensure the checkmark is always correctly set regardless of what happened. Fixes fregante#29.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
Yes, GitHub is bad in this regard. In fact I documented this in detail a long time ago:
However, it got a bit better over the years. For example, you can click on either of these links to see the difference: (Although in this PR, you will see a force-push which didn't change the code at all. That doesn't mean this feature is broken; it's just because I realised I had created the commit with the wrong author and committer emails, so I fixed the commit metadata and force-pushed to prevent invalid email addresses from polluting
True, I have noticed that a lot of developers really struggle with cleaning git history. I used to contribute to git, so this kind of stuff has been second nature to me for a long time. BTW in case you haven't seen it, Gerrit does code review WAY better. After years of working with both, GitHub code review still feels like bashing my head against the wall every time. And for Open Source repositories, it's possible to have the best of both worlds for free via https://gerrithub.io/ (although keeping GitHub Actions is a bit more effort). But I digress 😆 |
I know, but the difference is that with regular commits you don't have to click. In fact GitHub even has a nice commit dropdown in both the Files tab and the Commits tab. 😃 Also re-pushing the same commit with an issue reference causes noise in the referenced issue: #29 (reference) |
@@ -71,9 +71,6 @@ async function togglePermission(tab: chrome.tabs.Tab, toggle: boolean): Promise< | |||
|
|||
const userAccepted = await chromeP.permissions.request(permissionData); | |||
if (!userAccepted) { | |||
chrome.contextMenus.update(contextMenuId, { | |||
checked: false, | |||
}); | |||
return; |
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 still don't understand what's the problem with this line.
- item is unchecked
- user clicks it
- chrome checks it
- we ask for permission
- user denies ❌
- this piece of code is reached and we uncheck it
- done, no need for
finally
Or if successful:
- item is unchecked
- user clicks it
- chrome checks it
- we ask for permission
- user accepts ✅
- this piece of code is NOT reached
- done, no need for
finally
wither
I can't think of another scenario in which request()
returns true
but it should not be (natively) checked.
To put it in other words, we only have to fix the check:
- if the user rejects
- if it throws
The way I see it, the only reason #29 is an issue is because updateItem()
is called even if successful. That's 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.
I can't think of another scenario in which request() returns true but it should not be (natively) checked.
I've been through so many scenarios now that my head is exploding, but I'll do my best to respond accurately.
I don't think I ever said there was a problem with these three lines; it's just that my PR proposed another way of tackling the issue via modified usage of updateItem()
elsewhere, which made these three lines redundant (since we don't want to be updating the same menu item twice in rapid succession as part of the same event handling).
The problematic situation I described above was not relating to when the user wants to grant permission, but rather when they are trying to remove it:
- item is checked and in the manifest but not disabled (i.e. greyed out)
- user clicks it
- we ask to remove permission
- browser throws an exception because the URL matches a pattern already in the manifest
- at this point we need to make sure that the checkmark stays present
Yes it's true that in step 1 here, the item should be disabled, but as I observed above, this is not yet working reliably in all situations, which is why I was proposing a less brittle approach which doesn't assume that everything else is working perfectly.
But anyway, I think your draft approach in #38 is more promising so hopefully that will work well in which case I'm happy to leave this PR closed.
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.
browser throws an exception because the URL matches a pattern already in the manifest
This is where we keep butting heads. This does not happen if you keep the Chrome UI (#7) out of the equation.
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.
doesn't assume that everything else is working perfectly.
The issue with that is that you're already working on a broken piece of UI, so what you're suggesting is focusing on the wrong part of the code. Let's instead make sure that the UI is correct before the click.
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.
Sure, I've already given up worrying about the Chrome UI for now. However there are still problems, but I'll describe them in the relevant issues and PRs.
Closing this for now, I'm improving the overall logic in a new PR.
I agree with you here. Fixing it in a new PR
Yes. I also opened this: |
Sure. The problem with that is that it's mixing "private" / unclean history (which isn't intended for inclusion in So GitHub forces us to pick one of two suboptimal approaches. You prefer one, I prefer the other, it's all good :-)
Yes that's an annoying downside. It could be worked around by only referencing the issue in the PR and not in the commits. Also technically speaking, fixups commits if written correctly should also reference the issue they are relating to, so that the reviewer knows which issue they are fixing up. For small PRs and small repos like here, this is less relevant because typically the PR only targets a single issue. I normally work in much larger repos with larger teams, where writing comprehensive messages for every single commit and polishing history prior to merge are much more important, so that's where my preference comes from. Anyway, back to the main topic ;-) |
This is from fregante/webext-permission-toggle#38 which is a better fix for fregante/webext-permission-toggle#29 than my own in fregante/webext-permission-toggle#33
This is from fregante/webext-permission-toggle#38 which is a better fix for fregante/webext-permission-toggle#29 than my own in fregante/webext-permission-toggle#33
When the user requests the extension to be given access to the site and confirms this in the consequent browser dialog, the context menu item wasn't being immediately changed to checked.
We can fix this by ensuring that the
updateItem()
function which is responsible for updating the checkmark is called with the correct URL for the tab in question, so that it can detect whether the permission is now granted to the site and update the context menu item accordingly.Note that the browser will automatically enable the checkmark simply by virtue of the context menu item having been clicked. So if the user denies access in the dialog (despite previously requesting it to be given via a user-initiated "gesture", which is the only way the
chrome.permissions
API allows access to be given), thenchrome.contextMenus.update()
needs to be called to reverse that enabling of the checkmark. However by callingupdateItem()
within thefinally
block with the correct tab URL, we can ensure the checkmark is always correctly set regardless of what happened.Fixes #29.