Javascript 使用chrome.downloads api发送推荐人标题

Javascript 使用chrome.downloads api发送推荐人标题,javascript,google-chrome-extension,Javascript,Google Chrome Extension,我正在尝试创建一个扩展,它模拟了Opera12的Ctrl+单击保存图像功能。我正在使用扩展的后台脚本中的chrome.downloads.download()启动下载,而内容脚本会侦听用户操作并发送一条包含要下载的图像URL的消息 一切正常,只是在一些网站上,如pixiv.net,下载被中断并失败。使用WebRequestAPI,我验证了活动选项卡中的cookie是否与下载请求一起发送,但没有发送referer头。我认为这是因为该站点正在阻止来自外部站点的下载请求。但我无法验证这一点,因为下载失

我正在尝试创建一个扩展,它模拟了Opera12的Ctrl+单击保存图像功能。我正在使用扩展的后台脚本中的chrome.downloads.download()启动下载,而内容脚本会侦听用户操作并发送一条包含要下载的图像URL的消息

一切正常,只是在一些网站上,如pixiv.net,下载被中断并失败。使用WebRequestAPI,我验证了活动选项卡中的cookie是否与下载请求一起发送,但没有发送referer头。我认为这是因为该站点正在阻止来自外部站点的下载请求。但我无法验证这一点,因为下载失败时不会触发webRequest.onError事件

我自己无法设置referer标头,因为它无法通过chrome.downloads进行设置,而webRequest.onBeforeSendHeaders不能在其阻止形式中与下载请求一起使用,因此我以后无法添加标头。有没有一种方法可以在选项卡的上下文中启动下载,使其行为类似于右键单击>另存为

为了澄清我是如何开始下载的,这里是我的TypeScript代码,并将其精简到适用的部分

注入脚本:

window.addEventListener('click', (e) => {
    if (suspended) {
        return;
    }

    if (e.ctrlKey && (<Element>e.target).nodeName === 'IMG') {
        chrome.runtime.sendMessage({
            url: (<HTMLImageElement>e.target).src,
            saveAs: true,
        });

        // Prevent the click from triggering links, etc.
        e.preventDefault();
        e.stopImmediatePropagation();

        // This can trigger for multiple elements, so disable it 
        // for a moment so we don't download multiple times at once
        suspended = true;
        window.setTimeout(() => suspended = false, 100);
    }
}, true);
更新

在下面的ExpertSystem解决方案中,我找到了一种最有效的方法。当后台脚本收到下载图像的请求时,我现在将URL的主机名与用户配置的需要解决方法的站点列表进行比较。如果需要解决方法,我会将消息发送回选项卡。该选项卡使用带有
responseType='blob'
的XMLHttpRequest下载图像。然后在blob上使用
URL.createObjectURL()
,并将该URL传递回后台脚本进行下载。这避免了数据URI的任何大小限制。此外,如果XHR请求失败,我将强制使用标准方法进行尝试,以便用户至少可以看到失败的下载出现

代码现在看起来像这样:

注入脚本:

// ...Original code here...

chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
    switch (message.action) {
        case 'xhr-download':
            var xhr = new XMLHttpRequest();
            xhr.responseType = 'blob';

            xhr.addEventListener('load', (e) => {
                chrome.runtime.sendMessage({
                    url: URL.createObjectURL(xhr.response),
                    filename: message.url.substr(message.url.lastIndexOf('/') + 1),
                    saveAs: message.saveAs,
                });
            });

            xhr.addEventListener('error', (e) => {
                // The XHR method failed. Force an attempt using the
                // downloads API.
                chrome.runtime.sendMessage({
                    url: message.url,
                    saveAs: message.saveAs,
                    forceDownload: true,
                });
            });

            xhr.open('get', message.url, true);
            xhr.send();
            break;
    }
});
背景脚本:

interface DownloadMessage {
    url: string;
    saveAs: boolean;
    filename?: string;
    forceDownload?: boolean;
}

chrome.runtime.onMessage.addListener((message: DownloadMessage, sender, sendResponse) => {
    var downloadOptions = {
        url: message.url,
        saveAs: message.saveAs,
    };

    if (message.filename) {
        options.filename = message.filename;
    }

    var a = document.createElement('a');
    a.href = message.url;

    if (message.forceDownload || a.protocol === 'blob:' || !needsReferralWorkaround(a.hostname)) {
        // No workaround needed or the XHR workaround is giving
        // us its results.
        chrome.downloads.download(downloadOptions);

        if (a.protocol === 'blob:') {
            // We don't need the blob URL any more. Release it.
            URL.revokeObjectUrl(message.url);
        }
    } else {
        // The XHR workaround is needed. Inform the tab.
        chrome.tabs.sendMessage(sender.tab.id, { 
            action: 'xhr-download', 
            url: message.url, 
            saveAs: message.saveAs
        });
    }
});
当然,如果图像与页面不在同一个域中,并且服务器不发送CORS头,那么这将失败,但我认为不会有太多的站点限制引用者下载图像并提供来自不同域的图像。

方法的第一个参数(
options
)可以包括标题属性,一个对象数组:

如果URL使用HTTP[s]协议,则要随请求发送的额外HTTP头。每个头都表示为一个字典,其中包含键名和value或binaryValue,仅限于XMLHttpRequest允许的值

更新:

var suspended = false;
window.addEventListener('click', function(evt) {
    if (suspended) {
        return;
    }

    if (evt.ctrlKey && (evt.target.nodeName === 'IMG')) {
        var a = document.createElement('a');
        a.href = evt.target.src;
        a.target = '_blank';
        a.download = a.href.substring(a.href.lastIndexOf('/') + 1);
        a.click();

        evt.preventDefault();
        evt.stopImmediatePropagation();

        suspended = true;
        window.setTimeout(function() {
            suspended = false;
        }, 100);
    }
}, true);
不幸的是,正如您所指出的,“仅限于XMLHttpRequest所允许的那些”部分使情况完全不同,因为
Referer
也不是XHR允许的头

我在这个问题上搞砸了不少,但没有找到令人满意的解决方案或解决办法。不过我确实接近了,所以我将在这里展示结果,以防有人发现它们有用。(此外,HTML5规范即将推出的一些增强实际上可能会使这成为一个可行的解决方案。)


首先,简单快捷的替代方案(但有一个主要缺点)是通过编程方式创建和“单击”链接(
。希望它很快得到支持,这将使此解决方案回到游戏中:)

实施:

