canvasCssVariablesPolyfill.js 3.4 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980
  1. /*
  2. * Copyright (C) 2017 - present Instructure, Inc.
  3. *
  4. * This file is part of Canvas.
  5. *
  6. * Canvas is free software: you can redistribute it and/or modify it under
  7. * the terms of the GNU Affero General Public License as published by the Free
  8. * Software Foundation, version 3 of the License.
  9. *
  10. * Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
  11. * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  12. * A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
  13. * details.
  14. *
  15. * You should have received a copy of the GNU Affero General Public License along
  16. * with this program. If not, see <http://www.gnu.org/licenses/>.
  17. */
  18. import {get} from 'jquery'
  19. import brandableVariables from '../stylesheets/brandable_variables.json'
  20. const images = brandableVariables
  21. .reduce((acc, cur) => acc.concat(cur.variables), []) // flatten
  22. .filter(e => e.type === 'image')
  23. .map(e => e.variable_name)
  24. const variablesMap = window.CANVAS_ACTIVE_BRAND_VARIABLES || {}
  25. // makes a regex that will match any occurrence of any of the brandable css variables in a stylesheet
  26. const variablesRegex = new RegExp(`\\bvar\\(\\s*--(${Object.keys(variablesMap).join('|')})\\s*\\)`, 'g')
  27. export default function processSheet (element) {
  28. const replaceCssVariablesWithStaticValues = (cssText) => {
  29. let replacedAtLeastOneVar = false
  30. const replacedCss = cssText.replace(variablesRegex, (match, name) => {
  31. // if this variable exists in CANVAS_ACTIVE_BRAND_VARIABLES, replace it with it's value.
  32. // otherwise, leave it unchanged
  33. let replacement = variablesMap[name]
  34. if (replacement) {
  35. replacedAtLeastOneVar = true
  36. // the json contains raw urls for images, wrap them in css `url(...)` syntax.
  37. if (images.includes(name)) replacement = `url('${replacement}')`
  38. return replacement
  39. } else {
  40. return match
  41. }
  42. })
  43. if (replacedAtLeastOneVar) element.sheet.cssText = replacedCss
  44. // give anyone trying to debug things a hint that we processed this file
  45. element.setAttribute('data-css-variables-polyfilled', replacedAtLeastOneVar)
  46. }
  47. const url = element.href
  48. const cacheKey = `cssPolyfillCache-${url}`
  49. const cached = sessionStorage[cacheKey]
  50. if (cached) {
  51. replaceCssVariablesWithStaticValues(cached)
  52. } else {
  53. // Edge tries to reuse the cached resource it downloaded for the <link... tag for this,
  54. // but since when it made that request it didn't include an `origin:` request header,
  55. // cloudfront won't include the `access-control-allow-origin: *` response header.
  56. // so when it tries to reuse that response it fails with "No access-control-allow-origin header".
  57. // I wish Edge would either treat it as a new request (because the new request has different request headers)
  58. // and issue a new http request or that cloudfront would include `access-control-allow-origin: *` even
  59. // when an `origin:` header is not present, then we wouldn't need this.
  60. const urlWithCacheBuster = `${url}?forceEdgeToDownloadNewResourceSoItHasAccessControlAllowOriginHeader`
  61. get(urlWithCacheBuster).then((cssText) => {
  62. replaceCssVariablesWithStaticValues(cssText)
  63. sessionStorage.setItem(cacheKey, cssText)
  64. })
  65. }
  66. }
  67. // run polyfill against all stylesheets on the page
  68. [].forEach.call(document.querySelectorAll('link[rel="stylesheet"]'), processSheet)