Missing properties in window when using a content script - javascript

My addon is injecting some content scripts into various websites. After trying to bind onbeforeunload or calling window.location.reload I realized that the window object misses some properties.
Is there a way to binding certain events (onbeforeunload, onunload, etc) while injecting code via page-mod module?
I've created a test add-on, showing that these properties are missing: https://builder.addons.mozilla.org/addon/1037497/latest/
Solutions on how to use them anyway?

Short answer: You add an event listener using addEventListener() method, like this:
window.addEventListener("beforeunload", function(event)
{
...
}, false);
Long answer: For security reasons your content script isn't communicating with the DOM objects directly, e.g. it cannot see any script-added properties. The technical details also list some limitations:
Assigning to or reading an on* property on an XPCNativeWrapper of a
DOM node or Window object will throw an exception. (Use
addEventListener instead, and use event.preventDefault(); in your
handler if you used return false; before.)
In a content script you probably don't want to replace the web page's event handlers anyway, rather add your own - that's what addEventListener() is doing.
Additional reading

Related

Detecting javascript event handlers with Capybara

In my Rails app there is a whole lot of front-end js, and I'd like to check is a specific event is attached to a DOM element.
An example of the JS that adds the event is here:
$('.nextCard')[0].addEventListener('click',nextCard);
I'd like to write something like:
expect(page).evaluate_script('​$._data( $(".nextCard")[0], "events" )').to eq('nextCard');
I know it's a bit cryptic, and I could test the JS separately ... but I would like to do all the testing with Ruby / Capybara if possible.
How to return a value when using execute_script in capybara?
Can I find events bound on an element with jQuery?
Since you state the event is added using plain DOM methods (addEventListener) there is no way (cross browser/unprivileged code) to enumerate the listeners. However, if the handlers are attached via jQuery#on then you could check for the presence of a click handler with something like
expect(page.evaluate_script('​"click" in $._data( $(".nextCard")[0], "events" )')).to be true
That being said, what you're trying to do is not really a great idea, and will end up with really brittle tests. What you should be doing in your feature tests is verifying the behaviors those click handlers facilitate work.

How can I define a property on the tab's main window object?

