数栈技术干货:从0到1实现谷歌插件开发探索及应用

 

1、前言

笔者以前一直想了解一些关于谷歌插件的相关知识,但愿经过谷歌插件能够更好的认识到谷歌的调试工具,同时也想着可使用谷歌插件去写一些小工具,既学习了新的东西,又有必定的趣味性。恰逢近段时间须要分享,所以花了两周时间学习和了解谷歌插件相关知识,本篇文章就将笔者在学习过程当中的一些思考分享给你们。固然,由于时间的缘由,若是笔者对于这一块的认识有不对的地方,欢迎批评指正~css

2、什么是谷歌插件

下面先介绍一下谷歌插件的主要组成部分,由于目前谷歌插件使用比较广泛的版本为 2.0 版本,所以本文都是基于 2.0 版本进行使用说明,3.0 版本相较于 2.0 版本更为简便,感兴趣的同窗能够点击文章末尾处的连接了解更多相关知识。html

(一)配置文件react

谷歌插件的核心文件就是配置文件--manifest.json(清单)文件。
其中,manifest.json 文件最基本的 Api 以下:git

{
    "name": "chrome extension",
    "version": "1.0.0",
    "manifest_version": 2,
    "description": "A litlle chrome extension demo"
}

主要是包含所写谷歌插件的名称、版本以及相关描述,其中 manifest_version 表示清单文件版本。
manifest.json 做为谷歌插件的核心部分,笔者认为该文件对插件来讲就至关于一个入口配置文件,开发人员只须要在这个文件经过配置相应的 js,调用谷歌浏览器提供的 Api,就能实现达到完善这个插件的目的。github

一、基本使用Apichrome

在清单文件中有 许多的Api,笔者就不一一列举了,下面为你们介绍几个笔者认为比较重要的 Api,经过如下几个 Api 的介绍,但愿可使读者对于谷歌插件的开发过程有一个大概的认识。json

1)browser_action跨域

{
    ...

    "browser_action": {
        "default_icon": {
            "16": "images/get_started16.png",
            "32": "images/get_started32.png"
        },
        "default_title": "谷歌划词翻译",
        "default_popup": "popup.html"
    },

    ...
}

browser_action可设置浏览器右上角的图标及名称。
default_popup 可配置点击图标后会出现的一个小窗口,这里能够作一些临时性的操做浏览器


2)permissionsantd

{
    ...

    "permissions": [ "activeTab", "storage", "tabs", "contextMenus" ],
    
    ...
}

permissions 可配置谷歌插件权限申请,如 contextMenus(右键菜单)、 tabs(标签)和storage(插件本地存储)。

3)content_scripts

{
    ...

    "content_scripts": {
        "matches": ["<all_urls>"],
        "css": ["content/content_script.css"],
        "js": ["content/content_script.js"]
    },
    
    ...
}

content-scripts其实就是谷歌插件中向页面注入脚本的一种形式(虽然名为 script,其实还能够包括 CSS 的),借助 content-scripts 能够实现经过配置的方式轻松向指定页面注入 JS 和 CSS。

4)background

{
    ···

    "background": {
        "scripts": ["background.js"],
        "persistent": false
    },

    ···
}

background 是一个常驻的页面,它的生命周期是插件中全部类型页面中最长的,它随着浏览器的打开而打开,随着浏览器的关闭而关闭,因此一般把须要一直运行的、启动就运行的及全局的代码放在 background 里面。

笔者也画了一个上面涉及到的脚本在浏览器中的分布,以下图:

 

以上就是笔者认为比较重要的一些Api,在介绍完以后,感兴趣的同窗就能够开始着手写几个简单的工具,用来实现本身远大的抱负以及理想,升华本身高贵的灵魂。

一些同窗可能不太知道开发谷歌插件的前置条件,在这里为你们简单介绍下。

首先须要打开管理扩展程序,打开开发者模式。

点击加载已解压的程序按钮便可加载本地谷歌插件,开发的时候代码若是有更新的话,须要刷新已加载插件,点击关闭后再开启,没必要刷新开发页面。

在了解完前置条件后,笔者将在下文中为你们分享谷歌划词翻译插件从0-1的实现过程,经过开发这个工具也能够加深对于你们谷歌插件的认识。

