Force download in Google Chrome Extension - google-chrome

I'm writing an Google Chrome extension that lets you download a backup file of your data. I want the user to be able to press a button and a "Save as" dialog box should open and they can save the file to their computer. Nothing appears to work and I have not found an answer on the internet. I have tried several approaches:
Using document.execCommand('SaveAs', null, 'filename.json'); This does not work because this command is IE-only and there does not appear to be a Webkit-alternative
Using data URIs. This was the most promising and worked in Opera and Firefox, but the problem being that neither Chrome nor Safari appear to support the Content-disposition=attachment;-header in the URI. This should work. (Chrome doesn't even allow me to ctrl/cmd+s a page from a data URI)
Using an XMLHTTPRequest. I haven't tried this, but there has to be some way in which you could relay the request around? Please note that I do not want to use an external server (in that case I could have simply sent a POST-request and applied a Content-disposition:-header)
Using an available Chrome Extension API. But there does not seem to be anything for this purpose.
The reason I don't want to use any external server is that I don't want to have to pay for the hosting, and the data sent might be sensitive to the user, and I don't want infringe on anyone's privacy.
Has anyone gotten this to work?

I did it as follows in Appmator code on Github.
The basic approach is to build your Blob, however you want (Chrome/WebKit/Firefox has a responseBlob on XmlHttpRequest so you can use that), create an iframe (hidden, display:none) then assign the src of the iframe to be the Blob.
This will initiate a download and save it to the filesystem. The only problem is, you can't set the filename yet.
var savaeas = document.getElementById("saveas");
var bb = new (window.BlobBuilder || window.WebKitBlobBuilder)();
var output = Builder.output({"binary":true});
var ui8a = new Uint8Array(output.length);
for(var i = 0; i< output.length; i++) {
ui8a[i] = output.charCodeAt(i);
}
bb.append(ui8a.buffer);
var blob = bb.getBlob("application/octet-stream");
var saveas = document.createElement("iframe");
saveas.style.display = "none";
if(!!window.createObjectURL == false) {
saveas.src = window.webkitURL.createObjectURL(blob);
}
else {
saveas.src = window.createObjectURL(blob);
}
document.body.appendChild(saveas);

Related

Is it possible to use Watir-Webdriver to interact with Polymer?

I just updated my Chrome browser (Version 50.0.2661.75) and have found that the chrome://downloads page has changed such that my automated tests can no longer interact with it. Previously, I had been using Watir-Webdriver to clear the downloads page, delete files from my machine, etc, without too much difficulty.
It looks like Google is using Polymer on this page, and
there are new (to me) elements like paper-button that Watir-Webdriver doesn't recognize
even browser.img(:id, 'file-icon').present? returns false when I
can clearly see that the image is on the page.
Is automating a page made with Polymer (specifically the chrome://downloads page) a lost cause until changes are made to Watir-Webdriver, or is there a solution to this problem?
Given that the download items are accessible in Javascript and that Watir allows Javascript execution (as #titusfortner pointed out), it's possible to automate the new Downloads page with Watir.
Note the shadow root elements (aka "local DOM" in Polymer) can be queried with $$.
Here's an example Javascript that logs the icon presence and filename of each download item and removes the items from the list. Copy and paste the snippet into Chrome's console to test (verified in Chrome 49.0.2623.112 on OS X El Capitan).
(function() {
var items = document
.querySelector('downloads-manager')
.$$('iron-list')
.querySelectorAll('downloads-item');
Array.from(items).forEach(item => {
let hasIcon = typeof item.$$('#file-icon') !== 'undefined';
console.log('hasIcon', hasIcon);
let filename = item.$$('#file-link').textContent;
console.log('filename', filename);
item.$.remove.click();
});
})();
UPDATE: I verified the Javascript with Watir-Webdriver in OS X (with ChromeDriver 2.21). It works the same as in the console for me (i.e., I see the console logs, and the download items are removed). Here are the steps to reproduce:
Run the following commands in a new irb shell (copy+paste):
require 'watir-webdriver'
b = Watir::Browser.new :chrome
In the newly opened Chrome window, download several files to create some download items, and then open the Downloads tab.
Run the following commands in the irb shell (copy+paste):
script = "(function() {
var items = document
.querySelector('downloads-manager')
.$$('iron-list')
.querySelectorAll('downloads-item');
Array.from(items).forEach(item => {
let hasIcon = typeof item.$$('#file-icon') !== 'undefined';
console.log('hasIcon', hasIcon);
let filename = item.$$('#file-link').textContent;
console.log('filename', filename);
item.$.remove.click();
});
})();"
b.execute_script(script)
Observe the Downloads tab no longer contains download items.
Open the Chrome console from the Downloads tab.
Observe the console shows several lines of hasIcon true and the filenames of the downloaded items.
Looks like Google put the elements inside the Shadow-Dom, which isn't supported by Selenium/Watir/WebDriver spec (yet). There might a way to obtain the element via javascript (browser.execute_script(<...>)), but it is experimental at best still.
Attempting to automated a Polymer page, I found I was able to access the web elements by asking Polymer to use the shady dom by adding ?dom=shady in the URL. Like in the example on this page https://www.polymer-project.org/1.0/docs/devguide/settings:
http://example.com/test-app/index.html?dom=shady
Adding the dom parameter to request Polymer to use the shady dom may be worth a try.

What does "blob" mean in the `href` property in "<link>"? [duplicate]

My page generates a URL like this: "blob:http%3A//localhost%3A8383/568233a1-8b13-48b3-84d5-cca045ae384f" How can I convert it to a normal address?
I'm using it as an <img>'s src attribute.
A URL that was created from a JavaScript Blob can not be converted to a "normal" URL.
A blob: URL does not refer to data the exists on the server, it refers to data that your browser currently has in memory, for the current page. It will not be available on other pages, it will not be available in other browsers, and it will not be available from other computers.
Therefore it does not make sense, in general, to convert a Blob URL to a "normal" URL. If you wanted an ordinary URL, you would have to send the data from the browser to a server and have the server make it available like an ordinary file.
It is possible convert a blob: URL into a data: URL, at least in Chrome. You can use an AJAX request to "fetch" the data from the blob: URL (even though it's really just pulling it out of your browser's memory, not making an HTTP request).
Here's an example:
var blob = new Blob(["Hello, world!"], { type: 'text/plain' });
var blobUrl = URL.createObjectURL(blob);
var xhr = new XMLHttpRequest;
xhr.responseType = 'blob';
xhr.onload = function() {
var recoveredBlob = xhr.response;
var reader = new FileReader;
reader.onload = function() {
var blobAsDataUrl = reader.result;
window.location = blobAsDataUrl;
};
reader.readAsDataURL(recoveredBlob);
};
xhr.open('GET', blobUrl);
xhr.send();
data: URLs are probably not what you mean by "normal" and can be problematically large. However they do work like normal URLs in that they can be shared; they're not specific to the current browser or session.
another way to create a data url from blob url may be using canvas.
var canvas = document.createElement("canvas")
var context = canvas.getContext("2d")
context.drawImage(img, 0, 0) // i assume that img.src is your blob url
var dataurl = canvas.toDataURL("your prefer type", your prefer quality)
as what i saw in mdn, canvas.toDataURL is supported well by browsers. (except ie<9, always ie<9)
For those who came here looking for a way to download a blob url video / audio, this answer worked for me. In short, you would need to find an *.m3u8 file on the desired web page through Chrome -> Network tab and paste it into a VLC player.
Another guide shows you how to save a stream with the VLC Player.
UPDATE:
An alternative way of downloading the videos from a blob url is by using the mass downloader and joining the files together.
Download Videos Part
Open network tab in chrome dev tools
Reload the webpage
Filter .m3u8 files
Look through all filtered files and find the playlist of the '.ts' files. It should look something like this:
You need to extract those links somehow. Either download and edit the file manually OR use any other method you like. As you can see, those links are very similar, the only thing that differs is the serial number of the video: 's-0-v1-a1.ts', 's-1-v1-a1.ts' etc.
https://some-website.net/del/8cf.m3u8/s-0-v1-a1.ts
https://some-website.net/del/8cf.m3u8/s-1-v1-a1.ts
https://some-website.net/del/8cf.m3u8/s-2-v1-a1.ts
and so on up to the last link in the .m3u8 playlist file. These .ts files are actually your video. You need to download all of them.
For bulk downloading I prefer using the Simple Mass Downloader extension for Chrome (https://chrome.google.com/webstore/detail/simple-mass-downloader/abdkkegmcbiomijcbdaodaflgehfffed)
If you opt in for the Simple Mass Downloader, you need to:
a. Select a Pattern URL
b. Enter your link in the address field with only one modification: that part of the link that is changing for each next video needs to be replaced with the pattern in square brackets [0:400] where 0 is the first file name and 400 is the last one. So your link should look something like this https://some-website.net/del/8cf.m3u8/s-[0:400]-v1-a1.ts.
Afterwards hit the Import button to add these links into the Download List of Mass Downloader.
c. The next action may ask you for the destination folder for EACH video you download. So it is highly recommended to specify the default download folder in Chrome Settings and disable the Select Destination option in Chrome Settings as well. This will save you a lot of time! Additionally you may want you specify the folder where these files will go to:
c1. Click on Select All checkbox to select all files from the Download List.
c2. Click on the Download button in the bottom right corner of the SMD extension window. It will take you to next tab to start downloading
c3. Hit Start selected. This will download all vids automatically into the download folder.
That is it! Simply wait till all files are downloaded and you can watch them via the VLC Player or any other player that supports the .ts format. However, if you want to have one video instead of those you have downloaded, you need to join all these mini-videos together
Joining Videos Part
Since I am working on Mac, I am not aware of how you would do this on Windows. If you are the Windows user and you want to merge the videos, feel free to google for the windows solution. The next steps are applicable for Mac only.
Open Terminal in the folder you want the new video to be saved in
Type: cat and hit space
Open the folder where you downloaded your .ts video. Select all .ts videos that you want to join (use your mouse or cmd+A)
Drag and drop them into the terminal
Hit space
Hit >
Hit Space
Type the name of the new video, e.g. my_new_video.ts. Please note that the format has to be the same as in the original videos, otherwise it will take long time to convert and even may fail!
Hit Enter. Wait for the terminal to finish the joining process and enjoy watching your video!
Found this answer here and wanted to reference it as it appear much cleaner than the accepted answer:
function blobToDataURL(blob, callback) {
var fileReader = new FileReader();
fileReader.onload = function(e) {callback(e.target.result);}
fileReader.readAsDataURL(blob);
}
I'm very late to the party.
If you want to download the content you can simply use fetch now
fetch(blobURL)
.then(res => res.blob())
.then(blob => /*do what you want with the blob here*/)
Here the solution:
let blob = new Blob(chunks, { 'type' : 'video/mp4;' });
let videoURL = window.URL.createObjectURL(blob);
const blobF = await fetch(videoURL).then(res => res.blob())
As the previous answer have said, there is no way to decode it back to url, even when you try to see it from the chrome devtools panel, the url may be still encoded as blob.
However, it's possible to get the data, another way to obtain the data is to put it into an anchor and directly download it.
<a href="blob:http://example.com/xxxx-xxxx-xxxx-xxxx" download>download</a>
Insert this to the page containing blob url and click the button, you get the content.
Another way is to intercept the ajax call via a proxy server, then you could view the true image url.

How to embed a Base64 encoded PDF data URI into a HTML 5 `<object>` data attribute?

So in my application, users have the option to upload a file to an <input type = "file" id = "my-file"> (HTML 5 File input). I can subsequently grab this file using the following Javascript:
var files = $("#my-file").files;
var file = files[0];
Can I somehow use this var file as the data in an <object> tag? Here is an example of an object tag where the PDF is grabbed by hitting a URL and contacting the server.
<object data="data/test.pdf" /*<-- I want the var file here */ type="application/pdf" width="300" height="200">
</object>
How can I accomplish this? Please note, I must support Internet Explorer 11.
UPDATE:
I've tried something that ultimately ended in failure. I converted the PDF to a data-uri using FileReader and then used that in the data attribute of the <object> tag, which renders perfectly in Chrome but not at all in Internet explorer.
var files = $(e.currentTarget.files);
file = files[0];
var reader = new FileReader();
reader.onloadend = function() {
var data = reader.result;
console.log(data);
$("#content").prepend('<object id="objPdf" data="'+data+'" type="application/pdf"></object>');
};
reader.readAsDataURL(file);
Where the reader data comes out looking like:
data:application/pdf;base64,JVBERi0xLjQKJe...
Here is the reason PDF's don't work with this approach in Internet Explorer (images only)...so sad :(
UPDATE 2:
Tried pdf.js with some success...it displays the PDF on the page, although it is incredibly slow (5 seconds for a single page) in Internet Explorer 11. It also only displays one page at a time, whereas the <object> tag displayed all pages instantly in a nice scrollable container. While this is an extreme fallback option, I'd like to not go down this route.
Anyone have any other idea as to how I can preview the PDF's that the user uploads directly in the webpage?
Yes I got it working with a file...
HTML:
<object id="pdf" data="" type="application/pdf"></object>
Javascript (Jquery)
var fr = new FileReader();
fr.onload = function(evtLoaded) {
$('#pdf').attr('data', evtLoaded.target.result );
};
fr.readAsDataURL(inFile);
Compared to your approach I use the 'I am done reading the file'-callback differently by using the event.
If I got it right: 'loadend' will always be called no matter if reading succeeded or failed.
Appendix on 04AUG2021:
#Adil talks about a plural of PDFs. Well, this solution talks about 1, I never tried several PDFs. Since the solution is made by an html 'id' we know that the 'id' comes with a singleton pattern per page. Nevertheless, I am convinced it is doable with severla PDFs per page somehow with whatever use case one creates.
#netotz I did not investigate here. It just comes to my mind that it could come to issues regarding the hardware. You do not mention the browser where it happens nor any operating system internals... I just guess (though I can be wrong) that 1.8 MB is rather a small amount of data...
In addition to the above answer and the comments from Dirk's answer.(Since I don't have 50 reputation to comment directly)
I want to point out that there is a way to prevent the issue that
<object> has an issue rendering base64 data directly when the file size is bigger than 1mb.
Steps:
Convert base64 to blob
Create URL from blob
use the URL as the data's src
A good post can be found here: an answer for another pdf lib. But this also suitable for our case.

First Chrome Extension

I have a page one my website that I'd like to emulate into a 'newtab' chrome extension.
I want to make something similar to this:
https://chrome.google.com/webstore/detail/animatedtabs-a-new-gif-on/kenhfdoiondldpcoajdbackbnmehgahl?hl=en
I have a lot of information on my server that i'd like to be able to serve as a chrome newtab extension - and this extension above loads a different GIF everytime a new tab is opened - how do they get data from their server? I don't seem to be able to find a way to do this.
I found the answer, for anyone wondering. The answer came in the form of this page:
https://developer.chrome.com/extensions/xhr
Which basically states that if you put a URL into the 'permissions' field in the manifest.json file, you can then access that URL in your application using the JS found on that page, eg:
var xhr = new XMLHttpRequest();
xhr.open("GET", "http://api.example.com/data.json", true);
xhr.onreadystatechange = function() {
if (xhr.readyState == 4) {
// innerText does not let the attacker inject HTML elements.
document.getElementById("resp").innerText = xhr.responseText;
}
}
xhr.send();
I've now got my chrome extension working perfectly!

How to make browser download json returned from rest service

The frameworks i'm using are AngularJS(client-side) and Sitebricks(server-side).
I want to return a list of objects into json and make the client download that json by prompting the user where he wants to save the file.
I accept all kind of propositions.
Cheers in advance.
It sounds like you want to create the file in the browser, and save it to the user's local machine. One method is presented here, but it doesn't work in IE Javascript: Create and save file
Here's a fiddle: http://jsfiddle.net/vUdyD/
$(function() {
function exportToCsv() {
var myCsv = "Col1,Col2,Col3\nval1,val2,val3";
window.open('data:text/csv;charset=utf-8,' + escape(myCsv));
}
var button = document.getElementById('b');
button.addEventListener('click', exportToCsv);
});
You could use the HTML5 download attribute. It is not perfect and browser support
<!-- will download as "expenses.pdf" -->
Download Your Expense Report
Demo: http://davidwalsh.name/download-attribute
Support: http://caniuse.com/#feat=download