Skip to content

Conversation

@cyfung1031
Copy link
Collaborator

@cyfung1031 cyfung1031 commented Nov 14, 2025

概述 Descriptions

依存: #949

变更内容 Changes

截图 Screenshots


测试代码(一):

修改后:GM.listValues() 能在冲突中取得最新,而且不会因本地缓存与valueUpdate冲突而造成次序不一
useAsync 改为 false 的话就能看 GM_xxxx 的结果 )

// ==UserScript==
// @name         测试 GM.getValue 能否取得最新值
// @namespace    yourname.scripts
// @version      0.1.0
// @description  把当前网页URL保存到存储列表中
// @author       You
// @match        *://*/*
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_deleteValue
// @grant        GM_listValues
// @grant        GM.getValue
// @grant        GM.setValue
// @grant        GM.deleteValue
// @grant        GM.listValues
// @grant        GM_addValueChangeListener
// @grant        GM_removeValueChangeListener
// ==/UserScript==

(function () {
    'use strict';
    let useAsync = true;
    const rid = `${(Math.floor(Math.random() * 5000) + 4000).toString(36)}`;

    const trigger = async () => {
        console.log(`[${rid}]`,"trigger in " + location.href, "current time = " + Date.now());
        useAsync ? action2() : action1();
    }
    let k = 0;
    const action1 = async () => {
        ++k;
        GM_setValue(`${"list_"}${rid}${k}`, Date.now());
        console.log(`[${rid}]`, GM_listValues());
    };

    const action2 = async () => {
        ++k;
        await GM.setValue(`${"list_"}${rid}${k}`, Date.now());
        console.log(`[${rid}]`, await GM.listValues());
    };

    const wins = [];
    let tc0;
    window.addEventListener("message", (e) => {
        if (e.data && typeof e.data === "object" && e.data?.test_call_id && e.data?.type === "response_tc") {
            const tc1 = e.data.test_call_id;
            if (tc1 === tc0) {
                trigger();
            }
        }
    });
    if (location.search.startsWith("?test_call=") && top !== window) {
        const usp = new URLSearchParams(location.search);
        const tc = usp.get("test_call");
        if (tc) {
            tc0 = tc;
            window.top.postMessage({ type: "done_iframe_tc", test_call_id: tc }, "*");
        }
    } else if (window.location.href.includes("example.com") && top === window) {
        const test_call_id = `tc${Date.now()}_${Math.random()}`;
        tc0 = test_call_id;
        let q = 1;
        let ec = 0;
        const doFunc = async (elements) => {
            console.log(`[${rid}]`,"---- ADD SOME INFO... ------");
            await (useAsync ? action2() : action1());
            await (useAsync ? action2() : action1());
            await (useAsync ? action2() : action1());
            console.log(`[${rid}]`,"---------------------------");
            setTimeout(() => {
                console.log(`[${rid}]`,"do trigger");
                for (const iframe of elements) {
                    try {
                        iframe.contentWindow.postMessage({ type: "response_tc", test_call_id },
                            "*"
                        )
                    } catch (e) {
                        // ignored
                    }
                }
                window.postMessage({ type: "response_tc", test_call_id }, "*");

                setTimeout(async () => {
                     console.log(`[${rid}]`,"final list", await GM.listValues());
                }, 1500)
            }, 1500);
        }
        window.addEventListener("message", (e) => {
            if (e.data && typeof e.data === "object" && e.data?.test_call_id && e.data?.type === "done_iframe_tc") {
                const tc = e.data.test_call_id;
                if (tc === test_call_id) {
                    const elements = document.querySelectorAll("iframe.tt0011");
                    // wins.push([e.source, e.origin]);
                    wins.push(1);
                    if (wins.length === elements.length && elements.length === ec && q) {
                        q = 0;
                        doFunc(elements);
                    }
                }
            }
        });
        
        const makeIframe = () => {
            const elm = document.body.appendChild(document.createElement("iframe"));
            elm.classList.add("tt0011");
            return elm;
        }

        ec = 3;
        setTimeout(()=>{

            makeIframe().src = `https://example.com/?test_call=${test_call_id}`;
        }, 1800);

        setTimeout(()=>{

            makeIframe().src = `https://example.com/?test_call=${test_call_id}`;
        }, 2400);

        setTimeout(() => {
            makeIframe().src = `https://example.com/?test_call=${test_call_id}`;
        }, 3200);
    }
})();

