@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.mjs 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
  import * as EFFECTS from '@galacean/effects';
@@ -609,28 +609,39 @@ function _renderDOMToImage() {
609
609
  "data:application/x-font"
610
610
  ];
611
611
  /**
612
- * 规范化 URL 属性值:解码 HTML 实体和百分号编码,
613
- * 移除控制字符和空白,转小写后检查是否包含危险协议
614
- */ function normalizeUrlAttr(value) {
615
- var normalized = value;
616
- // 解码 HTML 实体(数字和命名)
617
- normalized = normalized.replace(/&#x([0-9a-fA-F]+);?/g, function(_, hex) {
612
+ * 解码 HTML 实体(数字和常用命名实体),不做其他变换。
613
+ * 用于把从 HTML 字面字符串提取出的 URL 还原为 fetch 时浏览器实际会发起的 URL。
614
+ *
615
+ * 必须做这一步,否则 `<img src="...?a=1&amp;b=2">` 提取到的字面 `&amp;` 会被直接
616
+ * 拼进 XHR 请求,CDN 收到 `?a=1&amp;b=2` 视作 query 参数名 `amp;b`,普遍返回 404。
617
+ */ function decodeHtmlEntities(value) {
618
+ return value.replace(/&#x([0-9a-fA-F]+);?/g, function(_, hex) {
618
619
  return String.fromCharCode(parseInt(hex, 16));
619
- });
620
- normalized = normalized.replace(/&#(\d+);?/g, function(_, dec) {
620
+ }).replace(/&#(\d+);?/g, function(_, dec) {
621
621
  return String.fromCharCode(parseInt(dec, 10));
622
- });
623
- normalized = normalized.replace(/&(amp|lt|gt|quot|apos);/gi, function(match, name) {
622
+ }).replace(/&([a-zA-Z]+);/gi, function(match, name) {
624
623
  var map = {
625
624
  amp: "&",
626
625
  lt: "<",
627
626
  gt: ">",
628
627
  quot: '"',
629
- apos: "'"
628
+ apos: "'",
629
+ // 安全相关:协议分隔符和空白字符,防止绕过 isDangerousUrl 检查
630
+ colon: ":",
631
+ semi: ";",
632
+ tab: " ",
633
+ newline: "\n",
634
+ nbsp: "\xa0"
630
635
  };
631
636
  var _map_name_toLowerCase;
632
637
  return (_map_name_toLowerCase = map[name.toLowerCase()]) != null ? _map_name_toLowerCase : match;
633
638
  });
