Javascript: Hijack Copy? - javascript

I was just reading the Times online and I wanted to copy a bit of text from the article and IM it to a friend, but I noticed when I did so, it automatically appended the link back to the article in what I had copied.
This is not a feature of my IM client, so I assume this happened because of some javascript on Times website.
How would I accomplish this if I wanted to implement it on my site? Basically, I would have to hijack the copy operation and append the URL of the article to the end of the copied content, right? Thoughts?
Here's the article I was reading, for reference: http://www.time.com/time/health/article/0,8599,1914857,00.html

It's a breeze with jQuery (which your referenced site is using):
$("body").bind('copy', function(e) {
// The user is copying something
});
You can use the jQuery Search & Share Plugin which does this exact thing whenever somebody copies more than 40 chars from your site: http://www.latentmotion.com/search-and-share/
The site that you referenced is apparently using a service called Tynt Insight to accomplish this though.

They are using the free service Tynt. If you want to accomplish the same thing, just use the same service.

What browser are you using (and what version)?
In some newer browsers, the user is either asked if a website can access the clipboard, or its simply not allowed. In other browsers (IE 6, for example), it is allowed, and websites can easily read from and write to your copy clipboard.
Here is the code (IE only)
clipboardData.setData("Text", "I just put this in the clipboard using JavaScript");

The "Copy & Paste Hijacker" jQuery plugin does exactly what you want and seems better suited for your purposes than Tynt or Search & Share: http://plugins.jquery.com/project/copypaste
You can easily format the copied content, specify max or min characters, and easily include the title of the page or the URL in the copied content exactly where you want.

I recently noticed this on another website and wrote a blog post on how it works. The jQuery example doesn't seem to actually modify what the user copies and pastes, it just adds a new context menu.
In short:
var content = document.getElementById("content");
content.addEventListener("copy", oncopy);
function oncopy() {
var newEl = document.createElement("p");
document.body.appendChild(newEl);
newEl.innerHTML = "In your copy, messing with your text!";
var selection = document.getSelection();
var range = selection.getRangeAt(0);
selection.selectAllChildren(newEl);
setTimeout(function() {
newEl.parentNode.removeChild(newEl);
selection.removeAllRanges();
selection.addRange(range);
}, 0)
}
The setTimeout at the end is important as it doesn't seem to work if the last part is executed immediately.
This example replaces your selected text at the last minute with my chosen string. You can also grab the existing selection and append whatever you like to the end.

Related

How to get copied text from JavaScript

