chrome-extention replace the selected text - google-chrome-extension

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.

Related

Chrome extension contextmenus sendResponse doesn't work on some pages

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
}
});

Chrome extension for selected text which matches a specific form [duplicate]

I am trying to create entries on the Chrome context menu based on what is selected.
I found several questions about this on Stackoverflow, and for all of them the answer is: use a content script with a "mousedown" listener that looks at the current selection and creates the Context Menu.
I implemented this, but it does not always work. Sometimes all the log messages say that the context menu was modified as I wanted, but the context menu that appears is not updated.
Based on this I suspected it was a race condition: sometimes chrome starts rendering the context menu before the code ran completely.
I tried adding a eventListener to "contextmenu" and "mouseup". The later triggers when the user selects the text with the mouse, so it changes the contextmenu much before it appears (even seconds). Even with this technique, I still see the same error happening!
This happens very often in Chrome 22.0.1229.94 (Mac), occasionally in Chromium 20.0.1132.47 (linux) and it did not happen in 2 minutes trying on Windows (Chrome 22.0.1229.94).
What is happening exactly? How can I fix that? Is there any other workaround?
Here is a simplified version of my code (not so simple because I am keeping the log messages):
manifest.json:
{
"name": "Test",
"version": "0.1",
"permissions": ["contextMenus"],
"content_scripts": [{
"matches": ["http://*/*", "https://*/*"],
"js": ["content_script.js"]
}],
"background": {
"scripts": ["background.js"]
},
"manifest_version": 2
}
content_script.js
function loadContextMenu() {
var selection = window.getSelection().toString().trim();
chrome.extension.sendMessage({request: 'loadContextMenu', selection: selection}, function (response) {
console.log('sendMessage callback');
});
}
document.addEventListener('mousedown', function(event){
if (event.button == 2) {
loadContextMenu();
}
}, true);
background.js
function SelectionType(str) {
if (str.match("^[0-9]+$"))
return "number";
else if (str.match("^[a-z]+$"))
return "lowercase string";
else
return "other";
}
chrome.extension.onMessage.addListener(function(msg, sender, sendResponse) {
console.log("msg.request = " + msg.request);
if (msg.request == "loadContextMenu") {
var type = SelectionType(msg.selection);
console.log("selection = " + msg.selection + ", type = " + type);
if (type == "number" || type == "lowercase string") {
console.log("Creating context menu with title = " + type);
chrome.contextMenus.removeAll(function() {
console.log("contextMenus.removeAll callback");
chrome.contextMenus.create(
{"title": type,
"contexts": ["selection"],
"onclick": function(info, tab) {alert(1);}},
function() {
console.log("ContextMenu.create callback! Error? " + chrome.extension.lastError);});
});
} else {
console.log("Removing context menu")
chrome.contextMenus.removeAll(function() {
console.log("contextMenus.removeAll callback");
});
}
console.log("handling message 'loadContextMenu' done.");
}
sendResponse({});
});
The contextMenus API is used to define context menu entries. It does not need to be called right before a context menu is opened. So, instead of creating the entries on the contextmenu event, use the selectionchange event to continuously update the contextmenu entry.
I will show a simple example which just displays the selected text in the context menu entry, to show that the entries are synchronized well.
Use this content script:
document.addEventListener('selectionchange', function() {
var selection = window.getSelection().toString().trim();
chrome.runtime.sendMessage({
request: 'updateContextMenu',
selection: selection
});
});
At the background, we're going to create the contextmenu entry only once. After that, we update the contextmenu item (using the ID which we get from chrome.contextMenus.create).
When the selection is empty, we remove the context menu entry if needed.
// ID to manage the context menu entry
var cmid;
var cm_clickHandler = function(clickData, tab) {
alert('Selected ' + clickData.selectionText + ' in ' + tab.url);
};
chrome.runtime.onMessage.addListener(function(msg, sender, sendResponse) {
if (msg.request === 'updateContextMenu') {
var type = msg.selection;
if (type == '') {
// Remove the context menu entry
if (cmid != null) {
chrome.contextMenus.remove(cmid);
cmid = null; // Invalidate entry now to avoid race conditions
} // else: No contextmenu ID, so nothing to remove
} else { // Add/update context menu entry
var options = {
title: type,
contexts: ['selection'],
onclick: cm_clickHandler
};
if (cmid != null) {
chrome.contextMenus.update(cmid, options);
} else {
// Create new menu, and remember the ID
cmid = chrome.contextMenus.create(options);
}
}
}
});
To keep this example simple, I assumed that there's only one context menu entry. If you want to support more entries, create an array or hash to store the IDs.
Tips
Optimization - To reduce the number of chrome.contextMenus API calls, cache the relevant values of the parameters. Then, use a simple === comparison to check whether the contextMenu item need to be created/updated.
Debugging - All chrome.contextMenus methods are asynchronous. To debug your code, pass a callback function to the .create, .remove or .update methods.
MDN doc for menus.create(), 'title' param
You can use "%s" in the string. If you do this in a menu item, and some text is selected in the page when the menu is shown, then the selected text will be interpolated into the title.
https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/menus/create
Thus
browser.contextMenus.create({
id: 'menu-search',
title: "Search '%s'", // selected text as %s
contexts: ['selection'], // show only if selection exist
})

Website Message Box by Automating InternetExplorer with VBA Excel

