Changing Window.prototype.open in a way that isn't detectable/reversible
Asked Answered
M

4

14

I am looking into ways to extend Firefox pop-up blocking from an extension. One option is replacing window.open() (or rather Window.prototype.open()) in the webpage by a wrapper function. An important requirement is that this manipulation cannot be detected or reverted by the webpage. For example, if I simply do this:

Window.prototype.open = wrapper;

The webpage can easily revert the change by doing:

delete Window.prototype.open;

Instead I can use Object.defineProperty() to set advanced property flags:

Object.defineProperty(Window.prototype, "open", {value: wrapper, configurable: false});

The webpage can no longer revert this change but it can still detect it: delete Window.prototype.open normally changes the value of Window.prototype.open (different instance of the same function it seems), here delete won't have any effect at all. Also, Window.prototype.open = "test";delete Window.prototype.open; will produce inconsistent results (different ones depending on whether writable: false flag is specified for the property).

Is there anything else that I can do to emulate the behavior of the original property (short of using binary XPCOM components which has way too many issues of its own)?

Mightily answered 11/7, 2011 at 11:57 Comment(4)
Why does it have to be undetectable?Slobber
@cwallenpoole: The webpage shouldn't know that anything beyond the usual pop-up blocker is being used - some webmasters tend to use some very unfriendly techniques if they get a chance.Mightily
Are you ruling out use of a binary XPCOM component?Edison
@Matthew: No, using binary XPCOM components definitely isn't going to work (see adblockplus.org/blog/…).Mightily
M
4

In the end I had to give up on using JavaScript proxies for the job. Even though with some effort I can create a wrapper for window.open() that behaves exactly like the original (bug 650299 needs to be considered), there doesn't seem to be a proper way to replace the original window.open() function. The changed property will always behave differently from the original one, too bad.

So I decided to go with a different approach as a pop-up blocking solution: listen for content-document-global-created notification and have a look at the subject (the new window) as well as its opener. Windows with a non-null opener are pop-up windows of some kind. One can look at the URLs and decide whether the pop-up should be blocked. To block one would call window.stop() (stops all network activities before any network requests are sent) and window.close(). The latter has to be called asynchronously (with a delay) because it will cause a crash otherwise as the initialization of the window continues. Some notes on this approach:

  • For pop-ups opening in a new window the window will still show up but disappear immediately. This seems to be unavoidable.
  • For the web page it looks like its pop-up window opened but was closed immediately - this isn't how the built-in pop-up blocker works, more like an external pop-up blocking application.
  • New windows always load about:blank first before changing to their actual destination. For same-origin pop-ups the latter won't send a new content-document-global-created notification which is unfortunate.

All in all: not perfect but usable. And it is very simple, nowhere near the amount of code required for JavaScript proxies.

Mightily answered 4/10, 2011 at 7:0 Comment(0)
E
7

You might try using the nsIWindowWatcher interface to register your own window creator (nsIWindowCreator). That way you can control whether a new window is opened without affecting the window object itself (and thus remaining invisible to web sites).

I'm not sure whether the inability to change the implementation of window.open() without this being detectable is a bug. Perhaps it's just not considered an important requirement for methods like Object.defineProperty. But it might be worth filing a bug to see what others think about making this an option in the future. After all, ad blocking is a major use case.

Edison answered 14/9, 2011 at 7:48 Comment(3)
nsIWindowWatcher.setWindowCreator() looks like a viable solution. Only obvious problem is that getting the old window creator is impossible, so the new window creator will have to call the default window creator - no chaining possible. But that's something that should be easy to fix with a bug report.Mightily
Yes, I think setWindowCreator() should return the old window creator (or they could simply add a new getWindowCreator() method).Edison
Sorry, I only managed to test it now. Unfortunately, I noticed that proper pop-up blocking needs to happen earlier, before the window creator is called. One reason is that it only gets called for actual windows created, not for window.open() calls that get diverted into a tab. But it also lacks the context. While you get the URL that is being opened, you don't have any idea who is trying to open it. The aParent parameter is pretty useless because it only indicates the browser window but not the tab.Mightily
M
4

In the end I had to give up on using JavaScript proxies for the job. Even though with some effort I can create a wrapper for window.open() that behaves exactly like the original (bug 650299 needs to be considered), there doesn't seem to be a proper way to replace the original window.open() function. The changed property will always behave differently from the original one, too bad.

