Impossible to load an iframe inside the background page (status=canceled) - iframe

I want to dynamicaly inject and load an iframe inside the background page. But every time, the request is canceled.
http://i.imgur.com/Puto33c.png
That used to work a week ago. I don't know where I'm wrong.
To reproduce this issue, I created a small extension :
manifest.js :
{
"name": "iframe background",
"version": "1.0.0",
"manifest_version": 2,
"browser_action": {
"default_title": "iframe"
},
"background": {
"persistent": false,
"scripts": ["background.js"]
}
}
background.js :
chrome.browserAction.onClicked.addListener(function() {
var iframe = document.createElement('iframe');
iframe.src = 'http://localhost:3000/';
iframe.onload = function() {
console.log(iframe.contentDocument); // return null
};
document.body.appendChild(iframe);
});
The page to load is not blocked by X-Frame-Options SAMEORIGIN.
I tried to put the iframe directly within a HTML background page with no luck.
I also tried to add an content_security_policy :
"content_security_policy": "script-src 'self'; object-src 'self'; frame-src 'self' http://localhost:3000/"
But the iframe still doesn't load.
Does someone has a workaround or a solution to this problem?
Thanks !

Chrome 58.0.3014.0 enables Site Isolation for extensions by default that makes the iframe load in a different renderer process handled by a separate chrome.exe OS process.
The 'canceled' message means that the extension's chrome.exe process canceled the request and it was handled by a different hidden chrome.exe process.
The correct approach is to declare a content script that will automatically run on the iframe URL and communicate to the background page. Note: only JSON-fiable data may be passed, in other words, you can pass innerHTML but not DOM elements. This is easy to handle though via DOMParser.
manifest.json additions:
"content_scripts": [{
"matches": ["http://localhost:3000/*"],
"js": ["iframe.js"],
"run_at": "document_end",
"all_frames": true
}],
iframe.js:
var port = chrome.runtime.connect();
// send something immediately
port.postMessage({html: document.documentElement.innerHTML});
// process any further messages from the background page
port.onMessage.addListener(msg => {
..............
// reply
port.postMessage(anyJSONfiableObject); // not DOM elements!
});
background.js:
var iframePort;
chrome.browserAction.onClicked.addListener(() => {
document.body.insertAdjacentHTML('beforeend',
'<iframe src="http://localhost:3000/"></iframe>');
});
chrome.runtime.onConnect.addListener(port => {
// save in a global variable to access it later from other functions
iframePort = port;
port.onMessage.addListener(msg => {
if (msg.html) {
const doc = new DOMParser().parseFromString(msg.html, 'text/html');
console.log(doc);
alert('Received HTML from the iframe, see the console');
}
});
});
See also a similar QA: content.js in iframe from chrome-extension popup

Related

Can I get the full-control of an cross-origin iframe by proxy the iframe on my server?

Here I need to implant an iframe in my site, and I want to inject some script or css to change the iframe, because of the iframe is not same-origin with my site, so I set a proxy makes it same-origin(localhost:4000).
When I try to get the element from the iframe, an error occured in the console which doesn't meet my expectation.
My site was hosted on localhost:4000 too.
Any idea about this?
Error details>>
<iframe name="editorFrame" id="editorId" src="http://localhost:4000/iframe"></iframe>
// error was raised on
let iframeHead = iframeWin.document.querySelector("head") as HTMLElement;
// the server listens on localhost:4000
// the proxy code
app.use(path, function(req, res) {
return proxy({
target: "http://47.75.177.99",
pathRewrite: {
"^/iframe": "/",
},
onProxyReq(proxyReq, req, res) {
proxyReq.setHeader(
"Cookie",
"tableau_locale=zh; workgroup_session_id=W2fKwkFeSzeC76d0hAAPmA|9vpJTvdHXW9VhaRrMLtwDzIazGYSgCIq; XSRF-TOKEN=NeRc6YfknJ5Vgj03xFwTQ30mftd1Jqlt"
);
},
changeOrigin: true
})(...arguments);
});