var suspended = false;
window.addEventListener('click', function(evt) {
    if (suspended) {
        return;
    }

    if (evt.ctrlKey && (evt.target.nodeName === 'IMG')) {
        var a = document.createElement('a');
        a.href = evt.target.src;
        a.target = '_blank';
        a.download = a.href.substring(a.href.lastIndexOf('/') + 1);
        a.click();

        evt.preventDefault();
        evt.stopImmediatePropagation();

        suspended = true;
        window.setTimeout(function() {
            suspended = false;
        }, 100);
    }
}, true);
示例扩展名由3个文件组成:

  • manifest.json
    :清单
  • content.js
    :内容脚本
  • background.js
    :背景页面
  • manifest.json:

    content.js:

    background.js:


    很抱歉给出了这么长的答案(也没有提出有效的解决方案)。
    我希望有人在那里找到有用的东西(或者至少节省一些时间尝试同样的东西)。

    其中的关键部分是“限制在XMLHttpRequest允许的范围内”。无法设置Referer。似乎我错过了这一点。你可以发布一些代码(或者一个被中断的特定图像URL,因为我似乎可以毫无问题地下载它们)。例如,pixiv上的任何高分辨率图像都无法下载。如果我在图像上右键单击>另存为,它将正确下载,但是
    chrome.downloads.download({url:'http://i1.pixiv.net/img121/img/tsuguminoto/40236819.jpg“,saveAs:true})
    失败。使用webRequest API,我可以看到浏览器(本例中为Opera 20)正在从其内容脚本将消息发送到后台脚本的页面发送一个与
    document.cookie
    匹配的cookie头,因此我认为身份验证不是问题。@Joel:我用两个部分解决方法更新了答案(不是100%有效的解决方案)。我花了相当长的时间试图解决这个问题,但我做不到。我只是想分享一下我的“经验”,以防有人在其中发现有用的东西。@Joel:对我漫长的努力连一点评论都没有?:(:d)
    {
        "manifest_version": 2,
    
        "name":    "Test Extension",
        "version": "0.0",
        "offline_enabled": false,
    
        "background": {
            "persistent": false,
            "scripts": ["background.js"]
        },
    
        "content_scripts": [{
            "matches":    ["*://*/*"],
            "js":         ["content.js"],
            "run_at":     "document_idle",
            "all_frames": true
        }],
    
        "permissions": [
            "downloads",
            "*://*/*"
        ],
    }
    
    var suspended = false;
    window.addEventListener('click', function(evt) {
        if (suspended) {
            return;
        }
    
        if (evt.ctrlKey && (evt.target.nodeName === 'IMG')) {
    
            /* Initialize the "download" process
             * for the specified image's source-URL */
            chrome.runtime.sendMessage({
                action: 'downloadImgStart',
                url:    evt.target.src
            });
    
            evt.preventDefault();
            evt.stopImmediatePropagation();
    
            suspended = true;
            window.setTimeout(function() {
                suspended = false;
            }, 100);
        }
    }, true);
    
    /* This function, which will be injected into the tab with the image,
     * takes care of putting the image into a canvas and converting it to a dataURL
     * (which is sent back to the background-page for further processing) */
    var imgToDataURL = function() {
        /* Determine the image's name, type, desired quality etc */
        var src     = window.location.href;
        var name    = src.substring(src.lastIndexOf('/') + 1);
        var type    = /\.jpe?g([?#]|$)/i.test(name) ? 'image/jpeg' : 'image/png';
        var quality = 1.0;
    
        /* Load the image into a canvas and convert it to a dataURL */
        var img = document.body.querySelector('img');
        var canvas = document.createElement('canvas');
        canvas.width = img.naturalWidth;
        canvas.height = img.naturalHeight;
        var ctx = canvas.getContext('2d');
        ctx.drawImage(img, 0, 0);
        var dataURL = canvas.toDataURL(type, quality);
    
        /* If the specified type was not supported (falling back to the default
         * 'image/png'), update the `name` accordingly */
        if ((type !== 'image/png') && (dataURL.indexOf('data:image/png') === 0)) {
            name += '.png';
        }
    
        /* Pass the dataURL and `name` back to the background-page */
        chrome.runtime.sendMessage({
            action: 'downloadImgEnd',
            url:    dataURL,
            name:   name
        });
    }
    
    /* An 'Immediatelly-invoked function expression' (IIFE)
     * to be injected into the web-page containing the image */
    var codeStr = '(' + imgToDataURL + ')();';
    
    /* Listen for messages from the content scripts */
    chrome.runtime.onMessage.addListener(function(msg, sender) {
    
        /* Require that the message contains a 'URL' */
        if (!msg.url) {
            console.log('Invalid message format: ', msg);
            return;
        }
    
        switch (msg.action) {
        case 'downloadImgStart':
            /* Request received from the original page:
             * Open the image's source-URL in an unfocused, new tab
             * (to avoid "tainted canvas" errors due to CORS)
             * and inject 'imgToDataURL' */
            chrome.tabs.create({
                url: msg.url,
                active: false
            }, function(tab) {
                chrome.tabs.executeScript(tab.id, {
                    code:      codeStr,
                    runAt:     'document_idle',
                    allFrames: false
                });
            });
            break;
        case 'downloadImgEnd':
            /* The "dirty" work is done: We acquired the dataURL !
             * Close the "background" tab and initiate the download */
            chrome.tabs.remove(sender.tab.id);
            chrome.downloads.download({
                url:      msg.url,
                filename: msg.name || '',
                saveAs:   true
            });
            break;
        default:
            /* Repot invalie message 'action' */
            console.log('Invalid action: ', msg.action, ' (', msg, ')');
            break;
        }
    });