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

UA full version info is too tightly bound to primary brand instead of engine #196

Closed
erik-anderson opened this issue Feb 8, 2021 · 17 comments · Fixed by GoogleChrome/web.dev#6935
Assignees

Comments

@erik-anderson
Copy link
Contributor

Imagine you're a browser embedding an engine that is more broadly used by a larger browser vendor.

In rare occasions, sites may need to work around a platform bug which is later addressed within a major version patch of the shared engine. In this scenario, let's assume it's not otherwise detectable/observable outside of version number.

The way a site would likely get the context for that with UA-Client-Hints is to request the full version via Accept-CH: Sec-CH-UA-Full-Version or calling navigator.userAgentData.getHighEntropyValues(["uaFullVersion"]), parsing it, and then scoping the workaround to builds without the bug fix.

It's also likely the site will do that targeting solely on the version number of the largest browser shipping that engine. At this point, we've lost a lot of the utility of having a fuller set of brands available via the sec-ch-ua header and the navigator.userAgentData.brands list.

To make this more concrete, here's a current example Microsoft Edge UA string:
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36 Edg/88.0.705.63

If Edge were to turn on UA Client Hints, Chrome would report 88.0.4324.150 for the UA full version and Edge would report 88.0.705.63.

Let's say there's a an issue in Chromium versions <= to that version. Even if someone understands it's a Chromium issue affecting multiple browsers and gates their check on "brands contains Chromium", they would need to be aware of every browser's version that maps to that Chromium release. Instead, they might naively author a check like if (majorVersion == 88 && minor == 0 && build <= 4324) { /* tell user to update their browser */ } and break non-Chrome embedders that choose a different versioning scheme.

A browser like Edge could choose to simply report the Chromium version number for the full UA version, but that then precludes a site matching on Edge version number (which would be unfortunate since Edge could have its own bugs that need such targeting).

I think an approach that looks something like this would be more likely to help sites author the correct checks:

  • The base brand version string should report a number with a version format that matches the full version, but zeroed out for the non-major version numbers. i.e. it might look like sec-ch-ua: " Not A;Brand";v="99.0.0.0", "Chromium";v="88.0.0.0", "Microsoft Edge";v="88.0.0.0".

    If full UA version info is requested via Accept-CH, all of the other version parts would be filled in, e.g. sec-ch-ua: " Not A;Brand";v="99.1.300.grease", "Chromium";v="88.0.4324.150", "Microsoft Edge";v="88.0.705.63". Someone parsing the string would now be able to directly target a specific Chromium release across different browser brands.

  • For JS callers, the navigator.userAgentData.brands' version value would work the same way-- placeholder version numbers for consistent parsing. Perhaps also expose a boolean property (navigator.uaData.fullVersionAvailable?) that tells you if the version info is truncated so that version checks don't need to check if all of the non-major version components are zeroed out.

    getHighEntropyValues for a uaFullVersion request might return a brands value with the full version info now filled out and future calls to navigator.userAgentData.brands would also show the full version info. I would prefer to not have a single string passed provided as currently defined due to the aforementioned potential "brand-versus-platform version" confusion it introduces for site owners.

@miketaylr
Copy link
Collaborator

Thanks for the issue, @erik-anderson!

  • The base brand version string should report a number with a version format that matches the full version, but zeroed out for the non-major version numbers. i.e. it might look like sec-ch-ua: " Not A;Brand";v="99.0.0.0", "Chromium";v="88.0.0.0", "Microsoft Edge";v="88.0.0.0".

I think this is an interesting suggestion, and has the side-benefit of reducing default entropy.

But this proposal is also suggesting we change what Sec-CH-UA-Full-Version returns, right? (instead of a sh-string w/ full version of the UA, a sh-list brandlist with full versions).

I'm not sure how I feel about navigator.userAgentData.brands returning different values on its first and N+1 invocations. Won't developers just learn you just have to call it twice? I need to think more about that.

Also, can you provide some theoretical sample code to help me understand how navigator.uaData.fullVersionAvailable would be used?

I would prefer to not have a single string passed provided as currently defined due to the aforementioned potential "brand-versus-platform version" confusion it introduces for site owners.

