Explorar o código

增加网页导出为图片的功能

zxlie %!s(int64=7) %!d(string=hai) anos
pai
achega
5096e79dfc

+ 19 - 0
apps/background/index.js

@@ -7,6 +7,7 @@ var BgPageInstance = (function () {
     let MSG_TYPE = Tarp.require('../static/js/msg_type');
     let Settings = Tarp.require('../options/settings');
     let Network = Tarp.require('../background/network');
+    let PageCapture = Tarp.require('../page-capture/capture-api')(MSG_TYPE);
 
     let feHelper = {
         codeStandardMgr: {},
@@ -233,6 +234,10 @@ var BgPageInstance = (function () {
                     case MSG_TYPE.FCP_HELPER_DETECT:
                         _doFcpDetect(tab);
                         break;
+                    //将当前网页转为图片
+                    case MSG_TYPE.PAGE_CAPTURE:
+                        PageCapture.full(tab);
+                        break;
                     //查看网页加载时间
                     case MSG_TYPE.SHOW_PAGE_LOAD_TIME:
                         _getPageWpoInfo();
@@ -383,6 +388,20 @@ var BgPageInstance = (function () {
                 _showColorPicker();
             }
         });
+
+        chrome.contextMenus.create({
+            type: 'separator',
+            contexts: ['all'],
+            parentId: feHelper.contextMenuId
+        });
+        chrome.contextMenus.create({
+            title: "将页面导出为图片",
+            contexts: ['all'],
+            parentId: feHelper.contextMenuId,
+            onclick: function (info, tab) {
+                PageCapture.full(tab);
+            }
+        });
     };
 
     /**

+ 7 - 2
apps/content-script/index.js

@@ -32,8 +32,6 @@ chrome.runtime.sendMessage({
     type: MSG_TYPE.JS_CSS_PAGE_BEAUTIFY_REQUEST
 });
 
-Tarp.require('../code-beautify/automatic');
-
 // 在tab创建或者更新时候,监听事件,看看是否有参数传递过来
 chrome.runtime.onMessage.addListener(function (request, sender, callback) {
 
@@ -42,15 +40,22 @@ chrome.runtime.onMessage.addListener(function (request, sender, callback) {
         case MSG_TYPE.JSON_PAGE_FORMAT:
             request.canIDoIt && Tarp.require('../json-format/automatic', true).then(JsonTools => JsonTools.format());
             break;
+
         // js、css页面自动检测,提示格式化
         case MSG_TYPE.JS_CSS_PAGE_BEAUTIFY:
             request.canIDoIt && Tarp.require('../code-beautify/automatic', true).then(beautifier => beautifier.detect());
             break;
+
         // 二维码解码
         case MSG_TYPE.QR_DECODE:
             Tarp.require('../qr-code/decode', true).then(qrcode => qrcode.show(request.result));
             break;
 
+        // 全屏截图
+        case MSG_TYPE.PAGE_CAPTURE_SCROLL:
+            Tarp.require('../page-capture/index', true).then(page => page.scroll(callback));
+            break;
+
         // 页面性能检测
         case MSG_TYPE.GET_PAGE_WPO_INFO:
             Tarp.require('../wpo/inject', true).then(wpo => {

+ 1 - 0
apps/manifest.json

@@ -54,6 +54,7 @@
     "qr-code/decode.js",
     "wpo/inject.js",
     "color-picker/index.js",
+    "page-capture/index.js",
     "static/vendor/jquery/jquery-3.3.1.min.js",
     "code-standards/index.css",
     "code-standards/index.js",

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 0 - 0
apps/options/index.html


+ 2 - 1
apps/options/settings.js

@@ -23,7 +23,8 @@ module.exports = (() => {
         'SHOW_PAGE_LOAD_TIME',
         'AJAX_DEBUGGER',
         'JS_CSS_PAGE_BEAUTIFY',
-        'HTML_TO_MARKDOWN'
+        'HTML_TO_MARKDOWN',
+        'PAGE_CAPTURE'
     ];
 
     /**

+ 360 - 0
apps/page-capture/capture-api.js

@@ -0,0 +1,360 @@
+/**
+ * chrome extension页面截屏API
+ *
+ * @copyright https://github.com/mrcoles/full-page-screen-capture-chrome-extension
+ * @modify zhaoxianlie
+ */
+
+module.exports = function (MSG_TYPE) {
+
+    let MAX_PRIMARY_DIMENSION = 15000 * 2,
+        MAX_SECONDARY_DIMENSION = 4000 * 2,
+        MAX_AREA = MAX_PRIMARY_DIMENSION * MAX_SECONDARY_DIMENSION;
+
+    let matches = ['http://*/*', 'https://*/*', 'ftp://*/*', 'file://*/*'],
+        noMatches = [/^https?:\/\/chrome.google.com\/.*$/];
+
+    let listenerFunc ;
+
+    /**
+     * URL合法性校验
+     * @param url
+     * @returns {boolean}
+     */
+    function isValidUrl(url) {
+        // couldn't find a better way to tell if executeScript
+        // wouldn't work -- so just testing against known urls
+        // for now...
+        let r, i;
+        for (i = noMatches.length - 1; i >= 0; i--) {
+            if (noMatches[i].test(url)) {
+                return false;
+            }
+        }
+        for (i = matches.length - 1; i >= 0; i--) {
+            r = new RegExp('^' + matches[i].replace(/\*/g, '.*') + '$');
+            if (r.test(url)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+
+    /**
+     * 执行 capture
+     * @param data
+     * @param screenshots
+     * @param callback
+     */
+    function capture(data, screenshots, callback) {
+        chrome.tabs.captureVisibleTab(
+            null, {format: 'png', quality: 100}, function (dataURI) {
+                if (dataURI) {
+                    let image = new Image();
+                    image.onload = function () {
+                        data.image = {width: image.width, height: image.height};
+
+                        // given device mode emulation or zooming, we may end up with
+                        // a different sized image than expected, so let's adjust to
+                        // match it!
+                        if (data.windowWidth !== image.width) {
+                            let scale = image.width / data.windowWidth;
+                            data.x *= scale;
+                            data.y *= scale;
+                            data.totalWidth *= scale;
+                            data.totalHeight *= scale;
+                        }
+
+                        // lazy initialization of screenshot canvases (since we need to wait
+                        // for actual image size)
+                        if (!screenshots.length) {
+                            Array.prototype.push.apply(
+                                screenshots,
+                                _initScreenshots(data.totalWidth, data.totalHeight)
+                            );
+                        }
+
+                        // draw it on matching screenshot canvases
+                        _filterScreenshots(
+                            data.x, data.y, image.width, image.height, screenshots
+                        ).forEach(function (screenshot) {
+                            screenshot.ctx.drawImage(
+                                image,
+                                data.x - screenshot.left,
+                                data.y - screenshot.top
+                            );
+                        });
+
+                        callback(data);
+                    };
+                    image.src = dataURI;
+                }
+            });
+    }
+
+
+    /**
+     * 如果页面超级超级长,需要拆分成多张图片来存储
+     * @param totalWidth
+     * @param totalHeight
+     * @returns {Array}
+     * @private
+     */
+    function _initScreenshots(totalWidth, totalHeight) {
+        let badSize = (totalHeight > MAX_PRIMARY_DIMENSION ||
+            totalWidth > MAX_PRIMARY_DIMENSION ||
+            totalHeight * totalWidth > MAX_AREA),
+            biggerWidth = totalWidth > totalHeight,
+            maxWidth = (!badSize ? totalWidth :
+                (biggerWidth ? MAX_PRIMARY_DIMENSION : MAX_SECONDARY_DIMENSION)),
+            maxHeight = (!badSize ? totalHeight :
+                (biggerWidth ? MAX_SECONDARY_DIMENSION : MAX_PRIMARY_DIMENSION)),
+            numCols = Math.ceil(totalWidth / maxWidth),
+            numRows = Math.ceil(totalHeight / maxHeight),
+            row, col, canvas, left, top;
+
+        let canvasIndex = 0;
+        let result = [];
+
+        for (row = 0; row < numRows; row++) {
+            for (col = 0; col < numCols; col++) {
+                canvas = document.createElement('canvas');
+                canvas.width = (col === numCols - 1 ? totalWidth % maxWidth || maxWidth :
+                    maxWidth);
+                canvas.height = (row === numRows - 1 ? totalHeight % maxHeight || maxHeight :
+                    maxHeight);
+
+                left = col * maxWidth;
+                top = row * maxHeight;
+
+                result.push({
+                    canvas: canvas,
+                    ctx: canvas.getContext('2d'),
+                    index: canvasIndex,
+                    left: left,
+                    right: left + canvas.width,
+                    top: top,
+                    bottom: top + canvas.height
+                });
+
+                canvasIndex++;
+            }
+        }
+
+        return result;
+    }
+
+
+    /**
+     * 从截屏中筛选有效数据
+     * @param imgLeft
+     * @param imgTop
+     * @param imgWidth
+     * @param imgHeight
+     * @param screenshots
+     * @private
+     */
+    function _filterScreenshots(imgLeft, imgTop, imgWidth, imgHeight, screenshots) {
+        // Filter down the screenshots to ones that match the location
+        // of the given image.
+        //
+        let imgRight = imgLeft + imgWidth,
+            imgBottom = imgTop + imgHeight;
+        return screenshots.filter(function (screenshot) {
+            return (imgLeft < screenshot.right &&
+                imgRight > screenshot.left &&
+                imgTop < screenshot.bottom &&
+                imgBottom > screenshot.top);
+        });
+    }
+
+
+    /**
+     * 获取Blobs数据
+     * @param screenshots
+     */
+    function getBlobs(screenshots) {
+        return screenshots.map(function (screenshot) {
+            let dataURI = screenshot.canvas.toDataURL();
+
+            // convert base64 to raw binary data held in a string
+            // doesn't handle URLEncoded DataURIs
+            let byteString = atob(dataURI.split(',')[1]);
+
+            // separate out the mime component
+            let mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];
+
+            // write the bytes of the string to an ArrayBuffer
+            let ab = new ArrayBuffer(byteString.length);
+            let ia = new Uint8Array(ab);
+            for (let i = 0; i < byteString.length; i++) {
+                ia[i] = byteString.charCodeAt(i);
+            }
+
+            // create a blob for writing to a file
+            return new Blob([ab], {type: mimeString});
+        });
+    }
+
+
+    /**
+     * 将Blob数据存储到本地临时文件
+     * @param blob
+     * @param filename
+     * @param index
+     * @param callback
+     * @param errback
+     */
+    function saveBlob(blob, filename, index, callback, errback) {
+        filename = ((filename, index) => {
+            if (!index) {
+                return filename;
+            }
+            let sp = filename.split('.');
+            let ext = sp.pop();
+            return sp.join('.') + '-' + (index + 1) + '.' + ext;
+        })(filename, index);
+
+        function onwriteend() {
+            // open the file that now contains the blob - calling
+            // `openPage` again if we had to split up the image
+            let urlName = ('filesystem:chrome-extension://' +
+                chrome.i18n.getMessage('@@extension_id') +
+                '/temporary/' + filename);
+
+            callback(urlName);
+        }
+
+        // come up with file-system size with a little buffer
+        let size = blob.size + (1024 / 2);
+
+        // create a blob for writing to a file
+        let reqFileSystem = window.requestFileSystem || window.webkitRequestFileSystem;
+        reqFileSystem(window.TEMPORARY, size, function (fs) {
+            fs.root.getFile(filename, {create: true}, function (fileEntry) {
+                fileEntry.createWriter(function (fileWriter) {
+                    fileWriter.onwriteend = onwriteend;
+                    fileWriter.write(blob);
+                }, errback); // TODO - standardize error callbacks?
+            }, errback);
+        }, errback);
+    }
+
+
+    /**
+     * 截屏保存为Blob对象
+     * @param tab
+     * @param doneback
+     * @param errback
+     * @param progress
+     */
+    function captureToBlobs(tab, doneback, errback, progress) {
+        let screenshots = [],
+            noop = new Function();
+
+        doneback = doneback || noop;
+        errback = errback || noop;
+        progress = progress || noop;
+
+        if (!isValidUrl(tab.url)) {
+            errback('invalid url');
+        }
+
+        if(typeof listenerFunc !== 'undefined') {
+            chrome.runtime.onMessage.removeListener(listenerFunc);
+        }
+        listenerFunc = function (request, sender, sendResponse) {
+            if (request.type === MSG_TYPE.PAGE_CAPTURE_CAPTURE) {
+                progress(request.complete);
+                capture(request, screenshots, (data) => {
+                    sendResponse(data);
+                    request.complete === 1 && doneback(getBlobs(screenshots));
+                });
+                return true;
+            }
+        };
+        chrome.runtime.onMessage.addListener(listenerFunc);
+
+        chrome.tabs.sendMessage(tab.id, {type: MSG_TYPE.PAGE_CAPTURE_SCROLL}, () => progress(0));
+    }
+
+    /**
+     * 截屏保存为文件
+     * @param tab
+     * @param callback
+     * @param errback
+     * @param progress
+     */
+    function captureToFiles(tab, callback, errback, progress) {
+        let doneback = (blobs) => {
+            let i = 0,
+                len = blobs.length;
+
+            // 生成临时文件名
+            let baseName = ((contentURL) => {
+                let name = contentURL.split('?')[0].split('#')[0];
+                if (name) {
+                    name = name
+                        .replace(/^https?:\/\//, '')
+                        .replace(/[^A-z0-9]+/g, '-')
+                        .replace(/-+/g, '-')
+                        .replace(/^[_\-]+/, '')
+                        .replace(/[_\-]+$/, '');
+                    name = '-' + name;
+                } else {
+                    name = '';
+                }
+                return 'screencapture' + name + '-' + Date.now() + '.png';
+            })(tab.url);
+
+            // 保存 & 打开
+            (function doNext() {
+                saveBlob(blobs[i], baseName, i, function (filename) {
+                    ++i >= len ? callback(filename) : doNext();
+                }, errback);
+            })();
+        };
+
+        captureToBlobs(tab, doneback, errback, progress);
+    }
+
+
+    /**
+     * 截屏入口
+     * @param tab
+     */
+    function fullPageCapture(tab) {
+
+        // 配置项
+        let captureConfig = {
+            success: filename => {
+                chrome.tabs.create({
+                    url: filename
+                });
+            },
+
+            fail: reason => {
+                console.log(reason)
+            },
+
+            progress: percent => {
+                if (percent === 0) {
+                    console.log('loading');
+                }
+                else {
+                    percent = parseInt(percent * 100, 10) + '%';
+                    console.log(percent);
+                }
+            }
+        };
+
+        // 截屏走起
+        captureToFiles(tab, captureConfig.success, captureConfig.fail, captureConfig.progress);
+    }
+
+    return {
+        full: fullPageCapture
+    };
+
+};

+ 132 - 0
apps/page-capture/index.js

@@ -0,0 +1,132 @@
+/**
+ * FeHelper Full Page Capture
+ * @type {{scroll}}
+ */
+module.exports = (() => {
+
+    function max(nums) {
+        return Math.max.apply(Math, nums.filter(function (x) {
+            return x;
+        }));
+    }
+
+    function getPositions(callback) {
+
+        let body = document.body,
+            originalBodyOverflowYStyle = body ? body.style.overflowY : '',
+            originalX = window.scrollX,
+            originalY = window.scrollY,
+            originalOverflowStyle = document.documentElement.style.overflow;
+
+        if (body) {
+            body.style.overflowY = 'visible';
+        }
+
+        let widths = [
+                document.documentElement.clientWidth,
+                body ? body.scrollWidth : 0,
+                document.documentElement.scrollWidth,
+                body ? body.offsetWidth : 0,
+                document.documentElement.offsetWidth
+            ],
+            heights = [
+                document.documentElement.clientHeight,
+                body ? body.scrollHeight : 0,
+                document.documentElement.scrollHeight,
+                body ? body.offsetHeight : 0,
+                document.documentElement.offsetHeight
+            ],
+            fullWidth = max(widths),
+            fullHeight = max(heights),
+            windowWidth = window.innerWidth,
+            windowHeight = window.innerHeight,
+            arrangements = [],
+            // pad the vertical scrolling to try to deal with
+            // sticky headers, 250 is an arbitrary size
+            scrollPad = 200,
+            yDelta = windowHeight - (windowHeight > scrollPad ? scrollPad : 0),
+            xDelta = windowWidth,
+            yPos = fullHeight - windowHeight,
+            xPos,
+            numArrangements;
+
+        // During zooming, there can be weird off-by-1 types of things...
+        if (fullWidth <= xDelta + 1) {
+            fullWidth = xDelta;
+        }
+
+        // Disable all scrollbars. We'll restore the scrollbar state when we're done
+        // taking the screenshots.
+        document.documentElement.style.overflow = 'hidden';
+
+        while (yPos > -yDelta) {
+            xPos = 0;
+            while (xPos < fullWidth) {
+                arrangements.push([xPos, yPos]);
+                xPos += xDelta;
+            }
+            yPos -= yDelta;
+        }
+
+        numArrangements = arrangements.length;
+
+        function cleanUp() {
+            document.documentElement.style.overflow = originalOverflowStyle;
+            if (body) {
+                body.style.overflowY = originalBodyOverflowYStyle;
+            }
+            window.scrollTo(originalX, originalY);
+        }
+
+        (function processArrangements() {
+            if (!arrangements.length) {
+                cleanUp();
+                if (callback) {
+                    callback();
+                }
+                return;
+            }
+
+            let next = arrangements.shift(),
+                x = next[0], y = next[1];
+
+            window.scrollTo(x, y);
+
+            let data = {
+                type: MSG_TYPE.PAGE_CAPTURE_CAPTURE,
+                x: window.scrollX,
+                y: window.scrollY,
+                complete: (numArrangements - arrangements.length) / numArrangements,
+                windowWidth: windowWidth,
+                totalWidth: fullWidth,
+                totalHeight: fullHeight,
+                devicePixelRatio: window.devicePixelRatio
+            };
+
+            // Need to wait for things to settle
+            window.setTimeout(function () {
+                // In case the below callback never returns, cleanup
+                let cleanUpTimeout = window.setTimeout(cleanUp, 1250);
+
+                chrome.runtime.sendMessage(data, function (captured) {
+                    window.clearTimeout(cleanUpTimeout);
+
+                    if (captured) {
+                        // Move on to capture next arrangement.
+                        processArrangements();
+                    } else {
+                        // If there's an error in popup.js, the response value can be
+                        // undefined, so cleanup
+                        cleanUp();
+                    }
+                });
+
+            }, 150);
+        })();
+    }
+
+    return {
+        scroll: getPositions
+    }
+
+})();

+ 3 - 0
apps/popup/index.css

@@ -90,6 +90,9 @@ ul.fe-function-list li.-x-ajax-debugger b {
 ul.fe-function-list li.-x-markdown b {
 	background-position:-83px -222px;
 }
+ul.fe-function-list li.-x-pagecapture b {
+	background-position:-16px -111px;
+}
 
 ul.fe-function-list li i {
     color: #aaa;

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 0 - 0
apps/popup/index.html


+ 3 - 0
apps/static/js/msg_type.js

@@ -84,6 +84,9 @@ const MSG_TYPE = {
     AJAX_DEBUGGER_SWITCH: "ajax-debugger-switch",
 
     HTML_TO_MARKDOWN: "html2markdown",
+    PAGE_CAPTURE:'PAGE_CAPTURE',
+    PAGE_CAPTURE_SCROLL:"page_capture_scroll",
+    PAGE_CAPTURE_CAPTURE:"page_capture_capture",
 
     // dev tools页面
     DEV_TOOLS: 'dev-tools',

Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio