diff --git a/lwc-shadowpath/README.md b/lwc-shadowpath-builder/README.md similarity index 76% rename from lwc-shadowpath/README.md rename to lwc-shadowpath-builder/README.md index 5b08ca7..244afb2 100644 --- a/lwc-shadowpath/README.md +++ b/lwc-shadowpath-builder/README.md @@ -1,11 +1,13 @@ Chrome Extension to generate CssSelector paths for elements encapsulated by our LWC components' Synthetic Shadow DOM. Attempting to reproduce the functionality of Copy > Copy JS Path that Chrome provides for native Shadow DOM +**Updated to Manifest V3**: This extension has been updated to comply with Chrome Extension Manifest V3 requirements, including removal of unsafe `eval()` usage and proper Content Security Policy. + ### Load as Chrome extension by: 1. Clone or download this repository from github.com 2. Go to chrome://extensions/ and toggle the "Developer mode" button. - 3. Click "Load Unpacked" and navigate to directory /lwc-shadowpath of this repository. - 4. Inspect any element to open Element Inspector, and then open up the LWC ShadowRoot panel by clicking '>>' on the far right. + 3. Click "Load Unpacked" and navigate to directory /lwc-shadowpath-builder of this repository. + 4. Inspect any element to open Element Inspector, and then open up the LWC ShadowPath panel by clicking '>>' on the far right. 5. Select the element that the obsolete Selenium selector used to select. 6. Copy and paste from the panel's shadowpath field into the console to confirm that it is selecting the desired element (remove the extra surrounding double quotes after pasting). 7. Copy and paste from the panel's java field into existing java test code to replace the obsolete Selenium selector. @@ -61,6 +63,18 @@ You do not need to build this repository to use @FindByJS. Instead add this piec Please replace the version with the currently latest one by checking out https://repository.sonatype.org/#nexus-search;quick~test-drop-in +### Changelog + +#### Version 1.3.0 - Manifest V3 Compliance Update +- **Updated to Manifest V3**: Migrated from deprecated Manifest V2 to Manifest V3 +- **Security Improvements**: Removed unsafe `eval()` usage and replaced with safe DOM traversal methods +- **Content Security Policy**: Added proper CSP headers to devtools.html +- **Permissions**: Added required permissions for Manifest V3 compliance (`scripting`, ``) +- **Code Safety**: Replaced dynamic JavaScript evaluation with deterministic DOM selection logic +- **HTML Structure**: Updated devtools.html with proper DOCTYPE and meta tags + +**Breaking Changes**: None for end users - the extension functionality remains the same while being compliant with current Chrome extension policies. + ### Developing extension 1. When you update code, to see changes you will need to: 1. Click reload on the chrome://extensions page diff --git a/lwc-shadowpath-builder/devtools.html b/lwc-shadowpath-builder/devtools.html new file mode 100644 index 0000000..b264f94 --- /dev/null +++ b/lwc-shadowpath-builder/devtools.html @@ -0,0 +1,11 @@ + + + + + + LWC JS Path Generator DevTools + + + + + \ No newline at end of file diff --git a/lwc-shadowpath/devtools.js b/lwc-shadowpath-builder/devtools.js similarity index 71% rename from lwc-shadowpath/devtools.js rename to lwc-shadowpath-builder/devtools.js index 37d4150..1895ace 100644 --- a/lwc-shadowpath/devtools.js +++ b/lwc-shadowpath-builder/devtools.js @@ -69,6 +69,36 @@ let generateShadowJSPath = function() { return "document" + expanded.join(".shadowRoot"); } + function executeJSPath(jsPath) { + let currentElement = document; + + for (let i = 0; i < jsPath.length; i++) { + const [selector, index] = jsPath[i]; + + if (index === 0) { + currentElement = currentElement.querySelector(selector); + } else if (index === '*') { + // Return all elements for this selector - used for finding possibilities + const elements = currentElement.querySelectorAll(selector); + return Array.from(elements); + } else { + const elements = currentElement.querySelectorAll(selector); + currentElement = elements[index]; + } + + if (!currentElement) { + return null; + } + + // Move to shadow root if not the last element + if (i < jsPath.length - 1 && currentElement.shadowRoot) { + currentElement = currentElement.shadowRoot; + } + } + + return currentElement; + } + function jsPathElementsToShadowPath(jsPath) { let selectors = []; jsPath.forEach(function(elem) { @@ -83,32 +113,34 @@ let generateShadowJSPath = function() { /** * Turn potential path into JavaScript selector. - * Use eval to select between multiple possible top-down paths. + * Use safe DOM traversal instead of eval. */ function jsPathFromPath(path) { if (!path || path.length === 0) return; let jsPath = []; - // let jsPath = "document"; + for (let i = path.length - 1; i >= 0; i--) { const node = path[i]; const selector = node.selector; - const query = jsPathElementsToQuerySelector(jsPath.concat([[selector, '*']])); - console.log(query); - // const test = query + ".querySelectorAll('" + selector + "')"; - const possibilities = eval(query); - if (possibilities.length === 0) { - console.log("Error: Lost my way. No valid paths from " + query); - throw "Lost my way. No valid paths from " + query; - } else if (possibilities.length === 1) { // Selector gives an unique element - // jsPath += ".querySelector('" + selector + "')"; - // jspathElements.push(".querySelector('" + selector + "')"); + + // Execute the path built so far to get current context + const testPath = jsPath.concat([[selector, '*']]); + const possibilities = executeJSPath(testPath); + + if (!possibilities || (Array.isArray(possibilities) && possibilities.length === 0)) { + console.log("Error: Lost my way. No valid paths for selector: " + selector); + throw "Lost my way. No valid paths for selector: " + selector; + } else if (!Array.isArray(possibilities)) { + // Single element returned by querySelector + jsPath.push([selector, 0]); + } else if (possibilities.length === 1) { + // Single element in array from querySelectorAll jsPath.push([selector, 0]); } else { + // Multiple elements, find the correct index let found = false; for (let p = 0; p < possibilities.length; p++) { if (possibilities[p] === node.elem) { - // jsPath += ".querySelectorAll('" + selector + "')[" + p + "]"; - //jspathElements.push(".querySelectorAll('" + selector + "')[" + p + "]"); jsPath.push([selector, p]); found = true; break; @@ -119,10 +151,6 @@ let generateShadowJSPath = function() { throw "Could not find way to " + selector; } } - // We have not reached the element yet, so add shadowRoot. - // if (i !== 0) { - // jsPath = jsPath + '.shadowRoot'; - // } } return jsPath; } @@ -144,7 +172,7 @@ let generateShadowJSPath = function() { * Inspired by: https://chromium.googlesource.com/chromium/src/+/master/chrome/common/extensions/docs/examples/api/devtools/panels/chrome-query */ chrome.devtools.panels.elements.createSidebarPane( - "LWC JS Path", + "LWC ShadowPath", function(sidebar) { function updateElementProperties() { sidebar.setExpression("(" + generateShadowJSPath.toString() + ")()"); diff --git a/lwc-shadowpath/images/shadow.png b/lwc-shadowpath-builder/images/shadow.png similarity index 100% rename from lwc-shadowpath/images/shadow.png rename to lwc-shadowpath-builder/images/shadow.png diff --git a/lwc-shadowpath/images/shadow128.png b/lwc-shadowpath-builder/images/shadow128.png similarity index 100% rename from lwc-shadowpath/images/shadow128.png rename to lwc-shadowpath-builder/images/shadow128.png diff --git a/lwc-shadowpath/images/shadow16.png b/lwc-shadowpath-builder/images/shadow16.png similarity index 100% rename from lwc-shadowpath/images/shadow16.png rename to lwc-shadowpath-builder/images/shadow16.png diff --git a/lwc-shadowpath/images/shadow32.png b/lwc-shadowpath-builder/images/shadow32.png similarity index 100% rename from lwc-shadowpath/images/shadow32.png rename to lwc-shadowpath-builder/images/shadow32.png diff --git a/lwc-shadowpath/images/shadow48.png b/lwc-shadowpath-builder/images/shadow48.png similarity index 100% rename from lwc-shadowpath/images/shadow48.png rename to lwc-shadowpath-builder/images/shadow48.png diff --git a/lwc-shadowpath/manifest.json b/lwc-shadowpath-builder/manifest.json similarity index 60% rename from lwc-shadowpath/manifest.json rename to lwc-shadowpath-builder/manifest.json index 58c876d..771d5cd 100644 --- a/lwc-shadowpath/manifest.json +++ b/lwc-shadowpath-builder/manifest.json @@ -1,7 +1,8 @@ { - "name": "LWC JS Path Generator", - "version": "1.2", + "name": "LWC ShadowPath Builder", + "version": "1.3.0", "description": "Generates JS selector path for LWC Synthetic ShadowDOMs.", + "manifest_version": 3, "devtools_page": "devtools.html", "icons": { "16": "images/shadow16.png", @@ -9,5 +10,10 @@ "48": "images/shadow48.png", "128": "images/shadow128.png" }, - "manifest_version": 2 + "permissions": [ + "scripting" + ], + "host_permissions": [ + "" + ] } diff --git a/lwc-shadowpath/devtools.html b/lwc-shadowpath/devtools.html deleted file mode 100644 index a34c06b..0000000 --- a/lwc-shadowpath/devtools.html +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file