Can you clarify? I'm having a hard time understanding what this means. Thanks.

@erik-anderson
Copy link
Contributor Author

Thanks for the issue, @erik-anderson!

  • The base brand version string should report a number with a version format that matches the full version, but zeroed out for the non-major version numbers. i.e. it might look like sec-ch-ua: " Not A;Brand";v="99.0.0.0", "Chromium";v="88.0.0.0", "Microsoft Edge";v="88.0.0.0".

I think this is an interesting suggestion, and has the side-benefit of reducing default entropy.

But this proposal is also suggesting we change what Sec-CH-UA-Full-Version returns, right? (instead of a sh-string w/ full version of the UA, a sh-list brandlist with full versions).

Sec-CH-UA-Full-Version should either include a brand list or be removed entirely in favor of moving the availability directly into Sec-CH-UA.

After thinking about it more, I'm also not sure if placeholder values in Sec-CH-UA would be a great model. It's an indirect signal and would preclude a vendor from reporting a shipping a version with all zeroes in the other components if they wanted to. Putting the full info in the Sec-CH-UA header, though, still seems reasonable. Entities parsing it would still split on the . and just understand if there's one value that's all they got. Duplicating most of the content between the headers and further inflating the size of the request headers seems mildly undesirable.

I'm not sure how I feel about navigator.userAgentData.brands returning different values on its first and N+1 invocations. Won't developers just learn you just have to call it twice? I need to think more about that.

Yes, developers would need to call it a second time. Alternatively, we could consider updating getHighEntropyValues to return a FrozenArray<NavigatorUABrandVersion> for the response to a uaFullVersion request (i.e. getHighEntropyValues would no longer be just a sequence of DOMStrings).

Also, can you provide some theoretical sample code to help me understand how navigator.uaData.fullVersionAvailable would be used?

If we chose to put placeholder '0' values for version components, then you can imagine sites would need to add special code to handle that. Here's some sample script that shows a few patterns:

  const uaData = navigator.userAgentData;
  const brands = uaData.brands;     // [ {brand: "Google Chrome", version: "84"}, {brand: "Chromium", version: "84"} ]

  for (brandVersionPair in navigator.userAgentData.brands) {
    if (brandVersionPair.brand == "Chromium") {
      const versionParts = brandVersionPair.version.split('.');
      // Chromium 84 versions older than 84.0.124.0 is broken; require a higher version.
      if (versionParts[0] === 84 && versionParts[1] === 0 && versionParts[2] <= 123) {
        // This is broken if the third part is 0 because full UA info isn't available.
      }

      // Fix the previous check to handle not having full version info.
      let haveFullVersionInfo = false;
      if (versionParts[1] === 0 && versionParts[2] === 0 && versionParts[3] === 0) {
        haveFullVersionInfo = true;
      }
      if (haveFullVersionInfo && versionParts[0] === 84 && versionParts[1] === 0 && versionParts[2] <= 123) {
        // This check doesn't accidentally assume it's too old of a version if full version info isn't available.
      }

      // A potential simplification:
      if (uaData.fullVersionAvailable && versionParts[0] === 84 && versionParts[1] === 0 && versionParts[2] <= 123) {
        // This check also works.
      }
    }
  }

That said, this is still pretty messy. Keeping it separated in the getHighEntropyValues API would probably be better on the JavaScript side. That would then look like:

  const uaData = navigator.userAgentData;
  const brands = uaData.brands;     // [ {brand: "Google Chrome", version: "84"}, {brand: "Chromium", version: "84"} ]

  (async ()=>{
    const highEntropyValues = await uaData.getHighEntropyValues(
      ["platform", "uaFullVersion"]);
    const platform = highEntropyValues.platform;               // "Mac OS X"
    const uaFullVersions = highEntropyValues.uaFullVersion;     // a FrozenArray<NavigatorUABrandVersion>

    for (brandVersionPair in uaFullVersions) {
      if (brandVersionPair.brand == "Chromium") {
        const versionParts = brandVersionPair.version.split('.');
        if (versionParts[0] === 84 && versionParts[1] === 0 && versionParts[2] <= 123) {
          // Do something special.
        }
      }
    }
  })();
  }

