background.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452
  1. /**
  2. * FeJson后台运行程序
  3. * @author zhaoxianlie
  4. */
  5. import MSG_TYPE from '../static/js/common.js';
  6. import Settings from '../options/settings.js';
  7. import Menu from './menu.js';
  8. import Awesome from './awesome.js';
  9. import InjectTools from './inject-tools.js';
  10. import Monkey from './monkey.js';
  11. let BgPageInstance = (function () {
  12. let FeJson = {
  13. notifyTimeoutId: -1
  14. };
  15. // 黑名单页面
  16. let blacklist = [
  17. /^https:\/\/chrome\.google\.com/
  18. ];
  19. /**
  20. * 文本格式,可以设置一个图标和标题
  21. * @param {Object} options
  22. * @config {string} type notification的类型,可选值:html、text
  23. * @config {string} icon 图标
  24. * @config {string} title 标题
  25. * @config {string} message 内容
  26. */
  27. let notifyText = function (options) {
  28. let notifyId = 'FeJson-notify-id';
  29. clearTimeout(FeJson.notifyTimeoutId);
  30. if (options.closeImmediately) {
  31. return chrome.notifications.clear(notifyId);
  32. }
  33. if (!options.icon) {
  34. options.icon = "static/img/fe-48.png";
  35. }
  36. if (!options.title) {
  37. options.title = "温馨提示";
  38. }
  39. chrome.notifications.create(notifyId, {
  40. type: 'basic',
  41. title: options.title,
  42. iconUrl: chrome.runtime.getURL(options.icon),
  43. message: options.message
  44. });
  45. FeJson.notifyTimeoutId = setTimeout(() => {
  46. chrome.notifications.clear(notifyId);
  47. }, parseInt(options.autoClose || 3000, 10));
  48. };
  49. // 往当前页面直接注入脚本,不再使用content-script的配置了
  50. let _injectContentScripts = function (tabId) {
  51. // 其他工具注入
  52. Awesome.getInstalledTools().then(tools => {
  53. // 注入样式
  54. let cssFiles = Object.keys(tools).filter(tool => tools[tool].contentScriptCss)
  55. .map(tool => `${tool}/content-script.css`);
  56. InjectTools.inject(tabId, {files: cssFiles});
  57. // 注入js
  58. let jsTools = Object.keys(tools).filter(tool => tools[tool].contentScriptJs);
  59. let jsCodes = [];
  60. jsTools.forEach((t, i) => {
  61. let func = `window['${t.replace(/-/g, '')}ContentScript']`;
  62. jsCodes.push(`(()=>{let func=${func};func&&func();})()`);
  63. });
  64. let jsFiles = jsTools.map(tool => `${tool}/content-script.js`);
  65. InjectTools.inject(tabId, {files: jsFiles,code: jsCodes.join(';')});
  66. });
  67. };
  68. /**
  69. * 动态运行工具
  70. * @param configs
  71. * @config tool 工具名称
  72. * @config withContent 默认携带的内容
  73. * @config query 请求参数
  74. * @config noPage 无页面模式
  75. * @constructor
  76. */
  77. chrome.DynamicToolRunner = async function (configs) {
  78. let tool = configs.tool || configs.page;
  79. let withContent = configs.withContent;
  80. let activeTab = null;
  81. let query = configs.query;
  82. // 如果是noPage模式,则表名只完成content-script的工作,直接发送命令即可
  83. if (configs.noPage) {
  84. let toolFunc = tool.replace(/-/g, '');
  85. chrome.tabs.query({active: true, currentWindow: true}, tabs => {
  86. let found = tabs.some(tab => {
  87. if (/^(http(s)?|file):\/\//.test(tab.url) && blacklist.every(reg => !reg.test(tab.url))) {
  88. let codes = `window['${toolFunc}NoPage'] && window['${toolFunc}NoPage'](${JSON.stringify(tab)});`;
  89. InjectTools.inject(tab.id, {code: codes});
  90. return true;
  91. }
  92. return false;
  93. });
  94. if (!found) {
  95. notifyText({
  96. message: '抱歉,此工具无法在当前页面使用!'
  97. });
  98. }
  99. });
  100. return;
  101. }
  102. chrome.tabs.query({currentWindow: true}, function (tabs) {
  103. activeTab = tabs.filter(tab => tab.active)[0];
  104. Settings.getOptions((opts) => {
  105. let isOpened = false;
  106. let tabId;
  107. // 允许在新窗口打开
  108. if (String(opts['FORBID_OPEN_IN_NEW_TAB']) === 'true') {
  109. let reg = new RegExp("^chrome.*\\/" + tool + "\\/index.html" + (query ? "\\?" + query : '') + "$", "i");
  110. for (let i = 0, len = tabs.length; i < len; i++) {
  111. if (reg.test(tabs[i].url)) {
  112. isOpened = true;
  113. tabId = tabs[i].id;
  114. break;
  115. }
  116. }
  117. }
  118. if (!isOpened) {
  119. chrome.tabs.create({
  120. url: `/${tool}/index.html` + (query ? "?" + query : ''),
  121. active: true
  122. }).then(tab => { FeJson[tab.id] = { content: withContent }; });
  123. } else {
  124. chrome.tabs.update(tabId, {highlighted: true}).then(tab => {
  125. FeJson[tab.id] = { content: withContent };
  126. chrome.tabs.reload(tabId);
  127. });
  128. }
  129. });
  130. });
  131. };
  132. /**
  133. * 动态在icon处显示提示
  134. * @param tips
  135. * @private
  136. */
  137. let _animateTips = (tips) => {
  138. setTimeout(() => {
  139. chrome.action.setBadgeText({text: tips});
  140. setTimeout(() => {
  141. chrome.action.setBadgeText({text: ''});
  142. }, 2000);
  143. }, 3000);
  144. };
  145. /**
  146. * 更新browser action的点击动作
  147. * @param action install / upgrade / offload
  148. * @param showTips 是否notify
  149. * @param menuOnly 只管理Menu
  150. * @private
  151. */
  152. let _updateBrowserAction = function (action, showTips, menuOnly) {
  153. if (!menuOnly) {
  154. if (action === 'offload') {
  155. _animateTips('-1');
  156. } else {
  157. _animateTips('+1');
  158. }
  159. } else {
  160. // 重绘菜单
  161. Menu.manage(Settings);
  162. }
  163. if (showTips) {
  164. let actionTxt = '';
  165. switch (action) {
  166. case 'install':
  167. actionTxt = '工具已「安装」成功,并已添加到弹出下拉列表,点击FeHelper图标可正常使用!';
  168. break;
  169. case 'offload':
  170. actionTxt = '工具已「卸载」成功,并已从弹出下拉列表中移除!';
  171. break;
  172. case 'menu-install':
  173. actionTxt = '已将此工具快捷方式加入到「右键菜单」中!';
  174. break;
  175. case 'menu-offload':
  176. actionTxt = '已将此工具快捷方式从「右键菜单」中移除!';
  177. break;
  178. default:
  179. actionTxt = '恭喜,操作成功!';
  180. }
  181. notifyText({
  182. message: actionTxt,
  183. autoClose: 2500
  184. });
  185. }
  186. };
  187. // 捕获当前页面可视区域
  188. let _captureVisibleTab = function (callback) {
  189. chrome.tabs.captureVisibleTab(null, {format: 'png', quality: 100}, uri => {
  190. callback && callback(uri);
  191. });
  192. };
  193. let _addScreenShotByPages = function(params,callback){
  194. chrome.tabs.captureVisibleTab(null, {format: 'png', quality: 100}, uri => {
  195. callback({ params,uri });
  196. });
  197. };
  198. let _showScreenShotResult = function(data){
  199. chrome.DynamicToolRunner({
  200. tool: 'screenshot',
  201. withContent: data
  202. });
  203. };
  204. let _colorPickerCapture = function(params) {
  205. chrome.tabs.query({active: true, currentWindow: true}, function (tabs) {
  206. chrome.tabs.captureVisibleTab(null, {format: 'png'}, function (dataUrl) {
  207. let code = `window.colorpickerNoPage(${JSON.stringify({
  208. setPickerImage: true,
  209. pickerImage: dataUrl
  210. })})`;
  211. InjectTools.inject(tabs[0].id, { code });
  212. });
  213. });
  214. };
  215. let _codeBeautify = function(params){
  216. if (['javascript', 'css'].includes(params.fileType)) {
  217. Awesome.StorageMgr.get('JS_CSS_PAGE_BEAUTIFY').then(val => {
  218. if(val !== '0') {
  219. let code = `window._codebutifydetect_('${params.fileType}')`;
  220. InjectTools.inject(params.tabId, { code });
  221. }
  222. });
  223. }
  224. };
  225. /**
  226. * 接收来自content_scripts发来的消息
  227. */
  228. let _addExtensionListener = function () {
  229. chrome.runtime.onMessage.addListener(function (request, sender, callback) {
  230. // 如果发生了错误,就啥都别干了
  231. if (chrome.runtime.lastError) {
  232. console.log('chrome.runtime.lastError:',chrome.runtime.lastError);
  233. return true;
  234. }
  235. // 动态安装工具或者卸载工具,需要更新browserAction
  236. if (request.type === MSG_TYPE.DYNAMIC_TOOL_INSTALL_OR_OFFLOAD) {
  237. _updateBrowserAction(request.action, request.showTips, request.menuOnly);
  238. callback && callback();
  239. }
  240. // 截屏
  241. else if (request.type === MSG_TYPE.CAPTURE_VISIBLE_PAGE) {
  242. _captureVisibleTab(callback);
  243. }
  244. // 打开动态工具页面
  245. else if (request.type === MSG_TYPE.OPEN_DYNAMIC_TOOL) {
  246. chrome.DynamicToolRunner(request);
  247. callback && callback();
  248. }
  249. // 打开其他页面
  250. else if (request.type === MSG_TYPE.OPEN_PAGE) {
  251. chrome.DynamicToolRunner({
  252. tool: request.page
  253. });
  254. callback && callback();
  255. }
  256. // 任何事件,都可以通过这个钩子来完成
  257. else if (request.type === MSG_TYPE.DYNAMIC_ANY_THING) {
  258. switch(request.thing){
  259. case 'save-options':
  260. //管理右键菜单
  261. Menu.manage(Settings);
  262. notifyText({
  263. message: '配置修改已生效,请继续使用!',
  264. autoClose: 2000
  265. });
  266. break;
  267. case 'request-jsonformat-options':
  268. Awesome.StorageMgr.get(request.params).then(result => {
  269. Object.keys(result).forEach(key => {
  270. if (['MAX_JSON_KEYS_NUMBER', 'JSON_FORMAT_THEME'].includes(key)) {
  271. result[key] = parseInt(result[key]);
  272. } else {
  273. result[key] = (result[key] !== 'false');
  274. }
  275. });
  276. callback && callback(result);
  277. });
  278. return true; // 这个返回true是非常重要的!!!要不然callback会拿不到结果
  279. case 'save-jsonformat-options':
  280. Awesome.StorageMgr.set(request.params).then(() => {
  281. callback && callback();
  282. });
  283. return true;
  284. case 'toggle-jsonformat-options':
  285. Awesome.StorageMgr.get('JSON_TOOL_BAR_ALWAYS_SHOW').then(result => {
  286. let show = result !== 'false';
  287. Awesome.StorageMgr.set('JSON_TOOL_BAR_ALWAYS_SHOW',!show).then(() => {
  288. callback && callback(!show);
  289. });
  290. });
  291. return true; // 这个返回true是非常重要的!!!要不然callback会拿不到结果
  292. case 'code-beautify':
  293. _codeBeautify(request.params);
  294. break;
  295. case 'close-beautify':
  296. Awesome.StorageMgr.set('JS_CSS_PAGE_BEAUTIFY',0);
  297. break;
  298. case 'qr-decode':
  299. chrome.DynamicToolRunner({
  300. withContent: request.params.uri,
  301. tool: MSG_TYPE.QR_CODE,
  302. query: `mode=decode`
  303. });
  304. break;
  305. case 'request-page-content':
  306. request.params = FeJson[request.tabId];
  307. delete FeJson[request.tabId];
  308. break;
  309. case 'set-page-timing-data':
  310. chrome.DynamicToolRunner({
  311. tool: 'page-timing',
  312. withContent: request.wpoInfo
  313. });
  314. break;
  315. case 'color-picker-capture':
  316. _colorPickerCapture(request.params);
  317. break;
  318. case 'add-screen-shot-by-pages':
  319. _addScreenShotByPages(request.params,callback);
  320. return true;
  321. case 'page-screenshot-done':
  322. _showScreenShotResult(request.params);
  323. break;
  324. case 'request-monkey-start':
  325. Monkey.start(request.params);
  326. break;
  327. }
  328. callback && callback(request.params);
  329. } else {
  330. callback && callback();
  331. }
  332. return true;
  333. });
  334. // 每开一个窗口,都向内容脚本注入一个js,绑定tabId
  335. chrome.tabs.onUpdated.addListener(function (tabId, changeInfo, tab) {
  336. if (String(changeInfo.status).toLowerCase() === "complete") {
  337. if(/^(http(s)?|file):\/\//.test(tab.url) && blacklist.every(reg => !reg.test(tab.url))){
  338. InjectTools.inject(tabId, { code: `window.__FH_TAB_ID__=${tabId};` });
  339. _injectContentScripts(tabId);
  340. }
  341. }
  342. });
  343. // 安装与更新
  344. chrome.runtime.onInstalled.addListener(({reason, previousVersion}) => {
  345. switch (reason) {
  346. case 'install':
  347. chrome.runtime.openOptionsPage();
  348. break;
  349. case 'update':
  350. _animateTips('+++1');
  351. if (previousVersion === '2019.12.2415') {
  352. notifyText({
  353. message: '历尽千辛万苦,FeHelper已升级到最新版本,可以到插件设置页去安装旧版功能了!',
  354. autoClose: 5000
  355. });
  356. }
  357. // 从V2020.02.1413版本开始,本地的数据存储大部分迁移至chrome.storage.local
  358. // 这里需要对老版本升级过来的情况进行强制数据迁移
  359. let getAbsNum = num => parseInt(num.split(/\./).map(n => n.padStart(4, '0')).join(''), 10);
  360. // let preVN = getAbsNum(previousVersion);
  361. // let minVN = getAbsNum('2020.02.1413');
  362. // if (preVN < minVN) {
  363. // Awesome.makeStorageUnlimited();
  364. // setTimeout(() => chrome.runtime.reload(), 1000 * 5);
  365. // }
  366. break;
  367. }
  368. });
  369. // 卸载
  370. chrome.runtime.setUninstallURL(chrome.runtime.getManifest().homepage_url);
  371. };
  372. /**
  373. * 检查插件更新
  374. * @private
  375. */
  376. let _checkUpdate = function () {
  377. setTimeout(() => {
  378. chrome.runtime.requestUpdateCheck((status) => {
  379. if (status === "update_available") {
  380. chrome.runtime.reload();
  381. }
  382. });
  383. }, 1000 * 30);
  384. };
  385. /**
  386. * 初始化
  387. */
  388. let _init = function () {
  389. _checkUpdate();
  390. _addExtensionListener();
  391. Menu.manage(Settings);
  392. // 定期清理冗余的垃圾
  393. setTimeout(() => {
  394. Awesome.gcLocalFiles();
  395. }, 1000 * 10);
  396. };
  397. return {
  398. pageCapture: _captureVisibleTab,
  399. init: _init
  400. };
  401. })();
  402. BgPageInstance.init();