测试代码(二):

(有GM_lock 做时间控制)修改后 GM.getValue 的列表新增没问题

// ==UserScript==
// @name         Example Script for GM_lock
// @namespace    yourname.scripts
// @version      0.1
// @description  把当前网页URL保存到存储列表中
// @author       You
// @match        *://*/*
// @grant        GM.getValue
// @grant        GM.setValue
// @grant        GM.setValues
// @grant        GM.deleteValue
// @grant        GM.deleteValues
// @grant        GM.listValues
// @grant        GM_addValueChangeListener
// @grant        GM_removeValueChangeListener
// @require      https://update.greasyfork.org/scripts/554436/1692608/GM_lock.js
// @noframes
// ==/UserScript==

/* global GM_lock */
(function () {
  'use strict';
  GM_lock("lock_urls", async () => {
    console.log("开始", Date.now(), performance.now());
    // 等一下这个页面SC的缓存更新
    // await new Promise(resolve => setTimeout(resolve, 50));
    // 从存储中读取已有的列表
    let list = await GM.getValue('list', []); // 设置默认值为空数组
    // 如果当前URL不在列表中,就添加进去
    console.log("初始列表:", list.slice());
    if (!list.includes(location.href)) {
      list.push(location.href);
      await GM.setValue('list', list);
      console.log('✅ 已保存此页面到列表:', location.href);
    } else {
      console.log('ℹ️ 当前页面已在列表中');
    }
    // 可选:在控制台查看当前列表
    console.log('当前列表:', list.slice());
    // 等一下其他页面SC的缓存更新
    // await new Promise(resolve => setTimeout(resolve, 50));
    console.log("结束", Date.now(), performance.now());
  });
})();

@cyfung1031 cyfung1031 changed the title 異步 getValue/getValues/listValues 相关修改 異步 getValue/getValues/listValues 相关修改 & 修正 deleteValue/deleteValues 无法执行问题 Nov 14, 2025
@CodFrm
Copy link
Member

CodFrm commented Nov 15, 2025

怎么都到这个分支去了 develop/raw-message

@cyfung1031
Copy link
Collaborator Author

怎么都到这个分支去了 develop/raw-message

因为两边的 commit 互相影响
但处理的内容不一样,写在同一PR又太多又乱

@CodFrm CodFrm changed the base branch from develop/raw-message to release/v1.3 November 15, 2025 14:10
@cyfung1031 cyfung1031 changed the title 異步 getValue/getValues/listValues 相关修改 & 修正 deleteValue/deleteValues 无法执行问题 異步 getValue/getValues/listValues 相关修改 Nov 16, 2025
@cyfung1031 cyfung1031 force-pushed the develop-values-api-8 branch 3 times, most recently from b6d77de to 31a4165 Compare November 17, 2025 23:52
@cyfung1031 cyfung1031 changed the title 異步 getValue/getValues/listValues 相关修改 [v1.3] 異步 getValue/getValues/listValues 相关修改 Nov 22, 2025
@CodFrm CodFrm requested a review from Copilot November 28, 2025 07:04
Copilot finished reviewing on behalf of CodFrm November 28, 2025 07:04
@CodFrm CodFrm requested review from Copilot and removed request for Copilot November 28, 2025 10:00
Copilot finished reviewing on behalf of CodFrm November 28, 2025 10:04
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

这个 PR 实现了异步 getValue/getValues/listValues 相关的重要改进,主要解决了在多标签页并发场景下值读取的一致性问题。通过引入 waitForFreshValueState 机制和批处理架构,确保在读取值之前能够获得最新的状态。

