Cannot set input field value with Element.value [duplicate] - google-chrome-extension

My extension has a context menu with items. What I'd like it to do: is when I right-click an editable html element (eg input or textarea) and then select and click on an item in my menu - some value defined by my extension gets entered into the input.
For now I have realised that with document.activeElement.value = myValue.
With simple inputs it works alright.
Problems start when there is an input with custom onChange event handling, eg a calendar or a phone input, or currency input - that transforms user-input in some way.
Since I am setting a value directly onto the element - the handling logic gets omitted, which causes all manner of problems.
Since javascript doesn't allow for KeySend-like features - what are my options here?
I have thought about testing tools like Puppeteer or Cypress - but they all seem not to be packageable into an extension. Puppeteer does have such an option, but it still requires a node instance running to connect to. And I would like my extension to be solely client-sided and distributed in Chrome webstore - so I cannot ask my users to spin up a node server.

There is a built-in DOM method document.execCommand.
In case of an extension, use this code in the content script.
// some.selector may be `input` or `[contenteditable]` for richly formatted inputs
const el = document.querySelector('some.selector');
el.focus();
document.execCommand('insertText', false, 'new text');
el.dispatchEvent(new Event('change', {bubbles: true})); // usually not needed
It imitates physical user input into the currently focused DOM element so all the necessary events will be fired (like beforeinput, input) with isTrusted field set to true. On some pages the change event should be additionally dispatched as shown above.
You may want to select the current text to replace it entirely instead of appending:
replaceValue('some.selector', 'new text');
function replaceValue(selector, value) {
const el = document.querySelector(selector);
if (el) {
el.focus();
el.select();
if (!document.execCommand('insertText', false, value)) {
// Fallback for Firefox: just replace the value
el.value = 'new text';
}
el.dispatchEvent(new Event('change', {bubbles: true})); // usually not needed
}
return el;
}
Note that despite execCommand being marked as obsolete in 2020, it'll work in the foreseeable future because a new editing API specification is not finished yet, and knowing how slow such things usually move it may take another 5-20 years.

#wOxxOm, thank you very much !
I used your code solved my problem which has bothered me for long time. I googled many code and article for nearly one month.
It works on Facebook and many strong website.
Because execCommand has depredated, I try below code it works well, include Facebook.
function imitateKeyInput(el, keyChar) {
if (el) {
const keyboardEventInit = {bubbles:false, cancelable:false, composed:false, key:'', code:'', location:0};
el.dispatchEvent(new KeyboardEvent("keydown", keyboardEventInit));
el.value = keyChar;
el.dispatchEvent(new KeyboardEvent("keyup", keyboardEventInit));
el.dispatchEvent(new Event('change', {bubbles: true})); // usually not needed
} else {
console.log("el is null");
}
}
The following code can only work on ordinary websites, but it is invalid for strong website.
function fireKeyEvent(el, evtType, keyChar) {
el.addEventListener(evtType, function(e) {el.value += e.key;}, false);
el.focus();
const keyboardEventInit = {bubbles:false, cancelable:false, composed:false, key:keyChar, code:'', location:0};
var evtObj = new KeyboardEvent(evtType, keyboardEventInit);
el.dispatchEvent(evtObj);
}

Related

Element is not currently visible and so may not be interacted with node and selenium driver

I have the following code and I cannot get the driver to click the div. It keeps throwing the error
"Element is not currently visible and so may not be interacted"
when debugging you can clearly see that the element is visible. How can I ignore the warning or the error?
var webdriver = require('selenium-webdriver')
, By = webdriver.By
, until = webdriver.until;
var driver = new webdriver.Builder().forBrowser('firefox').build();
driver.get('http://www.vapeworld.com/');
driver.manage().timeouts().implicitlyWait(10, 10000);
var hrefs = driver.findElements(webdriver.By.tagName("a"));
hrefs.then(function (elements) {
elements.forEach(function (element) {
element.getAttribute('name').then(function (obj) {
if (obj == '1_name') {
console.log(obj);
element.click();
}
});
});
});
Your code is clicking an A tag with the name "1_name". I'm looking at the page right now and that element doesn't exist, hidden or otherwise.
You'd be better served by replacing the bulk of your code with a CSS selector, "a[name='1_name']" or "a[name='" + tagName + "']", that will find the element you want with a single find. You can then click on that element.
The issue you are running into is that the element you are trying to click is not visible, thus the error message. Selenium is designed to only interact with elements that the user can see, which would be visible elements. You will need to find the element you are looking for and figure out how to make it visible. It may be clicking another link on the page or scrolling a panel over, etc.
If you don't care about user scenarios and just want to click the element, visible or not, look into .executeScript().
Looked at the website and used the F12 tool (Chrome) to investigate the page:
var elements = [].slice.call(document.getElementsByTagName("a"));
var elementNames = elements.map(function (x) { return x.getAttribute("name"); });
var filledElementNames = elementNames.filter(function (x) { return x != null; });
console.log(filledElementNames);
The content of the website http://www.vapeworld.com is very dynamic. Depending on the situation you get one or more anchors with "x_name" and not always "1_name": the output of the script in Chrome was ["2_name"] and Edge returns ["1_name", "9_name", "10_name", "17_name", "2_name"]. So "you can clearly see that the element is visible" is not true in all situations. Also there were some driver bugs on this subject so it is worthwhile to update the driver if needed. See also the answers in this SO question explaining all the criteria the driver uses. If you want to ignore this error you can catch this exception:
try {
element.click();
} catch (Exception ex) {
console.log("Error!");
}
See this documentation page for more explanation.

How to find out if a user is dragging a tab

I am trying to figure out, if a user is dragging a tab, any tab. I don't care which tab it is, I just need to know, if any tab is being dragged.
What is the best way to do this?
Please note: I asked a similar question. However, in that other question I wanted to know, when dragging stopped so I could perform my move operation. The solution given there (retrying until it works) doesn't seem to apply to this new question.
There is no way to tell whether any tab is being "dragged" (=mouse button held down on a tab).
If you want to know that a tab drag has occurred (opposed to "is about to happen"), then you could use the chrome.tabs.onMoved (moved within a tab) and/or chrome.tabs.onAttached / chrome.tabs.onDetached events.
I built a solution based on the fact that Chrome doesn't allow the moving of tabs in the window that contains a tab that is currently being dragged.
In this case, chrome.runtime.lastError.message will be Tabs cannot be edited right now (user may be dragging a tab).
I utilize this by getting the first tab of the focused window and moving it to its index. Because I use its own index, there isn't actually a visual change, when the operation succeeds.
var Chrome = {
isUserDragging: function (callback) {
chrome.windows.getAll({ populate: true }, function (windows) {
var window = windows.filter(function (x) {
return x.type === 'normal' && x.focused && x.tabs
&& x.tabs.length;
})[0];
if (window === undefined)
return;
var tab = window.tabs[0];
chrome.tabs.move(tab.id, { index: tab.index }, function () {
callback(
chrome.runtime.lastError !== undefined &&
chrome.runtime.lastError.message.indexOf('dragging') !== -1);
});
});
}
}
Usage would be:
Chrome.isUserDragging(function(userIsDragging) {
if(userIsDragging)
// do something
else
// do something else
});
Now, based on this, I built a polling mechanism using setTimeout that checks periodically if the user is still dragging and executes an action, when the user stopped dragging.
The full implementation can be seen here and it uses these two helper classes.

Updating an extension button dynamically - inspiration required

I am building an extension where I want to be able to add a signifier to the extension button when the extension in the code has been activated. I was hoping I could add text to the extension button (top right)
Here is a simple scenario. Let's say my extension monitors browsing and gets the tab url, in my extension I have a list of domains to watch for.
Watch for these domains
www.website1.com
www.website2.com
If a user visits a domain in the watched list I want to indicate this somehow, by adding some text somewhere - I was hoping in the top right of the browser where the extensions buttons are. I don't really want to use a notification window as I want something unobtrusive. The text that I want to display would just be a few letters but different for different urls.
Does anyone have any inspiration?
Thanks
You may change the extension icon like this:
chrome.browserAction.setIcon({path: icon});
There is also a 'badge' - small box over the extension icon that shows ie. number of unread messages in gmail extension. You can manipulate it like this:
chrome.browserAction.setBadgeBackgroundColor({color:[190, 190, 190, 230]});
chrome.browserAction.setBadgeText({text:"?"});
It is also possible to generate icon dynamically on a canvas element and then display it like this:
chrome.browserAction.setIcon({imageData:canvasContext.getImageData(0, 0, canvas.width,canvas.height)});
Note that you must call this from your background script, since the content script will not have permission!
tl;dr: Call it from background.js
I googled around this comment because I was trying to call a chrome.browserActions function from my content script
It's only accessible to scripts that are running as part of a chrome extension, since content_scripts are the same as client scripts you'd have to access them through the chrome.* api's
and to fix some addition headaches I had the call for setBadge text needs to look like:
chrome.browserAction.setBadgeText({text: 'ASDF'});
You can put as many characters as you want, but only 4 or so will appear. I got hung up on what the object property needed to be.
You can also set a timeout to check changes on the system every x minutes, and then update de badge.
On my extension, I have an task counter called inside a notification function. Something like :
$.getJSON(
"http://mydomain/notifications?ajax=1&callback=?",
function(data){
var result = data.retorno;
if(result.length > 0){
var totalItens = result[0].total
} else {
var totalItens = 0;
}
chrome.browserAction.setIcon({path: '19.png'});
chrome.browserAction.setBadgeText({text:''+totalItens+''});
for(var i=0; i<result.length; i++){
var imagem = 'http://mydomain/myimage';
var titulo = 'mytitle';
var desciption = 'mydescription';
var urlNot = 'http://mydomain/mypageOnclick';
var not = new Notifier(urlNot);
not.Notify(
imagem, // The image.
titulo, // The title.
desciption // The body.
);
}
}
);
You have to change in 3 files.
manifest.json
Check this code added
"background": { "scripts": ["background.js"], "persistent": false }
script.js
Add the following code:
const msg = 'updateIcon'
chrome.runtime.sendMessage({ message: msg }, function(response) {
});
background.js
Add the following code:
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
console.log(request);
// Callback for that request
chrome.browserAction.setIcon({path: "/assets/icon.png"});
});

