日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

Chrome擴(kuò)展程序開發(fā)

 典刻華章 2017-11-20


概述

chrome擴(kuò)展程序

chrome擴(kuò)展程序大家應(yīng)該都很熟悉了,它可以通過腳本幫我們完成一些快速的操作。通過插件可以捕捉到網(wǎng)頁內(nèi)容、標(biāo)簽頁、本地存儲(chǔ),或者用戶的操作行為;它也可以在一定程度上改變?yōu)g覽器的UI,例如頁面上右鍵的菜單、瀏覽器右上角點(diǎn)擊插件logo后的彈窗,或者瀏覽器新標(biāo)簽頁

開發(fā)緣由

按照慣例,開發(fā)前多問問自己 why? how?

why:

  • 我在平常看博文時(shí),對(duì)于一些段落想進(jìn)行摘抄或者備注,又懶得復(fù)制粘貼

how:

  • 一個(gè)chrome擴(kuò)展程序,可以通過鼠標(biāo)右鍵的菜單,或者鍵盤快捷鍵快速保存當(dāng)前頁面上選擇的文本

  • 如果沒有選擇文本,則保存網(wǎng)頁鏈接

  • 要有對(duì)應(yīng)的后臺(tái)服務(wù),保存 user、cliper、page (后話,本文不涉及)

  • 還要有對(duì)應(yīng)的前端,以便瀏覽我的保存記錄 (后話,本文不涉及)

先上個(gè)成果圖:

clip 有剪輯之意,因此項(xiàng)目命名為 cliper

這兩天終于安奈不住買了服務(wù)器,終于把網(wǎng)址部署了,也上線了chrome插件:

manifest.json

在項(xiàng)目根目錄下創(chuàng)建manifest.json文件,其中會(huì)涵蓋擴(kuò)展程序的基本信息,并指明需要的權(quán)限和資源文件

{
  // 以下為必寫
  "manifest_version": 2, // 必須為2,1號(hào)版本已棄用
  "name": "cliper", // 擴(kuò)展程序名稱
  "version": "0.01", // 版本號(hào)
  
  // 以下為選填
  
  // 推薦
  "description": "描述",
  "icons": {
    "16": "icons/icon_16.png",
    "48": "icons/icon_48.png",
    "64": "icons/icon_64.png",
    "128": "icons/icon_128.png"
  },
  "author": "ecmadao",
  
  // 根據(jù)自己使用的權(quán)限填寫
  "permissions": [
    // 例如
    "tab",
    "storage",
    // 如果會(huì)在js中請(qǐng)求外域API或者資源,則要把外域鏈接加入
    "http://localhost:5000/*"
  ],
  
  // options_page,指右鍵點(diǎn)擊右上角里的插件logo時(shí),彈出列表中的“選項(xiàng)”是否可點(diǎn),以及在可以點(diǎn)擊時(shí),左鍵點(diǎn)擊后打開的頁面
  "options_page": "view/options.html",
  
  // browser_action,左鍵點(diǎn)擊右上角插件logo時(shí),彈出的popup框。不填此項(xiàng)則點(diǎn)擊logo不會(huì)有用
  "browser_action": {
    "default_icon": {
      "38": "icons/icon_38.png"
    },
    "default_popup": "view/popup.html", // popup頁面,其實(shí)就是普通的html
    "default_title" : "保存到cliper"
  },
  
  // background,后臺(tái)執(zhí)行的文件,一般只需要指定js即可。會(huì)在瀏覽器打開后全局范圍內(nèi)后臺(tái)運(yùn)行
  "background": {
    "scripts": ["js/vendor/jquery-3.1.1.min.js", "js/background.js"],
    // persistent代表“是否持久”。如果是一個(gè)單純的全局后臺(tái)js,需要一直運(yùn)行,則不需配置persistent(或者為true)。當(dāng)配置為false時(shí)轉(zhuǎn)變?yōu)槭录s,依舊存在于后臺(tái),在需要時(shí)加載,空閑時(shí)卸載
    "persistent": false
  },
  
  // content_scripts,在各個(gè)瀏覽器頁面里運(yùn)行的文件,可以獲取到當(dāng)前頁面的上下文DOM
  "content_scripts": [
    {
      // matches 匹配 content_scripts 可以在哪些頁面運(yùn)行
      "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"]
    }
  ]
}

