@webskill/sdk 0.22.0 → 0.23.0
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/agent.d.ts +3 -3
- package/dist/agent.js +168 -45
- package/dist/{approval-DWQlDPbY.js → approval-7CuZS3Z4.js} +3 -3
- package/dist/browser.d.ts +14 -4
- package/dist/browser.js +364 -48
- package/dist/{geometry-wx5ZTYzi.js → geometry-CRhuySKV.js} +8 -1
- package/dist/governance.d.ts +22 -4
- package/dist/governance.js +100 -10
- package/dist/{index-YPd8ZSEa.d.ts → index-7No55dRb.d.ts} +6 -3
- package/dist/{index-BiO9_XRk.d.ts → index-ShvCB5I9.d.ts} +2 -2
- package/dist/{index-CjwdZQOS.d.ts → index-V76_OFj2.d.ts} +92 -12
- package/dist/index.d.ts +4 -4
- package/dist/index.js +10 -8
- package/dist/{linkedDocument-CIuh_Yp0.js → linkedDocument-CZZOl9cO.js} +44 -4
- package/dist/mcp.d.ts +2 -2
- package/dist/node.d.ts +3 -3
- package/dist/node.js +12 -5
- package/dist/{pathSecurity-B1owvJAF.js → pathSecurity-CirkQwWP.js} +12 -1
- package/dist/{skill-CAJMsLod.js → skill-WSUDp6A1.js} +1 -1
- package/dist/{skillVersionStore-B24Jjjry-BY1H7Krp.d.ts → skillVersionStore-S4_HJ_Fl-Cvy9LqVu.d.ts} +35 -4
- package/dist/testing.d.ts +1 -1
- package/dist/{types-CpDRZ0rA-BM5FI9oN.d.ts → types-CpDRZ0rA-BiZBXGm4.d.ts} +8 -1
- package/dist/ui-react.d.ts +3 -3
- package/dist/ui-react.js +28 -19
- package/dist/ui-vue.d.ts +1 -1
- package/dist/ui.d.ts +3 -3
- package/dist/{webSkillApi-Dy-Zjv0y.js → webSkillApi-D5VMUnrv.js} +2 -2
- package/package.json +1 -1
package/dist/browser.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { n as messageOf$1, t as WebSkillError } from "./errors-BDZNpC13.js";
|
|
2
|
-
import { A as SKILL_MANIFEST_FILE, D as verifySkillSignature, M as buildManifest, O as MANIFEST_EXCLUDED_FILES, P as verifyManifest, b as parseSkillMarkdown, c as unzipWithLimits, f as SKILL_PACK_FILE, i as buildCatalog, k as SKILLS_LOCKFILE, m as parseSkillPackManifest, n as SkillDiscovery, o as readResponseWithLimit, p as exportSkills, t as validateSkills, u as detectSkillArchiveShapeFromFs, w as readSkillSignature, y as isValidSkillName } from "./skill-
|
|
3
|
-
import {
|
|
2
|
+
import { A as SKILL_MANIFEST_FILE, D as verifySkillSignature, M as buildManifest, O as MANIFEST_EXCLUDED_FILES, P as verifyManifest, b as parseSkillMarkdown, c as unzipWithLimits, f as SKILL_PACK_FILE, i as buildCatalog, k as SKILLS_LOCKFILE, m as parseSkillPackManifest, n as SkillDiscovery, o as readResponseWithLimit, p as exportSkills, t as validateSkills, u as detectSkillArchiveShapeFromFs, w as readSkillSignature, y as isValidSkillName } from "./skill-WSUDp6A1.js";
|
|
3
|
+
import { o as atomicWriteText, r as resolveInsideRoot, s as isAtomicTempPath } from "./pathSecurity-CirkQwWP.js";
|
|
4
4
|
import { t as assertRemoteUrlAllowed } from "./urlSafety-CiSuCJvX.js";
|
|
5
5
|
import { t as ATTACHMENT_TEXT_LIMIT } from "./kind-Dc8x0HWz.js";
|
|
6
|
-
import { _ as FsRunSnapshotStore, a as networkPolicyLibSource, c as bridgeError, ct as ProgressiveRouter, d as FsMemoryStore, l as parseBridgeRequest, m as AgentLoop, r as normalizeToolError, s as UPLOAD_FILES_UNAVAILABLE, t as CapabilityApproval, u as FsArtifactStore } from "./approval-
|
|
6
|
+
import { _ as FsRunSnapshotStore, a as networkPolicyLibSource, c as bridgeError, ct as ProgressiveRouter, d as FsMemoryStore, l as parseBridgeRequest, m as AgentLoop, r as normalizeToolError, s as UPLOAD_FILES_UNAVAILABLE, t as CapabilityApproval, u as FsArtifactStore } from "./approval-7CuZS3Z4.js";
|
|
7
7
|
import { a as GoogleGenAiClient, o as AnthropicClient, s as OpenAiCompatibleClient, t as MockLlmClient } from "./llm-eIQNO9tr.js";
|
|
8
|
-
import { c as toBase64$1, f as SPREADSHEET_MIME_TYPE } from "./linkedDocument-
|
|
8
|
+
import { c as toBase64$1, f as SPREADSHEET_MIME_TYPE } from "./linkedDocument-CZZOl9cO.js";
|
|
9
9
|
import { n as normalizeToolContent, t as mergeCatalogEntries } from "./external-_ZRQe-V9.js";
|
|
10
|
-
import { t as createWebSkillApi } from "./webSkillApi-
|
|
11
|
-
import { D as
|
|
10
|
+
import { t as createWebSkillApi } from "./webSkillApi-D5VMUnrv.js";
|
|
11
|
+
import { A as frameSteps, D as SCROLL_DIRECTIONS, E as toActionFrameScopes, O as toFrameScopes, _ as defersImage, a as columnWidthToPixels, b as isPdfTextTrustworthy, f as readImageSize, k as frameLabel, l as parseCellAddress, n as EMU_PER_POINT, o as fitWithin, p as PDF_LARGE_IMAGE_RATIO, r as EMU_PER_TWIP$1, s as formatCellAddress, t as EMU_PER_PIXEL, u as parseCellRange, v as takesImage } from "./geometry-CRhuySKV.js";
|
|
12
12
|
import { zipSync } from "fflate";
|
|
13
13
|
|
|
14
14
|
//#region ../browser/src/fs/featureDetection.ts
|
|
@@ -2634,24 +2634,56 @@ const ROUTE_ATTRIBUTES = [
|
|
|
2634
2634
|
* 选项往往就是不带 role 的 `<li>`(0.17.0 分册 11)。它与另外三个一样受
|
|
2635
2635
|
* 「有可操作后代就不提升」约束,所以真正的语义列表不会被误伤。
|
|
2636
2636
|
*
|
|
2637
|
-
*
|
|
2637
|
+
* 判据是「条目 / 单元」对「容器」(0.23.0 FR-11.1):一格、一项、一栏能承载一次点击,
|
|
2638
|
+
* `grid` / `menu` / `tablist` 这类容器不能,所以它们不在这里。
|
|
2639
|
+
*
|
|
2640
|
+
* 0.23.0 补齐了显式 ARIA 写法:过去 `<td>`(隐式 `cell`)能被提升,而
|
|
2641
|
+
* `<td role="gridcell">` 不能——把无障碍角色写对反而被降级。自绘日历、菜单、
|
|
2642
|
+
* 下拉普遍写显式角色,于是整片点不到(0.23.0 分册 11)。
|
|
2643
|
+
*
|
|
2644
|
+
* 这张白名单**只约束启发式信号**,放宽的是「够不够格被考察」,不是「够不够格被提升」:
|
|
2645
|
+
* 后面三道闸门(自身可操作 / 有可操作后代 / 看起来可交互)一个都没松。
|
|
2646
|
+
* 宿主探针点名的元素走另一条路(分册 12)。
|
|
2638
2647
|
*/
|
|
2639
2648
|
const PROMOTABLE_ROLES = /* @__PURE__ */ new Set([
|
|
2640
2649
|
"generic",
|
|
2641
2650
|
"img",
|
|
2642
2651
|
"cell",
|
|
2643
|
-
"listitem"
|
|
2652
|
+
"listitem",
|
|
2653
|
+
"columnheader",
|
|
2654
|
+
"gridcell",
|
|
2655
|
+
"rowheader",
|
|
2656
|
+
"option",
|
|
2657
|
+
"treeitem",
|
|
2658
|
+
"tab",
|
|
2659
|
+
"menuitem",
|
|
2660
|
+
"menuitemcheckbox",
|
|
2661
|
+
"menuitemradio"
|
|
2644
2662
|
]);
|
|
2645
2663
|
/** 合成名的前缀,必须让模型看得出「这不是页面上的字,别拿它去搜」(FR-15.4 第 3 级) */
|
|
2646
2664
|
const SYNTHETIC_NAME_PREFIX = "unlabeled control";
|
|
2647
2665
|
/** 往上找具名祖先的层数上限:再往上就是整页容器,拿它当限定语反而更含糊 */
|
|
2648
2666
|
const SYNTHETIC_NAME_LOOKUP_DEPTH = 5;
|
|
2649
2667
|
const classAndId = (element) => `${element.getAttribute("class") ?? ""} ${element.getAttribute("id") ?? ""}`;
|
|
2650
|
-
/**
|
|
2668
|
+
/**
|
|
2669
|
+
* `cursor: pointer` —— 页面自己声明了可点。`getComputedStyle` 最贵,殿后。
|
|
2670
|
+
*
|
|
2671
|
+
* 只认元素**自身引入**的那一次声明:`cursor` 是 CSS 继承属性,一句
|
|
2672
|
+
* `.card { cursor: pointer }` 会让整张卡片里的每段文字计算值都是 `pointer`。
|
|
2673
|
+
* 实测某差旅站点首页 1537 个可见元素里 **1050 个**仅凭继承来的 `cursor`
|
|
2674
|
+
* 就被判成可交互,感知里因此冒出 826 个「按钮」(0.23.0 FR-16.1)。
|
|
2675
|
+
*
|
|
2676
|
+
* 判据是与父元素的计算值**有没有差异**:没有差异就当作继承来的。
|
|
2677
|
+
* 父子都显式声明 `pointer` 的情形分辨不了(没有任何 DOM API 能做到),
|
|
2678
|
+
* 那类元素由其余四级信号兜底,见 0.23.0 设计 17 §3。
|
|
2679
|
+
*/
|
|
2651
2680
|
function looksClickable(element, view) {
|
|
2652
2681
|
if (view === null) return false;
|
|
2653
2682
|
try {
|
|
2654
|
-
|
|
2683
|
+
if (view.getComputedStyle(element).cursor !== "pointer") return false;
|
|
2684
|
+
const parent = element.parentElement;
|
|
2685
|
+
if (parent === null) return true;
|
|
2686
|
+
return view.getComputedStyle(parent).cursor !== "pointer";
|
|
2655
2687
|
} catch {
|
|
2656
2688
|
return false;
|
|
2657
2689
|
}
|
|
@@ -2901,7 +2933,9 @@ function nameOf(element, doc) {
|
|
|
2901
2933
|
}
|
|
2902
2934
|
if (FORM_CONTROL_TAGS.has(element.tagName)) return externalNameOf(element);
|
|
2903
2935
|
const own = ownText(element);
|
|
2904
|
-
|
|
2936
|
+
if (own !== "") return own;
|
|
2937
|
+
const title = element.getAttribute("title");
|
|
2938
|
+
return title !== null && title.trim() !== "" ? clip(title) : void 0;
|
|
2905
2939
|
}
|
|
2906
2940
|
function valueOf(element) {
|
|
2907
2941
|
if (element.tagName === "INPUT") {
|
|
@@ -2934,6 +2968,35 @@ function hidden$1(element, view) {
|
|
|
2934
2968
|
}
|
|
2935
2969
|
return false;
|
|
2936
2970
|
}
|
|
2971
|
+
/**
|
|
2972
|
+
* 页面是否有真实布局。jsdom 有 `getComputedStyle` 却没有布局引擎,那里所有元素的
|
|
2973
|
+
* rect 都是 0×0,把零尺寸判定套上去会让**全部**控件失去句柄(0.23.0 FR-10.4)。
|
|
2974
|
+
*
|
|
2975
|
+
* 判据与被判据用**同一种测量手段**:拿根元素的 rect 为下面的 rect 背书。
|
|
2976
|
+
* 真实浏览器里根元素必有尺寸,jsdom 里恒为 0×0。
|
|
2977
|
+
*
|
|
2978
|
+
* 不用 `clientHeight`,更不用 `view.innerHeight`:
|
|
2979
|
+
* 前者与 rect 是两套独立机制,测试桩(以及 `overflow` 的各种写法)可以只动其中一套,
|
|
2980
|
+
* 用 A 去为 B 背书会得出「有布局」却量不到尺寸的矛盾结论;
|
|
2981
|
+
* 后者在 jsdom 里被编了 1024×768,两边根本看不出区别。
|
|
2982
|
+
*/
|
|
2983
|
+
function hasLayout(doc) {
|
|
2984
|
+
const rect = doc.documentElement.getBoundingClientRect();
|
|
2985
|
+
return rect.width > 0 || rect.height > 0;
|
|
2986
|
+
}
|
|
2987
|
+
/**
|
|
2988
|
+
* 元素在屏幕上不占任何像素(0.23.0 FR-10.1)。人的鼠标点不到它,
|
|
2989
|
+
* 用户也无从在逐次确认卡上核对它——`act_on_page` 的安全模型建立在「用户看得懂自己批准了什么」上。
|
|
2990
|
+
*
|
|
2991
|
+
* 不复用 `measuredSizeOf()`:那个函数在 rect 为 0 时会回退去读 naturalWidth /
|
|
2992
|
+
* canvas 属性 / viewBox,为的是「测不到尺寸就别过滤图片」。这里要的恰恰相反:
|
|
2993
|
+
* 必须分得清「确实量到了 0」和「没有布局」,一档都不能回退。
|
|
2994
|
+
*/
|
|
2995
|
+
function occupiesNoSpace(element) {
|
|
2996
|
+
if (!hasLayout(element.ownerDocument)) return false;
|
|
2997
|
+
const rect = element.getBoundingClientRect();
|
|
2998
|
+
return rect.width === 0 || rect.height === 0;
|
|
2999
|
+
}
|
|
2937
3000
|
/** script / style 的正文是代码,不是页面内容 */
|
|
2938
3001
|
const SKIPPED_TAGS = /* @__PURE__ */ new Set([
|
|
2939
3002
|
"SCRIPT",
|
|
@@ -2943,6 +3006,33 @@ const SKIPPED_TAGS = /* @__PURE__ */ new Set([
|
|
|
2943
3006
|
]);
|
|
2944
3007
|
/** 活动模态的三种形态;Radix 之流用 aria-modal,原生用 <dialog open> */
|
|
2945
3008
|
const MODAL_SELECTOR = "[role=dialog][open], dialog[open], [aria-modal=true]";
|
|
3009
|
+
const scrollsOverflow = (overflow) => overflow === "auto" || overflow === "scroll" || overflow === "overlay";
|
|
3010
|
+
/**
|
|
3011
|
+
* 该元素**自身**还能往哪些方向滚(0.23.0 分册 22 FR-22.1)。
|
|
3012
|
+
*
|
|
3013
|
+
* 先读布局属性再查计算样式,顺序不能倒:实测某 ERP 页 2488 个元素里只有 18 个
|
|
3014
|
+
* 在第一道过筛(1.4ms),对这 18 个查 overflow 只要 0.1ms;对全部 2488 个查要 5.8ms。
|
|
3015
|
+
* 这与 `looksClickable` 把 `getComputedStyle` 殿后是同一条原则。
|
|
3016
|
+
*
|
|
3017
|
+
* 只判元素自身、不上溯,标示因此只落在容器节点上(FR-22.3)。
|
|
3018
|
+
* jsdom 没有布局,这些尺寸恒为 0,筛子自然不命中。
|
|
3019
|
+
*/
|
|
3020
|
+
function scrollableDirectionsOf(element, view) {
|
|
3021
|
+
const overflowsX = element.scrollWidth > element.clientWidth;
|
|
3022
|
+
const overflowsY = element.scrollHeight > element.clientHeight;
|
|
3023
|
+
if (!overflowsX && !overflowsY) return [];
|
|
3024
|
+
const style = view.getComputedStyle(element);
|
|
3025
|
+
const out = [];
|
|
3026
|
+
if (overflowsX && scrollsOverflow(style.overflowX)) {
|
|
3027
|
+
if (element.scrollLeft > 0) out.push("left");
|
|
3028
|
+
if (element.scrollLeft < element.scrollWidth - element.clientWidth - 1) out.push("right");
|
|
3029
|
+
}
|
|
3030
|
+
if (overflowsY && scrollsOverflow(style.overflowY)) {
|
|
3031
|
+
if (element.scrollTop > 0) out.push("up");
|
|
3032
|
+
if (element.scrollTop < element.scrollHeight - element.clientHeight - 1) out.push("down");
|
|
3033
|
+
}
|
|
3034
|
+
return out;
|
|
3035
|
+
}
|
|
2946
3036
|
/** 按尺寸落选取像的说明(FR-20.3);与「超名额」「取像失败」必须可区分,三者排查方向不同 */
|
|
2947
3037
|
const ICON_SKIPPED_NOTE = "Not captured: this image is smaller than the icon threshold";
|
|
2948
3038
|
/**
|
|
@@ -2968,6 +3058,8 @@ function describe(element, excluded, doc, view, capture, handles, frame, provena
|
|
|
2968
3058
|
const value = valueOf(element);
|
|
2969
3059
|
let role = roleOf$1(element);
|
|
2970
3060
|
let promotedHref;
|
|
3061
|
+
let roleBeforePromotion;
|
|
3062
|
+
let nameWasSynthesized = false;
|
|
2971
3063
|
if (promotion !== void 0) {
|
|
2972
3064
|
const promoted = promoteRole({
|
|
2973
3065
|
element,
|
|
@@ -2978,14 +3070,37 @@ function describe(element, excluded, doc, view, capture, handles, frame, provena
|
|
|
2978
3070
|
context: promotion
|
|
2979
3071
|
});
|
|
2980
3072
|
if (promoted !== void 0) {
|
|
3073
|
+
roleBeforePromotion = role;
|
|
2981
3074
|
role = promoted.role;
|
|
2982
3075
|
promotedHref = promoted.href;
|
|
2983
|
-
if (name === void 0)
|
|
3076
|
+
if (name === void 0) {
|
|
3077
|
+
name = clip(iconHintOf(element) ?? synthesizeName(element, (a) => nameOf(a, doc)));
|
|
3078
|
+
nameWasSynthesized = true;
|
|
3079
|
+
}
|
|
2984
3080
|
}
|
|
2985
3081
|
}
|
|
3082
|
+
let ref = handles?.issue(element, role, frame);
|
|
3083
|
+
if (roleBeforePromotion !== void 0 && ref === void 0) {
|
|
3084
|
+
role = roleBeforePromotion;
|
|
3085
|
+
promotedHref = void 0;
|
|
3086
|
+
if (nameWasSynthesized) name = void 0;
|
|
3087
|
+
ref = handles?.issue(element, role, frame);
|
|
3088
|
+
}
|
|
2986
3089
|
const href = promotedHref ?? (role === "link" ? hrefOf(element) : void 0);
|
|
2987
|
-
|
|
2988
|
-
|
|
3090
|
+
let scrollable;
|
|
3091
|
+
let scrollRef;
|
|
3092
|
+
if (view !== null) {
|
|
3093
|
+
const directions = scrollableDirectionsOf(element, view);
|
|
3094
|
+
if (directions.length > 0) {
|
|
3095
|
+
const issued = ref ?? handles?.issueScrollable(element, frame);
|
|
3096
|
+
if (issued !== void 0) {
|
|
3097
|
+
scrollable = directions;
|
|
3098
|
+
scrollRef = issued;
|
|
3099
|
+
}
|
|
3100
|
+
}
|
|
3101
|
+
}
|
|
3102
|
+
if (role === "generic" && name === void 0 && value === void 0 && children.length === 0 && scrollable === void 0) return;
|
|
3103
|
+
if (promotion !== void 0 && role === "generic" && name === void 0 && value === void 0 && scrollable === void 0) {
|
|
2989
3104
|
if (children.length === 1) return children[0];
|
|
2990
3105
|
}
|
|
2991
3106
|
const node = {
|
|
@@ -2997,8 +3112,11 @@ function describe(element, excluded, doc, view, capture, handles, frame, provena
|
|
|
2997
3112
|
...provenance !== void 0 ? { provenance } : {},
|
|
2998
3113
|
...children.length > 0 ? { children } : {}
|
|
2999
3114
|
};
|
|
3000
|
-
const ref = handles?.issue(element, role, frame);
|
|
3001
3115
|
if (ref !== void 0) node.ref = ref;
|
|
3116
|
+
if (scrollable !== void 0) {
|
|
3117
|
+
node.ref = scrollRef;
|
|
3118
|
+
node.scrollable = scrollable;
|
|
3119
|
+
}
|
|
3002
3120
|
if (isCapturableElement(element)) {
|
|
3003
3121
|
const captureRef = node.ref ?? handles?.issueCapture(element, frame);
|
|
3004
3122
|
if (captureRef !== void 0) {
|
|
@@ -3116,6 +3234,7 @@ var HandleIssuer = class {
|
|
|
3116
3234
|
if (!this.#actionable.has(element) || this.#excluded.has(element)) return void 0;
|
|
3117
3235
|
const kind = ACTIONABLE_ROLES.has(role) ? "action" : ANCHOR_ROLES.has(role) ? "anchor" : void 0;
|
|
3118
3236
|
if (kind === void 0) return void 0;
|
|
3237
|
+
if (kind === "action" && occupiesNoSpace(element)) return void 0;
|
|
3119
3238
|
const known = this.#refByElement.get(element);
|
|
3120
3239
|
if (known !== void 0 && this.#table.has(known)) {
|
|
3121
3240
|
this.#table.set(known, {
|
|
@@ -3139,6 +3258,19 @@ var HandleIssuer = class {
|
|
|
3139
3258
|
if (known !== void 0 && this.#table.has(known)) return known;
|
|
3140
3259
|
return this.#mint(element, "image", frame);
|
|
3141
3260
|
}
|
|
3261
|
+
/**
|
|
3262
|
+
* 可滚容器的句柄(0.23.0 分册 22)。角色白名单之外——表格容器往往是个 `generic` div,
|
|
3263
|
+
* 但 `scroll` 确实作用在它身上。不发就成了「标了能滚、模型指认不了」,正是 AC-G8 禁的那种幽灵能力。
|
|
3264
|
+
*
|
|
3265
|
+
* 仍过操作范围与零尺寸两道闸(FR-22.12);已有句柄就原样复用且**不改 kind**。
|
|
3266
|
+
*/
|
|
3267
|
+
issueScrollable(element, frame) {
|
|
3268
|
+
if (!this.#actionable.has(element) || this.#excluded.has(element)) return void 0;
|
|
3269
|
+
if (occupiesNoSpace(element)) return void 0;
|
|
3270
|
+
const known = this.#refByElement.get(element);
|
|
3271
|
+
if (known !== void 0 && this.#table.has(known)) return known;
|
|
3272
|
+
return this.#mint(element, "action", frame);
|
|
3273
|
+
}
|
|
3142
3274
|
#mint(element, kind, frame) {
|
|
3143
3275
|
const bytes = /* @__PURE__ */ new Uint8Array(8);
|
|
3144
3276
|
crypto.getRandomValues(bytes);
|
|
@@ -3612,6 +3744,39 @@ function hidden(element) {
|
|
|
3612
3744
|
}
|
|
3613
3745
|
return false;
|
|
3614
3746
|
}
|
|
3747
|
+
/**
|
|
3748
|
+
* 「开着的浮层」里必须有可操作的东西。空的定位容器(装饰角标、测量层、定位锚点)
|
|
3749
|
+
* 满页都是,只看定位会把它们全当成浮层——实测该页面误伤 337/400(0.23.0 设计 16 §3.1)。
|
|
3750
|
+
*/
|
|
3751
|
+
const POPOVER_CONTENT_SELECTOR = "a[href],button,input,select,textarea,[role=\"button\"],[role=\"link\"],[role=\"option\"],[role=\"menuitem\"],[role=\"gridcell\"],[role=\"tab\"],[role=\"checkbox\"],[role=\"radio\"]";
|
|
3752
|
+
/**
|
|
3753
|
+
* 这个元素旁边是不是已经有一个开着的浮层(0.23.0 FR-15.1)。
|
|
3754
|
+
*
|
|
3755
|
+
* 零 DOM 变更有两种成因:元素真的不响应,和**页面已经处于这个动作要达成的状态**。
|
|
3756
|
+
* 后者按分册 12 的原判据会被报成 `noop`,模型于是放弃一条已经走通的路。
|
|
3757
|
+
*
|
|
3758
|
+
* 只看**父元素这一层**:自绘下拉的写法是触发器与浮层同属一个包装容器,
|
|
3759
|
+
* 往上多走一层就开始把无关区块算进来(误伤 3/400 → 31/400)。
|
|
3760
|
+
*
|
|
3761
|
+
* 用 `hidden()` 而不是尺寸:jsdom 没有布局,`getBoundingClientRect()` 恒为 0,
|
|
3762
|
+
* 依赖尺寸这条判据就没法被测试守住(设计 16 §3.2)。
|
|
3763
|
+
*/
|
|
3764
|
+
function popoverOpenBeside(element) {
|
|
3765
|
+
if (element.getAttribute("aria-expanded") === "true") return true;
|
|
3766
|
+
const parent = element.parentElement;
|
|
3767
|
+
if (parent === null) return false;
|
|
3768
|
+
const view = element.ownerDocument.defaultView;
|
|
3769
|
+
if (view === null) return false;
|
|
3770
|
+
for (const candidate of parent.querySelectorAll("*")) {
|
|
3771
|
+
if (candidate === element || element.contains(candidate) || candidate.contains(element)) continue;
|
|
3772
|
+
const position = view.getComputedStyle(candidate).position;
|
|
3773
|
+
if (position !== "absolute" && position !== "fixed") continue;
|
|
3774
|
+
if (hidden(candidate)) continue;
|
|
3775
|
+
if (candidate.querySelector(POPOVER_CONTENT_SELECTOR) === null) continue;
|
|
3776
|
+
return true;
|
|
3777
|
+
}
|
|
3778
|
+
return false;
|
|
3779
|
+
}
|
|
3615
3780
|
const inputType = (element) => (element.getAttribute("type") ?? "text").toLowerCase();
|
|
3616
3781
|
const isSecret = (element) => element.tagName === "INPUT" && SECRET_INPUT_TYPES.has(inputType(element));
|
|
3617
3782
|
const textOf$2 = (element) => element?.textContent?.replace(/\s+/g, " ").trim() ?? "";
|
|
@@ -4028,6 +4193,12 @@ function attachFiles(input, files) {
|
|
|
4028
4193
|
input.dispatchEvent(new Event("change", { bubbles: true }));
|
|
4029
4194
|
}
|
|
4030
4195
|
const scrolls = (overflow) => overflow === "auto" || overflow === "scroll" || overflow === "overlay";
|
|
4196
|
+
const axisOf = (direction) => direction === "left" || direction === "right" ? "x" : "y";
|
|
4197
|
+
/** 该元素自身在这条轴上能不能滚。两个条件的合取,见 `scrollTargetOf` 的注释 */
|
|
4198
|
+
function scrollsOn(node, axis, view) {
|
|
4199
|
+
const style = view.getComputedStyle(node);
|
|
4200
|
+
return axis === "y" ? scrolls(style.overflowY) && node.scrollHeight > node.clientHeight : scrolls(style.overflowX) && node.scrollWidth > node.clientWidth;
|
|
4201
|
+
}
|
|
4031
4202
|
/**
|
|
4032
4203
|
* 滚动落点(FR-10.3):从被指认元素**自身**起沿祖先链上溯,取第一个真正能滚的容器。
|
|
4033
4204
|
*
|
|
@@ -4041,20 +4212,21 @@ const scrolls = (overflow) => overflow === "auto" || overflow === "scroll" || ov
|
|
|
4041
4212
|
* 根滚动容器单独兜底而不是靠链上命中:`documentElement` 的 `overflow-y` 计算值
|
|
4042
4213
|
* 通常是 `visible`(滚动被传播到视口),不会被上面的判据命中,但文档确实可滚。
|
|
4043
4214
|
* 顺序必须是先链后兜底——嵌套场景下先命中的必须是内层列表。
|
|
4215
|
+
*
|
|
4216
|
+
* **按轴判(0.23.0 分册 22 FR-22.7)**:同一个容器常常只在一条轴上溢出,
|
|
4217
|
+
* 拿纵向判据去找横向落点会整体判成不可滚,那正是横向滚动此前失败的原因。
|
|
4044
4218
|
*/
|
|
4045
|
-
function scrollTargetOf(element) {
|
|
4219
|
+
function scrollTargetOf(element, axis) {
|
|
4046
4220
|
const doc = element.ownerDocument;
|
|
4047
4221
|
const view = doc.defaultView;
|
|
4048
4222
|
if (view === null) return void 0;
|
|
4049
|
-
for (let node = element; node !== null; node = node.parentElement) {
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
isRoot: false
|
|
4054
|
-
};
|
|
4055
|
-
}
|
|
4223
|
+
for (let node = element; node !== null; node = node.parentElement) if (scrollsOn(node, axis, view)) return {
|
|
4224
|
+
container: node,
|
|
4225
|
+
isRoot: false
|
|
4226
|
+
};
|
|
4056
4227
|
const root = doc.scrollingElement ?? doc.documentElement;
|
|
4057
|
-
|
|
4228
|
+
const rootOverflows = root !== null && (axis === "y" ? root.scrollHeight > root.clientHeight : root.scrollWidth > root.clientWidth);
|
|
4229
|
+
if (root !== null && rootOverflows) return {
|
|
4058
4230
|
container: root,
|
|
4059
4231
|
isRoot: true
|
|
4060
4232
|
};
|
|
@@ -4159,11 +4331,12 @@ function createDomPageActionExecutor(options) {
|
|
|
4159
4331
|
* 稳定化是滚动自己的收尾,见 `watchQuiescence`。
|
|
4160
4332
|
*/
|
|
4161
4333
|
const scrollRegion = async (element, target, direction) => {
|
|
4162
|
-
const
|
|
4334
|
+
const axis = axisOf(direction);
|
|
4335
|
+
const found = scrollTargetOf(element, axis);
|
|
4163
4336
|
if (found === void 0) return {
|
|
4164
4337
|
ok: false,
|
|
4165
4338
|
target,
|
|
4166
|
-
reason:
|
|
4339
|
+
reason: `This area cannot be scrolled ${direction}: nothing around this element scrolls ${axis === "y" ? "vertically" : "horizontally"}.`
|
|
4167
4340
|
};
|
|
4168
4341
|
const { container, isRoot } = found;
|
|
4169
4342
|
if (!isRoot && !reader.inActionScope(container)) return {
|
|
@@ -4171,17 +4344,22 @@ function createDomPageActionExecutor(options) {
|
|
|
4171
4344
|
target,
|
|
4172
4345
|
reason: "The scrollable region around this element is outside the actionable scope."
|
|
4173
4346
|
};
|
|
4174
|
-
const
|
|
4175
|
-
const
|
|
4347
|
+
const extent = axis === "y" ? container.clientHeight : container.clientWidth;
|
|
4348
|
+
const step = Math.max(extent - SCROLL_OVERLAP_PX, Math.round(extent / 2), 1);
|
|
4349
|
+
const forward = direction === "down" || direction === "right";
|
|
4350
|
+
const before = axis === "y" ? container.scrollTop : container.scrollLeft;
|
|
4176
4351
|
const watch = watchQuiescence(container);
|
|
4177
|
-
|
|
4352
|
+
const next = forward ? before + step : before - step;
|
|
4353
|
+
if (axis === "y") container.scrollTop = next;
|
|
4354
|
+
else container.scrollLeft = next;
|
|
4178
4355
|
await watch.settle(clampScrollSettleMs(options.scrollSettleMs?.()));
|
|
4179
|
-
const after = container.scrollTop;
|
|
4356
|
+
const after = axis === "y" ? container.scrollTop : container.scrollLeft;
|
|
4357
|
+
const limit = axis === "y" ? container.scrollHeight - container.clientHeight - 1 : container.scrollWidth - container.clientWidth - 1;
|
|
4180
4358
|
return {
|
|
4181
4359
|
ok: true,
|
|
4182
4360
|
target,
|
|
4183
4361
|
scrolled: {
|
|
4184
|
-
atEnd:
|
|
4362
|
+
atEnd: forward ? after >= limit : after <= 0,
|
|
4185
4363
|
movedBy: Math.abs(after - before)
|
|
4186
4364
|
}
|
|
4187
4365
|
};
|
|
@@ -4205,6 +4383,7 @@ function createDomPageActionExecutor(options) {
|
|
|
4205
4383
|
if (reader.modalStateOf(element) === "unelevated") return fail("That dialog is not in the authorized scope because it was opened manually. Ask me to open it, or add it to the host allowlist.");
|
|
4206
4384
|
if (reader.modalStateOf(element) !== "elevated" && !reader.inActionScope(element)) return fail("The element is no longer inside the actionable scope.");
|
|
4207
4385
|
if (hidden(element)) return fail("The element is not visible.");
|
|
4386
|
+
if (occupiesNoSpace(element)) return fail("The element takes up no space on screen, so it cannot be clicked or pointed at.");
|
|
4208
4387
|
if (element.hasAttribute("disabled")) return fail("The element is disabled.");
|
|
4209
4388
|
const modalsBefore = reader.modalSnapshot();
|
|
4210
4389
|
const frameView = element.ownerDocument.defaultView;
|
|
@@ -4231,15 +4410,22 @@ function createDomPageActionExecutor(options) {
|
|
|
4231
4410
|
};
|
|
4232
4411
|
};
|
|
4233
4412
|
if (request.action === "click") {
|
|
4413
|
+
const watch = watchMutations(element);
|
|
4234
4414
|
press(element);
|
|
4235
|
-
|
|
4415
|
+
const outcome = await done({
|
|
4236
4416
|
ok: true,
|
|
4237
4417
|
target
|
|
4238
4418
|
});
|
|
4419
|
+
const changed = watch.changed();
|
|
4420
|
+
watch.stop();
|
|
4421
|
+
return changed || outcome.navigated === true || outcome.elevatedModal !== void 0 || popoverOpenBeside(element) ? outcome : {
|
|
4422
|
+
...outcome,
|
|
4423
|
+
noop: true
|
|
4424
|
+
};
|
|
4239
4425
|
}
|
|
4240
4426
|
if (request.action === "fill") {
|
|
4241
4427
|
if (!FILLABLE_TAGS.has(element.tagName)) return fail("The element is not a text control.");
|
|
4242
|
-
if (element.hasAttribute("readonly")) return fail("The element is read-only.");
|
|
4428
|
+
if (element.hasAttribute("readonly")) return fail("The element is read-only. Read-only text fields often open a picker when clicked, so try the click action on this same element.");
|
|
4243
4429
|
fillValue(element, request.value ?? "");
|
|
4244
4430
|
return await done({
|
|
4245
4431
|
ok: true,
|
|
@@ -4258,7 +4444,7 @@ function createDomPageActionExecutor(options) {
|
|
|
4258
4444
|
}
|
|
4259
4445
|
if (request.action === "set") return await done(setToggle(element, request.value ?? "", target));
|
|
4260
4446
|
if (request.action === "scroll") {
|
|
4261
|
-
if (
|
|
4447
|
+
if (!SCROLL_DIRECTIONS.includes(request.value)) return fail(`Use ${SCROLL_DIRECTIONS.map((d) => `"${d}"`).join(" / ")}.`);
|
|
4262
4448
|
return await scrollRegion(element, target, request.value);
|
|
4263
4449
|
}
|
|
4264
4450
|
if (request.action === "hover") {
|
|
@@ -4270,7 +4456,7 @@ function createDomPageActionExecutor(options) {
|
|
|
4270
4456
|
return await done({
|
|
4271
4457
|
ok: true,
|
|
4272
4458
|
target,
|
|
4273
|
-
...changed ? {} : { noop: true }
|
|
4459
|
+
...changed || popoverOpenBeside(element) ? {} : { noop: true }
|
|
4274
4460
|
});
|
|
4275
4461
|
}
|
|
4276
4462
|
if (request.action === "drag") {
|
|
@@ -4475,16 +4661,71 @@ async function roundTrip(transport, request, expect, code, timeoutMs) {
|
|
|
4475
4661
|
*/
|
|
4476
4662
|
function createRemotePerceptionReader(options) {
|
|
4477
4663
|
const { transport, targets, timeoutMs = DEFAULT_PAGE_AGENT_TIMEOUT_MS, onDocument } = options;
|
|
4478
|
-
|
|
4479
|
-
|
|
4480
|
-
|
|
4481
|
-
|
|
4482
|
-
|
|
4483
|
-
|
|
4484
|
-
|
|
4485
|
-
|
|
4486
|
-
|
|
4487
|
-
|
|
4664
|
+
/**
|
|
4665
|
+
* 上一批写进分片的条目(0.23.0 分册 13 / FR-13.4)。
|
|
4666
|
+
*
|
|
4667
|
+
* `registry.replace` 是**整体替换分片**,而子树读取只回传子树内的条目——
|
|
4668
|
+
* 照搬就等于「读完日期面板,页面别处全部失去句柄」。本地形态没有这个问题:
|
|
4669
|
+
* `HandleIssuer` 的表跨感知保留,子树读取只是少发几个 ref,既有的一个都不丢。
|
|
4670
|
+
* 差值由这里补出来,而不是转嫁给模型。
|
|
4671
|
+
*
|
|
4672
|
+
* 状态留在发起方而不是给 `RemoteTargetRegistry` 加 `merge()`:
|
|
4673
|
+
* 后者对所有实现方都是破坏性变更,需求 §2.2 把它列为禁止项。
|
|
4674
|
+
*/
|
|
4675
|
+
let lastShard;
|
|
4676
|
+
const commit = (entries, document, merge) => {
|
|
4677
|
+
const sameDocument = lastShard !== void 0 && lastShard.key === document?.key;
|
|
4678
|
+
let next = entries;
|
|
4679
|
+
if (merge && sameDocument && lastShard !== void 0) {
|
|
4680
|
+
const byRef = /* @__PURE__ */ new Map();
|
|
4681
|
+
for (const entry of lastShard.entries) byRef.set(entry.ref, entry);
|
|
4682
|
+
for (const entry of entries) byRef.set(entry.ref, entry);
|
|
4683
|
+
next = [...byRef.values()];
|
|
4684
|
+
}
|
|
4685
|
+
targets.replace(next, document);
|
|
4686
|
+
lastShard = {
|
|
4687
|
+
key: document?.key,
|
|
4688
|
+
entries: next
|
|
4689
|
+
};
|
|
4690
|
+
if (document !== void 0) onDocument?.(document);
|
|
4691
|
+
};
|
|
4692
|
+
return {
|
|
4693
|
+
async read(scope, capture) {
|
|
4694
|
+
const reply = await roundTrip(transport, {
|
|
4695
|
+
type: "perceive",
|
|
4696
|
+
scope,
|
|
4697
|
+
...capture !== void 0 ? { capture } : {}
|
|
4698
|
+
}, "perceive-result", "PERCEPTION_FAILED", timeoutMs);
|
|
4699
|
+
commit(reply.targets, reply.document, false);
|
|
4700
|
+
return reply.result;
|
|
4701
|
+
},
|
|
4702
|
+
/**
|
|
4703
|
+
* 子树续读(0.23.0 分册 13 / FR-13.1)。
|
|
4704
|
+
*
|
|
4705
|
+
* 本地形态早就有这个方法,缺的只是这一段跨上下文接线;不实现它,
|
|
4706
|
+
* policy 会直接抛 `PERCEPTION_SUBTREE_UNSUPPORTED`,模型在浏览器扩展这类
|
|
4707
|
+
* 宿主里就永远没有「第一段没找到 → 展开某个子树再看」这条自救路径。
|
|
4708
|
+
*/
|
|
4709
|
+
async readSubtree(scope, ref, capture) {
|
|
4710
|
+
let reply;
|
|
4711
|
+
try {
|
|
4712
|
+
reply = await roundTrip(transport, {
|
|
4713
|
+
type: "perceive",
|
|
4714
|
+
scope,
|
|
4715
|
+
ref,
|
|
4716
|
+
...capture !== void 0 ? { capture } : {}
|
|
4717
|
+
}, "perceive-result", "PERCEPTION_FAILED", timeoutMs);
|
|
4718
|
+
} catch (e) {
|
|
4719
|
+
if (e instanceof WebSkillError) {
|
|
4720
|
+
if (e.code === "PERCEPTION_SUBTREE_OUT_OF_SCOPE") return { rejected: "out-of-scope" };
|
|
4721
|
+
if (e.code === "PERCEPTION_SUBTREE_UNKNOWN_REF") return { rejected: "unknown-ref" };
|
|
4722
|
+
}
|
|
4723
|
+
throw e;
|
|
4724
|
+
}
|
|
4725
|
+
commit(reply.targets, reply.document, true);
|
|
4726
|
+
return reply.result;
|
|
4727
|
+
}
|
|
4728
|
+
};
|
|
4488
4729
|
}
|
|
4489
4730
|
|
|
4490
4731
|
//#endregion
|
|
@@ -4553,6 +4794,24 @@ function collectRefs(nodes, out = []) {
|
|
|
4553
4794
|
return out;
|
|
4554
4795
|
}
|
|
4555
4796
|
/**
|
|
4797
|
+
* 子树被拒 → 既有的 `error` 应答变体(0.23.0 分册 13 / FR-13.5)。
|
|
4798
|
+
* 不新增应答形态,是把本册的契约变更压到「一个可选请求字段」的关键。
|
|
4799
|
+
*
|
|
4800
|
+
* 这两个错误码本来就在 `WebSkillErrorCode` 里,远程 reader 靠它们还原出
|
|
4801
|
+
* `{ rejected }`,policy 那边的 `#recordDenied` 才能照常留痕。
|
|
4802
|
+
*/
|
|
4803
|
+
function rejectionReply(rejected) {
|
|
4804
|
+
return rejected === "out-of-scope" ? {
|
|
4805
|
+
type: "error",
|
|
4806
|
+
code: "PERCEPTION_SUBTREE_OUT_OF_SCOPE",
|
|
4807
|
+
message: "that reference is outside the perception scope, so its subtree cannot be read"
|
|
4808
|
+
} : {
|
|
4809
|
+
type: "error",
|
|
4810
|
+
code: "PERCEPTION_SUBTREE_UNKNOWN_REF",
|
|
4811
|
+
message: "that reference is not on this page any more; perceive the page again to get a fresh reference"
|
|
4812
|
+
};
|
|
4813
|
+
}
|
|
4814
|
+
/**
|
|
4556
4815
|
* 页面侧处理器(分册 13 / FR-13.7)。
|
|
4557
4816
|
*
|
|
4558
4817
|
* 必须是工厂而不是裸函数:`DomPerceptionReader` **是有状态的**——
|
|
@@ -4596,8 +4855,15 @@ function createPageAgentHandler(options = {}) {
|
|
|
4596
4855
|
return { async handle(request) {
|
|
4597
4856
|
try {
|
|
4598
4857
|
if (request.type === "perceive") {
|
|
4599
|
-
|
|
4600
|
-
|
|
4858
|
+
let result;
|
|
4859
|
+
if (request.ref !== void 0) {
|
|
4860
|
+
const out = request.capture === void 0 ? await reader.readSubtree(request.scope, request.ref) : await reader.readSubtree(request.scope, request.ref, request.capture);
|
|
4861
|
+
if ("rejected" in out) return rejectionReply(out.rejected);
|
|
4862
|
+
result = out;
|
|
4863
|
+
} else {
|
|
4864
|
+
const raw = request.capture === void 0 ? reader.read(request.scope) : await reader.read(request.scope, request.capture);
|
|
4865
|
+
result = Array.isArray(raw) ? { nodes: raw } : raw;
|
|
4866
|
+
}
|
|
4601
4867
|
const targets = [];
|
|
4602
4868
|
for (const ref of collectRefs(result.nodes)) {
|
|
4603
4869
|
const target = executor.describe(ref);
|
|
@@ -6579,7 +6845,7 @@ function serializeXmlPart(declaration, doc) {
|
|
|
6579
6845
|
/** @throws WebSkillError 归档损坏,或不是 OOXML(缺 `[Content_Types].xml`) */
|
|
6580
6846
|
async function readOoxmlPackage(bytes) {
|
|
6581
6847
|
const entries = await unzipWithLimits(bytes);
|
|
6582
|
-
const pkg = new Map(entries);
|
|
6848
|
+
const pkg = new Map(entries.filter(([path]) => !path.endsWith("/")));
|
|
6583
6849
|
if (!pkg.has(CONTENT_TYPES)) throw new WebSkillError("TEMPLATE_UNSUPPORTED", `This file is not an Office Open XML document: it has no ${CONTENT_TYPES} entry.`);
|
|
6584
6850
|
return pkg;
|
|
6585
6851
|
}
|
|
@@ -6686,6 +6952,54 @@ function dropPart(pkg, partName, relsPath) {
|
|
|
6686
6952
|
pkg.set(relsPath, new TextEncoder().encode(declaration + stripped));
|
|
6687
6953
|
}
|
|
6688
6954
|
}
|
|
6955
|
+
/** VBA 工程部件的 content type 前缀。宏的判据看这个,不看文件路径。 */
|
|
6956
|
+
const VBA_CONTENT_TYPE = "application/vnd.ms-office.vbaProject";
|
|
6957
|
+
/** macroEnabled 的主部件 content type → 对应的非宏值(0.23.0 分册 25 设计 26 §3.1)。 */
|
|
6958
|
+
const NON_MACRO_MAIN_TYPE = /* @__PURE__ */ new Map([
|
|
6959
|
+
["application/vnd.ms-excel.sheet.macroEnabled.main+xml", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"],
|
|
6960
|
+
["application/vnd.ms-excel.template.macroEnabled.main+xml", "application/vnd.openxmlformats-officedocument.spreadsheetml.template.main+xml"],
|
|
6961
|
+
["application/vnd.ms-word.document.macroEnabled.main+xml", "application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"],
|
|
6962
|
+
["application/vnd.ms-word.template.macroEnabled.main+xml", "application/vnd.openxmlformats-officedocument.wordprocessingml.template.main+xml"]
|
|
6963
|
+
]);
|
|
6964
|
+
const attributeOf = (tag, name) => new RegExp(`${name}="([^"]*)"`).exec(tag)?.[1] ?? "";
|
|
6965
|
+
/**
|
|
6966
|
+
* 把宏痕迹从包里摘干净(0.23.0 分册 25 · FR-25.4)。
|
|
6967
|
+
*
|
|
6968
|
+
* 我们填完模板交出去的文件叫 `.xlsx` / `.docx`,声明的也是非宏 MIME。如果里面还留着
|
|
6969
|
+
* macroEnabled 的 content type 或 `vbaProject.bin`,文件就自相矛盾,Excel 会拒绝打开。
|
|
6970
|
+
* 宿主报来的那类文件更极端:`vbaProject.bin` 本身已经损坏或丢失,只剩标记——所以判据是
|
|
6971
|
+
* **`[Content_Types].xml` 里的登记**,不是「包里有没有那个部件」。
|
|
6972
|
+
*
|
|
6973
|
+
* 不是宏包时**一个字节都不改**(FR-25.5):无条件重写 `[Content_Types].xml` 会动到属性顺序
|
|
6974
|
+
* 和空白,非宏模板的产物字节就跟着变了。所以第一件事是判断,不中就原样返回。
|
|
6975
|
+
*/
|
|
6976
|
+
function stripMacros(pkg) {
|
|
6977
|
+
const typesBytes = pkg.get(CONTENT_TYPES);
|
|
6978
|
+
if (typesBytes === void 0) return;
|
|
6979
|
+
const text = new TextDecoder().decode(typesBytes);
|
|
6980
|
+
if (!text.includes(VBA_CONTENT_TYPE) && !text.includes("macroEnabled.main+xml")) return;
|
|
6981
|
+
const { declaration, body } = splitXmlDeclaration(text);
|
|
6982
|
+
let rewritten = body;
|
|
6983
|
+
for (const match of body.matchAll(/<Override\s[^>]*\/>/g)) {
|
|
6984
|
+
const tag = match[0];
|
|
6985
|
+
const contentType = attributeOf(tag, "ContentType");
|
|
6986
|
+
if (contentType.startsWith(VBA_CONTENT_TYPE)) {
|
|
6987
|
+
pkg.delete(attributeOf(tag, "PartName").replace(/^\//, ""));
|
|
6988
|
+
rewritten = rewritten.replace(tag, "");
|
|
6989
|
+
continue;
|
|
6990
|
+
}
|
|
6991
|
+
const replacement = NON_MACRO_MAIN_TYPE.get(contentType);
|
|
6992
|
+
if (replacement !== void 0) rewritten = rewritten.replace(tag, tag.replace(contentType, replacement));
|
|
6993
|
+
}
|
|
6994
|
+
pkg.set(CONTENT_TYPES, new TextEncoder().encode(declaration + rewritten));
|
|
6995
|
+
for (const [path, bytes] of pkg) {
|
|
6996
|
+
if (!path.endsWith(".rels")) continue;
|
|
6997
|
+
const split = splitXmlDeclaration(new TextDecoder().decode(bytes));
|
|
6998
|
+
let strippedRels = split.body;
|
|
6999
|
+
for (const match of split.body.matchAll(/<Relationship\s[^>]*\/>/g)) if (attributeOf(match[0], "Type").includes("/vbaProject")) strippedRels = strippedRels.replace(match[0], "");
|
|
7000
|
+
if (strippedRels !== split.body) pkg.set(path, new TextEncoder().encode(split.declaration + strippedRels));
|
|
7001
|
+
}
|
|
7002
|
+
}
|
|
6689
7003
|
|
|
6690
7004
|
//#endregion
|
|
6691
7005
|
//#region ../browser/src/document/xlsxTemplate.ts
|
|
@@ -7182,6 +7496,7 @@ async function fillXlsxTemplate(bytes, spec) {
|
|
|
7182
7496
|
}
|
|
7183
7497
|
if ([...touched.values()].some(({ doc }) => byTag$1(doc, "f").length > 0)) requestFullCalcOnLoad(pkg);
|
|
7184
7498
|
for (const [path, { declaration, doc }] of touched) pkg.set(path, serializeXmlPart(declaration, doc));
|
|
7499
|
+
stripMacros(pkg);
|
|
7185
7500
|
assertOoxmlPackage(pkg);
|
|
7186
7501
|
return writeOoxmlPackage(pkg);
|
|
7187
7502
|
}
|
|
@@ -7421,6 +7736,7 @@ async function fillDocxTemplate(bytes, spec) {
|
|
|
7421
7736
|
for (const text of spec.texts ?? []) fillText(doc, locate(doc, text), text.value);
|
|
7422
7737
|
(spec.images ?? []).forEach((image, index) => attachImage(pkg, doc, locate(doc, image), image, index));
|
|
7423
7738
|
pkg.set(DOCUMENT, serializeXmlPart(declaration, doc));
|
|
7739
|
+
stripMacros(pkg);
|
|
7424
7740
|
assertOoxmlPackage(pkg);
|
|
7425
7741
|
return writeOoxmlPackage(pkg);
|
|
7426
7742
|
}
|
|
@@ -43,6 +43,13 @@ function toFrameScopes(scope) {
|
|
|
43
43
|
...scope.exclude ? { exclude: scope.exclude } : {}
|
|
44
44
|
}];
|
|
45
45
|
}
|
|
46
|
+
/** 四个方向的规范清单;工具层校验与文案生成都从这里取,不各写一份 */
|
|
47
|
+
const SCROLL_DIRECTIONS = [
|
|
48
|
+
"up",
|
|
49
|
+
"down",
|
|
50
|
+
"left",
|
|
51
|
+
"right"
|
|
52
|
+
];
|
|
46
53
|
|
|
47
54
|
//#endregion
|
|
48
55
|
//#region ../agent/src/pageAction/types.ts
|
|
@@ -712,4 +719,4 @@ function readJpegSize(view, length) {
|
|
|
712
719
|
}
|
|
713
720
|
|
|
714
721
|
//#endregion
|
|
715
|
-
export { DOCUMENT_READ_BUDGET as C,
|
|
722
|
+
export { frameSteps as A, DOCUMENT_READ_BUDGET as C, SCROLL_DIRECTIONS as D, toActionFrameScopes as E, toFrameScopes as O, sniffDocumentFormat as S, PAGE_DOCUMENT_REF as T, defersImage as _, columnWidthToPixels as a, isPdfTextTrustworthy as b, isRangeTopLeft as c, rangeContains as d, readImageSize as f, asReadMode as g, createDocumentToolSource as h, MAX_DIGIT_WIDTH as i, frameLabel as k, parseCellAddress as l, READ_DOCUMENT_TOOL as m, EMU_PER_POINT as n, fitWithin as o, PDF_LARGE_IMAGE_RATIO as p, EMU_PER_TWIP as r, formatCellAddress as s, EMU_PER_PIXEL as t, parseCellRange as u, takesImage as v, PAGE_ACTION_KINDS as w, measurePdfTextQuality as x, PDF_GARBLED_RATIO as y };
|