主要变更:

  • 新增 waitForFreshValueState 方法,确保在读取前获取最新的 value 状态
  • 重构 setValues 方法为批处理架构,使用任务队列和 setValuesByStorageName 进行批量处理
  • 修改 GM.getValue/GM.listValues/GM.getValues 以在读取前等待最新状态

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/app/service/service_worker/value.ts 核心变更:新增 waitForFreshValueStatesetValuesByStorageName,重构 value 更新逻辑为批处理模式,引入 ValueUpdateTaskInfo 任务队列
src/app/service/service_worker/value.test.ts 更新测试以反映新的批处理行为,调整期望值以匹配新的数据结构,添加 flush() 调用处理异步逻辑
src/app/service/service_worker/runtime.ts 更新 valueUpdate 订阅以使用新的 TScriptValueUpdate 类型,添加脚本状态重新验证逻辑
src/app/service/service_worker/permission_verify.ts 更新泛型约束从 TT extends Array<any> 以匹配 API 参数类型
src/app/service/service_worker/gm_api/gm_api.ts 新增 internalApiWaitForFreshValueState API 方法,修正权限链接配置
src/app/service/sandbox/runtime.ts 更新 valueUpdate 方法以处理新的 ValueUpdateSendData 数据结构
src/app/service/queue.ts 修改 TScriptValueUpdate 类型定义,从包含 Script 对象改为包含 uuidstatusisEarlyStart 字段
src/app/service/content/types.ts ValueUpdateDataEncoded 中添加 updatetime 字段,新增 ValueUpdateSendData 类型
src/app/service/content/script_executor.ts 更新 valueUpdate 方法签名以适配新的数据结构
src/app/service/content/inject.ts 更新类型引用从 ValueUpdateDataEncodedValueUpdateSendData
src/app/service/content/gm_api/gm_api.ts 实现 waitForFreshValueState 静态方法,更新 GM.getValue/GM.listValues/GM.getValues 以调用该方法,重构 valueUpdate 处理多个更新事件,引入 extValueStoreCopyreadFreshes 机制
src/app/service/content/gm_api/gm_api.test.ts 添加 valueDaoUpdatetimeFix 辅助函数,更新测试以处理新的 waitForFreshValueState 行为,修正正则表达式以匹配计数器格式
src/app/service/content/exec_script.ts 更新 valueUpdate 方法签名以传递 storageNameuuid 和数据列表

uuid,
storageName,
sender: valueSender,
valueUpdated,
Copy link

Copilot AI Nov 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ValueUpdateDataEncoded 对象中包含了 valueUpdated 字段(第257行),但根据 types.ts 中的类型定义(第26-33行),ValueUpdateDataEncoded 类型不包含 valueUpdated 字段,只有 identriesuuidstorageNamesenderupdatetime。这会导致类型不匹配。建议删除第257行的 valueUpdated

Suggested change
valueUpdated,

Copilot uses AI. Check for mistakes.
const { id, uuid, entries, storageName, sender, valueUpdated } = data;
if (uuid === scriptRes.uuid || storageName === getStorageName(scriptRes)) {
let lastUpdateTime = 0;
if (uuid == scriptRes.uuid || storageName === getStorageName(scriptRes)) {
Copy link

Copilot AI Nov 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

应使用严格相等运算符 === 而不是 ==,以避免类型强制转换可能导致的意外行为。应改为 if (uuid === scriptRes.uuid || ...)

Suggested change
if (uuid == scriptRes.uuid || storageName === getStorageName(scriptRes)) {
if (uuid === scriptRes.uuid || storageName === getStorageName(scriptRes)) {

Copilot uses AI. Check for mistakes.
const cacheKey = `${CACHE_KEY_SET_VALUE}${storageName}`;
const ret = await stackAsyncTask<number | undefined>(cacheKey, async () => {
const valueModel: Value | undefined = await this.valueDAO.get(storageName);
// await this.valueDAO.save(storageName, valueModel);
Copy link

Copilot AI Nov 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

此处的注释代码 // await this.valueDAO.save(storageName, valueModel); 应该被删除。注释掉的代码会降低代码可读性,如果不需要这行代码,应该完全删除它。

Suggested change
// await this.valueDAO.save(storageName, valueModel);

Copilot uses AI. Check for mistakes.
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 this pull request may close these issues.

2 participants