I've been working on a chrome extension just for a bit of fun and trying to learn a bit more javascript to add the +1 button to Google Buzz posts.
I think I'm running into the "Unsafe JavaScript attempt to access frame with URL" error / Cross domain issue. That's according to my chrome console log.
My code is as follows:
function addGJS() {
var po = document.createElement('script'); po.type = 'text/javascript';
po.src = 'https://apis.google.com/js/plusone.js';
po.innerHTML = '{"parsetags": "explicit"}';
jQuery(po).load (function() {
var items;
var startInt = setInterval(function() {
items = $("#canvas_frame").contents().find('.nH.h7.HY.aW');
if (items.length > 0) { clearInterval(startInt); main(items); }
}, 1000);
});
var insert;
var poInt = setInterval(function() {
insert = document.getElementById("canvas_frame");
if (insert != null) { insert.contentDocument.body.appendChild(po); clearInterval(poInt) }
}, 1000);
}
The main content of google buzz appears in the iframe "canvas_frame" so I attempted to add the plus-one src to that. I've had a good search and can't find any definitive answers. I tried to change the type of the iframe to content. I also read about postMessage but not sure if that is possible in this context?
or am I just screwed? :)
Cheers,
UPDATE:
manifest.json (description and icons i've taken out):
"content_scripts": [
{
"js": [ "scripts/jquery.js", "scripts/plusone.js", "user_scripts/buzzplusone.js" ],
"matches": [ "*://*.google.com/*/#buzz", "*://google.com/*/#buzz", "https://mail.google.com/*" ],
"run_at": "document_end"
}],
"permissions": [ "https://mail.google.com/*", "https://plus.google.com/*" ],
Also the console.log errors i'm seeing now are:
Uncaught CustomError: Error in protected function: SYNTAX_ERR: DOM Exception 12
googleapis.client__plusone.js:27
Uncaught TypeError: Cannot read property 'src' of null
Unsafe JavaScript attempt to access frame with URL
https://mail.google.com/mail/#buzz from frame with URL /apps-static//js/nw/nw_i/rt=h/ver=H-Y4RtFct_c.en./am=!oHhtMCRDHb3v-YjahgnJviPf2CNHgPs1tsZl/d=1/">https://plus.google.com//apps-static//js/nw/nw_i/rt=h/ver=H-Y4RtFct_c.en./am=!oHhtMCRDHb3v-YjahgnJviPf2CNHgPs1tsZl/d=1/. Domains, protocols and ports must match
Related
I want to build a extension which is able to get the English word selected by users when reading some English articles and get the whole sentence at the same time.
↓ This is my background.js file. I use getSelection function to send a message to content.js to request a response which contains selection info.
//background.js
chrome.runtime.onInstalled.addListener((tableId, changeInfo, tab) => {
chrome.contextMenus.create({
id: 'addWords',
title: "Send \"%s\" to background",
contexts: ['all']
})
function getSelection(info, tab) {
if (info.menuItemId === "addWords") {
chrome.tabs.sendMessage(tab.id, {action: "getSelection"}, (response) => {
console.log(response);
});
}
}
chrome.contextMenus.onClicked.addListener(getSelection);
});
↓ This is my content.js file. I use onMessage to respond to the background's request.
//content.js
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
if(request.action === "getSelection"){
let selection = window.getSelection();
if(selection.toString() !== ""){
let arr = selection.anchorNode.data.split(".");
let word = selection.toString();
let sentence = arr.find(str => str.includes(word))
alert(word);
sendResponse({word: word, sentence: sentence});
}
}
})
↓ This is my manifest.json file
{
"manifest_version": 3,
"name": "Words Repeater",
"description": "Repeat words what you want to memorize",
"version": "1.0.0",
"permissions": ["contextMenus", "activeTab", "tabs"],
"action": {
"default_popup": "popup.html"
},
"background": {
"service_worker": "./js/background.js"
},
"content_scripts": [
{
"matches": ["http://*/*", "https://*/*"],
"js": ["./js/content.js"]
}
]
}
The extension works correctly initially, but it fails after changing a website and I got a error "Unchecked runtime.lastError: Could not establish connection. Receiving end does not exist."
How to make the context menu run correctly on every tabs? Really appreicate your help!
Your onClicked event listener is misplaced inside onInstalled listener, so it will work only for less than a minute immediately after the installation/update until the background script is auto-terminated, then it will be ignored like it doesn't exist. Move it outside to properly re-register it every time the background script starts on an event like the context menu click.
When the extension is installed/updated its new content script won't be automatically injected into the currently opened tabs in Chrome/ium, so you'll have to do it explicitly yourself as shown here, but there's a much better alternative in cases like yours where the access to the page is necessary only on demand after the user invoked the extension: programmatic injection via executeScript in tandem with the activeTab permission.
remove content_scripts from manifest.json - now your extension won't require broad host permissions i.e. there'll be no installation warning in the web store.
remove "tabs" from "permissions" - it's not necessary and now there'll be no warning about observing the browser history.
add "scripting" to "permissions".
Consider limiting the contexts to "selection" to show it only when text is selected, and not show in the wrong contexts like the built-in menu of the extension's icon in the toolbar.
Parameters of chrome.runtime.onInstalled were incorrectly copied from chrome.tabs.onUpdated listener, but since they are unused you can remove them.
chrome.runtime.onInstalled.addListener(() => {
chrome.contextMenus.create({
id: 'addWords',
title: 'Send "%s" to background',
contexts: ['selection'],
});
});
chrome.contextMenus.onClicked.addListener(async (info, tab) => {
if (info.menuItemId === 'addWords') {
let word = info.selectionText.trim();
let sentence;
try {
[{ result: sentence }] = await chrome.scripting.executeScript({
target: {
tabId: tab.id,
frameIds: [info.frameId],
},
func: () => {
const selection = window.getSelection();
const arr = selection.anchorNode.data.split('.');
const word = selection.toString();
const sentence = arr.find(str => str.includes(word));
return sentence;
},
});
} catch (e) {} // invoked on a page that doesn't allow injection
console.log(word, sentence); // this prints in background console
}
});
The only thing I could think of was using chrome.tabs.discard, and below is my current code:
var test_button= document.getElementById('test_button');
test_button.onclick = function(element) {
var to_load = {"url": "https://stackoverflow.com", "active": false, "selected": false};
chrome.tabs.create(to_load, function(tab) {
chrome.tabs.discard(tab.id);
});
};
However, rather than preventing this page from loading, calling chrome.tabs.discard before it's loaded results in Chrome replacing it with about:blank.
The only "solution" I found was to wait for the tab to load, but waiting for it to load before unloading it defeats the purpose, especially if I'm opening a large amount of tabs at once.
Any help would be appreciated.
The solution is to only call chrome.tabs.discard on the tab after its URL value has updated, such as:
var tabs_to_unload = {}
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, changedTab) {
if (tabs_to_unload[tabId] == true) {
// We can only discard the tab once its URL is updated, otherwise it's replaced with about:empty
if(changeInfo.url) {
chrome.tabs.discard(tabId);
delete tabs_to_unload[tabId];
}
}
});
var test_button= document.getElementById('test_button');
test_button.onclick = function(element) {
var to_load = {"url": "https://stackoverflow.com", "active": false, "selected": false};
chrome.tabs.create(to_load, function(tab) {
tabs_to_unload[tab.id] = true;
});
};
In my case, the exact code was a bit different, as I was performing these actions from within a popup, and the variables and listeners registered by its script only lived as long as the popup, but the principle behind it was the same.
i have to add div in google search page through chrome extension
my inject.js file is as follow:
var body=document.getElementById("search");
//creates bar and creates bar removal function
var bar = document.createElement("DIV");
function removeBar(){
bar.remove();
}
//styles bar
var ds = bar.style;
ds.position = "fixed";
ds.width = "512px";
ds.height = "33px";
ds.background = "rgba(0,0,0,0.86)";
ds.zIndex = "9999999999999";
//creates X button and makes it so clicking it runs the removeBar() function
var x = document.createElement("BUTTON");
x.onclick = removeBar;
bar.appendChild(x);
//styles button
var xs = x.style;
xs.background = "black";
xs.borderColor = "black";
xs.color = "rgba(255,255,255,.86)";
xs.position = "fixed";
xs.left = "100%";
xs.marginTop = "5px";
xs.marginLeft = "-29px"
//puts X in button
var xtext = document.createTextNode("X");
x.appendChild(xtext);
//puts bar in page
body.insertBefore(bar, body.children[0]);
and my manifest.json is as follow:
{
"manifest_version": 2,
"name": "Inject script in webpage",
"version": "2.0.1",
"description": "inject script in webpage.",
"icons": {
"48" : "sample-48.png",
"128" : "sample-128.png"
},
"content_scripts": [
{
"matches": ["https://www.google.com.pk/*"],
"js" : ["inject.js"],
"all_frames": true
}
]
}
but when i load google SERP page, error message shows somewhat like " cannot call "insertBefore" property of null", it seems that variable "body" is undefined. by inspect element of google page i checked that DIV with ID "search" is present if i am doing it wrong then plzz let me know i am novice in chrome extension development
The problem is probably here:
var body=document.getElementById("search");
the "body" variable is null - probably because the id you're looking for doesn't exist. Try looking for some other id (I used "gbqfb"). But rather than feeding you the answer here, what you should really do is learn to use Chrome's built-in debugger to find out why your code isn't working. Have a look at this youtube video:
https://www.youtube.com/watch?v=htZAU7FM7GI
I'm trying to modify the response headers of the images to save bandwith and improve the response time.These are my files:
manifest.json
{
"name": "Cache all images",
"version": "1.0",
"description": "",
"background": {"scripts": ["cacheImgs.js"]},
"permissions": [ "<all_urls>", "webRequest", "webRequestBlocking" ],
"icons": {"48": "48.png"},
"manifest_version": 2
}
cacheImgs.js
var expDate = new Date(Date.now()+1000*3600*24*365).toUTCString();
var newHeaders =
[{name : "Access-Control-Allow-Origin", value : "*"},
{name : "Cache-Control", value : "public, max-age=31536000"},
{name : "Expires", value : expDate},
{name : "Pragma", value : "cache"}];
function handler(details) {
var headers = details.responseHeaders;
for(var i in headers){
if(headers[i].name.toLowerCase()=='content-type' && headers[i].value.toLowerCase().match(/^image\//)){
for(var i in newHeaders) {
var didSet = false;
for(var j in headers) {
if(headers[j].name.toLowerCase() == newHeaders[i].name.toLowerCase() ) {
headers[j].value = newHeaders[i].value;
did_set = true; break;
}
}
if(!didSet) { headers.push( newHeaders[i] ); }
}
break;
}
}
console.log(headers);
return {responseHeaders: headers}
};
var requestFilter = {urls:['<all_urls>'], types: ['image'] };
var extraInfoSpec = ['blocking', 'responseHeaders'];
chrome.webRequest.onHeadersReceived.addListener(handler, requestFilter, extraInfoSpec);
the console.log fires many times and i can see the new headers. The problem is that when I open the chrome developer tools of the page, in the network tab, i see the same original headers of the images. Also note the blocking value in the extraInfoSpec, so that's supposed to be synchronous. Does someone happen the same?
UPDATE
Now I see the modified response headers in the network panel.
But now I only see from images whose initiator is the webpage itself. The images whose initiator are jquery.min.js doesn't change the response headers
There are two relevant issues here.
First, the headers displayed in the developer tools are those that are received from the server. Modifications by extensions do not show up (http://crbug.com/258064).
Second (this is actually more important!), modifying the cache headers (such as Cache-control) has no influence on the caching behavior of Chromium, because the caching directives have already been processed when the webRequest API is notified of the headers.
See http://crbug.com/355232 - "Setting caching headers in webRequest.onHeadersReceived does not influence caching"
After doing some research, it turns out my previous answer was wrong. This is actually a Chrome bug - Chrome's DevTools Network panel will only show the actual headers received from the server. However, the headers you've injected will still have the desired effect.
Another extension developer identified the issue here and provided a link to the Chrome defect report
I m trying to port my firefox plugin to chrome and here is my sample code.
File: myscript.js (partial)
.
.
function init() {
.
.
.
}
function myFunc(inp, option) {
.
.
.
}
chrome.extension.onMessage.addListener(function (message, sender, response) {
switch (message) {
case "ITRANS":
console.log("ITRANS");
if (document.getSelection().baseNode != null){
init();
window.modifySelection(myFunc(window.getSelection().toString(), 0));
}
break;
case "Devanagari":
console.log("Devanagari");
if (document.getSelection().baseNode != null){
init();
window.modifySelection(myFunc(window.getSelection().toString(), 1));
}
break;
default:
console.log("Default");
}
});
File: background.js
var _selection_univ = chrome.contextMenus.create({
"title": "INDIC 2 ITRANS",
"id": "ITRANS",
"onclick": reportclick,
"contexts": ["selection"]
}, function () {
console.log("Context Menu 1 ITRANS");
});
var _selection_univ = chrome.contextMenus.create({
"title": "Eng 2 Devanagari",
"id": "Devanagari",
"onclick": reportclick,
"contexts": ["selection"]
}, function () {
console.log("Context Menu 2 Devanagari");
});
function reportclick(info, tab) {
switch (info.menuItemId) {
case "ITRANS":
console.log("BG: ITRANS");
chrome.tabs.sendMessage(tab.id, "ITRANS");
break;
case "Devanagari":
console.log("BG: Devanagari");
chrome.tabs.sendMessage(tab.id, "Devanagari");
break;
default:
console.log("BG: Default");
}
}
File: manifest.json
{
"name": "Parivartan",
"version": "0.8.2",
"manifest_version": 2,
"permissions":[
"contextMenus",
"<all_urls>",
"tabs"
],
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["myscript.js"],
"all_frames": true
}
],
"background": {
"scripts": ["background.js"]
}
}
I am not able to figure out few things.
(1) Where should my init() function (which should run only once to inititialize my plugin globals) be placed.
(2) Replace the selected text with the output of a function.
The above code does not work says "modifySelection" not found.
(3) How can call my functions if they are in a different (file2.js) file.
At present I placed all my functions in a single file (myscript.js).
(4) How can I create menu within a menu.
I tried to search on google but could not find solutions to the above. Can anyone please help me.
-Mohan
(1) Where should my init() function (which should run only once to inititialize my plugin globals) be placed ?
Depending on your requirements there are two events that should cover your initialization needs:
chrome.runtime.onInstalled:
Fired when the extension is first installed, when the extension is updated to a new version, and when Chrome is updated to a new version.
E.g.: chrome.runtime.onInstalled.addListener(function() {...});
chrome.runtime.onStartup:
Fired when a profile that has this extension installed first starts up. This event is not fired when an incognito profile is started, even if this extension is operating in 'split' incognito mode.
E.g.: chrome.runtime.onStartup.addListener(function(details) {...});
(2) Replace the selected text with the output of a function. The above code does not work says "modifySelection" not found.
That is because the function modifySelection is not defined. Where did you get that name ?
UPDATE:
Based on OP's feedback in comments, a simple modifySelection() function could look like this:
function modifySelection(newValue) {
var range = document.getSelection().getRangeAt(0);
range.deleteContents();
range.insertNode(document.createTextNode(newValue));
}
(Note: It will only work properly if the selection involves TextNodes only. In other cases it might break the DOM, so more detailed parsing of the selection is required.)
(3) How can call my functions if they are in a different (file2.js) file. At present I placed all my functions in a single file (myscript.js).
You inject all the necessary files and then you call the functions as usual. I.e. all injected content scripts are executed in the same JS context. E.g.:
"content_scripts": [{
"matches": ["<all_urls>"],
"js": ["file1.js", "file2.js", ...],
"all_frames": true
}],
In file1.js:
...
function funcInFile1() {...}
...
In file2.js:
...
var res = funcInFile1();
...
(Note: Content scripts are injected in the order in which they appear in the "js" array. Make sure each resource is available before calling it. E.g. trying to call funcInFile1() before injecting file1.js will result in error.)
(4) How can I create menu within a menu.
If by that you mean "create a submenu", there is an parentId attribute that you can include in the createProperties argument of the chrome.contextMenus.create function:
parentId:
The ID of a parent menu item; this makes the item a child of a previously added item.
See, also, this demo extension which does (among other things) exactly that.
Some final remarks
chrome.extension.onMessage is deprecated. Please, use chrome.runtime.onMessage instead.
Try using Event Pages (instead of background pages) when possible.