PMFlow/frontend/.playwright-profiles/chrome-clone-1775093734046/Default/Extensions/bpojelgakakmcfmjfilgdlmhefp.../1.0.1_0/bg.js

252 lines
7.4 KiB
JavaScript

import {TEXT_CODING_MAP} from "./scr/charsets.js"
import * as mv3 from "./scr/mv3_util.js"
function allocateRuleId(oldRules){
function id_occupied(id){
for (let rule of oldRules){
if (rule.id === id){
return true
}
}
return false
}
//for historical reason, oldRule IDs can be sparsely large, so we search empty slots instead of increment on old ID
for (let i = 1; i < 32768; i++){
if (! id_occupied(i)){
//console.log(`allocate new rule id = ${i}`)
return i
}
}
return 1 //not likely to happen
}
async function getLiveCodeMap(){
let live_map = {...TEXT_CODING_MAP};
let items = await chrome.storage.sync.get({
"disabled_menu": "[]",
"usr_menu": "[]"
})
let disabled_menu = JSON.parse(items["disabled_menu"]);
let usr_menu = JSON.parse(items["usr_menu"]);
disabled_menu.forEach(function(code){
delete(live_map[code]);
})
//default menu may be overwritten by user menu if code is the same
usr_menu.forEach(function({lang, code}){
live_map[code] = [lang, code, true]; //3rd item as flag of user defined
})
return live_map
}
//extract site url pattern from page url
//this pattern serves as url filter for dynamic rules
function extract_site_url_pattern(url) {
if (typeof(url) !== "string"){
return "Newtab";
}
var fragments = url.split('/').slice(0, 3);
return fragments.join("/") + "/*";
}
function injectTxt(txt){
function _escapeHTML(html) {
return html.replaceAll('&', '&amp;')
.replaceAll('<', '&lt;')
.replaceAll('>', '&gt;')
.replaceAll('"', '&quot;')
.replaceAll("'", '&#039;')
}
let url = location.href
let is_html = !!url.match(/\.html?$/i)
let mime_marker = is_html ? "html" : "plain"
let doc_contents = is_html ? txt : `<pre>${_escapeHTML(txt)}</pre>`
let newDoc = document.open(`text/${mime_marker}`, "replace")
newDoc.write(doc_contents)
newDoc.close()
}
//load local file with specified character set
async function loadLocalFile(url, new_code, tabId){
//XMLHttpRequest() not allowed from file:// scheme
let response = null
try{
response = await fetch(url)
}
catch (e){
chrome.tabs.create({ url: chrome.i18n.getMessage("msgPage")});
return
}
const buf = await response.arrayBuffer()
let decoder = new TextDecoder(new_code);
let txt = decoder.decode(buf)
chrome.scripting.executeScript({
target: {tabId: tabId},
func: injectTxt,
args: [txt],
});
}
// The onClicked callback function.
async function onClickHandler(info, tab) {
var new_code = info.menuItemId;
if (new_code === "help"){
var locale = chrome.i18n.getMessage("_locale");
chrome.tabs.create({
url: "http://www.deep-watch.net/" + locale
+ "/blog/bring-character-encoding-menu-back-to-chrome-with-extension"}
);
}
let flg_need_reload = false;
let flg_add_rule = true
let url_pattern = extract_site_url_pattern(tab.url);
let oldRules = await chrome.declarativeNetRequest.getDynamicRules()
let id_to_remove = mv3.findRuleIdByUrl(oldRules, url_pattern)
if (new_code == "default"){
flg_need_reload = true;
flg_add_rule = false
}
else{
if (url_pattern == "file:///*"){
await loadLocalFile(tab.url, new_code, tab.id);
}
else{
flg_need_reload = true;
}
}
if (flg_need_reload){
//if (flg_add_rule && id_to_remove){
// console.log(`reuse rule id = ${id_to_remove}`)
//}
//reuse old rule ID to avoid adding duplicated rules for the same site
let new_id = id_to_remove || allocateRuleId(oldRules)
await chrome.declarativeNetRequest.updateDynamicRules({
removeRuleIds : id_to_remove ? [id_to_remove] : [],
addRules : flg_add_rule ? [mv3.composeRule(new_id, url_pattern, new_code)] : []
})
chrome.tabs.update(tab.id, {url: tab.url});
}
}
chrome.contextMenus.onClicked.addListener(onClickHandler);
//update selected coding every time new page loaded
//this action may be expensive
chrome.tabs.onUpdated.addListener(async function (tabId, changeInfo, tab) {
if (changeInfo.status === "complete") {
//update context menu
const g_site2Code = await mv3.get_site2Code()
var url_pattern = extract_site_url_pattern(tab.url);
var my_code = g_site2Code[url_pattern] || "default";
let live_code_map = await getLiveCodeMap()
//update menu status
/*
for (var menuId in live_code_map){
chrome.contextMenus.update(menuId, {
"checked" : false
})
}
*/
if (live_code_map[my_code]){
chrome.contextMenus.update(my_code, {
"checked" : true
},
()=>{
let err = chrome.runtime.lastError
if (err && err.message.indexOf("Cannot find menu item with id") >=0){
//for some reason, menu may not be present
updateContextMenu().then(()=>{
chrome.contextMenus.update(my_code, {
"checked" : true
})
})
}
})
}
else{
//console.warn(`${my_code} not found in menu`)
let oldRules = await chrome.declarativeNetRequest.getDynamicRules()
let ruleId = await mv3.findRuleIdByUrl(oldRules, url_pattern)
if (ruleId){
//console.log(`remove code ${my_code} for site ${url_pattern}`)
await chrome.declarativeNetRequest.updateDynamicRules({
removeRuleIds : [ruleId]
})
}
}
}
});
async function updateContextMenu(){
let live_code_map = await getLiveCodeMap()
await chrome.contextMenus.removeAll()
chrome.contextMenus.create({
"title": chrome.i18n.getMessage("Help_page"),
"type" : "normal",
"contexts":["page"],
"id": "help"
});
chrome.contextMenus.create({
"type" : "separator",
"contexts":["page"],
"id": "separator"
});
for (var [code, [language, ui_code, is_custom]] of Object.entries(live_code_map)) {
if (! is_custom){
language = chrome.i18n.getMessage(language)
}
var menu_title = language
if (ui_code){
menu_title += " ("+ ui_code + ")";
}
chrome.contextMenus.create({
"title": menu_title,
"type" : "radio",
"contexts":["page"],
"id": code
});
}
console.log("Context menu updated")
}
chrome.storage.onChanged.addListener((changes, namespace) => {
if (namespace === "sync"){
updateContextMenu().then(()=>{})
}
})
// Set up context menu at install time.
chrome.runtime.onInstalled.addListener(updateContextMenu)