I am in the process of automating a process to fill up a website. With an Excel Macro, once I log into the website, insert a value in the required textbox and click the button, a website Message Box comes up with an alert asking me to confirm - Are you sure to update the value?
The execution of the macro stops at that level, resulting in no further execution of the macro.
On searching for the solution, I found out that a JavaScript function, the function which is executed on confirmation of the message box, should be called from macro instead of clicking the original button on the webpage.
I would like to have help in writing the code to call JavaScript function in Excel Macro.
Following is the HTML code from the view source page of the webpage.
$('#reloadButton').click(function () {
$(this).text(
$(this).attr('name')
).attr('disabled', 'disabled');
window.location.href = window.location.href.replace(/#.*$/, '');
});
SignalConsumer = function () {};
SignalConsumer.prototype = new TraderSettingsTool();
SignalConsumer.prototype.mySummaryPage = 'https://kvinvest.com/month/?action=template&tid=my_status';
SignalConsumer.prototype.isShowWaiver = 0;
SignalConsumer.prototype.amountPrecision = 1;
SignalConsumer.prototype._elements = {
"trading": {
"popup": $('#ssc-trading-popup'),
"amount": $('#ssc-trading-amount'),
"trade": $('#ssc-trading-trade'),
"provides": $('#ssc-trading-provides')
},
"slippage": {
"popup": $('#ssc-slippage-popup')
},
"provider": {
"popup": $('#ssc-provider-popup')
},
"consumers":{
"holder": $('#ssc-consumers-holder'),
"template": $('#ssc-consumers-template'),
"form": $('#ssc-consumers-form')
},
"subscribe": {
"server": $('#ssc-subscribe-server'),
"apply": $('#ssc-subscribe-apply'),
"loader": $('#ssc-subscribe-loader'),
"info": $('#ssc-subscribe-info'),
"form": $('#ssc-subscribe-form'),
"description": $('#ssc-subscribe-description')
},
"activate": {
"form": $('#ssc-activate-form'),
"slippage": $('#ssc-activate-slippage'),
"amount": $('#ssc-activate-amount'),
"popup": $('#ssc-activate-popup'),
"apply": $('#ssc-activate-apply'),
"cancel": $('#ssc-activate-cancel'),
"agree": $('#ssc-activate-agree'),
"sll": $('#ssc-activate-sll-value'),
"loader": $('#ssc-activate-sll-loader'),
"redirect": $('#ssc-activate-redirect')
},
"waiver": {
"popup": $('#ssc-waiver-popup'),
"agree": $('#ssc-waiver-agree'),
"apply": $('#ssc-waiver-apply'),
"subscribe": $('#ssc-waiver-subscribe')
},
"history": {
"log": $('#ssc-history-log')
}
};
SignalConsumer.prototype.bindEvents = function () {
var self = this;
this._elements.subscribe.form.find('form').submit(function () {
return false;
});
// I THINK BELOW IS THE MESSAGE BOX POP UP
this._elements.subscribe.apply.click(function () {
if(!confirm('Are you sure to update?')){
return false;
}
self.subscribeToServer();
return false;
});
// On show history popup
this._elements.history.log.click(function () {
self.loadHistoryLog();
return false;
});
// --- ACTIVATION LOGIC ---
this._elements.activate.apply.click(function () {
self.applyActivateServer();
return false;
});
this._elements.activate.agree.change(function () {
var disabled = $(this).is(':checked') ? '' : 'disabled';
self._elements.activate.apply.attr('disabled', disabled);
});
this._elements.activate.cancel.click(function () {
self.hidePopUp();
return false;
});
this._elements.activate.redirect.click(function () {
self.hidePopUp();
});
Well, what I won't do in this answer is provide the code that you request.
This answer is more like a suggestion since I'm not entirely sure about where you are going with this and if the technical approach that you suggest is what you are actually looking for.
It doesn't make a lot of sense to me, to connect the VBA engine with a web client unless you're aiming at retrieval of data only - such as for example a web query.
If you want to create an interactive data flow between the VBA engine and a web application, it seems more logical to create a connection with a server side script (written in PHP or ASP) that is connected to a database system (or if you want to store values in temporary session variables for that matter).
The fact that a user inputs a value, followed by a button click suggests that you want to build in a certain calculation logic. This is typically done on the server, and not browser level.
Therefore what I suggest you do is:
Javascript/jQuery -> PHP/ASP -> VBA
If that makes any sense to you.

Only allow one active instance of a Chrome extension

I have a Chrome extension which needs to be open only in one window per machine at a time.
What would be the best way to enforce this condition? For example, is there a mechanism to point the user to an existing tab running the extension, if there exists such a tab?
The relevant parts of my manifest file are as follows:
manifest.json
{
"manifest_version": 2,
"browser_action": {
"default_icon": "/img/favicon.ico",
"popup": "main.html"
},
"background": {
"scripts": ["open.js"]
}
}
And the open.js reads as follows:
open.js
chrome.browserAction.onClicked.addListener(function(tab) {
chrome.tabs.create({'url': chrome.extension.getURL('test.html')}, function(tab) {
});
});
Update Updated to also include focusing on a tab that is in a different window
Since no one posted the answer to this question here it is, taken from Rob W's github as linked in the comments under the question https://github.com/Rob--W/stackexchange-notifications/blob/8947b9982cd7b9e04ccf0ef23510571f39d33c4e/Chrome/using-websocket.js#L66-L82.
This is a more basic version replacing the openTab() function found in the original code with the basic command to open a new tab.
var options_url = chrome.extension.getURL('options.html');
chrome.tabs.query({
url: options_url
}, function(tabs) {
if (tabs.length == 0) {
chrome.tabs.create({ url: "main.html" });
} else {
// If there's more than one, close all but the first
for (var i=1; i<tabs.length; i++)
chrome.tabs.remove(tabs[i].id);
// And focus the options page
chrome.tabs.update(tabs[0].id, {active: true});
chrome.windows.update(tabs[0].windowId, {focused:true})
}
});

Applying google plus one src to iframe in chrome extension

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

Resources