fix: Added missing chrome privacy api settings - #205
Conversation
66869a3 to
b082082
Compare
| }, | ||
| }; | ||
| // Wrap all "network", "services", "websites" APIs in chrome.privacy.* | ||
| if (extensionAPIs.privacy) { |
There was a problem hiding this comment.
This dereferences the privacy namespace even when not used.
Could you make this a lazy getter? Maybe a getter for each of network, services and websites.
There was a problem hiding this comment.
Good point, will update.
There was a problem hiding this comment.
Updated, I have created separated getters for each of the privacyTypes as you have asked, but I feel like that implementation below is simpler:
// Wrap all "network", "services", "websites" APIs in chrome.privacy.*
const privacy = {
get supported() {
return extensionAPIs.privacy;
},
};
if (privacy.supported) {
apiMetadata.privacy = {};
for (const privacyType of ["network", "services", "websites"]) {
apiMetadata.privacy[privacyType] = {};
for (const privacyName of Object.keys(extensionAPIs.privacy[privacyType])) {
apiMetadata.privacy[privacyType][privacyName] = settingMetadata;
}
}
}What you think?
| apiMetadata.privacy = {}; | ||
| for (const privacyType of ["network", "services", "websites"]) { | ||
| apiMetadata.privacy[privacyType] = {}; | ||
| for (const privacyName in extensionAPIs.privacy[privacyType]) { |
There was a problem hiding this comment.
This would also include enumerable properties from Object.prototype, which shouldn't be exported.
There was a problem hiding this comment.
Good point, will update.
Guess using Object.keys() might be better.
|
@Rob--W I have addressed the comments, but seems like there is some problems with ChromeDriver Most probably I won't be available tomorrow, but will ensure to address anything needed the day after. |
Rob--W
left a comment
There was a problem hiding this comment.
@Rob--W I have addressed the comments, but seems like there is some problems with ChromeDriver
SessionNotCreatedError: session not created: This version of ChromeDriver only supports Chrome version 76.Most probably I won't be available tomorrow, but will ensure to address anything needed the day after.
We should probably bump the chromedriver version again... As done in #199. If you open a pull request in the same format, I'd approve and merge it.
| return {}; | ||
| }}); | ||
| } | ||
| if (privacy.supported) { |
There was a problem hiding this comment.
This is not a lazy getter.
As soon as you dereference the extensionAPIs.privacy object, the browser has to initialize the extension bindings for chrome.privacy object.
A lazy getter is one where you avoid dereferencing the extensionAPIs.privacy object until absolutely necessary.
For example, something like this:
apiMetadata.privacy = {
get network() { // <-- This is a getter. You should probably use Object.defineProperty instead.
let target = extensionAPIs.privacy && extensionAPIs.privacy.network;
if (!target) {
let network = {};
for (let k of Object.keys(target)) {
network[k] = settingMetadata;
}
this.network = network; // <-- This caches the result, hence the "lazy getter" is run only once.
return network;
}
},
};
There was a problem hiding this comment.
Now I feel so stupid, thanks will update.
There was a problem hiding this comment.
@Rob--W Updated, hope now it looks better.
Done -> #208 |
|
|
||
| apiMetadata.privacy = {}; | ||
| for (const type of ["network", "services", "websites"]) { | ||
| Object.defineProperty(apiMetadata.privacy, type, { |
There was a problem hiding this comment.
Add configurable: true, to the properties of the parameter to Object.defineProperty, to ensure that the property can be replaced later.
| return privacyType; | ||
| } | ||
| }, | ||
| set: (value) => value, |
There was a problem hiding this comment.
This is only used internally; no setter necessary.
There was a problem hiding this comment.
When I don't use setters, getters only error is thrown when caching the value as the strict mode is used.
Not sure why this is not being caught by tests.
Steps to reproduce:
npm run build.- Load
dist/browser-polyfill.jsinto a chrome extension. - execute
browser.privacy.services.passwordSavingEnabled.get({});
Observer behavior:
Uncaught TypeError: Cannot set property services of #<Object> which has only a getter
Expected:
No error is thrown.
There was a problem hiding this comment.
This is because you're triggering the setter (at line 505).
There are two ways to replace a getter property:
deletethe original property and assign the value.- Use Object.defineProperty` to replace the property descriptor with one that only has a value.
In a unit test, you should count the number of accesses to chrome.privacy.network when you dereference the chrome.privacy.network object.
After looking at the implementation, it is apparent that the metadata is dereferenced only once and cached afterwards. I guess that you don't even have to replace the getter, so the code can be simplified.
implementation where metadata is dereferenced once:
webextension-polyfill/src/browser-polyfill.js
Lines 256 to 261 in 5f7a845
There was a problem hiding this comment.
In a unit test, you should count the number of accesses to chrome.privacy.network when you dereference the chrome.privacy.network object.
I don't understand this comment, you want me to update the tests in a way?
After looking at the implementation, it is apparent that the metadata is dereferenced only once and cached afterwards. I guess that you don't even have to replace the getter, so the code can be simplified
Sounds good, then I guess I can just remove the line where I overwrite the getter and that's it?
There was a problem hiding this comment.
After looking at the implementation, it is apparent that the metadata is dereferenced only once and cached afterwards. I guess that you don't even have to replace the getter, so the code can be simplified
Done, removed the cache. Hope I understood your comment correctly.
In a unit test, you should count the number of accesses to chrome.privacy.network when you dereference the chrome.privacy.network object.
I need a hint here, not sure how I can implement that.
There was a problem hiding this comment.
Here is an existing test that verifies that a property is accessed only once:
webextension-polyfill/test/test-proxied-properties.js
Lines 140 to 183 in ad207a8
You would have to add a new test that verifies that fakeChrome.privacy.network is accessed only once, even if the test gets window.browser.privacy.network.somePropertyName twice.
It would probably also be good to verify that if fakeChrome.privacy.services is undefined, that window.privacy.network.services is undefined as well.
There was a problem hiding this comment.
@Rob--W I have added a test, looking into existing implementation you provided, but I'm bit unsure about it.
It would probably also be good to verify that if fakeChrome.privacy.services is undefined, that window.privacy.network.services is undefined as well
Not sure if I understood this one, are you referring to something like?
equal(window.browser.privacy.services, undefined);
… has only a getter
e735f85 to
8277c03
Compare
| window.chrome.runtime.onMessage.addListener(() => {}); | ||
| equal(lazyInitCount, 1, "chrome.runtime should be re-used upon accessing chrome.runtime"); | ||
|
|
||
| window.browser.privacy.network.networkPredictionEnabled(); |
There was a problem hiding this comment.
Don't call the function, just check that the returned object looks like a Setting object (object with set, get and clear methods).
|
|
||
| const networkPredictionEnabled = () => true; | ||
| const value = {networkPredictionEnabled}; | ||
| Object.defineProperty(fakeChrome.privacy, "network", {value}); |
There was a problem hiding this comment.
Don't replace the property. Now we're interested in how often privacy.network is dereferenced.
There was a problem hiding this comment.
Noted, done.
It fails, but I can confirm it's not a regression as it was calling twice for the old implementation as well.
Any idea why it's so? Might be a problem with my test?
There was a problem hiding this comment.
It fails, but I can confirm it's not a regression as it was calling twice for the old implementation as well.
This was wrong assumption, apparently I was running test without running npm run build first, Sorry for this, will have a look into it later today, the old tests were passing correctly, the problem is in the new implementation.
There was a problem hiding this comment.
After taking a closer look into implementation, now I see what is the problem.
The problem is that I can't fix it without touching wrapAPIs() code, unless I hardcode everything.
I'm thinking about something like:
const settingMetadata = {
clear: {minArgs: 1, maxArgs: 1},
get: {minArgs: 1, maxArgs: 1},
set: {minArgs: 1, maxArgs: 1},
};
apiMetadata.privacy = {"*": {"*": settingMetadata}};and updating wrapObject():
if (hasOwnProperty(metadata, "*")) {
value = wrapObject(value, wrappers[prop], metadata["*"]);
}
I'm quite uncomfortable updating wrapObject(), but I'll give it a shot and revert it if you say so.
| describe("without side effects", () => { | ||
| it("should proxy non-wrapped methods", () => { | ||
| let lazyInitCount = 0; | ||
| let lazyInitPrivacyCount = 0; |
There was a problem hiding this comment.
Could you create a new describe(...) block to test the privacy API?
| referrersEnabled: settingMetadata, | ||
| }, | ||
| }; | ||
| apiMetadata.privacy = {"*": {"*": settingMetadata}}; |
There was a problem hiding this comment.
I'm not immediately against this implementation, since it is still somewhat readable and concise.
I do wonder whether we should explicitly spell out network, services, and websites.
@rpl Thoughts?
There was a problem hiding this comment.
👍 from me on spelling out the first level and use the "*" to cover all the properties inside those.
There was a problem hiding this comment.
Thanks @rpl and @Rob--W, done.
I was thinking about 2 different implementation:
const privacyType = {"*": settingMetadata};
apiMetadata.privacy = {
network: privacyType,
services: privacyType,
websites: privacyType,
};
apiMetadata.privacy = {};
for (const type of ["network", "services", "websites"]) {
apiMetadata.privacy[type] = {"*": settingMetadata};
}
If you have preference for one or another please let me know which to use. This seem to be most consistent comparing to settingMetadata.
There was a problem hiding this comment.
Keep the current code. It is more readable than the proposed alternatives.
| }, | ||
| }; | ||
|
|
||
| return setupTestDOMWindow(fakeChrome).then(window => { |
There was a problem hiding this comment.
equal(lazyInitCount, 0, ...); to check that chrome.privacy is not accessed at first.
|
@Rob--W Is there anything blocking this issue to be merged into master? Thanks in advance. |
|
Thanks for the patch @Manvel ! |
This PR is for fixing #204