• 谷歌浏览器插件开发


            对于浏览器插件相信大家都不陌生,谁的浏览器不装几个好用的插件呢,更是有油猴这个强大的神器。但是大家有没有尝试自己去写一个插件呢?对于这个问题,其实很长时间以来我是一直有想法但从来没有付出行动,直到前一段时间有朋友找我写一些关于浏览器自动化的程序,当然如果简单的话,直接在控制台运行js脚本就可以了(但是真的不好用),转念一想,浏览器插件也许也是一个不错的选择,感觉应该不会太难,就去稍微了解了一下,有这个想法另一方面原因之前是有看到关于用c#来直接写浏览器插件的,嗯,没错,就是Blazor。还是花了点时间去研究这个东西,最后发现,极为难用。。。

            随后简单看了下开发浏览器插件的原生写法——这不是稍微会一点前端技术就能搞么,恰好,我就懂这么一点。。。

            不过最后我并没有用插件去实现这个需求,因为我发现了另外一个简单且好用的东西:Selenium。但是刚刚学会这个东西,总得写个东西来记录一下吧,后面用到的时候也比较好找。

            现在的浏览器基本都自带密码记住的功能。但是对于有些网站却并不能做很好的识别,要么输入的位置错乱,要么账号密码错乱。所以最终写了这个插件出来。暂且叫做"密码箱"吧,我们可以自己定义捕获输入框的标识然后赋值进去,来解决上面提到的问题。

            这里我们使用到的开发工具是 Visual Studio Code,然后整体看下我们的框架,其实跟我们写前端一样的,只是多了一个manifest.json文件,这也是最重要的一个文件;不过现在大家都用Vue了,一套命令下来应有尽有。

    1. 先来看下manifest.json文件的配置,因为我们做的比较简单,所以配置的也并不多,关于每项配置的含义已经在文件中注释了。
      1. {
      2. "name": "密码箱",//插件名称
      3. "description": "密码箱",//插件描述
      4. "version": "1.0.0",//插件版本
      5. "manifest_version": 3,//文件版本2或者3,2目前是主流,3是趋势,谷歌浏览器目前是使用3版本的
      6. "background": { //常驻后台服务
      7. "service_worker": "/js/background.js"
      8. },
      9. "permissions": ["storage", "activeTab", "scripting", "contextMenus", "tabs"], //需要的权限
      10. "homepage_url": "https://www.baidu.com",//插件主页地址
      11. "action": {
      12. "default_popup": "html/popup.html",//可交互页面
      13. "default_icon": {
      14. "16": "/img/icon.png",
      15. "32": "/img/icon.png",
      16. "48": "/img/icon.png",
      17. "128": "/img/icon.png"
      18. }
      19. },
      20. "options_ui": {//插件选项配置
      21. "page": "/html/options.html",
      22. "open_in_tab": true
      23. },
      24. "icons": {//图标
      25. "16": "/img/icon.png",
      26. "32": "/img/icon.png",
      27. "48": "/img/icon.png",
      28. "128": "/img/icon.png"
      29. },
      30. "host_permissions": ["*://*/*"]//主机权限
      31. }
    2. 然后我们需要一个配置页面,来设置我们需要保存的账号信息以及表单元素即options.html和options.js。主要代码都在options.js中了,所以这里主要放下js的代码
      1. let btn_save = document.getElementById("btn_save");
      2. let btn_add = document.getElementById("btn_add");
      3. init();
      4. function add_row(id){
      5. let element_str = `
  • `;
  • let element = document.createElement("div");
  • element.setAttribute("class", "row");
  • element.setAttribute("id", "row" + id);
  • element.innerHTML = element_str;
  • document.getElementById("main").appendChild(element);
  • return element;
  • }
  • function bind_rowdel(element,id){
  • let btn_del = element.getElementsByClassName("btn_del")[0];
  • btn_del.addEventListener("click", function () {
  • let row = document.getElementById("row" + id);
  • document.getElementById("main").removeChild(row);
  • btn_save.click();
  • });
  • }
  • function init(){
  • chrome.storage.sync.get("pwd_box", function (result) {
  • let save_list = result.pwd_box;
  • if (save_list==undefined||save_list.length == 0) {
  • save_list=[];
  • save_list.push({
  • user: "",
  • pwd: "",
  • url: "",
  • user_tag: "",
  • pwd_tag: "",
  • });
  • }
  • for (let i = 0; i < save_list.length; i++) {
  • let element=add_row(i);
  • element.getElementsByClassName("user")[0].value = save_list[i].user;
  • element.getElementsByClassName("pwd")[0].value = save_list[i].pwd;
  • element.getElementsByClassName("url")[0].value = save_list[i].url;
  • element.getElementsByClassName("user_tag")[0].value = save_list[i].user_tag;
  • element.getElementsByClassName("pwd_tag")[0].value = save_list[i].pwd_tag;
  • bind_rowdel(element,i);
  • }
  • });
  • }
  • btn_save.addEventListener("click", async () => {
  • let items = document.getElementsByClassName("row");
  • let save_list = [];
  • for (let i = 0; i < items.length; i++) {
  • save_list.push({
  • user: items[i].getElementsByClassName("user")[0].value,
  • pwd: items[i].getElementsByClassName("pwd")[0].value,
  • url: items[i].getElementsByClassName("url")[0].value,
  • user_tag: items[i].getElementsByClassName("user_tag")[0].value,
  • pwd_tag: items[i].getElementsByClassName("pwd_tag")[0].value,
  • });
  • }
  • await chrome.storage.sync.set({ pwd_box: save_list }, null);
  • alert("操作成功");
  • });
  • btn_add.addEventListener("click", async () => {
  • let id=new Date().getTime();
  • let element=add_row(id);
  • bind_rowdel(element,id);
  • });
  • 最后就是我们需要做一个主动赋值的操作了。即上面提到的可交互的页面:popup.js。当然我们也可以写在background.js中,自动监听然后直接赋值,但我这里没有使用这种方式。
    1. let btn = document.getElementById("btn");
    2. async function getCurrentTab() {
    3. let queryOptions = { active: true, lastFocusedWindow: true };
    4. let [tab] = await chrome.tabs.query(queryOptions);
    5. return tab;
    6. }
    7. btn.addEventListener("click", async () => {
    8. let tab = await getCurrentTab();
    9. chrome.scripting.executeScript({
    10. target: { tabId: tab.id },
    11. func: inputs,
    12. args: [tab.url],
    13. });
    14. });
    15. function inputs(url) {
    16. chrome.storage.sync.get("pwd_box", async (result) => {
    17. let model = result.pwd_box.find((s) => {
    18. return s.url == url;
    19. });
    20. if (model != undefined) {
    21. let user_selector, pwd_selector;
    22. let utag = model.user_tag.split(":", 2);
    23. switch (utag[0].toLowerCase()) {
    24. case "id":
    25. user_selector = document.getElementById(utag[1]);
    26. break;
    27. case "class":
    28. user_selector = document.getElementsByClassName(utag[1])[0];
    29. break;
    30. case "name":
    31. user_selector = document.getElementsByName(utag[1])[0];
    32. break;
    33. default:
    34. break;
    35. }
    36. user_selector.value = model.user;
    37. //await sleep(1000);
    38. let ptag = model.pwd_tag.split(":", 2);
    39. switch (ptag[0].toLowerCase()) {
    40. case "id":
    41. pwd_selector = document.getElementById(ptag[1]);
    42. break;
    43. case "class":
    44. pwd_selector = document.getElementsByClassName(ptag[1])[0];
    45. break;
    46. case "name":
    47. pwd_selector = document.getElementsByName(ptag[1])[0];
    48. break;
    49. default:
    50. break;
    51. }
    52. pwd_selector.value = model.pwd;
    53. }
    54. });
    55. function sleep(ms) {
    56. return new Promise((resolve) => setTimeout(resolve, ms));
    57. }
    58. }

    到此,我们这个插件就写完了;

    说实话,我前端技术确实不怎么会,如果有前端大佬的话,发现语法问题希望不吝赐教。(我觉得我写js的方式都比较偏向C#)

    最后我们来看下效果吧!

    实现效果:

    我这个插件做的处理比较简单。大家有兴趣可以自己完善下。最后提一下,V2和V3的写法有很多地方还是不一样的,这里我再推荐几个网址,大家可以去学习下,写的很详细:

    【干货】Chrome插件(扩展)开发全攻略 - 我是小茗同学 - 博客园

    清单文件--扩展开发文档

    https://developer.chrome.com/extensions/manifest

    最后一个是官网。写的很全面,需要vpn访问。

     

    由简入繁,拿来即用

    更多精彩,请搜索公 Z 号:Csharp 小记

  • 相关阅读:
    卷王必备学习的MyBatis-Plus用法,不来瞧瞧吗~~
    Python面向对象(全套)
    Golang CSV Reader
    研究生拟录取分享
    SpringCloud之gateway基本使用解读
    cpu设计和实现(协处理器cp0)
    Apache Doris 基础 -- 部分数据类型及操作
    Spring Boot项目开发实战:使用STS快速构建一个生产级项目
    @Accessors 注解作用
    Leetcode 901. Online Stock Span (单调栈经典题)
  • 原文地址:https://blog.csdn.net/qq_27410185/article/details/127845750