So I decided to go with a different approach as a pop-up blocking solution: listen for content-document-global-created notification and have a look at the subject (the new window) as well as its opener. Windows with a non-null opener are pop-up windows of some kind. One can look at the URLs and decide whether the pop-up should be blocked. To block one would call window.stop() (stops all network activities before any network requests are sent) and window.close(). The latter has to be called asynchronously (with a delay) because it will cause a crash otherwise as the initialization of the window continues. Some notes on this approach:

  • For pop-ups opening in a new window the window will still show up but disappear immediately. This seems to be unavoidable.
  • For the web page it looks like its pop-up window opened but was closed immediately - this isn't how the built-in pop-up blocker works, more like an external pop-up blocking application.
  • New windows always load about:blank first before changing to their actual destination. For same-origin pop-ups the latter won't send a new content-document-global-created notification which is unfortunate.

All in all: not perfect but usable. And it is very simple, nowhere near the amount of code required for JavaScript proxies.

Mightily answered 4/10, 2011 at 7:0 Comment(0)
T
0

Web browsers intentionally prevent this behavior, it's for maintaing the security of web e.g. when you use iFrame you don't want that iFrame to mess up or hack your page.

But instead of manipulating the window object properties why not to create a wrapper for the window object and override window by the wrapper locally?

Example:

// Copy window object to wraper
var wrapper = {};
for(prop in window) {
  wrapper[prop] = window[prop];
}

wrapper.open = function yourNewOpenFunction() {
  /// do your custom code here
}

(function fakeScope(window){
    window.open(); // this is wrapper.open
}(wrapper));

BTW this affects only the body inside fakeScope() function, and cannot be applied globally.

Trustworthy answered 10/9, 2011 at 19:41 Comment(3)
The frame cannot mess up your page because the same-origin policy prevents it. The fact that behavior of "built-in" properties cannot be reproduced is a bug, not a feature. As to replacing the window object - this is not possible even if it helped here. (function(){alert(this)})() will always show the "real" global object, no matter what you set the window variable to.Mightily
@Omar where does i come from in that loop?Thedrick
@WladimirPalant I stated this above. And of course window cannot be changed, except that you always control the local scope of a function. It was just a quick and dirty solution to avoid search/replace your already available code when your place it in the body of the fakeScope function.Trustworthy
E
0

it striked me this morning: you can use Object.freeze(Window.prototype); ! test have shown, that methods protected with this cannon be deleted, but they can be easily detected.


old answer:

What about ES:Harmony Proxies ? http://brendaneich.com/2010/11/proxy-inception/

Of course, they are unstable, but they are working in Firefox 4+, and you are not the man, who is afraid of difficulties ;)

Ethelstan answered 16/9, 2011 at 7:1 Comment(7)
I already looked at the proxies (that's how I plan to create an identical wrapper for window.open). But please explain how you would use them to replace that method. Please note that replacing window isn't possible (see my comment to Omar's answer) and replacing only Window.prototype wouldn't help because window.__proto__ would still point to the original prototype object.Mightily
On the paper they sounded much more powerful... but after initial look on them, it seems that to reliably hide modified code, you need to also replace / wrap - toString and everything similar to it, and maybe even call, apply and bind.. which is just wrong.Ethelstan
Yes, see adblockplus.org/blog/…. This is something I've sort of solved already (turned out that Object.prototype.toString needs to be wrapped as well). At this point the remaining question is really - how does one replace the original method?Mightily
Alright.. it seems that delete handler is not firing at all (neither in original form, nor with delete quoted, or with underscore, like its written in source code), so either code provided at MDN is wrong, or implementation is buggy, or i don't understand the spec.. bug546590.bugzilla.mozilla.org/attachment.cgi?id=445925 And in that case - you cannot prevent deletion.Ethelstan
You do not understand the spec. The delete handler is fired when properties of the proxy are deleted - not when properties of an unrelated object are deleted that happen to have the proxy as their value.Mightily
I know, the party has almost over a month ago, but you might still be interested in solutions for this.Ethelstan
Isn't your new solution effectively the same as making this property non-configurable as indicated in my question? Btw, please don't delete your answer again - even though it doesn't help me it might be useful for somebody else.Mightily

© 2022 - 2024 — McMap. All rights reserved.