I'm still working on my first Chrome extension and I've hit a wall. I'm trying to insert some text into a text field using document.activeElement.value.
I can't get it to work at all. I've used tons of examples from a bunch of folks and haven't gotten it to work.
Here's my code in my background.js that the extension uses to create the context menu and insert the text. I'm assuming at this point something is wrong with my onClickHandler or click event but I'm at a loss.
chrome.runtime.onInstalled.addListener(function() {
var context = "all";
var title = "Test";
var id = chrome.contextMenus.create({"title": title, "contexts":[context],
"id": "context" + context});
});
// add click event
chrome.contextMenus.onClicked.addListener(onClickHandler);
//The onClicked callback function.
function onClickHandler() {
document.activeElement.value = 'some text';
}
Okay, you need some background.
First off, you're executing code in the background page; as such, document refers to the background page itself.
Take a moment to read the Architecture Overview (and maybe the rest of the page if you're starting out, it's very helpful).
If you read that, you should understand you need a content script. Once injected in the active tab (and the "activeTab" permission should be enough to do it from the click handler), you can access the proper document.
However, this task is more complex than you think. There was a recent discussion on the topic; see this question for details.
Related
So lately I have been learning JS and trying to interact with webpages, scraping at first but now also doing interactions on a specific webpage.
For instance, I have a webpage that contains a button, I want to press this button roughly every 30 seconds and then it refreshes (and the countdown starts again). I wrote to following script to do this:
var klikCount = 0;
function getPlayElement() {
var playElement = document.querySelector('.button_red');
return playElement;
}
function doKlik() {
var playElement = getPlayElement();
klikCount++;
console.log('Watched ' + klikCount);
playElement.click();
setTimeout(doKlik, 30000);
}
doKlik()
But now I need to step up my game, and every time I click the button a new window pops up and I need to perform an action in there too, then close it and go back to the 'main' script.
Is this possible through JS? Please keep in mind I am a total javascript noob and not aware of a lot of basic functionality.
Thank you,
Alex
DOM events have an isTrusted property that is true only when the event has been generated by the user, instead of synthetically, as it is for the el.click() case.
The popup is one of the numerous Web mechanism that works only if the click, or similar action, has been performed by the user, not the code itself.
Giving a page the ability to open infinite amount of popups has never been a great idea so that very long time ago they killed the feature in many ways.
You could, in your own tab/window, create iframes and perform actions within these frames through postMessage, but I'm not sure that's good enough for you.
Regardless, the code that would work if the click was generated from the user, is something like the following:
document.body.addEventListener(
'click',
event => {
const outer = open(
'about:blank',
'blanka',
'menubar=no,location=yes,resizable=no,scrollbars=no,status=yes'
);
outer.document.open();
outer.document.write('This is a pretty big popup!');
// post a message to the opener (aka current window)
outer.document.write(
'<script>opener.postMessage("O hi Mark!", "*");</script>'
);
// set a timer to close the popup
outer.document.write(
'<script>setTimeout(close, 1000)</script>'
);
outer.document.close();
// you could also outer.close()
// instead of waiting the timeout
}
);
// will receive the message and log
// "O hi Mark!"
addEventListener('message', event => {
console.log(event.data);
});
Every popup has an opener, and every different window can communicate via postMessage.
You can read more about window.open in MDN.
I am learning chrome extension programming from the tutorial here .
You can find the full code for the chrome extension here.
The code snippet where I tried to remove few links:
var clean_twitter = function(){
var ugly = [];
ugly.push('.Trends module trends');
ugly.push('.flex-module');
ugly.push('.MomentMakerHomeModule-header');
ugly.push('.Footer module roaming-module');
ugly.push('.flex-module-header');
$('.promoted-tweet').hide(); // oops! :P
for(var i=0;i<ugly.length;i++) {
var u = $(ugly[i]).find('a'); // also 'b'
u.text('');
}
}
The code tries to remove some buttons and div from the twitter website.
Now, when I put it on my pc nothing happens. I tried to remove the change link inside the trends box and it isn't removed.
Please help if I am doing something wrong here. Thanks.
At the beginning of the process_new_tweets function there's a comment explaining how the presence or absence of .mini-profile in the DOM is used as a flag.
In summary, the absence of the .mini-profile element in the DOM means that the function returns and won't proceed any further. Since the tutorial was written it would appear that Twitter no longer has a .mini-profile element anywhere in its DOM, so the function is always returning and script execution is not proceeding any further.
Remove the following lines from the beginning of the process_new_tweets function:
var mp = document.getElementsByClassName('mini-profile');
if(mp.length === 0) { return; }
And the elements that you've selected in your clean_twitter function will be removed from the DOM as expected.
I'm creating a Google Chrome extension and I need to detect when a page's title changes. The page's title is changed like in Twitter: (num) Twitter (see the screenshot below) - when a new tweet is posted, the number increments. Example:
I'm trying to detect the title changes of a URL that's loaded in one of my tabs and play a beep sound whenever there's a difference. This check is to be done in a repeated interval and I think that can be accomplished using setTimeOut() function.
I've created a manifest.json as follows:
{
"manifest_version": 2,
"name": "Detect Page Title Changes",
"description": "Blah",
"version": "1.0",
"browser_action": {
"default_icon": "icon.png",
"default_popup": "background.html"
},
"permissions": [
"tabs"
]
}
However, I'm clueless about the rest. I've searched through the docs 1 2 and tried the solutions on similar Stack Overflow threads such as this one I but couldn't find anything that suits my requirements.
Do you have any suggestions? Please include an example, if possible.
Instead of arguing in comments that a certain approach is better, let me be more constructive and add an answer by showing a particular implementation I co-wrote myself, and explain some gotchas you may run into. Code snippets refer to a service different from Twitter, but the goal was the same. In fact, this code's goal is to report the exact number of unread messages, so yours might be simpler.
My approach is based on an answer here on SO, and instead of being polling-driven (check condition at fixed intervals) is event-driven (be notified of potential changes in condition).
Advantages include immediate detection of a change (which would otherwise not be detected until the next poll) and not wasting resources on polls while the condition does not change. Admittedly, the second argument hardly applies here, but the first one still stands.
Architecture at a glance:
Inject a content script into the page in question.
Analyze initial state of the title, report to background page via sendMessage.
Register a handler for a title change event.
Whenever the event fires and the handler is called, analyze the new state of the title, report to background page via sendMessage.
Already step 1 has a gotcha to it. Normal content script injection mechanism, when the content script is defined in the manifest, will inject it in pages upon navigation to a page that matches the URL.
"content_scripts": [
{
"matches": [
"*://theoldreader.com/*"
],
"js": ["observer.js"],
"run_at": "document_idle"
}
]
This works pretty well, until your extension is reloaded. This can happen in development as you're applying changes you've made, or in deployed instances as it is auto-updated. What happens then is that content scripts are not re-injected in existing open pages (until navigation happens, like a reload). Therefore, if you rely on manifest-based injection, you should also consider including programmatic injection into already-open tabs when extension initializes:
function startupInject() {
chrome.tabs.query(
{url: "*://theoldreader.com/*"},
function (tabs) {
for (var i in tabs) {
chrome.tabs.executeScript(tabs[i].id, {file: "observer.js"});
}
}
);
}
On the other end, content script instances that were active at the time of extension reload are not terminated, but are orphaned: any sendMessage or similar request will fail. It is, therefore, recommended to always check for exceptions when trying to communicate with the parent extension, and self-terminate (by removing handlers) if it fails:
try {
chrome.runtime.sendMessage({'count' : count});
} catch(e) { // Happens when parent extension is no longer available or was reloaded
console.warn("Could not communicate with parent extension, deregistering observer");
observer.disconnect();
}
Step 2 also has a gotcha to it, though it depends on the specifics of the service you're watching. Some pages inside the scope of the content script will not show the number of unread items, but it does not mean that there are no new messages.
After observing how the web service works, I concluded that if the title changes to something without navigation, it's safe to assume the new value if correct, but for the initial title "no new items" should be ignored as unreliable.
So, the analysis code accounts for whether it's the initial reading or handling an update:
function notify(title, changed) {
// ...
var match = /^\((\d+)\)/.exec(title);
var match_zero = /^The Old Reader$/.exec(title);
if (match && match[1]) {
count = match[1];
} else if (match_zero && changed) {
count = 0;
}
// else, consider that we don't know the count
//...
}
It is called with the initial title and changed = false in step 2.
Steps 3 & 4 are the main answer to "how to watch for title changes" (in an event-driven way).
var target = document.querySelector('head > title');
var observer = new window.MutationObserver(
function(mutations) {
mutations.forEach(
function(mutation){
notify(mutation.target.textContent, true);
}
);
}
);
observer.observe(target, { subtree: true, characterData: true, childList: true });
For specifics as to why certain options of observer.observe are set, see the original answer.
Note that notify is called with changed = true, so going from "(1) The Old Reader" to "The Old Reader" without navigation is considered to be a "true" change to zero unread messages.
Put chrome.tabs.onUpdated.addListener in your background script:
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
console.log(changeInfo);
});
changeInfo is an object which includes title changes, e.g. here:
Can then filter on the object so that an action only occurs if changeInfo includes a title change. For additional manipulation, e.g. responding to page title changes with page content / actions, you can send a message to content script from inside the listener after whatever conditions are met.
Create an event page.
Create a content script that gets injected into a webpage when a webpage loads.
Within the content script, use setInterval to poll the page to see if window.document.title changes.
If the title has changed, use chrome.runtime.sendMessage to send a message to your event page.
On your event page, listen for messages with chrome.runtime.onMessage and play a sound.
After researching Chrome's tabs API, it doesn't look like anything stands out to help you directly. However, you should be able to attach an event listener to the title node of the tab(s) you're interested in. The DOMSubtreeModified mutation event works in Chrome, and a quick test in a normal html document proves to work for me - should be no different from within an extension.
var title = document.getElementsByTagName('title')[0];
if (title) {
title.addEventListener('DOMSubtreeModified', function (e) {
// title changed
}, false);
}
I'm having a rather mind-boggling problem with some JS I'm working on for a web app. Unfortunately, I can't post the full code for this, as it's part of a not-yet-released project. This issue has both me and some colleagues I asked stumped - from what I can tell, it should work.
Now, to the actual problem: a user enters some info into some form fields and clicks a "confirm" image, whereupon an AJAX request is sent back to the server. The server does some processing, then sends back a response with a status and some attached data. A status message is displayed in the modal dialogue window the user was using, and an icon with a link is displayed. Here's the "onComplete" Handler for the Mootools Request.JSON object, with some error condition handling removed:
onComplete: function(response) {
if (response) {
if (response.status == 0) {
// this means the request was successful
licenses = response.licenses;
updateControls();
licenseList();
// here I add the status message...
$("createform_result").innerHTML = "<img src=\'/media/com_supportarea/images/db_success.png\' /> License created. Download:<br /><br />";
// ...and the download "link"
if (response.tlsid) {
$("createform_result").innerHTML += "<img src=\'/media/com_supportarea/images/download_small.png\' /> <em>TLS</em>";
// this line is here for debugging only, to make sure this
// block of code is run (it is) and the element is found (it is)
$("newtlslic-"+response.tlsid).style.border = "1px solid red";
$("newtlslic-"+response.tlsid).addEvent("click", function(e) {
// I've stripped out all other code, also for debugging
e.stop();
});
}
}
}
}
The message and icon with link appears, the style is applied (red border appears) and no error message appears in either Firefox or Chrome. However, clicking the icon results in a # being appended to the URL (the e.stop()) does nothing). According to the EventBug plugin, no click event is attached to the link. It seems like .addEvent() simply does nothing here.
Now, and here's they prize question: why is this and how can I fix it? Help!
strings in javascript are immutable. when you do stuff like:
$("createform_result").innerHTML += "<img src=\'/media/com_supportarea/images/download_small.png\' /> <em>TLS</em>";
you are referencing the innerHTML as a string. what this does is, it fetches the property into a string, concatenates it to the other strings you pass on and then returns a new string in the end, which gets set as the innerHTML.
in doing so, you are OVERWRITING the contents of the element every time, for every iteration.
events attached to elements are not done by a generic ID handler - they rely on the element being in the DOM, then the element UID (an internal property mootools assigns to all passed elements) is being read and the event callback is added into the element storage behind that UID.
you can see this work by doing console.log(element.retrieve("events"));
if you rewrite the innerHTML, the inner element is re-created and gets a NEW UID, which means the callback now points to an empty pointer as the UID is the key in element storage.
I may be wrong about what you are doing here as I don't actually see the bit where you rewrite it again, but there probably is one in the code you stripped, especially if you are running a loop.
the best way to deal with this is something else - use Event Delegation.
it can allow you to add the click event to the parent element instead via some selector. this will work for ANY element added in any way at any time that matches.
eg.
// add this once, outside the loop
$("createform_result").addEvent("click:relay(a.editLink)", function(event, element) {
console.log(this === element);
console.log(this === event.target);
console.log(this.get("data-id"));
});
// then as you loop the results, just inject the els or use innerHTML or whatever...
new Element("a.editLink", {
html: '<img src=\'/media/com_supportarea/images/download_small.png\' /></a> <em>TLS</em>',
"data-id": response.tlsid
}).inject($("createform_result"));
Event delegation is now a part of mootools core in 1.4.0 or is in mootools-more in previous versions.
have fun!
I am looking for a quick way to grab some data off of one Web page and throw it into another. I don't have access to the query string in the URL of the second page, so passing the data that way is not an option. Right now, I am using a Greasemonkey user script in tandem with a JS bookmarklet trigger: javascript:doIt();
// ==UserScript==
// #include public_site
// #include internal_site
// ==/UserScript==
if (document.location.host.match(internal_site)) {
var datum1 = GM_getValue("d1");
var datum2 = GM_getValue("d2");
}
unsafeWindow.doIt = function() {
if(document.location.host.match(public_site)) {
var d1 = innerHTML of page element 1;
var d2 = innerHTML of page element 2;
//Next two lines use setTimeout to bypass GM_setValue restriction
window.setTimeout(function() {GM_setValue("d1", d1);}, 0);
window.setTimeout(function() {GM_setValue("d2", d2);}, 0);
}
else if(document.location.host.match(internal_site)) {
document.getElementById("field1").value = datum1;
document.getElementById("field2").value = datum2;
}
}
While I am open to another method, I would prefer to stay with this basic model if possible, as this is just a small fraction of the code in doIt() which is used on several other pages, mostly to automate date-based form fills; people really like their "magic button."
The above code works, but there's an interruption to the workflow: In order for the user to know which page on the public site to grab data from, the internal page has to be opened first. Then, once the GM cookie is set from the public page, the internal page has to be reloaded to get the proper information into the internal page variables. I'm wondering if there's any way to GM_getValue() at bookmarklet-clicktime to prevent the need for a refresh. Thanks!
Can you move the bookmarklet to a button or link -- that Greasemonkey will add to the page(s)?
Then you could set click-event handlers to fire GM_getValue().
It looks like the current method is exploiting a "security hole" -- one that may be closed in the future. You might consider doing everything in a Firefox extension, instead.
Possibly useful link: http://articles.sitepoint.com/article/ten-tips-firefox-extensions/1