I'm trying to catch console logs of an iframe which I don't have access to its source and its implementation.
<iframe src="an http address" name="my-iframe" allowFullScreen/>
When the iframe logs data I see them in my console. But the problem is,
this piece of code
// define a new console
var console = (function(oldCons){
return {
log: function(text){
oldCons.log(text);
if(text === "hi"){
alert("text")
}
},
info: function (text) {
oldCons.info(text);
if(text === "hi"){
alert("text")
}
},
warn: function (text) {
oldCons.warn(text);
if(text === "hi"){
alert("text")
}
},
error: function (text) {
oldCons.error(text);
if(text === "hi"){
alert("text")
}
}
};
}(window.console));
//Then redefine the old console
window.console = console;
Which I got from this post is not working for the iframe logs. It only works on my app console logs.
If the src is a different domain, and you can't run code directly on the other domain (for example, by being able to modify or insert a .js into it), then there's nothing you can do. This is for security reasons: browsers don't want to leak any information between domains unless the sender of the information deliberately allows it.
If you can alter code on the other domain, you could monkeypatch the console and have it pass the log information to the parent window with postMessage so that the parent can do something with it.
If you happen to be trying to examine messages for your personal use only (rather than for use by random people on the internet), you can modify your browser to run your custom JavaScript on the other domain with a userscript, by using a userscript manager like Tampermonkey.
Related
Apps script web app works in <iframe>. It seems Chrome is no longer supporting alert(), confirm(), Promote these functions on the web app.
Any workaround to this?
Chrome Version 92.0.4515.107 (Official Build) (64-bit) -- does not work
Edge Version 91.0.864.71 (Official build) (64-bit) -- works
Tried replacing alert() with window.alert(), but still does not work.
exec:1 A different origin subframe tried to create a JavaScript dialog. This is no longer allowed and was blocked. See https://www.chromestatus.com/feature/5148698084376576 for more details.
This is absurd and subjective decision of Google to remove alert(), confirm(), and prompt() for cross origin iframes. And they called it "feature". And justification is very poor - see "motivation" bellow. A very weak reason for removing such an important feature! Community and developers should protest!
Problem
https://www.chromestatus.com/feature/5148698084376576
Feature: Remove alert(), confirm(), and prompt for cross origin iframes
Chrome allows iframes to trigger Javascript dialogs, it shows “ says ...” when the iframe is the same origin as the top frame, and “An embedded page on this page says...” when the iframe is cross-origin. The current UX is confusing, and has previously led to spoofs where sites pretend the message comes from Chrome or a different website. Removing support for cross origin iframes’ ability to trigger the UI will prevent this kind of spoofing, and unblock further UI simplifications.
Motivation
The current UI for JS dialogs (in general, not just for the cross-origin subframe case) is confusing, because the message looks like the browser’s own UI. This has led to spoofs (particularly with window.prompt) where sites pretend that a particular message is coming from Chrome (e.g. 1,2,3). Chrome mitigates these spoofs by prefacing the message with “ says...”. However, when these alerts are coming from a cross-origin iframe, the UI is even more confusing because Chrome tries to explain the dialog is not coming from the browser itself or the top level page. Given the low usage of cross-origin iframe JS dialogs, the fact that when JS dialogs are used they are generally not required for the site’s primary functionality, and the difficulty in explaining reliably where the dialog is coming from, we propose removing JS dialogs for cross-origin iframes. This will also unblock our ability to further simplify the dialog by removing the hostname indication and making the dialog more obviously a part of the page (and not the browser) by moving it to the center of the content area. These changes are blocked on removing cross-origin support for JS dialogs, since otherwise these subframes could pretend their dialog is coming from the parent page.
Solution
Send message via Window.postMessage() from iframe to parent and show dialog via parent page. It is very elegant hack and shame on Google because before Chrome version 92 client saw alert dialog e.g. An embedded page iframe.com" says: ... (which was correct - client see real domain which invoked alert) but now with postMessage solution client will see a lie e.g. The page example.com" says: ... but alert was not invoked by example.com. Stupid google decision caused them to achieve the opposite effect - client will be much more confused now. Google's decision was hasty and they didn't think about the consequences. In case of prompt() and confirm() it is a little bit tricky via Window.postMessage() because we need to send result from top back to iframe.
What Google will do next? Disable Window.postMessage()? Déjà vu. We are back in Internet Explorer era... developers waste time by doing stupid hacks.
TL;DR: Demo
https://domain-a.netlify.app/parent.html
Code
With code bellow you can use overridden native alert(), confirm() and prompt() in cross origin iframe with minimum code change. There is no change for alert() usage. I case of confirm() and prompt() just add "await" keyword before it or feel free to use callback way in case you can not switch easily your sync functions to async functions. See all usage examples in iframe.html bellow.
Everything bad comes with something good - now I gained with this solution an advantage that iframe domain is not revealed (domain from address bar is now used in dialogs).
https://example-a.com/parent.html
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Parent (domain A)</title>
<script type="text/javascript" src="dialogs.js"></script>
</head>
<body>
<h1>Parent (domain A)</h1>
<iframe src="https://example-b.com/iframe.html">
</body>
</html>
https://example-b.com/iframe.html
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Iframe (domain B)</title>
<script type="text/javascript" src="dialogs.js"></script>
</head>
<body>
<h1>Iframe (domain B)</h1>
<script type="text/javascript">
alert('alert() forwarded from iframe.html');
confirm('confirm() forwarded from iframe.html via callback', (result) => {
console.log('confirm() result via callback: ', result);
});
prompt('prompt() forwarded from iframe.html via callback', null, (result) => {
console.log('prompt() result via callback: ', result);
});
(async () => {
var result1 = await confirm('confirm() forwarded from iframe.html via promise');
console.log('confirm() result via promise: ', result1);
var result2 = await prompt('prompt() forwarded from iframe.html via promise');
console.log('prompt() result via promise: ', result2);
})();
</script>
</body>
</html>
dialogs.js
(function() {
var id = 1,
store = {},
isIframe = (window === window.parent || window.opener) ? false : true;
// Send message
var sendMessage = function(windowToSend, data) {
windowToSend.postMessage(JSON.stringify(data), '*');
};
// Helper for overridden confirm() and prompt()
var processInteractiveDialog = function(data, callback) {
sendMessage(parent, data);
if (callback)
store[data.id] = callback;
else
return new Promise(resolve => { store[data.id] = resolve; })
};
// Override native dialog functions
if (isIframe) {
// alert()
window.alert = function(message) {
var data = { event : 'dialog', type : 'alert', message : message };
sendMessage(parent, data);
};
// confirm()
window.confirm = function(message, callback) {
var data = { event : 'dialog', type : 'confirm', id : id++, message : message };
return processInteractiveDialog(data, callback);
};
// prompt()
window.prompt = function(message, value, callback) {
var data = { event : 'dialog', type : 'prompt', id : id++, message : message, value : value || '' };
return processInteractiveDialog(data, callback);
};
}
// Listen to messages
window.addEventListener('message', function(event) {
try {
var data = JSON.parse(event.data);
}
catch (error) {
return;
}
if (!data || typeof data != 'object')
return;
if (data.event != 'dialog' || !data.type)
return;
// Initial message from iframe to parent
if (!isIframe) {
// alert()
if (data.type == 'alert')
alert(data.message)
// confirm()
else if (data.type == 'confirm') {
var data = { event : 'dialog', type : 'confirm', id : data.id, result : confirm(data.message) };
sendMessage(event.source, data);
}
// prompt()
else if (data.type == 'prompt') {
var data = { event : 'dialog', type : 'prompt', id : data.id, result : prompt(data.message, data.value) };
sendMessage(event.source, data);
}
}
// Response message from parent to iframe
else {
// confirm()
if (data.type == 'confirm') {
store[data.id](data.result);
delete store[data.id];
}
// prompt()
else if (data.type == 'prompt') {
store[data.id](data.result);
delete store[data.id];
}
}
}, false);
})();
File a feature request:
Consider filing a feature request using this Issue Tracker template.
I'd either request that an exception is made for Apps Script web apps, or that built-in methods for alert and confirm are added, similar to the existing alert and prompt dialogs, which currently work on Google editors.
Bug filed:
By the way, this behavior has been reported in Issue Tracker (as a bug):
Javascript in html files doesn't work
I'd consider starring it in order to keep track of it.
Workaround:
In the meanwhile, as others said, consider downgrading or changing the browser, or executing it with the following command line flag:
--disable-features="SuppressDifferentOriginSubframeJSDialogs"
Related question:
Chrome SuppressDifferentOriginSubframeJSDialogs setting override using JS?
So far, the only 'solution' for this is to add the following to your Chrome/Edge browser shortcut:
--disable-features="SuppressDifferentOriginSubframeJSDialogs"
Or downgrade your browser. Obviously neither of these are ideal. Google trying really hard to save us from ourselves here.
I'm looking to override the existing console commands via my Chrome extension - the reason for this is I wish to record the console logs for a specific site.
Unfortunately I cannot seem to update the DOM, this is what i've tried so far:
// Run functions on page change
chrome.tabs.onUpdated.addListener( function (tabId, changeInfo, tab) {
var s = document.createElement('script');
// TODO: add "script.js" to web_accessible_resources in manifest.json
s.src = chrome.runtime.getURL('core/js/app/console.js');
s.onload = function() {
this.remove();
};
(document.head || document.documentElement).appendChild(s);
});
console.js
// Replace functionality of console log
console.defaultLog = console.log.bind(console);
console.logs = [];
console.log = function(){
console.defaultLog.apply(console, arguments);
console.logs.push(Array.from(arguments));
};
// Replace functionality of console error
console.defaultError = console.error.bind(console);
console.errors = [];
console.error = function(){
console.defaultError.apply(console, arguments);
console.errors.push(Array.from(arguments));
};
// Replace functionality of console warn
console.defaultWarn = console.warn.bind(console);
console.warns = [];
console.warn = function(){
console.defaultWarn.apply(console, arguments);
console.warns.push(Array.from(arguments));
};
// Replace functionality of console debug
console.defaultDebug = console.debug.bind(console);
console.debugs = [];
console.debug = function(){
console.defaultDebug.apply(console, arguments);
console.debugs.push(Array.from(arguments));
};
The script runs successfully with an alert().
The goal for me is to access console.logs - but its undefined which means I haven't gotten access to the DOM, despite injecting a script.
If not possible, even a third party integration would be helpful i.e. Java or C?
Any thoughts would be greatly appreciated :)
I found this post and I think Tampermonkey injects a script with the immediate function that you add in the Tampermonkey Chrome extension page, I found something similar in extensions like Wappalyzer, and looks good and safe, you could use WebRequest to inject to your website the new "polyfill" before the page is fully loaded as the post says.
Here the example of Wappalyzer that I mentioned before, this is the JS load in StackOverflow with Wappalyzer using the code injection, I didn't test it with Tampermonkey yet
EDIT
Checking Wappalyzer, how to inject the code is the easy part, you can use (Wappalyzer github example):
const script = document.createElement('script')
script.setAttribute('src', chrome.extension.getURL('js/inject.js'))
This probably will not fix your problem, this code is executed after all the content was loaded in the DOM. But, you can find how to fix that problem in this post
I'll suggest to use onCommitted event (doc1/doc2)
Using the mozilla.org example you will have something like
const filter = {
url: //website to track logs
[
{hostContains: "example.com"},
{hostPrefix: "developer"}
]
}
function logOnCommitted(details) {
//Inject Script on webpage
}
browser.webNavigation.onCommitted.addListener(logOnCommitted, filter);
It might be worth trying to redefine the entire console object:
const saved = window.console
window.console = {...saved, log: function(...args){ saved.log("Hello", ...args) }}
But it's probably impossible, because content scripts live in an isolated world:
Isolated worlds do not allow for content scripts, the extension, and the web page to access any variables or functions created by the others. This also gives content scripts the ability to enable functionality that should not be accessible to the web page.
Although in Tampermonkey this script works.
I believe Tampermonkey handles this by knowing the subtleties and tracking changes in the extensions host's protection mechanism.
BTW, for small tasks, there is a decent alternative to chrome extensions in the form of code snippets.
I'm struggling to find the best way to communicate with my web app, which I'm opening with chrome.windows.create in my extension.
I've got the wiring between content script and background script right. I can right click an element and send it's value to the background script, and the background script creates a window containing my webapp. But from there I can't figure out how to access and use that value in my webapp (it needs to load the value into an editor).
I've tried setting fns and vars on the window and tab objects, but somehow they go missing from the window object once the web app is loaded.
With chrome.tabs.executeScript I can fiddle with the dom, but not set global variables or anything on 'window' either.
If there isn't a better way, I guess I'm forced to add to the DOM and pick that up once my web app is loaded, but it seems messy. I was hoping for a cleaner method, like setting an onLoadFromExtension fn which my web app can execute to get the value it needs.
I found a method that works after much trial and error, though it still seems error prone. And it also depends on the extension ID matching the installed one, so if that can't be hard-coded it'll be another message that needs passing through another channel (after reading up, looks like that can be hard-coded since it's a hash of the public key, so problem solved)... Starting to think manipulating the DOM is less messy...
background.js:
var selectedContent = null;
chrome.runtime.onMessageExternal.addListener(
function(request, sender, sendResponse) {
console.info("------------------------------- Got request", request);
if (request.getSelectedContent) {
sendResponse(selectedContent);
}
});
web app:
var extensionId = "naonkagfcedpnnhdhjahadkghagenjnc";
chrome.runtime.sendMessage(extensionId, {getSelectedContent: "true"},
response => {
console.info("----------------- Got response", response);
if(response) {
this.text = response;
}
});
manifest.json:
"externally_connectable": {
"ids": ["naonkagfcedpnnhdhjahadkghagenjnc"],
"matches": ["http://localhost:1338/*"]
},
Within the popup, do the following:
const parentWindow = window.opener
parentWindow.postMessage({ action: 'opened' })
window.onmessage = msg => {
alert(JSON.stringify(msg.data)) // Alerts you with {"your":"data"}
}
Within the script that will call chrome.windows.create, do the following:
window.onmessage = msg => {
if (msg.data.action == 'opened') {
msg.source.postMessage({ your: 'data' })
}
}
Set setSelfAsOpener: true when calling chrome.windows.create
How does this work?
Due to limitations of the Chrome extension windows API, the created window needs to post a message to its creator (aka window.opener) or else the creator won't have access to a WindowProxy (useful for posting messages to the created window).
How I can read WebSocket frames of a web page in a Chrome extension or Firefox add-on, in a way that cannot be detected by the page?
Inspect WebSockets frames from a Chrome Dev Tools extension formulates a similar question, but developing a NPAPI plugin no longer makes sense because it will soon be removed.
Intercepting the WebSocket data is easy. Simply execute the following script before the page constructs the WebSocket. This snippet monkey-patches the WebSocket constructor: When a new WebSocket constructor is created, the snippet subscribes to the message event, from where you can do whatever you want with the data.
This snippet is designed to be indistinguishable from native code so the modification cannot easily be detected by the page (however, see the remarks at the end of this post).
(function() {
var OrigWebSocket = window.WebSocket;
var callWebSocket = OrigWebSocket.apply.bind(OrigWebSocket);
var wsAddListener = OrigWebSocket.prototype.addEventListener;
wsAddListener = wsAddListener.call.bind(wsAddListener);
window.WebSocket = function WebSocket(url, protocols) {
var ws;
if (!(this instanceof WebSocket)) {
// Called without 'new' (browsers will throw an error).
ws = callWebSocket(this, arguments);
} else if (arguments.length === 1) {
ws = new OrigWebSocket(url);
} else if (arguments.length >= 2) {
ws = new OrigWebSocket(url, protocols);
} else { // No arguments (browsers will throw an error)
ws = new OrigWebSocket();
}
wsAddListener(ws, 'message', function(event) {
// TODO: Do something with event.data (received data) if you wish.
});
return ws;
}.bind();
window.WebSocket.prototype = OrigWebSocket.prototype;
window.WebSocket.prototype.constructor = window.WebSocket;
var wsSend = OrigWebSocket.prototype.send;
wsSend = wsSend.apply.bind(wsSend);
OrigWebSocket.prototype.send = function(data) {
// TODO: Do something with the sent data if you wish.
return wsSend(this, arguments);
};
})();
In a Chrome extension, the snippet can be run via a content script with run_at:'document_start', see Insert code into the page context using a content script.
Firefox also supports content scripts, the same logic applies (with contentScriptWhen:'start').
Note: The previous snippet is designed to be indistinguishable from native code when executed before the rest of the page. The only (unusual and fragile) ways to detect these modifications are:
Pass invalid parameters to the WebSocket constructor, catch the error and inspecting the implementation-dependent (browser-specific) stack trace. If there is one more stack frame than usual, then the constructor might be tampered (seen from the page's perspective).
Serialize the constructor. Unmodified constructors become function WebSocket() { [native code] }, whereas a patched constructor looks like function () { [native code] } (this issue is only present in Chrome; in Firefox, the serialization is identical).
Serialize the WebSocket.prototype.send method. Since the function is not bound, serializing it (WebSocket.prototype.send.toString()) reveals the non-native implementation. This could be mitigated by overriding the .toString method of .send, which in turn can be detected by the page by a strict comparison with Function.prototype.toString. If you don't need the sent data, do not override OrigWebSocket.prototype.send.
There is an alternative to Rob W's method that completely masks any interaction with the page (for Chrome)
Namely, you can take out some heavy artillery and use chrome.debugger.
Note that using it will stop you from opening Dev Tools for the page in question (or, more precisely, opening the Dev Tools will make it stop working, since only one debugger client can connect). This has been improved since: multiple debuggers can be attached.
This is a pretty low-level API; you'll need to construct your queries using the debugger protocol yourself. Also, the corresponding events are not in the 1.1 documentation, you'll need to look at the development version.
You should be able to receive WebSocket events like those and examine their payloadData:
{"method":"Network.webSocketFrameSent","params":{"requestId":"3080.31","timestamp":18090.353684,"response":{"opcode":1,"mask":true,"payloadData":"Rock it with HTML5 WebSocket"}}}
{"method":"Network.webSocketFrameReceived","params":{"requestId":"3080.31","timestamp":18090.454617,"response":{"opcode":1,"mask":false,"payloadData":"Rock it with HTML5 WebSocket"}}}
This extension sample should provide a starting point.
In fact, here's a starting point, assuming tabId is the tab you're interested in:
chrome.debugger.attach({tabId:tab.id}, "1.1", function() {
chrome.debugger.sendCommand({tabId:tabId}, "Network.enable");
chrome.debugger.onEvent.addListener(onEvent);
});
function onEvent(debuggeeId, message, params) {
if (tabId != debuggeeId.tabId)
return;
if (message == "Network.webSocketFrameSent") {
// do something with params.response.payloadData,
// it contains the data SENT
} else if (message == "Network.webSocketFrameReceived") {
// do something with params.response.payloadData,
// it contains the data RECEIVED
}
}
I have tested this approach (with the linked sample modified as above) and it works.
Just to add an exception to #Xan answer (I don't have enough rep to post a comment on his answer so I add it here cause I believe it can save some time to someone else).
That example won't work if the WebSocket connection is established in a context that was loaded via about:, data: and blob: schemes.
See here for the related bugs: Attach debugger to worker from chrome devtools extension
Is there a way to print log messages to the server console from front-end?
For Example, if my GUI HTML pages is running on JBOSS server. I want to write logs to the server console ie, "SERVER.log" file generated on the JBOSS server.
(Just like Sysout() for printing simple debugging messages.)
You could overwrite the default console.log api or use another method;
var logHandler = {
send: function(args) {
$.post("URL", {
messages: arguments
});
}
}
window.defaultConsoleLog = console.log;
console.log = function() {
logHandler.send(arguments)
defaultConsoleLog.call(this, arguments);
}
Not tested code
I would recommend not overwriting default behavior of the browser, but instead have a "proxy"
var logger = function() {
logHandler.send(arguments);
console.log.call(this, arguments)
}
And then use logger("Test") instead of console.log("Test")
Can you be more clear on the "server console" part and your specific requirements?
If all you need is to capture activity on the server, I suggest you look into something like log4javascript.
http://log4javascript.org/docs/index.html