How to access DOM in Firefox addon script? - javascript

I tried to use something like document.forms in Firefox-addon script but it doesn't work.
So, I need to manipulate DOM objects in Firefox-addon script such as forms, inputs... etc. How can I do that without using SDK?

document.forms will not work, because document is not what you think it is: It is the top level browser (Firefox) window, and not the content in a tab.
A Firefox browser window can have multiple tabs, one of which is the active tab. The active tab <browser> element (which is the XUL element containing the actual content document) also has a shortcut named content, e.g. content.document.forms will be a collection of forms in the active tab.
So you'll have to adjust your mental model here from
window and document refer to a website
to
window and document refer to the top-level browser window that may contain a lot of different websites.
The top-level window is more like a document containing multiple frames (the actual websites), really, but with a different APIs to access them.
So, e.g. when executing some action after the user pressed some add-on toolbar button, it might be enough to just use content.document.forms to get the forms of the currently active tab.
But using content. is often not enough: Add-ons would listen for page loads in tabs as the user navigates by adding appropriate event listeners to the <tabbrowser> element (gBrowser), which is the element containing all tabs. MDN has some code snippets for that and lots of other stuff.
Other add-ons add item(s) to the content context menu (contentAreaContextMenu) and use the popupshowing event to know what DOM node (and by this what .ownerDocument and content window == .ownerDocument.defaultView) is currently focused.
An important thing to always keep in mind: Your add-on code runs with full privileges, while websites of course do not. So be careful not to write insecure code. E.g. all forms of unbound eval are evil.

Judging by your comments, your code is running in the context of the browser window. This means that document refers to the document of the browser window, not the document that is loaded into it. The easiest way to get to the latter is using the window.content property:
var contentDoc = content.document;
alert(contentDoc.forms.length);
This will give you only the current tab however. For the other tabs you can use the APIs provided by the <tabbrowser> element (accessible via the global gBrowser variable), e.g. to access the first tab:
var contentDoc = gBrowser.browsers[0].contentDocument;
alert(contentDoc.forms.length);

Related

jQuery cannot detect an injected script that updates the page