I would prefer to not have a single string passed provided as currently defined due to the aforementioned potential "brand-versus-platform version" confusion it introduces for site owners.

Can you clarify? I'm having a hard time understanding what this means. Thanks.

Sorry! I agree this was hard to parse.

What I was trying to reiterate that the Sec-CH-UA-Full-Version and the JavaScript equivalent which currently return a single version value is likely to be harmful since it would likely encourage site authors to check against the dominant brand's version. Any API exposing version info should expose the info for all brands.

@jwrosewell
Copy link

Would a structured list of keys and guidance on values help? I've contemplated this in issue 200.

If there is only one value per key and the browser vendor (as identified by the user) and layout engine (embedded in multiple browsers) are two separate keys isn't the problem raised here largely addressed?

@miketaylr
Copy link
Collaborator

I think this is an interesting suggestion, and has the side-benefit of reducing default entropy.

(just pointing out to myself that my comment doesn't make sense, thanks everyone for being kind and ignoring it)

Sec-CH-UA-Full-Version should either include a brand list or be removed entirely in favor of moving the availability directly into Sec-CH-UA.

The first suggestion is something to consider - maybe as Sec-CH-UA-Full-Versions in case sites are relying on this now (half-joking) - I'll ponder that a bit this over the coming days. I do have concerns about adding the full version info into Sec-CH-UA by default, cause then it'll cease to be a "low-entropy" header: https://wicg.github.io/client-hints-infrastructure/#low-entropy-table

I think the issue of how to make this API useful for embedders is an important one.

cc @yoavweiss in case he has some insights.

@amtunlimited
Copy link
Contributor

(Wrote this before seeing Mike's comment)

I personally don't like the idea of changing the format of the Sec-CH-UA header based on the presence separate client hint. I think the one-to-one mapping of hints to headers is super important. If we were to go this route, turning Sec-CH-UA-Full-Version would be the way to go, or possibly adding a new hint that would be that list if we didn't want to muddle with a currently defined one. (Sec-CH-UA-Version-List? Bikes gonna shed)

One mildly interesting fingerprinting question: are there times when the full version of a derivative browser (I don't know the proper term here) isn't incremented when a new version of the base browser is up-streamed/rebased/whatever?

Put another way, are there situations were a the full brand-list of full versions isn't a one-to-one relationship with the full version of the derived browser? Not a blocker or anything, but my have interesting entropy implications to consider.

@erik-anderson
Copy link
Contributor Author

One mildly interesting fingerprinting question: are there times when the full version of a derivative browser (I don't know the proper term here) isn't incremented when a new version of the base browser is up-streamed/rebased/whatever?

Put another way, are there situations were a the full brand-list of full versions isn't a one-to-one relationship with the full version of the derived browser? Not a blocker or anything, but my have interesting entropy implications to consider.

I don't currently anticipate that. If the browser is picking up new platform changes, as part of releasing that update it would also presumably bump its own version to a higher number.

The other way around isn't the case, though-- if that browser is bumping its own version due to changes it made while not updating what it's taken from the common engine, that version may not get bumped (e.g. Edge's would increase, but the Chromium version wouldn't).

@rowan-m
Copy link

rowan-m commented Feb 17, 2021

I feel slightly concerned about encouraging the assumption that NewBrowser x.x.x being based on BaseBrowser y.y.y means it will exhibit the same bugs or features as BaseBrowser y.y.y. I can see it potentially penalising browsers that may fix or change behaviour downstream. I do get the benefit for user experience in communicating where they're potentially impacted by a bug, but this does start to feel quite brittle in the way GREASE is meant to combat.

@erik-anderson
Copy link
Contributor Author

I feel slightly concerned about encouraging the assumption that NewBrowser x.x.x being based on BaseBrowser y.y.y means it will exhibit the same bugs or features as BaseBrowser y.y.y. I can see it potentially penalising browsers that may fix or change behaviour downstream. I do get the benefit for user experience in communicating where they're potentially impacted by a bug, but this does start to feel quite brittle in the way GREASE is meant to combat.

The current state seems to discourage NewBrowser x.x.x from reporting its version at all, otherwise it risks a BaseBrowserEngine z.z.z version check failing because x.x.x doesn't match the z.z.z scheme.

As a result, it would more likely choose to report the BaseBrowserEngine y.y.y version to maximize the odds of passing the site check.

Can you clarify your concern? Are you saying you share the concern about the current API encouraging brittle checks tied to the dominant browser tied to each engine? Or that exposing the full version info about all of the brands would introduce other undesirable side effects?

@amtunlimited
Copy link
Contributor

I believe what @rowan-m is trying to say is that listing both NewBrowser and BaseBrowser's full version implys a level of compatibility that's not necessarily a safe assumption, even in terms of things like bug fixes

@miketaylr
Copy link
Collaborator

miketaylr commented Mar 12, 2021

emerges from a pile of email

So, I'm still thinking about this problem and had a thought:

Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36 Edg/88.0.705.63

@erik-anderson do you think this problem is exacerbated by the fact that Edge tracks the major number of Chrome?

I can totally see developers seeing the same Major version, and same Engine, and making assumptions that the Build will be a signal they can fork behavior on. And if they did, build 705 for Chrome was somewhere between Chrome 11 and 12 (if Wikipedia can be trusted).

Here's a terrible, non-API suggested fix: change the Build to always be higher than Chrome's (say, by 100 or whatever), or even more terrible, add some arbitrary number to the major version. 🙈

Also, have y'all seen any real-world compat bugs from this UA sniffing scenario (or, a non-UA-CH version) yet?

@erik-anderson
Copy link
Contributor Author

emerges from a pile of email

So, I'm still thinking about this problem and had a thought:

Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36 Edg/88.0.705.63

@erik-anderson do you think this problem is exacerbated by the fact that Edge tracks the major number of Chrome?

I can totally see developers seeing the same Major version, and same Engine, and making assumptions that the Build will be a signal they can fork behavior on. And if they did, build 705 for Chrome was somewhere between Chrome 11 and 12 (if Wikipedia can be trusted).

I think it's actually made less impactful because we track the same major version number. For at least broader version-based checks we'll squeak through.

My general concern is that we went to all of this trouble to GREASE the UA brands/major version list and then we simultaneously expose a version number that is very much specific to just one of those brands. I'm not actually sure how a site is actually supposed to know which brand the full version info is supposed to be associated with.

Here's a terrible, non-API suggested fix: change the Build to always be higher than Chrome's (say, by 100 or whatever), or even more terrible, add some arbitrary number to the major version. 🙈

Also, have y'all seen any real-world compat bugs from this UA sniffing scenario (or, a non-UA-CH version) yet?

I have not yet seen breakage here. However, what triggered me to think through this more and file this issue is because I was talking to site owners for Microsoft Edge-associated content to discuss with them what it would look like if they were to look at UA Client Hints first. One of their use cases was actually providing different content based on the full version number.

If/when we end up seeing some sites and/or libraries code to very specific Chrome version boundaries in the future (no browser check surprises me at this point), we'd likely look at just exposing the Chromium version and simultaneously consider doing something undesirable like exposing a second, proprietary version header that reveals the true Edge version.

@miketaylr
Copy link
Collaborator

I'm not actually sure how a site is actually supposed to know which brand the full version info is supposed to be associated with.

🤔 I'll keep chewing on this. Ideally we can solve for this and not require other UAs to have to implement something else because the API in insufficient.

@miketaylr
Copy link
Collaborator

Circling back to your original proposal, and re-wording it just to see if I understand:

  1. We update the definition of sec-ch-ua to include "zeroed-out" minor/build/patch strings by default, e.g.:
sec-ch-ua: "Google Chrome";v="89.0.0.0", "Chromium";v="89.0.0.0", ";Not A Brand";v="99.0"

Side note: not all browser engines follow Chromium's MAJOR.MINOR.BUILD.PATCH format. For example, Firefox will only report MAJOR.MINOR at first, and then MAJOR.MINOR.PATCH if a dot release is released (but, they only expose MAJOR.MINOR in the UA string: https://bugzilla.mozilla.org/show_bug.cgi?id=572659 -- I would assume they would be consistent were they to implement UA-Ch). Safari also currently exposes MAJOR.MINOR.PATCH in the UA string, as a potential UA-CH implementer.

(GREASE could possibly help here and prevent sites from expecting a certain number of trailing 0s.)

Some imaginary headers:

sec-ch-ua: "Firefox";v="89.0", "Gecko";v="89.0", ";Not A Brand";v="100.0.0"
sec-ch-ua: "Safari";v="14.0.0", "WebKit";v="1.0.0", ";Not A Brand";v="12.0.0.0"

(The downside is now you have to do more parsing to get the major version, if that's all you wanted)

  1. Someone requests the full version via Accept-CH: Sec-CH-UA-Full-Version. The next request will have the following headers:
sec-ch-ua: "Microsoft Edge";v="89.0.774.63", "Chromium";v="89.0.4389.90", ";Not A Brand";v="99.0.1.2.3.4.5.6"
sec-ch-ua-full-version: "89.0.774.63"

Question: could a site use requesting sec-ch-ua-full-version for some users and not others be used to store an extra bit of info about them through sec-ch-ua? (I guess this "full-version mode" could be emphemeral somehow, but that sounds maybe messy to keep track of).

Would this issue be solved if we instead encouraged always displaying the full "engine" brand/equivalence class version (if a browser decides to include it)?:

sec-ch-ua: "Microsoft Edge";v="89", "Chromium";v="89.0.4389.90", ";Not A Brand";v="99"

But that also increases the default entropy of sec-ch-ua. 🤔

But, perhaps Sec-CH-UA-Full-Version-List (or whatever) would satisfy the use case of being able to get at the Chromium and other brands full versions?

Sec-CH-UA-Full-Version-List: "Microsoft Edge";v="89.0.774.63", "Chromium";v="89.0.4389.90", ";Not A Brand";v="99.0.1.2.3.4.5.6"

Thoughts @erik-anderson?

@erik-anderson
Copy link
Contributor Author

  1. Someone requests the full version via Accept-CH: Sec-CH-UA-Full-Version. The next request will have the following headers:
sec-ch-ua: "Microsoft Edge";v="89.0.774.63", "Chromium";v="89.0.4389.90", ";Not A Brand";v="99.0.1.2.3.4.5.6"
sec-ch-ua-full-version: "89.0.774.63"

Question: could a site use requesting sec-ch-ua-full-version for some users and not others be used to store an extra bit of info about them through sec-ch-ua? (I guess this "full-version mode" could be emphemeral somehow, but that sounds maybe messy to keep track of).

Presumably it wouldn't be any more info than could be observed with the presence or absence of the sec-ch-full-version header.

Would this issue be solved if we instead encouraged always displaying the full "engine" brand/equivalence class version (if a browser decides to include it)?:

sec-ch-ua: "Microsoft Edge";v="89", "Chromium";v="89.0.4389.90", ";Not A Brand";v="99"

But that also increases the default entropy of sec-ch-ua. 🤔

Yes, this might be reasonable. It does increase default entropy, but given the rapid rollout schedule we see with Chrome and Edge releases, at least for those browsers it probably wouldn't be significant since we'd expect almost all users to be mapped to one or two values within a given major release.

But, perhaps Sec-CH-UA-Full-Version-List (or whatever) would satisfy the use case of being able to get at the Chromium and other brands full versions?

Sec-CH-UA-Full-Version-List: "Microsoft Edge";v="89.0.774.63", "Chromium";v="89.0.4389.90", ";Not A Brand";v="99.0.1.2.3.4.5.6"

Yes, this could be reasonable. If we went with this, I would strongly advocate for removing Sec-Ch-UA-Full-Version (or, depending on compat risk, replace the definition of it with the list form) to discourage sites from using a bad pattern. That said, if we did support both header variants, we would likely make Edge report the equivalent value to what Chrome does (which, in practice, is also the same value as the Chromium version).

@miketaylr
Copy link
Collaborator

I would strongly advocate for removing Sec-Ch-UA-Full-Version (or, depending on compat risk, replace the definition of it with the list form) to discourage sites from using a bad pattern

Yeah, having both co-exist in the medium- to long-term seems confusing and prone to mistakes.

@amtunlimited
Copy link
Contributor

amtunlimited commented Apr 1, 2021

I'd throw my hat in for Sec-CH-UA-Full-Version-List et al, mostly because I don't like changing the definition of one Client Hint header based on the presence of a different Client Hint token in the cache.

(The fact that it took a non-trivial amount of time to figure out how to word that sentence makes me worry about the added complexity to the spec as well :P)

@miketaylr miketaylr self-assigned this Jun 23, 2021
@jyasskin
Copy link
Member

mozilla/standards-positions#552 also flags the problem of having a single entry in Sec-CH-UA-Full-Version that can only apply to one of the GREASEd UAs.

miketaylr added a commit to miketaylr/ua-client-hints that referenced this issue Aug 11, 2021
miketaylr added a commit to miketaylr/ua-client-hints that referenced this issue Aug 11, 2021
miketaylr added a commit to miketaylr/ua-client-hints that referenced this issue Aug 11, 2021
miketaylr added a commit to miketaylr/ua-client-hints that referenced this issue Aug 13, 2021
miketaylr added a commit to miketaylr/ua-client-hints that referenced this issue Aug 13, 2021
miketaylr added a commit to miketaylr/ua-client-hints that referenced this issue Aug 13, 2021
miketaylr added a commit to miketaylr/ua-client-hints that referenced this issue Aug 13, 2021
miketaylr added a commit to miketaylr/ua-client-hints that referenced this issue Aug 31, 2021
miketaylr added a commit to miketaylr/ua-client-hints that referenced this issue Aug 31, 2021
miketaylr added a commit to miketaylr/ua-client-hints that referenced this issue Aug 31, 2021
miketaylr added a commit to miketaylr/ua-client-hints that referenced this issue Aug 31, 2021
miketaylr added a commit to miketaylr/ua-client-hints that referenced this issue Sep 3, 2021
miketaylr added a commit to miketaylr/ua-client-hints that referenced this issue Sep 9, 2021
It should be more clear this is a type, and not a single value.
miketaylr added a commit to miketaylr/ua-client-hints that referenced this issue Sep 9, 2021
miketaylr added a commit to miketaylr/ua-client-hints that referenced this issue Sep 10, 2021
miketaylr added a commit to miketaylr/ua-client-hints that referenced this issue Sep 13, 2021
soffieswan041 added a commit to soffieswan041/ua-client-hints that referenced this issue Sep 20, 2021
Tanych added a commit to Tanych/web.dev that referenced this issue Dec 1, 2021
As we are going to deprecate the existing client hint `Sec-CH-UA-Full-Version`, using `Sec-CH-UA-Full-Version-List` instead. For details please check WICG/ua-client-hints#196
jpmedley pushed a commit to GoogleChrome/web.dev that referenced this issue Dec 9, 2021
As we are going to deprecate the existing client hint `Sec-CH-UA-Full-Version`, using `Sec-CH-UA-Full-Version-List` instead. For details please check WICG/ua-client-hints#196
klyok pushed a commit to klyok/web.dev that referenced this issue Dec 15, 2021
As we are going to deprecate the existing client hint `Sec-CH-UA-Full-Version`, using `Sec-CH-UA-Full-Version-List` instead. For details please check WICG/ua-client-hints#196
Tanych added a commit to Tanych/content that referenced this issue Mar 3, 2022
As we are going to deprecate the existing client hint Sec-CH-UA-Full-Version, using Sec-CH-UA-Full-Version-List instead. For details please check WICG/ua-client-hints#196. Update the documents to match the spec: https://wicg.github.io/ua-client-hints/#interface
Tanych added a commit to Tanych/content that referenced this issue Mar 4, 2022
As we are going to deprecate the existing client hint Sec-CH-UA-Full-Version, using Sec-CH-UA-Full-Version-List instead. For details please check WICG/ua-client-hints#196. Update the documents to match the spec: https://wicg.github.io/ua-client-hints/#interface
hamishwillee pushed a commit to mdn/content that referenced this issue Mar 8, 2022
* update gethighentropyvalues client hints options

As we are going to deprecate the existing client hint Sec-CH-UA-Full-Version, using Sec-CH-UA-Full-Version-List instead. For details please check WICG/ua-client-hints#196. Update the documents to match the spec: https://wicg.github.io/ua-client-hints/#interface
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 a pull request may close this issue.

6 participants