This question is related to another question I asked, but I realized that a side-question in that question deserved its own question.
Using JavaScript, I'd like to see what users are copying from a webpage. Reading clipboard content is fairly easy when the user is pasting:
document.addEventListener("paste", e => {
let text = e.clipboardData.getData("text");
alert("pasting text: " + text);
});
This correctly creates an alert with whatever was just pasted. However, getting clipboard data is more difficult when the user is copying.
Method 1 (doesn't work)
document.addEventListener("copy", e => {
let text = e.clipboardData.getData("text");
alert("copying text: " + text);
});
This alerts "copying data: " but with no text after it. That's because the getData method is returning "" (the empty string). My understanding is that it would be considered too much of a security problem for sites to read your clipboard when you're doing anything other than pasting.
Method 2 (works, but with a popup)
document.addEventListener("copy", () => {
navigator.clipboard.readText().then(text => alert("copied text: " + text));
});
This works, but before alerting, it creates a popup asking for permission for the site to read the clipboard. I would prefer to not have this popup.
Method 3 (seems to work, but doesn't seem right)
document.addEventListener("copy", () => {
let text = window.getSelection().toString();
alert("copying text: " + text);
});
This appears to do what I want. It seems odd that this would be allowed, but Method 1 would not.
I have a couple of questions:
Why is Method 1 not allowed, if Method 3 is? It seems like Method 1 could provide the same information as Method 3 does, and it would be more convenient and just as secure.
Are there any cases where Method 3 would provide different results than Method 2 (in terms of the text variable, not popup behavior)?
Are there any other downsides to using Method 3 that I'm not considering?
At this point, I only care about these answers in the context of Google Chrome or Chromium, not other browsers. Answers to any of these questions would be appreciated.
tl;dr use Method 3 if you really think that not being honest with the user about what you're doing is justified- it's a good workaround, although many might consider it an exploit.
Looking at the W3 specification (https://www.w3.org/TR/clipboard-apis/#Cases) we can see some insight into why these events (and the still-developing API) exist in the first place. Specifically that copy is there for you to change what was copied in the case of your target not being what the user would actually want to end up on their clipboard, while paste exists to let you handle transfering that data into your application.
Knowing this, we can come to some conclusions:
Method 1: The spec does not go into much detail about clipboard security, except for making the intention that implementations should work to protect users. I'm not surprised, therefore, that the copied data is hidden from you; it seems like a sensible decision by the implementers. More than that, looking at the algorithms set-out by the spec, it's quite possible that there is not data in the clipboard yet, as the aim of this event is to allow you to set what should end up their.
Method 2: This seems much more the intention of the authors. If an application is going to access the clipboard, it should really get permission from the user. Especially because the clipboard might contain data from outside of your page.
Method 3: It's an exploit, but it's hard to see cases where it wouldn't work. From an implementer's perspective it's hard to block- as they would have to check event delegate functions for calls; compared to just 'not making the data readily available'. It's also, arguably, secure enough as the only information you can access is information that is already on your own document.
const imageUrl = await navigator.clipboard.readText();
console.log(imageUrl);
You can also use Promise's .then() and .catch() if your code doesn't support async/await.

Print html to a surface to be copied

I stored an table's html as a text, using this code.
var Data = document.getElementsByClassName("result")[0].innerHTML;
I am able to observe the selected part using console.log, however, I wish to extract this data to be copied and used outside.
So I tried alert(Data), but it does not offer a good surface to copy the data (it does work though, however I cannot use right click on the pop-up window)
I also tried to programmatically copy the data to the clipboard, but it seems, it only works on selected text data.
Is there a better way to extract such data to be used outside ?
Note: I am using a firefox bookmark to execute javascript. But I expect the code to work also in the other browsers.
Edit: I tried the method suggested in the comments, however in firefox, I got an error.
document.execCommand(‘cut’/‘copy’) was denied because it was not called from inside a short running user-generated event handler.
So rather than copying with that command, printing to a surface seems a better choice, if possible. The linked question does not solve my issue.
Edit2: window.prompt did a much better job, however it rocked my world by pressing the text to a single line. I still should be able to parse it programmatically, but if there is a better answer, I wish to learn it.
Below is my solution to keep multiple lines.
It creates one temp 'textarea', then remove it after select()->copy.
function triggercopy() {
var target_obj = document.getElementById('test1');
var copy_text = target_obj.innerHTML; //replace with your actual data.
var hidden_obj = document.createElement("textarea");
hidden_obj.value = copy_text;
document.body.insertBefore(hidden_obj,target_obj);
console.log('prepare:' + copy_text);
hidden_obj.select();
document.execCommand("copy");
document.body.removeChild(hidden_obj);
console.log('already copied:' + copy_text);
}
Text3as
dfsadf
<a id="test1" onclick="triggercopy();">Text3as
dfsadf</a>
I found two methods best suit my interests.
First, window.prompt:
var Data = document.getElementsByClassName("result")[0].innerHTML;
function copyToClipboard(text) {
window.prompt("Copy data.", text);
}
copyToClipboard(Data)
This is a good method, taken from a suggested answer. This puts the data into a single-line text field. And in an interesting manner, when written without a function, executes document.write(Data) when clicked OK, this does not happen when written in a function as above.
Second, document.write:
var target = document.getElementsByClassName("resultTable striped")[0].outerHTML;
document.open('text/plain');
document.write(target);
I first tried to open a new tab with the desired content, however encountered with the issue of pop-up blockers and non-plain text html data (formatted html instead of the desired table html data). This solves both issues.

Javascript code to insert text at cursor position in Firefox?

The use case
When I'm typing a query into a search engine, sometimes it's useful to quote
a part of the query, so the engine doesn't bother me with useless suggestions.
The task
This operation is so frequent, that I want to do this with a shortcut.
The shortcut part isn't the issue, there's a way to assign a shortcut to a bookmarklet.
What I don't know how to do is
Get the current text area. The only thing I know about it is that the cursor is there.
I cannot assume any ids etc. Also, I don't want to install any hooks.
Insert "", and go backwards one character.
I'm expecting a one/two liner that I can place in a bookmarklet.
The solution
Since no one wanted to answer, and I'm just a novice in JavaScript,
I decided to see if there's a plugin that does close to what I want.
The choice fell to Firemacs,
since I'm using it anyway.
The code to go backward one char is simplicity itself:
goDoCommand('cmd_charPrevious');
However, the command to insert text didn't work.
But the command to paste is simple again:
goDoCommand('cmd_paste');
Now it only remains to put '""' in the clipboard. This one isn't easy:
var str = Components.classes["#mozilla.org/supports-string;1"]
.createInstance(Components.interfaces.nsISupportsString);
str.data = '""';
var trans = Components.classes["#mozilla.org/widget/transferable;1"]
.createInstance(Components.interfaces.nsITransferable);
trans.addDataFlavor("text/unicode");
trans.setTransferData("text/unicode",str, str.data.length * 2);
var clipid = Components.interfaces.nsIClipboard;
var clip = Components.classes["#mozilla.org/widget/clipboard;1"]
.getService(clipid);
clip.setData(trans,null,clipid.kGlobalClipboard);
Then I just patched this code into the extension instead of the "Ctrl-h" binding,
which I don't use. Problem solved. Now I can insert a pair of quotes very fast in Firefox.

Remove prefix with unknown characters in JavaScript

I have a web page that the title is changed from 'Pagename' to '(1) Pagename' when there is an update on the page. That number increments to 50 each time there is a new update and then is maxed out showing '(50+) Timeline'.
When logging page views, Google Analytics shows the '(n) Pagename', which I don't want. So I found out how to manually change to logged page title, _gaq.push(["_set", "title", 'new title']);.
So my question is, how do I most efficiently remove the (1-50)/(50+) prefix and just get 'Pagename'? Is regex best for this?
This is what I'm using based on the answer from Ross:
var window_title = window.title.replace(/^\(\d+\+?\)\s/, '');
_gaq.push(["_set", "title", window_title]);
Yes, RegEx can do that.
window.title.replace(/^\(\d+\+?\)\s/, '');
Of course it depends on what software your site is using as perhaps it would be possible to just output the page title without that prefix in the relevant part of the template. So echoing that directly into the Google Analytics tag. But I think the above javascript is probably the easier solution to implement.

Dashboard widget: getting version number from Info.plist

I'm writing a Dashboard widget in Dashcode, and on the back side, I've got a string for credits. I want to include the widget's version number in that string, but if possible, I want to programmatically grab it from the CFBundleVersion or CFBundleShortVersionString key in Info.plist to avoid having to change the number in multiple places if and when I update the widget.
Searches on Apple's developer documentation, Google and various forums have proven fruitless so far. What I'd like to know is whether there's a built-in way to do this that Apple included but forgot to mention (like var version = widget.version(); or something), or whether my script will have to pull in and parse the entire plist before plucking out the one value I actually want.
Thanks for any help you can provide!
I seem to have found the answer: use Dashcode's "data source" facility to read in Info.plist as an XML data source. From there, this blog post showed me how to traverse the plist's structure and get the correct string (in this case, the fifth <string> element in the file, corresponding to CFBundleShortVersionString.
The function I ended up with:
function getWidgetVersion() {
var dataSource = dashcode.getDataSource("infoPlist");
var version = dataSource.selection().valueForKey("dict").valueForKey("string")[4]; // This line and the previous could probably be combined for the sake of brevity
if (typeof(version) == 'string') {
document.getElementById("creditsLabel").innerHTML += version; //I'll change this to just pass the number on
}
}
Since the text of the creditsLabel div has already been started off with a localized string, I get a nice little label saying "Version 1.0".

Categories

Resources