-
Notifications
You must be signed in to change notification settings - Fork 25
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
Instead of assignable FrozenArray, use add / remove #45
Comments
We can do both! add/remove would be sugar, in addition to allowing access to the list. |
Just catching up on this - I'm definitely fond of explicit methods. Here's my pitch: interface DocumentOrShadowRoot {
void adoptStyleSheet(CSSStyleSheet sheet)
void removeStyleSheet(CSSStyleSheet sheet)
} |
The methods as described above might not make it obvious enough that there is an ordering to the style sheets, with later style sheets overriding earlier ones for cases when both apply to an element. |
What about |
(Not that I'm particularly fond of it, just dumping thoughts :)) |
Perhaps the FrozenArray used here could be extended to allow indirect modifications like CSSRuleList ( I'll admit it seems odd to add a whole new API for working with an ordered set of like objects. |
May I ask what the rational was behind making it a frozen array in the first place? It seems a bit odd to make it a frozen array and then re-implement methods to basically allow arbitrary manipulation. |
It's not possible to make an array that the browser "watches" for changes. So, if it were a normal array, you'd do |
Fair. Thank you. |
@developit 's comment on Jan 28 is key: because order is important to the list, you need to be able to insert/remove elements from specific positions, not just the front / end. |
Again, the fact people are introducing race conditions, etc... by writing a simple snippet of code in WICG/webcomponents#759 (comment) is yet another evidence that having add/remove is better than having people assign a FrozenArray. Now I consider this issue as an absolute show stopper. I don't think we want to ever implement this feature in WebKit unless this issue is resolved. |
As an aside, it already feels odd that assigning an array to const sheets = [...document.adoptedStyleSheets, sheet];
document.adoptedStyleSheets = sheets;
sheets.add(otherSheet); // huh? Is there precedent for that elsewhere in the DOM? Having a non-reassignable object with methods for adding and removing feels much more natural, notwithstanding the ergonomic issues around prepending (though I'd expect that to be a very small minority of cases, and maybe not the thing to optimise the API for). |
Very anecdotal, but as an author seeing this in examples: document.adoptedStyleSheets = [ ...document.adoptedStyleSheets, sheet ]; my gut reaction was that it was a useless recreation of the array and I would quickly simplify it to: document.adoptedStyleSheets.push(sheet); Maybe the assignment style suggests that any kind of Array manipulation (push, sort, etc.) would work? (Or maybe there are several other web APIs where you set an Array and get a FrozenArray, with similar ergonomics and restrictions, and I'm just not used to them.) |
The potential race condition linked to in the CSS modules thread is a problem with any of the built-in data structures like arrays. It's not unique to |
Is there a reason a FrozenArray was chosen as the type as opposed to a Could that interface be modified to support for addition, removal and reordering of stylesheet objects (even as a subclass)? |
|
Right, but the question was if it could be made to be more useful … |
No-one is claiming that. But 'this pattern is bug-prone anyway' isn't a reason to embrace it. An API along these lines wouldn't suffer the same problem: document.whatever.add(await import('./styles.css')); |
More specifically, it's a problem with using
Yup, StyleSheetList is just one of those shitty legacy "whoops I guess we have to act like Java" interfaces that the old DOM is plagued with. We didn't want to spread it further, since it's kinda actively hostile to web authors.
With the additional point that we probably want to make prepending as easy as appending, yeah, I'm okay with some sugar methods for append/prepend/remove, and letting array manip handle all the rest, as Emilio suggests. |
I know this is a huge undertaking and tangential aside to this discussion, but I wish there was a dedicated effort to writing a new DOM, and standardizing interfaces and methods and deprecating old ones. Like, I'd love if someone attacked the problem of "if we built a DOM from scratch in 2019, what would it look like?" |
Yeah, I guess none of that actually answered the question of if |
I disagree with that proposition. Consistency is important.
Yes. In fact, I've suggested that we use |
StyleSheetList can't be made modifiable because then it would no longer serve well for document.styleSheets, which is a read-only view onto the list of link/style-element generated style sheets. |
Could it work to have an exotic subclass of
Old exampleclass StyleSheetArray extends Array {
#invalidateCurrentStyles() {
/* Recompute styles, etc, whatever browsers do when new style sheets are added */
}
#maybeInvalidateStyles() {
/* Test if stuff has changed and if so invalidate current styles */
}
constructor() {
super();
return new Proxy(this, {
get(...args) {
const value = Reflect.get(...args);
if (typeof value === 'function') {
const that = this;
return function(...args) {
const result = Reflect.apply(value, this, args);
this.#maybeInvalidateStyles();
return result;
}
}
return value;
},
set(...args) {
const result = Reflect.set(...args);
this.#invalidateCurrentStyles();
return result;
},
/* etc for all the other traps */
});
}
} EDIT: Actually array methods are generic things so just having exotic behavior on e.g.: 'use strict';
function isArrayIndex(string) {
if (typeof string !== 'string') {
return false;
}
const number = Number(string);
return Number.isSafeInteger(number)
&& String(number) === string
&& number >= 0;
}
class StyleSheetArray extends Array {
#updateStyles() {
/* Do usual updates when style list changes */
}
constructor(...args) {
super(...args);
return new Proxy(this, {
defineProperty: (target, prop, descriptor) => {
try {
return Reflect.defineProperty(target, prop, descriptor);
} finally {
if (prop === 'length' || isArrayIndex(prop)) {
this.#updateStyles();
}
}
},
deleteProperty: (target, prop) => {
try {
return Reflect.deleteProperty(target, prop);
} finally {
if (prop === 'length' || isArrayIndex(prop)) {
this.#updateStyles();
}
}
},
});
}
} |
Should this feature be a sort of That sort of object should have the following methods:
Ideally I'd like to see these added to a subclass of Thoughts:
Edit: |
I think a more consistent naming and API with the existing CSSOM interfaces for inserting / removing rules would be good... CSSOM interfaces have:
And same for
would be the bare minimum? I agree that maybe an |
Since you can't observe an existing array, that would only be possible to spec in cases where you assigned another observable array of the correct type, e.g. |
This was discussed today at the Web Components F2F meeting, notes here: https://docs.google.com/document/d/160HetuBh6sLArif1y-heWpe749b3KPnX54AS-aB8UQ4/edit# We discussed the state of this issue, and the primary open issue, which is assignability. (There seemed to be general consensus on the rest, i.e. that we should move
The tentative resolution from the meeting was that the pros outweigh the cons, and we should move ahead with an assignable |
I must once again object to that resolution. |
how about a reset method on ObservableArray? |
@rniwa could you follow-up to #45 (comment) please? |
There is a long history of discussion but I guess the key take away is WICG/webcomponents#759 (comment). The key difference of other properties that allow assignment and this API is that the constructible stylesheet API was made so that parsing happens asynchronously. Even if the parsing itself was made sync, then there is an issue of fetching the stylesheet content itself, which may also involve an asynchronous work. There were a few people who wrote incorrect code just during the discussion of |
By the way, it's highly problematic that this feature doesn't work with declarative shadow DOM. It's actively harmful for the web platform as a whole to introduce a bunch of new APIs that behave differently to or don't work well with another existing API. Such inconsistencies force more cognitive strains on new developers who are learning to use the web platform, and even experienced developers would have to spend more time navigating through dozens of different documentations and keep reminding themselves of which API uses which programming paradigm or idiom. |
I'm not sure how that comment is related. It seems to be from two years ago, which is prior to the introduction of observable arrays. Now that Firefox has no plans to implement declarative shadow DOM, but I think it would be good to get this unblocked. Are the issues with declarative shadow DOM written down in an issue somewhere? |
Well, the point isn't so much that we need asynchronous parsing, it's that asynchronous parsing combined with assignment will result in a racy code. I'd also add that we remain skeptical of this whole feature as it introduces yet another way of inserting a stylesheet into a document, and the claim that this will help with the performance when the same stylesheet is used in multiple shadow roots since modern browser engines like WebKit and Gecko have a mechanism to share the underlying data structures across different shadow trees. |
@rniwa well, once the import of a CSS module script completes the style sheet is parsed, right? Otherwise you wouldn't have a usable |
@rniwa as someone is that previous discussion, I think racy code is possible to write with any mutable collection and multiple actors, even with sync resources, and whether or not that mutability comes from replacement (assignability) or mutation. And I don't think that In any case, the Lit team's highest priority is to see some feature like this ship, assignable or not. Chrome also has a mechanism to share underlying stylesheet data-structures from To me, this pattern of importing CSS modules and adding them to Regarding integration with declarative shadow DOM, I'm confident we can make this work - it's similar to other cross-scope element reference issues that are being worked on - with a requirement for serializability. I'm not sure if we have a central issue for this yet, but if not, let's open a discuss there - I agree it's critical to get that solved, but don't see any blocker or drastically different alternative that would make it easier. |
The FAST team at Microsoft is completely aligned with the Lit team on this issue. We have very similar scenarios and concerns. This is an extremely high priority for us. |
off-thread main-thread parsing? That's news to me, how would such a thing work? I guess it was just a typo? :) Performance shouldn't be different from
Anyhow, Firefox has an implementation of adopted stylesheets behind a flag and I'd be curious if someone can build a test-case that shows any measurable performance benefit compared to But anyhow I think concerns about performance are valid, but kind of off-topic for this particular issue, we can file a separate one. |
Sorry, I meant off-main-thread |
The dev ergonomics of
|
Given that assignment has this common pattern today (because of a lack of mutation APIs) associated with the shadow.adoptedStyleSheets = [ ...shadow.adoptedStyleSheets, componentStylesheet ]; Which is not a great pattern to encourage when we do add mutation methods, it seems to me that we will want a new name associated with the new type to force a code-change. It also makes sense to me that we might not want to support assignment in that new API to avoid web developers just replacing the I suppose there is a use case for clearing any previously assigned stylesheets and adding a whole new set, but based on the discussion above I don't think that's typical. (Clear/reset can be done by setting I could also get behind dropping support for assignment on I don't find the race condition argument for dropping assignment support particularly compelling. As others have pointed out, a potential race condition will still be present even without supporting assignment. Even with document.newAdoptedStyleSheets.push( localSheet1, localSheet2, await import('./remoteSheet3.css') ); |
@justinfagnani sure, I was not debating the ergonomics of the API, just the perf argument :) |
Let me restate that from my team's perspective, it's much, much higher priority that this feature ship in all browsers than that the property remain assignable and keep the same name. |
Sure, I agree! We discussed this on the last F2F and I'm totally fine with the conclusion we reached there. I was just pointing out that @rniwa's initial objection and the performance concerns are two totally different things... I mentioned this in the F2F, but if ObservableArray is compatible with assignability, I personally don't think changing the name just to remove assignability is worth the churn (for both authors and implementors alike). |
I've been using the Here's one way to remove a sheet, for example: root.adoptedStyleSheets = root.adoptedStyleSheets.filter(s => someSheet !== s) With this API, writing racy code would be my own fault, not If people really want to "solve racy code" then a possibility is supporting root.adoptedStyleSheets = [fetch('foo.css'), getStyleSheetAsync(), styleSheet]
root.adoptedStyleSheets.push(fetch('another.css')) But I'd argue this is unnecessary because the developer will also need to know when things are loaded if they wish to avoid FOUC, which means they need to handle async code themselves anyway. Handling async code is a basic part of the web developer job description. Trying to prevent people from having to know how to handle async code is probably futile. |
I think this issue can be closed, since the spec now uses ObservableArray, and multiple browsers have shipped this behavior. |
In cases where this API is used by custom elements, different subclasses may want to add a different stylesheet to
adoptedStyleSheets
. In that case, it's better to have explicitadd
andremove
methods rather than to have author scripts manipulateFrozenArray
.The text was updated successfully, but these errors were encountered: