草庐IT

javascript - 如何将 Blob 从 Chrome 扩展程序传递到 Chrome 应用程序

coder 2024-05-08 原文

一点背景

我已经在 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,因为它是在扩展程序上创建的?任何人都可以请说明一下吗?

    更新 (9/23/14)
    抱歉更新晚了,但我被分配到另一个项目,直到 2 天前才能回到这个项目。

    因此,经过多次环顾之后,我决定采用@Danniel Herr 的建议,该建议建议使用 SharedWorker 和嵌入在应用程序框架中的页面。这个想法是扩展将向 SharedWorker 提供 blob,后者将 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>
    

    共享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);
        } 
    );
    

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

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

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

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

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

    很抱歉那些长篇大论的人,但我希望有人能解释一下,因为这让我发疯了。

    谢谢

    最佳答案

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

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

    该代码可能适用于文本文件或简单字符串,但不适用于图像(可能是由于字符编码问题)。那是我发疯的地方。解决方案是使用弗朗索瓦从一开始就提供的:
    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);
      } 
    }
    

    APP上
    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);
          }
        }
     }
    );
    

    关于javascript - 如何将 Blob 从 Chrome 扩展程序传递到 Chrome 应用程序,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25668998/

    有关javascript - 如何将 Blob 从 Chrome 扩展程序传递到 Chrome 应用程序的更多相关文章

    1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

      我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

    2. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

      总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

    3. ruby - 在 Ruby 程序执行时阻止 Windows 7 PC 进入休眠状态 - 2

      我需要在客户计算机上运行Ruby应用程序。通常需要几天才能完成(复制大备份文件)。问题是如果启用sleep,它会中断应用程序。否则,计算机将持续运行数周,直到我下次访问为止。有什么方法可以防止执行期间休眠并让Windows在执行后休眠吗?欢迎任何疯狂的想法;-) 最佳答案 Here建议使用SetThreadExecutionStateWinAPI函数,使应用程序能够通知系统它正在使用中,从而防止系统在应用程序运行时进入休眠状态或关闭显示。像这样的东西:require'Win32API'ES_AWAYMODE_REQUIRED=0x0

    4. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

      关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

    5. ruby-on-rails - 如何验证 update_all 是否实际在 Rails 中更新 - 2

      给定这段代码defcreate@upgrades=User.update_all(["role=?","upgraded"],:id=>params[:upgrade])redirect_toadmin_upgrades_path,:notice=>"Successfullyupgradeduser."end我如何在该操作中实际验证它们是否已保存或未重定向到适当的页面和消息? 最佳答案 在Rails3中,update_all不返回任何有意义的信息,除了已更新的记录数(这可能取决于您的DBMS是否返回该信息)。http://ar.ru

    6. ruby-on-rails - 'compass watch' 是如何工作的/它是如何与 rails 一起使用的 - 2

      我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t

    7. ruby - 将差异补丁应用于字符串/文件 - 2

      对于具有离线功能的智能手机应用程序,我正在为Xml文件创建单向文本同步。我希望我的服务器将增量/差异(例如GNU差异补丁)发送到目标设备。这是计划:Time=0Server:hasversion_1ofXmlfile(~800kiB)Client:hasversion_1ofXmlfile(~800kiB)Time=1Server:hasversion_1andversion_2ofXmlfile(each~800kiB)computesdeltaoftheseversions(=patch)(~10kiB)sendspatchtoClient(~10kiBtransferred)Cl

    8. ruby - 如何将脚本文件的末尾读取为数据文件(Perl 或任何其他语言) - 2

      我正在寻找执行以下操作的正确语法(在Perl、Shell或Ruby中):#variabletoaccessthedatalinesappendedasafileEND_OF_SCRIPT_MARKERrawdatastartshereanditcontinues. 最佳答案 Perl用__DATA__做这个:#!/usr/bin/perlusestrict;usewarnings;while(){print;}__DATA__Texttoprintgoeshere 关于ruby-如何将脚

    9. ruby - 如何指定 Rack 处理程序 - 2

      Rackup通过Rack的默认处理程序成功运行任何Rack应用程序。例如:classRackAppdefcall(environment)['200',{'Content-Type'=>'text/html'},["Helloworld"]]endendrunRackApp.new但是当最后一行更改为使用Rack的内置CGI处理程序时,rackup给出“NoMethodErrorat/undefinedmethod`call'fornil:NilClass”:Rack::Handler::CGI.runRackApp.newRack的其他内置处理程序也提出了同样的反对意见。例如Rack

    10. ruby - 在 Ruby 中编写命令行实用程序 - 2

      我想用ruby​​编写一个小的命令行实用程序并将其作为gem分发。我知道安装后,Guard、Sass和Thor等某些gem可以从命令行自行运行。为了让gem像二进制文件一样可用,我需要在我的gemspec中指定什么。 最佳答案 Gem::Specification.newdo|s|...s.executable='name_of_executable'...endhttp://docs.rubygems.org/read/chapter/20 关于ruby-在Ruby中编写命令行实用程序

    随机推荐