How do I get my clipboard back?
August 22, 2010 10:37 PM Subscribe
Is it possible to use GreaseMonkey to delete jQuery event bindings?
There is a web page I visit, which I sometimes like to copy text from. Sadly, the owners have seen fit to append spam to my clipboard whenever I do this. They use jQuery to bind to the "copy" event to trigger the spam-appending. I want to get rid of this.
The code they're using is this:
There is a web page I visit, which I sometimes like to copy text from. Sadly, the owners have seen fit to append spam to my clipboard whenever I do this. They use jQuery to bind to the "copy" event to trigger the spam-appending. I want to get rid of this.
The code they're using is this:
var clip=null; $(document).ready(function() { $("div[id$='divArticleText']").bind("copy",function() { // let's spam the user's clipboard now! } }I tried the plainly obvious unbind without a parameter, but it doesn't work. I don't know jack about GM or jQuery, so talk to me like I'm an idiot, please.
I employ a much simpler solution: I just add rules to Adblock to block the scripts that do that kind of thing (along with the scripts that put annoying hover popups on sites like the nytimes, and so on.) When the script is hosted on a third party site this has the additional advantage of eliminating the annoyances on every site that uses that service instead of just one.
If you want to do it in Greasemonkey though it should still be possible, but events are a little tricky in Greasemonkey scripts. You can't just assign to the .oncopy property of an object, you have to use .addEventListener(). The problem with this is that it means that now you have two functions bound to the oncopy event -- yours, and the evil one -- and the order in which they execute is unspecified. In theory you could call removeEventListener() instead, but that requires that the event listener to be removed has a name, but in this case it's an anonymous function.
There is a workaround for the problem of not being able to just assign to .oncopy(), which would let you override the evil one with yours (which would do nothing.) The workaround involves the location hack. So, maybe something like
posted by Rhomboid at 11:07 PM on August 22, 2010
If you want to do it in Greasemonkey though it should still be possible, but events are a little tricky in Greasemonkey scripts. You can't just assign to the .oncopy property of an object, you have to use .addEventListener(). The problem with this is that it means that now you have two functions bound to the oncopy event -- yours, and the evil one -- and the order in which they execute is unspecified. In theory you could call removeEventListener() instead, but that requires that the event listener to be removed has a name, but in this case it's an anonymous function.
There is a workaround for the problem of not being able to just assign to .oncopy(), which would let you override the evil one with yours (which would do nothing.) The workaround involves the location hack. So, maybe something like
location.href = "javascript:(" + function() { document.getElementById('divArticleText').oncopy=(function(){return true;}); } + ")()";(completely untested)
posted by Rhomboid at 11:07 PM on August 22, 2010
Response by poster: Sneaky bastards that they are, they wrote themselves a little server-side script to combine javascript files into one, so I can't just can the whole thing without losing useful functionality. :(
It's actually called from the following URL: http://www.example.com/jsstatic.axd?path=%2fincludes%2fjs%2fjquery-1-3-2-min.js|%2fincludes%2fjs%2farticleText.js|
Prior to their doing that, I just had ABP block articleText.js on that site. If that was the only use of the jsstatic.axd, I'd kill it the same way.
Also, the name of the div isn't actually 'divArticleText', it's 'ctl00_body1_ArticleControl_divArticleText' on the particular article I'm looking at, and the 'ctl00_body1' part is occasionally different, which I suppose is why they use the jQuery selector in the first place.
Perhaps I could hit it from another angle and kill jsstatic.axd in ABP and use GreaseMonkey to load jQuery?
posted by wierdo at 12:15 AM on August 23, 2010
It's actually called from the following URL: http://www.example.com/jsstatic.axd?path=%2fincludes%2fjs%2fjquery-1-3-2-min.js|%2fincludes%2fjs%2farticleText.js|
Prior to their doing that, I just had ABP block articleText.js on that site. If that was the only use of the jsstatic.axd, I'd kill it the same way.
Also, the name of the div isn't actually 'divArticleText', it's 'ctl00_body1_ArticleControl_divArticleText' on the particular article I'm looking at, and the 'ctl00_body1' part is occasionally different, which I suppose is why they use the jQuery selector in the first place.
Perhaps I could hit it from another angle and kill jsstatic.axd in ABP and use GreaseMonkey to load jQuery?
posted by wierdo at 12:15 AM on August 23, 2010
You should be able to use the jQuery selector in conjunction with the location hack.
Loading jQuery from Greasemonkey is not really an option. Greasemonkey scripts don't start to execute until the DOMContentLoaded event fires. Depending on how the page is designed there could be script elements that depend on jQuery being loaded before that. Besides, you can't really 'load' jQuery from a user script. You can use @include at the top of your user script to include jQuery in the user script, but the two scripts (the page and your user script) are in two separate environments/sandboxes so having jQuery loaded in the user script doesn't mean that it's accessible from the page's script elements. In fact this was a major security vulnerability in early versions of Greasemonkey -- a page could call functions in the user script, which resulted in potentially disastrous consequences because the security context of the user script is significantly relaxed (e.g. no cross-site XmlHttpRequest restrictions, access to internal Firefox functions, etc.) They fixed this by strictly separating the two and having the user script only access the page's object-space through wrappers. This is why you have to use the location hack in the first place, because the result of .getElementById() in the user script is not the real element but a wrapped copy of it and you can't set the event properties through the wrapper.
Anyway, if you wanted to inject jQuery into the page's object space by using the location hack to create a <script> tag in the page, then that would probably work. But it wouldn't get around the timing issue. It would be much easier to just modify the snippet I posted to use the jQuery selector instead of getElementById().
posted by Rhomboid at 12:42 AM on August 23, 2010
Loading jQuery from Greasemonkey is not really an option. Greasemonkey scripts don't start to execute until the DOMContentLoaded event fires. Depending on how the page is designed there could be script elements that depend on jQuery being loaded before that. Besides, you can't really 'load' jQuery from a user script. You can use @include at the top of your user script to include jQuery in the user script, but the two scripts (the page and your user script) are in two separate environments/sandboxes so having jQuery loaded in the user script doesn't mean that it's accessible from the page's script elements. In fact this was a major security vulnerability in early versions of Greasemonkey -- a page could call functions in the user script, which resulted in potentially disastrous consequences because the security context of the user script is significantly relaxed (e.g. no cross-site XmlHttpRequest restrictions, access to internal Firefox functions, etc.) They fixed this by strictly separating the two and having the user script only access the page's object-space through wrappers. This is why you have to use the location hack in the first place, because the result of .getElementById() in the user script is not the real element but a wrapped copy of it and you can't set the event properties through the wrapper.
Anyway, if you wanted to inject jQuery into the page's object space by using the location hack to create a <script> tag in the page, then that would probably work. But it wouldn't get around the timing issue. It would be much easier to just modify the snippet I posted to use the jQuery selector instead of getElementById().
posted by Rhomboid at 12:42 AM on August 23, 2010
(And btw, if you still can't get it to work then please post the real actual URL of the site in question so that we can test potential user scripts instead of having to guess if they would work or not.)
posted by Rhomboid at 12:46 AM on August 23, 2010
posted by Rhomboid at 12:46 AM on August 23, 2010
To get around the fact that you can't remove the event handler from #divArticleText, I suppose the workaround could be to create a new div, insert it right before or after #divArticleText, copy the innerHTML of #divArticleText into this new div (which will not have any event handlers on it, since you just created it), and then delete the original #divArticleText from the DOM (and maybe assign the ID divArticleText to your new div in case there is CSS styling attached to that ID).
posted by letourneau at 5:23 AM on August 23, 2010
posted by letourneau at 5:23 AM on August 23, 2010
Here's how to do what I just described with jQuery...
posted by letourneau at 5:42 AM on August 23, 2010$(document).ready(function () { $("#divArticleText").clone().attr("id", "TEMP").insertAfter($("#divArticleText")); $("#divArticleText").detach(); $("#TEMP").attr("id", "divArticleText"); });
The selector "
Save the following as
posted by artlung at 6:42 AM on August 23, 2010
div[id$='divArticleText']
" indicates that the selector is intended to apply to any div whose id
attribute ends with the text divArticleText
. So let's change the divs on the page named like that.Save the following as
copybuster.user.js
, have Greasemonkey installed, and load the file, you should be prompted to install it.
// ==UserScript== // @include http://example.com/* // var divs = document.getElementsByTagName('div'); // for each div for (var i=0;i<divs.length;i++) { var div = divs[i]; // that has the text "divArticleText" in the id if (div.getAttribute('id').indexOf('divArticleText') !== -1) { // give it a whole new unique name div.setAttribute('id', 'div-article-text-DISABLED-' + i); } }
posted by artlung at 6:42 AM on August 23, 2010
Both jQuery's .ready() and user scripts fire at the same event (DOMContentLoaded) so there is no guarantee that the user script executes before the oncopy event handlers have already been bound to the div. If that works it's only by sheer luck and it's very fragile.
posted by Rhomboid at 10:57 AM on August 23, 2010
posted by Rhomboid at 10:57 AM on August 23, 2010
You mentioned that you've tried the default unbind, but have you checked out some of the parameters?
posted by blue_beetle at 11:10 AM on August 23, 2010
posted by blue_beetle at 11:10 AM on August 23, 2010
Response by poster: Rhomboid: I tried the following, which doesn't kill the spam. Perhaps I'm not getting the syntax correct:
The website is www.tulsaworld.com.
Thanks for all the help thus far.
OK, that's disturbing. Since I posted that question, they've gone back to including articleText.js directly, so I can once again use ABP to kill the spam. If I hadn't actually posted about it last night, I would now be thinking I had hallucinated the whole thing.
posted by wierdo at 2:01 PM on August 23, 2010
location.href = "javascript:(" + function() { document.$("div[id$='divArticleText']").oncopy=(function(){return true;}); } + ")()";I also tried letourneau and artlung's snippets, but they didn't work either. I have a feeling I may just be doing something stupid here.
The website is www.tulsaworld.com.
Thanks for all the help thus far.
OK, that's disturbing. Since I posted that question, they've gone back to including articleText.js directly, so I can once again use ABP to kill the spam. If I hadn't actually posted about it last night, I would now be thinking I had hallucinated the whole thing.
posted by wierdo at 2:01 PM on August 23, 2010
I don't have GreaseMonkey, but running this from the Firebug console removes the copy handler (and all the rest) quite happily:
posted by robertc at 2:35 PM on August 23, 2010
$("*").each(function(){$(this).unbind()});
If they merge all the scripts again then maybe someone can make it work in GreaseMonkey (and perhaps target it a little more efficiently).posted by robertc at 2:35 PM on August 23, 2010
Response by poster: Ah, I had actually tried using the $("*") selector (like so: $("*").unbind("copy");
, but I guess that's not the right way to do it. :p
posted by wierdo at 2:53 PM on August 23, 2010
, but I guess that's not the right way to do it. :p
posted by wierdo at 2:53 PM on August 23, 2010
Aha, okay, I see the problem with my script, i wasn't checking for the existence of an
posted by artlung at 7:04 PM on August 23, 2010
id
so it fell over. Thanks for posting the site, that lett's me test it.
// ==UserScript== // @include http://www.tulsaworld.com/* // var divs = document.getElementsByTagName('div'); // for each div for (var i=0;i<divs.length;i++) { var div = divs[i]; // that has the text "divArticleText" in the id if (div.getAttribute('id') && div.getAttribute('id').indexOf('divArticleText') !== -1) { // give it a whole new unique name div.setAttribute('id', 'div-article-text-DISABLED-' + i); } }Also posted here: http://gist.github.com/546759
posted by artlung at 7:04 PM on August 23, 2010
This thread is closed to new comments.
posted by muta at 10:42 PM on August 22, 2010