Javascript function document.querySelectorAll works weird on Chrome - javascript

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.

Related

How to access add-on content script in Firefox console?

I have developed an add-on for Firefox and Chrome. It has content scripts. I want to access them in the browser tab's console (on Firefox the Web Console). For example, I want to enter a global variable defined in the content script(s) in the console, and it will output its value.
In Chrome, I can open the console by pressing F12, then navigate to the Console tab in the developer tools. It has a dropbox, right after the filter button, to select which context I am in (page/content script):
In Firefox, how to do the same thing?
The ability to change the context/scope of the Web Console (opened directly with Ctrl-Shift-K or F12 and selecting the Console tab) to that of the content scripts for an extension does not appear to exist. In addition, this capability does not exist in any of the other ways to view a console in Firefox. A bug/RFE should be filed on Bugzilla requesting this functionality; it would be quite useful. You will want the RFE to clearly explain that there should be the ability to switch to the content script context/scope for each frame in the tab (i.e. the top frame and each child frame). This should be the case for both the Console and the Debugger.
NOTE: You can change the console into the context/scope of the iframe's page scripts by selecting the frame from the drop-down menu opened from the frame-selector drop-down:
If this drop-down icon is not appearing for you, go to the DevTools settings and check "Select an iframe as the currently targeted document". However, doing this A) does not switch into the content script context/scope and B) does not work properly with the Web Debugger (testing in the current version of Firefox and Nightly (54.0a1).
Web Debugger
You can use the Web Debugger (Ctrl-Shift-S, or F12 and selecting the Debugger tab) with WebExtension content scripts. The content scripts for the extension are listed in the "Sources" under a moz-extension:// URL. You will need to identify the UUID that is used for the extension. You can view the content of variables, set breakpoints, etc. However, this does not give you the ability to explicitly switch to the context of the child frame. Placing debugger; directives in the JavaScript which is running in the child iframe is ineffective.
Web Debugger debugging content script (in top frame):
Console in background script context
If you were wanting to open a console which was in the context of your WebExtensions' background script, you could do so by clicking on the Debug button for your extension in about:debugging. However, this will not get you access to a console in the content script's context.
Workarounds for seeing variable values in the iframe
For what you need: unambiguously indicating that values are in the iframe context, not the top frame; I see two methods of doing so:
Use console.log() with information prepended that clearly indicates that the script believes it is running in the iframe. For example:
console.log('In iframe', 'foo=', foo);
So that you don't have to have 'In iframe' in every call to console.log() you make, you could create a function that prepends that text to all calls to that function. You could even override the console.log() function so your code still just calls console.log().
However, this only tells you that your code thinks that it is running in the iframe. Part of what you may be debugging is your content script code detecting that it is in an iframe.
This method does not indicate with certainty that the reported values are actually in the iframe.
Store values into the DOM using Element.dataset, or other DOM element attributes, and then inspect the DOM for these values. To view these attributes, I find that the DOM Inspector shows these quite clearly:
This method can be used to unambiguously show that the values are ones in the iframe, and exactly which iframe, without relying on the code running in the iframe to accurately determine that it is in an iframe and which iframe it is in.
A simple solution is to just console.log() in the content script and then click the sourcemap link to view the script. As shown below:
It's not yet possible. There is a bug Implement UI for switching context to content script opened (since Nov 2017) for that.
In Firefox Developer Edition, go on "about:debugging" page and click on the "Debug" button beside your add-on to open the dev tools.

Chrome Dev Tools console - select frame programmatically

Using the Chrome Dev Tools console, I'm trying to select an element inside an iframe on the page. Is there a way to do this programmatically without having to select the frame in the frames dropdown in order to set the console context to that frame first? Assuming the target iframe is frames[1], and the element inside that iframe has an id of "some-elem", the following does not seem to work:
frames[1].document.getElementById('some-elem');
I think you want contentDocument instead of document (see this related question).
Note that this will only work if iframe and main document are in the same domain. Otherwise, you are attempting cross-site scripting and it will be blocked by the browser.

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.

How to access DOM in Firefox addon script?

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);

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