From d37ae6aa7740506666bcf7e1f7a0f59ba22d1c85 Mon Sep 17 00:00:00 2001 From: daidr Date: Wed, 15 Mar 2023 18:50:58 +0800 Subject: [PATCH 1/5] Add a sample of clicking an anchor element with the javascript: scheme --- .../README.md | 19 ++++++++++++ .../background.js | 19 ++++++++++++ .../content.js | 31 +++++++++++++++++++ .../inline-script-demo.html | 5 +++ .../manifest.json | 25 +++++++++++++++ 5 files changed, 99 insertions(+) create mode 100644 functional-samples/sample.inline-script-anchor-clicker/README.md create mode 100644 functional-samples/sample.inline-script-anchor-clicker/background.js create mode 100644 functional-samples/sample.inline-script-anchor-clicker/content.js create mode 100644 functional-samples/sample.inline-script-anchor-clicker/inline-script-demo.html create mode 100644 functional-samples/sample.inline-script-anchor-clicker/manifest.json diff --git a/functional-samples/sample.inline-script-anchor-clicker/README.md b/functional-samples/sample.inline-script-anchor-clicker/README.md new file mode 100644 index 0000000000..6dba1a058c --- /dev/null +++ b/functional-samples/sample.inline-script-anchor-clicker/README.md @@ -0,0 +1,19 @@ +# Anchor with inline script clicker sample + +## Context + +Manifest V3's CSP for content scripts prevents extensions from executing inline JavaScript. Typically inline script execution would occur when an element has inline event handlers bound or when an anchor tag's href attribute contains a `javascript:` scheme. As a result, if an extension tries to call `.click()` on such an element inside an isolated world content script, Chrome will throw a CSP error. + +This sample shows how to solve this issue. + +Relevant issues: [#807](https://github.com/GoogleChrome/chrome-extensions-samples/issues/807) [#769](https://github.com/GoogleChrome/chrome-extensions-samples/issues/769) + +## Try this sample + +1. Clone this repository. +2. Load this directory in Chrome as an [unpacked extension][0]. +3. Find the extension named "Anchor Element Clicker" and drag the file `inline-script-demo.html` into your browser. (This extension requires an HTML file opened with the file protocol to inject content script.) + +You will find the link `Link Text` will be clicked automatically and the alert dialog will be shown. + +[0]: https://developer.chrome.com/docs/extensions/mv3/getstarted/development-basics/#load-unpacked diff --git a/functional-samples/sample.inline-script-anchor-clicker/background.js b/functional-samples/sample.inline-script-anchor-clicker/background.js new file mode 100644 index 0000000000..86b841f754 --- /dev/null +++ b/functional-samples/sample.inline-script-anchor-clicker/background.js @@ -0,0 +1,19 @@ +function clickElement(elementSelector) { + // get the element by selector + let el = document.querySelector(elementSelector); + if (el) { + el.click(); + } +} + +chrome.runtime.onMessage.addListener(function (request, sender) { + if (request.type === 'click') { + // execute the clickElement function in the MAIN world + chrome.scripting.executeScript({ + target: { tabId: sender.tab.id }, + function: clickElement, + args: [request.element], + world: 'MAIN' + }); + } +}); diff --git a/functional-samples/sample.inline-script-anchor-clicker/content.js b/functional-samples/sample.inline-script-anchor-clicker/content.js new file mode 100644 index 0000000000..4d3d06820a --- /dev/null +++ b/functional-samples/sample.inline-script-anchor-clicker/content.js @@ -0,0 +1,31 @@ +// get the anchor element with javascript: scheme in demo page +const el = document.getElementById('demo-anchor-with-js-scheme'); + +// the function to get the CSS path of an element +const getCssPath = function (el) { + if (!(el instanceof Element)) return; + const path = []; + while (el.nodeType === Node.ELEMENT_NODE) { + var selector = el.nodeName.toLowerCase(); + if (el.id) { + selector += '#' + el.id; + path.unshift(selector); + break; + } else { + let sib = el, + nth = 1; + while ((sib = sib.previousElementSibling)) { + if (sib.nodeName.toLowerCase() == selector) nth++; + } + if (nth != 1) selector += ':nth-of-type(' + nth + ')'; + } + path.unshift(selector); + el = el.parentNode; + } + return path.join(' > '); +}; +console.log(el); +if (el) { + // send a message to the background script to click the element + chrome.runtime.sendMessage({ type: 'click', element: getCssPath(el) }); +} diff --git a/functional-samples/sample.inline-script-anchor-clicker/inline-script-demo.html b/functional-samples/sample.inline-script-anchor-clicker/inline-script-demo.html new file mode 100644 index 0000000000..7c2523580a --- /dev/null +++ b/functional-samples/sample.inline-script-anchor-clicker/inline-script-demo.html @@ -0,0 +1,5 @@ +
+ Link Text +
diff --git a/functional-samples/sample.inline-script-anchor-clicker/manifest.json b/functional-samples/sample.inline-script-anchor-clicker/manifest.json new file mode 100644 index 0000000000..8c3060e836 --- /dev/null +++ b/functional-samples/sample.inline-script-anchor-clicker/manifest.json @@ -0,0 +1,25 @@ +{ + "name": "Anchor Element Clicker", + "version": "1.0", + "manifest_version": 3, + "description": "A sample of clicking on an anchor element with javascript: scheme", + "background": { + "service_worker": "background.js" + }, + "content_scripts": [ + { + "matches": [ + "file:///*/sample.inline-script-anchor-clicker/inline-script-demo.html" + ], + "js": [ + "content.js" + ] + } + ], + "permissions": [ + "scripting" + ], + "host_permissions": [ + "file:///*/sample.inline-script-anchor-clicker/inline-script-demo.html" + ] +} \ No newline at end of file From 34f075da90635a32e282cafff92128d1da0969e3 Mon Sep 17 00:00:00 2001 From: daidr Date: Thu, 30 Mar 2023 00:37:23 +0800 Subject: [PATCH 2/5] Rewrite sample using mouse event. --- .../background.js | 19 ------------ .../content.js | 30 ++----------------- .../manifest.json | 18 +++++------ .../proxy-click.js | 7 +++++ 4 files changed, 18 insertions(+), 56 deletions(-) delete mode 100644 functional-samples/sample.inline-script-anchor-clicker/background.js create mode 100644 functional-samples/sample.inline-script-anchor-clicker/proxy-click.js diff --git a/functional-samples/sample.inline-script-anchor-clicker/background.js b/functional-samples/sample.inline-script-anchor-clicker/background.js deleted file mode 100644 index 86b841f754..0000000000 --- a/functional-samples/sample.inline-script-anchor-clicker/background.js +++ /dev/null @@ -1,19 +0,0 @@ -function clickElement(elementSelector) { - // get the element by selector - let el = document.querySelector(elementSelector); - if (el) { - el.click(); - } -} - -chrome.runtime.onMessage.addListener(function (request, sender) { - if (request.type === 'click') { - // execute the clickElement function in the MAIN world - chrome.scripting.executeScript({ - target: { tabId: sender.tab.id }, - function: clickElement, - args: [request.element], - world: 'MAIN' - }); - } -}); diff --git a/functional-samples/sample.inline-script-anchor-clicker/content.js b/functional-samples/sample.inline-script-anchor-clicker/content.js index 4d3d06820a..3a96dbe018 100644 --- a/functional-samples/sample.inline-script-anchor-clicker/content.js +++ b/functional-samples/sample.inline-script-anchor-clicker/content.js @@ -1,31 +1,5 @@ // get the anchor element with javascript: scheme in demo page const el = document.getElementById('demo-anchor-with-js-scheme'); -// the function to get the CSS path of an element -const getCssPath = function (el) { - if (!(el instanceof Element)) return; - const path = []; - while (el.nodeType === Node.ELEMENT_NODE) { - var selector = el.nodeName.toLowerCase(); - if (el.id) { - selector += '#' + el.id; - path.unshift(selector); - break; - } else { - let sib = el, - nth = 1; - while ((sib = sib.previousElementSibling)) { - if (sib.nodeName.toLowerCase() == selector) nth++; - } - if (nth != 1) selector += ':nth-of-type(' + nth + ')'; - } - path.unshift(selector); - el = el.parentNode; - } - return path.join(' > '); -}; -console.log(el); -if (el) { - // send a message to the background script to click the element - chrome.runtime.sendMessage({ type: 'click', element: getCssPath(el) }); -} +// dispatch a mouse event to trigger the click event +window.dispatchEvent(new MouseEvent('proxy-click', { relatedTarget: el })); diff --git a/functional-samples/sample.inline-script-anchor-clicker/manifest.json b/functional-samples/sample.inline-script-anchor-clicker/manifest.json index 8c3060e836..a5a4785ab2 100644 --- a/functional-samples/sample.inline-script-anchor-clicker/manifest.json +++ b/functional-samples/sample.inline-script-anchor-clicker/manifest.json @@ -3,10 +3,16 @@ "version": "1.0", "manifest_version": 3, "description": "A sample of clicking on an anchor element with javascript: scheme", - "background": { - "service_worker": "background.js" - }, "content_scripts": [ + { + "matches": [ + "file:///*/sample.inline-script-anchor-clicker/inline-script-demo.html" + ], + "js": [ + "proxy-click.js" + ], + "world": "MAIN" + }, { "matches": [ "file:///*/sample.inline-script-anchor-clicker/inline-script-demo.html" @@ -15,11 +21,5 @@ "content.js" ] } - ], - "permissions": [ - "scripting" - ], - "host_permissions": [ - "file:///*/sample.inline-script-anchor-clicker/inline-script-demo.html" ] } \ No newline at end of file diff --git a/functional-samples/sample.inline-script-anchor-clicker/proxy-click.js b/functional-samples/sample.inline-script-anchor-clicker/proxy-click.js new file mode 100644 index 0000000000..b62d6f0373 --- /dev/null +++ b/functional-samples/sample.inline-script-anchor-clicker/proxy-click.js @@ -0,0 +1,7 @@ +// add a custom event listener handle click event +window.addEventListener('proxy-click', function ({ relatedTarget: element }) { + console.log('proxy-click event received, element: ', element); + if (element) { + element.click(); + } +}); From 44f5750d603e13f629d86d6b28fbd77eb081a822 Mon Sep 17 00:00:00 2001 From: daidr Date: Thu, 30 Mar 2023 00:40:10 +0800 Subject: [PATCH 3/5] Update README.md --- .../sample.inline-script-anchor-clicker/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/functional-samples/sample.inline-script-anchor-clicker/README.md b/functional-samples/sample.inline-script-anchor-clicker/README.md index 6dba1a058c..87e6692cee 100644 --- a/functional-samples/sample.inline-script-anchor-clicker/README.md +++ b/functional-samples/sample.inline-script-anchor-clicker/README.md @@ -14,6 +14,6 @@ Relevant issues: [#807](https://github.com/GoogleChrome/chrome-extensions-sample 2. Load this directory in Chrome as an [unpacked extension][0]. 3. Find the extension named "Anchor Element Clicker" and drag the file `inline-script-demo.html` into your browser. (This extension requires an HTML file opened with the file protocol to inject content script.) -You will find the link `Link Text` will be clicked automatically and the alert dialog will be shown. +You will find the link `Link Text` will be clicked automatically, and a alert dialog with the text `Clicked` will be shown. [0]: https://developer.chrome.com/docs/extensions/mv3/getstarted/development-basics/#load-unpacked From 58eccf464030b0f9f835022d92b8ca5c7201654a Mon Sep 17 00:00:00 2001 From: daidr Date: Thu, 30 Mar 2023 23:08:51 +0800 Subject: [PATCH 4/5] fix: typo --- .../sample.inline-script-anchor-clicker/proxy-click.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/functional-samples/sample.inline-script-anchor-clicker/proxy-click.js b/functional-samples/sample.inline-script-anchor-clicker/proxy-click.js index b62d6f0373..71c6686a4f 100644 --- a/functional-samples/sample.inline-script-anchor-clicker/proxy-click.js +++ b/functional-samples/sample.inline-script-anchor-clicker/proxy-click.js @@ -1,4 +1,4 @@ -// add a custom event listener handle click event +// add a custom event listener to handle click event window.addEventListener('proxy-click', function ({ relatedTarget: element }) { console.log('proxy-click event received, element: ', element); if (element) { From c9cc7a21e8cc732ace0d3e6ecdc502558a747df2 Mon Sep 17 00:00:00 2001 From: daidr Date: Thu, 30 Mar 2023 23:18:09 +0800 Subject: [PATCH 5/5] Remove unpacked extension loading doc link --- .../README.md | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/functional-samples/sample.inline-script-anchor-clicker/README.md b/functional-samples/sample.inline-script-anchor-clicker/README.md index 87e6692cee..bafa2087ff 100644 --- a/functional-samples/sample.inline-script-anchor-clicker/README.md +++ b/functional-samples/sample.inline-script-anchor-clicker/README.md @@ -1,8 +1,8 @@ -# Anchor with inline script clicker sample +# Clicker Example for Element with Inline Script -## Context +## Overview -Manifest V3's CSP for content scripts prevents extensions from executing inline JavaScript. Typically inline script execution would occur when an element has inline event handlers bound or when an anchor tag's href attribute contains a `javascript:` scheme. As a result, if an extension tries to call `.click()` on such an element inside an isolated world content script, Chrome will throw a CSP error. +Manifest V3's CSP for content scripts prevents extensions from executing inline JavaScript. Typically inline script execution would occur when an element has inline event handlers bound or when an anchor's href attribute contains a `javascript:` scheme. As a result, if an extension tries to call `.click()` on such an element inside an isolated world content script, Chrome will throw a CSP error. This sample shows how to solve this issue. @@ -11,9 +11,17 @@ Relevant issues: [#807](https://github.com/GoogleChrome/chrome-extensions-sample ## Try this sample 1. Clone this repository. -2. Load this directory in Chrome as an [unpacked extension][0]. +2. Load this directory in Chrome as an unpacked extension. 3. Find the extension named "Anchor Element Clicker" and drag the file `inline-script-demo.html` into your browser. (This extension requires an HTML file opened with the file protocol to inject content script.) You will find the link `Link Text` will be clicked automatically, and a alert dialog with the text `Clicked` will be shown. -[0]: https://developer.chrome.com/docs/extensions/mv3/getstarted/development-basics/#load-unpacked +## Implementation Notes + +To achieve clicking on a element with an inline event handler or a link with a javascript: scheme in an isolated script and bypassing CSP, the following steps should be taken: + +1. Inject a main world script where scripts are not subject to extension CSP restrictions. This script needs to listen for an event named `proxy-click`. + +2. In the isolated script, dispatch a `proxy-click` event and pass the element to be clicked to the main world script through the `relatedTarget` field of the `MouseEvent`. + +3. Once the main world script receives the event, it clicks on the corresponding element.