I'm trying to define the property jQuery on the window object from a content script with code in the setter. That way, when the actual jQuery object is defined, I can use it right away. I seem to be unable to get it right, though.
The target website is Outlook.com. That's the webmail version of Outlook.
I tried to put the code in the content script directly, but even if I put "all_frames": true in the content_scripts section of the manifest (so the code gets injected into every frame), it isn't working.
function afterInit(){
var _jQuery;
console.log('This gets logged');
Object.defineProperty(window, 'jQuery', {
get: function() { return _jQuery; },
set: function(newValue) {
_jQuery = $ = newValue;
console.log('This is never logged!');
$(document.body).append($('<span>I want to use jQuery here</span>'));
}
});
}
afterInit();
I verified that window.jQuery is properly defined afterwards by the actual jQuery function/object, but my setter code is never executed.
I also tried it with message passing: I send a message with the code as a string to a background script, and use executeScript to execute it on the correct tab, but this also doesn't work.
chrome.runtime.sendMessage(
{action:'jQueryPreInit', value: '('+afterInit.toString()+')();'});
And in my background script:
chrome.runtime.onMessage.addListener(function(message, sender, callback) {
switch (message.action){
case "jQueryPreInit": chrome.tabs.executeScript(sender.tab.id, {code: message.value});
}
});
If I put something else than the Object.defineProperty code in the executeScript code, that works fine. I only have problems defining the property.
(quotes are from a comment)
I want to use the jQuery provided by the page itself. I could try inserting the same jQuery file as Outlook does and hope it gets loaded from cache, but I'd rather just keep my extension as clean as possible, and use what is already available.
Your attempt at optimizing the extension is not workable / not recommended.
First off, you will not be able to use the page's code anyway because of the isolation between content script and webpage code. You cannot obtain a reference to page's own jQuery/$.
But let's for a moment suppose that you could. And then the site updates jQuery to another version, renames the jQuery object or stops using it entirely, which is outside your control. Result: your extension is broken. This is, partially, the rationale behind the isolation in the first place.
As a result of the context isolation, you are guaranteed there are no conflicts between your copy of jQuery and whatever runs on the site. So you don't need to worry about that: use your copy, and use the standard $ to access it.
Bundling a <100 KB file with your extension as a one-time download that makes sure code is available 100% of the time and with at worst disk-access latency is not making it less "clean", quite the opposite. It's a common practice and is enshrined in the docs.
Looking at your actual code, it executes in the content script context (regardless whether it's through manifest or executeScript), not in the page context. As such, no matter what the page does, $ will not be defined there.
I verified that window.jQuery is properly defined afterwards by the actual jQuery function/object [...]
I assume that you tried to execute window.jQuery in the console; by default, that executes it in the page context, not in your content script context (therefore, not reflecting the state of the content script context and not invoking your getter/setter). If you want to test your content script, you need to change top in the context drop-down above the console to your extension's context.
All that said, however,
When all is said and done, I want to use jQuery's ajaxSuccess function to execute code every time an e-mail is opened in the read pane.
Here we've got a problem. Since the content script code and webpage code are isolated, your code will never know about AJAX executing in the page's copy (not through ajaxSuccess, anyway).
Possible courses of action:
Rely on other methods to detect the event you want. Perhaps monitoring the DOM.
Inject some code into the page itself; the only way to do so is by injecting a <script> tag into the page from the content script. There, you can access the page's copy of jQuery, attach your listener and message your content script when something happens.
Rely on the background page to detect activity you need with webRequest API. This will likely intercept the AJAX calls, but will not give you the reply contents.
Final note: this may not be as simple as AJAX calls; perhaps the page maintains a WebSocket connection to get realtime push updates. Tapping into this is trickier.
Thanks to Xan, I found there are only two ways to do this.
The first is by adding a <script> element to the DOM containing the appropriate code. This is a pretty extensive StackOverflow answer on how to do that: https://stackoverflow.com/a/9517879/125938.
The second is using Javascript pseudo-URLs and the window.location object. By assigning window.location a bookmarklet-style URL containing Javascript, you also bypass certain security measures. In the content script, put:
location = 'javascript:(' + (function(){
var _jQuery;
Object.defineProperty(window, 'jQuery', {
get: function() { return _jQuery; },
set: function(newValue) {
_jQuery = $ = newValue;
console.log('totally logged!');
$('<span>jQuery stuff here</span>');
}
});
}).toString().replace(/\n/g, ' ')+')();';
The reason I/you were originally failing to define it, was because both methods of code injection we were using, caused our code to be sandboxed into isolated worlds: https://developer.chrome.com/extensions/content_scripts#execution-environment. Meaning, they share the page's DOM and could communicate through it, but they can't access each other's window object directly.

Use of this.own() method in dojo

i would like to know the intention of the "this.own()" method in dojo widgets. This method is mentioned in the Dojo Api 1.8 Documentation, for example under diijit/form/button. I did not find anything that made sense to me on google. That is how the method is mentioned:
connect(obj, event, method)
Deprecated, will be removed in 2.0, use this.own(on(...)) or
this.own(aspect.after(...)) instead.
The own function is defined in dijit/Destroyable, which is a base of dijit/_WidgetBase and thus most widgets.
dijit/Destroyable is used to track handles of an instance, and then
destroy them when the instance is destroyed. The application must call
destroy() on the instance in order to release the handles
http://dojotoolkit.org/reference-guide/1.8/dijit/Destroyable.html
http://dojotoolkit.org/reference-guide/1.8/dojo/Evented.html
The short answer is: most of the things that you define inside .own() are getting correctly removed once the widget itself is destroyed. Using .own() prevents memory leaks in your app.
To remove a widget from a page, you can either call destroy or
destroyRecursively on your widget.
When you do that, anything that you added using this.own ( dojo/on,
dojo/aspect, dojo/topic, dojo/router, the creation of a related DOM
node or widget, etc.) will be removed and/or unregistered
automatically. This is implemented via the dijit/Destroyable
interface.
Understanding-WidgetBase-own-td4002453.html
Related Tutorial

Inline script elements in an AJAX response applied to the DOM - Do they execute in a child window context?

I am working on a bug, where there is a request made and then an updated view is returned with an inline script block to update some of the view. I was getting an odd reference error with $ not being defined. After some investigation in chrome, I discovered that in the context that appears to exist at the very instant the reference error occurs there is no $ defined, but window.parent.$ is defined.
Method of discovery:
I basically had Chrome break on unhandled errors, which naturally brought me to a callstack of one call that was just the anonymous function. Usually the console seems to use a context consistent with the breakpoint, but in this case I had to use the watch window to see what was defined and what was not. I observe this behavior in all of Firefox, Chrome, and I.E.
The fact that the request is an AJAX request is probably irrelevant. When HTML is appended to an already existing DOM, and it has inline script tags - do these inline script tags execute within their own child window context similar to an iframe?
We are injecting the returned response via jQuery, which I am assuming may be handling the execution. Could jQuery be creating a new child window context?
In what situations outside of an IFrame is a child window context used?
The problem actually did end up being an IFrame. Someone was using a callback within a callback using a "ajaxSubmit" extension which used an IFrame internally. My assumption about IFrames not being used was incorrect.

Interact with DOM before DOMReady fires

I would like to interact with the DOM immediately when the element becomes available. I can do this with a setInterval, but it won't work with Crossrider because the js that can interact with the DOM is only loaded once the DOM is ready. Any way to get aroind this?
Thanks
The current API doesn't officially support this feature at this time.
However, you're welcome to try our new appAPI.dom methods which work in Chrome and Firefox but are currently undocumented until they are officially released. The new methods are pretty self-explanatory, so I've listed them here with a brief description:
appAPI.dom.onDocumentStart.addJS(String jsCode, [Array siteList])This method adds the specified jsCode when the document starts to load. Optionally specify an array of strings/regex expressions (see http://docs.crossrider.com/#!/api/appAPI-method-isMatchPages for examples) specifying which URLs to add the code to.
appAPI.dom.onDocumentStart.addCSS(String cssRules, [Array siteList])This method adds the specified cssRules when the document starts to load. Optionally specify an array of strings/regex expressions (see http://docs.crossrider.com/#!/api/appAPI-method-isMatchPages for examples) specifying which URLs to add the CSS to.
you might want to check out the appAPI.dom.onDocumentStart object because it doesn't seem to be defined even when called after appAPI.ready(). This is the error I'm getting:
Uncaught TypeError: Cannot call method 'addCSS' of undefined
Thanks! Keep up the good work!

Categories