綜上,我們一共有三種資源文件,針對(duì)著三個(gè)運(yùn)行環(huán)境:

  • browser_action

    • 控制logo點(diǎn)擊后出現(xiàn)的彈窗,涵蓋相關(guān)的html/js/css

    • 在彈窗中,會(huì)進(jìn)行登錄/注冊(cè)的操作,并將用戶信息保存在本地儲(chǔ)存中。已登錄用戶則展現(xiàn)基本信息

  • background

    • 在后臺(tái)持續(xù)運(yùn)行,或者被事件喚醒后運(yùn)行

    • 右鍵菜單的點(diǎn)擊和異步保存事件將在這里觸發(fā)

  • content_scripts

    • 當(dāng)前瀏覽的頁面里運(yùn)行的文件,可以操作DOM

    • 因此,我會(huì)在這個(gè)文件里監(jiān)聽用戶的選擇事件

注:

  • content_scripts中如果沒有matches,則擴(kuò)展程序無法正常加載,也不能通過“加載未封裝的擴(kuò)展程序”來添加。如果你的content_scripts中有js可以針對(duì)所有頁面運(yùn)行,則填寫"matches" : ["http://*/*", "https://*/*"]即可

  • 推薦將background中的persistent設(shè)置為false,根據(jù)事件來運(yùn)行后臺(tái)js

不同運(yùn)行環(huán)境JS的繩命周期

如上所述,三種JS有著三種運(yùn)行環(huán)境,它們的生命周期、可操作DOM/接口也不同

content_scripts

content_scripts會(huì)在每個(gè)標(biāo)簽頁初始化加載的時(shí)候進(jìn)行調(diào)用,關(guān)閉頁面時(shí)卸載

內(nèi)容腳本,在每個(gè)標(biāo)簽頁下運(yùn)行。雖然它可以訪問到頁面DOM,但無法訪問到這個(gè)里面里,其他JS文件創(chuàng)建的全局變量或者函數(shù)。也就是說,各個(gè)content_scripts(以及外部JS文件)之間是相互獨(dú)立的,只有:

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

js所定義的一個(gè)Array里的各個(gè)JS可以相互影響。

background

官方建議將后臺(tái)js配置為"persistent": false,以便在需要時(shí)加載,再次進(jìn)入空閑狀態(tài)后卸載

什么時(shí)候會(huì)讓background的資源文件加載呢?

  • 應(yīng)用程序第一次安裝或者更新

  • 監(jiān)聽某個(gè)事件觸發(fā)(例如chrome.runtime.onInstalled.addListener)

  • 監(jiān)聽其他環(huán)境的JS文件發(fā)送消息(例如chrome.runtime.onMessage.addListener)

  • 擴(kuò)展程序的其他資源文件調(diào)用了runtime.getBackgroundPage

browser_action

browser_action里的資源會(huì)在彈窗打開時(shí)初始化,關(guān)閉時(shí)卸載

browser_action里定義的JS/CSS運(yùn)行環(huán)境僅限于popup,并且會(huì)在每次點(diǎn)開彈窗的時(shí)候初始化。但是它可以調(diào)用一些chrome api,以此來和其他js進(jìn)行交互

除此以外:

  • browser_action的HTML文件里使用的JS,不能直接以<script></script>的形式行內(nèi)寫入HTML里,需要獨(dú)立成JS文件再引入

  • 如果有其他第三方依賴,比如jQuery等文件,也無法通過CDN引入,而需要保持資源文件到項(xiàng)目目錄后再引入

不同運(yùn)行環(huán)境JS之間的交互

雖然運(yùn)行環(huán)境和繩命周期都不相同,但幸運(yùn)的是,chrome為我們提供了一些三種JS都通用的API,可以起到JS之間相互通訊的效果。

