index.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658
  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 feHelper = {};
  10. let devToolsDetected = false;
  11. // debug cache,主要记录每个tab的ajax debug 开关
  12. let ajaxDbgCache = {};
  13. //侦测就绪情况
  14. let _detectReadyState = function (callback) {
  15. if (_readyState.css && _readyState.js && _readyState.html) {
  16. _readyState.allDone = true;
  17. }
  18. if (_readyState.allDone && typeof callback === 'function') {
  19. callback();
  20. }
  21. };
  22. //各种元素的就绪情况
  23. let _readyState = {
  24. css: false,
  25. js: false,
  26. html: true,
  27. allDone: false
  28. };
  29. let _fcp_detect_interval = [];
  30. /**
  31. * 执行前端FCPHelper检测
  32. */
  33. let _doFcpDetect = function (tab) {
  34. //所有元素都准备就绪
  35. if (_readyState.allDone) {
  36. clearInterval(_fcp_detect_interval[tab.id]);
  37. chrome.tabs.sendMessage(tab.id, {
  38. type: MSG_TYPE.CODE_STANDARDS,
  39. event: MSG_TYPE.FCP_HELPER_DETECT
  40. });
  41. } else if (_fcp_detect_interval[tab.id] === undefined) {
  42. chrome.tabs.sendMessage(tab.id, {
  43. type: MSG_TYPE.CODE_STANDARDS,
  44. event: MSG_TYPE.FCP_HELPER_INIT
  45. });
  46. //显示桌面提醒
  47. notifyText({
  48. message: "正在准备数据,请稍等..."
  49. });
  50. _fcp_detect_interval[tab.id] = setInterval(function () {
  51. _doFcpDetect(tab);
  52. }, 200);
  53. }
  54. };
  55. /**
  56. * 文本格式,可以设置一个图标和标题
  57. * @param {Object} options
  58. * @config {string} type notification的类型,可选值:html、text
  59. * @config {string} icon 图标
  60. * @config {string} title 标题
  61. * @config {string} message 内容
  62. */
  63. let notifyText = function (options) {
  64. if (!window.Notification) {
  65. return;
  66. }
  67. if (!options.icon) {
  68. options.icon = "static/img/fe-48.png";
  69. }
  70. if (!options.title) {
  71. options.title = "温馨提示";
  72. }
  73. return chrome.notifications.create('', {
  74. type: 'basic',
  75. title: options.title,
  76. iconUrl: chrome.runtime.getURL(options.icon),
  77. message: options.message
  78. });
  79. };
  80. /**
  81. * 查看页面wpo信息
  82. */
  83. let _showPageWpoInfo = function (wpoInfo) {
  84. chrome.tabs.query({active: true, currentWindow: true}, function (tabs) {
  85. if (!wpoInfo) {
  86. notifyText({
  87. message: "对不起,检测失败"
  88. });
  89. } else {
  90. chrome.tabs.create({
  91. url: "wpo/index.html?" + btoa(encodeURIComponent(JSON.stringify(wpoInfo))),
  92. active: true
  93. });
  94. }
  95. });
  96. };
  97. /**
  98. * 获取页面wpo信息
  99. * @return {[type]}
  100. */
  101. let _getPageWpoInfo = function () {
  102. chrome.tabs.query({active: true, currentWindow: true}, function (tabs) {
  103. let tab = tabs[0];
  104. //显示桌面提醒
  105. chrome.tabs.sendMessage(tab.id, {
  106. type: MSG_TYPE.GET_PAGE_WPO_INFO
  107. });
  108. });
  109. };
  110. /**
  111. * 创建或更新成功执行的动作
  112. * @param evt
  113. * @param content
  114. * @private
  115. */
  116. let _tabUpdatedCallback = function (evt, content) {
  117. return function (newTab) {
  118. if (content) {
  119. setTimeout(function () {
  120. chrome.tabs.sendMessage(newTab.id, {
  121. type: MSG_TYPE.TAB_CREATED_OR_UPDATED,
  122. content: content,
  123. event: evt
  124. });
  125. }, 300)
  126. }
  127. };
  128. };
  129. /**
  130. * 打开对应文件,运行该Helper
  131. * @param tab
  132. * @param file
  133. * @param txt
  134. * @private
  135. */
  136. let _openFileAndRun = function (tab, file, txt) {
  137. chrome.tabs.query({windowId: chrome.windows.WINDOW_ID_CURRENT}, function (tabs) {
  138. let isOpened = false;
  139. let tabId;
  140. let reg = new RegExp("^chrome.*" + file + ".html$", "i");
  141. for (let i = 0, len = tabs.length; i < len; i++) {
  142. if (reg.test(tabs[i].url)) {
  143. isOpened = true;
  144. tabId = tabs[i].id;
  145. break;
  146. }
  147. }
  148. if (!isOpened) {
  149. chrome.tabs.create({
  150. url: '' + file + '/index.html',
  151. active: true
  152. }, _tabUpdatedCallback(file, txt));
  153. } else {
  154. chrome.tabs.update(tabId, {highlighted: true}, _tabUpdatedCallback(file, txt));
  155. }
  156. });
  157. };
  158. /**
  159. * ajax debugger 开关切换
  160. * @private
  161. */
  162. let _debuggerSwitchOn = function (callback) {
  163. chrome.tabs.query({active: true, currentWindow: true}, function (tabs) {
  164. let tab = tabs[0];
  165. ajaxDbgCache[tab.id] = !ajaxDbgCache[tab.id];
  166. chrome.tabs.executeScript(tab.id, {
  167. code: 'console.info("FeHelper提醒:Ajax Debugger开关已' + (ajaxDbgCache[tab.id] ? '开启' : '关闭') + '!");',
  168. allFrames: false
  169. });
  170. callback && callback();
  171. });
  172. };
  173. /**
  174. * 告诉DevTools页面,当前的debug开关是否打开
  175. * @param callback
  176. * @param withAlert
  177. * @private
  178. */
  179. let _tellDevToolsDbgSwitchOn = function (callback, withAlert) {
  180. chrome.tabs.query({active: true, currentWindow: true}, function (tabs) {
  181. let tab = tabs[0];
  182. callback && callback(ajaxDbgCache[tab.id]);
  183. if (withAlert) {
  184. if (ajaxDbgCache[tab.id]) {
  185. let msg = '';
  186. if (devToolsDetected) {
  187. msg = 'DevTools已打开,确保已切换到【Console】界面,并关注信息输出,愉快的进行Ajax Debugger!'
  188. } else {
  189. msg = '请打开DevTools,并切换到【Console】界面,关注信息输出,愉快的进行Ajax Debugger!';
  190. }
  191. alert(msg);
  192. }
  193. }
  194. });
  195. };
  196. /**
  197. * 根据给定参数,运行对应的Helper
  198. */
  199. let _runHelper = function (config, callback) {
  200. chrome.tabs.query({active: true, currentWindow: true}, function (tabs) {
  201. let tab = tabs[0];
  202. // 如果是采用独立文件方式访问,直接打开该页面即可
  203. if (config.useFile === 1) {
  204. let content = config.msgType === MSG_TYPE.QR_CODE ? tab.url : '';
  205. _openFileAndRun(tab, config.msgType, content);
  206. } else {
  207. switch (config.msgType) {
  208. //编码规范检测
  209. case MSG_TYPE.FCP_HELPER_DETECT:
  210. _doFcpDetect(tab);
  211. break;
  212. //查看网页加载时间
  213. case MSG_TYPE.SHOW_PAGE_LOAD_TIME:
  214. _getPageWpoInfo();
  215. break;
  216. //Ajax调试
  217. case MSG_TYPE.AJAX_DEBUGGER:
  218. _debuggerSwitchOn(callback);
  219. break;
  220. default :
  221. break;
  222. }
  223. }
  224. });
  225. };
  226. /**
  227. * 创建扩展专属的右键菜单
  228. */
  229. let _createContextMenu = function () {
  230. _removeContextMenu();
  231. feHelper.contextMenuId = chrome.contextMenus.create({
  232. title: "FeHelper",
  233. contexts: ['page', 'selection', 'editable', 'link', 'image'],
  234. documentUrlPatterns: ['http://*/*', 'https://*/*', 'file://*/*']
  235. });
  236. chrome.contextMenus.create({
  237. title: "二维码生成",
  238. contexts: ['page', 'selection', 'editable', 'link', 'image'],
  239. parentId: feHelper.contextMenuId,
  240. onclick: function (info, tab) {
  241. chrome.tabs.executeScript(tab.id, {
  242. code: '(' + (function (pInfo) {
  243. let linkUrl = pInfo.linkUrl;
  244. let pageUrl = pInfo.pageUrl;
  245. let imgUrl = pInfo.srcUrl;
  246. let selection = pInfo.selectionText;
  247. return linkUrl || imgUrl || selection || pageUrl;
  248. }).toString() + ')(' + JSON.stringify(info) + ')',
  249. allFrames: false
  250. }, function (txt) {
  251. _openFileAndRun(tab, MSG_TYPE.QR_CODE, (typeof txt === 'object') ? txt[0] : txt);
  252. });
  253. }
  254. });
  255. chrome.contextMenus.create({
  256. type: 'separator',
  257. contexts: ['image'],
  258. parentId: feHelper.contextMenuId
  259. });
  260. chrome.contextMenus.create({
  261. title: "二维码解码",
  262. contexts: ['image'],
  263. parentId: feHelper.contextMenuId,
  264. onclick: function (info, tab) {
  265. _qrDecode(info, tab);
  266. }
  267. });
  268. chrome.contextMenus.create({
  269. type: 'separator',
  270. contexts: ['all'],
  271. parentId: feHelper.contextMenuId
  272. });
  273. chrome.contextMenus.create({
  274. title: "字符串编解码",
  275. contexts: ['page', 'selection', 'editable'],
  276. parentId: feHelper.contextMenuId,
  277. onclick: function (info, tab) {
  278. chrome.tabs.executeScript(tab.id, {
  279. code: '(' + (function (pInfo) {
  280. return pInfo.selectionText;
  281. }).toString() + ')(' + JSON.stringify(info) + ')',
  282. allFrames: false
  283. }, function (txt) {
  284. _openFileAndRun(tab, MSG_TYPE.EN_DECODE, (typeof txt === 'object') ? txt[0] : txt);
  285. });
  286. }
  287. });
  288. chrome.contextMenus.create({
  289. type: 'separator',
  290. contexts: ['all'],
  291. parentId: feHelper.contextMenuId
  292. });
  293. chrome.contextMenus.create({
  294. title: "JSON格式化",
  295. contexts: ['page', 'selection', 'editable'],
  296. parentId: feHelper.contextMenuId,
  297. onclick: function (info, tab) {
  298. chrome.tabs.executeScript(tab.id, {
  299. code: '(' + (function (pInfo) {
  300. return pInfo.selectionText;
  301. }).toString() + ')(' + JSON.stringify(info) + ')',
  302. allFrames: false
  303. }, function (txt) {
  304. _openFileAndRun(tab, MSG_TYPE.JSON_FORMAT, (typeof txt === 'object') ? txt[0] : txt);
  305. });
  306. }
  307. });
  308. chrome.contextMenus.create({
  309. type: 'separator',
  310. contexts: ['all'],
  311. parentId: feHelper.contextMenuId
  312. });
  313. chrome.contextMenus.create({
  314. title: "代码格式化",
  315. contexts: ['page', 'selection', 'editable'],
  316. parentId: feHelper.contextMenuId,
  317. onclick: function (info, tab) {
  318. chrome.tabs.executeScript(tab.id, {
  319. code: '(' + (function (pInfo) {
  320. return pInfo.selectionText;
  321. }).toString() + ')(' + JSON.stringify(info) + ')',
  322. allFrames: false
  323. }, function (txt) {
  324. _openFileAndRun(tab, MSG_TYPE.CODE_BEAUTIFY, (typeof txt === 'object') ? txt[0] : txt);
  325. });
  326. }
  327. });
  328. chrome.contextMenus.create({
  329. type: 'separator',
  330. contexts: ['all'],
  331. parentId: feHelper.contextMenuId
  332. });
  333. chrome.contextMenus.create({
  334. title: "页面取色器",
  335. contexts: ['page', 'selection', 'editable'],
  336. parentId: feHelper.contextMenuId,
  337. onclick: function (info, tab) {
  338. _showColorPicker();
  339. }
  340. });
  341. };
  342. /**
  343. * 移除扩展专属的右键菜单
  344. */
  345. let _removeContextMenu = function () {
  346. if (!feHelper.contextMenuId) return;
  347. chrome.contextMenus.remove(feHelper.contextMenuId);
  348. feHelper.contextMenuId = null;
  349. };
  350. /**
  351. * 创建或移除扩展专属的右键菜单
  352. */
  353. let _createOrRemoveContextMenu = function () {
  354. Settings.getOptsFromBgPage((opts) => {
  355. if (opts['opt_item_contextMenus'] !== 'false') {
  356. _createContextMenu();
  357. } else {
  358. _removeContextMenu();
  359. }
  360. });
  361. };
  362. /**
  363. * 二维码转码
  364. * @param info
  365. * @param tab
  366. * @private
  367. */
  368. let _qrDecode = function (info, tab) {
  369. let qrcode = Tarp.require('../static/vendor/zxing/zxing.min.js');
  370. qrcode.callback = function (text) {
  371. if ((text || '').indexOf('error decoding QR Code') !== -1) {
  372. let image = new Image();
  373. image.src = info.srcUrl;
  374. image.onload = function () {
  375. let width = this.naturalWidth;
  376. let height = this.naturalHeight;
  377. // url方式解码失败,再转换成data uri后继续解码
  378. (function createCanvasContext(img, t, l, w, h) {
  379. let canvas = document.createElement('canvas');
  380. canvas.setAttribute('id', 'qr-canvas');
  381. canvas.height = h + 100;
  382. canvas.width = w + 100;
  383. let context = canvas.getContext('2d');
  384. context.fillStyle = 'rgb(255,255,255)';
  385. context.fillRect(0, 0, canvas.width, canvas.height);
  386. context.drawImage(img, l, t, w, h, 50, 50, w, h);
  387. qrcode.callback = function (txt) {
  388. chrome.tabs.sendMessage(tab.id, {
  389. type: MSG_TYPE.QR_DECODE,
  390. result: txt
  391. });
  392. };
  393. qrcode.decode(canvas.toDataURL());
  394. })(image, 0, 0, width, height);
  395. }
  396. } else {
  397. chrome.tabs.sendMessage(tab.id, {
  398. type: MSG_TYPE.QR_DECODE,
  399. result: text
  400. });
  401. }
  402. };
  403. qrcode.decode(info.srcUrl);
  404. };
  405. /**
  406. * 显示color picker
  407. * @private
  408. */
  409. let _showColorPicker = function () {
  410. chrome.tabs.query({active: true, currentWindow: true}, function (tabs) {
  411. let tab = tabs[0];
  412. let tabid = tab.id;
  413. chrome.tabs.sendMessage(tabid, {
  414. type: MSG_TYPE.SHOW_COLOR_PICKER,
  415. enableColorPicker: true
  416. }, function (response) {
  417. chrome.tabs.sendMessage(tabid, {
  418. type: MSG_TYPE.SHOW_COLOR_PICKER,
  419. doPick: true
  420. }, function (r) {
  421. });
  422. });
  423. });
  424. };
  425. /**
  426. * 将网页截成一张图,实现取色
  427. * @param callback
  428. * @private
  429. */
  430. let _drawColorPicker = function (callback) {
  431. chrome.tabs.query({active: true, currentWindow: true}, function (tabs) {
  432. let tab = tabs[0];
  433. let tabid = tab.id;
  434. chrome.tabs.captureVisibleTab(null, {format: 'png'}, function (dataUrl) {
  435. chrome.tabs.sendMessage(tabid, {
  436. type: MSG_TYPE.SHOW_COLOR_PICKER,
  437. setPickerImage: true,
  438. pickerImage: dataUrl
  439. }, function (response) {
  440. callback && callback();
  441. });
  442. });
  443. });
  444. };
  445. /**
  446. * 在当前页面的控制台输出console
  447. * @param request
  448. * @private
  449. */
  450. let _ajaxDebugger = function (request) {
  451. chrome.tabs.query({active: true, currentWindow: true}, function (tabs) {
  452. let tab = tabs[0];
  453. chrome.tabs.executeScript(tab.id, {
  454. code: "(" + (function (jsonStr) {
  455. let args = JSON.parse(unescape(jsonStr));
  456. console[args[0]].apply(console, Array.prototype.slice.call(args, 1));
  457. }).toString() + ")('" + request.content + "');"
  458. });
  459. });
  460. };
  461. // 当前页面是否进行自动格式化
  462. let _jsonAutoFormatRequest = function () {
  463. Settings.getOptsFromBgPage(opts => {
  464. chrome.tabs.query({active: true, currentWindow: true}, function (tabs) {
  465. chrome.tabs.sendMessage(tabs[0].id, {
  466. type: MSG_TYPE.JSON_PAGE_FORMAT,
  467. canIDoIt: opts.JSON_PAGE_FORMAT
  468. });
  469. });
  470. });
  471. };
  472. /**
  473. * 接收来自content_scripts发来的消息
  474. */
  475. let _addExtensionListener = function () {
  476. chrome.runtime.onMessage.addListener(function (request, sender, callback) {
  477. //提取配置项
  478. if (request.type === MSG_TYPE.GET_OPTIONS) {
  479. Settings.getOptsFromBgPage(callback);
  480. }
  481. //保存配置项
  482. else if (request.type === MSG_TYPE.SET_OPTIONS) {
  483. Settings.setOptsFromBgPage(request.items, callback);
  484. //管理右键菜单
  485. _createOrRemoveContextMenu();
  486. }
  487. //判断是否可以进行自动格式化
  488. else if (request.type === MSG_TYPE.JSON_PAGE_FORMAT_REQUEST) {
  489. _jsonAutoFormatRequest();
  490. }
  491. //保存当前网页加载时间
  492. else if (request.type === MSG_TYPE.CALC_PAGE_LOAD_TIME) {
  493. _showPageWpoInfo(request.wpo);
  494. }
  495. // color picker
  496. else if (request.type === MSG_TYPE.COLOR_PICKER) {
  497. _drawColorPicker(callback);
  498. }
  499. // console switch
  500. else if (request.type === MSG_TYPE.AJAX_DEBUGGER_SWITCH) {
  501. _tellDevToolsDbgSwitchOn(callback);
  502. }
  503. // console show
  504. else if (request.type === MSG_TYPE.AJAX_DEBUGGER_CONSOLE) {
  505. _ajaxDebugger(request);
  506. }
  507. // ===========================以下为编码规范检测====start==================================
  508. //处理CSS的请求
  509. else if (request.type === MSG_TYPE.GET_CSS) {
  510. //直接AJAX获取CSS文件内容
  511. Network.readFileContent(request.link, callback);
  512. }
  513. //处理JS的请求
  514. else if (request.type === MSG_TYPE.GET_JS) {
  515. //直接AJAX获取JS文件内容
  516. Network.readFileContent(request.link, callback);
  517. }
  518. //处理HTML的请求
  519. else if (request.type === MSG_TYPE.GET_HTML) {
  520. //直接AJAX获取JS文件内容
  521. Network.readFileContent(request.link, callback);
  522. }
  523. //处理cookie
  524. else if (request.type === MSG_TYPE.GET_COOKIE) {
  525. Network.getCookies(request, callback);
  526. }
  527. //移除cookie
  528. else if (request.type === MSG_TYPE.REMOVE_COOKIE) {
  529. Network.removeCookie(request, callback);
  530. }
  531. //设置cookie
  532. else if (request.type === MSG_TYPE.SET_COOKIE) {
  533. Network.setCookie(request, callback);
  534. }
  535. //CSS准备就绪
  536. else if (request.type === MSG_TYPE.CSS_READY) {
  537. _readyState.css = true;
  538. _detectReadyState(callback);
  539. }
  540. //JS准备就绪
  541. else if (request.type === MSG_TYPE.JS_READY) {
  542. _readyState.js = true;
  543. _detectReadyState(callback);
  544. }
  545. //HTML准备就绪
  546. else if (request.type === MSG_TYPE.HTML_READY) {
  547. _readyState.html = true;
  548. _detectReadyState(callback);
  549. }
  550. // ===========================以上为编码规范检测====end==================================
  551. return true;
  552. });
  553. // 检测DevTools是否打开
  554. let openCount = 0;
  555. chrome.runtime.onConnect.addListener(function (port) {
  556. if (port.name === MSG_TYPE.DEV_TOOLS) {
  557. if (openCount === 0) {
  558. devToolsDetected = true;
  559. }
  560. openCount++;
  561. port.onDisconnect.addListener(function (port) {
  562. openCount--;
  563. if (openCount === 0) {
  564. devToolsDetected = false;
  565. }
  566. });
  567. }
  568. });
  569. // 安装与更新
  570. chrome.runtime.onInstalled.addListener(({reason, previousVersion}) => {
  571. switch (reason) {
  572. case 'install':
  573. chrome.runtime.openOptionsPage();
  574. break;
  575. case 'update':
  576. chrome.browserAction.getBadgeText({tabId: null}, ({text}) => {
  577. setTimeout(() => {
  578. chrome.browserAction.setBadgeText({text: '恭喜'});
  579. setTimeout(() => {
  580. chrome.browserAction.setBadgeText({text: '升级'});
  581. setTimeout(() => {
  582. chrome.browserAction.setBadgeText({text: '成功'});
  583. setTimeout(() => {
  584. chrome.browserAction.setBadgeText({text: ''});
  585. }, 1500);
  586. }, 1500);
  587. }, 1500);
  588. }, 2000);
  589. });
  590. break;
  591. }
  592. });
  593. };
  594. /**
  595. * 初始化
  596. */
  597. let _init = function () {
  598. _addExtensionListener();
  599. _createOrRemoveContextMenu();
  600. };
  601. return {
  602. init: _init,
  603. runHelper: _runHelper,
  604. showColorPicker: _showColorPicker,
  605. tellMeAjaxDbgSwitch: _tellDevToolsDbgSwitchOn
  606. };
  607. })();
  608. //初始化
  609. BgPageInstance.init();