How to embed youtube channel page using iframe in my extension?

I'm creating extension to organize youtube channels using tags. It has angular frontend with url like this
moz-extension://f78b3bd9-a210-41c5-9d8d-9b7ab3717f6e/index.html#/channel/UCtinbF-Q-fVthA0qrFQTgXQ
And I want to embed channel's page using iframe, but security policies doesn't allow me to do that.
Load denied by X-Frame-Options: https://www.youtube.com/ does not permit cross-origin framing.
So I tried to modify X-Frame-Options, but it doesn't change anything(headers aren't added).
What I did:
1 Added permissions to manifest.json:
"webRequest",
"://.youtube.com/",
"://www.youtube.com/*"
2 Wrote some code in background.js
function addFramePermissions(e) {
console.log("Loading url: " + e.url);
var allowedHeaders = [];
for (var header of e.responseHeaders) {
if (header.name.toLowerCase() !== "x-frame-options") {
allowedHeaders.push(header);
} else {
console.log('x-frame-options found!!!');
}
}
e.responseHeaders = allowedHeaders;
return { responseHeaders: e.responseHeaders };
}
browser.webRequest.onHeadersReceived.addListener(
addFramePermissions,
{
urls: [
"*://*.youtube.com/*",
"*://youtube.com/*"
]
},
["blocking", "responseHeaders"]
);
Code reaches function and I can see "x-frame-options found!!!" in console, but firefox's Network Monitor shows that x-frame-options exists with value SAMEORIGIN
I ran my extension in Chrome and Chrome said that I forgot to add "webRequestBlocking" in permissions. Thanks, Chrome!

ASP.NET Route config for Backbone Routes with PushState

