Print a form from another link on clicking a button - javascript

I'm trying to figure out how to print an image from another web page link on clicking a button.
I know window.print() but how could I specify the other link I want to print the image from?

Same domain
If the page you wish to print is from the same domain as the iframe's parent then MDN has a good example of how to do this.
You should create a hidden iframe, load your page in it, print the iframe contents and then remove the iframe.
JavaScript:
function printURL( url ) {
var frame = document.createElement( "iframe" );
frame.onload = printFrame;
frame.style.display = 'none';
frame.src = url;
document.body.appendChild(frame);
return false;
}
function printFrame() {
this.contentWindow.__container__ = this;
this.contentWindow.onbeforeunload = closeFrame;
this.contentWindow.onafterprint = closeFrame;
this.contentWindow.focus(); // Required for IE
this.contentWindow.print();
}
function closeFrame () {
document.body.removeChild(this.__container__);
}
HTML:
<button onclick="printURL('page.html');">Print external page!</button>
Cross domain
If the page you wish to print is from another domain then your browser will throw a Same-Origin Policy error. This is a security feature that forbids scripts accessing some data from different domains.
To print cross domain content you will need to scrape the page's source and load it into the iframe. The browser will then believe that the iframe's content comes from your domain and won't hiccough when you try to print.
However, if you try to do this in the frontend, this just pushes the problem back one step further, as the same-origin policy also won't let you scrape content from another domain in this way. But the same-origin policy for data scraping is the equivalent of tying a bull up with cotton thread - it doesn't really hold you back - so this hurdle is easily circumvented. You can either write your own backend script (in PHP or your choice of language) that will scrape the content and deliver it to your page, or you can use any one of a number of web services that already do this. https://multiverso.me/AllOrigins/ is as good as any, it doesn't require backend programming, and it's free so I'll use that in this example.
Using Jquery, the modified printURL function from above would be:
function printURL( url ) {
var jsonUrl = 'http://allorigins.me/get?url=' + encodeURIComponent(url) + '&callback=?';
// the url / php function that allows content to be scraped from different origins.
$.getJSON( jsonUrl, function( data ) {
// get the scraped content in data.content
var frame = document.createElement( "iframe" ),
iframedoc = frame.contentDocument || frame.contentWindow.document;
frame.onload = printFrame;
frame.style.display = 'none';
iframedoc.body.html( data.contents );
document.body.appendChild(frame);
}
return false;
}
The other functions from above would remain the same.
Note that if the page you're printing is built using AJAX calls or is significantly styled with scripting then the iframe may print something that looks quite unlike what you were expecting.

Related

Improve page load latency in chrome extension

I'm building a google chrome extension that looks at a webpage, does some calculations based on features of the page, and then loads an iFrame to display the results. Currently, I am working on trying to create a more accessible version for visually-impared users. I have an option in my options page to allow users to click if they want to use the visually accessible option, and then that information is stored as a boolean in browser storage. The issue is, I have to check for that boolean in storage every single time I load the iFrame (which is every time the page switches or refreshes), and it adds roughly 500ms of latency to the iFrame load.
I have tried using both chrome.storage.sync, and localStorage (from background, with message passing to my content script) to see if the synchronous version would be a bit faster, but they both add roughly 500ms to the running of my content script. Right now I have two different html files, the standard one and the visually accessible one, and the content script chooses which to load based on retrieving the accessibility boolean from storage. If there is a faster way to just programmatically switch the css that the standard html file loads, I could do that as well. The thing is, any way I figure it, I just can't seem to think of a way to avoid having to retrieve the boolean from storage every single time the iFrame loads.
I suppose I'm wondering if there is some other way around this, like if I could somehow direct the extension to just automatically use a certain version of the html based on which option the user selects when they install the extension. Any suggestions would be greatly appreciated.
Here is the function in question (from content script):
function insertFrame(){
var extensionOrigin = 'chrome-extension://' + chrome.runtime.id;
if (!location.ancestorOrigins.contains(extensionOrigin)) {
chrome.runtime.sendMessage({contentScriptQuery: "accessible?"}, function(response){
var accessible = response;
if(accessible === "true"){
//load the accessible frame
var iframe = document.createElement('iframe');
iframe.id = "myFrame";
iframe.src = chrome.runtime.getURL('accessible.html');
document.body.appendChild(iframe);
}else{
//load the regular frame
var iframe = document.createElement('iframe');
iframe.id = "myFrame";
iframe.src = chrome.runtime.getURL('popup.html');
document.body.appendChild(iframe);
}
console.log("Time to run content script:", Date.now() - timer);
});
}
//for testing purposes
// var extensionOrigin = 'chrome-extension://' + chrome.runtime.id;
// if (!location.ancestorOrigins.contains(extensionOrigin)) {
// var iframe = document.createElement('iframe');
// iframe.id = "myFrame";
// iframe.src = chrome.runtime.getURL('popup.html');
// document.body.appendChild(iframe);
// }
// console.log("Time to run content script:", Date.now() - startTime);
}
And in my background page:
chrome.runtime.onMessage.addListener(function (request, sender, sendResponse){
if(request.contentScriptQuery === 'accessible?'){
var a = localStorage.getItem('accessible');
sendResponse(a);
}
return true; //with or without this line, timing is the same
});
I've been testing the content script just by commenting out each half (with and without reading from storage). You can see the line where I am logging how many milliseconds have elapsed since the content script started running. I have also verified this latency by testing load times in the network panel of dev tools. I get an average of 6.33s for load time without reading storage, and 6.72s with reading storage, which confirms the timing discrepancy I am logging in my content script. The only thing I am changing between tests are commenting out the half of the function so that I can test the other half.

Get the height of a same domain iframe when that iframe is inside a different domain iframe?

I have a site which has a media player embedded inside an iframe. The media player and the site are on the same domain, preventing cross-origin issues. Each page, the main page as well as the media player page, have a bit of code which finds the height and width of any parent iframe:
var height = $(parent.window).height();
var width = $(parent.window).width();
No problems so far....until:
A client wants to embed my site inside an iframe on his own site. His site is on a different domain. Now, my iframe is inside another iframe and my code is throwing cross-origin errors.
The following does not throw errors:
var test1 = parent.window; // returns my site
var test2 = window.top; // returns client site
The following does throw cross-origin errors:
var test3 = parent.window.document;
var test4 = $(parent.window);
var test5 = window.top.document;
var test6 = $(window.top);
How do I get the height of the iframe on my domain without the cross-origin errors? I'm hoping for a pure javascript/jQuery solution.
Options which will not work for my solution are:
Using document.domain to white list the site.
Modifying the web.config to white list the site.
Like in Inception, I must go deeper. Please help.
You will need to use Javascript's messager. First, you need to define a function like this:
function myReceiver(event) {
//Do something
}
Then you need an event listener:
window.addEventListener("message", myReceiver);
You will need something like this on both sides. Now, you can send a message like this to the iframe:
innerWindow.contentWindow.postMessage({ message: {ResponseKey: "your response key", info1: "something1", info2: "something2"}}, innerWindow.src)
and this is how you can send a message to the parent:
window.parent.postMessage({ message: {ResponseKey: "your response key", info1: "something1", info2: "something2"}}, myorigin);
The only missing item in the puzzle is myorigin. You will be able to find it out in your iframe using event.origin || event.originalEvent.origin in the message receiver event.
However, the pages using your site in their pages inside an iframe will have to include a Javascript library which will handle the communication you need. I know how painful is this research, I have spent days when I have done it before to find out the answer.
Your code is running from the iframe in the middle of the parent and the child window. So, anytime you call
window.parent
and your site is embedded inside an iframe and the parent is a different domain (Same origin policy), an error will be thrown. I would recommend first checking if the parent is the same origin. You need to wrap this check in a try catch.
NOTE: Most browsers, but not Edge, will not throw an error if the parent is http://localhost:xxx and the iframe is http://localhost:zzz where xxx is a different port number than zzz. So, you also need to manually check the origins match by comparing the protocol, domain, and port.
var isEmbeddedInCrossOriginIframe = false;
try {
var originSelf = (window.self.location.protocol + '//' +
window.self.location.hostname +
(window.self.location.port ? ':' +
window.self.location.port : '')).toLowerCase();
var originParentOrSelf = (window.parent.location.protocol + '//' +
window.parent.location.hostname +
(window.parent.location.port ? ':' +
window.parent.location.port : '')).toLowerCase();
isEmbeddedInCrossOriginIframe = originSelf != originParentOrSelf;
}
catch(err) {
isEmbeddedInCrossOriginIframe = true;
//console.log(err);
}
Your solution will then be:
var height = $(isEmbeddedInCrossOriginIframe ? window : parent.window)
.height();
var width = $(isEmbeddedInCrossOriginIframe ? window : parent.window)
.width();

How to reference HTML from external webpage

I apologize in advance for the rudimentary question.
I have web page A that has a link to web page B on it. I need to locate the link to web page B (easy enough), and then store the HTML from web page B in a variable in my javascript script.
To store the HTML from web page A, I know it's a simple:
html_A = document.body.innerHTML;
How do I store the HTML from web page B? I believe I need to use AJAX correct? Or can I do it with javascript? And if it's the former, let's just assume the server for web page B allows for it.
Thank you in advance!
If youre trying to load HTML from a website that resides on a different server you will get a Cross-Origin Request Blocked Error. I dealt with this in the past and found a way to do it using YQL. Try it out:
//This code is located on Website A
$(document).ready(function() {
var websiteB_url = 'http://www.somewebsite.com/page.html';
var yql = '//query.yahooapis.com/v1/public/yql?q=' + encodeURIComponent('select * from html where url="' + websiteB_url + '"') + '&format=xml&callback=?';
$.getJSON(yql, function(data) {
function filterDataCUSTOM(data) {
data = data.replace(/<?\/body[^>]*>/g, '');// no body tags
data = data.replace(/[\r|\n]+/g, ''); // no linebreaks
return data;
}
if (data.results[0]) {
var res = filterDataCUSTOM(data.results[0]);
$("div#results").html(res);
} else {
console.log("Error: Could not load the page.");
}
});
});
This is only possible if web page B is on the same domain due to the same-origin policy security feature of all major browsers.
If both pages are on the same domain you could do
$.get("/uri/to/webpage/b").then(function(html) {
//do something with the html;
});
Note that the html will be available only once the ajax request finishes inside the .then(...) function. It will NOT be available on the line after this code block.
Hard to tell without knowing more about your situation but this is rarely the correct thing to do. You might want to look into $.fn.load() (is limited by SOP) or using iframes (is not limited by SOP) as one of these might be more appropriate.
I should note that the standard way of doing this when you need access to html from another domain is to pull it down on your webserver and then re-serve it from there. That being said, it is probably a violation of that website's terms of use.

detect XHR on a page using javascript

I want to develop a Chrome extension, just imagine when Facebook loads you are allowed to add extra JS on it.
But my problem is I can't modify the DOM of the later content, which means the newly loaded content that appear when the user scrolled down.
So I want to detect XHR using JavaScript.
I tried
send = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.send = function() {
/* Wrap onreadystaechange callback */
var callback = this.onreadystatechange;
this.onreadystatechange = function() {
if (this.readyState == 4) {
/* We are in response; do something, like logging or anything you want */
alert('test');
}
callback.apply(this, arguments);
}
_send.apply(this, arguments);
}
But this is not working.. any ideas?
Besides Arun's correct remark that you should use _send for both, your approach doesn't work because of how Content Scripts work.
The code running in the content script works in an isolated environment, to prevent it from conflicting with page's own code. So it's not like you described - you're not simply adding JS to the page, you have it run isolated. As a result, your XHR replacement only affects XHR calls from your extension's content scripts and not the page.
It's possible to inject the code into the page itself. This will affect XHR's from the page, but might not work on all pages, if the Content Security Policy of the page in question disallows inline code. It seems like Facebook's CSP would allow this. Page's CSP should not be a problem according to the docs. So, this approach should work, see the question I linked.
That said, you're not specifically looking for AJAX calls, you're looking for new elements being inserted in the DOM. You can detect that without modifying the page's code, using DOM MutationObservers.
See this answer for more information.
to detect AJAX calls on a webpage you have to inject the code directly in that page and then call the .ajaxStart or .ajaxSuccess
Example:
// To Successfully Intercept AJAX calls, we had to embed the script directly in the Notifications page
var injectedCode = '(' + function() {
$('body').ajaxSuccess(function(evt, request, settings) {
if (evt.delegateTarget.baseURI == 'URL to check against if you want') {
// do your stuff
}
});
} + ')();';
// Inserting the script into the page
var script = document.createElement('script');
script.textContent = injectedCode;
(document.head || document.documentElement).appendChild(script);
script.parentNode.removeChild(script);

Making a Same Domain iframe Secure

tl;dr Can I execute un-trusted scripts on an iframe safely?
Back story:
I'm trying to make secure JSONP requests. A lot of older browsers do not support Web Workers which means that the current solution I came up with is not optimal.
I figured I could create an <iframe> and load a script inside it. That script would perform a JSONP request (creating a script tag), which would post a message to the main page. The main page would get the message, execute the callback and destroy the iframe. I've managed to do this sort of thing.
function jsonp(url, data, callback) {
var iframe = document.createElement("iframe");
iframe.style.display = "none";
document.body.appendChild(iframe);
var iframedoc = iframe.contentDocument || iframe.contentWindow.document;
sc = document.createElement("script");
sc.textContent = "(function(p){ cb = function(result){p.postMessage(result,'http://fiddle.jshell.net');};})(parent);";
//sc.textContent += "alert(cb)";
iframedoc.body.appendChild(sc);
var jr = document.createElement("script");
var getParams = ""; // serialize the GET parameters
for (var i in data) {
getParams += "&" + i + "=" + data[i];
}
jr.src = url + "?callback=cb" + getParams;
iframedoc.body.appendChild(jr);
window.onmessage = function (e) {
callback(e.data);
document.body.removeChild(iframe);
}
}
jsonp("http://jsfiddle.net/echo/jsonp/", {
foo: "bar"
}, function (result) {
alert("Result: " + JSON.stringify(result));
});
The problem is that since the iframes are on the same domain, the injected script still has access to the external scope through .top or .parent and such.
Is there any way to create an iframe that can not access data on the parent scope?
I want to create an iframe where scripts added through script tags will not be able to access variables on the parent window (and the DOM). I tried stuff like top=parent=null but I'm really not sure that's enough, there might be other workarounds. I tried running a for... in loop, but my function stopped working and I was unable to find out why.
NOTE:
I know optimally WebWorkers are a better isolated environment. I know JSONP is a "bad" technique (I even had some random guy tell me he'd never use it today). I'm trying to create a secure environment for scenarios where you have to perform JSONP queries.
You can't really delete the references, setting null will just silently fail and there is always a way to get the reference to the parent dom.
References like frameElement and frameElement.defaultView etc. cannot be deleted. Attempting to do so will either silently fail or throw exception depending on browser.
You could look into Caja/Cajita though.
tl;dr no
Any untrusted script can steal cookies (like a session id!) or read information from the DOM like the value of a credit card input field.
JavaScript relies on the security model that all code is trusted code. Any attempts at access from another domain requires explicit whitelisting.
If you want to sandbox your iframe you can serve the page from another domain. This does mean that you can't share a session or do any kind of communication because it can be abused. It's just like including an unrelated website. Even then there are possibilities for abuse if you allow untrusted JavaScript. You can for instance do: window.top.location.href = 'http://my.phishing.domain/';, the user might not notice the redirect.

Categories

Resources