index.js 40 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087
  1. /**
  2. * FE-Helper后台运行程序
  3. * @author zhaoxianlie
  4. */
  5. var BgPageInstance = (function () {
  6. let MSG_TYPE = Tarp.require('../static/js/msg_type');
  7. let Settings = Tarp.require('../options/settings');
  8. let Network = Tarp.require('../background/network');
  9. let PageCapture = Tarp.require('../page-capture/capture-api')(MSG_TYPE);
  10. let feHelper = {
  11. codeStandardMgr: {},
  12. ajaxDebuggerMgr: {},
  13. csDetectIntervals: [],
  14. manifest: chrome.runtime.getManifest(),
  15. notifyTimeoutId: -1
  16. };
  17. let devToolsDetected = false;
  18. //侦测就绪情况
  19. let _detectReadyState = function (getType, callback) {
  20. chrome.tabs.query({active: true, currentWindow: true}, function (tabs) {
  21. let tabId = tabs[0].id;
  22. feHelper.codeStandardMgr[tabId][getType] = true;
  23. if (feHelper.codeStandardMgr[tabId].css && feHelper.codeStandardMgr[tabId].js) {
  24. feHelper.codeStandardMgr[tabId].allDone = true;
  25. }
  26. if (feHelper.codeStandardMgr[tabId].allDone && typeof callback === 'function') {
  27. callback();
  28. }
  29. });
  30. };
  31. /**
  32. * 执行前端FCPHelper检测
  33. */
  34. let _doFcpDetect = function (tab) {
  35. feHelper.codeStandardMgr[tab.id] = feHelper.codeStandardMgr[tab.id] || {};
  36. //所有元素都准备就绪
  37. if (feHelper.codeStandardMgr[tab.id].allDone) {
  38. clearInterval(feHelper.csDetectIntervals[tab.id]);
  39. chrome.tabs.sendMessage(tab.id, {
  40. type: MSG_TYPE.CODE_STANDARDS,
  41. event: MSG_TYPE.FCP_HELPER_DETECT
  42. });
  43. } else if (feHelper.csDetectIntervals[tab.id] === undefined) {
  44. chrome.tabs.sendMessage(tab.id, {
  45. type: MSG_TYPE.CODE_STANDARDS,
  46. event: MSG_TYPE.FCP_HELPER_INIT
  47. });
  48. //显示桌面提醒
  49. notifyText({
  50. message: "正在准备数据,请稍等..."
  51. });
  52. feHelper.csDetectIntervals[tab.id] = setInterval(function () {
  53. _doFcpDetect(tab);
  54. }, 200);
  55. }
  56. };
  57. /**
  58. * 文本格式,可以设置一个图标和标题
  59. * @param {Object} options
  60. * @config {string} type notification的类型,可选值:html、text
  61. * @config {string} icon 图标
  62. * @config {string} title 标题
  63. * @config {string} message 内容
  64. */
  65. let notifyText = function (options) {
  66. let notifyId = 'fehleper-notify-id';
  67. clearTimeout(feHelper.notifyTimeoutId);
  68. if (options.closeImmediately) {
  69. return chrome.notifications.clear(notifyId);
  70. }
  71. if (!options.icon) {
  72. options.icon = "static/img/fe-48.png";
  73. }
  74. if (!options.title) {
  75. options.title = "温馨提示";
  76. }
  77. chrome.notifications.create(notifyId, {
  78. type: 'basic',
  79. title: options.title,
  80. iconUrl: chrome.runtime.getURL(options.icon),
  81. message: options.message
  82. });
  83. feHelper.notifyTimeoutId = setTimeout(() => {
  84. chrome.notifications.clear(notifyId);
  85. }, parseInt(options.autoClose || 3000, 10));
  86. };
  87. /**
  88. * 查看页面wpo信息
  89. */
  90. let _showPageWpoInfo = function (wpoInfo) {
  91. chrome.tabs.query({active: true, currentWindow: true}, function (tabs) {
  92. if (!wpoInfo) {
  93. notifyText({
  94. message: "对不起,检测失败"
  95. });
  96. } else {
  97. chrome.tabs.create({
  98. url: "wpo/index.html?" + btoa(encodeURIComponent(JSON.stringify(wpoInfo))),
  99. active: true
  100. });
  101. }
  102. });
  103. };
  104. /**
  105. * 获取页面wpo信息
  106. * @return {[type]}
  107. */
  108. let _getPageWpoInfo = function () {
  109. chrome.tabs.query({active: true, currentWindow: true}, function (tabs) {
  110. let tab = tabs[0];
  111. //显示桌面提醒
  112. chrome.tabs.sendMessage(tab.id, {
  113. type: MSG_TYPE.GET_PAGE_WPO_INFO
  114. });
  115. });
  116. };
  117. /**
  118. * 创建或更新成功执行的动作
  119. * @param evt
  120. * @param content
  121. * @private
  122. */
  123. let _tabUpdatedCallback = function (evt, content) {
  124. return function (newTab) {
  125. if (content) {
  126. setTimeout(function () {
  127. chrome.tabs.sendMessage(newTab.id, {
  128. type: MSG_TYPE.TAB_CREATED_OR_UPDATED,
  129. content: content,
  130. event: evt
  131. });
  132. }, 300)
  133. }
  134. };
  135. };
  136. /**
  137. * 打开对应文件,运行该Helper
  138. * @param tab
  139. * @param file
  140. * @param txt
  141. * @private
  142. */
  143. let _openFileAndRun = function (tab, file, txt) {
  144. chrome.tabs.query({windowId: chrome.windows.WINDOW_ID_CURRENT}, function (tabs) {
  145. Settings.getOptsFromBgPage((opts) => {
  146. let isOpened = false;
  147. let tabId;
  148. // 允许在新窗口打开
  149. if (opts['FORBID_OPEN_IN_NEW_TAB']) {
  150. let reg = new RegExp("^chrome.*/" + file + "/index.html$", "i");
  151. for (let i = 0, len = tabs.length; i < len; i++) {
  152. if (reg.test(tabs[i].url)) {
  153. isOpened = true;
  154. tabId = tabs[i].id;
  155. break;
  156. }
  157. }
  158. }
  159. if (!isOpened) {
  160. chrome.tabs.create({
  161. url: '' + file + '/index.html',
  162. active: true
  163. }, _tabUpdatedCallback(file, txt));
  164. } else {
  165. chrome.tabs.update(tabId, {highlighted: true}, _tabUpdatedCallback(file, txt));
  166. }
  167. });
  168. });
  169. };
  170. /**
  171. * ajax debugger 开关切换
  172. * @private
  173. */
  174. let _debuggerSwitchOn = function (callback) {
  175. chrome.tabs.query({active: true, currentWindow: true}, function (tabs) {
  176. let tab = tabs[0];
  177. feHelper.ajaxDebuggerMgr[tab.id] = !feHelper.ajaxDebuggerMgr[tab.id];
  178. chrome.tabs.executeScript(tab.id, {
  179. code: 'console.info("FeHelper提醒:Ajax Debugger开关已' + (feHelper.ajaxDebuggerMgr[tab.id] ? '开启' : '关闭') + '!");',
  180. allFrames: false
  181. });
  182. callback && callback();
  183. });
  184. };
  185. /**
  186. * 告诉DevTools页面,当前的debug开关是否打开
  187. * @param callback
  188. * @param withAlert
  189. * @private
  190. */
  191. let _tellDevToolsDbgSwitchOn = function (callback, withAlert) {
  192. chrome.tabs.query({active: true, currentWindow: true}, function (tabs) {
  193. let tab = tabs[0];
  194. if (tab) {
  195. callback && callback(feHelper.ajaxDebuggerMgr[tab.id]);
  196. if (withAlert) {
  197. let msg = '';
  198. if (feHelper.ajaxDebuggerMgr[tab.id]) {
  199. if (devToolsDetected) {
  200. msg = 'DevTools已打开,确保已切换到【Console】界面,并关注信息输出,愉快的进行Ajax Debugger!'
  201. } else {
  202. msg = '请打开DevTools,并切换到【Console】界面,关注信息输出,愉快的进行Ajax Debugger!';
  203. }
  204. } else {
  205. msg = '已停止当前页面的Ajax Debugger功能!';
  206. }
  207. alert(msg);
  208. }
  209. }
  210. });
  211. };
  212. /**
  213. * 屏幕栅格标尺
  214. */
  215. let _doGridDetect = function (tab) {
  216. chrome.tabs.sendMessage(tab.id, {
  217. type: MSG_TYPE.GRID_RULER
  218. });
  219. };
  220. /**
  221. * 根据给定参数,运行对应的Helper
  222. */
  223. let _runHelper = function (config, callback) {
  224. chrome.tabs.query({active: true, currentWindow: true}, function (tabs) {
  225. let tab = tabs[0];
  226. // 如果是采用独立文件方式访问,直接打开该页面即可
  227. if (config.useFile === 1) {
  228. let content = config.msgType === MSG_TYPE.QR_CODE ? tab.url : '';
  229. _openFileAndRun(tab, config.msgType, content);
  230. } else {
  231. switch (config.msgType) {
  232. //编码规范检测
  233. case MSG_TYPE.FCP_HELPER_DETECT:
  234. _doFcpDetect(tab);
  235. break;
  236. //将当前网页转为图片
  237. case MSG_TYPE.PAGE_CAPTURE:
  238. PageCapture.full(tab);
  239. break;
  240. //查看网页加载时间
  241. case MSG_TYPE.SHOW_PAGE_LOAD_TIME:
  242. _getPageWpoInfo();
  243. break;
  244. //Ajax调试
  245. case MSG_TYPE.AJAX_DEBUGGER:
  246. _debuggerSwitchOn(callback);
  247. break;
  248. // 屏幕栅格标尺
  249. case MSG_TYPE.GRID_RULER:
  250. _doGridDetect(tab);
  251. break;
  252. default :
  253. break;
  254. }
  255. }
  256. });
  257. };
  258. /**
  259. * 检测Google chrome服务能不能访问,在2s内检测心跳
  260. * @param success
  261. * @param failure
  262. */
  263. let detectGoogleDotCom = function (success, failure) {
  264. Promise.race([
  265. fetch('https://clients2.google.com/service/update2/crx'),
  266. new Promise(function (resolve, reject) {
  267. setTimeout(() => reject(new Error('request timeout')), 2000)
  268. })])
  269. .then((data) => {
  270. success && success();
  271. }).catch(() => {
  272. failure && failure();
  273. });
  274. };
  275. /**
  276. * 从google官方渠道下载chrome扩展
  277. * @param crxId 需要下载的extension id
  278. * @param crxName 扩展名称
  279. * @param callback 下载动作结束后的回调
  280. */
  281. let downloadCrxFileByCrxId = function (crxId, crxName, callback) {
  282. detectGoogleDotCom(() => {
  283. // google可以正常访问,则正常下载
  284. let url = "https://clients2.google.com/service/update2/crx?response=redirect&acceptformat=crx2,crx3&x=id%3D"
  285. + crxId + "%26uc&prodversion=" + navigator.userAgent.split("Chrome/")[1].split(" ")[0];
  286. if (!chrome.downloads) {
  287. let a = document.createElement('a');
  288. a.href = url;
  289. a.download = crxName || (crxId + '.crx');
  290. (document.body || document.documentElement).appendChild(a);
  291. a.click();
  292. a.remove();
  293. } else {
  294. chrome.downloads.download({
  295. url: url,
  296. filename: crxName || crxId,
  297. conflictAction: 'overwrite',
  298. saveAs: true
  299. }, function (downloadId) {
  300. if (chrome.runtime.lastError) {
  301. alert('抱歉,下载失败!错误信息:' + chrome.runtime.lastError.message);
  302. }
  303. });
  304. }
  305. }, () => {
  306. // google不能正常访问
  307. callback ? callback() : alert('抱歉,下载失败!');
  308. });
  309. };
  310. /**
  311. * 从chrome webstore下载crx文件
  312. * 在chrome extension详情页使用
  313. */
  314. let downloadCrxFileFromWebStoreDetailPage = function (callback) {
  315. chrome.tabs.query({active: true, currentWindow: true}, function (tabs) {
  316. let tab = tabs[0];
  317. let crxId = tab.url.split("/")[6].split('?')[0];
  318. let crxName = tab.title.split(" - Chrome")[0] + ".crx";
  319. crxName = crxName.replace(/[&\/\\:"*<>|?]/g, '');
  320. downloadCrxFileByCrxId(crxId, crxName, callback);
  321. });
  322. };
  323. /**
  324. * 通过右键菜单下载或者分享crx
  325. * @param tab
  326. * @private
  327. */
  328. let _downloadCrx = function (tab) {
  329. let isWebStoreDetailPage = tab.url.indexOf('https://chrome.google.com/webstore/detail/') === 0;
  330. if (isWebStoreDetailPage) {
  331. // 如果是某个chrome extension的详情页面了,直接下载当前crx文件
  332. downloadCrxFileFromWebStoreDetailPage(() => {
  333. alert('下载失败,可能是当前网络无法访问Google站点!');
  334. });
  335. } else {
  336. // 否则,下载FeHelper并分享出去:ID:pkgccpejnmalmdinmhkkfafefagiiiad
  337. // feHelper.manifest.name
  338. if (confirm('下载最新版【FeHelper】并分享给其他小伙伴儿,走你~~~')) {
  339. let crxId = MSG_TYPE.STABLE_EXTENSION_ID;
  340. let crxName = feHelper.manifest.name + '- latestVersion.crx';
  341. downloadCrxFileByCrxId(crxId, crxName, () => {
  342. chrome.tabs.create({
  343. url: MSG_TYPE.DOWNLOAD_FROM_GITHUB
  344. });
  345. });
  346. }
  347. }
  348. };
  349. /**
  350. * 右键菜单创建工具
  351. * @param menuList
  352. * @private
  353. */
  354. let _contextMenuCreator = function (menuList) {
  355. let menus = Settings.getMenuOpts();
  356. menuList.forEach(m => {
  357. if (m === 'MENU_PAGE_ENCODING') {
  358. // 网页编码设置的menu
  359. PageEncoding.createMenu(feHelper.contextMenuId, menus.MENU_PAGE_ENCODING);
  360. } else {
  361. let onClick = {
  362. MENU_QRCODE_CREATE: function (info, tab) {
  363. chrome.tabs.executeScript(tab.id, {
  364. code: '(' + (function (pInfo) {
  365. let linkUrl = pInfo.linkUrl;
  366. let pageUrl = pInfo.pageUrl;
  367. let imgUrl = pInfo.srcUrl;
  368. let selection = pInfo.selectionText;
  369. return linkUrl || imgUrl || selection || pageUrl;
  370. }).toString() + ')(' + JSON.stringify(info) + ')',
  371. allFrames: false
  372. }, function (txt) {
  373. _openFileAndRun(tab, MSG_TYPE.QR_CODE, (typeof txt === 'object') ? txt[0] : txt);
  374. });
  375. },
  376. MENU_QRCODE_DECODE: function (info, tab) {
  377. _qrDecode(info, tab);
  378. },
  379. MENU_PAGE_CAPTURE: function (info, tab) {
  380. PageCapture.full(tab);
  381. },
  382. MENU_COLOR_PICKER: function (info, tab) {
  383. _showColorPicker();
  384. },
  385. MENU_STR_ENDECODE: function (info, tab) {
  386. chrome.tabs.executeScript(tab.id, {
  387. code: '(' + (function (pInfo) {
  388. return pInfo.selectionText;
  389. }).toString() + ')(' + JSON.stringify(info) + ')',
  390. allFrames: false
  391. }, function (txt) {
  392. _openFileAndRun(tab, MSG_TYPE.EN_DECODE, (typeof txt === 'object') ? txt[0] : txt);
  393. });
  394. },
  395. MENU_JSON_FORMAT: function (info, tab) {
  396. chrome.tabs.executeScript(tab.id, {
  397. code: '(' + (function (pInfo) {
  398. return pInfo.selectionText;
  399. }).toString() + ')(' + JSON.stringify(info) + ')',
  400. allFrames: false
  401. }, function (txt) {
  402. _openFileAndRun(tab, MSG_TYPE.JSON_FORMAT, (typeof txt === 'object') ? txt[0] : txt);
  403. });
  404. },
  405. MENU_CODE_FORMAT: function (info, tab) {
  406. chrome.tabs.executeScript(tab.id, {
  407. code: '(' + (function (pInfo) {
  408. return pInfo.selectionText;
  409. }).toString() + ')(' + JSON.stringify(info) + ')',
  410. allFrames: false
  411. }, function (txt) {
  412. _openFileAndRun(tab, MSG_TYPE.CODE_BEAUTIFY, (typeof txt === 'object') ? txt[0] : txt);
  413. });
  414. },
  415. MENU_AJAX_DEBUGGER: function (info, tab) {
  416. _debuggerSwitchOn(() => {
  417. _tellDevToolsDbgSwitchOn(null, true);
  418. });
  419. },
  420. MENU_CODE_STANDARD: function (info, tab) {
  421. _doFcpDetect(tab);
  422. },
  423. MENU_PAGE_OPTIMI: function (info, tab) {
  424. _getPageWpoInfo();
  425. },
  426. MENU_IMAGE_BASE64: function (info, tab) {
  427. _openFileAndRun(tab, MSG_TYPE.IMAGE_BASE64, info.srcUrl);
  428. },
  429. MENU_JSON_COMPARE: function (info, tab) {
  430. _openFileAndRun(tab, MSG_TYPE.JSON_COMPARE);
  431. },
  432. MENU_CODE_COMPRESS: function (info, tab) {
  433. _openFileAndRun(tab, MSG_TYPE.CODE_COMPRESS);
  434. },
  435. MENU_TIME_STAMP: function (info, tab) {
  436. _openFileAndRun(tab, MSG_TYPE.TIME_STAMP);
  437. },
  438. MENU_RANDOM_PASS: function (info, tab) {
  439. _openFileAndRun(tab, MSG_TYPE.RANDOM_PASSWORD);
  440. },
  441. MENU_JS_REGEXP: function (info, tab) {
  442. _openFileAndRun(tab, MSG_TYPE.REGEXP_TOOL);
  443. },
  444. MENU_MARKDOWN_TL: function (info, tab) {
  445. _openFileAndRun(tab, MSG_TYPE.HTML_TO_MARKDOWN);
  446. },
  447. MENU_STICKY_NOTE: function (info, tab) {
  448. _openFileAndRun(tab, MSG_TYPE.STICKY_NOTES);
  449. },
  450. MENU_REMOVE_BG: function (info, tab) {
  451. _openFileAndRun(tab, MSG_TYPE.REMOVE_BG);
  452. },
  453. MENU_MULTI_TOOLKIT: function (info, tab) {
  454. _openFileAndRun(tab, MSG_TYPE.MULTI_TOOLKIT);
  455. },
  456. MENU_PAGE_MODIFIER: function (info, tab) {
  457. _openFileAndRun(tab, MSG_TYPE.PAGE_MODIFIER);
  458. },
  459. MENU_POST_MAN: function (info, tab) {
  460. _openFileAndRun(tab, MSG_TYPE.POST_MAN);
  461. },
  462. MENU_GRID_RULER: function (info, tab) {
  463. _doGridDetect(tab);
  464. },
  465. MENU_DOWNLOAD_CRX: function (info, tab) {
  466. _downloadCrx(tab);
  467. }
  468. };
  469. chrome.contextMenus.create({
  470. title: menus[m].icon + ' ' + menus[m].text,
  471. contexts: menus[m].contexts || ['all'],
  472. parentId: feHelper.contextMenuId,
  473. onclick: onClick[m]
  474. });
  475. }
  476. });
  477. };
  478. /**
  479. * 创建扩展专属的右键菜单
  480. */
  481. let _createContextMenu = function () {
  482. _removeContextMenu();
  483. feHelper.contextMenuId = chrome.contextMenus.create({
  484. title: "FeHelper工具",
  485. contexts: ['page', 'selection', 'editable', 'link', 'image'],
  486. documentUrlPatterns: ['http://*/*', 'https://*/*', 'file://*/*']
  487. });
  488. if (!Settings.didMenuSettingSaved()) {
  489. _contextMenuCreator(Settings.getDefaultContextMenus());
  490. } else {
  491. Settings.getOptsFromBgPage((opts) => {
  492. _contextMenuCreator(Object.keys(opts).filter(m => /^MENU_/.test(m)));
  493. });
  494. }
  495. };
  496. /**
  497. * 移除扩展专属的右键菜单
  498. */
  499. let _removeContextMenu = function () {
  500. if (!feHelper.contextMenuId) return;
  501. chrome.contextMenus.remove(feHelper.contextMenuId);
  502. feHelper.contextMenuId = null;
  503. };
  504. /**
  505. * 创建或移除扩展专属的右键菜单
  506. */
  507. let _createOrRemoveContextMenu = function () {
  508. Settings.getOptsFromBgPage((opts) => {
  509. if (opts['opt_item_contextMenus']) {
  510. _createContextMenu();
  511. } else {
  512. _removeContextMenu();
  513. }
  514. });
  515. };
  516. /**
  517. * 二维码转码
  518. * @param info
  519. * @param tab
  520. * @private
  521. */
  522. let _qrDecode = function (info, tab) {
  523. let qrcode = Tarp.require('../static/vendor/zxing/zxing.min.js');
  524. qrcode.callback = function (text) {
  525. if ((text || '').indexOf('error decoding QR Code') !== -1) {
  526. let image = new Image();
  527. image.src = info.srcUrl;
  528. image.onload = function () {
  529. let width = this.naturalWidth;
  530. let height = this.naturalHeight;
  531. // url方式解码失败,再转换成data uri后继续解码
  532. (function createCanvasContext(img, t, l, w, h) {
  533. let canvas = document.createElement('canvas');
  534. canvas.setAttribute('id', 'qr-canvas');
  535. canvas.height = h + 100;
  536. canvas.width = w + 100;
  537. let context = canvas.getContext('2d');
  538. context.fillStyle = 'rgb(255,255,255)';
  539. context.fillRect(0, 0, canvas.width, canvas.height);
  540. context.drawImage(img, l, t, w, h, 50, 50, w, h);
  541. qrcode.callback = function (txt) {
  542. chrome.tabs.sendMessage(tab.id, {
  543. type: MSG_TYPE.QR_DECODE,
  544. result: txt
  545. });
  546. };
  547. qrcode.decode(canvas.toDataURL());
  548. })(image, 0, 0, width, height);
  549. }
  550. } else {
  551. chrome.tabs.sendMessage(tab.id, {
  552. type: MSG_TYPE.QR_DECODE,
  553. result: text
  554. });
  555. }
  556. };
  557. qrcode.decode(info.srcUrl);
  558. };
  559. /**
  560. * 显示color picker
  561. * @private
  562. */
  563. let _showColorPicker = function () {
  564. chrome.tabs.query({active: true, currentWindow: true}, function (tabs) {
  565. let tab = tabs[0];
  566. let tabid = tab.id;
  567. chrome.tabs.sendMessage(tabid, {
  568. type: MSG_TYPE.SHOW_COLOR_PICKER,
  569. enableColorPicker: true
  570. }, function (response) {
  571. chrome.tabs.sendMessage(tabid, {
  572. type: MSG_TYPE.SHOW_COLOR_PICKER,
  573. doPick: true
  574. }, function (r) {
  575. });
  576. });
  577. });
  578. };
  579. /**
  580. * 将网页截成一张图,实现取色
  581. * @param callback
  582. * @private
  583. */
  584. let _drawColorPicker = function (callback) {
  585. chrome.tabs.query({active: true, currentWindow: true}, function (tabs) {
  586. let tab = tabs[0];
  587. let tabid = tab.id;
  588. chrome.tabs.captureVisibleTab(null, {format: 'png'}, function (dataUrl) {
  589. chrome.tabs.sendMessage(tabid, {
  590. type: MSG_TYPE.SHOW_COLOR_PICKER,
  591. setPickerImage: true,
  592. pickerImage: dataUrl
  593. }, function (response) {
  594. callback && callback();
  595. });
  596. });
  597. });
  598. };
  599. /**
  600. * 在当前页面的控制台输出console
  601. * @param request
  602. * @private
  603. */
  604. let _ajaxDebugger = function (request) {
  605. chrome.tabs.query({active: true, currentWindow: true}, function (tabs) {
  606. let tab = tabs[0];
  607. chrome.tabs.executeScript(tab.id, {
  608. code: "(" + (function (jsonStr) {
  609. let args = JSON.parse(unescape(jsonStr));
  610. console[args[0]].apply(console, Array.prototype.slice.call(args, 1));
  611. }).toString() + ")('" + request.content + "');"
  612. });
  613. });
  614. };
  615. //判断是否可以针对json页面进行自动格式化
  616. let _jsonAutoFormatRequest = function () {
  617. Settings.getOptsFromBgPage(opts => {
  618. opts.JSON_PAGE_FORMAT && chrome.tabs.query({active: true, currentWindow: true}, function (tabs) {
  619. chrome.tabs.sendMessage(tabs[0].id, {
  620. type: MSG_TYPE.JSON_PAGE_FORMAT,
  621. options: {
  622. MAX_JSON_KEYS_NUMBER: opts.MAX_JSON_KEYS_NUMBER,
  623. AUTO_TEXT_DECODE: opts.AUTO_TEXT_DECODE === 'true'
  624. }
  625. });
  626. });
  627. });
  628. };
  629. //判断是否可以针对js、css自动检测格式化
  630. let _jsCssAutoDetectRequest = function () {
  631. chrome.tabs.query({active: true, currentWindow: true}, function (tabs) {
  632. let tab = tabs[0];
  633. chrome.tabs.executeScript(tab.id, {
  634. code: '(' + (() => {
  635. let ext = location.pathname.substring(location.pathname.lastIndexOf(".") + 1).toLowerCase();
  636. let fileType = ({'js': 'javascript', 'css': 'css'})[ext];
  637. let contentType = document.contentType.toLowerCase();
  638. if (!fileType) {
  639. if (/\/javascript$/.test(contentType)) {
  640. fileType = 'javascript';
  641. } else if (/\/css$/.test(contentType)) {
  642. fileType = 'css';
  643. }
  644. } else if (contentType === 'text/html') {
  645. fileType = undefined;
  646. }
  647. return fileType;
  648. }).toString() + ')()'
  649. }, function (fileType) {
  650. if (fileType && fileType.length && (fileType[0] === 'javascript' || fileType[0] === 'css')) {
  651. Settings.getOptsFromBgPage(opts => {
  652. opts.JS_CSS_PAGE_BEAUTIFY && chrome.tabs.sendMessage(tab.id, {
  653. type: MSG_TYPE.JS_CSS_PAGE_BEAUTIFY,
  654. content: fileType[0]
  655. });
  656. });
  657. }
  658. });
  659. });
  660. };
  661. /**
  662. * 存储 网页涂鸦精灵 的配置
  663. * @param params
  664. * @param callback
  665. * @private
  666. */
  667. let _savePageMonkeyConfigs = function (params, callback) {
  668. !RegExp.prototype.toJSON && Object.defineProperty(RegExp.prototype, "toJSON", {
  669. value: RegExp.prototype.toString
  670. });
  671. localStorage.setItem(MSG_TYPE.PAGE_MODIFIER_KEY, JSON.stringify(params));
  672. callback && callback();
  673. };
  674. /**
  675. * 获取 网页涂鸦精灵 的配置,如果指定了url参数,则表示只获取对应的一条配置,否则获取全部
  676. * @param params
  677. * @param callback
  678. * @returns {*}
  679. * @private
  680. */
  681. let _getPageMonkeyConfigs = function (params, callback) {
  682. let cacheMonkeys = JSON.parse(localStorage.getItem(MSG_TYPE.PAGE_MODIFIER_KEY) || '[]');
  683. if (params && params.url) {
  684. let result = null;
  685. cacheMonkeys.some(cm => {
  686. let m = cm.mPattern.match(/\/(.*)\/(.*)?/);
  687. if ((new RegExp(m[1], m[2] || "")).test(params.url)) {
  688. result = cm;
  689. return true;
  690. }
  691. return false;
  692. });
  693. callback && callback(result);
  694. } else {
  695. callback && callback(cacheMonkeys);
  696. }
  697. };
  698. /**
  699. * 接收来自content_scripts发来的消息
  700. */
  701. let _addExtensionListener = function () {
  702. chrome.runtime.onMessage.addListener(function (request, sender, callback) {
  703. //提取配置项
  704. if (request.type === MSG_TYPE.GET_OPTIONS) {
  705. Settings.getOptsFromBgPage(callback);
  706. }
  707. //保存配置项
  708. else if (request.type === MSG_TYPE.SET_OPTIONS) {
  709. Settings.setOptsFromBgPage(request.items);
  710. //管理右键菜单
  711. _createOrRemoveContextMenu();
  712. notifyText({
  713. message: '配置已生效,请继续使用!',
  714. autoClose: 2000
  715. });
  716. }
  717. // 判断菜单是否保存过
  718. else if (request.type === MSG_TYPE.MENU_SAVED) {
  719. Settings.didMenuSettingSaved(callback);
  720. }
  721. //判断是否可以针对json页面进行自动格式化
  722. else if (request.type === MSG_TYPE.JSON_PAGE_FORMAT_REQUEST) {
  723. _jsonAutoFormatRequest();
  724. }
  725. //判断是否可以针对js、css自动检测格式化
  726. else if (request.type === MSG_TYPE.JS_CSS_PAGE_BEAUTIFY_REQUEST) {
  727. _jsCssAutoDetectRequest();
  728. }
  729. //保存当前网页加载时间
  730. else if (request.type === MSG_TYPE.CALC_PAGE_LOAD_TIME) {
  731. _showPageWpoInfo(request.wpo);
  732. }
  733. // color picker
  734. else if (request.type === MSG_TYPE.COLOR_PICKER) {
  735. _drawColorPicker(callback);
  736. }
  737. // console switch
  738. else if (request.type === MSG_TYPE.AJAX_DEBUGGER_SWITCH) {
  739. _tellDevToolsDbgSwitchOn(callback);
  740. }
  741. // console show
  742. else if (request.type === MSG_TYPE.AJAX_DEBUGGER_CONSOLE) {
  743. _ajaxDebugger(request);
  744. }
  745. // 打开设置页
  746. else if (request.type === MSG_TYPE.OPEN_OPTIONS_PAGE) {
  747. chrome.runtime.openOptionsPage();
  748. }
  749. // 开启remove-bg功能
  750. else if (request.type === MSG_TYPE.REMOVE_PERSON_IMG_BG) {
  751. Tarp.require('../remove-bg/proxy').addBackgroundRemoveListener(callback);
  752. }
  753. // 网页涂鸦精灵:获取配置
  754. else if (request.type === MSG_TYPE.GET_PAGE_MODIFIER_CONFIG) {
  755. _getPageMonkeyConfigs(request.params, callback);
  756. }
  757. // 网页涂鸦精灵:保存配置
  758. else if (request.type === MSG_TYPE.SAVE_PAGE_MODIFIER_CONFIG) {
  759. _savePageMonkeyConfigs(request.params, callback);
  760. }
  761. // ===========================以下为编码规范检测====start==================================
  762. //处理CSS的请求
  763. else if (request.type === MSG_TYPE.GET_CSS) {
  764. //直接AJAX获取CSS文件内容
  765. Network.readFileContent(request.link, callback);
  766. }
  767. //处理JS的请求
  768. else if (request.type === MSG_TYPE.GET_JS) {
  769. //直接AJAX获取JS文件内容
  770. Network.readFileContent(request.link, callback);
  771. }
  772. //处理HTML的请求
  773. else if (request.type === MSG_TYPE.GET_HTML) {
  774. //直接AJAX获取JS文件内容
  775. Network.readFileContent(request.link, callback);
  776. }
  777. //处理cookie
  778. else if (request.type === MSG_TYPE.GET_COOKIE) {
  779. Network.getCookies(request, callback);
  780. }
  781. //移除cookie
  782. else if (request.type === MSG_TYPE.REMOVE_COOKIE) {
  783. Network.removeCookie(request, callback);
  784. }
  785. //设置cookie
  786. else if (request.type === MSG_TYPE.SET_COOKIE) {
  787. Network.setCookie(request, callback);
  788. }
  789. //CSS准备就绪
  790. else if (request.type === MSG_TYPE.CSS_READY) {
  791. _detectReadyState('css', callback);
  792. }
  793. //JS准备就绪
  794. else if (request.type === MSG_TYPE.JS_READY) {
  795. _detectReadyState('js', callback);
  796. }
  797. //HTML准备就绪
  798. else if (request.type === MSG_TYPE.HTML_READY) {
  799. _detectReadyState('html', callback);
  800. }
  801. // ===========================以上为编码规范检测====end==================================
  802. return true;
  803. });
  804. // 检测DevTools是否打开
  805. let openCount = 0;
  806. chrome.runtime.onConnect.addListener(function (port) {
  807. if (port.name === MSG_TYPE.DEV_TOOLS) {
  808. if (openCount === 0) {
  809. devToolsDetected = true;
  810. }
  811. openCount++;
  812. port.onDisconnect.addListener(function (port) {
  813. openCount--;
  814. if (openCount === 0) {
  815. devToolsDetected = false;
  816. }
  817. });
  818. }
  819. });
  820. // 安装与更新
  821. chrome.runtime.onInstalled.addListener(({reason, previousVersion}) => {
  822. switch (reason) {
  823. case 'install':
  824. chrome.runtime.openOptionsPage();
  825. break;
  826. case 'update':
  827. setTimeout(() => {
  828. chrome.browserAction.setBadgeText({text: '+++1'});
  829. setTimeout(() => {
  830. chrome.browserAction.setBadgeText({text: ''});
  831. }, 1500);
  832. }, 1500);
  833. break;
  834. }
  835. });
  836. // 卸载
  837. chrome.runtime.setUninstallURL(feHelper.manifest.homepage_url);
  838. };
  839. /**
  840. * 检查插件更新
  841. * @private
  842. */
  843. let _checkUpdate = function () {
  844. setTimeout(() => {
  845. chrome.runtime.requestUpdateCheck((status) => {
  846. if (status === "update_available") {
  847. chrome.runtime.reload();
  848. }
  849. });
  850. }, 1000 * 10);
  851. };
  852. /**
  853. * 将本地存储在localStorage的数据,同步到Google,确保每次更换浏览器,配置也能随之同步
  854. * @private
  855. */
  856. let _localDataSyncByGoogle = function () {
  857. let allNoteKeys = [];
  858. // 数据下载
  859. let funcDownload = function () {
  860. // 从服务端同步数据
  861. chrome.storage.sync.get({
  862. fhConfigs: null,
  863. stickyNotes: null,
  864. pageMonkey: null,
  865. otherData: null
  866. }, result => {
  867. // 先把服务端存储的所有笔记key存储起来,上报的时候要用
  868. allNoteKeys = result.stickyNotes || [];
  869. let localDataSize = localStorage.length;
  870. // 做数据对比、数据更新、数据上报
  871. ['fhConfigs', 'stickyNotes', 'pageMonkey', 'otherData'].forEach(key => {
  872. if (result[key] !== null && typeof result[key] === 'object') {
  873. // 以下情况都强制从服务端强制同步配置下来:
  874. // 1、如果本地缓存数量少于10
  875. // 2、本地数据缓存最后一次上报时间,比服务端已保存的时间早10天
  876. if (localDataSize < 10) {
  877. if (key === 'stickyNotes') {
  878. result[key].forEach(k => {
  879. chrome.storage.sync.get(JSON.parse(`{"stickynote${k}":null}`), r => {
  880. if (r !== null) {
  881. localStorage.setItem('stickynote' + k, r['stickynote' + k]);
  882. }
  883. });
  884. });
  885. } else {
  886. Object.keys(result[key]).forEach(item => {
  887. localStorage.setItem(item, result[key][item]);
  888. });
  889. }
  890. console.log(key, ' 相关数据均已从服务器同步至本地!');
  891. }
  892. }
  893. });
  894. });
  895. };
  896. // 数据上报
  897. let funcUpload = function () {
  898. // 获取本地缓存的数据
  899. let theLocalData = {
  900. fhConfigs: {},
  901. stickyNotes: {},
  902. pageMonkey: {},
  903. otherData: {}
  904. };
  905. let theKey, theData;
  906. for (let i = 0; i < localStorage.length; i++) {
  907. theKey = localStorage.key(i);
  908. theData = localStorage.getItem(localStorage.key(i));
  909. if (/^stickynote/.test(theKey)) { // 便签笔记
  910. theLocalData.stickyNotes[theKey] = theData;
  911. } else if (!/[^A-Z_]/.test(theKey)) { // FeHelper配置项
  912. theLocalData.fhConfigs[theKey] = theData;
  913. } else if (theKey === 'PAGE-MODIFIER-LOCAL-STORAGE-KEY') { // 网页油猴
  914. theLocalData.pageMonkey[theKey] = theData;
  915. } else if (!/FE_ENCODING_PREFIX_/.test(theKey)) { // 其他缓存数据
  916. theLocalData.otherData[theKey] = theData;
  917. }
  918. }
  919. // 做数据对比、数据更新、数据上报
  920. ['fhConfigs', 'stickyNotes', 'pageMonkey', 'otherData'].forEach(key => {
  921. if (Object.keys(theLocalData[key]).length) {
  922. // 上报数据
  923. let uploadData = {};
  924. if (key === 'stickyNotes') {
  925. // 服务器端的数据先做清空处理
  926. if (allNoteKeys && allNoteKeys.length) {
  927. chrome.storage.sync.remove(allNoteKeys);
  928. }
  929. uploadData[key] = Object.keys(theLocalData[key]).map(k => k.replace(/^stickynote/, ''));
  930. if (JSON.stringify(uploadData).length <= chrome.storage.sync.QUOTA_BYTES_PER_ITEM) {
  931. chrome.storage.sync.set(uploadData, () => {
  932. Object.keys(theLocalData.stickyNotes).forEach(k => {
  933. let tmp = {};
  934. tmp[k] = theLocalData.stickyNotes[k];
  935. if (JSON.stringify(tmp).length <= chrome.storage.sync.QUOTA_BYTES_PER_ITEM) {
  936. chrome.storage.sync.set(tmp);
  937. } else {
  938. console.log('便签笔记 ', k, ' 数据量太大,无法同步到服务器!');
  939. }
  940. });
  941. console.log(key, ' 数据同步到服务器成功!')
  942. });
  943. } else {
  944. console.log(key, ' 数据量太大,无法同步到服务器!');
  945. }
  946. } else {
  947. uploadData[key] = theLocalData[key];
  948. if (JSON.stringify(uploadData).length <= chrome.storage.sync.QUOTA_BYTES_PER_ITEM) {
  949. chrome.storage.sync.set(uploadData, () => console.log(key, ' 数据同步到服务器成功!'));
  950. } else {
  951. console.log(key, ' 数据量太大,无法同步到服务器!');
  952. }
  953. }
  954. }
  955. });
  956. };
  957. setTimeout(() => funcDownload(), 0); // 立即下载数据同步到本地
  958. setTimeout(() => funcUpload(), 10000); // 稍后将本地数据上报到服务器
  959. };
  960. /**
  961. * 初始化
  962. */
  963. let _init = function () {
  964. _checkUpdate();
  965. _addExtensionListener();
  966. _createOrRemoveContextMenu();
  967. _localDataSyncByGoogle();
  968. };
  969. /**
  970. * 打开任意一个URL
  971. * @param url
  972. * @private
  973. */
  974. let _openUrl = function (url) {
  975. chrome.tabs.create({url: url});
  976. };
  977. return {
  978. init: _init,
  979. runHelper: _runHelper,
  980. notify: notifyText,
  981. showColorPicker: _showColorPicker,
  982. tellMeAjaxDbgSwitch: _tellDevToolsDbgSwitchOn,
  983. getCapturedData: PageCapture.getCapturedData,
  984. openUrl: _openUrl
  985. };
  986. })();
  987. //初始化
  988. BgPageInstance.init();