3、谷歌划词翻译插件

谷歌翻译算是笔者使用比较频繁的插件,对于在网页上看到的不懂的英文单词或者句子,直接使用鼠标选中,轻松快捷的翻译出相应的中文。所以在学习的过程当中,笔者就在想谷歌浏览器插件的翻译工具是如何实现的呢?

(一)思考

如何去作一个划词翻译插件,首先要考虑的有如下几点:

  • 如何实现翻译效果
  • 如何选中咱们须要的元素
  • 选中元素以后如何展现划词翻译面板
  • 全部的浏览器 Tab 都须要支持翻译效果

思考完上面的这些点后,带着这几个疑惑,笔者将在下文一一解答,同时也列举一下遇到的一些点。

(二)划词翻译面板

首先不去考虑该插件的功能,先写下划词翻译的面板的样式,所达到的效果以下:

 

HTML 代码以下:

<div class="translate-panel show">
    <header>谷歌划词翻译插件<span class="close">X</span></header>
    <main>
        <div class="source">
            <div class="title">英文</div>
            <div class="content">test</div>
        </div>
        <div class="result">
            <div class="title">简体中文</div>
            <div class="content">...</div>
        </div>
    </main>
</div>

将上面的样式简单写好以后,开始考虑如何将划词翻译的面板展现在浏览器当前页面。对于谷歌浏览器来讲,在网页上进行的交互是属于 content_scripts 的,须要引入划词翻译面板所须要的 JS 或者 CSS来生成当前面板。

其次,在配置文件中配置 content_scripts引入 JS 文件,动态的生成 DOM 元素。大体的思路就是经过监听到鼠标松开后,去生成翻译面板,在生成的元素上面添加 opacity 样式控制显隐,使用谷歌免费翻译 Api 进行翻译。

其中代码以下所示:

// manifest.json
{
    ...

    "content_scripts": {
        "matches": ["<all_urls>"],
        "css": ["content_script.css"],
        "js": ["content_script.js"]
    },
    "permissions": [
        "activeTab"
    ],

    ...
}

// content_script.js
class TranslatePanel {

    createPanel = () => {
        let wrapper = document.createElement('div')
        wrapper.innerHTML = `
            <header>谷歌划词翻译插件<span class="close">X</span></header>
            <main>
                <div class="source">
                    <div class="title">英文</div>
                    <div class="content">test</div>
                </div>
                <div class="result">
                    <div class="title">简体中文</div>
                    <div class="content">...</div>
                </div>
            </main>
        `
        wrapper.classList.add('translate-panel')
        wrapper.querySelector('.close').onclick = () => {
            this.wrapper.classList.remove('show')
        }
        document.body.appendChild(wrapper)
        this.wrapper = wrapper

    }

    showPanel = () => {
        this.wrapper.classList.add('show')
    }

    translateSelect = (content) => {
        const source = this.wrapper.querySelector('.source .content')
        const result = this.wrapper.querySelector('.result .content')
        source.innerHTML = content
        result.innerHTML = '翻译中...'

        fetch(`https://translate.google.cn/translate_a/single?client=at&sl=en&tl=zh-CN&dt=t&q=${content}`)
            .then(res => res.json())
            .then(res => {
                result.innerHTML = res[0][0][0]
            })
    }

    locationPanel = (target) => {
        this.wrapper.style.top = target.y + 'px'
        this.wrapper.style.left = target.x + 'px'
   }
}

let panel = new TranslatePanel()
panel.createPanel()

window.onmouseup = (target) => {
    // 获取选中内容
    const content = window.getSelection().toString().trim()

    if (!content) return
    panel.locationPanel({ x: target.pageX, y: target.pageY })
    panel.translateSelect(content)
    panel.showPanel()

}

在上面过程的中,笔者使用了谷歌免费的翻译接口,可是这个接口按照目前的设置仍是有一点问题,咱们暂时不表。如今划词翻译的面板就已经基本写好了。

(三)脚本通讯

划词翻译插件开发到这里,细心的同窗应该发现了,每次选中单词时都会触发划词翻译功能,此时急需一个控制翻译功能的开关,这个开关就能够放在 popup 脚本上面。

