Chrome扩大顺序开辟

十一在家无聊时开辟了这个项目。其起点是想经由历程chrome插件,来保留网页上选中的文本。后来就顺手把前后端都做了(Koa2 + React):

chrome插件源码

插件对应的前后端源码

概述

chrome扩大顺序

chrome扩大顺序人人应当都很熟习了,它能够经由历程剧本帮我们完成一些疾速的操纵。经由历程插件能够捕捉到网页内容、标签页、当地存储,或许用户的操纵行动;它也能够在肯定程度上转变阅读器的UI,比方页面上右键的菜单、阅读器右上角点击插件logo后的弹窗,或许阅读器新标签页

开辟启事

依据通例,开辟前多问问本身 why? how?

why:

  • 我在平常看博文时,关于一些段落想举行摘抄或许备注,又懒得复制粘贴

how:

  • 一个chrome扩大顺序,能够经由历程鼠标右键的菜单,或许键盘快捷键疾速保留当前页面上挑选的文本

  • 假如没有挑选文本,则保留网页链接

  • 要有对应的背景效劳,保留 user、cliper、page (后话,本文不触及)

  • 还要有对应的前端,以便阅读我的保留纪录 (后话,本文不触及)

先上个结果图:

《Chrome扩大顺序开辟》

《Chrome扩大顺序开辟》

《Chrome扩大顺序开辟》

clip 有剪辑之意,因而项目命名为 cliper

这两天终究安奈不住买了效劳器,终究把网址布置了,也上线了chrome插件:

manifest.json

在项目根目次下竖立manifest.json文件,其中会涵盖扩大顺序的基本信息,并指明须要的权限和资本文件

{
  // 以下为必写
  "manifest_version": 2, // 必需为2,1号版本已弃用
  "name": "cliper", // 扩大顺序称号
  "version": "0.01", // 版本号
  
  // 以下为选填
  
  // 引荐
  "description": "形貌",
  "icons": {
    "16": "icons/icon_16.png",
    "48": "icons/icon_48.png",
    "64": "icons/icon_64.png",
    "128": "icons/icon_128.png"
  },
  "author": "ecmadao",
  
  // 依据本身运用的权限填写
  "permissions": [
    // 比方
    "tab",
    "storage",
    // 假如会在js中请求外域API或许资本,则要把外域链接到场
    "http://localhost:5000/*"
  ],
  
  // options_page,指右键点击右上角里的插件logo时,弹出列表中的“选项”是不是可点,以及在能够点击时,左键点击后翻开的页面
  "options_page": "view/options.html",
  
  // browser_action,左键点击右上角插件logo时,弹出的popup框。不填此项则点击logo不会有效
  "browser_action": {
    "default_icon": {
      "38": "icons/icon_38.png"
    },
    "default_popup": "view/popup.html", // popup页面,实在就是平常的html
    "default_title" : "保留到cliper"
  },
  
  // background,背景实行的文件,平常只须要指定js即可。会在阅读器翻开后全局范围内背景运转
  "background": {
    "scripts": ["js/vendor/jquery-3.1.1.min.js", "js/background.js"],
    // persistent代表“是不是耐久”。假如是一个纯真的全局背景js,须要一向运转,则不需设置persistent(或许为true)。当设置为false时转变为事宜js,照旧存在于背景,在须要时加载,余暇时卸载
    "persistent": false
  },
  
  // content_scripts,在各个阅读器页面里运转的文件,能够猎取到当前页面的上下文DOM
  "content_scripts": [
    {
      // matches 婚配 content_scripts 能够在哪些页面运转
      "matches" : ["http://*/*", "https://*/*"],
      "js": ["js/vendor/jquery-3.1.1.min.js", "js/vendor/keyboard.min.js", "js/selection.js", "js/notification.js"],
      "css": ["css/notification.css"]
    }
  ]
}

综上,我们一共有三种资本文件,针对着三个运转环境:

  • browser_action

    • 掌握logo点击后涌现的弹窗,涵盖相干的html/js/css

    • 在弹窗中,会举行登录/注册的操纵,并将用户信息保留在当地贮存中。已登录用户则展现基本信息

  • background

    • 在背景延续运转,或许被事宜叫醒后运转

    • 右键菜单的点击和异步保留事宜将在这里触发

  • content_scripts

    • 当前阅读的页面里运转的文件,能够操纵DOM

    • 因而,我会在这个文件里监听用户的挑选事宜

注:

  • content_scripts中假如没有matches,则扩大顺序没法一般加载,也不能经由历程“加载未封装的扩大顺序”来增加。假如你的content_scripts中有js能够针对一切页面运转,则填写"matches" : ["http://*/*", "https://*/*"]即可

  • 引荐将background中的persistent设置为false,依据事宜来运转背景js

差别运转环境JS的绳命周期

如上所述,三种JS有着三种运转环境,它们的生命周期、可操纵DOM/接口也差别

content_scripts

content_scripts会在每一个标签页初始化加载的时刻举行挪用,封闭页面时卸载

