The Question is in continuation to the link given below.
Getting the source HTML of the current page from chrome extension
The top answer given in the above URL gets the source code of the present tab. But this does not get the source code inside the iframes of the source code.
How do I access the source code the entire page INCLUDING FRAMES in chrome extensions?
Use allFrames: true parameter of chrome.tabs.executeScript to inject the code into all frames. The callback function will receive an array with the results for all frames:
popup.js:
chrome.tabs.executeScript({
allFrames: true,
code: 'JSON.stringify({ \
url: location.href, \
html: document.documentElement.innerHTML \
})'
}, function(results) {
if (chrome.runtime.lastError || !results || !results.length) {
console.log("Some error occurred", chrome.runtime.lastError);
} else {
results.forEach(function(str, index) {
var info = JSON.parse(str);
console.log("Frame #%d | %s | %s",
index, info.url, info.html.substr(0, 200) + "...");
});
}
});
Related
I am trying to copy 'window.location.href' e.g. the URL of the current page to clipboard from my extension.
My issue is that when I copy the URL to clipboard, it is the extensions URL that is copied and not the page I am visiting.
Extensionbar:
<!DOCTYPE HTML>
<html>
<head>
<button onclick="copyFunction();">Copy</button>
<script type="text/javascript">
function copyFunction() {
var inputDump = document.createElement('input'),
hrefText = window.location.href;
document.body.appendChild(inputDump);
inputDump.value = hrefText;
inputDump.select();
document.execCommand('copy');
document.body.removeChild(inputDump);
}
</script>
</head>
</html>
From my understanding the solution should be this, but I fear I am too clueless how to proceed: https://developer.apple.com/documentation/safariservices/safari_app_extensions/passing_messages_between_safari_app_extensions_and_injected_scripts
This is how I (tried to) proceed, by creating a global.html page and an injected script.
Global page:
<!DOCTYPE HTML>
<script>
safari.application.addEventListener("command", copyFunction, false);
function copyFunctionEvent(event) {
if (event.command == "CopyToClipboard") {
safari.application.activeBrowserWindow.activeTab.page.dispatchMessage("CopyToClipboard", "all");
}
}
</script>
Injected script:
function myextension_openAll(event){
if (event.name == 'CopyToClipboard'){
function copyFunction() {
var inputDump = document.createElement('input'),
hrefText = window.location.href;
document.body.appendChild(inputDump);
inputDump.value = hrefText;
inputDump.select();
document.execCommand('copy');
document.body.removeChild(inputDump);
}
}
safari.self.addEventListener("message", myextension_openAll, true);
Actual:
safari-extension://com.myextension-0000000000/abc123/extensionbar.html
Expected:
http://www.google.com (e.g. if current tab)
From your code above (Extensionbar html), you seem to write legacy Safari extension (.safariextz), and it has been deprecated. See What’s New in Safari and WebKit" session on WWDC18
I recommend you rewrite your code into Safari App Extension by following process, which can be written in Swift. I'm not sure why wrong URL is copied to clipboard in your code, but rewriting your code would solve the problem as a result.
Creating App Extension project
Create App Extension by following [File] -> [New] -> [Project...] then choose [Safari Extension App] on Xcode. Project template contains example of menubar implementation.
Copying location.href by clicking menu bar button
Following code would add functionality to copy location.href when you click menu bar button.
Just paste this into SafariExtensionHandler.swift.
class SafariExtensionHandler: SFSafariExtensionHandler {
override func messageReceived(withName messageName: String, from page: SFSafariPage, userInfo: [String : Any]?) {
// WHen injected script calls safari.extension.dispatchMessage, the message will come here
guard let href = userInfo?["href"] as? String else { return }
// Save href to clipboard
NSPasteboard.general.clearContents()
NSPasteboard.general.setString(href, forType: .string)
}
override func toolbarItemClicked(in window: SFSafariWindow) {
// Request injected script a message to send location.href
window.getActiveTab { currentTab in
currentTab!.getActivePage { currentPage in
currentPage!.dispatchMessageToScript(withName: "getHref", userInfo: nil)
}
}
}
}
And injected script (script.js) as follows.
safari.self.addEventListener("message", function(event) {
console.log("event received");
safari.extension.dispatchMessage("sendHref", { "href": location.href });
});
Working Example
Complete working code here, This may help your work. Good luck :)
https://github.com/horimislime/safari-extension-menubar-example
I know it's possible to postMessage from a web page to a chrome extension and get an ok response back to the web page, but is it possible the other way around ?
My question "I need to be able to sent a request into a webpage and wait for a response that I can use in the extension. Maybe not with postMessage ?"
I know the docs says "Only the web page can initiate a connection." (https://developer.chrome.com/extensions/messaging#external-webpage)
This is what I have tried so far.
background.js
chrome.extension.sendMessage("Hello from extension to webpage");
window.postMessage("Hello from extension to webpage");
chrome.tabs.sendMessage(TAB_ID, "Hello from extension to webpage");
webpage.js (not part of the extension)
window.onmessage = (event) => {
// Waiting for that message.
console.info("Event received ", event);
}
window.chrome.runtime.connect(CHROME_EXTENSION_APP_ID).onMessage.addListener(function (){
function(request, sender, sendResponse) {
console.info("Event received from the extension", event);
}
})
Any idea will be appreciated :)
You can use chrome.tabs.executeScript API to do this.
var dataToWebPage = {
text: 'test',
foo: 1,
bar: false
};
chrome.tabs.executeScript({
code: '(' + function(params) {
//This function will work in webpage
console.log(params); //logs in webpage console
return {
success: true,
response: "This is from webpage."
};
} + ')(' + JSON.stringify(dataToWebPage) + ');'
}, function(results) {
//This is the callback response from webpage
console.log(results[0]); //logs in extension console
});
Please note that it needs tabs permission.
The thing I want to build is that by clicking a button I want to trigger the print of a PDF file, but without opening it.
+-----------+
| Print PDF |
+-----------+
^ Click *---------> printPdf(pdfUrl)
The way how I first tried it is to use an iframe:
var $iframe = null;
// This is supposed to fix the onload bug on IE, but it's not fired
window.printIframeOnLoad = function() {
if (!$iframe.attr("src")) { return; }
var PDF = $iframe.get(0);
PDF.focus();
try {
// This doesn't work on IE anyways
PDF.contentWindow.print();
// I think on IE we can do something like this:
// PDF.document.execCommand("print", false, null);
} catch (e) {
// If we can't print it, we just open it in the current window
window.location = url;
}
};
function printPdf(url) {
if ($iframe) {
$iframe.remove();
}
$iframe = $('<iframe>', {
class: "hide",
id: "idPdf",
// Supposed to be a fix for IE
onload: "window.printIframeOnLoad()",
src: url
});
$("body").prepend($iframe);
}
This works on Safari (desktop & iOS) and Chrome (can we generalize it maybe to webkit?).
On Firefox, PDF.contentWindow.print() ends with a permission denied error (even the pdf is loaded from the same domain).
On IE (11), the onload handler is just not working.
Now, my question is: is there another better way to print the pdf without visually opening it to the user?
The cross browser thing is critical here. We should support as many browsers as possible.
What's the best way to achieve this? Is my start a good one? How to complete it?
We are now in 2016 and I feel like this is still a pain to implement across the browsers.
UPDATE: This link details an elegant solution that involves editing the page properties for the first page and adding an action on Page Open. Works across all browsers (as browsers will execute the JavaScript placed in the actions section). Requires Adobe Acrobat Pro.
It seems 2016 brings no new advancements to the printing problem. Had a similar issue and to make the printing cross-browser I solved it using PDF.JS but had to make a one-liner addition to the source (they ask you to build upon it in anyways).
The idea:
Download the pre-built stable release from https://mozilla.github.io/pdf.js/getting_started/#download and add the "build" and "web" folders to the project.
The viewer.html file is what renders out PDFs with a rich interface and contains print functionality. I added a link in that file to my own JavaScript that simply triggers window.print() after a delay.
The link added to viewer:
<script src="viewer.js"></script>
<!-- this autoPrint.js was added below viewer.js -->
<script src="autoPrint.js"></script>
</head>
The autoPrint.js javascript:
(function () {
function printWhenReady() {
if (PDFViewerApplication.initialized) {
window.print();
}
else {
window.setTimeout(printWhenReady, 3000);
}
};
printWhenReady();
})();
I could then put calls to viewer.html?file= in the src of an iframe and hide it. Had to use visibility, not display styles because of Firefox:
<iframe src="web/viewer.html?file=abcde.pdf" style="visibility: hidden">
The result: the print dialog showed after a short delay with the PDF being hidden from the user.
Tested in Chrome, IE, Firefox.
After spending the past couple of hours trying to figure this one out and lots of searching here is what I have determined...
The HTML5 Web API spec for Printing indicates that one of the printing steps must fire beforeprint, a simple event (an event that is non-cancelable), to the window object of the Document being printed (as well as any nested browsing contexts, this relates to iframes) to allow for changes to the Document prior to printing. This step is internal to the browser and not something you'll be able to adjust. During this process, the browser's print dialog sometimes shows a preview of the file (Chrome does this)...so if your goal is to never display the file to the viewer you might be stuck.
The closest to achieving this I came was by creating an index.html file which has a button containing data-* attributes which provided context. Change the path/filename.ext in the data-print-resource-uri attribute to a local file of your own.
<!DOCTYPE html>
<html>
<head>
<title>Express</title>
<link rel="stylesheet" href="/stylesheets/style.css">
</head>
<body>
<h1>Express</h1>
<p>Welcome to Express</p>
<button name="printFile" id="printFile" data-print-resource-uri="/binary/paycheckStub.pdf" data-print-resource-type="application/pdf">Print File</button>
<iframe name="printf" id="printf" frameborder="0"></iframe>
<script src="/javascripts/print.js"></script>
</body>
</html>
Then in the print.js file, I tried a few things, but never quite got it working (leaving different things I had played with in the comments).
// Reference vars
var printButton = document.getElementById('printFile');
var printFrame = document.getElementById('printf');
// onClick handler
printButton.onclick = function(evt) {
console.log('evt: ', evt);
printBlob('printf', printButton.getAttribute('data-print-resource-uri'), printButton.getAttribute('data-print-resource-type'));
}
// Fetch the file from the server
function getFile( fileUri, fileType, callback ) {
var xhr = new XMLHttpRequest();
xhr.open('GET', fileUri);
xhr.responseType = 'blob';
xhr.onload = function(e) {
// Success
if( 200 === this.status ) {
// Store as a Blob
var blob = new Blob([this.response], {type: fileType});
// Hang a URL to it
blob = URL.createObjectURL(blob);
callback(blob);
} else {
console.log('Error Status: ', this.status);
}
};
xhr.send();
}
function printBlob(printFrame, fileUri, fileType) {
// Debugging
console.log('inside of printBlob');
console.log('file URI: ', fileUri);
console.log('file TYPE: ', fileType);
// Get the file
getFile( fileUri, fileType, function(data) {
loadAndPrint(printFrame, data, fileType);
});
}
function loadAndPrint(printFrame, file, type) {
// Debugging
console.log('printFrame: ', printFrame);
console.log('file: ', file);
window.frames[printFrame].src = file;
window.frames[printFrame].print();
/*
// Setup the print window content
var windowContent = '<!DOCTYPE html>';
windowContent += '<html>'
windowContent += '<head><title>Print canvas</title></head>';
windowContent += '<body>'
windowContent += '<embed src="' + file + '" type="' + type + '">';
windowContent += '</body>';
windowContent += '</html>';
// Setup the print window
var printWin = window.open('','','width=340,height=260');
printWin.document.open();
printWin.document.write(windowContent);
printWin.document.close();
printWin.focus();
printWin.print();
printWin.close();
*/
}
I think that if you can get it working properly using the Blob might work the best in the cross-browser method you wanted.
I found a few references about this topic which might be helpful:
How to send a pdf file directly to the printer using JavaScript?
https://www.w3.org/TR/html5/webappapis.html#printing
https://developer.mozilla.org/en-US/docs/Web/Guide/Printing#Print_an_external_page_without_opening_it
Printing a web page using just url and without opening new window?
I will post here the modified functions of the OP functional on IE 11
printPdf: function (url) {
$('#mainLoading').show();
let iframe = $('#idPdf');
if (iframe) {
iframe.remove();
}
iframe = $('<iframe>', {
style: "display:none",
id: "idPdf"
});
$("body").prepend(iframe);
$('#idPdf').on("load", function(){
utilities.printIframeOnLoad()
})
utilities.getAsyncBuffer(url, function(response){
let path = utilities.getPdfLocalPath(response);
$('#idPdf').attr('src', path);
})
},
printIframeOnLoad: function () {
let iframe = $('#idPdf');
if (!iframe.attr("src")) { return; }
var pdf = iframe.get(0);
pdf.focus();
$('#mainLoading').hide();
pdf.contentWindow.print();
},
getPdfLocalPath: function (data) {
var filename = "Application_" + utilities.uuidv4() + ".pdf";
var blob = new Blob([data], { type: 'application/pdf' });
if (window.navigator.msSaveOrOpenBlob) {
window.navigator.msSaveBlob(blob, filename);
return filename;
}
else {
let url = window.URL || window.webkitURL;
let href = url.createObjectURL(blob);
return href;
}
},
getAsyncBuffer: function (uriPath, callback) {
var req = new XMLHttpRequest();
req.open("GET", uriPath, true);
req.responseType = "blob";
req.onreadystatechange = function () {
if (req.readyState === 4 && req.status === 200) {
callback(req.response);
}
};
req.send();
}
I have a chrome extension that toggles a sidebar with the browser action click. The sidebar contains an iframe with a local (chrome extension) source. I thought the page within the iframe would be considered a local chrome extension file with open access to the chrome APIs and etc. However, I keep getting the following errors in the web console:
Uncaught TypeError: Cannot read property 'onClicked' of undefined <-- background.js
TypeError: Cannot read property 'query' of undefined <-- sidebar.js
How do I get it so that the iframe injected with a context script has access to the local chrome environment?
Code below:
sidebar.js:
var myApp = angular.module('PlaceMates', []);
myApp.service('pageInfoService', function() {
this.getInfo = function(callback) {
var model = {};
chrome.tabs.query({
'active': true, // Select active tabs
lastFocusedWindow: true // In the current window
},
function (tabs) {
if (tabs.length > 0)
{
model.title = tabs[0].title;
model.url = tabs[0].url;
chrome.tabs.sendMessage(tabs[0].id, { 'action': 'PageInfo' }, function (response) {
model.pageInfos = response;
console.log("popup js: " + model.pageInfos);
callback(model);
});
}
});
};
});
myApp.controller("PageController", function ($scope, pageInfoService) {
$scope.message = "This extension identifies the photos on this page!";
pageInfoService.getInfo(function (info) {
$scope.title = info.title;
$scope.url = info.url;
$scope.pageInfos = info.pageInfos;
$scope.place_name = info.place_name;
$scope.$apply();
});
});
background.js
console.log( 'Background.html starting!' );
// Called when the user clicks on the browser action.
chrome.browserAction.onClicked.addListener(function(tab) {
// No tabs or host permissions needed!
console.log('Toggling sidebar on ' + tab.url);
// send message to current tab when clicked
var tabId = tab.id;
console.log("tab.id: " + tabId);
chrome.tabs.query({active: true, currentWindow: true}, function(tab) {
chrome.tabs.sendMessage(
//Selected tab id
tabId,
//Params inside a object data
{callFunction: "toggleSidebar"},
//Optional callback function
function(response) {
console.log(response);
}
);
});
console.log('Done toggling sidebar!');
});
chrome.extension.onMessage.addListener(function(request, sender, sendResponse) {
if(request.cmd == "read_file") {
$.ajax({
url: chrome.extension.getURL("sidebar.html"),
dataType: "html",
success: sendResponse
});
}
})
chrome.runtime.onMessage.addListener(
function(msg, sender, sendResponse) {
console.log(sender.tab ?
"from a content script:" + sender.tab.url :
"from the extension");
if (msg.command == "read_info"){
console.log(JSON.parse(JSON.stringify(msg.myInfo)));
sendResponse({command: "done"});
}
}
);
console.log( 'Background.html done.' );
The only pages that have access to all chrome.* extension API are pages that are run at the extension's origin and within the Chrome extension process.
When an extension page is embedded in an iframe, its extension runtime is equivalent to a content script: The page can only use cross-origin XMLHttpRequest and some of the extension APIs (messaging and some other methods in the chrome.runtime / chrome.extension namespace).
If you wish to make the functionality of the other Chrome APIs available to your iframe, then you have to call these APIs from the background page, and use the messaging API to proxy requests from the iframe to the background page and back. Luckily, most of the Chrome extension API is asynchronous by design, so it will not be difficult to change your code to use these proxies.
I'm so close to finishing my Chrome extension. I have one or two things to do. One of them is sending a message from the content script to the background script. I wrote the following, but it doesn't quite what I want.
content.js
var a=document.getElementsByTagName('a');
for (i=0,len=a.length;i<len;i++) {
a[i].addEventListener('contextmenu', function() {
var linkTitle = this.getAttribute('title').trim();
var linkUrl = this.getAttribute('href');
if ((linkTitle != null) && (linkTitle.length > 0)) {
chrome.extension.sendMessage({action:'bookmarkLink', 'title':linkTitle, 'url': linkUrl}, function(msg) {
alert('Messages sent: '+action+' and '+linkTitle+' also '+linkUrl);
});
}
});
};
background.js
chrome.contextMenus.create({'title': 'Add to mySU bookmarks', 'contexts': ['link'], 'onclick': mySUBookmarkLink});
function mySUBookmarkLink(info, tab) {
chrome.extension.onMessage.addListener(function(msg, sender, sendResponse) {
if (msg.action == 'bookmarkLink') {
chrome.storage.sync.set({'title': msg.linkTitle, 'url': msg.linkUrl}, function(msg) {
alert('Saved '+msg.linkTitle+' to bookmarks');
});
}
});
};
My problems are:
In the first code block, it alerts Saved undefined to bookmarks as soon as I right click on the link, while as I understand it should only send a message on right click and the second code block should alert Saved to bookmarks when I click on the context menu. What am I missing or doing wrong?
I may not have used parameters correctly (I am fairly new to extension development and Javascript in general). Do the above look okay?
Thank you in advance,
K.
It's chrome.runtime.sendMessage and chrome.runtime.onMessage rather than chrome.extension.
There used to be chrome.extension.sendRequest and chrome.extension.onRequest which have been deprecated in favor of the chrome.runtime API methods mentioned above.
See Chrome Extensions - Message Passing
it's JSON-serializable messaging, where first pair is for recognition, and then followed by pairs of
key: value.
You pull the value from received message by calling it's key.
is should be:
alert('Saved '+msg.title+' to bookmarks');
or even better:
function mySUBookmarkLink(info, tab) {
chrome.extension.onMessage.addListener(function(msg, sender, sendResponse) {
if (msg.action == 'bookmarkLink') {
var receivedValue = msg.title; //pull it out first, for better overview
chrome.storage.sync.set({'title': msg.title, 'url': msg.url}, function(msg) {
alert('Saved '+receivedValue+' to bookmarks');
});
}
});
};