chrome.runtime

消息傳遞

普通的消息傳遞

通過runtimeonMessage、sendMessage等方法,可以在各個(gè)JS之間傳遞并監(jiān)聽消息。舉個(gè)栗子:

popup.js中,我們讓它初始化之后發(fā)送一個(gè)消息:

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

然后在background.js中,監(jiān)聽消息的接收,并進(jìn)行處理:

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

以上代碼,會(huì)在每次打開插件彈窗的時(shí)候彈出一個(gè)Alert。

chrome.runtime的常用方法:

// 獲取當(dāng)前擴(kuò)展程序中正在運(yùn)行的后臺(tái)網(wǎng)頁的 JavaScript window 對(duì)象
chrome.runtime.getBackgroundPage(function (backgroundPage) {
  // backgroundPage 即 window 對(duì)象
});
// 發(fā)送消息
chrome.runtime.sendMessage(message, function(response) {
  // response 代表消息回復(fù),可以接受到通過 sendResponse 方法發(fā)送的消息回復(fù)
});
// 監(jiān)聽消息
chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) {
  // message 就是你發(fā)送的 message
  // sender 代表發(fā)送者,可以通過 sender.tab 判斷消息是否是從內(nèi)容腳本發(fā)出
  // sendResponse 可以直接發(fā)送回復(fù),如:
  sendResponse({
    method: 'response',
    message: 'send a response'
  });
});

需要注意的是,即便你在多個(gè)JS中注冊(cè)了消息監(jiān)聽onMessage.addListener,也只有一個(gè)監(jiān)聽者能收到通過runtime.sendMessage發(fā)送出去的消息。如果需要不同的監(jiān)聽者分別監(jiān)聽消息,則需要使用chrome.tab API來指定消息接收對(duì)象

舉個(gè)栗子:

上文說過,需要在content_scripts中監(jiān)聽選擇事件,獲取選擇的文本,而對(duì)于右鍵菜單的點(diǎn)擊則是在background中監(jiān)聽的。那么需要把選擇的文本作為消息,發(fā)送給background,在background完成異步保存。

// content_scripts 中獲取選擇,并發(fā)送消息
// 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;
}
// 發(fā)送消息
function sendSelectionMessage(message) {
  chrome.runtime.sendMessage(message, function(response) {});
}
// 監(jiān)聽鼠標(biāo)松開的事件,只有在右鍵點(diǎn)擊時(shí),才會(huì)去獲取文本
window.onmouseup = function(e) {
  if (!e.button === 2) {
    return;
  }
  var message = getSelectionMessage();
  sendSelectionMessage(message);
};
// background 中接收消息,監(jiān)聽右鍵菜單的點(diǎn)擊,并異步保存數(shù)據(jù)
// js/background.js

// 創(chuàng)建一個(gè)全局對(duì)象,來保存接收到的消息值
var selectionObj = null;

// 首先要?jiǎng)?chuàng)建菜單
chrome.runtime.onInstalled.addListener(function() {
  chrome.contextMenus.create({
    type: 'normal',
    title: 'save selection',
    id: 'save_selection',
    // 有選擇才會(huì)出現(xiàn)
    contexts: ['selection']
  });
});
// 監(jiān)聽菜單的點(diǎn)擊
chrome.contextMenus.onClicked.addListener(function(menuItem) {
  if (menuItem.menuItemId === "save_selection") {
    addCliper();
  }
});

// 消息監(jiān)聽,接收從 content_scripts 傳遞來的消息,并保存在一個(gè)全局對(duì)象中
chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) {
  if (message.method === 'get_selection') {
    selectionObj = message.data;
  }
});

// 異步保存
function addCliper() {
  $.ajax({
    // ...
  });
}
長(zhǎng)鏈接

通過chrome.runtime.connect(或者chrome.tabs.connect)可以建立起不同類型JS之間的長(zhǎng)鏈接。