内容剧本,在每一个标签页下运转。虽然它能够访问到页面DOM,但没法访问到这个内里里,其他JS文件竖立的全局变量或许函数。也就是说,各个content_scripts(以及外部JS文件)之间是互相自力的,只要:

"content_scripts": [
  {
    "js": [...]
  }
]

js所定义的一个Array里的各个JS能够互相影响。

background

官方发起将背景js设置为"persistent": false,以便在须要时加载,再次进入余暇状况后卸载

什么时刻会让background的资本文件加载呢?

  • 运用顺序第一次装置或许更新

  • 监听某个事宜触发(比方chrome.runtime.onInstalled.addListener)

  • 监听其他环境的JS文件发送音讯(比方chrome.runtime.onMessage.addListener)

  • 扩大顺序的其他资本文件挪用了runtime.getBackgroundPage

browser_action

browser_action里的资本会在弹窗翻开时初始化,封闭时卸载

browser_action里定义的JS/CSS运转环境仅限于popup,而且会在每次点开弹窗的时刻初始化。然则它能够挪用一些chrome api,以此来和其他js举行交互

除此以外:

  • browser_action的HTML文件里运用的JS,不能直接以<script></script>的情势行内写入HTML里,须要自力成JS文件再引入

  • 假如有其他第三方依靠,比方jQuery等文件,也没法经由历程CDN引入,而须要坚持资本文件到项目目次后再引入

差别运转环境JS之间的交互

虽然运转环境和绳命周期都不雷同,但荣幸的是,chrome为我们供应了一些三种JS都通用的API,能够起到JS之间互相通信的结果。

chrome.runtime

音讯通报

平常的音讯通报

经由历程runtimeonMessagesendMessage等要领,能够在各个JS之间通报并监听音讯。举个栗子:

popup.js中,我们让它初始化以后发送一个音讯:

chrome.runtime.sendMessage({
  method: 'showAlert'
}, function(response) {});

然后在background.js中,监听音讯的吸收,并举行处置惩罚:

chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) {
  if (message.method === 'showAlert') {
    alert('showAlert');
  }
});

以上代码,会在每次翻开插件弹窗的时刻弹出一个Alert。

chrome.runtime的经常使用要领:

// 猎取当前扩大顺序中正在运转的背景网页的 JavaScript window 对象
chrome.runtime.getBackgroundPage(function (backgroundPage) {
  // backgroundPage 即 window 对象
});
// 发送音讯
chrome.runtime.sendMessage(message, function(response) {
  // response 代表音讯复兴,能够接受到经由历程 sendResponse 要领发送的音讯复兴
});
// 监听音讯
chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) {
  // message 就是你发送的 message
  // sender 代表发送者,能够经由历程 sender.tab 推断音讯是不是是从内容剧本发出
  // sendResponse 能够直接发送复兴,如:
  sendResponse({
    method: 'response',
    message: 'send a response'
  });
});

须要注重的是,即使你在多个JS中注册了音讯监听onMessage.addListener,也只要一个监听者能收到经由历程runtime.sendMessage发送出去的音讯。假如须要差别的监听者离别监听音讯,则须要运用chrome.tab API来指定音讯吸收对象

举个栗子:

上文说过,须要在content_scripts中监听挑选事宜,猎取挑选的文本,而关于右键菜单的点击则是在background中监听的。那末须要把挑选的文本作为音讯,发送给background,在background完成异步保留。

// content_scripts 中猎取挑选,并发送音讯
// js/selection.js

// 猎取挑选的文本
function getSelectedText() {
  if (window.getSelection) {
    return window.getSelection().toString();
  } else if (document.getSelection) {
    return document.getSelection();
  } else if (document.selection) {
    return document.selection.createRange().text;
  }
}
// 组建信息
function getSelectionMessage() {
  var text = getSelectedText();
  var title = document.title;
  var url = window.location.href;
  var data = {
    text: text,
    title: title,
    url: url
  };
  var message = {
    method: 'get_selection',
    data: data
  }
  return message;
}
// 发送音讯
function sendSelectionMessage(message) {
  chrome.runtime.sendMessage(message, function(response) {});
}
// 监听鼠标松开的事宜,只要在右键点击时,才会去猎取文本
window.onmouseup = function(e) {
  if (!e.button === 2) {
    return;
  }
  var message = getSelectionMessage();
  sendSelectionMessage(message);
};
// background 中吸收音讯,监听右键菜单的点击,并异步保留数据
// js/background.js

// 竖立一个全局对象,来保留吸收到的音讯值
var selectionObj = null;

// 起首要竖立菜单
chrome.runtime.onInstalled.addListener(function() {
  chrome.contextMenus.create({
    type: 'normal',
    title: 'save selection',
    id: 'save_selection',
    // 有挑选才会涌现
    contexts: ['selection']
  });
});
// 监听菜单的点击
chrome.contextMenus.onClicked.addListener(function(menuItem) {
  if (menuItem.menuItemId === "save_selection") {
    addCliper();
  }
});

// 音讯监听,吸收从 content_scripts 通报来的音讯,并保留在一个全局对象中
chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) {
  if (message.method === 'get_selection') {
    selectionObj = message.data;
  }
});

