如何将 blob 从 Chrome 扩展程序传递到 Chrome 应用程序

Posted

技术标签:

【中文标题】如何将 blob 从 Chrome 扩展程序传递到 Chrome 应用程序【英文标题】:How to pass a blob from a Chrome extension to a Chrome app 【发布时间】:2014-10-29 09:51:38 【问题描述】:

一点背景

我已经在 Chrome 扩展程序上工作了几天,该扩展程序每天多次截取给定网页的屏幕截图。我以this 为指导,一切正常。

不过,有一个小要求扩展无法满足。用户必须有权访问保存图像(屏幕截图)的文件夹,但 Chrome Extensions don't have access to the file system。另一方面,Chrome 应用程序可以。因此,环顾四周后,我得出结论,我必须同时创建一个 Chrome 扩展程序和一个 Chrome 应用程序。这个想法是扩展程序将创建一个截图的 blob,然后将该 blob 发送到应用程序,然后将其作为图像保存到用户指定的位置。这正是我正在做的——我在扩展端创建一个截图的 blob,然后将其发送到应用程序,要求用户选择保存图像的位置。

问题

直到节省部分,一切都按预期工作。 Blob 在扩展程序上创建,发送到应用程序,由应用程序接收,用户被询问在哪里保存,图像被保存......这就是事情崩溃的地方。 生成的图像无法使用。当我尝试打开它时,我收到一条消息,提示 “无法确定类型”。以下是我正在使用的代码:

    首先在扩展方面,我创建一个 blob 并将其发送过来,如下所示:

     chrome.runtime.sendMessage(
        APP_ID, /* I got this from the app */
        myMessage: blob, /* Blob created previously; it's correct */
        function(response) 
          appendLog("response: "+JSON.stringify(response));
        
     );
    

    然后,在 APP 端,我收到 blob 并尝试像这样保存它:

    // listen for external messages
    chrome.runtime.onMessageExternal.addListener(
      function(request, sender, sendResponse) 
        if (sender.id in blacklistedIds) 
          sendResponse("result":"sorry, could not process your message");
          return;  // don't allow this extension access
         else if (request.incomingBlob) 
          appendLog("from "+sender.id+": " + request.incomingBlob);
    
          // attempt to save blob to choosen location
          if (_folderEntry == null) 
             // get a directory to save in if not yet chosen
             openDirectory();
          
          saveBlobToFile(request.incomingBlob, "screenshot.png");
    
          /*
          // inspect object to try to see what's wrong
          var keys = Object.keys(request.incomingBlob);
          var keyString = "";
          for (var key in keys) 
             keyString += " " + key;
          
          appendLog("Blob object keys:" + keyString);
          */
    
          sendResponse("result":"Ok, got your message");
         else 
          sendResponse("result":"Ops, I don't understand this message");
        
      
    );
    

    这是执行实际保存的应用程序上的功能:

    function saveBlobToFile(blob, fileName) 
      appendLog('entering saveBlobToFile function...');
      chrome.fileSystem.getWritableEntry(_folderEntry, function(entry)          
        entry.getFile(fileName, create: true, function(entry)          
          entry.createWriter(function(writer) 
            //writer.onwrite = function() 
            //   writer.onwrite = null;
            //   writer.truncate(writer.position);
            //;
            appendLog('calling writer.write...');
            writer.write(blob);                       
            // Also tried writer.write(new Blob([blob], type: 'image/png'));
          );
        );
      );
    
    

没有错误。没有打嗝。代码有效,但图像无用。我到底错过了什么?我哪里错了?是不是我们只能在扩展/应用程序之间传递字符串? blob 是否在途中损坏?我的应用程序是否因为 blob 是在扩展程序上创建的而无法访问它?任何人都可以解释一下吗?

更新(2014 年 9 月 23 日) 抱歉更新晚了,但我被分配到另一个项目,直到 2 天前才能回复。

所以在环顾四周后,我决定采用@Danniel Herr 的建议,该建议建议使用 SharedWorker 和嵌入在应用程序框架中的页面。这个想法是扩展将 blob 提供给 SharedWorker,SharedWorker 将 blob 转发到扩展中的页面,该页面嵌入在应用程序的框架中。该页面,然后使用 parent.postMessage(...) 将 blob 转发到应用程序。这有点麻烦,但似乎这是我唯一的选择。

让我发布一些代码,以便更有意义:

扩展:

var worker = new SharedWorker(chrome.runtime.getURL('shared-worker.js'));
worker.port.start();
worker.postMessage('hello from extension'); // Can send blob here too
worker.port.addEventListener("message", function(event) 
   $('h1Title').innerhtml = event.data;
);

proxy.js

var worker = new SharedWorker(chrome.runtime.getURL('shared-worker.js'));
worker.port.start();

worker.port.addEventListener("message",
   function(event)       
      parent.postMessage(event.data, 'chrome-extension://[extension id]');
   
);

proxy.html

<script src='proxy.js'></script>

shared-worker.js

var ports = [];
var count = 0;
onconnect = function(event) 
    count++;
    var port = event.ports[0];
    ports.push(port);
    port.start(); 

    /* 
    On both the extension and the app, I get count = 1 and ports.length = 1
    I'm running them side by side. This is so maddening!!!
    What am I missing?
    */
    var msg = 'Hi, you are connection #' + count + ". ";
    msg += " There are " + ports.length + " ports open so far."
    port.postMessage(msg);

    port.addEventListener("message",       
      function(event) 
        for (var i = 0; i < ports.length; ++i) 
            //if (ports[i] != port) 
                ports[i].postMessage(event.data);
            //
        
    );
;

在应用上

context.addEventListener("message", 
    function(event) 
        appendLog("message from proxy: " + event.data);
     
);

这就是执行流程...在扩展上,我创建了一个共享工作者并向其发送消息。共享工作者应该能够接收 blob,但出于测试目的,我只发送一个简单的字符串。

接下来,共享工作者接收消息并将其转发给所有已连接的人。此时应用程序框架内的 proxy.html/js 确实已连接,并且应该接收共享工作者转发的任何内容。

接下来,proxy.js [应该] 接收来自共享工作者的消息并使用 parent.postMessage(...) 将其发送到应用程序。该应用程序正在通过 window.addEventListener("message",...) 进行侦听。

为了测试这个流程,我首先打开应用程序,然后单击扩展按钮。我在应用程序上没有收到任何消息。我也没有错误。

扩展程序可以与共享工作者进行来回通信。该应用程序可以很好地与共享工作者通信。但是,我从 extension->proxy->app 发送的消息没有到达应用程序。我错过了什么?

对不起,长帖子的家伙,但我希望有人能解释一下,因为这让我发疯了。

谢谢

【问题讨论】:

【参考方案1】:

感谢大家的帮助。我发现解决方案是将 blob 转换为扩展上的二进制字符串,然后使用 chrome 的消息传递 API 将字符串发送到应用程序。然后在应用程序上,我按照 Francois 的建议将二进制字符串转换回 blob。我之前尝试过这个解决方案,但我没有工作,因为我在应用程序上使用了以下代码:

blob = new Blob([blobAsBinString], type: mimeType);

该代码可能适用于文本文件或简单字符串,但不适用于图像(可能是由于字符编码问题)。那就是我要发疯的地方。解决方案是使用 Francois 从一开始就提供的内容:

var bytes = new Uint8Array(blobAsBinString.length);
for (var i=0; i<bytes.length; i++) 
   bytes[i] = blobAsBinString.charCodeAt(i);            
             
blob = new Blob([bytes], type: mimeString);

该代码重新训练二进制字符串的完整性,并在应用程序上正确重新创建 blob。

现在我还结合了一些我在这里发现的和其他地方的 RobW 提出的建议,即将 blob 分成块并像这样发送,以防 blob 太大。整个解决方案如下:

关于扩展:

function sendBlobToApp()   

  // read the blob in chunks/chunks and send it to the app
  // Note: I crashed the app using 1 KB chunks. 1 MB chunks work just fine. 
  // I decided to use 256 KB as that seems neither too big nor too small
  var CHUNK_SIZE = 256 * 1024;
  var start = 0;
  var stop = CHUNK_SIZE;      

  var remainder = blob.size % CHUNK_SIZE;
  var chunks = Math.floor(blob.size / CHUNK_SIZE);      

  var chunkIndex = 0;

  if (remainder != 0) chunks = chunks + 1;           

  var fr = new FileReader();
  fr.onload = function() 
      var message = 
          blobAsText: fr.result,
          mimeString: mimeString,                 
          chunks: chunks 
      ;          
      // APP_ID was obtained elsewhere
      chrome.runtime.sendMessage(APP_ID, message, function(result) 
          if (chrome.runtime.lastError) 
              // Handle error, e.g. app not installed
              // appendLog is defined elsewhere
              appendLog("could not send message to app");
           
      );

      // read the next chunk of bytes
      processChunk();
  ;
  fr.onerror = function()  appendLog("An error ocurred while reading file"); ;
  processChunk();

  function processChunk() 
     chunkIndex++;         

     // exit if there are no more chunks
     if (chunkIndex > chunks) 
        return;
     

     if (chunkIndex == chunks && remainder != 0) 
        stop = start + remainder;
                                

     var blobChunk = blob.slice(start, stop);

     // prepare for next chunk
     start = stop;
     stop = stop + CHUNK_SIZE;

     // convert chunk as binary string
     fr.readAsBinaryString(blobChunk);
   


在应用上

chrome.runtime.onMessageExternal.addListener(
  function(request, sender, sendResponse) 
    if (sender.id in blacklistedIds) 
      return;  // don't allow this extension access
     else if (request.blobAsText)                   
       //new chunk received  
      _chunkIndex++;                   

      var bytes = new Uint8Array(request.blobAsText.length);                     
      for (var i=0; i<bytes.length; i++) 
         bytes[i] = request.blobAsText.charCodeAt(i);            
               
      // store blob
      _blobs[_chunkIndex-1] = new Blob([bytes], type: request.mimeString);           

      if (_chunkIndex == request.chunks)                       
         // merge all blob chunks
         for (j=0; j<_blobs.length; j++) 
            var mergedBlob;
            if (j>0)                   
               // append blob
               mergedBlob = new Blob([mergedBlob, _blobs[j]], type: request.mimeString);
            
            else                   
               mergedBlob = new Blob([_blobs[j]], type: request.mimeString);
            
                                  

         saveBlobToFile(mergedBlob, "myImage.png", request.mimeString);
      
    
 
);

【讨论】:

感谢分享这个非常全面的解决方案。我喜欢这种方法,目前看来效果很好。 不起作用。块去同步。需要一种以正确顺序接收所有内容的方法【参考方案2】:

我的应用程序是否无法访问 blob,因为它是在 延期?任何人都可以解释一下吗?

没错!您可能想要传递 dataUrl 而不是 blob。下面这样的事情可能会起作用:

/* Chrome Extension */

var blobToDataURL = function(blob, cb) 
  var reader = new FileReader();
  reader.onload = function() 
    var dataUrl = reader.result;
    var base64 = dataUrl.split(',')[1];
    cb(base64);
  ;
  reader.readAsDataURL(blob);
  ;

blobToDataUrl(blob, function(dataUrl) 
  chrome.runtime.sendMessage(APP_ID, databUrl: dataUrl, function() );
);

/* Chrome App */

function dataURLtoBlob(dataURL) 
    var byteString = atob(dataURL.split(',')[1]),
        mimeString = dataURL.split(',')[0].split(':')[1].split(';')[0];

    var ab = new ArrayBuffer(byteString.length);
    var ia = new Uint8Array(ab);
    for (var i = 0; i < byteString.length; i++) 
        ia[i] = byteString.charCodeAt(i);
    

    var blob = new Blob([ia], type: mimeString);
    return blob;


chrome.runtime.onMessageExternal.addListener(
    function(request)   
  var blob =  dataURLtoBlob(request.dataUrl); 
  saveBlobToFile(blob, "screenshot.png");
);

【讨论】:

No :( dataURL 是我首先尝试的。它不起作用,因为我拥有的 dataURL 看起来像这样:filesystem:chrome-extension://' + chrome.i18n.getMessage(' @@extension_id') + '/persistent/' + fileName。该应用程序无法访问创建的图像,因为它位于扩展程序的沙箱中。至少当我无法在使用 dataURL 的应用程序。 这可能会有所帮助。让我知道它是否有效。 ***.com/questions/24582573/… @LeonAlpha 如果你向应用发送 blob,为什么不能发送 dataURL? 谢谢丹尼尔赫尔。明天我会看看这个选项,然后回来报告它是否有效。 @vux777 我在将 blob 发送到应用程序时遇到问题。似乎一切都过去了,但生成的图像无法使用。然后当我尝试dataURL时,它也不起作用。预期的';' dataURL 中的 ':' 和 ':' 缺失,并且提供用于将其解码为 blob 的代码失败。 @DanielHerr 嘿丹尼尔,我已经更新了我的问题以反映我到目前为止所做的事情。我有代码,但我发送的消息在中途丢失了,我不知道在哪里。请你看看我的编辑,看看有没有什么想法?这真的让我发疯。我想不通。已经花了大约10个小时。再次感谢。【参考方案3】:

我对这个问题非常感兴趣,因为我正在尝试完成类似的事情。

这些是我发现相关的问题:

    How can a Chrome extension save many files to a user-specified directory? Implement cross extension message passing in chrome extension and app Does chrome.runtime support posting messages with transferable objects? Pass File object to background.js from content script or pass createObjectURL (and keep alive after refresh)

根据 Rob W 的说法,在第一个链接中:

“Chrome 的 fileSystem (app) API 可以直接写入用户指定的文件系统(例如 ~/Documents 或 %USERPROFILE%\Documents)。”

如果您可以写入用户的文件系统,您应该能够从中读取,对吧?

我没有机会尝试这一点,但您可以使用 chrome 扩展程序下载 api 将该项目保存到您的下载中,而不是直接将文件 blob 传递给应用程序。

然后您可以使用 chrome 应用程序文件系统 api 检索它以访问它。

编辑:

我一直在读到 api 可以访问的文件系统是沙盒的。所以我不知道这个解决方案是否可行。它被沙盒化,Rob W 对“直接写入用户文件系统”的描述听起来与我相反。

编辑:

Rob W 在这里修改了他的答案:Implement cross extension message passing in chrome extension and app。

它不再使用共享工作者,而是将文件数据作为字符串传递给后端,后端可以将字符串转回 blob。

我不确定消息的最大长度是多少,但 Rob W 还提到了一种将 blob 切片以分段发送的解决方案。

编辑:

我已经发送了 43 mbs 的数据,而我的应用程序没有崩溃。

【讨论】:

这是一个想法。现在,我前段时间浏览了 API,发现扩展程序只能访问临时存储或永久存储,这两者都是用户无法访问的,其他应用程序也无法访问。也就是说,每个扩展程序都在沙箱中运行,而其他应用程序无权访问它创建的文件(或者至少这是我记得的)。这就是我需要一个应用程序的原因,因为该应用程序可以在任何地方编写。我现在的问题是从扩展程序中获取图像的 blob 到应用程序。我现在唯一的希望是一个 SharedWorker,但它不能像这里看到的那样工作。 假设 Rob W 是正确的,为什么还需要发送 blob?您可以使用扩展程序将所需的文件下载到 chrome 的默认下载目录中,然后使用应用程序中的文件系统 api 来访问它们。不需要 blob,您只需使用所需的实际文件。 fileSystem API 仍然需要用户选择要访问的文件夹。这对用户来说有点奇怪。 这听起来不太理想。 各位感谢您的意见! @markain 我必须检查下载 api。到目前为止,我所阅读的所有内容都指向永久或临时存储。但是,如果下载 api 允许我在没有用户交互的情况下 1-创建目录和 2-保存到这些目录,那么我就有了我的解决方案。我不确定我能不能。 @Xan 用户只能通过应用程序 UI 选择一次位置。在上面的代码中,块 'if (_folderEntry != null) openDirectory(); ' 实现了这一点。用户选择要保存图像的文件夹。这非常有效。我的问题是图像现在没用了。【参考方案4】:

这真是一个有趣的问题。在我看来,可以使用以下技术来完成:

    首先,您应该将您的 blob 转换为 arraybuffer。这可以通过FileReader 完成,并且是异步操作 然后是Encoding API 的一些魔力,目前可在稳定的 Chrome 上使用。因此,您将数组缓冲区转换为字符串。此操作是同步的 然后您可以使用 Chrome API like this 与其他扩展程序/应用程序进行通信。我正在使用这种技术来推广我的一个应用程序(新打包的应用程序)使用另一个著名的旧应用程序。由于传统的打包应用实际上是扩展,我认为一切都会好起来的。

【讨论】:

谢谢@Dmitry Sorin。明天我会看看你的解决方案,然后报告给你,让你知道它是否有效。再次感谢。 再次感谢您的回答。尽管我尝试了它,但我无法让它工作。我能够很好地发送数组缓冲区,但生成的图像无法使用并且无法打开。我已经编辑了我的问题以显示我现在想要做什么。我在应用程序中使用 SharedWorker 和一个框架,如下所示:***.com/questions/24582573/…。非常感谢您对此的见解(或您先前的建议无效的原因)。谢谢

以上是关于如何将 blob 从 Chrome 扩展程序传递到 Chrome 应用程序的主要内容,如果未能解决你的问题,请参考以下文章

如何将 Blob 对象从 javascript 传递到 Android?

如何正确地将 blob 从前端传递到后端?

如何在 chrome 扩展本机消息传递和 c# 应用程序之间发送/接收消息

如何将消息从 DLL 传递到应用程序

如何从 chrome 扩展访问 iframe?

Chrome 扩展如何将数据从内容脚本发送到 popup.html