using popupNode in a javascript firefox extension - javascript

I am trying to use popupNode in a little javascript based firefox extension. So if a user right click on a link and then clicks on an additional menu item a new tab opens with the link (sorta like "open in new tab"):
`
var foo = {
onLoad: function() {
// initialization code
this.initialized = true;
},
onMenuItemCommand: function() {
var tBrowser = document.getElementById("content");
var target = document.popupNode;
tBrowser.selectedTab = tab;
var tab = tBrowser.addTab(target);
}
};
window.addEventListener("load", function(e) { foo.onLoad(e); }, false);
`
It works mostly, but I am wondering in that is the right use. The problem is I want replace some characters on the var target, but somehow that partdoes not work. something like target.replace() will cause problems. So I am guessing target is not a string.
Mostly I would like to know what popupNode actually does ...
thanks
Peter

I haven't really used "popupNode", but in general nodes aren't the same as strings. I suggest reading up on the Document Object Model (DOM) to learn more.
As far as replacing text, assuming popupNodes work like other nodes then something like this may work for you:
var target = document.popupNode;
target.innerHTML = target.innerHTML.replace("old_string", "new_string")

Related

Post comments on Facebook page via console

Like in the image, the Facebook comment box has no submit button, when you write something and press Enter button, the comment posted.
I want to submit the comment via JavaScript that running in console, but I tried to trigger Enter event, submit event of the DOM. Could not make it work.
The current comment boxes aren't a traditional <textarea> inside of a <form>. They're using the contenteditable attribute on a div. In order to submit in this scenario, you'd want to listen to one of the keyboard events (keydown, keypress, keyup) and look for the Enter key which is keycode 13.
Looks like FB is listening to the keydown evt in this case, so when I ran this code I was able to fake submit a comment:
function fireEvent(type, element) {
var evt;
if(document.createEvent) {
evt = document.createEvent("HTMLEvents");
evt.initEvent(type, true, true);
} else {
evt = document.createEventObject();
evt.eventType = type;
}
evt.eventName = type;
evt.keyCode = 13;
evt.which = 13;
if(document.createEvent) {
element.dispatchEvent(evt);
} else {
element.fireEvent("on" + evt.eventType, evt);
}
}
fireEvent('keydown', document.querySelector('[role="combobox"]._54-z span span'));
A couple of things to note about this. The class ._54-z was a class they just happened to use on my page. Your mileage may vary. Use dev tools to make sure you grab the right element (it should have the aria role "combobox"). Also, if you're looking to support older browsers, you're going to have to tweak the fireEvent function code above. I only tested the above example in the latest Chrome.
Finally, to complicate matters on your end, Facebook is using React which creates a virtual DOM representation of the current page. If you're manually typing in the characters into the combobox and then run the code above, it'll work as expected. But you will not be able to set the combobox's innermost <span>'s innerHTML to what you're looking to do and then trigger keydown. You'll likely need to trigger the change event on the combobox to ensure your message persists to the Virtual DOM.
That should get you started! Hope that helps!
Some years after, this post remains relevant and is actually the only one I found regarding this, whilst I was toying around trying to post to FB groups through JS code (a task similar to the original question).
At long last I cracked it - tested and works:
setTimeout(() => {
document.querySelector('[placeholder^="Write something"]').click();
setTimeout(() => {
let postText = "I'm a Facebook post from Javascript!";
let dataDiv = document.querySelector('[contenteditable] [data-offset-key]');
let dataKey = dataDiv.attributes["data-offset-key"].value;
//Better to construct the span structure exactly in the form FB does it
let spanHTML = `<span data-offset-key="${dataKey}"><span data-text="true">${postText}</span></span>`;
dataDiv.innerHTML = spanHTML;
let eventType = "input";
//This can probably be optimized, no need to fire events for so many elements
let div = document.querySelectorAll('div[role=presentation]')[1].parentElement.parentElement;
let collection = div.getElementsByTagName("*");
[...collection].forEach(elem => {
let evt = document.createEvent("HTMLEvents");
evt.initEvent(eventType, true, true); //second "true" is for bubbling - might be important
elem.dispatchEvent(evt);
});
//Clicking the post button
setTimeout(()=>{
document.querySelector('.rfloat button[type=submit][value="1"]').click();
},2000);
}, 4000);
}, 7000);
So here's the story, as I've learned from previous comments in this post and from digging into FB's code. FB uses React, thus changes to the DOM would not "catch on" as React uses virtual DOM. If you were to click "Post" after changing the DOM from JS, the text would not be posted. That's why you'd have to fire the events manually as was suggested here.
However - firing the right event for the right element is tricky business and has almost prevented me from succeeding. After some long hours I found that this code works, probably because it targets multiple elements, starting from a parent element of the group post, and drilling down to all child elements and firing the event for each one of them (this is the [...collection].forEach(elem => { bit). As written this can be obviously be optimized to find the one right element that needs to fire the event.
As for which event to fire, as was discussed here, I've experimented with several, and found "input" to be the one. Also, the code started working after I changed the second argument of initEvent to true - i.e. evt.initEvent(eventType, true, true). Not sure if this made a difference but I've had enough hours fiddling with this, if it works, that enough for me. BTW the setTimeouts can be played around with, of course.
(Unsuccessfully) Digging into FB's React Data Structure
Another note about a different path I tried to go and ended up being fruitless: using React Dev Tools Chrome extension, you're able to access the components themselves and all their props and states using $r. Surprisingly, this also works outside of the console, so using something like TamperMonkey to run JS code also works. I actually found where FB keeps the post text in the state. For reference, it's in a component called ComposerStatusAttachmentMentionsInputContainer that's in charge of the editor part of the post, and below is the code to access it.
$r actually provides access to a lot of React stuff, like setState. Theoritically I believed I could use that to set the state of the post text in React (if you know React, you'd agree that setState would be the right way to trigger a change that would stick).
However, after some long hours I found that this is VERY hard to do, since FB uses a framework on top of React called Draft.js, which handles all posts. This framework has it's own methods, classes, data structures and what not, and it's very hard to operate on those from "outside" without the source code.
I also tried manually firing the onchange functions attached to the components, which didn't work because I didn't have the right parameters, which are objects in the likes of editorContent and selectionContent from Draft.Js, which need to be carefully constructed using methods like Modifier from Draft.js that I didn't have access to (how the hell do you externally access a static method from a library entangled in the source code?? I didn't manage to).
Anyway, the code for accessing the state variable where the text is stored, provided you have React dev tools and you've highlighted ComposerStatusAttachmentMentionsInputContainer:
let blockMap = $r["state"].activeEditorState["$1"].currentContent.blockMap;
let innerObj = JSON.parse(JSON.stringify(blockMap)); //this is needed to get the next property as it's not static or something
let id = Object.keys(innerObj)[0]; //get the id from the obj property
console.log(innerObj[id].text); //this is it!
But as I wrote, this is pretty much useless :-)
as I wasn't able to post comments through the "normal" facebook page, I remembered that they also have the mobile version, which is on m.facebook. com, there, they still have the submit Button, so depending on your needs, this may be a good option
so, you could go to the mobile facebook post (eg https://m.facebook.com/${author}/posts/${postId}) and do
// Find the input element that saves the message to be posted
document.querySelector("input[name='comment_text']").value='MESSAGE TO POST';
// find the submit button, enable it and click it
const submitButton = document.querySelector("button[name='submit']");
submitButton.disabled = false;
submitButton.click();
Here is a working solution after 3 weeks of experimenting (using #Benjamin Solum's fireEvent function):
this version posts a comment only for the first post on the page (by using querySelector method)
this version can be used only on your personal wall (unless you change the query selectors)
function fireEvent(type, element, keyCode) {
var evt;
if(document.createEvent) {
evt = document.createEvent("HTMLEvents");
evt.initEvent(type, true, true);
} else {
evt = document.createEventObject();
evt.eventType = type;
}
evt.eventName = type;
if (keyCode !== undefined){
evt.keyCode = keyCode;
evt.which = keyCode;
}
if(document.createEvent) {
element.dispatchEvent(evt);
} else {
element.fireEvent("on" + evt.eventType, evt);
}
}
// clicking the comment link - it reveals the combobox
document.querySelector(".fbTimelineSection .comment_link").click();
setTimeout(function(){
var combobox = document.querySelector(".fbTimelineSection [role='combobox']");
var spanWrapper = document.querySelector(".fbTimelineSection [role='combobox'] span");
// add text to the combobox
spanWrapper.innerHTML = "<span data-text='true'>Thank you!</span>";
var spanElement = document.querySelector(".fbTimelineSection [role='combobox'] span span");
fireEvent("blur", combobox);
fireEvent("focus", combobox);
fireEvent("input", combobox);
fireEvent("keydown", spanElement, 13); // pushing enter
},2000);
function fireEvent(type, element) {
var evt;
if(document.createEvent) {
evt = document.createEvent("HTMLEvents");
evt.initEvent(type, true, true);
} else {
evt = document.createEventObject();
evt.eventType = type;
}
evt.eventName = type;
evt.keyCode = 13;
evt.which = 13;
if(document.createEvent) {
element.dispatchEvent(evt);
} else {
element.fireEvent("on" + evt.eventType, evt);
}
}
fireEvent('keydown', document.
to solve your question may you see this link, there is a example how to "Auto comment on a facebook post using JavaScript"
"Below are the steps:
Go to facebook page using m.facebook.com
Sign in and open any post.
Open developer mode in Chrome by pressing Ctrl+Shift+I
Navigate to the console.
Now, run the below script."
var count = 100;
var message = "Hi";
var loop = setInterval(function(){
var input = document.getElementsByName("comment_text")[0];
var submit = document.querySelector('button[type="submit"]');
submit.disabled = false;
input.value = message;
submit.click();
count -= 1;
if(count == 0)
{
clearInterval(loop);
}
}, 10000);
Kind regards
ref.: source page

Change style of tab using add-on SDK

I'm trying to dynamically change the style of a tab using the add-on SDK. How can I do this?
Here's what I have tried:
I can access tab objects like this:
var tabs=require('sdk/tabs');
tabs.on('ready',function(tab){
console.log('url is: '+tab.url); //-> url is http://www.google.com
console.log('stlye is: '+tab.url); //-> style is null
});
But the style attribute is null and none of the following work:
tab.setAttribute('style','background-color:blue'); // the method doesn't exist
tab.style.backgroundColor='blue'; // type error because style is null
tab.style='background-color:blue'; // has no effect
So how can I change the style of a tab dynamically? Another thing I have tried is converting the tab to a XUL object using code from the docs:
var { modelFor } = require("sdk/model/core");
var { viewFor } = require("sdk/view/core");
var tabs = require("sdk/tabs");
var tab_utils = require("sdk/tabs/utils");
function mapHighLevelToLowLevel(tab) {
// get the XUL tab that corresponds to this high-level tab
var lowLevelTab = viewFor(tab);
// now we can, for example, access the tab's content directly
var browser = tab_utils.getBrowserForTab(lowLevelTab);
console.log(browser.contentDocument.body.innerHTML);
// get the high-level tab back from the XUL tab
var highLevelTab = modelFor(lowLevelTab);
console.log(highLevelTab.url);
}
tabs.on("ready", mapHighLevelToLowLevel);
But the code throws an error: Module 'sdk/model/core' is not found at resource://gre/modules/commonjs/sdk/model/core.js
even though I followed the directions and created the core.js file. Also, I don't understand the what the curly braces are doing in the var { modelFor}= syntax.
You need to access the xul tab element.
Try this:
var tabsLib = require("tabs/tab.js");
var tab = tabsLib.getTabForWindow(htmlWindow);
tab.style.color = 'red'; //makes the tab text red
pass htmlWindow as the topmost window of the html document. So like document.defaultView.top. document.defaultView is the window of the document
if that doesnt work then just get the browser window, this example gets the most recent browser window (keeep in mind there may be multiple browser windows open (browser window is a firefox window that has tabs, [and maybe popup windows- im not sure in sdk])
const { getMostRecentBrowserWindow } = require('sdk/window/utils');
var aDOMWindow = getMostRecentBrowserWindow();
if (aDOMWindow.gBrowser && aDOMWindow.gBrowser.tabContainer) {
var tabs = aDOMWindow.gBrowser.tabContainer.childNodes;
for (var i=0; i<tabs.length; i++) {
tabs[i].style.color = 'red'; //makes the tab text red;
}
}

document.activeElement returning a XULElement

I'm trying to get the focused element when the user press CTRL + SPACE on my Firefox add-on.
To do it, I thought to use the document.activeElement to get the focused element, but it is not working. It's always returning a XULElement.
I have just two files in this add-on:
Lib/main.js
var self = require ("sdk/self");
var workers = require("sdk/content/worker");
let worker = workers.Worker({
window: require("sdk/window/utils").getMostRecentBrowserWindow(),
contentScriptFile: self.data.url("script.js")
});
var { Hotkey } = require("sdk/hotkeys");
var showHotKey = Hotkey({
combo: "control-space",
onPress: function() {
worker.port.emit ("getFocused", "");
}
});
and the file Data/script.js
self.port.on ('getFocused', function (msg){
var campo = document.activeElement;
alert (campo);
});
(How you can see here: Add-on SDK Builder Test Project )
So, can someone help me with it?
It's possible to get the input text or textarea from the XULElement and change its text?
Thank you very much!
------------------------ EDIT ----------------------
I don't know if it helps, but, when the url text area (where we write the adress of the sites) has the focus, it returns a
[object HTMLInputElement]
getMostRecentBrowserWindow returns a ChromeWindow object. So it's normal that the activeElement` is a XUL element.
What you should do is
window: require("sdk/window/utils").getMostRecentBrowserWindow().gBrowser.contentWindow

Calling url assigned in a var

While playing around with Raphael.js Australia map, I tried assigning URLs for each element by changing their attribute in the end of the path:
country.cityone = R.path("coordinates").attr({href: "cityone.html"}).attr(attr);
country.citytwo = R.path("coordinates").attr({href: "citytwo.html"}).attr(attr);
...
The above works with Firefox, Chrome etc, but IE6-IE9 have trouble with that declaration.
So I thought of declaring another variable after var country and assigning the urls to that:
var url = {};
url.cityone = "cityone.html";
url.citytwo = "citytwo.html";
then calling it on mouse click/down:
st[0].onmousedown = function() {
current && country[current] && document.getElementById(“current”).appendChild(url);
};
However it won't work at all. Apparently I'm not making the call properly from the function, to relate each URL to its respective city. What am I missing?
I haven't tested this but I'm pretty sure you should just do away with the href and add a mouse event:
country.cityone = R.path("coordinates").attr(attr).click(function(){
window.location.href = "cityone.html";
});
I'm pretty sure that will work.
Just to close this question, after several trials, I ended up finding out that IE requires a double-click on click(function(){ for the href page to open up
I found out that in the above code:
country.cityone = R.path("coordinates").attr(attr).click(function(){
window.location.href = "cityone.html";
});
I had to change the .click(function(){ into .mousedown(function(){ for IE to work as it should.
Thank you #Zevan!
Cheers

Accessing contents of NativeWindow in a HTML AIR application?

I'm currently building a HTML/JS AIR application. The application needs to display to the user a different 'window' - dependant on whether this is the first time they've launched the application or not. This part is actually fine and I have the code below to do that:
if(!startUp()) { // this simply returns a boolean from a local preferences database that gets shipped with the .air
// do first time stuff
var windowOptions = new air.NativeWindowInitOptions();
windowOptions.systemChrome = 'none';
windowOptions.type = 'lightweight';
windowOptions.transparent = 'true';
windowOptions.resizable = 'false';
var windowBounds = new air.Rectangle(300, 300, 596, 490);
var newHtmlLoader = air.HTMLLoader.createRootWindow(true, windowOptions, true, windowBounds);
newHtmlLoader.load(new air.URLRequest('cover.html'));
}
else {
// display default window
// just set nativeWindow.visible = true (loaded from application.xml)
}
However, what I want to be able to do is manipulate the html content from within cover.html after it has loaded up. There seems to be plenty of tutorials online of how to move, resize, etc. the NativeWindow, but I simply want access to the NativeWindow's HTML content.
For example, how would I add a new paragraph to that page? I've tried the following:
newHtmlLoader.window.opener = window;
var doc = newHtmlLoader.window.opener.document.documentElement;
Using AIR's Introspector console, ....log(doc) returns [object HTMLHtmlElement].
Hmm, seems promising right? I then go on to try:
var p = document.createElement('p');
var t = document.createTextNode('Insert Me');
p.appendChild(t);
doc.appendChild(p);
...but nothing gets inserted. I've also tried the following replacements for doc:
var doc = newHtmlLoader.window.opener.document.body; // .log(doc) -> [object HTMLBodyElement]
var doc = newHtmlLoader.window.opener.document; // .log(doc) -> Error: HIERARCHY_REQUEST_ERR: DOM Exception 3
...as well as the following with jQuery:
$(doc).append('<p>Insert Me</p>'); // again, nothing
So, anyone had any experience in accessing a NativeWindow's inner content programmatically? Any help will be greatly appreciated.
Hmm, so I think I may have found out how to do it...If we amend the original code and add an event listener on the loader:
var newHtmlLoader = air.HTMLLoader.createRootWindow(true, windowOptions, true, windowBounds);
newHtmlLoader.addEventListener(air.Event.COMPLETE, doEventComplete);
newHtmlLoader.load(new air.URLRequest('cover.html'));
You can then interact (assuming you're using jQuery) with the contents of the newly created window by using:
function doEventComplete(event) {
doc = $(event.currentTarget.window.document.body);
doc.append('<p>Insert Me!</p>')
}
:)
I'm not sure this has the effect you intended:
newHtmlLoader.window.opener = window;
var doc = newHtmlLoader.window.opener.document.documentElement;
What is does is set var doc = window.document.documentElement;, so 'doc' is your local document, not the one in the other window.
I think what you want is
var doc = newHtmlLoader.window.document.documentElement;
Do note that this will not work until the document has loaded.

Categories

Resources