I have run into an issue recently where we have been told to remove the hash symbols from our Backbone applications. This presents two problems: (a) the ASP.NET routes need to handle any remotely linked URL (currently this is no problem with the hash symbols) so that we're not hitting a 404 error and (b) the proper route needs to be preserved and passed on to the client side (Backbone) application. We're currently using ASP.NET MVC5 and Web API 2 for our backend.
The setup
For an example (and test project), I've created a test project with Backbone - a simple C# ASP.NET MVC5 Web Application. It is pretty simple (here is a copy of the index.cshtml file, please ignore what is commented out as they'll be explained next):
<script type="text/javascript">
$(document).ready(function(event) {
Backbone.history.start({
//pushState: true,
//root: "/Home/Index/"
});
var Route = Backbone.Router.extend({
routes: {
"test/:id": function (event) {
$(".row").html("Hello, " + event);
},
"help": function () {
alert("help!");
}
}
});
var appRouter = new Route();
//appRouter.navigate("/test/sometext", { trigger: true });
//appRouter.navigate("/help", { trigger: true });
});
</script>
<div class="jumbotron">
<h3>Backbone PushState Test</h3>
</div>
<div class="row"></div>
Now, without pushState enabled I have no issue remote linking to this route, ie http://localhost/Home/Index#test/sometext
The result of which is that the div with a class of .row is now "Hello, sometext".
The problem
Enabling pushState will allow us to replace that pesky # in the URL with a /, ie: http://localhost/Home/Index/test/sometext. We can use the Backbone method of router.navigate("url", true); (as well as other methods) to use adjust the URL manually. However, this does not solve the problem of remote linking. So, when trying to access http://localhost/Home/Index/test/sample you just end up with the typical 404.0 error served by IIS. so, I assume that it is handled in in the RouteConfig.cs file - inside, I add a "CatchAll" route:
routes.MapRoute(
name: "CatchAll",
url: "{*clientRoute}",
defaults: new { controller = "Home", action = "Index" }
);
I also uncomment out the pushState and root attributes in the Backbone.history.start(); method:
Backbone.history.start({
pushState: true,
root: "/Home/Index/"
});
var Route = Backbone.Router.extend({
routes: {
"test/:id": function (event) {
$(".row").html("Hello, " + event);
},
"help": function () {
alert("help!");
}
}
});
var appRouter = new Route();
//appRouter.navigate("/test/sometext", { trigger: true });
//appRouter.navigate("/help", { trigger: true });
This allows me to at least let get past the 404.0 page when linking to these routes - which is good. However, none of the routes actually "trigger" when I head to them. After attempting to debug them in Chrome, Firefox, and IE11 I notice that none of the events fire. However, if I manually navigate to them using appRouter.navigate("/help", { trigger: true }); the routes are caught and events fired.
I'm at a loss at this point as to where I should start troubleshooting next. I've placed my Javascript inside of the $(document).ready() event as well as the window.onload event also (as well as not inside of an event); none of these correct the issue. Can anyone offer advice on where to look next?
You simply have to move Backbone.history.start after the "new Route" line.
var Route = Backbone.Router.extend({
routes: {
"test/:id": function (event) {
$(".row").html("Hello, " + event);
},
"help": function () {
alert("help!");
}
}
});
var appRouter = new Route();
Backbone.history.start({
pushState: true,
root: "/Home/Index/"
});
Make sure you go to ".../Home/Index/help". If it doesn't work, try temporarily removing the root and go to ".../help" to see if the root is the problem.
If you still have troubles, set a js breakpoint in Backbone.History.loadUrl on the "return" line. It is called from the final line of History.start to execute the current browser url on page load. "this.matchRoot()" must pass then, "fragment" is matched against each "route" or regexp string in "this.handlers". You can see why or why not the browser url matches the route regexps.
To set to the js breakpoint, press F12 in the browser to open the dev console, press Ctrl-O or Ctrl-P to open a js file, then type the name of the backbone js file. Then search for "loadUrl:". You can also search for "Router =" to find the start of the router class definition (same as for "View =" and "Model =" to find the backbone view/model implementation code). I find it quite useful to look at the backbone code when I have a question like this. It is surprisingly readable and what better place to get answers?
If your js files happen to be minified/compressed, preferably turn this off. Alternately you can try the browser unminify option. In Chrome this is the "{}" button or "pretty print". Then the js code is not all on 1 line and you can set breakpoints. But the function and variable names may still be mangled.
I have solved my own problem using what feels to be "hackish", via the following. If anyone can submit a better response it would be appreciated!
My Solution:
I globally override the default Backbone.Router.intilaize method (it is empty) with the following:
$(document).ready(function (event) {
var _root = "/Home/Index/";
_.extend(Backbone.Router.prototype, {
initialize: function () {
/* check for route & navigate to it */
var pathName = window.location.pathname;
var route = pathName.split(_root)[1];
if (route != undefined && route != "") {
route = "/" + route;
this.navigate("", { trigger: false });
this.navigate(route, { trigger: true });
}
}
});
});

browser-sync for SPA: how to sync document fragments: #/contact

