Skip to content
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

Invalid Web IDL: Sanitizer.config attribute type cannot be dictionary #107

Closed
tidoust opened this issue Jul 7, 2021 · 19 comments
Closed
Labels
tag-tracker Group bringing to attention of the TAG, or tracked by the TAG but not needing response.

Comments

@tidoust
Copy link
Contributor

tidoust commented Jul 7, 2021

A recent commit switched config to be an attribute getter. However, that is forbidden in Web IDL: "the type of the attribute [...] must not be a nullable or non-nullable version of [...] a dictionary type".

(I suspect one reason for the rule is that devs expect attributes to return the same object, as in obj.config === obj.config, and that cannot easily be enforced when a readonly attribute returns a dictionary)

tidoust added a commit to w3c/webref that referenced this issue Jul 7, 2021
@otherdaniel
Copy link
Collaborator

Right. This was the reason for originally doing it as methods. However, we've repeatedly been told that this is bad API ergonomics, and that developers would expect to use attribute syntax (s.config instead of s.config()).

At this point, I'm rather confused whether I'm using the syntax wrong -- that is, we can get our attribute syntax for .config, but not with an actual attribute -- or whether we're just caught between two different expectations (accessor syntax vs. object identity for accessor values) and get to pick which one we violate.

Can I return a dictionary copy and get accessor syntax in WebIDL? How?

@dontcallmedom
Copy link

a couple of non-fully-formed thoughts:

Paging @LeaVerou since this relates to the feedback she conveyed on the spec.

@dontcallmedom dontcallmedom added the tag-tracker Group bringing to attention of the TAG, or tracked by the TAG but not needing response. label Jul 7, 2021
tidoust added a commit to w3c/webref that referenced this issue Jul 7, 2021
@tidoust
Copy link
Contributor Author

tidoust commented Jul 7, 2021

Related TAG design principle: Attributes should behave like data properties which says "Ensure that obj.attribute === obj.attribute is always true. Don’t create a new value each time the getter is called".

@LeaVerou
Copy link

LeaVerou commented Jul 7, 2021

Related TAG design principle: Attributes should behave like data properties which says "Ensure that obj.attribute === obj.attribute is always true. Don’t create a new value each time the getter is called".

Does .config create a new value every time the getter is called?

@otherdaniel
Copy link
Collaborator

Related TAG design principle: Attributes should behave like data properties which says "Ensure that obj.attribute === obj.attribute is always true. Don’t create a new value each time the getter is called".

Does .config create a new value every time the getter is called?

I don't think WebIDL has an opinion on that, since WebIDL is convinced this doen't exist.

The 'naive' implementation (at least in Chromium) would indeed create a new value each time, but that could also be implemented differently by memo-izing the value once and then returning the same value each time

@dontcallmedom
Copy link

re memoizing, whatwg/webidl#981 (comment) alludes to a would-be [Cached] extended attribute (but also note the concern re mutability)

@otherdaniel
Copy link
Collaborator

re memoizing, heycam/webidl#981 (comment) alludes to a would-be [Cached] extended attribute (but also note the concern re mutability)

Oh, right. If we were to return the same dictionary, then that dictionary could be modified by a first caller, and the second caller would then see the same, modified dictionary. That's just a bad idea. So after a second thought, I'm going to say that .config would indeed create a new value every time. Which would disregard the s.config === s.config expectation.

@LeaVerou
Copy link

LeaVerou commented Jul 7, 2021

I think it's fine to create a new object when the configuration actually changes, the spirit of the TAG guidance is that you should return the same object if the value hasn't changed.

@otherdaniel
Copy link
Collaborator

otherdaniel commented Jul 7, 2021

Well, Sanitizers don't change configurations anyways; configuration is a constructor thing. The point is that JavaScript doesn't have constant objects. I.e.:

const s = new Sanitizer( ...bla bla...);
let c1 = s.config():
let c2 = s.config();
c1.hello = "world";  // Modify the dictionary we received.
"world" == c2.hello;  // true., c1 & c2 are the same object, and thus c2 "sees" the modification of c1

That could be very extra suprising if some part of the code modifies the return value - for example, to build a new configuration - and then another part of the code tries to examine it. Example:

const main_sanitizer = new Sanitizer(...);  // A carefully configured sanitizer for our application.

// Somewhere in libary we want to be slightly more strict.
// Intent: Build a new sanitizer based on main_sanitizer.
// Reality: Have modified main_sanitizer's config.
let config = main_sanitizer.config;
config.blockElements.push("table", "td", "th", "tr");  // We hate tables.
const my_sanitizer = new Sanitizer(config);