具体的样式的实现就不去介绍了,主要看一下 HTML 结构:

<div class="switch-wrapper">
    <div class="switch-desc">是否启用划词翻译</div>
    <input type="checkbox" class="switch" />
</div>

基本效果以下:

 

此时面板和划词翻译的面板都已经有了,再考虑一下如何实现 popup 脚本与 content_script 脚本之间的通讯。首先,在
popup 脚本,咱们在打开窗口的时候须要去查询是否有存储开启划词翻译的状态,同时,
同时当状态发生变动的时候须要将其存储时,再在当前的Tab下面发送请求。

// popup.js
let switchWrapp = document.querySelector('.switch')

chrome.storage.sync.get(['checked'], (target) => {
    if (target) {
        switchWrapp.checked = target.checked
    }
})

switchWrapp.onclick = (e) => {
    chrome.storage.sync.set({ checked: e.target.checked })

    chrome.tabs.query( {active: true, currentWindow: true }, (tabs) => {
        chrome.tabs.sendMessage(tabs[0].id, { checked: e.target.checked })
    })
}

上面代码中的 chrome.storage 可用于存储数据,追踪数据。storage.sync 的做用是让谷歌浏览器的数据同步,这使得在不一样 Tab 页上面切换的状态也是能够同步的,同时也不用将数据保存在 background 后台页面中,storage还有不少Api好比监听 storage数据变化的onChanged,这里就不一一介绍了。

将开启或关闭划词翻译的状态发送后,content_script.JS 须要添加监听事件,获取到该状态后,进行关闭或开启操做。

// content_script.js
let checked = false

window.onmouseup = (target) => {
    ···

    if (!content || !checked) return

    ···

}

chrome.storage.sync.get(['checked'], (target) => {
    if (target) checked = target.checked
})

chrome.runtime.onMessage.addListener((target) => {
    if (target) {
        checked = target.checked
    }
})

在开发过程当中,发如今当前的 Tab 是能够去完成这个操做的,可是当开启了多个 Tab 的状况下就会出现开启翻译却不能展现翻译面板的状况。

针对以上状况笔者思考了一下,此时应该将checked储存起来,不该该放在 content_script 脚本当中。

// content_script.js
let panel = new TranslatePanel()
panel.createPanel()

window.onmouseup = (target) => {
    // 获取选中内容
    const content = window.getSelection().toString().trim()

    if (!content) return

    window.chrome.storage.sync.get(['checked'], (result) => {
        if (result.checked) {
            panel.locationPanel({ x: target.pageX, y: target.pageY })
            panel.translateSelect(content)
            panel.showPanel()
        }
    })
}

chrome.runtime.onMessage.addListener((target) => {
    if (target.type == 'CHECKED') {
        chrome.storage.sync.set({ checked: target.checked })
    }
})

以上,popup 脚本和 content_script 脚本之间就实现了通讯,翻译插件也能够经过 popup 上面的按钮,进行开启或关闭翻译功能。

同理,也能够知道其余模块也是能够经过这种方式去进行通讯,不一样的是其余脚本向 content_script 通讯是须要使用 tabs,先查找到当前的 Tab 在发送请求。

(四)右键直达翻译页面

当关闭划词翻译的时候,直接没法翻译选中内容也不是很友好,这个时候能够设置为点击右键的时候出现翻译菜单项。由于这部份内容须要一直存在就加在 background 中。

// backgrond.js
// 当扩展程序第一次安装、更新至新版本或 Chrome 浏览器更新至新版本时产生
chrome.runtime.onInstalled.addListener(() => {
    chrome.contextMenus.create({
        "id": "SELECT_TRANSLATE",
        "title": "翻译 %s",
        "contexts": ["selection"]
    })
})

chrome.contextMenus.onClicked.addListener((target) => {
    if (target.menuItemId == 'SELECT_TRANSLATE') {
        chrome.tabs.create({url: `https://translate.google.cn/?sl=en&tl=zh-CN&text=${target.selectionText}&op=translate`})
    }
})

(五)跨域问题

开发过程当中,有的同窗也看出来了一个问题,好比说谷歌的这个翻译的 Api 须要同源的状况下才能正常调用该接口,而后就只能在谷歌翻译的页面中使用划词翻译,场面一度十分尴尬...

那么,正常来讲这个划词翻译使用起来也是十分不合理的,接下来就须要解决一下这个跨域的问题。

笔者当时想要尝试的是使用 JSONP,也就是去使用嵌入脚本去进行跨域,发现仍是会有一些问题,主要是谷歌的翻译的接口不支持回调函数。

同时也去查阅了一些资料,发现是能够在 content_script 中通知 background,background 后台去调用谷歌翻译的 Api 是来避免这个状况的。

主要由于 background 的权限很是高,几乎能够调用全部的 Chrome 扩展 Api,并且它能够无限制跨域,也就是能够跨域访问任何网站而无须要求对方设置 CORS。

具体添加的代码以下:

// content_script.js
translateSelect = (content) => {
    const source = this.wrapper.querySelector('.source .content')
    const result = this.wrapper.querySelector('.result .content')
    source.innerHTML = content
    result.innerHTML = '翻译中...'

    chrome.runtime.sendMessage({ type: 'QUERY_TRANSLATE', queryContent: content }, (res) => {
        result.innerHTML = res[0][0][0]
    })
}

// background.js
chrome.runtime.onMessage.addListener((request, sender, callBack)  => {
    if (request.type == 'QUERY_TRANSLATE') {
        fetch(`https://translate.google.cn/translate_a/single?client=at&sl=en&tl=zh-CN&dt=t&q=${request.queryContent}`)
            .then(res => res.json())
            .then(res => {
                callBack(res)
            })
        return true
    }
})

background 中的发送消息的监听事件返回 true 是为了与 content_script 的消息通道保持打开,经过异步的方式发送请求。

如今想一想,若是使用插件的 background 就能够去跨域去进行请求一些借口,使用不得当的话感受仍是很危险的,能够去获取其余网站的一些信息,因而可知,仍是要慎重的进行此操做。

(六)待完善的点

  • 支持其余语言的翻译,谷歌翻译的接口有两个 Api,sl(文本翻译以前的语言) 和 tl(文本须要翻译成的语言) 可经过改变对应的值支持其余语言的翻译;
  • 样式完善,实现先选中图标在进行翻译。多添加一步感受对交互更友好,不用去进行开关的操做。

代码结构

介绍完了划词翻译插件,笔者本来是打算再分享一个关于谷歌 devtool 开发工具,开发一个相似于 React Developer Tools 的本地开发工具,可是因为时间也是不太够,涉及到的点比较多,同时查阅了不少的资料对于这一块的介绍也是比较的浅,因此仍是决定着重于介绍划词翻译。

相信经过划词翻译的开发能使得读者比较快速的认识到谷歌插件,除了谷歌插件外,若是对于 devtool 工具插件开发有兴趣的同窗也能够去了解一下。

另外,有的同窗可能会认为目前开发的效率是有一点低的,如今的话谷歌插件的开发也是能够基于 react + antd 去进行开发的,也是能够达到高效快速的去开发一个插件的效果。

4、结尾

在学习谷歌插件的时候,笔者遇到了一些问题,好比说文档比较少,官方文档又是英文的还常常 404,不过呢,好在谷歌浏览器有提供了不少 Api,笔者这边在写插件的时候也感受到了不少趣味性。本篇文章仍是写的比较通俗易懂,若是有什么点写的不对或着不清晰的地方,欢迎你们留言进行探讨。

相关资源及参考:

 

本文整理自:技术干货丨谷歌插件开发探索及其应用

数栈是云原生—站式数据中台PaaS,咱们在github和gitee上有一个有趣的开源项目:FlinkX,记得给咱们点个star!star!star!

gitee开源项目:https://gitee.com/dtstack_dev_0/flinkx

github开源项目:https://github.com/DTStack/flinkx

FlinkX是一个基于Flink的批流统一的数据同步工具,既能够采集静态的数据,好比MySQL,HDFS等,也能够采集实时变化的数据,好比MySQL binlog,Kafka等,是全域、异构、批流一体的数据同步引擎,你们若是有兴趣,欢迎来github社区找咱们玩~

相关文章
相关标签/搜索