From cffd4b35159898a7211003df80b18cacf96fc916 Mon Sep 17 00:00:00 2001 From: Thomas Rittson <31796059+eliykat@users.noreply.github.com> Date: Thu, 7 Oct 2021 09:52:33 +1000 Subject: [PATCH] Fix Copy Custom Field Name on pages with iframes (#2091) * Improve error messages * Send getClickedElement msg to specific frameId * Add support for finding input element from label * Use i18n for error messages * Fix unrelated linting --- src/_locales/en/messages.json | 6 ++++ src/background/contextMenus.background.ts | 6 ++-- src/content/contextMenuHandler.ts | 34 +++++++++++++++++++---- src/popup/services/services.module.ts | 1 + src/popup/settings/options.component.ts | 2 +- 5 files changed, 40 insertions(+), 9 deletions(-) diff --git a/src/_locales/en/messages.json b/src/_locales/en/messages.json index 79ca58563e..af284dde2b 100644 --- a/src/_locales/en/messages.json +++ b/src/_locales/en/messages.json @@ -1807,5 +1807,11 @@ }, "personalVaultExportPolicyInEffect": { "message": "One or more organization policies prevents you from exporting your personal vault." + }, + "copyCustomFieldNameInvalidElement": { + "message": "Unable to identify a valid form element. Try inspecting the HTML instead." + }, + "copyCustomFieldNameNotUnique": { + "message": "No unique identifier found." } } diff --git a/src/background/contextMenus.background.ts b/src/background/contextMenus.background.ts index f690d28320..b0fa7097d2 100644 --- a/src/background/contextMenus.background.ts +++ b/src/background/contextMenus.background.ts @@ -30,7 +30,7 @@ export default class ContextMenusBackground { if (info.menuItemId === 'generate-password') { await this.generatePasswordToClipboard(); } else if (info.menuItemId === 'copy-identifier') { - await this.getClickedElement(); + await this.getClickedElement(info.frameId); } else if (info.parentMenuItemId === 'autofill' || info.parentMenuItemId === 'copy-username' || info.parentMenuItemId === 'copy-password' || @@ -47,13 +47,13 @@ export default class ContextMenusBackground { this.passwordGenerationService.addHistory(password); } - private async getClickedElement() { + private async getClickedElement(frameId: number) { const tab = await BrowserApi.getTabFromCurrentWindow(); if (tab == null) { return; } - BrowserApi.tabSendMessageData(tab, 'getClickedElement'); + BrowserApi.tabSendMessage(tab, { command: 'getClickedElement' }, { frameId: frameId }); } private async cipherAction(info: any) { diff --git a/src/content/contextMenuHandler.ts b/src/content/contextMenuHandler.ts index e4be3aa1a8..6a2c1eca7f 100644 --- a/src/content/contextMenuHandler.ts +++ b/src/content/contextMenuHandler.ts @@ -1,25 +1,49 @@ const inputTags = ['input', 'textarea', 'select']; +const labelTags = ['label', 'span']; const attributes = ['id', 'name', 'label-aria', 'placeholder']; +const invalidElement = chrome.i18n.getMessage('copyCustomFieldNameInvalidElement'); +const noUniqueIdentifier = chrome.i18n.getMessage('copyCustomFieldNameNotUnique'); + let clickedEl: HTMLElement = null; // Find the best attribute to be used as the Name for an element in a custom field. function getClickedElementIdentifier() { if (clickedEl == null) { - return 'Unable to identify clicked element.'; + return invalidElement; } - if (!inputTags.includes(clickedEl.nodeName.toLowerCase())) { - return 'Invalid element type.'; + const tagName = clickedEl.nodeName.toLowerCase(); + let inputEl = null; + + // Try to identify the input element (which may not be the clicked element) + if (inputTags.includes(tagName)) { + inputEl = clickedEl; + } else if (labelTags.includes(tagName)) { + let inputName = null; + if (tagName === 'label') { + inputName = clickedEl.getAttribute('for'); + } else { + inputName = clickedEl.closest('label')?.getAttribute('for'); + } + + if (inputName != null) { + inputEl = document.querySelector('input[name=' + inputName + '], select[name=' + inputName + + '], textarea[name=' + inputName + ']'); + } + } + + if (inputEl == null) { + return invalidElement; } for (const attr of attributes) { - const attributeValue = clickedEl.getAttribute(attr); + const attributeValue = inputEl.getAttribute(attr); const selector = '[' + attr + '="' + attributeValue + '"]'; if (!isNullOrEmpty(attributeValue) && document.querySelectorAll(selector)?.length === 1) { return attributeValue; } } - return 'No unique identifier found.'; + return noUniqueIdentifier; } function isNullOrEmpty(s: string) { diff --git a/src/popup/services/services.module.ts b/src/popup/services/services.module.ts index 9507ea9647..1e6248f61c 100644 --- a/src/popup/services/services.module.ts +++ b/src/popup/services/services.module.ts @@ -62,6 +62,7 @@ import { StateService } from 'jslib-common/services/state.service'; import { PopupSearchService } from './popup-search.service'; import { PopupUtilsService } from './popup-utils.service'; + import { ThemeType } from 'jslib-common/enums/themeType'; function getBgService(service: string) { diff --git a/src/popup/settings/options.component.ts b/src/popup/settings/options.component.ts index ba9def8803..8ff25e0c27 100644 --- a/src/popup/settings/options.component.ts +++ b/src/popup/settings/options.component.ts @@ -3,6 +3,7 @@ import { OnInit, } from '@angular/core'; +import { ThemeType } from 'jslib-common/enums/themeType'; import { UriMatchType } from 'jslib-common/enums/uriMatchType'; import { I18nService } from 'jslib-common/abstractions/i18n.service'; @@ -12,7 +13,6 @@ import { StorageService } from 'jslib-common/abstractions/storage.service'; import { TotpService } from 'jslib-common/abstractions/totp.service'; import { ConstantsService } from 'jslib-common/services/constants.service'; -import { ThemeType } from 'jslib-common/enums/themeType'; @Component({ selector: 'app-options',