bilibiliClickProgressBar development process
bilibiliClickProgressBar 开发过程
1.创建右键菜单
background.js中,当扩展(Extension)首次安装、升级,或chrome升级时,创建右键菜单。
chrome.runtime.onInstalled.addListener(() => {
chrome.contextMenus.create({
"id": "addTimeTag",
"title": "添加时间标签",
"contexts": ["page"],
"documentUrlPatterns": ["*://*.bilibili.com/*"]
});
});
在background.js中监听右键菜单的点击,若所点击的选项是我们所创建的那个,则通知content script完成“添加时间标签”的功能。
chrome.contextMenus.onClicked.addListener(function (data) {
if(data.menuItemId === "addTimeTag") {
//后台脚本可以访问所有WebExtension JavaScript APIS,但是他们不能直接访问网页的内容(而 content script 可以)
// console.log(window);//undefined
callContentScript({info: "background: addTimeTag"}, function (res) {
console.log(res);
let notifyOptions = {
type: "basic",
title: "B站小助手",
iconUrl: "whiteIcon.png",
message: "时间标签添加成功!",
silent: true
};
chrome.notifications.create(new Date().getTime() + "AddSucceedNotify", notifyOptions);
})
}
})
function callContentScript(msg, callback) {
chrome.tabs.query({
active: true,
currentWindow: true
}, (tabs) => {
chrome.tabs.sendMessage(tabs[0].id, msg, res => {
callback(res);
})
});
}
注意contextMenus.onClicked
的监听不要写在runtime.onInstalled
的监听里。
之所以background.js不能做而交给content script做,是因为后台脚本可以访问所有WebExtension JavaScript APIS,但是他们不能直接访问网页的内容。
2.实现添加时间标签功能
content script监听来自background的消息,若收到“添加时间标签”的消息,则收集Web Page上与时间标签相关的信息,将信息存入chrome.storage
中。
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
if(request.info === "background: addTimeTag") {
addTimeTag();
} else if(request.info === "popup: jump") {
jump(request.url, request.pNum, request.tNum);
}
sendResponse("(content script)我滴任务完成了,啊哈哈哈~");
})
function addTimeTag() {
let url = "https://www.bilibili.com" + window.location.pathname;
let pNum = getPNum();
let title = getTitle();
let tNum = getTNum();
let timeTag1 = {"url": url, "pNum": pNum, "title": title, "tNum": tNum};
chrome.storage.sync.get(['timeTagList'], function(result) {
if(JSON.stringify(result) === "{}") {
chrome.storage.sync.set({"timeTagList": [timeTag1]}, function () {
console.log("Storage finished!");
})
} else {
result.timeTagList.push(timeTag1);
chrome.storage.sync.set({"timeTagList": result.timeTagList}, function () {
console.log("Storage finished!");
})
}
});
// content scripts 得到的是一个“干净的DOM视图”, 意味着:
// 1. content scripts 不能看见页面脚本定义的javascript 变量。
// 2. 如果一个页面脚本重定义了一个DOM内置属性,content scripts将获取到这个属性的原始版本,而不是重定义版本。
// console.log(window.player);//undefined
// useWebPagesVarsFunctions();
}
function getPNum() {
let pNum = 1;
let div = document.getElementById("multi_page");
if(div != null) {
let lis = div.getElementsByClassName("on");
if(lis.length > 0) {
let ps = lis[0].getElementsByClassName("page-num");
if(ps.length > 0) {
let p = ps[0].innerHTML;
pNum = parseInt(p.substr(1));
} else {
let spans = lis[0].getElementsByTagName("span");
if(spans.length === 1) pNum = parseInt(spans[0].innerHTML);
}
}
}
return pNum;
}
function getTitle() {
let title;
let ts = document.getElementsByClassName("tit");
if(ts.length > 0) title = ts[0].innerHTML;
else {
ts = document.getElementById("player-title");
if(ts) title = ts.innerHTML;
}
return title;
}
function getTNum() {
let tNum;
let times = document.getElementsByClassName('bilibili-player-video-time-now');
if(times.length > 0) {
let tStr = times[0].innerHTML;
tNum = hms2s(tStr);
} else {
times = document.getElementsByClassName('squirtle-video-time-now');
if(times.length > 0) {
let tStr = times[0].innerHTML;
tNum = hms2s(tStr);
}
}
return tNum;
}
//function: hh:mm:ss to second
function hms2s(hms) {
let s = hms.split(":");
let ans = 0;
for(let i = s.length - 1, j = 1; i >= 0; --i, j *= 60) {
ans += parseInt(s[i]) * j;
}
return ans;
}
3.点开popup取storage数据
chrome.storage.sync.get(['timeTagList'], function(result) {
if(result !== undefined && result.timeTagList !== undefined) {
for(let i = 0; i < result.timeTagList.length; ++i) {
let li = document.createElement("li");
li.setAttribute("class", "tag");
let stdTNum = s2hms(result.timeTagList[i].tNum);
liAppendSpan(li, "url", result.timeTagList[i].url);
liAppendSpan(li, "title", result.timeTagList[i].title);
liAppendSpan(li, "pNum", result.timeTagList[i].pNum);
liAppendSpan(li, "tNum", stdTNum);
liAppendSpan(li, "close", '<img src="close.svg"/>');
ls.appendChild(li);
}
}
console.log("Caught you! timeTag.")
});
function liAppendSpan(li, name, value) {
let span = document.createElement("span");
span.setAttribute("class", name);
if(name === "url") span.style.display = "none";
span.innerHTML = value;
li.appendChild(span);
}
function callContentScript(msg, callback) {
chrome.tabs.query({
active: true,
currentWindow: true
}, (tabs) => {
chrome.tabs.sendMessage(tabs[0].id, msg, res => {
callback(res);
})
});
}
//function: second to hh:mm:ss
function s2hms(ss) {
let s = parseInt(ss);
let hour = 0;
let min = 0;
let result;
if(s >= 3600) {
hour = Math.floor(s / 3600);
min = Math.floor((s - hour * 3600) / 60);
s = (s - hour * 3600) % 60;
result = `${hour.toString().padStart(2,'0')}:${min.toString().padStart(2,'0')}:${s.toString().padStart(2,'0')}`;
}else if (s < 3600 && s >= 60) {
min = Math.floor(s / 60);
s = s % 60;
result = `${min.toString().padStart(2,'0')}:${s.toString().padStart(2,'0')}`;
}else {
result = `${min.toString().padStart(2,'0')}:${s.toString().padStart(2,'0')}`;
}
return result;
}
//function: hh:mm:ss to second
function hms2s(hms) {
let s = hms.split(":");
let ans = 0;
for(let i = s.length - 1, j = 1; i >= 0; --i, j *= 60) {
ans += parseInt(s[i]) * j;
}
return ans;
}
4.点击时间标签通知跳转
let ls = document.getElementById("list");
ls.onclick = function (e) {
e = e || window.event;
let url, pNum, tNum;
if(e.target.parentNode.className === "close") {
url = e.target.parentNode.parentNode.getElementsByClassName("url")[0].innerHTML;
pNum = e.target.parentNode.parentNode.getElementsByClassName("pNum")[0].innerHTML;
tNum = e.target.parentNode.parentNode.getElementsByClassName("tNum")[0].innerHTML;
console.log(url + " " + pNum + " " + tNum);
}
if(e.target.className === "tag") {
url = e.target.getElementsByClassName("url")[0].innerHTML;
pNum = e.target.getElementsByClassName("pNum")[0].innerHTML;
tNum = e.target.getElementsByClassName("tNum")[0].innerHTML;
}
else if(e.target.className === "pNum" ||
e.target.className === "tNum" ||
e.target.className === "title" ||
e.target.className === "close") {
url = e.target.parentNode.getElementsByClassName("url")[0].innerHTML;
pNum = e.target.parentNode.getElementsByClassName("pNum")[0].innerHTML;
tNum = e.target.parentNode.getElementsByClassName("tNum")[0].innerHTML;
}
if(e.target.parentNode.className === "close" || e.target.className === "close") {
chrome.storage.sync.get(['timeTagList'], function(result) {
if(result !== undefined && result.timeTagList !== undefined) {
for (let i = 0; i < result.timeTagList.length; i++) {
if(result.timeTagList[i].url === url &&
result.timeTagList[i].pNum.toString() === pNum &&
result.timeTagList[i].tNum.toString() === hms2s(tNum).toString()) {
result.timeTagList.splice(i, 1);
break;
}
}
chrome.storage.sync.set({"timeTagList": result.timeTagList}, function () {
console.log("Remove Storage finished!");
if(e.target.parentNode.className === "close") e.target.parentNode.parentNode.remove();
else e.target.parentNode.remove();
})
}
});
} else {
callContentScript({url, pNum, tNum: hms2s(tNum), info: "popup: jump"}, function (res) {
console.log(res);
})
}
}
5.实现跳转
function jump(url, pNum, tNum) {
let nowUrl = window.location.protocol + "//" + window.location.hostname + window.location.pathname;
let nowPNum = getPNum();
if(nowUrl === url && nowPNum.toString() === pNum.toString() && window.location.pathname.substr(1, 7) !== "bangumi") {
sessionStorage.setItem("nextTime", tNum.toString());
useWebPagesVarsFunctions();
} else {
window.location.href = url + "?p=" + pNum + "&t=" + tNum;
}
}
// content scripts 是运行在一个被称为 isolated world 的运行环境里,
// 和页面上的脚本互不干扰,因为不在一个运行环境里,所以也无法调用页面上脚本定义的方法
// 以下方法可以解决
function useWebPagesVarsFunctions() {
let s = document.createElement('script');
s.src = chrome.runtime.getURL('webPage.js');
s.onload = function() {
this.remove();
};
(document.head || document.documentElement).appendChild(s);
}