// 异步保留
function addCliper() {
  $.ajax({
    // ...
  });
}
长链接

经由历程chrome.runtime.connect(或许chrome.tabs.connect)能够竖立起差别范例JS之间的长链接。

信息的发送者须要制订奇特的信息范例,发送并监听信息:

var port = chrome.runtime.connect({type: "connection"});
port.postMessage({
  method: "add",
  datas: [1, 2, 3]
});
port.onMessage.addListener(function(msg) {
  if (msg.method === "answer") {
      console.log(msg.data);
  }
});

而接受者则要注册监听,并推断音讯的范例:

chrome.runtime.onConnect.addListener(function(port) {
  console.assert(port.type == "connection");
  port.onMessage.addListener(function(msg) {
    if (msg.method == "add") {
      var result = msg.datas.reduce(function(previousValue, currentValue, index, array){
      return previousValue + currentValue;
  });
      port.postMessage({
        method: "answer",
        data: result
      });
    }
  });
});

chrome.tabs

要运用这个API则须要先在manifest.json中注册:

"permissions": [
  "tabs",
  // ...
]
// 猎取到当前的Tab
chrome.tabs.getCurrent(function(tab) {
  // 经由历程 tab.id 能够拿到标签页的ID
});

// 经由历程 queryInfo,以Array的情势筛选出相符前提的tabs
chrome.tabs.query(queryInfo, function(tabs) {})

// 精准的给某个页面的`content_scripts`发送音讯
chrome.tabs.sendMessage(tabId, message, function(response) {});

举个栗子:

background.js中,我们猎取到当前Tab,并发送音讯:

chrome.tabs.getCurrent(function(tab) {
  chrome.tabs.sendMessage(tab.id, {
    method: 'tab',
    message: 'get active tab'
  }, function(response) {});
});
// 或许
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
  chrome.tabs.sendMessage(tabs[0].id, {
    method: 'tab',
    message: 'get active tab'
  }, function(response) {
  });
});

然后在content_scripts中,举行音讯监听:

chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) {
  if (message.method === 'tab') {
    console.log(message.message);
  }
});

chrome.storage

chrome.storage是一个基于localStorage的当地贮存,但chrome对其举行了IO的优化,能够贮存对象情势的数据,也不会由于阅读器完整封闭而清空。

一样,运用这个API须要先在manifest.json中注册:

"permissions": [
  "storage",
  // ...
]

chrome.storage有两种情势,chrome.storage.syncchrome.storage.local

chrome.storage.local是基于当地的贮存,而chrome.storage.sync会先推断当前用户是不是登录了google账户,假如登录,则会将贮存的数据经由历程google效劳自动同步,不然,会运用chrome.storage.local仅举行当地贮存

注:由于贮存区没有加密,所以不应当贮存用户的敏感信息

API:

// 数据贮存
StorageArea.set(object items, function callback)

// 数据猎取
StorageArea.get(string or array of string or object keys, function callback)

// 数据移除
StorageArea.remove(string or array of string keys, function callback)

// 清空悉数贮存
StorageArea.clear(function callback)

// 监听贮存的变化
chrome.storage.onChanged.addListener(function(changes, namespace) {});

举栗子:

我们在browser_action完成了用户的登录/注册操纵,将部份用户信息贮存在storage中。每次初始化时,都邑搜检是不是有贮存,没有的话则须要用户登录,胜利后再增加:

// browser_action
// js.popup.js

chrome.storage.sync.get('user', function(result) {
  // 经由历程 result.user 猎取到贮存的 user 对象
  result && setPopDOM(result.user);
});

function setPopDOM(user) {
  if (user && user.userId) {
    // show user UI
  } else {
    // show login UI
  }
};

document.getElementById('login').onclick = function() {
  // login user..
  // 经由历程 ajax 请求异步登录,猎取到胜利的回调后,将返回的 user 对象贮存在 storage 中
  chrome.storage.sync.set({user: user}, function(result) {});
}

而在其他环境的JS里,我们能够监听storage的变化:

// background
// js/background.js

// 一个全局的 user 对象,用来保留用户信息,以便在异步时发作 userId
var user = null;

chrome.storage.onChanged.addListener(function(changes, namespace) {
  for (key in changes) {
    if (key === 'user') {
      console.log('user storage changed!');
      user = changes[key];
    }
  }
});

大体上,我们目前为止理清了三种环境下JS的差别,以及他们交换和贮存的体式格局。除此以外,另有popup弹窗、右键菜单的竖立和运用。实在运用这些学问就充足做出一个简朴的chrome扩大了。

正式宣布

实在我以为全部历程中最蛋疼的一步就是把插件正式宣布到chrome市肆了。

末了终究搞定,线上可见:cliper extension

进修资本

下一步?

  • 插件功用丰富化

  • 插件可在网页上高亮展现标记的文本

  • es6 + babel重构

  • 须要运用框架吗?

注:本文源码位于github堆栈:cliper-chrome,线上产物见:clipercliper extension

    原文作者:ecmadao
    原文地址: https://segmentfault.com/a/1190000007182038
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