I can't seem to get popover to work in my chrome extension.
The extension is intended to go through my school's registration page, pull up info on my professors through a professor rating website, and show it in a popover. (And link to the pages which works fine).
Here is the school page. Just hit something like Subject = Art
Less than = 200
Some results pop up, you click the extension icon, and bam the script runs. With all that, maybe the error is in my code. I'm not sure.
My code:
Manifest:
{
"name": "RMP",
"description": "Work in Progress",
"manifest_version": 2,
"version": "0.8",
"icons": {
"16": "icon16.png",
"48": "icon48.png",
"128": "icon128.png"
},
"permissions": [
"activeTab",
"http://*/",
"https://*/"
],
"background": {
"scripts": ["jquery-3.2.1.min.js","bootstrap.min.js","background.js"],
"persistent": false
},
"browser_action": {
"default_title": "Click me!"
}//,
//"web_accessible_resources": ["bootstrap-native.js"]
}
Background:
chrome.browserAction.onClicked.addListener(function(tab) {
chrome.tabs.executeScript(tab.Id, {file: "jquery-3.2.1.min.js"}, function() {
chrome.tabs.executeScript(tab.Id, {file: "bootstrap.min.js"}, function(){
chrome.tabs.executeScript(tab.Id, {file: "content_script.js"})
});
});
});
Content_script.js:
NOTE --- I am aware I have like 2-3 different attempts at getting popover to work. Can't get anything at all to work unfortunately.
// Handle page's frame (to allow DOM access)
var page = top.frames["TargetContent"].document;
// Reference every professor listed and modify the registration page
Array.from(page.querySelectorAll("[id^='MTG_INSTR$']") ).forEach( el => {
if (el.textContent == "## Heading ##Staff") {
return;
}
// For every professor found, search for RMP page
searchProfessor(el)
});
/**
* Search for professor on RMP, then pass along to pageCheck
*
* #param {Reference to prof} professorEl
*/
function searchProfessor(professorEl) {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
pageCheck(this.response,professorEl);
}
}
// Search RMP using CSUF + Prof Name
xhr.open('GET', 'https://www.ratemyprofessors.com/search.jsp?queryoption=HEADER&queryBy=teacherName&schoolName=california+state+university+fullerton&schoolID=&query=' + professorEl.textContent +'&_action_search=Search');
xhr.responseType = 'document';
xhr.send();
}
/**
* Verify prof's page exists and modify registration page
*
* #param {DOM Obj} page
* #param {Reference to prof} element
*/
function pageCheck(page,element){
var ProfURL = page.getElementsByClassName('listing PROFESSOR')[0].childNodes[1].href
// If the element exists, we have a hit (and the prof's page!)
if (ProfURL) {
// Link to the prof's RMP page
addAnchor(element, ProfURL);
// Access the prof's specific RMP page
var xhr1 = new XMLHttpRequest();
// Create box to display prof info on hover
xhr1.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
addTooltip(this.response,element);
}
}
xhr1.open('GET', ProfURL);
xhr1.responseType = 'document';
xhr1.send();
}
}
function addTooltip(profPage,profElement) {
var name = profElement.textContent;
var grade = profPage.getElementsByClassName('grade')[0].textContent;
var difficulty = profPage.getElementsByClassName('grade')[2].textContent;
var ratings = profPage.getElementsByClassName('table-toggle rating-count active')[0].textContent;
ratings = ratings.trim();
var content = "Grade: " + grade;
profElement.firstChild.setAttribute("data-toggle","popover");
profElement.firstChild.setAttribute("data-trigger","hover");
profElement.firstChild.setAttribute("title",name);
profElement.firstChild.setAttribute("data-content",content);
//profElement.popover();
$("[data-toggle=popover]",top.frames["TargetContent"]).popover();
}
/**
* Assign hyperlink to element
*
* #param {Element} wrapper
* #param {String} URL
*/
function addAnchor (wrapper, URL) {
var a = document.createElement('a');
a.href = URL;
a.textContent = wrapper.textContent;
// Opens in new window/tab
a.setAttribute('target', '_blank');
wrapper.replaceChild(a, wrapper.firstChild);
}
$(document).ready(function()
{
$('[data-toggle=popover]').popover(
{
trigger: 'hover',
html: true,
placement: 'right',
content: 'hello world'
});
});
$("[data-toggle=popover]",top.frames["TargetContent"]).popover();
$(".PSLONGEDITBOX",top.frames["TargetContent"].document).popover();
Related
I've searched for this for quite a while and found some solutions relying on mutationobserver (How to wait until an element exists?). However, I don't understand how to lay a project out like this. I don't know how the manifest would look like and how it would interact with the .js files. I don't know if there would be a background.js or a content script. I'm pretty new to building chrome extensions. Can somebody provide a solution (with file names)? so, when a selector becomes visible (after a drop down is clicked, exposing it, for example) text is sent to that selector via something like:
document.getElementById("Note_2").value = "text sent";
tried this:
popup.js
elementLoaded('1662123261320', function(el) {
// Element is ready to use.
el.click(function() {
alert("You just clicked a dynamically inserted element");
});
});
manifest.json
{
"manifest_version": 2,
"name": "Content Scripts example",
"description": "A simple content_scripts example.",
"version": "1.0",
"content_scripts": [
{
"matches": ["http://news.163.com/*","file://*"],
"js": ["popup.js"]
}
],
"permissions": [
"<all_urls>"
]
}
on this site:
https://inputtypes.com/
doesn't alert. still trying
tried adding the text dropdown as well: "#\31 662123564414-inputType". nothing happens
tried this on paypal.com as well.. login button:
waitForElementToDisplay("#ul-btn",function(){alert("Hi");},1000,9000);
anybody know why it's not working?
edit: I kind of got it... I can get it to alert when an element is present, but not to send text to that element. paypal login:
// Call the below function
waitForElementToDisplay("#email",function(){
document.getElementById("#email").value = "N/A";
},1000,9000);
function waitForElementToDisplay(selector, callback, checkFrequencyInMs, timeoutInMs) {
var startTimeInMs = Date.now();
(function loopSearch() {
if (document.querySelector(selector) != null) {
callback();
return;
}
else {
setTimeout(function () {
if (timeoutInMs && Date.now() - startTimeInMs > timeoutInMs)
return;
loopSearch();
}, checkFrequencyInMs);
}
})();
}
anybody know why the text wont prefill?
ok this works just had to remove the #
paypal login page:
// Call the below function
waitForElementToDisplay("#email",function(){
document.getElementById("email").value = "N/A";
},1000,9000);
function waitForElementToDisplay(selector, callback, checkFrequencyInMs, timeoutInMs) {
var startTimeInMs = Date.now();
(function loopSearch() {
if (document.querySelector(selector) != null) {
callback();
return;
}
else {
setTimeout(function () {
if (timeoutInMs && Date.now() - startTimeInMs > timeoutInMs)
return;
loopSearch();
}, checkFrequencyInMs);
}
})();
}
I am new to Chrome Extension development. I followed some of the tutorials and created an extension. The extension I am trying is to highlight the webelements on HOVER. My problem is that the extension works ONLY when the page loads. It does not work on the already opened active tab. I want to make sure that the extension works in the already opened active tab when clicking the icon.
Manifest:
{
"manifest_version": 2,
"name": "Highlight Web Element",
"description": "Extension for highlighting element in a web page",
"version": "1.0",
"content_scripts": [
{
"matches": ["http://*/*","https://*/*"],
"css": ["core.css"],
"js": ["contentscript.js"],
"run_at": "document_end",
"all_frames": false
}
],
"background" : {
"scripts" : ["background.js"]
},
"browser_action": {
//"default_icon": "logo .png"
},
"permissions": ["tabs", "activeTab"]
}
background.js
chrome.browserAction.onClicked.addListener(function(tab) {
chrome.tabs.executeScript(null, {file: "testScript.js"});
});
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
console.log(sender);
console.log(sender.tab ?
"from a content script:" + sender.tab.url :
"from the extension");
if (request.greeting == "hello")
sendResponse({farewell: request.greeting, "srcElementTagName" : request.srcElementTagName});
});
contentscript.js
// Unique ID for the className.
var MOUSE_VISITED_CLASSNAME = 'plugin_crx_mouse_visited';
// Previous dom, that we want to track, so we can remove the previous styling.
var prevDOM = null;
// Mouse listener for any move event on the current document.
document.addEventListener('mousemove', function (e) {
var srcElement = e.srcElement;
if(prevDOM != srcElement) {
//console.log(srcElement.tagName);
chrome.runtime.sendMessage({greeting: "hello", "srcElementTagName" : srcElement.tagName}, function(response) {
console.log(response.farewell + " : " + response.srcElementTagName);
});
}
// Lets check if our underlying element is a DIV.
//if (srcElement.nodeName == 'DIV') {
// For NPE checking, we check safely. We need to remove the class name
// Since we will be styling the new one after.
if (prevDOM != null) {
prevDOM.classList.remove(MOUSE_VISITED_CLASSNAME);
}
// Add a visited class name to the element. So we can style it.
srcElement.classList.add(MOUSE_VISITED_CLASSNAME);
// The current element is now the previous. So we can remove the class
// during the next iteration.
prevDOM = srcElement;
//}
}, false);
core.css
.plugin_crx_mouse_visited {
background-color: #bcd5eb !important;
outline: 1px solid #5166bb !important;
}
I tried invoking the below code in testScript.js. But not successful.
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
console.log(sender);
console.log(sender.tab ?
"from a content script:" + sender.tab.url :
"from the extension");
if (request.greeting == "hello")
sendResponse({farewell: request.greeting, "srcElementTagName" : request.srcElementTagName});
});
I just got it resolved with the below approach. Thanks Makyen for your reply!!!
{
"name": "Invoke when Extension is Clicked",
"version": "1.0",
"manifest_version": 2,
"browser_action": {
"name": "Click to get URL"
},
"background":{
"scripts":["background.js"]
},
"permissions":["tabs", "activeTab"] //Put All your URL here
}
background.js
chrome.browserAction.onClicked.addListener(function (tab) { //Fired when User Clicks ICON
chrome.tabs.executeScript(tab.id, {
"file": "contentscript.js"
}, function () { // Execute your code
console.log("Script Executed .. "); // Notification on Completion
});
});
contentscript.js
// Unique ID for the className.
var MOUSE_VISITED_CLASSNAME = 'plugin_crx_mouse_visited';
// Previous dom, that we want to track, so we can remove the previous styling.
var prevDOM = null;
// Mouse listener for any move event on the current document.
document.addEventListener('mousemove', function (e) {
var srcElement = e.srcElement;
if(prevDOM != srcElement) {
//console.log(srcElement.tagName);
chrome.runtime.sendMessage({greeting: "hello", "srcElementTagName" : srcElement.tagName}, function(response) {
console.log(response.farewell + " : " + response.srcElementTagName);
});
}
// Lets check if our underlying element is a DIV.
//if (srcElement.nodeName == 'DIV') {
// For NPE checking, we check safely. We need to remove the class name
// Since we will be styling the new one after.
if (prevDOM != null) {
prevDOM.classList.remove(MOUSE_VISITED_CLASSNAME);
}
// Add a visited class name to the element. So we can style it.
srcElement.classList.add(MOUSE_VISITED_CLASSNAME);
// The current element is now the previous. So we can remove the class
// during the next iteration.
prevDOM = srcElement;
//}
}, false);
Enabled the extension using chrome://extensions and when the Icon is clicked on the active tab, the extension got invoked and I was able to highlight the webelement.
I am creating an extension that I need to have the ability to run the content script multiple times when clicking on the page action button. I have this in my background.js:
chrome.pageAction.onClicked.addListener(function(tab) {
alert('calling content_script');
chrome.tabs.executeScript(null, {
file: 'content_script.js'
},function(){alert("success");});
});
This works the first time the button is clicked. When clicked a second time, I get my popup saying "calling content_script" but the content script is never executed. Why is this?
Here is the background script in its entirety:
function checkForValidUrl(tabId, ChangeInfo, tab){
if(tab.url.indexOf("tiger.armstrong")> -1){
chrome.pageAction.show(tabId);
if(tab.url.indexOf("tiger.armstrong") == 0){
chrome.pageAction.hide(tabId);
}
}
}
chrome.tabs.onUpdated.addListener(checkForValidUrl);
chrome.pageAction.onClicked.addListener(function(tab) {
alert('calling content_script');
chrome.tabs.executeScript(null, {
file: 'content_script.js'
},function(){alert("success");});
});
Here is the manifest:
{
"name": "LiveLab Post Grades",
"version": "2.0",
"permissions": [
"activeTab","tabs","http://*/*","https://*/*"
],
"background": {
"scripts": ["jquery.min.js","background3.js"],
"persistent": false
},
"page_action": {
"default_icon": {
"19": "GIcon.png"
},
"default_title": "LiveLab Tools"
},
"content_scripts": [ {
"js": [ "jquery.min.js" ],
"matches": [ "http://*/*", "https://*/*"],
"run_at": "document_end"
}],
"manifest_version": 2
}
Here is the content script:
var livelabtools = {
/**
* this function is like window.open, but it can POST (rather than GET) from js
* source: http://canop.org/blog/?p=426
*/
canop_open: function (verb, url, data, target) {
var form = document.createElement("form");
form.action = url;
form.method = verb;
form.target = target || "_self";
if (data) {
//for (var key in data) {
var input = document.createElement("input");
input.name = 'data';
input.value = data;//typeof data[key] === "object" ? JSON.stringify(data[key]) : data[key];
form.appendChild(input);
//console.log(form);
//}
}
// these two lines are only needed for ie
//form.style.display = 'none';
//document.body.appendChild(form);
form.submit();
console.log("form submit === " + form);
form.remove();
},
post_grades: function () {
alert('in post grades!!!!');
var str, exercise,
i = 0;
grades = {};
do {
ex_str = "form1:tabSet1:tabInstr:lp2:tabSet4:tabp:lpProgress:ts1:tab7:lp7:table3:rg3:" + i + ":tc3:st3";
lname_str = "form1:tabSet1:tabInstr:lp2:tabSet4:tabp:lpProgress:ts1:tab7:lp7:table3:rg3:" + i + ":tc1:st1";
grade_str = "form1:tabSet1:tabInstr:lp2:tabSet4:tabp:lpProgress:ts1:tab7:lp7:table3:rg3:" + i + ":tc7:st7_field";
exercise = document.getElementById(ex_str);
lname = document.getElementById(lname_str);
grade = document.getElementById(grade_str);
if (exercise != null) {
if (grades[lname.innerHTML] === undefined)
grades[lname.innerHTML] = {};
console.log(lname.innerHTML + ", " + exercise.innerHTML + ", " + grade.innerHTML);
if (grade.value != null && grade.value != '')
grades[lname.innerHTML][exercise.innerHTML] = grade.value;
else
grades[lname.innerHTML][exercise.innerHTML] = "0";
}
i++;
} while (exercise != null);
// console.log(JSON.stringify(grades));
// console.log(JSON.stringify(grades).length)
//window.open("http://aspen2.cscofc.info/jsontocsv.php?data="+JSON.stringify(grades));
console.log('posting...' + "\n JSON.String... = "+ JSON.stringify(grades));
livelabtools.canop_open("post", "http://aspen2.cscofc.info/jsontocsv.php", JSON.stringify(grades));
console.log('done');
return "function end";
}
}
console.log(livelabtools.post_grades());
I won't go into detail about it, unless asked but the important parts to note are the return statement and the console log. Everything runs perfectly fine the first time the page action button is clicked, and when finished, I get "function end" printed to the console. After the initial run, however, whenever I click on the page action button, I get an alert saying "calling content_script" and nothing else happens. Why won't my content script run more than once?
It seems like when a script has already been injected it is not injected again.
So, the way to go would be to initiate your action by passing a message to the content script. (Of course, if the content script has not been injected yet, you need to inject it first.)
One solution I came up with (and after testing confirmed it works fine) is the following:
On pageAction.onClicked send a message from the background page to the content script, asking it to do something (e.g. post the grades). Also, request a response to be sent back to the background page.
[See, also, chrome.tabs.sendMessage(...).]
If the content script has already been injected, have it receive the message, send a confirmation back to the background page and proceed doing something (e.g. posting the grades). This process can take place as many times as you want.
[See, also, chrome.runtime.onMessage.]
The first time that the pageAction.onClicked is fired, there will be no content script listening for messages. In that case, there will be no message confirmation. Instead chrome.runtime.lastError will be set. In that case, the background page will have to inject the content script first and then send the message again.
[See, also, chrome.runtime.lastError.]
Theoreticaly speaking, that should do it !
Practicaly, this is the sample code that worked for me:
manifest.json:
(Note: If you have more specific requirements regarding the pages you need to access, you could incorporate them into the manifest and get rid of some of the permissions.)
{
...
"background": {
"persistent": false,
"scripts": ["background.js"]
},
"page_action": {
"default_title": "Test Extension"
},
"permissions": [
"tabs",
"http://*/*",
"https://*/*"
]
...
}
background.js:
function checkForValidURL(tabId, info, tab) {
var idx = tab.url.indexOf("tiger.armstrong");
if (idx > 0) {
chrome.pageAction.show(tabId);
} else {
chrome.pageAction.hide(tabId);
}
}
chrome.tabs.onUpdated.addListener(checkForValidURL);
function onPageActionClicked(tab) {
// Send message to content script, asking to post grades
alert("Calling content_script...");
chrome.tabs.sendMessage(tab.id, { action: "postGrades" }, function() {
if (chrome.runtime.lastError) {
// The error indicates that the content script
// has not been injected yet. Inject it and...
chrome.tabs.executeScript(tab.id, {
file: "content.js"
}, function() {
if (!chrome.runtime.lastError) {
// ...if injected successfully, send the message anew
onPageActionClicked(tab);
}
});
} else {
// The content script called our response callback,
// confirming that it is there and got our message
alert("Message got through !");
}
});
};
chrome.pageAction.onClicked.addListener(onPageActionClicked);
content.js:
var livelabtools = {
/**
* This function is like window.open, but it can POST (rather than GET) from
* JS source: http://canop.org/blog/?p=426
*/
canop_open: function (method, url, data, target) {
var form = document.createElement("form");
form.action = url;
form.method = method;
form.target = target || "_self";
// 'data' is an object with key-value pairs
// of fields to be sent
if (data) {
for (var key in data) {
var input = document.createElement("input");
input.name = key;
input.value = (typeof(data[key]) === "object")
? JSON.stringify(data[key]) : data[key];
form.appendChild(input);
}
}
form.submit();
form.remove();
},
post_grades: function () {
console.log("Posting some grades...");
livelabtools.canop_open("POST",
"http://aspen2.cscofc.info/jsontocsv.php",
"{}");
console.log("Grades sent !");
}
}
// Listen for messages from the background page
// (It actually listens for messages from anyone in the context,
// but the background page is the one that interrests us)
chrome.runtime.onMessage.addListener(function(msg, sender, response) {
// If we've been asked to post grades...
if (msg.action && (msg.action == "postGrades")) {
// ...confirm we got the message and...
response();
// ...do what we do best: post grades !
livelabtools.post_grades();
}
});
Let's hope this covers it :)
I am looking for a function inside a webpage te activate a chrome extension.
Imagine that http://www.example.com/test.html contains:
<script>
hello();
</script>
And my background page contains the definition of the hello function:
function hello() {
alert("test");
}
How can I make sure that the Chrome extension's background page's hello is called when test.html calls hello();?
Before a web page is able to call a background page's function, the following problems need to be solved:
Be able to use hello(); from a web page. This is done by injecting a script defining hello using Content scripts. The injected function communicates with the content script using a custom event or postMessage.
The content script needs to communicate with the background. This is implemented through chrome.runtime.sendMessage.
If the web page needs to receive a reply as well:
Send a reply from the background page (sendMessage / onMessage, see below).
In the content script, create a custom event or use postMessage to send a message to the web page.
In the web page, handle this message.
All of these methods are asynchronous, and have to be implemented through callback functions.
These steps need to be designed carefully. Here's a generic implementation which implements all of the above steps. What you need to know about the implementation:
In the code-to-be-injected, use the sendMessage method whenever the content script need to be contacted.
Usage: sendMessage(<mixed message> [, <function callback>])
contentscript.js
// Random unique name, to be used to minimize conflicts:
var EVENT_FROM_PAGE = '__rw_chrome_ext_' + new Date().getTime();
var EVENT_REPLY = '__rw_chrome_ext_reply_' + new Date().getTime();
var s = document.createElement('script');
s.textContent = '(' + function(send_event_name, reply_event_name) {
// NOTE: This function is serialized and runs in the page's context
// Begin of the page's functionality
window.hello = function(string) {
sendMessage({
type: 'sayhello',
data: string
}, function(response) {
alert('Background said: ' + response);
});
};
// End of your logic, begin of messaging implementation:
function sendMessage(message, callback) {
var transporter = document.createElement('dummy');
// Handles reply:
transporter.addEventListener(reply_event_name, function(event) {
var result = this.getAttribute('result');
if (this.parentNode) this.parentNode.removeChild(this);
// After having cleaned up, send callback if needed:
if (typeof callback == 'function') {
result = JSON.parse(result);
callback(result);
}
});
// Functionality to notify content script
var event = document.createEvent('Events');
event.initEvent(send_event_name, true, false);
transporter.setAttribute('data', JSON.stringify(message));
(document.body||document.documentElement).appendChild(transporter);
transporter.dispatchEvent(event);
}
} + ')(' + JSON.stringify(/*string*/EVENT_FROM_PAGE) + ', ' +
JSON.stringify(/*string*/EVENT_REPLY) + ');';
document.documentElement.appendChild(s);
s.parentNode.removeChild(s);
// Handle messages from/to page:
document.addEventListener(EVENT_FROM_PAGE, function(e) {
var transporter = e.target;
if (transporter) {
var request = JSON.parse(transporter.getAttribute('data'));
// Example of handling: Send message to background and await reply
chrome.runtime.sendMessage({
type: 'page',
request: request
}, function(data) {
// Received message from background, pass to page
var event = document.createEvent('Events');
event.initEvent(EVENT_REPLY, false, false);
transporter.setAttribute('result', JSON.stringify(data));
transporter.dispatchEvent(event);
});
}
});
background.js
chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) {
if (message && message.type == 'page') {
var page_message = message.message;
// Simple example: Get data from extension's local storage
var result = localStorage.getItem('whatever');
// Reply result to content script
sendResponse(result);
}
});
A Chrome extension is not complete without a manifest file, so here's the manifest.json file which I used to test the answer:
{
"name": "Page to background and back again",
"version": "1",
"manifest_version": 2,
"background": {
"scripts": ["background.js"]
},
"content_scripts": [{
"matches": ["http://jsfiddle.net/jRaPj/show/*"],
"js": ["contentscript.js"],
"all_frames": true,
"run_at": "document_start"
}]
}
This extension was tested at http://jsfiddle.net/jRaPj/show/ (containing hello(); as seen in the question), and shows a dialog saying "Background said: null".
Open the background page, use localStorage.setItem('whatever', 'Hello!'); to see that the message is correctly changed.
There is a builtin solution to Send messages from web pages to the extension
mainfest.json
"externally_connectable": {
"matches": ["*://*.example.com/*"]
}
Web page:
// The ID of the extension we want to talk to.
var editorExtensionId = "abcdefghijklmnoabcdefhijklmnoabc";
// Make a simple request:
chrome.runtime.sendMessage(editorExtensionId, {openUrlInEditor: url},
function(response) {
if (!response.success)
handleError(url);
});
Extension's background script:
chrome.runtime.onMessageExternal.addListener(
function(request, sender, sendResponse) {
if (sender.url == blacklistedWebsite)
return; // don't allow this web page access
if (request.openUrlInEditor)
openUrl(request.openUrlInEditor);
});
No, with your above code because of background page(s) architecture
Yes with content scripts
Demonstration Using Content Scripts
manifest.json
Registering content scripts myscripts.js
{
"name": "NFC",
"description": "NFC Liken",
"version": "0.1",
"manifest_version": 2,
"permissions": ["tabs", "http://*/", "https://*/"],
"content_scripts": {
"matches": "http://www.example.com/*",
"js": [ "myscript.js"]
},
"browser_action": {
"default_icon": "sync-icon.png",
"default_title": "I Like I Tag"
}
}
Let me know if you need more information.
I want to see if the current tab is a PDF file from a background page.
I can check the url for .pdf at the end but there are some PDF files that don't have that.
Issuing a new request just to get the MIME type is a bit heavy, and not reliable. For instance, if the currently displayed page is the result of a POST form submission, then issuing a GET request will usually not lead to the same page.
If you're developing an extension that frequently needs access to this information, use the chrome.webRequest API to track the responses. The following demo extension shows the content type upon click of the browser button:
// background.js
var tabToMimeType = {};
chrome.webRequest.onHeadersReceived.addListener(function(details) {
if (details.tabId !== -1) {
var header = getHeaderFromHeaders(details.responseHeaders, 'content-type');
// If the header is set, use its value. Otherwise, use undefined.
tabToMimeType[details.tabId] = header && header.value.split(';', 1)[0];
}
}, {
urls: ['*://*/*'],
types: ['main_frame']
}, ['responseHeaders']);
chrome.browserAction.onClicked.addListener(function(tab) {
alert('Tab with URL ' + tab.url + ' has MIME-type ' + tabToMimeType[tab.id]);
});
function getHeaderFromHeaders(headers, headerName) {
for (var i = 0; i < headers.length; ++i) {
var header = headers[i];
if (header.name.toLowerCase() === headerName) {
return header;
}
}
}
Notes:
This extension only shows the result for tabs which are loaded after the extension is loaded.
This only works on http/https pages. ftp:, file:, filesystem:, blob:, data: is not supported.
When no MIME-type is specified by the server or when the MIME-type is text/plain, Chrome falls back to MIME sniffing unless the X-Content-Type-Options: nosniff is set. In the first case, the detected MIME-type could be anything. In the latter case, the default MIME-type is text/plain.
For completeness, here is a manifest.json file that can be used to test the previous code:
{
"name": "Click button to see MIME",
"version": "1",
"manifest_version": 2,
"background": {
"scripts": ["background.js"],
"persistent": true
},
"browser_action": {
"default_title": "Show MIME"
},
"permissions": [
"webRequest",
"activeTab",
"*://*/*"
]
}
You can't get it using current Chrome API afaik. What you can do is load this page again through XHR and check returned content-type header. Something like this:
background html:
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
if(changeInfo.status == "loading") {
if(checkIfUrlHasPdfExtension(tab.url)) {
//.pdf
pdfDetected(tab);
} else {
var xhr = new XMLHttpRequest();
xhr.open("GET", tab.url, true);
xhr.onreadystatechange = function() {
if (xhr.readyState == 4) {
var contentType = xhr.getResponseHeader("Content-Type");
if(checkIfContentTypeIsPdf(contentType)) {
pdfDetected(tab);
}
}
}
xhr.send();
}
}
});
manifest.json:
"permissions": [
"tabs", "http://*/*", "https://*/*"
]
For PDF files returned content type should be application/pdf. Something to keep in mind though is that content-type header could contain encoding as well: text/html; charset=UTF-8.
You can evaluate the property document.contentType on the current tab.
Here is an example on browserAction :
chrome.browserAction.onClicked.addListener(() => {
chrome.tabs.getSelected((tab) => {
chrome.tabs.executeScript(tab.id, { code: 'document.contentType' }, ([ mimeType ]) => {
alert(mimeType);
});
})
});
This property returns the MIME type that the document is being rendered as, not the Content-Type header (no information about the charset).
A somewhat hackish way (I have no idea if it works always or just sometimes) is to look at the page content. There you will find an element for chrome's PDF viewer. It looks along these lines:
<embed width="100%" height="100%" name="plugin" src="https://example.com/document.pdf" type="application/pdf">
You can check that "type" attribute to see what you are dealing with.
I had to do something similar in one of my extensions and did something very similar to the answer given by #serg but using a HEAD request instead. In theory, a HEAD request should be identical to a GET request but without sending the response body, which in the case of an image or file could be quite a bit of extra data and time waiting.
I also split and shift the header to drop any charsets that might be appended on the content-type.
getContentType: function(tab, callback){
var xhr = new XMLHttpRequest();
xhr.open("HEAD", tab.url, false);
xhr.onload = function(e) {
if (xhr.readyState === 4) {
if(xhr.status === 200) {
callback(xhr.getResponseHeader("Content-Type").split(";").shift());
}
else{
callback('Unknown');
console.error(xhr.statusText);
return;
}
}
};
xhr.onerror = function (e) {
console.error(xhr.statusText);
return;
};
xhr.send();
}