标签 chrome extension 下的文章

manifest.json

{
  "name": "__MSG_name__",
  "default_locale": "en",
  "version": "1.0.0",
  "manifest_version": 3,
  "description": "__MSG_description__",
  "background": {
    "service_worker": "background.js"
  },
  "permissions": [
    "declarativeNetRequest",
    "declarativeNetRequestWithHostAccess"
  ],
  "host_permissions": [
    "*://*/*"
  ]
}

background.js

// 生成动态规则函数
const createCspRule = (extensionId) => ({
  id: 1001,
  priority: 1001,
  action: {
    type: "modifyHeaders",
    responseHeaders: [
      {
        header: "Content-Security-Policy",
        operation: "set",
        value: `frame-ancestors 'self' chrome-extension://${extensionId}`
      },
      { header: "X-Frame-Options", operation: "remove" }
    ]
  },
  condition: {
    urlFilter: "|*://*/*",
    resourceTypes: ["sub_frame"]
  }
});

// 安装时更新规则
chrome.runtime.onInstalled.addListener(async () => {
  const extensionId = chrome.runtime.id;

  await chrome.declarativeNetRequest.updateDynamicRules({
    removeRuleIds: [1001],
    addRules: [createCspRule(extensionId)]
  });

  console.log('动态规则已更新,当前扩展ID:', extensionId);
});

content js

chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
    console.log('got message: ', message)
    if (message.type === 'get_slider_position') {
// 获取滑块元素(需替换为实际选择器)
        const slider = document.querySelector('.tc-slider-normal');
        let rect = slider.getBoundingClientRect();
        sendResponse({rect})
    } else if (message.type === 'get_captcha_position') {
        console.log('got message: ', 'get_captcha_position')
// 获取滑块元素(需替换为实际选择器)
        const slider = document.querySelector('#slideBg');
        let rect = slider.getBoundingClientRect();
        sendResponse({rect})
    } else if (message.type === 'devicePixelRatio') {
        console.log('devicePixelRatio', window.devicePixelRatio)
        sendResponse({devicePixelRatio: window.devicePixelRatio});
    }
});

background js

chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
    console.log('got message: ', message)
    if (message.type === 'TAKE_ELEMENT_SCREENSHOT') {
        get_captcha_screen(message, sender, sendResponse)
        return true
    } else if (message.type === 'download_image') {
        chrome.downloads.download(message.data);
    }

});

const get_captcha_screen = (message, sender, sendResponse) => {
// 获取当前标签页的 windowId
    const windowId = sender.tab.windowId;

    chrome.tabs.captureVisibleTab(windowId, {format: 'png'}, async (dataUrl) => {
        if (chrome.runtime.lastError) {
            console.error(chrome.runtime.lastError.message);
            return;
        }

        // chrome.downloads.download({
        //     url: dataUrl,
        //     filename: 'page.png'
        // });

        try {
            // 将 dataUrl 转为 Blob 并创建 ImageBitmap 便于裁剪
            const blob = await fetch(dataUrl).then(r => r.blob());
            const imageBitmap = await createImageBitmap(blob);

            const rect = message.rect
            console.log('rect', rect)
            let iframe = await get_element_position(sender.tab.id, 'iframe').then(result => result.rect)
            console.log('iframe', iframe)

            // 创建 OffscreenCanvas,其尺寸与目标元素一致
            // const canvas = new OffscreenCanvas(iframe.width, iframe.height);
            const radio = await chrome.tabs.sendMessage(sender.tab.id, {type: "devicePixelRatio"}).then(r => r.devicePixelRatio)
            const canvas = new OffscreenCanvas(rect.width, rect.height);
            const ctx = canvas.getContext('2d');
            ctx.drawImage(
                imageBitmap,
                (iframe.left + rect.left) * radio, //源图像的裁剪起点 X 坐标。
                (iframe.top + rect.top) * radio,//iframe.top,  //源图像的裁剪起点 Y 坐标。
                rect.width * radio,//iframe.width, //源图像的裁剪宽度。
                rect.height * radio,//iframe.height, //源图像的裁剪高度。
                0, //目标 Canvas 上的 X 坐标。
                0, //目标 Canvas 上的 Y 坐标。
                rect.width, //绘制到 Canvas 上的宽度(可以缩放图像)。
                rect.height,//绘制到 Canvas 上的高度(可以缩放图像)
            );

            // 将裁剪结果转换为 Blob 或 dataURL
            const croppedBlob = await canvas.convertToBlob();
            const reader = new FileReader();
            reader.onload = () => {
                const croppedDataUrl = reader.result;
                console.log('元素截图完成,dataURL:', croppedDataUrl);
                chrome.downloads.download({
                    url: croppedDataUrl,
                    filename: 'screenshot.png'
                });
                sendResponse({url: croppedDataUrl})
                // 此处可以将截图 dataURL 发送到 popup 显示,或者打开新窗口展示
            };
            reader.readAsDataURL(croppedBlob);

        } catch (error) {
            console.error('截图过程中出错:', error);
        }
    });
}

async function get_element_position(tabId, element_type) {
    const type = `get_${element_type}_position`
    return await chrome.tabs.sendMessage(tabId, {type})
}

查询tabs

function queryTabs(queryOptions) {
    return new Promise((resolve, reject) => {
        chrome.tabs.query(queryOptions, (result) => {
            if (chrome.runtime.lastError) {
                reject(chrome.runtime.lastError);
            } else {
                resolve(result);
            }
        });
    });
}

后台和内容脚本通信

接收方

chrome.runtime.onMessage.addListener( (request, sender, sendResponse) =>{
        console.log(`receive message: `, request);
        
        sendResponse({msg: 'msg'}); 
    //表示异步操作,不会立刻关闭消息接收
    return true; 

发送方

chrome.runtime.sendMessage({type: "query"}, (res) => {
        console.log("返回结果 ", res);
    });

let selects = document.getElementsByTagName("select");
  if (selects.length !== selectAnswer.length) {
    layer.msg("页面上的题可能已经发生了变化", { icon: 0 });
    return;
  }
  let lastScope = NaN;
  let idx = -1;
  // 获取Angular作用域
  selectAnswer.forEach((answer, index) => {
    let scope = angular.element(selects[index]).scope();
    // console.log(scope.subject.sub_subjects);
    // console.log(lastScope === scope, lastScope);
    if (idx === -1) {
      idx = 0;
    } else if (lastScope === scope) {
      idx += 1;
    } else {
      idx = 0;
    }
    lastScope = scope;

    //angular.element(document.getElementsByTagName("select")[0]).scope().subject.sub_subjects;
    scope.$apply(() => {
      console.log(scope.subject.sub_subjects[idx]);
      let old = scope.subject.sub_subjects[idx].answeredOption;
      scope.subject.sub_subjects[idx].answeredOption = answer;
      scope.onChangeSubmission(scope.subject.sub_subjects[idx]);

      console.log(scope.subject.sub_subjects[idx]);
      console.log(
        `old: ${old},new:${answer}, now:${scope.subject.sub_subjects[idx].answeredOption}`,
      );
    });
    $(selects[index]).multiselect("refresh");
    angular.element(selects[index]).triggerHandler("change");
  });