How do I store values for specific tab for Chrome, and Safari extensions?

I am developing an extension for all the browsers. How do I store tab specific values in the session? I solved this problem in Firefox with an NSISessionStore object. In Safari and Google Chrome, I used SessionStorage; this object stores values for a specific tab with a specific domain. I want a solution for how to store values for a specific tab.
If you're asking how to manage data throughout the life of a tab you can simply create an object for the tab when it's created and delete it when it is closed.
// Create data store
var tabDataStore = {};
// Create listeners
chrome.tabs.onCreated.addListener(function (tab) {
tabDataStore['tab_' + tab.id] = {
urls: []
};
});
chrome.tabs.onRemoved.addListener(function (tabId) {
delete tabDataStore['tab_' + tabId];
});
// Save something against that tab's data
function saveUrl(tab) {
tabDataStore['tab_' + tab.id].urls.push(tab.url);
}
// Load something from tab's data
function loadOriginalUrl(tab) {
tabDataStore['tab_' + tab.id].urls[0];
}
However, this is all an assumption and you may want something completely different. Also, it depends when and what exactly you want to store.
Further information on tabs can be found in the official documentation.
If you want to persist anything you can use localStorage.
a simple way to do it, though not ideal as it would look messy would be to store the values in the URL hash
say the URL of the tab was http://whatever.com/
you could store the value in the hash like so
http://whatever.com/#value1=12&value2=10&value3=15212
there will also be a problem if the website uses the hash object for anyhting such as "in page" anchors, or ajaxy type stuff
Safari Answer
In your global page save directly to the tab.. so for instance on message from injected script
// global page
safari.application.addEventListener("message", function(event){
switch(event.name){
case "saveData":
event.target.page.tabData = { data: myData }
break;
case "getData":
event.target.page.dispatchMessage("tabData", myData);
break;
}
}, false);
-
// injected page
// first save data
safari.self.tab.dispatchMessage("saveData", {firstname:"mike", age: 25} );
// setup listner to recevie data
safari.self.addEventListener("message", function(event){
switch(event.name){
case "tabData":
// get data for page
console.debug(event.message);
// { firstname: "mike", age: 25 }
break;
}
}, false);
// send message to trigger response
safari.self.tab.dispatchMessage("getData", {} );

modified innerHTML does not show on page

I am writing a Google extension. Here my content script modifies a page based on a list of keywords requested from background. But the new innerHTML does not show up on the screen. I've kluged it with an alert so I can see the keywords before deciding to actually send a message, but it is not how the routine should work. Here's the code:
// MESSAGE.JS //
//alert("Message Page");
var keyWordList= new Array();
var firstMessage="Hello!";
var contentMessage=document.getElementById("message");
contentMessage.value=firstMessage;
var msgComments=document.getElementsByClassName("comment");
msgComments[1].value="Hello Worlds!";//marker to see what happens
chrome.extension.sendRequest({cmd: "sendKeyWords"}, function(response) {
keyWordList=response.keyWordsFound;
//alert(keyWordList.length+" key words.");//did we get any keywords back?
var keyWords="";
for (var i = 0; i<keyWordList.length; ++i)
{
keyWords=keyWords+" "+keyWordList[i];
}
//alert (keyWords);//let's see what we got
document.getElementsByClassName("comment")[1].firstChild.innerHTML=keyWords;
alert (document.getElementsByClassName("comment")[1].firstChild.innerHTML);// this is a band aid - keyWords does not show up in tab
});
document.onclick= function(event) {
//only one button to click in page
document.onload=self.close();
};
What do I have to do so that the text area that is modified actually appears in the tab?
(Answering my own question) This problem really has two parts. The simplest part is that I was trying to modify a text node by setting its value like this:
msgComments1.value="Hello Worlds!"; //marker to see what happens
To make it work, simply set the innerHTML to a string value like this:
msgComment1.innerHTML="Hello Worlds!"; //now it works.
The second part of the problem is that the asynchronous call to chrome.extension.sendRequest requires a callback to update the innerHTML when the reply is received. I posted a question in this regard earlier and have answered it myself after finding a solution in an previous post by #serg.

Resources