信息的發(fā)送者需要制定獨(dú)特的信息類型,發(fā)送并監(jiān)聽信息:

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);
  }
});

而接受者則要注冊(cè)監(jiān)聽,并判斷消息的類型:

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

要使用這個(gè)API則需要先在manifest.json中注冊(cè):

"permissions": [
  "tabs",
  // ...
]
// 獲取到當(dāng)前的Tab
chrome.tabs.getCurrent(function(tab) {
  // 通過 tab.id 可以拿到標(biāo)簽頁的ID
});

// 通過 queryInfo,以Array的形式篩選出符合條件的tabs
chrome.tabs.query(queryInfo, function(tabs) {})

// 精準(zhǔn)的給某個(gè)頁面的`content_scripts`發(fā)送消息
chrome.tabs.sendMessage(tabId, message, function(response) {});

舉個(gè)栗子:

background.js中,我們獲取到當(dāng)前Tab,并發(fā)送消息:

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中,進(jìn)行消息監(jiān)聽:

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

chrome.storage

chrome.storage是一個(gè)基于localStorage的本地儲(chǔ)存,但chrome對(duì)其進(jìn)行了IO的優(yōu)化,可以儲(chǔ)存對(duì)象形式的數(shù)據(jù),也不會(huì)因?yàn)闉g覽器完全關(guān)閉而清空。

同樣,使用這個(gè)API需要先在manifest.json中注冊(cè):

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

chrome.storage有兩種形式,chrome.storage.syncchrome.storage.local

chrome.storage.local是基于本地的儲(chǔ)存,而chrome.storage.sync會(huì)先判斷當(dāng)前用戶是否登錄了google賬戶,如果登錄,則會(huì)將儲(chǔ)存的數(shù)據(jù)通過google服務(wù)自動(dòng)同步,否則,會(huì)使用chrome.storage.local僅進(jìn)行本地儲(chǔ)存

注:因?yàn)閮?chǔ)存區(qū)沒有加密,所以不應(yīng)該儲(chǔ)存用戶的敏感信息

API:

// 數(shù)據(jù)儲(chǔ)存
StorageArea.set(object items, function callback)

// 數(shù)據(jù)獲取
StorageArea.get(string or array of string or object keys, function callback)

// 數(shù)據(jù)移除
StorageArea.remove(string or array of string keys, function callback)

// 清空全部?jī)?chǔ)存
StorageArea.clear(function callback)

// 監(jiān)聽儲(chǔ)存的變化
chrome.storage.onChanged.addListener(function(changes, namespace) {});

舉栗子:

我們?cè)?code>browser_action完成了用戶的登錄/注冊(cè)操作,將部分用戶信息儲(chǔ)存在storage中。每次初始化時(shí),都會(huì)檢查是否有儲(chǔ)存,沒有的話則需要用戶登錄,成功后再添加:

// browser_action
// js.popup.js

chrome.storage.sync.get('user', function(result) {
  // 通過 result.user 獲取到儲(chǔ)存的 user 對(duì)象
  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 請(qǐng)求異步登錄,獲取到成功的回調(diào)后,將返回的 user 對(duì)象儲(chǔ)存在 storage 中
  chrome.storage.sync.set({user: user}, function(result) {});
}

而在其他環(huán)境的JS里,我們可以監(jiān)聽storage的變化:

// background
// js/background.js

// 一個(gè)全局的 user 對(duì)象,用來保存用戶信息,以便在異步時(shí)發(fā)生 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];
    }
  }
});

大體上,我們目前為止理清了三種環(huán)境下JS的不同,以及他們交流和儲(chǔ)存的方式。除此以外,還有popup彈窗、右鍵菜單的創(chuàng)建和使用。其實(shí)使用這些知識(shí)就足夠做出一個(gè)簡(jiǎn)單的chrome擴(kuò)展了。

正式發(fā)布

其實(shí)我覺得整個(gè)過程中最蛋疼的一步就是把插件正式發(fā)布到chrome商店了。

最后終于搞定,線上可見:cliper extension

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多