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

fix: implement stylesheet registration and disallow arbitrary functions #3443

Merged
merged 9 commits into from
Apr 6, 2023

Conversation

jye-sf
Copy link
Contributor

@jye-sf jye-sf commented Apr 4, 2023

Details

Implements registerStylesheet for marking stylesheets generated by the LWC compiler.
Restricts stylesheet evaluation to only those stylesheets.
Throws an error if arbitrary functions are found in the stylesheet list.

Addresses #3122 and #3156

Does this pull request introduce a breaking change?

  • ✅ No, it does not introduce a breaking change.

This has the potential to break use cases relying on behaviors from arbitrary functions being executed as a stylesheet function:

  • For legitimate stylesheets, users have other methods to inject their own styles (<style> tags, adoptedStylesheets, etc).
  • For other arbitrary uses, they should not be relying on this behavior.
  • We may evaluate exposing registerStylesheet to components per [HMR] CSS: Provide a mechanism to register stylesheets  #1887.

Does this pull request introduce an observable change?

  • ⚠️ Yes, it does include an observable change.

GUS work item

W-12040411

@jye-sf jye-sf requested a review from nolanlawson April 4, 2023 23:05
@@ -275,3 +281,18 @@ export function createStylesheet(vm: VM, stylesheets: string[]): VNode[] | null
}
return null;
}

const signedStylesheetSet: Set<StylesheetFactory> = new Set();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I always found it odd that we used Maps/Sets for registering components/templates (rather than a WeakMap/WeakSet), but this is fine since today there is no way to GC these anyway.

In the future, if we ever add the ability to GC/evict components/templates/stylesheets, then we can change these to weak collections.

Copy link
Collaborator

@nolanlawson nolanlawson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Side note: it looks like we won't need to update the ACT compiler, because apparently it doesn't emit stylesheets. (At least, our tests in packages/@lwc/integration-karma/test/act/act-components don't have any.) But it might be good to double-check.

This PR largely LGTM, although of course there is the risk of breakage. People really shouldn't be using arbitrary functions-that-return-strings, but of course they might be. The most prudent would be to make this a warning rather than an error, and roll it out with the reporting API first.

It depends on our appetite for risk, and how much breakage we think this will cause. WDYT?

@jye-sf
Copy link
Contributor Author

jye-sf commented Apr 5, 2023

I'll double check the ACT.

I like the idea of making this a warning first and integrating it into the reporting API as a low-risk rollout. Risk of breakage is fairly low, but I think our appetite for risk is also medium low right now. I'd also like to get Alex's take on our org's overall appetite for risk, so in the meantime, we can use warning+reporting.

@nolanlawson
Copy link
Collaborator

Sounds good. At the very least, we can get early data from first-party components. I suspect those are the most likely to be exploiting loopholes in LWC like this anyway.

@@ -47,6 +48,7 @@ export type ReportingPayloadMapping = {
[ReportingEventId.NonStandardAriaReflection]: NonStandardAriaReflectionPayload;
[ReportingEventId.TemplateMutation]: TemplateMutationPayload;
[ReportingEventId.StylesheetMutation]: StylesheetMutationPayload;
[ReportingEventId.UnexpectedStylesheetContent]: BasePayload;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I originally had a payload interface for this, but I don't see a point since we don't need any properties beyond what's provided by BasePayload. We can always add a new payload interface if that ever changes.

`TypeError: Unexpected LWC stylesheet content found for component <${vm.tagName.toLowerCase()}>.`
);
report(ReportingEventId.UnexpectedStylesheetContent, {
tagName: vm.tagName.toLowerCase(),
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there ever any functional difference between vm.elm.tagName and vm.tagName? I noticed we use the former for non-standard-aria and the latter for cross-root-aria and static stylesheet validation

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think there's a difference, no. I was probably just being inconsistent. 😅

@jye-sf
Copy link
Contributor Author

jye-sf commented Apr 5, 2023

Checked the ACT and as far as I could see we don't emit any stylesheets there. I couldn't find any references to stylesheets except for a single tmpl.stylesheets = [];.

Comment on lines 139 to 141
logError(
`TypeError: Unexpected LWC stylesheet content found for component <${vm.tagName.toLowerCase()}>.`
);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This error should only be logged in dev mode, and should also only be logged once ideally. I'd use the existing logWarnOnce, or create a new one called logErrorOnce. (In practice, it doesn't matter much whether the text is yellow or red IMO. 😄)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Went with logWarnOnce. This should now be consistent with how we're reporting and logging the other deprecated features.

report(ReportingEventId.UnexpectedStylesheetContent, {
tagName: vm.tagName.toLowerCase(),
});
continue;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This continue makes this a functional (breaking) change. My understanding was that we were just going to warn, but still call the stylesheet function?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, let's log a GH issue to track converting the warning into an error, then mention it with a TODO comment here. We can add it to the breaking changes wishlist: #2964

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My bad, that detail slipped my mind. 👍 on logging a GH issue to convert the warning to error.

Copy link
Collaborator

@nolanlawson nolanlawson Apr 5, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To play it safe, can we also add a test to check that the CSS string returned by the arbitrary function is actually inserted into the DOM (e.g. with getComputedStyle)? That would have caught the continue.

Copy link
Collaborator

@nolanlawson nolanlawson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a few nitpicks, otherwise everything LGTM. :shipit:

@jye-sf
Copy link
Contributor Author

jye-sf commented Apr 5, 2023

TODO: #3447

@@ -62,7 +62,7 @@ function checkAndReportViolation(elm: Element, prop: string, isSetter: boolean,
if (process.env.NODE_ENV !== 'production') {
logWarnOnce(
`Element <${elm.tagName.toLowerCase()}> ` +
(isUndefined(vm) ? '' : `owned by <${vm.elm.tagName.toLowerCase()}> `) +
(isUndefined(vm) ? '' : `owned by <${vm.tagName.toLowerCase()}> `) +
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated this to be consistent with everywhere else since this is the only place we do vm.elm.tagName.


// TODO [#3447]: This test verifies existing behavior is maintained and should be removed
// once the warnings are converted into errors.
it('applies the styles if the arbitrary function returns a valid string', () => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added test to verify existing behavior.

divmain pushed a commit that referenced this pull request Apr 27, 2023
This was referenced Apr 27, 2023
jye-sf added a commit that referenced this pull request May 2, 2023
…ns (#3443)

* fix: implement stylesheet registration and disallow arbitrary functions

* feat: log as error and use reporting API

* fix: move to dev-only warning, address PR comments

* fix: add test to verify existing behavior is maintained

* fix: only import registerStylesheet if new styles were compiled
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants