From 277ac935fa0388aca627968002a1a241569e6d84 Mon Sep 17 00:00:00 2001 From: Rob Wu Date: Mon, 24 Apr 2017 23:09:19 +0200 Subject: [PATCH] Add example with advanced clipboard manipulation (#207) (tested with Firefox 52 and 54) --- context-menu-copy-link-with-types/README.md | 35 ++++++++++++ .../background.js | 54 +++++++++++++++++++ .../clipboard-helper.js | 18 +++++++ .../manifest.json | 19 +++++++ .../preview.html | 22 ++++++++ 5 files changed, 148 insertions(+) create mode 100644 context-menu-copy-link-with-types/README.md create mode 100644 context-menu-copy-link-with-types/background.js create mode 100644 context-menu-copy-link-with-types/clipboard-helper.js create mode 100644 context-menu-copy-link-with-types/manifest.json create mode 100644 context-menu-copy-link-with-types/preview.html diff --git a/context-menu-copy-link-with-types/README.md b/context-menu-copy-link-with-types/README.md new file mode 100644 index 0000000..0d33607 --- /dev/null +++ b/context-menu-copy-link-with-types/README.md @@ -0,0 +1,35 @@ +# Context menu: Copy link with types + +This example adds a context menu item to every link that copies the URL to the +clipboard, as plain text and as rich HTML. + +## What it does + +This extension includes: + +* a background script that: + - Registers a context menu item for every link. + - Upon click, it invokes the function to copy text and HTML to the clipboard. +* a helper script, "clipboard-helper.js" that provides the copy-to-clipboard functionality. + In the example, this script is run as a content script, but the actual functionality can also + be used in visible extension pages such as extension button popups or extension tabs. +* a page, "preview.html" for testing the effect of copying to the clipboard. + This page does not need to be part of the extension, and can directly be opened in the browser. + +To test the extension, right-click on any link to open a context menu, and choose the +"Copy link to clipboard" option. Then open preview.html and paste the clipboard content +in the two displayed boxes. The first box will display "This is text: ..." and the second +box will display "This is HTML: ...". + +Note: since the add-on relies on a content script for copying the text, the copy operation +will only succeed if the add-on is allowed to run scripts in the current page. +If you wish to successfully copy the text even if the current page cannot be scripted, then +you can open an (extension) page in a new tab as a fallback. + +## What it shows + +* how to put data on the [clipboard](https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Interact_with_the_clipboard) + with custom types ("text/plain" and "text/html" in the example). +* how to safely construct HTML from given text. +* how to safely create JavaScript code to run as a dynamic content script. +* how to dynamically run a static content script only once. diff --git a/context-menu-copy-link-with-types/background.js b/context-menu-copy-link-with-types/background.js new file mode 100644 index 0000000..b8d1505 --- /dev/null +++ b/context-menu-copy-link-with-types/background.js @@ -0,0 +1,54 @@ +browser.contextMenus.create({ + id: "copy-link-to-clipboard", + title: "Copy link to clipboard", + contexts: ["link"], +}); +browser.contextMenus.onClicked.addListener(function(info, tab) { + if (info.menuItemId === "copy-link-to-clipboard") { + // Examples: text and HTML to be copied. + const text = "This is text: " + info.linkUrl; + // Always HTML-escape external input to avoid XSS. + const safeUrl = escapeHTML(info.linkUrl); + const html = `This is HTML: ${safeUrl}`; + + // The example will show how data can be copied, but since background + // pages cannot directly write to the clipboard, we will run a content + // script that copies the actual content. + + // clipboard-helper.js defines function copyToClipboard. + const code = "copyToClipboard(" + + JSON.stringify(text) + "," + + JSON.stringify(html) + ");"; + + browser.tabs.executeScript({ + code: "typeof copyToClipboard === 'function';", + }).then(function(results) { + // The content script's last expression will be true if the function + // has been defined. If this is not the case, then we need to run + // clipboard-helper.js to define function copyToClipboard. + if (!results || results[0] !== true) { + return browser.tabs.executeScript(tab.id, { + file: "clipboard-helper.js", + }); + } + }).then(function() { + return browser.tabs.executeScript(tab.id, { + code, + }); + }).catch(function(error) { + // This could happen if the extension is not allowed to run code in + // the page, for example if the tab is a privileged page. + console.error("Failed to copy text: " + error); + }); + } +}); + +// https://gist.github.com/Rob--W/ec23b9d6db9e56b7e4563f1544e0d546 +function escapeHTML(str) { + // Note: string cast using String; may throw if `str` is non-serializable, e.g. a Symbol. + // Most often this is not the case though. + return String(str) + .replace(/&/g, "&") + .replace(/"/g, """).replace(/'/g, "'") + .replace(//g, ">"); +} diff --git a/context-menu-copy-link-with-types/clipboard-helper.js b/context-menu-copy-link-with-types/clipboard-helper.js new file mode 100644 index 0000000..7e6d426 --- /dev/null +++ b/context-menu-copy-link-with-types/clipboard-helper.js @@ -0,0 +1,18 @@ +// This function must be called in a visible page, such as a browserAction popup +// or a content script. Calling it in a background page has no effect! +function copyToClipboard(text, html) { + function oncopy(event) { + document.removeEventListener("copy", oncopy, true); + // Hide the event from the page to prevent tampering. + event.stopImmediatePropagation(); + + // Overwrite the clipboard content. + event.preventDefault(); + event.clipboardData.setData("text/plain", text); + event.clipboardData.setData("text/html", html); + } + document.addEventListener("copy", oncopy, true); + + // Requires the clipboardWrite permission, or a user gesture: + document.execCommand("copy"); +} diff --git a/context-menu-copy-link-with-types/manifest.json b/context-menu-copy-link-with-types/manifest.json new file mode 100644 index 0000000..b01b5ba --- /dev/null +++ b/context-menu-copy-link-with-types/manifest.json @@ -0,0 +1,19 @@ +{ + "manifest_version": 2, + "name": "Context menu: Copy link with types", + "description": "Add a context menu option to links to copy the link to the clipboard, as plain text and as a link in rich HTML.", + "version": "1.0", + "homepage_url": "https://github.com/mdn/webextensions-examples/tree/master/context-menu-copy-link-with-types", + + "background": { + "scripts": [ + "background.js" + ] + }, + + "permissions": [ + "activeTab", + "contextMenus", + "clipboardWrite" + ] +} diff --git a/context-menu-copy-link-with-types/preview.html b/context-menu-copy-link-with-types/preview.html new file mode 100644 index 0000000..e86dca7 --- /dev/null +++ b/context-menu-copy-link-with-types/preview.html @@ -0,0 +1,22 @@ + + + + + + + +Use Ctrl+V (or Cmd+V) to paste plain text here: + + +Use Ctrl+V (or Cmd+V) to paste rich text (HTML) here: +
+ + +