I'm trying to use jQuery to modify an element that's "injected" externally. I've tried delegation with on but it didn't work.
Here's the page, scroll down and you'll see an avatar named "Sebastian" with <div class="Avatar">.
If I go right click, Console and type: $('.Avatar'), the element is identified, but this is only because I first clicked on "Inspect element" for that element. jQuery somehow "updated" the source and now it identifies the element.
Now, try to refresh the page and type $('.Avatar') again, jQuery will not identify the element (although it's already loaded on the page).
You can take a look under "A working example" how this script is injected into the page.
My question is, is it possible (and if so, how) to modify this HTML (which seems to be inserted dynamically as the page is loaded)? It doesn't seem to be using any sort of iFrame nor anything, it just dynamically loads into the page, yet jQuery is unable to recognize it (unless you "tell it" to do so by clicking on "Inspect element" on the actual element).
P.S. I've tried using on, delegate, it doesn't work.
jQuery will not identify the element after page because it's in another iframe.
You said "It doesn't seem to be using any sort of iFrame nor anything", but in the end it's iframe.
The reason why you can find it when you go right click on element and then in developers tools you write $('.Avatar') is because once you inspect element (right click) inside developer tool iframe will change.
Furthermore, your parent iframe and iframe that have avatar element have same origin. Run document.domain inside parent and other iframe. Iframe with avatar have origin "app.talkjs.com" and parent iframe have origin"talkjs.com".
Subdomains may be same-origin.
There’s a small exclusion in the “Same Origin” policy.
If windows share the same second-level domain, for instance john.site.com, peter.site.com and site.com (so that their common second-level domain is site.com), they can be treated as coming from the “same origin”.
https://javascript.info/cross-window-communication
You should be able to catch onload iframe event and then search for .avatar.
iframe.onload = function() {
let newDoc = iframe.contentDocument;
console.log(newDoc.getElementsByClassName("avatar");
};

Javascript function document.querySelectorAll works weird on Chrome

I am trying to get elements by the Chrome developer console using the function document.querySelectorAll, the point is that it does not return any element, however I see the elements on the Elements tabs.
I was wondering whether someone has faced similar issues. Shall I change some options on the browser configuration?
By the way, the Chrome version is 63 on MAC. In addition, the page I am working on has an iframe html tag, may this be the reason of the strange behavior?
This is what I get from the Developer Console
And this is what I get from the elements tabs:
There aren't any browser settings that would affect document.querySelectorAll(). It's pretty core functionality.
You mentioned an iframe, so it's likely that is the source of the confusion. When using iframes, you can't access or modify the contents of the iframe directly from the outer level. To the outer level, it's essentially a black box. This is due to sandboxing that the browser does.
The exception to this is if the iframe and the main page are on the same domain (e.g., http://example.com/page1 and http://example.com/page2).
If they are both on the same domain, then you can access it's window with contentWindow:
const iframe = document.querySelector('iframe');
iframe.contentWindow // the window for the iframe
From there, you can access its document, and run querySelectorAll() against that:
iframe.contentWindow.document.querySelectorAll('div');
That will get all of the div elements in the iframe.

Shadow DOM not clear

I need to access the body element of an opened window that has Shadow DOM. Run this code on your browser (you need to disable third party security on your browser):
<script type="text/javascript">
janela = window.open("http://www.google.com.br");
window.setTimeout(
function() {
console.log(janela.window.document.body.innerHTML);
},
5000
);
</script>
If you see at your console there will be an empty string. Now change the URL http://www.google.com.br to http://www.bing.com.br and it works fine: the BODY innerHTML is displayed in the console.
I see that Google is now using Shadow DOM and it's probably what is causing my problem. Open Google.com in your browser -> F12 and you will see there is a #shadow-root element and I think this creates my problem. How can I bypass that and have access to the DOM?
Shadow DOM has nothing to do with this. Your browser doesn't want to let any random website open up something like gmail.com and read whatever they see there. If it did, then any website you visit could read all your email any time you're signed in to your gmail account.
Please read the section on "Cross-origin script API access" here.
JavaScript APIs such as iframe.contentWindow, window.parent, window.open and window.opener allow documents to directly reference each other. When the two documents do not have the same origin, these references provide very limited access to Window and Location objects, as described in the next two sections.
The whole idea of shadow DOM is encapsulation, so you cannot access the shadow DOM using JavaScript outside of the context of the context that creates it.

Window.open reloading window

I have a an iframe that has a report within it. What I also have, is a feature to allow the user to detach the report within the iframe and open it up in it's own window, using window.open() call.
My problem is, when I press on the detach button, the whole report that initially loaded in the iframe actually goes through the motions of re-running the query again and so presents the user with a white screen until the report eventually renders again.
Is there anyway of not re-running the report in the detached window or somehow grabbing a cached version?
Thanks.
If you already have the HTML on the client side, you can write that to the popup window without going to the server.
var w = window.open();
w.document.write("Text in new Window");
That will open a window and write some text to it. All you need to do now is get the content from your iframe and write it to the new window. Bingo :)
BTW IMO: Opening new windows in browsers should be avoided where possible as many browsers block it and most automated UI testing tools don't support it.
EDIT (in response to comment):
Here is an example of reading from and writing to an iFrame using the jQuery JavaScript library.
// Write to
$("iframe").contents().find("body").html("Test Html")
// Read from
alert($("iframe").contents().find("body").html());
This basically finds iframe elements in the document and reads and write content to them. If your not using a JavaScript library I highly recommend learning up and using one of them.
BTW: My advice on popup windows also holds for iframes. You should avoid using them where possible.

How do I build an iframe with the same domain as the page in Safari/WebKit

The scene: I'm writing an embeddable widget. It takes the form of a <script> tag, which builds an iframe containing everything it needs to display. The iframe has no src, and the script writes to it with theIframe.contentWindow.document.write(). This keeps the widget contained, and keeps element ids and script from conflicting with the page on which the widget is embedded.
The trick: The widget has to be able to change its size. To do this, it sets its containing iframe's style.height. This requires access to the outer page's DOM. In Firefox and IE, this is allowed, because the iframe's document and the outer document are considered to share an origin.
The twist: In Safari, however, the two documents are considered not to share an origin. The inner document is considered to be at about:blank, while the outer document is clearly using a different protocol and "domain" (if blank can be considered the domain).
The question: How can I build an iframe programmatically whose document Safari/WebKit will consider to have the same origin as the document of the window creating it?
Edit: After further experimentation, I can't find a way to programmatically create an iframe whose location is not about:blank regardless of whether I change its contents.
If I create the frame with document.createElement(), give it a src which points to a real HTML resource on the same origin called "foo.html", and document.body.appendChild() it, Safari's console shows the element as expected in the DOM, but the contents of the page do not appear, and the document is listed in the sidebar as "about:blank".
If I include the HTML for the iframe directly in the page, the contents of foo.html appear, and "foo.html" appears in the sidebar.
If I insert the HTML using document.write(), I get the same result as with document.body.appendChild().
Both programmatic versions work in Firefox.
The best suggestion I could give is to have the iframe set to a blank page on the same server (ie blank.html) and then edit the content. A pain in the rear, I know but it's a workaround.
You could also try
iframe.contentDocument.open("replace");
iframe.contentDocument.write("<b>This is some content</b>");
iframe.contentDocument.close();
However, I'm not sure if that only works in IE. Sorry I couldn't be more helpful than that.
Aha. This seems to be a bug in WebKit. When an iframe is created programmatically, its src attribute is ignored. Instead, the frame defaults to about:blank and must be directed to a URL to point elsewhere. For example:
theIframe.contentWindow.location = theIframe.src

Categories