I have a settings page on my WebExtension, but I dont know how to acces the values of the settings with javascript.
Current .xul-File:
<?xml version="1.0"?>
<!DOCTYPE mydialog SYSTEM "chrome://myaddon/locale/mydialog.dtd">
<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<setting type="string" pref="extensions.check.email" title="email" desc="please insert your email here" />
</vbox>
How do I acces the "email" value? Can I just write something like "getPreferences('email')"?
Don't use XUL in a WebExtension add-on:
If you are using XUL from within a WebExtension, then something is probably wrong. If you are writing a WebExtension, and you start to do XUL: take a step back. Make sure that is really what you are supposed to be doing. One of the points of WebExtensions is that it insulates the add-on from the internals of Firefox (i.e. from XUL).
Options panels and pages:
In general, options should be stored in storage.local (or storage.sync, when supported). If you are attempting to communicate from an options page, or a panel back to your main background script there are, at least, 4 somewhat different ways to do so:
Options are stored to storage.local in the options.js code. The background code listens for events from storage.onChanged. No need to pass messages back and forth. No need for your options/panel code to specifically notify the background page that changes have occurred.
Options are stored to storage.local in the options.js code. Then, the options.js directly invokes the a function in your background script to have the background script re-read the options. In the code below, the getOptions() function in the background.js file is directly invoked by the options.js code.
Options are stored to storage.local in the options.js code. Use chrome.runtime.sendMessage() to send a message to your background page that the options have changed. In the code below: After storing the options in storage.local, the options.js sends an optionsUpdated message to the background script that the options have been updated. The background script then re-reads the options. [The message can be whatever you want that indicates this. optionsUpdated is merely what I chose as the message in the code below.]
Use chrome.runtime.sendMessage() to send a message with all options data to the background page. In the code below: An optionsData message is sent from the options.js code to the background page when the options are change which contains a data payload with all of the options. The options are then stored to storage.local in the background script. Once the options are stored, the background script sends a optionsStored message back to the options.js code. The options.js code then indicates to the user that the options have been saved. [The message can be whatever you want that indicates this. optionsData and optionsStored is merely what I chose as the message in the code below.]
Below is a WebExtension that demonstrates those four different methods of getting the changed option information back to the background script.
Note: The code below uses a browser_action button to bring up a panel with the exact same options as are used for options_ui. This is done for the purpose of demonstration. If you are going to have a button that opens your options, it may be better to directly open your options_ui page with runtime.openOptionsPage(). Which you do depends on the user interface you want to present to the user.
background.js:
var useDirect=0; //Holds the state of how we communicate with options.js
var emailAddress=''; //The email address from options.
const useDirectTypes=[ 'Listen to chrome.storage changes'
,'Directly invoke functions in the background script from'
+ ' the options/panel code'
,'Send a message that data in storage.local was updated'
,'Send a message with all options data' ];
//Register the message listener
chrome.runtime.onMessage.addListener(receiveMessage);
function receiveMessage(message,sender,sendResponse){
//Receives a message that must be an object with a property 'type'.
// This format is imposed because in a larger extension we may
// be using messages for multiple purposes. Having the 'type'
// provides a defined way for other parts of the extension to
// both indicate the purpose of the message and send arbitrary
// data (other properties in the object).
console.log('Received message: ',message);
if(typeof message !== 'object' || !message.hasOwnProperty('type')){
//Message does not have the format we have imposed for our use.
//Message is not one we understand.
return;
}
if(message.type === "optionsUpdated"){
//The options have been updated and stored by options.js.
//Re-read all options.
getOptions();
}
if(message.type === "optionsData"){
saveOptionsSentAsData(message.data,function(){
//Callback function executed once data is stored in storage.local
console.log('Sending response back to options page/panel');
//Send a message back to options.js that the data has been stored.
sendResponse({type:'optionsStored'});
//Re-read all options.
getOptions();
});
//Return true to leave the message channel open so we can
// asynchronously send a message back to options.js that the
// data has actually been stored.
return true;
}
}
function detectStorageChange(change){
//Ignore the change information. Just re-get all options
console.log('Background.js: Detected storage change');
getOptions();
}
function listenToStorageChanges(){
chrome.storage.onChanged.addListener(detectStorageChange);
}
function stopListeningToStorageChanges(){
chrome.storage.onChanged.removeListener(detectStorageChange);
}
function getOptions(){
//Options are normally in storage.sync (sync'ed across the profile).
//This example is using storage.local.
//Firefox does not currently support storage.sync.
chrome.storage.local.get({
useDirect: 0,
emailAddress: ''
}, function(items) {
if(typeof items.useDirect !== 'number' || items.useDirect <0
|| items.useDirect >= useDirectTypes.length) {
items.useDirect=0;
}
useDirect = items.useDirect;
emailAddress = items.emailAddress;
console.log('useDirect=' + useDirectTypes[useDirect]);
console.log('email address=' + emailAddress);
});
}
function saveOptionsSentAsData(data,callback) {
//Options data received as a message from options.js is
// stored in storeage.local.
chrome.storage.local.set(data, function() {
//Invoke a callback function if we were passed one.
if(typeof callback === 'function'){
callback();
}
});
}
//Read the options stored from prior runs of the extension.
getOptions();
//On Firefox, open the Browser Console:
//To determine if this is Chrome, multiple methods which are not implemented
// in Firefox are checked. Multiple ones are used as Firefox will eventually
// support more APIs.
var isChrome = !! window.InstallTrigger
|| (!!chrome.extension.setUpdateUrlData
&& !!chrome.runtime.reload
&& !!chrome.runtime.restart);
if(!isChrome) {
//In Firefox cause the Browser Console to open by using alert()
window.alert('Open the console. isChrome=' + isChrome);
}
options.js:
// Saves options to chrome.storage.local.
// It is recommended by Google that options be saved to chrome.storage.sync.
// Firefox does not yet support storage.sync.
function saveOptions(data, callback) {
chrome.storage.local.set(data, function() {
if(typeof callback === 'function'){
callback();
}
// Update status to let user know options were saved.
notifyOptionsSaved();
});
}
function optionsChanged() {
//Get the selected option values from the DOM
let useDirectValue = document.getElementById('useDirect').value;
let email = document.getElementById('email').value;
useDirectValue = +useDirectValue; //Force to number, not string
//Put all the option data in a single object
let optionData = {
useDirect: useDirectValue,
emailAddress: email
}
setBackgroundPageNotListeningToStorageChanges();
if(useDirectValue == 0 ) {
//Use listening to storage changes
//console.log('Going to listen for storage changes');
setBackgroundPageListeningToStorageChanges();
saveOptions(optionData);
} else if (useDirectValue == 1) {
//We save the options in the options page, or popup
saveOptions(optionData, function(){
//After the save is complete:
//The getOptions() functon already exists to retrieve options from
// storage.local upon startup of the extension. It is easiest to use that.
// We could remove and add the listener here, but that code already
// exists in background.js. There is no reason to duplicate the code here.
let backgroundPage = chrome.extension.getBackgroundPage();
backgroundPage.getOptions();
});
} else if (useDirectValue == 2) {
//We save the options in the options page, or popup
saveOptions(optionData, function(){
//Send a message to background.js that options in storage.local were updated.
chrome.runtime.sendMessage({type:'optionsUpdated'});
});
} else {
//Send all the options data to background.js and let it be dealt with there.
chrome.runtime.sendMessage({
type:'optionsData',
data: optionData
}, function(message){
//Get a message back that may indicate we have stored the data.
if(typeof message === 'object' && message.hasOwnProperty('type')){
if(message.type === 'optionsStored') {
//The message received back indicated the option data has
// been stored by background.js.
//Notify the user that the options have been saved.
notifyOptionsSaved();
}
}
});
}
}
function setBackgroundPageListeningToStorageChanges(){
//Normally the listener would be set up once, and only once, within the
// background page script. We are doing it here to demonstrate switing
// between the different methods of notification.
let backgroundPage = chrome.extension.getBackgroundPage();
//either add the listener directly:
chrome.storage.onChanged.addListener(backgroundPage.detectStorageChange);
//or let the background page add it:
//backgroundPage.listenToStorageChanges();
}
function setBackgroundPageNotListeningToStorageChanges(){
//Normally the listener would be set up once, and only once, within the
// background page script. We are doing it here to demonstrate switing
// between the different methods of notification.
let backgroundPage = chrome.extension.getBackgroundPage();
//either remove the listener directly:
chrome.storage.onChanged.removeListener(backgroundPage.detectStorageChange);
//or let the background page add it:
//backgroundPage.stopListeningToStorageChanges();
}
// Restores select box and input using the preferences
// stored in chrome.storage.
function useStoredOptionsForDisplayInDOM() {
chrome.storage.local.get({
useDirect: 0,
emailAddress: ''
}, function(items) {
//Store retrieved options as the selected values in the DOM
document.getElementById('useDirect').value = items.useDirect;
document.getElementById('email').value = items.emailAddress;
});
//notifyStatusChange('Option read');
}
function notifyOptionsSaved(callback){
//Notify the user that the options have been saved
notifyStatusChange('Options saved.',callback);
}
function notifyStatusChange(newStatus,callback){
let status = document.getElementById('status');
status.textContent = newStatus;
//Clear the notification after a second
setTimeout(function() {
status.textContent = '';
if(typeof callback === 'function'){
callback();
}
}, 1000);
}
document.addEventListener('DOMContentLoaded', useStoredOptionsForDisplayInDOM);
document.getElementById('optionsArea').addEventListener('change',optionsChanged);
//In order to track the change if this is open in _both_ the browser_action panel
// and the add-on options page, we need to listen for changes to storage.
// There is already a function which reads all of the options, so don't
// use the information as to what changed, just that a change occurred.
chrome.storage.onChanged.addListener(useStoredOptionsForDisplayInDOM);
options.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>WebRequest Logging Options</title>
<style>
body: { padding: 10px; }
</style>
</head>
<body>
<div id="optionsArea">
Communication with background page:
<select id="useDirect">
<option value="0">Listen for storage changes</option>
<option value="1">Directly call background page functions</option>
<option value="2">Send a Message Updated storage.local</option>
<option value="3">Send a Message with all Data</option>
</select>
<div>Email:
<input id="email"></input>
</div>
</div>
<div id="status" style="top:0px;display:inline-block;"></div>
<script src="options.js"></script>
</body>
</html>
manifest.json:
{
"description": "Demonstrate an email field in options with various ways of communicating with the background script.",
"manifest_version": 2,
"name": "email-in-options-demo",
"version": "0.1",
"applications": {
"gecko": {
//Firefox: must define id to use option_ui:
"id": "email-in-options-demo#example.example",
"strict_min_version": "48.0"
}
},
"permissions": [
"storage"
],
"background": {
"scripts": [
"background.js"
]
},
"browser_action": {
"default_icon": {
"48": "myIcon.png"
},
"default_title": "Show panel",
"browser_style": true,
"default_popup": "options.html"
},
"options_ui": {
"page": "options.html",
"chrome_style": true
}
}
The code above is based on code in my answer to Update WebExtension webRequest.onBeforeRequest listener URL settings from separate script.
Related
I have a function in the context.js which loads a panel and sends a message to panel.js at the last. The panel.js function updates the ui on receiving that msg. But it is not working for the first click i.e. it just loads normal ui, not the one that is expected that is updated one after the msg is received. while debugging it works fine.
manifest.json
"background": {
"scripts": ["background.js"],
"persistent": false
},
"content_scripts": [{
"all_frames": false,
"matches": ["<all_urls>"],
"js":["context.js"]
}],
"permissions": ["activeTab","<all_urls>", "storage","tabs"],
"web_accessible_resources":
"panel.html",
"panel.js"
]
context.js - code
fillUI (){
var iframeNode = document.createElement('iframe');
iframeNode.id = "panel"
iframeNode.style.height = "100%";
iframeNode.style.width = "400px";
iframeNode.style.position = "fixed";
iframeNode.style.top = "0px";
iframeNode.style.left = "0px";
iframeNode.style.zIndex = "9000000000000000000";
iframeNode.frameBorder = "none";
iframeNode.src = chrome.extension.getURL("panel.html")
document.body.appendChild(iframeNode);
var dataForUI = "some string data"
chrome.runtime.sendMessage({action: "update UI", results: dataForUI},
(response)=> {
console.log(response.message)
})
}
}
panel.js - code
var handleRequest = function(request, sender, cb) {
console.log(request.results)
if (request.action === 'update Not UI') {
//do something
} else if (request.action === 'update UI') {
document.getElementById("displayContent").value = request.results
}
};
chrome.runtime.onMessage.addListener(handleRequest);
background.js
chrome.runtime.onMessage.addListener((request,sender,sendResponse) => {
chrome.tabs.sendMessage(sender.tab.id,request,function(response){
console.log(response)`
});
});
panel.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="panel.css" />
</head>
<body>
<textarea id="displayContent" rows="10" cols="40"></textarea>
</body>
</html>
Any suggestions on what I am doing wrong or what can I do instead?
An iframe with a real URL loads asynchronously so its code runs after the embedding code finishes - hence, your message is sent too early and is lost. The URL in your case points to an extension resource so it's a real URL. For reference, a synchronously loading iframe would have a dummy URL e.g. no src at all (or an empty string) or it would be something like about:blank or javascript:/*some code here*/, possibly srcdoc as well.
Solution 1: send a message in iframe's onload event
Possible disadvantage: all extension frames in all tabs will receive it, including the background script and any other open extension pages such the popup, options, if they also have an onMessage listener.
iframeNode.onload = () => {
chrome.runtime.sendMessage('foo', res => { console.log(res); });
};
document.body.appendChild(iframeNode);
Solution 2: let iframe send a message to its embedder
Possible disadvantage: wrong data may be sent in case you add several such extension frames in one tab and for example the 2nd one loads earlier than the 1st one due to a bug or an optimization in the browser - in this case you may have to use direct DOM messaging (solution 3).
iframe script (panel.js):
chrome.tabs.getCurrent(ownTab => {
chrome.tabs.sendMessage(ownTab.id, 'getData', data => {
console.log('frame got data');
// process data here
});
});
content script (context.js):
document.body.appendChild(iframeNode);
chrome.runtime.onMessage.addListener(
function onMessage(msg, sender, sendResponse) {
if (msg === 'getData') {
chrome.runtime.onMessage.removeListener(onMessage)
sendResponse({ action: 'update UI', results: 'foo' });
}
});
Solution 3: direct messaging via postMessage
Use in case of multiple extension frames in one tab.
Disadvantage: no way to tell if the message was forged by the page or by another extension's content script.
The iframe script declares a one-time listener for message event:
window.addEventListener('message', function onMessage(e) {
if (typeof e.data === 'string' && e.data.startsWith(chrome.runtime.id)) {
window.removeEventListener('message', onMessage);
const data = JSON.parse(e.data.slice(chrome.runtime.id.length));
// process data here
}
});
Then, additionally, use one of the following:
if content script is the initiator
iframeNode.onload = () => {
iframeNode.contentWindow.postMessage(
chrome.runtime.id + JSON.stringify({foo: 'data'}), '*');
};
document.body.appendChild(iframeNode);
if iframe is the initiator
iframe script:
parent.postMessage('getData', '*');
content script:
document.body.appendChild(iframeNode);
window.addEventListener('message', function onMessage(e) {
if (e.source === iframeNode) {
window.removeEventListener('message', onMessage);
e.source.postMessage(chrome.runtime.id + JSON.stringify({foo: 'data'}), '*');
}
});
one possible way that worked for me is by using functionality in setTimeout() method.
in context.js
setTimeout(() => {
chrome.runtime.sendMessage({action: "update UI", results: dataForUI},
(response)=> {
console.log(response.message)
}
)
}, 100);
But I am not sure if this is the best way.
I am trying to get this chrome storage sync set to run when the person closes the popup window in my chrome extension but cannot seem to get it working. That way if they abruptly close the extension window say by clicking in their browser or whatever their data still stores. Anyone have any ideas?
window.addEventListener("beforeunload", function load(unloadEvent) {
let currentTimeGet = document.getElementById("currentTimeInfo").innerHTML;
chrome.storage.sync.set({ ct: currentTimeGet }, function() {
console.log("Value is set to " + currentTimeGet);
});
});
beforeunload event is ignored for the popup shown by browser_action or page_action.
unload won't wait for the asynchronous chrome.storage to complete so the data won't be stored
Based on this several solutions are possible.
Autosave on any change.
This is the preferable and modern user-friendly solution. You can give the elements an id that's equal to their name in chrome.storage like <input type="text" id="userName" class="storage">.
const STORAGE_SELECTOR = '.storage[id]';
let debounceTimer;
document.addEventListener('change', saveOnChange);
document.addEventListener('input', saveOnChange);
function saveOnChange(e) {
if (e.target.closest(STORAGE_SELECTOR)) {
clearTimeout(debounceTimer);
debounceTimer = setTimeout(doSave, 100);
}
}
function collectData() {
const data = {};
for (const el of document.querySelectorAll(STORAGE_SELECTOR))
data[el.id] = el.type === 'checkbox' ? el.checked : el.value;
return data;
}
As for doSave() function, either simply overwrite the current options data in storage
function doSave() {
chrome.storage.sync.set(collectData());
}
or save under a separate autoSave and check it the next time the popup is shown:
function doSave() {
chrome.storage.sync.set({autoSave: collectData()});
}
function loadFromStorage() {
chrome.storage.sync.get(data => {
if (data.autoSave) data = data.autoSave;
for (const [id, value] of Object.entries(data)) {
const el = document.getElementById(id);
if (el) el[el.type === 'checkbox' ? 'checked' : 'value'] = value;
}
});
}
loadFromStorage();
Save the data in unload event directly in the backround script's JavaScript window object e.g. chrome.extension.getBackgroundPage().popupData = collectData().
It's bad because it requires a persistent background page (or a temporarily persistent page) and because Chrome moves away from unload events in general. The data may be easily lost on a crash of the extension process or of the browser itself, it might be lost if the browser was closed by the user.
I'll start my saying I'm a JavaScript newbie (I'm more of a Bash guy)...
I'm trying to create a Firefox WebExtension to disable the Ctrl+Q shortcut and play a sound when Ctrl+Q is pressed. I'd also like to have a user choose from a small list of sounds in an options menu. So far, all of that is working.
The only snag I'm hitting is when the user changes a sound and clicks "Save", the new sound isn't played on Ctrl+Q until the extension is reloaded.
Doing some Googling, I think the problem is related to the fact that storage API is asynchronous. From what I can gather, I need to use a callback to get the sound option after it is set. Is that not what I'm doing below? The option is set in options.js and then background.js plays the sound.
I'd appreciate any help.
options.js
// Saves options to browser.storage
function save_options() {
browser.storage.local.set({
favoriteSound: document.getElementById('CtrlQSound').value,
}, function() {
// Update status to let user know options were saved.
var status = document.getElementById('status');
status.textContent = 'Options saved.';
setTimeout(function() {
status.textContent = '';
}, 750);
});
};
// Restores select box state using the preferences stored in browser.storage
function restore_options() {
// Use default value sound = 'Sound0'
browser.storage.local.get({
favoriteSound: 'Sound0',
}, function(items) {
document.getElementById('CtrlQSound').value = items.favoriteSound;
});
};
document.addEventListener('DOMContentLoaded', restore_options);
document.getElementById('save').addEventListener('click', save_options);
background.js
browser.storage.local.get("favoriteSound", function(result) {
browser.commands.onCommand.addListener(function(SoundToggle) {
if (result.favoriteSound == "Sound0"){
new Audio("Sound0.ogg").play();
}
else if (result.favoriteSound == "Sound1"){
new Audio("Sound1.ogg").play();
}
else if (result.favoriteSound == "Sound2"){
new Audio("Sound2.ogg").play();
}
});
});
Firefox uses Promise objects, not callbacks. On a Promise, you can call "then" with a success and failure handler. Like this:
browser.storage.local.set({
favoriteSound: document.getElementById('CtrlQSound').value
}).then(onSuccess, onError);
function onSuccess() {
// Saving into storage.local succeeded
// Update status to let user know options were saved.
var status = document.getElementById('status');
status.textContent = 'Options saved.';
setTimeout(function() {
status.textContent = '';
}, 750);
function onError() {
// Saving into storage local failed.
// You might want to use a notification to display this error / warning to the user.
});
If you're developing for Chrome, you have to use callbacks. Or you could use "chrome." instead of "browser." if you want to use callbacks instead of Promises.
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
})
I have an EventListener and a function generator.gettext.
Code:
var generator = {
gettext: function (mytext) {
//I send a message which is intercepted by my content script.
//Later my content script will send a reply back which will be processed by 'receiver'
window.postMessage({
type: "MESSAGE",
text: mytext
}, "*");
while (generatedtext == "") {
//WAIT until generatedtext is set by 'receiver'
}
return generator.generatedtext;
},
generatedtext: ""
};
function receiver(event) {
if (event.data.type && (event.data.type == "FROM_CONTENTSCRIPT")) {
generator.generatedtext = event.data.text;
}
}
window.addEventListener('message', receiver, false);
I want that generator.gettext returns generator.generatedtext but I can't make it wait until receiver sets it. I have the impression that the EventListener is locked while generator.gettext is called. I have to change it so it becomes asynchronous and uses a callback parameter, but I'm new into this and don't know how to make it work. Can somebody help me?
EDIT: What I want to do is to process input on a website. There is a Javascript file and I want that input is processed by my content script. I redirected the javascript-file to a modified one on my hard drive. I change a line where the javascript loads the input. When I submit text the Javascript file calls something like var message = generator.gettext(mytext.value); My code above is injected into the page and has to send mytext to my content-script and has to return the output of my content-script.
I think I understand what you're asking, though it isn't very clear.
You are posting a message and you want to store the result on generator.generatedtext. You are already doing so.
var generator = {
generatedtext:'',
gettext: function (mytext) {
window.postMessage({
type: "MESSAGE",
text: mytext
}, "*");
}
};
window.addEventListener('message', function(ev){
if (event.data.type && (event.data.type == "FROM_CONTENTSCRIPT")) {
generator.generatedtext = event.data.text;
}
},false);
Unless there is some other usage that you are trying to do but haven't made clear, this is all you need. There is no need to have generator.gettext() return anything, since the message event listener is assigning the messages being received to generator.generatedtext.
If this isn't exactly what you wanted, post a comment with more details and I'll try to help you out.