639
+ }
640
+ /**
641
+ * 规范化 URL 属性值:解码 HTML 实体和百分号编码,
642
+ * 移除控制字符和空白,转小写后检查是否包含危险协议
643
+ */ function normalizeUrlAttr(value) {
644
+ var normalized = decodeHtmlEntities(value);
634
645
  // 解码百分号编码
635
646
  try {
636
647
  normalized = decodeURIComponent(normalized);
@@ -943,15 +954,20 @@ function _inlineImageSources() {
943
954
  4,
944
955
  promisePool(urls.map(function(url) {
945
956
  return /*#__PURE__*/ _async_to_generator(function() {
946
- var _, _tmp, e;
957
+ var fetchUrl, _, _tmp, e;
947
958
  return __generator(this, function(_state) {
948
959
  switch(_state.label){
949
960
  case 0:
961
+ // fetch 前必须解码 HTML 实体,否则 URL 含 &amp; 等会被字面拼进请求,
962
+ // 导致 CDN 返回 404;Map key 仍用原始字符串,replaceUrls 才能精准匹配 HTML 原文。
963
+ fetchUrl = decodeHtmlEntities(url);
964
+ _state.label = 1;
965
+ case 1:
950
966
  _state.trys.push([
951
- 0,
952
- 2,
967
+ 1,
968
+ 3,
953
969
  ,
954
- 3
970
+ 4
955
971
  ]);
956
972
  _ = urlToBase64.set;
957
973
  _tmp = [
@@ -959,24 +975,24 @@ function _inlineImageSources() {
959
975
  ];
960
976
  return [
961
977
  4,
962
- fetchAsBase64(url)
978
+ fetchAsBase64(fetchUrl)
963
979
  ];
964
- case 1:
980
+ case 2:
965
981
  _.apply(urlToBase64, _tmp.concat([
966
982
  _state.sent()
967
983
  ]));
968
984
  return [
969
985
  3,
970
- 3
986
+ 4
971
987
  ];
972
- case 2:
988
+ case 3:
973
989
  e = _state.sent();
974
- logger.warn('inlineImageSources: Failed to fetch "' + url + '".', e);
990
+ logger.warn('inlineImageSources: Failed to fetch "' + fetchUrl + '". ' + FETCH_FAIL_HINT, e);
975
991
  return [
976
992
  3,
977
- 3
993
+ 4
978
994
  ];
979
- case 3:
995
+ case 4:
980
996
  return [
981
997
  2
982
998
  ];
@@ -1036,15 +1052,18 @@ function _inlineFontSources() {
1036
1052
  4,
1037
1053
  promisePool(urls.map(function(url) {
1038
1054
  return /*#__PURE__*/ _async_to_generator(function() {
1039
- var _, _tmp, e;
1055
+ var fetchUrl, _, _tmp, e;
1040
1056
  return __generator(this, function(_state) {
1041
1057
  switch(_state.label){
1042
1058
  case 0:
1059
+ fetchUrl = decodeHtmlEntities(url);
1060
+ _state.label = 1;
1061
+ case 1:
1043
1062
  _state.trys.push([
1044
- 0,
1045
- 2,
1063
+ 1,
1064
+ 3,
1046
1065
  ,
1047
- 3
1066
+ 4
1048
1067
  ]);
1049
1068
  _ = urlToBase64.set;
1050
1069
  _tmp = [
@@ -1052,24 +1071,24 @@ function _inlineFontSources() {
1052
1071
  ];
1053
1072
  return [
1054
1073
  4,
1055
- fetchAsBase64(url)
1074
+ fetchAsBase64(fetchUrl)
1056
1075
  ];
1057
- case 1:
1076
+ case 2:
1058
1077
  _.apply(urlToBase64, _tmp.concat([
1059
1078
  _state.sent()
1060
1079
  ]));
1061
1080
  return [
1062
1081
  3,
1063
- 3
1082
+ 4
1064
1083
  ];
1065
- case 2:
1084
+ case 3:
1066
1085
  e = _state.sent();
1067
- logger.warn('inlineFontSources: Failed to fetch font "' + url + '".', e);
1086
+ logger.warn('inlineFontSources: Failed to fetch font "' + fetchUrl + '". ' + FETCH_FAIL_HINT, e);
1068
1087
  return [
1069
1088
  3,
1070
- 3
1089
+ 4
1071
1090
  ];
1072
- case 3:
1091
+ case 4:
1073
1092
  return [
1074
1093
  2
1075
1094
  ];
@@ -1250,10 +1269,14 @@ function _inlineFontSources() {
1250
1269
  if (targetAttrs.includes(lowerName)) {
1251
1270
  var base64 = urlToBase64.get(value);
1252
1271
  if (base64) {
1253
- out += section.slice(nameStart, valueStart) + base64 + (quote != null ? quote : "") + section.slice(attrEnd, attrEnd);
1254
- // 上一行末尾追加的 '' 是为了清晰;attrEnd 之后的内容由外层循环继续处理
1255
- // 注意:当 quote 不为 null 时,前缀 section.slice(nameStart, valueStart) 已经包含开引号;
1256
- // 我们追加 base64 + 闭合引号 即可。
1272
+ if (quote === null) {
1273
+ // 原属性无引号时必须补引号:base64 ;,/=+ 等字符,无引号会被 HTML parser
1274
+ // 在首个 ; 或空白处截断 src 值,导致图片加载失败。
1275
+ out += section.slice(nameStart, valueStart) + '"' + base64 + '"';
1276
+ } else {
1277
+ // 有引号时 section.slice(nameStart, valueStart) 已包含开引号,只需追加闭合引号。
1278
+ out += section.slice(nameStart, valueStart) + base64 + quote;
1279
+ }
1257
1280
  i = attrEnd;
1258
1281
  continue;
1259
1282
  }
@@ -1300,6 +1323,7 @@ function _inlineFontSources() {
1300
1323
  var MAX_FETCH_CONCURRENCY = 6; // 最大并发获取数
1301
1324
  var MAX_RESOURCE_BYTES = 10 * 1024 * 1024; // 单个资源最大 10MB
1302
1325
  var MAX_URLS = 100; // 单次内联处理的最大 URL 数量
1326
+ /** 内联资源 fetch 失败时附加的排查 hint,覆盖业务最常踩的三种原因 */ var FETCH_FAIL_HINT = "可能原因: (1) 资源服务器未开启 CORS(需 Access-Control-Allow-Origin 响应头); " + "(2) URL 不可访问 (404 / 403); (3) 网络异常。";
1303
1327
  function promisePool(tasks, concurrency) {
1304
1328
  return _promisePool.apply(this, arguments);
1305
1329
  }
@@ -1693,7 +1717,7 @@ DomContentComponent = __decorate([
1693
1717
  effectsClass(DATA_TYPE)
1694
1718
  ], DomContentComponent);
1695
1719
 
1696
- var version = "2.9.1";
1720
+ var version = "2.9.3";
1697
1721
  registerPlugin("dom-content", DomContentLoader);
1698
1722
  logger.info("Plugin dom-content version: " + version + ".");
1699
1723
  if (version !== EFFECTS.version) {