// Somewhere else entirely:
let my_config = main_sanitizer.config.  // Oops. Now we're really seeing my_sanitizer's config.

Which is a pity, because the code above looks nice to me. That's how I'd want to use this.

@Sora2455
Copy link

Sora2455 commented Jul 7, 2021

JavaScript has frozen objects, but I don't think that resolves the problem here because freezing an array won't stop someone calling push etc on it.

@LeaVerou
Copy link

LeaVerou commented Jul 8, 2021

@Sora2455 Yes, it will. You can try it out yourself:

> let a = Object.freeze([1, 2, 3]); a.push(4)
VM188:1 Uncaught TypeError: Cannot add property 3, object is not extensible
    at Array.push (<anonymous>)
    at <anonymous>:1:3

@otherdaniel
Copy link
Collaborator

Thanks for bringin up 'freeze'. Frozen is apparently shallow, so Lea's example will no longer work if it's an array as a dictionary value. But I guess we could freeze recursively.

Is there precedent for relying on 'frozen' return values in JS APIs?

@LeaVerou
Copy link

LeaVerou commented Jul 8, 2021

Thanks for bringin up 'freeze'. Frozen is apparently shallow, so Lea's example will no longer work if it's an array as a dictionary value. But I guess we could freeze recursively.

Is there precedent for relying on 'frozen' return values in JS APIs?

I can't think of anything for objects, but there's a lot of precedent with lists of values. Here's the long ongoing related discussion.

@domenic
Copy link

domenic commented Jul 8, 2021

I'd strongly recommend using a method. There's no precedent for deeply frozen objects in JavaScript or web APIs (which is why the pattern is prohibited by Web IDL).

@domenic
Copy link

domenic commented Jul 8, 2021

The other usual alternative is flattening the properties, e.g. instead of using sanitizer.config.allowElements using sanitizer.allowElements or sanitizer.configAllowElements. That looks a bit less suitable here though.

@otherdaniel
Copy link
Collaborator

Hmm. So far, I'm not seeing a viable solution, or even consensus on a general direction. I'll revert PR #104 and close out this issue.

Thanks all for the discussion, and please do feel free to re-open this if there's additional info or a good proposal on how to solve this.

@dontcallmedom
Copy link

when reverting #104, I would suggest again that getConfiguration() would be better than config()

otherdaniel added a commit to otherdaniel/purification that referenced this issue Jul 9, 2021
This reverts commit f3f52ce.

See discussion at WICG#107.
otherdaniel added a commit that referenced this issue Jul 9, 2021
Revert "Change config to be an attribute getter."
  This reverts commit f3f52ce.
  See discussion at #107.

Also rename the config/defaultConfig methods to getConfiguration/getDefaultConfiguration, to better fit with existing web platform precedent.
@otherdaniel
Copy link
Collaborator

Reverted. Also renamed the methods to .getConfiguration() / .getDefaultConfiguration().

@LeaVerou
Copy link

LeaVerou commented Jul 9, 2021

Coming here late, but +1 for using a function, I think that is the least surprising option.
(to clarify, I wasn't suggesting deeply frozen objects at any point, just answering related questions)

blueboxd pushed a commit to blueboxd/chromium-legacy that referenced this issue Jul 12, 2021
Refs:
- github.com/WICG/sanitizer-api/issues/92
- github.com/WICG/sanitizer-api/issues/107
- WICG/sanitizer-api#108

Bug: 1213893
Change-Id: Ibbc89e2678107f835517af9743e5a1eeca3911d2
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3015576
Reviewed-by: Yifan Luo <lyf@chromium.org>
Commit-Queue: Daniel Vogelheim <vogelheim@chromium.org>
Cr-Commit-Position: refs/heads/master@{#900407}
mjfroman pushed a commit to mjfroman/moz-libwebrtc-third-party that referenced this issue Oct 14, 2022
Refs:
- github.com/WICG/sanitizer-api/issues/92
- github.com/WICG/sanitizer-api/issues/107
- WICG/sanitizer-api#108

Bug: 1213893
Change-Id: Ibbc89e2678107f835517af9743e5a1eeca3911d2
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3015576
Reviewed-by: Yifan Luo <lyf@chromium.org>
Commit-Queue: Daniel Vogelheim <vogelheim@chromium.org>
Cr-Commit-Position: refs/heads/master@{#900407}
NOKEYCHECK=True
GitOrigin-RevId: 66474c9aa1cf094fcbcf75ebea5d11255b17bd81
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
tag-tracker Group bringing to attention of the TAG, or tracked by the TAG but not needing response.
Projects
None yet
Development

No branches or pull requests

6 participants