@galacean/effects-plugin-dom-content 2.9.1 → 2.9.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@
3
3
  * Description: Galacean Effects plugin for rendering HTML/CSS DOM content as texture
4
4
  * Author: Ant Group CO., Ltd.
5
5
  * Contributors: 陈茉
6
- * Version: v2.9.1
6
+ * Version: v2.9.3
7
7
  */
8
8
 
9
9
  'use strict';
@@ -632,28 +632,39 @@ function _renderDOMToImage() {
632
632
  "data:application/x-font"
633
633
  ];
634
634
  /**
635
- * 规范化 URL 属性值:解码 HTML 实体和百分号编码,
636
- * 移除控制字符和空白,转小写后检查是否包含危险协议
637
- */ function normalizeUrlAttr(value) {
638
- var normalized = value;
639
- // 解码 HTML 实体(数字和命名)
640
- normalized = normalized.replace(/&#x([0-9a-fA-F]+);?/g, function(_, hex) {
635
+ * 解码 HTML 实体(数字和常用命名实体),不做其他变换。
636
+ * 用于把从 HTML 字面字符串提取出的 URL 还原为 fetch 时浏览器实际会发起的 URL。
637
+ *
638
+ * 必须做这一步,否则 `<img src="...?a=1&amp;b=2">` 提取到的字面 `&amp;` 会被直接
639
+ * 拼进 XHR 请求,CDN 收到 `?a=1&amp;b=2` 视作 query 参数名 `amp;b`,普遍返回 404。
640
+ */ function decodeHtmlEntities(value) {
641
+ return value.replace(/&#x([0-9a-fA-F]+);?/g, function(_, hex) {
641
642
  return String.fromCharCode(parseInt(hex, 16));
642
- });
643
- normalized = normalized.replace(/&#(\d+);?/g, function(_, dec) {
643
+ }).replace(/&#(\d+);?/g, function(_, dec) {
644
644
  return String.fromCharCode(parseInt(dec, 10));
645
- });
646
- normalized = normalized.replace(/&(amp|lt|gt|quot|apos);/gi, function(match, name) {
645
+ }).replace(/&([a-zA-Z]+);/gi, function(match, name) {
647
646
  var map = {
648
647
  amp: "&",
649
648
  lt: "<",
650
649
  gt: ">",
651
650
  quot: '"',
652
- apos: "'"
651
+ apos: "'",
652
+ // 安全相关:协议分隔符和空白字符,防止绕过 isDangerousUrl 检查
653
+ colon: ":",
654
+ semi: ";",
655
+ tab: " ",
656
+ newline: "\n",
657
+ nbsp: "\xa0"
653
658
  };
654
659
  var _map_name_toLowerCase;
655
660
  return (_map_name_toLowerCase = map[name.toLowerCase()]) != null ? _map_name_toLowerCase : match;
656
661
  });