I'm developing a SPA (Single Page Application) and use grunt-browsers-sync.
All browser-sync features seem to work: CSS injection, scrolling and form synchronization.
It's a SPA so no navigation to other pages in done. Navigation is done via routes in the document fragment (I use the SammyJs library for this)
mysite.com/#/home
mysite.com/#/contact
mysite.com/#/...
It seems BrowserSync doesn't synchronizes document fragments.
I think it's because document fragments are handled by the browser and not requested at the BrowserSync server/proxy.
Is there some way to make the scenario work?
PS: I have a javascript callback when navigating, which I can use to send the new url to BrowserSync at development (if BrowserSync supports something like that)
I also tried using browser-sync for a single-page backbone application.
Routes changes are basically triggered on clicking anchors. Unfortunately, browser-sync doesn't play well with events having stopPropagation & hence, the click wasn't triggered in other browsers and routes were synced.
Since then I've forked and fixed this plus other issues namely event syncing for mouseup, mousedown, keyup, keydown and contenteditable div.
The pull-request is still pending though, so meanwhile you can use browser-sync-client from https://github.com/nitinsurana/browser-sync-client
You'll need to have configuration as follows for the fixes to take effect . Notice the capture, contenteditable, mouseup and other config options not present in browser-sync
var bs = require("browser-sync").create();
var client = require("./");
client["plugin:name"] = "client:script";
bs.use(client);
bs.init({
//server: {
// baseDir: ["test/fixtures"]
//},
proxy: 'http://localhost:8080',
open: false,
minify: false,
snippetOptions: {
rule: {
//match: /SHNAE/,
match: /<\/head>/i,
fn: function (snippet) {
return snippet + "\n</head>";
}
}
},
clientEvents: [
"scroll",
"input:text",
"input:toggles",
"input:keydown",
"input:keypress",
"form:submit",
"form:reset",
"click",
"contenteditable:input",
"mouseup",
"mousedown",
"select:change"
],
ghostMode: {
clicks: true,
scroll: true,
forms: {
submit: true,
inputs: true,
toggles: true,
keypress: true,
keydown: true,
contenteditable: true,
change: true
},
mouseup: true,
mousedown: true
},
capture:true
});

Chrome extension not injecting Javascript into iframe

I have a contentscript that essentially does a console.log to indicate that it has been injected into a page, and my manifest.json has all_frames set to true.
As a result, if I go to http://www.reddit.com/r/videos I see a message per frame and if I click on a video, I will also see a message coming from a script injected into the video's iframe. This indicates to me that if the page is dynamically modified to include an iframe, the contentscript will be injected into it.
When I go to http://www.html5video.org, I only get a message from one frame, but if I look at the DOM I see that there is an iframe for the video so I would expect my contentscript to be injected into it, but it is not.
My ultimate goal is to get a selector for the video on the page and I would expect to be able to do so by injecting code that looks for it into the iframe.
Help is appreciated.
I suspect that Chrome will inject your content scripts into an IFRAME that is part of the original page source which is the case with the reddit.com example - the IFRAMEs are part of the original page so Chrome can and will inject into those. For the html5video link the IFRAME is not part of the original source. However, if you inspect the elements you can see the IFRAME which suggests to me that the IFRAME has been dynamically loaded to the DOM. I see the same behaviour with an extension I have written so it seems consistent.
If you need to inject into the IFRAME then perhaps you can hook the DOM creation event and take the action you require:
document.addEventListener('DOMNodeInserted', onNodeInserted, false);
UPDATE:
What about this for http://html5video.org/ - using the following content_script code I can get the IFRAME and then VIDEO tag. Note: This approach/concept should also work pretty well too for Reddit.
content_script.js
console.log("content script for: " + document.title);
var timer;
document.addEventListener('DOMNodeInserted', onNodeInserted, false);
function onNodeInserted(e)
{
if(timer) clearTimeout(timer);
timer = setTimeout("doSomething()", 250);
}
function doSomething()
{
$media = $(".mwEmbedKalturaIframe");
console.log($media);
$video = $media.contents().find("video");
console.log($video);
}
manifest.json
{
// Required
"name": "Foo Extension",
"version": "0.0.1",
// Recommended
"description": "A plain text description",
"icons": { "48": "foo.png" },
//"default_locale": "en",
// Pick one (or none)
"browser_action": {
"default_icon": "Foo.png", // optional
"default_title": "Foo Extension" // optional; shown in tooltip
},
"permissions": [ "http://*/", "https://*/", "tabs" ],
"content_scripts": [
{
"matches": ["http://*/*", "https://*/*"],
"js": ["jquery-1.7.1.min.js", "content_script.js" ],
"run_at": "document_idle",
"all_frames": true
}
]
}
See also: jQuery/JavaScript: accessing contents of an iframe

Resources