简单的谷歌插件开发记录

工作上遇到一个小问题, 就是桌面软件里有个打开浏览器获取cookie的功能, 这个功能C#里可能就是打开一个webview, 然后通过api获取页面cookie. 但是在网页端就很坑了, 放iframe也不行, 毕竟打开的页面不是可控的, 无法通信, 存在跨域问题.

功能类似上图

实现代码: https://github.com/klren0312/cookies-chrome-plugin/edit/master/README.md

原理

如果非要获取, 只能用浏览器插件或者套个Electron, 当然还是用浏览器插件啦.浏览器插件, 通过右键点击发送, 可以将获取的cookie和ua发送到需要的页面.

首先插件会在每个页面创建一个id为'content-block'的DOM, 然后主页面会通过postMessage, 通知插件获取主页面的tabId, 随后, 进入需要获取cookieua的页面, 右键获取, 然后通过之前缓存的主页面tabId将获取的cookieua发送到content.js, content.jscookieua组成的json写入id为'content-block'的DOM, 主页面通过mutationObserver监听id为'content-block'的DOM的变化, 触发数据获取

实现

1. 谷歌浏览器插件基本结构

前端内容(content.js), 后台处理(utils.js), 插件弹框(popup.js, popup.html), 以及配置文件(manifest.json). 前面三个JS文件名称都是自定义的, 需要在配置文件中配置

2.配置文件

{
  "name": "Cookie与UserAgent获取",
  "description": "辅助抓取网站登陆后有效Cookies和UserAgent",
  "version": "0.0.3",
  "author": "ZZES",
  "homepage_url": "https://github.com/klren0312/cookies-chrome-plugin",
  "permissions": [
    "contextMenus",
    "tabs",
    "cookies",
    "<all_urls>"
  ],
  "icons": {
    "16": "icon-16.png",
    "48": "icon-48.png",
    "128": "icon-128.png"
  },
  "background": {
    "scripts": [
      "utils.js"
    ]
  },
  "content_scripts": [{
    "matches": ["<all_urls>"],
    "js": ["content.js"],
    "all_frames":true,
    "run_at": "document_start"
  }],
  "browser_action": {
    "default_icon": "icon-16.png",
    "default_title": "Cookie与UserAgent获取",
    "default_popup": "popup.html"
  },
  "manifest_version": 2
}

主要问题解读

1. permissions

权限介绍可以查看: https://developer.chrome.com/extensions/permissions

权限, 需要操作一些浏览器功能, 就需要列出权限, 比如需要在右键菜单使用插件, 就开启contenMenus; 需要获取tab页信息, 就开启tabs; 需要获取浏览器cookie, 就开启cookies;最后是插件的应用域名, 这个如果想在所有域名下运用, 就使用<all_urls>

2.background

后台相关处理脚本, 幕后工作者, 进行一些浏览器相关操作

3.content_scripts

前台相关操作, 比如DOM操作

4.browser_action

就是浏览器插件那块的图标和弹框


3.background代码

let mainPageId = null

// 将当前页面的cookies复制到剪切板
function copyCookies(info, tab) {
  let cookies = ''
  chrome.cookies.getAll({
    url: tab.url
  }, function (cookie) {
    // 遍历当前域名下cookie, 拼接成字符串
    cookie.forEach(v => {
      cookies += v.name + "=" + v.value + ";"
    })
    // 添加到剪切板
    const input = document.createElement('input')
    input.style.position = 'fixed'
    input.style.opacity = 0
    input.value = cookies
    document.body.appendChild(input)
    input.select()
    document.execCommand('Copy')
    document.body.removeChild(input)
  })
}

// 将当前页面的UA复制到剪切板
function copyUA () {
  const input = document.createElement('input')
  input.style.position = 'fixed'
  input.style.opacity = 0
  input.value = navigator.userAgent
  document.body.appendChild(input)
  input.select()
  document.execCommand('Copy')
  document.body.removeChild(input)
}

// 发送Cookies和UA到主页面
function sendCookieAndUA (info, tab) {
  let cookies = ''
  const ua = navigator.userAgent
  chrome.cookies.getAll({
    url: tab.url
  }, function (cookie) {
    // 遍历当前域名下cookie, 拼接成字符串
    cookie.forEach(v => {
      cookies += v.name + "=" + v.value + ";"
    })
    // 如果存在主页面的 tabId, 则将当前页的cookies发送给主页面
    let sendId = mainPageId ? mainPageId : tab.id
    chrome.tabs.sendMessage(sendId, {
      cookies: cookies,
      ua: ua
    }, function(response) {
      console.log(response)
    })
  })
}

// 给popup使用的方法
// 获取页面ID
function popupGetTabId () {
  return mainPageId
}
// 清除页面ID
function popupCleanTabId () {
  mainPageId = null
}

chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
    // https://zhuanlan.zhihu.com/p/57820028
    if (request !== 'ok') {
      if (request.type === 'tab') {
        if (request.level === 'main') { // 如果页面是主页面, 则将其 tabId 缓存, 并发送给主页面
          mainPageId = sender.tab.id
          sendResponse({type: 'tab', level: 'main', tabId: sender.tab.id})
        }
      } else if (request.type === 'cookies') {
        let cookies = ''
        chrome.cookies.getAll({
          url: request.target
        }, function (cookie) {
          // 遍历当前域名下cookie, 拼接成字符串
          cookie.forEach(v => {
            cookies += v.name + "=" + v.value + ";"
          })
          sendResponse({type: 'cookies', cookies: cookies})
        })
      }

    }
  }
)

var parent = chrome.contextMenus.create({
  "title": "Cookie与UserAgent获取",
  "contexts": ["page"]
})
var copyCookie = chrome.contextMenus.create({
  "title": "提取Cookies至剪切板",
  "parentId": parent,
  "contexts": ["page"],
  "onclick": copyCookies
})

var copyUA = chrome.contextMenus.create({
  "title": "提取UserAgent至剪切板",
  "parentId": parent,
  "contexts": ["page"],
  "onclick": copyUA
})

var sendCookieAndUA = chrome.contextMenus.create({
  "title": "发送Cookies和UA到主页面",
  "parentId": parent,
  "contexts": ["page"],
  "onclick": sendCookieAndUA
})

4.content代码

(function() {
    document.addEventListener('DOMContentLoaded', function () {
        var div = document.createElement('div')
        div.id = 'cookie-block'
        div.style.display = 'none'
        document.body.appendChild(div);
    })
    chrome.runtime.onMessage.addListener(
        function (request, sender, sendResponse) {
            if (request !== 'ok') {
                document.getElementById('cookie-block').innerText = JSON.stringify(request)
                sendResponse('ok')
            }
        }
    )
})();
window.addEventListener('message', event => {
    if (event.source !== window) {
        return
    }
    // 如果是主页面发送message, 则与background通信, 获取页面的 tabId
    if (event.data && event.data.hasOwnProperty('type') && event.data.type === 'tab' && event.data.hasOwnProperty('level') && event.data.level === 'main') {
        chrome.runtime.sendMessage({type: 'tab', level: 'main'}, function(response) {
      console.log('收到响应', response)
    })
    }
}, false)

5.功能演示

1.右键复制当前页Cookies

演示图片


1.gif

2.右键复制当前页UserAgent

演示图片


2.gif

3.右键将Cookies和UserAgent发送到主页面
主页面需要先发送 message 给插件, 缓存页面 tabId

window.parent.postMessage({type: 'tab', level: 'main'}, '*');

然后在要获取Cookie与UserAgent的页面右键选择"发送Cookies和UA到主页面"

演示图片


3.gif

6.参考资料

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容