662
+ }
663
+ /**
664
+ * 规范化 URL 属性值:解码 HTML 实体和百分号编码,
665
+ * 移除控制字符和空白,转小写后检查是否包含危险协议
666
+ */ function normalizeUrlAttr(value) {
667
+ var normalized = decodeHtmlEntities(value);
657
668
  // 解码百分号编码
658
669
  try {
659
670
  normalized = decodeURIComponent(normalized);
@@ -966,15 +977,20 @@ function _inlineImageSources() {
966
977
  4,
967
978
  promisePool(urls.map(function(url) {
968
979
  return /*#__PURE__*/ _async_to_generator(function() {
969
- var _, _tmp, e;
980
+ var fetchUrl, _, _tmp, e;
970
981
  return __generator(this, function(_state) {
971
982
  switch(_state.label){
972
983
  case 0:
984
+ // fetch 前必须解码 HTML 实体,否则 URL 含 &amp; 等会被字面拼进请求,
985
+ // 导致 CDN 返回 404;Map key 仍用原始字符串,replaceUrls 才能精准匹配 HTML 原文。
986
+ fetchUrl = decodeHtmlEntities(url);
987
+ _state.label = 1;
988
+ case 1:
973
989
  _state.trys.push([
974
- 0,
975
- 2,
990
+ 1,
991
+ 3,
976
992
  ,
977
- 3
993
+ 4
978
994
  ]);
979
995
  _ = urlToBase64.set;
980
996
  _tmp = [
@@ -982,24 +998,24 @@ function _inlineImageSources() {
982
998
  ];
983
999
  return [
984
1000
  4,
985
- fetchAsBase64(url)
1001
+ fetchAsBase64(fetchUrl)
986
1002
  ];
987
- case 1:
1003
+ case 2:
988
1004
  _.apply(urlToBase64, _tmp.concat([
989
1005
  _state.sent()
990
1006
  ]));
991
1007
  return [
992
1008
  3,
993
- 3
1009
+ 4
994
1010
  ];
995
- case 2:
1011
+ case 3:
996
1012
  e = _state.sent();
997
- EFFECTS.logger.warn('inlineImageSources: Failed to fetch "' + url + '".', e);
1013
+ EFFECTS.logger.warn('inlineImageSources: Failed to fetch "' + fetchUrl + '". ' + FETCH_FAIL_HINT, e);
998
1014
  return [
999
1015
  3,
1000
- 3
1016
+ 4
1001
1017
  ];
1002
- case 3:
1018
+ case 4:
1003
1019
  return [
1004
1020
  2
1005
1021
  ];
@@ -1059,15 +1075,18 @@ function _inlineFontSources() {
1059
1075
  4,
1060
1076
  promisePool(urls.map(function(url) {
1061
1077
  return /*#__PURE__*/ _async_to_generator(function() {
1062
- var _, _tmp, e;
1078
+ var fetchUrl, _, _tmp, e;
1063
1079
  return __generator(this, function(_state) {
1064
1080
  switch(_state.label){
1065
1081
  case 0:
1082
+ fetchUrl = decodeHtmlEntities(url);
1083
+ _state.label = 1;
1084
+ case 1:
1066
1085
  _state.trys.push([
1067
- 0,
1068
- 2,
1086
+ 1,
1087
+ 3,
1069
1088
  ,
1070
- 3
1089
+ 4
1071
1090
  ]);
1072
1091
  _ = urlToBase64.set;
1073
1092
  _tmp = [
@@ -1075,24 +1094,24 @@ function _inlineFontSources() {
1075
1094
  ];
1076
1095
  return [
1077
1096
  4,
1078
- fetchAsBase64(url)
1097
+ fetchAsBase64(fetchUrl)
1079
1098
  ];
1080
- case 1:
1099
+ case 2:
1081
1100
  _.apply(urlToBase64, _tmp.concat([
1082
1101
  _state.sent()
1083
1102
  ]));
1084
1103
  return [
1085
1104
  3,
1086
- 3
1105
+ 4
1087
1106
  ];
1088
- case 2:
1107
+ case 3:
1089
1108
  e = _state.sent();
1090
- EFFECTS.logger.warn('inlineFontSources: Failed to fetch font "' + url + '".', e);
1109
+ EFFECTS.logger.warn('inlineFontSources: Failed to fetch font "' + fetchUrl + '". ' + FETCH_FAIL_HINT, e);
1091
1110
  return [
1092
1111
  3,
1093
- 3
1112
+ 4
1094
1113
  ];
1095
- case 3:
1114
+ case 4:
1096
1115
  return [
1097
1116
  2
1098
1117
  ];
@@ -1273,10 +1292,14 @@ function _inlineFontSources() {
1273
1292
  if (targetAttrs.includes(lowerName)) {
1274
1293
  var base64 = urlToBase64.get(value);
1275
1294
  if (base64) {
1276
- out += section.slice(nameStart, valueStart) + base64 + (quote != null ? quote : "") + section.slice(attrEnd, attrEnd);
1277
- // 上一行末尾追加的 '' 是为了清晰;attrEnd 之后的内容由外层循环继续处理
1278
- // 注意:当 quote 不为 null 时,前缀 section.slice(nameStart, valueStart) 已经包含开引号;
1279
- // 我们追加 base64 + 闭合引号 即可。
1295
+ if (quote === null) {
1296
+ // 原属性无引号时必须补引号:base64 ;,/=+ 等字符,无引号会被 HTML parser
1297
+ // 在首个 ; 或空白处截断 src 值,导致图片加载失败。
1298
+ out += section.slice(nameStart, valueStart) + '"' + base64 + '"';
1299
+ } else {
1300
+ // 有引号时 section.slice(nameStart, valueStart) 已包含开引号,只需追加闭合引号。
1301
+ out += section.slice(nameStart, valueStart) + base64 + quote;
1302
+ }
1280
1303
  i = attrEnd;
1281
1304
  continue;
1282
1305
  }
@@ -1323,6 +1346,7 @@ function _inlineFontSources() {
1323
1346
  var MAX_FETCH_CONCURRENCY = 6; // 最大并发获取数
1324
1347
  var MAX_RESOURCE_BYTES = 10 * 1024 * 1024; // 单个资源最大 10MB
1325
1348
  var MAX_URLS = 100; // 单次内联处理的最大 URL 数量
1349
+ /** 内联资源 fetch 失败时附加的排查 hint,覆盖业务最常踩的三种原因 */ var FETCH_FAIL_HINT = "可能原因: (1) 资源服务器未开启 CORS(需 Access-Control-Allow-Origin 响应头); " + "(2) URL 不可访问 (404 / 403); (3) 网络异常。";
1326
1350
  function promisePool(tasks, concurrency) {
1327
1351
  return _promisePool.apply(this, arguments);
1328
1352
  }
@@ -1716,7 +1740,7 @@ exports.DomContentComponent = __decorate([
1716
1740
  EFFECTS.effectsClass(DATA_TYPE)
1717
1741
  ], exports.DomContentComponent);
1718
1742
 
1719
- var version = "2.9.1";
1743
+ var version = "2.9.3";
1720
1744
  EFFECTS.registerPlugin("dom-content", DomContentLoader);
1721
1745
  EFFECTS.logger.info("Plugin dom-content version: " + version + ".");
1722
1746
  if (version !== EFFECTS__namespace.version) {