content-script.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374
  1. /**
  2. * FeHelper Full Page Capture
  3. * @type {{scroll}}
  4. */
  5. window.screenshotContentScript = function () {
  6. let screenshots = [];
  7. let capturedData = {};
  8. let MAX_PRIMARY_DIMENSION = 50000 * 2,
  9. MAX_SECONDARY_DIMENSION = 20000 * 2,
  10. MAX_AREA = MAX_PRIMARY_DIMENSION * MAX_SECONDARY_DIMENSION;
  11. let pageOriginalTitle = document.title;
  12. /**
  13. * URL合法性校验
  14. * @param url
  15. * @returns {boolean}
  16. */
  17. function isValidUrl(url) {
  18. let matches = ['http://*/*', 'https://*/*', 'ftp://*/*', 'file://*/*'],
  19. noMatches = [/^https?:\/\/chrome.google.com\/.*$/];
  20. let r, i;
  21. for (i = noMatches.length - 1; i >= 0; i--) {
  22. if (noMatches[i].test(url)) {
  23. return false;
  24. }
  25. }
  26. for (i = matches.length - 1; i >= 0; i--) {
  27. r = new RegExp('^' + matches[i].replace(/\*/g, '.*') + '$');
  28. if (r.test(url)) {
  29. return true;
  30. }
  31. }
  32. return false;
  33. }
  34. /**
  35. * 如果页面超级超级长,需要拆分成多张图片来存储
  36. * @param totalWidth
  37. * @param totalHeight
  38. * @returns {Array}
  39. * @private
  40. */
  41. function _initScreenshots(totalWidth, totalHeight) {
  42. let badSize = (totalHeight > MAX_PRIMARY_DIMENSION ||
  43. totalWidth > MAX_PRIMARY_DIMENSION ||
  44. totalHeight * totalWidth > MAX_AREA),
  45. biggerWidth = totalWidth > totalHeight,
  46. maxWidth = (!badSize ? totalWidth :
  47. (biggerWidth ? MAX_PRIMARY_DIMENSION : MAX_SECONDARY_DIMENSION)),
  48. maxHeight = (!badSize ? totalHeight :
  49. (biggerWidth ? MAX_SECONDARY_DIMENSION : MAX_PRIMARY_DIMENSION)),
  50. numCols = Math.ceil(totalWidth / maxWidth),
  51. numRows = Math.ceil(totalHeight / maxHeight),
  52. row, col, canvas, left, top;
  53. let canvasIndex = 0;
  54. let result = [];
  55. for (row = 0; row < numRows; row++) {
  56. for (col = 0; col < numCols; col++) {
  57. canvas = document.createElement('canvas');
  58. canvas.width = (col === numCols - 1 ? totalWidth % maxWidth || maxWidth : maxWidth);
  59. canvas.height = (row === numRows - 1 ? totalHeight % maxHeight || maxHeight : maxHeight);
  60. left = col * maxWidth;
  61. top = row * maxHeight;
  62. result.push({
  63. canvas: canvas,
  64. ctx: canvas.getContext('2d'),
  65. index: canvasIndex,
  66. left: left,
  67. right: left + canvas.width,
  68. top: top,
  69. bottom: top + canvas.height
  70. });
  71. canvasIndex++;
  72. }
  73. }
  74. return result;
  75. }
  76. /**
  77. * 从截屏中筛选有效数据
  78. * @param imgLeft
  79. * @param imgTop
  80. * @param imgWidth
  81. * @param imgHeight
  82. * @param screenshots
  83. * @private
  84. */
  85. function _filterScreenshots(imgLeft, imgTop, imgWidth, imgHeight, screenshots) {
  86. // Filter down the screenshots to ones that match the location
  87. // of the given image.
  88. let imgRight = imgLeft + imgWidth,
  89. imgBottom = imgTop + imgHeight;
  90. return screenshots.filter(function (screenshot) {
  91. return (imgLeft < screenshot.right &&
  92. imgRight > screenshot.left &&
  93. imgTop < screenshot.bottom &&
  94. imgBottom > screenshot.top);
  95. });
  96. }
  97. let addScreenShot = function (data, uri) {
  98. let image = new Image();
  99. image.onload = function () {
  100. data.image = {width: image.width, height: image.height};
  101. // given device mode emulation or zooming, we may end up with
  102. // a different sized image than expected, so let's adjust to
  103. // match it!
  104. if (data.windowWidth !== image.width) {
  105. let scale = image.width / data.windowWidth;
  106. data.x *= scale;
  107. data.y *= scale;
  108. data.totalWidth *= scale;
  109. data.totalHeight *= scale;
  110. }
  111. // lazy initialization of screenshot canvases (since we need to wait
  112. // for actual image size)
  113. if (!screenshots.length) {
  114. Array.prototype.push.apply(
  115. screenshots,
  116. _initScreenshots(data.totalWidth, data.totalHeight)
  117. );
  118. }
  119. // draw it on matching screenshot canvases
  120. _filterScreenshots(
  121. data.x, data.y, image.width, image.height, screenshots
  122. ).forEach(function (screenshot) {
  123. screenshot.ctx.drawImage(
  124. image,
  125. data.x - screenshot.left,
  126. data.y - screenshot.top
  127. );
  128. });
  129. if (data.complete === 1) {
  130. captureConfig.success(data);
  131. }
  132. };
  133. image.src = uri;
  134. };
  135. /**
  136. * 通过网页url生成默认的文件名
  137. * @param contentURL
  138. * @returns {string}
  139. */
  140. function buildFilenameFromUrl() {
  141. let name = location.href.split('?')[0].split('#')[0];
  142. if (name) {
  143. name = name
  144. .replace(/^https?:\/\//, '')
  145. .replace(/[^A-z0-9]+/g, '-')
  146. .replace(/-+/g, '-')
  147. .replace(/^[_\-]+/, '')
  148. .replace(/[_\-]+$/, '');
  149. name = '-' + name;
  150. } else {
  151. name = '';
  152. }
  153. return 'fehelper' + name + '-' + Date.now() + '.png';
  154. }
  155. // 配置项
  156. let captureConfig = {
  157. // 获取原始数据,用这个
  158. success: function (data) {
  159. chrome.runtime.sendMessage({
  160. type: 'fh-dynamic-any-thing',
  161. thing: 'page-screenshot-done',
  162. params: {
  163. filename: buildFilenameFromUrl(),
  164. screenshots: screenshots.map(ss => {
  165. ss.dataUri=ss.canvas.toDataURL()
  166. return ss;
  167. }),
  168. totalWidth: data.totalWidth,
  169. totalHeight: data.totalHeight
  170. }
  171. });
  172. },
  173. fail: reason => {
  174. alert(reason && reason.message || reason || '稍后尝试刷新页面重试!');
  175. },
  176. progress: complete => {
  177. let percent = parseInt(complete * 100, 10) + '%';
  178. document.title = `进度:${percent}...`;
  179. if (percent === '100%') {
  180. setTimeout(() => {
  181. document.title = pageOriginalTitle;
  182. }, 800);
  183. }
  184. return true;
  185. }
  186. };
  187. function max(nums) {
  188. return Math.max.apply(Math, nums.filter(function (x) {
  189. return x;
  190. }));
  191. }
  192. function goCapture(params) {
  193. if (!isValidUrl(location.href)) {
  194. return captureConfig.fail('invalid url');
  195. }
  196. let body = document.body,
  197. originalBodyOverflowYStyle = body ? body.style.overflowY : '',
  198. originalX = window.scrollX,
  199. originalY = window.scrollY,
  200. originalOverflowStyle = document.documentElement.style.overflow;
  201. if (body) {
  202. body.style.overflowY = 'visible';
  203. }
  204. let widths = [
  205. document.documentElement.clientWidth,
  206. body ? body.scrollWidth : 0,
  207. document.documentElement.scrollWidth,
  208. body ? body.offsetWidth : 0,
  209. document.documentElement.offsetWidth
  210. ],
  211. heights = [
  212. document.documentElement.clientHeight,
  213. body ? body.scrollHeight : 0,
  214. document.documentElement.scrollHeight,
  215. body ? body.offsetHeight : 0,
  216. document.documentElement.offsetHeight
  217. ],
  218. fullWidth = max(widths),
  219. fullHeight = max(heights),
  220. windowWidth = window.innerWidth,
  221. windowHeight = window.innerHeight,
  222. arrangements = [],
  223. scrollPad = 200,
  224. yDelta = windowHeight - (windowHeight > scrollPad ? scrollPad : 0),
  225. xDelta = windowWidth,
  226. yPos = fullHeight - windowHeight,
  227. xPos,
  228. numArrangements,
  229. captureVisible = false;
  230. // During zooming, there can be weird off-by-1 types of things...
  231. if (fullWidth <= xDelta + 1) {
  232. fullWidth = xDelta;
  233. }
  234. // Disable all scrollbars. We'll restore the scrollbar state when we're done
  235. // taking the screenshots.
  236. document.documentElement.style.overflow = 'hidden';
  237. // 截图:可视区域
  238. if (params.captureType === 'visible') {
  239. arrangements = [window.scrollX, window.scrollY];
  240. fullWidth = window.innerWidth;
  241. fullHeight = window.innerHeight;
  242. captureVisible = true;
  243. } else {
  244. // 全网页截图
  245. while (yPos > -yDelta) {
  246. xPos = 0;
  247. while (xPos < fullWidth) {
  248. arrangements.push([xPos, yPos]);
  249. xPos += xDelta;
  250. }
  251. yPos -= yDelta;
  252. }
  253. }
  254. numArrangements = arrangements.length;
  255. function cleanUp() {
  256. document.documentElement.style.overflow = originalOverflowStyle;
  257. if (body) {
  258. body.style.overflowY = originalBodyOverflowYStyle;
  259. }
  260. window.scrollTo(originalX, originalY);
  261. }
  262. (function processArrangements() {
  263. if (!arrangements.length) {
  264. return cleanUp();
  265. }
  266. let next = arrangements.pop(),
  267. x = next[0], y = next[1];
  268. let complete = 1;
  269. let dataX = 0;
  270. let dataY = 0;
  271. if (!captureVisible) {
  272. window.scrollTo(x, y);
  273. complete = (numArrangements - arrangements.length) / numArrangements;
  274. dataX = window.scrollX;
  275. dataY = window.scrollY;
  276. }
  277. let data = {
  278. x: dataX,
  279. y: dataY,
  280. complete: complete,
  281. windowWidth: windowWidth,
  282. totalWidth: fullWidth,
  283. totalHeight: fullHeight,
  284. devicePixelRatio: window.devicePixelRatio,
  285. tabId: window.__FH_TAB_ID__
  286. };
  287. // Need to wait for things to settle
  288. window.setTimeout(function () {
  289. // In case the below callback never returns, cleanup
  290. let cleanUpTimeout = window.setTimeout(cleanUp, 1250);
  291. captureConfig.progress(data.complete);
  292. chrome.runtime.sendMessage({
  293. type: 'fh-dynamic-any-thing',
  294. thing: 'add-screen-shot-by-pages',
  295. params: data
  296. }).then(resp => {
  297. if(resp.uri) {
  298. addScreenShot(resp.params,resp.uri);
  299. }
  300. window.clearTimeout(cleanUpTimeout);
  301. if (data.complete !== 1) {
  302. setTimeout(processArrangements,200);
  303. } else {
  304. cleanUp();
  305. }
  306. });
  307. }, 150);
  308. })();
  309. }
  310. window.screenshotNoPage = function(){
  311. let elWrapper = document.createElement('div');
  312. elWrapper.innerHTML = '<div id="fehelper_screenshot" style="position:fixed;left:0;top:0;right:0;z-index:1000000;padding:15px;background:#000;text-align:center;">' +
  313. '<button id="btnVisible" style="margin: 0 10px;padding: 10px;border-radius: 5px;border: 1px solid #fff;cursor:pointer;">可视区域截图</button>' +
  314. '<button id="btnWhole" style="margin: 0 10px;padding: 10px;border-radius: 5px;border: 1px solid #fff;cursor:pointer;">全网页截图</button></div>';
  315. elAlertMsg = elWrapper.childNodes[0];
  316. document.body.appendChild(elAlertMsg);
  317. document.querySelector('#btnVisible').onclick = e => {
  318. elAlertMsg.remove();
  319. goCapture({captureType:'visible'});
  320. };
  321. document.querySelector('#btnWhole').onclick = e => {
  322. elAlertMsg.remove();
  323. goCapture({captureType:'whole'});
  324. };
  325. };
  326. };