avatar
fireworks99
keep hungry keep foolish

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,但是他们不能直接访问网页的内容。

theory

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);
}
Site by Baole Zhao | Powered by Hexo | theme PreciousJoy