Skip to content

Commit

Permalink
Fix local iframes (#112)
Browse files Browse the repository at this point in the history
* Fix for local iframes

* fix replace html function for contenteditable

* reconfigure webpack

* Add name to rich text editors list

* Fix wordpress
  • Loading branch information
forgetso authored Jan 30, 2024
1 parent 5f4ed56 commit 12acc5b
Show file tree
Hide file tree
Showing 5 changed files with 142 additions and 73 deletions.
2 changes: 1 addition & 1 deletion manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
"permissions": ["activeTab", "storage", "notifications"],
"host_permissions": ["http://*/*", "https://*/*"],
"update_url": "http://clients2.google.com/service/update2/crx",
"version":"2.0.7",
"version":"2.0.8",
"options_page": "assets/options.html",
"icons": {
"16": "assets/icon-16.png",
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "search_and_replace",
"version": "2.0.7",
"version": "2.0.8",
"resolutions": {
"author": "Chris Taylor <[email protected]>"
},
Expand Down
7 changes: 5 additions & 2 deletions src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { Hint } from './types'

export const RICH_TEXT_EDITORS = {
class: ['mce-edit-area', 'cke_wysiwyg_frame', 'tinymce', 'wysiwyg'],
name: ['editor-canvas'],
}
export const ELEMENT_FILTER = /(HTML|HEAD|SCRIPT|STYLE|IFRAME)/i
export const INPUT_TEXTAREA_FILTER = /(INPUT|TEXTAREA)/
export const INPUT_TEXTAREA_CONTENT_EDITABLE_SELECTOR = 'input,textarea,*[contenteditable="true"]'
export const HINTS: Record<string, Hint> = {
gmail: {
hint: 'Hint: Gmail detected. Check "Input fields only?" when editing draft emails.',
Expand Down
97 changes: 80 additions & 17 deletions src/elements.ts
Original file line number Diff line number Diff line change
@@ -1,50 +1,113 @@
// Utils for dealing with Elements

import { HINTS } from './constants'
import { HINTS, INPUT_TEXTAREA_CONTENT_EDITABLE_SELECTOR, RICH_TEXT_EDITORS } from './constants'
import { SearchReplaceResult } from './types'
import { notEmpty } from './util'

export function getInputElements(
document: Document,
document: HTMLElement | Document,
elementFilter: Map<Element, SearchReplaceResult>,
hiddenContent?: boolean
): (HTMLInputElement | HTMLTextAreaElement)[] {
const inputs = Array.from(
<NodeListOf<HTMLInputElement>>document.querySelectorAll('input,textarea,div[contenteditable="true"]')
<NodeListOf<HTMLInputElement>>document.querySelectorAll(INPUT_TEXTAREA_CONTENT_EDITABLE_SELECTOR)
)
const visibleElements = !hiddenContent ? inputs.filter((input) => elementIsVisible(input, true, false)) : inputs
return visibleElements.filter((input) => !elementFilter.has(input))
}

export function isInputElement(el: Element): el is HTMLInputElement {
return (
el.tagName === 'INPUT' ||
el.tagName === 'TEXTAREA' ||
(el.hasAttribute('contentEditable') && el.getAttribute('contentEditable') === 'true')
)
}

export function isBlobIframe(el: Element) {
return el.tagName === 'IFRAME' && 'src' in el && typeof el.src === 'string' && el.src.startsWith('blob:')
}

export function getIframeElements(document: Document, blob = false): HTMLIFrameElement[] {
export function isWYSIWYGEditorIframe(el: Element) {
return (
RICH_TEXT_EDITORS.name.some((editor) => 'name' in el && el.name === editor) ||
RICH_TEXT_EDITORS.class.some((editor) => containsPartialClass(el, editor))
)
}

export function containsPartialClass(element: Element, partialClass: string) {
return Array.from(element.classList).some((c) => c.includes(partialClass))
}

export function getLocalIframes(window: Window, document: Document): HTMLIFrameElement[] {
return Array.from(<NodeListOf<HTMLIFrameElement>>document.querySelectorAll('iframe')).filter((iframe) => {
return iframe.src === '' || iframe.src === 'about:blank' || iframe.src === window.location.href
})
}

export function getWYSIWYGEditorIframes(window: Window, document: Document): HTMLIFrameElement[] {
return getLocalIframes(window, document).filter((iframe) => {
return isWYSIWYGEditorIframe(iframe)
})
}

export function getBlobIframes(document: Document): HTMLIFrameElement[] {
const blobIframes = Array.from(<NodeListOf<HTMLIFrameElement>>document.querySelectorAll('iframe')).filter(
(iframe) => {
return isBlobIframe(iframe)
}
)
return blobIframes
}

export const waitForIframeLoad = async (iframe: HTMLIFrameElement): Promise<HTMLIFrameElement | null> => {
return new Promise((resolve, reject) => {
if (iframe.contentDocument) {
if (iframe.contentDocument.readyState !== 'complete') {
iframe.contentDocument.onreadystatechange = () => {
if (iframe.contentDocument && iframe.contentDocument.readyState === 'complete') {
resolve(iframe)
}
}
} else {
resolve(iframe)
}
} else {
resolve(null)
}
})
}

export async function getSearchableIframes(window: Window, document: Document): Promise<HTMLIFrameElement[]> {
return (
await Promise.all(
[...getBlobIframes(document), ...getWYSIWYGEditorIframes(window, document)].map((iframe) => {
return waitForIframeLoad(iframe)
})
)
).filter(notEmpty)
}

export function getRespondingIframes(window: Window, document: Document): HTMLIFrameElement[] {
// we don't want to count iframes in gmail
if (document.querySelector(HINTS['gmail'].selector) || window.location.href.indexOf(HINTS['gmail'].domain) > -1) {
return []
}

return Array.from(<NodeListOf<HTMLIFrameElement>>document.querySelectorAll('iframe')).filter((iframe) => {
console.log('iframe.src.length', iframe.src.length)
console.log("iframe.src.startsWith('chrome-extension://')", iframe.src.startsWith('chrome-extension://'))
console.log('window location origin', window.location.origin)
console.log('iframe.src.startsWith(window.location.origin)', iframe.src.startsWith(window.location.origin))
console.log('blob', blob)
console.log('isBlobIframe(iframe)', isBlobIframe(iframe))
console.log(
'(blob ? isBlobIframe(iframe) : !isBlobIframe(iframe))',
blob ? isBlobIframe(iframe) : !isBlobIframe(iframe)
)
// We don't want empty iframes
const want =
// loaded iframes containing a content script need to satisfy the following conditions
iframe.src !== undefined &&
// we don't want WYSIWYG editors that use iframes
!getWYSIWYGEditorIframes(window, document).includes(iframe) &&
// we don't want iframes with no src
iframe.src.length > 0 &&
// We don't want to count iframes injected by other chrome extensions
!iframe.src.startsWith('chrome-extension://') &&
// We only want to wait on iframes from the same origin OR blob iframes
iframe.src.indexOf(window.location.origin) > -1 &&
// We may or may not want blob iframes
(blob ? isBlobIframe(iframe) : !isBlobIframe(iframe))
// We do not want blob iframes
!isBlobIframe(iframe)
return want
})
}
Expand Down
Loading

0 comments on commit 12acc5b

Please sign in to comment.