ContextSearch-web-ext icon indicating copy to clipboard operation
ContextSearch-web-ext copied to clipboard

Issue on Zimbra Webmail

Open Parvares opened this issue 1 year ago • 7 comments

Hi Mike, I'm trying to use your extension with Zimbra webmail in Chrome, but when I click on the target URL, nothing happens.

In the Chrome extension settings, the "Site access" permissions are set to include all sites, and the extension is enabled in "Incognito" mode. Interestingly, the extension works perfectly when I'm in Incognito Mode on the same webmail page. For reference, Chrome’s embedded context search works as expected.

I receive the following error from the Chrome extension page:

Uncaught (in promise) Error: Cannot access "about:blank" at origin "https://mail.bibliotechediroma.it". Extension must have permission to access the frame's origin, and matchAboutBlank must be true.

errore

Could you help me troubleshoot this? Thanks very much!

Parvares avatar Dec 08 '24 15:12 Parvares

Can you elaborate on the issue a bit, since i can't log into that website? Are links broken? What do you mean by "when I click on the target URL, nothing happens"?

Interestingly, the extension works perfectly when I'm in Incognito Mode on the same webmail page

That's interesting. Definitely a place to start.

ssborbis avatar Dec 19 '24 06:12 ssborbis

Hi, I noticed two issues, both in normal mode and incognito mode in Zimbra. When accessing the extension from the extensions bar, a new page opens, but the search function fails to work as expected. The extension almost never works properly from the context menu within Zimbra. Specifically, links almost never open when selected. Please refer to the attached video for comparison.

This is the code of the webmail page (I have removed some sensitive information for privacy reasons), link. This is what ChatGPT suggests: link.

Parvares avatar Dec 20 '24 14:12 Parvares

Hi Mike, I noticed that the extension with Zimbra webmail works perfectly from Firefox, but it still doesn't work from Chrome.

Parvares avatar Jan 18 '25 11:01 Parvares

Did this get resolved in any of the recent releases?

ssborbis avatar Jan 31 '25 04:01 ssborbis

Hi Mike, I've updated both the browser version and the extension, and I've tested it on different PCs. It works fine in Firefox, but it still doesn't work in Chrome.

Here are the new errors I see on the Chrome extension page, let me know if you need any additional details.

Uncaught (in promise) Error: Cannot find menu item with id add_engine Contesto _generated_background_page.html Analisi dello stack lib/browser-polyfill.min.js:1 (funzione anonima)

// line 1 (function(a,b){if("function"==typeof define&&define.amd)define("webextension-polyfill",["module"],b);else if("undefined"!=typeof exports)b(module);else{var c={exports:{}};b(c),a.browser=c.exports}})("undefined"==typeof globalThis?"undefined"==typeof self?this:self:globalThis,function(a){"use strict";if(!(globalThis.chrome&&globalThis.chrome.runtime&&globalThis.chrome.runtime.id))throw new Error("This script should only be loaded in a browser extension.");if(!(globalThis.browser&&globalThis.browser.runtime&&globalThis.browser.runtime.id)){a.exports=(a=>{const b={alarms:{clear:{minArgs:0,maxArgs:1},clearAll:{minArgs:0,maxArgs:0},get:{minArgs:0,maxArgs:1},getAll:{minArgs:0,maxArgs:0}},bookmarks:{create:{minArgs:1,maxArgs:1},get:{minArgs:1,maxArgs:1},getChildren:{minArgs:1,maxArgs:1},getRecent:{minArgs:1,maxArgs:1},getSubTree:{minArgs:1,maxArgs:1},getTree:{minArgs:0,maxArgs:0},move:{minArgs:2,maxArgs:2},remove:{minArgs:1,maxArgs:1},removeTree:{minArgs:1,maxArgs:1},search:{minArgs:1,maxArgs:1},update:{minArgs:2,maxArgs:2}},browserAction:{disable:{minArgs:0,maxArgs:1,fallbackToNoCallback:!0},enable:{minArgs:0,maxArgs:1,fallbackToNoCallback:!0},getBadgeBackgroundColor:{minArgs:1,maxArgs:1},getBadgeText:{minArgs:1,maxArgs:1},getPopup:{minArgs:1,maxArgs:1},getTitle:{minArgs:1,maxArgs:1},openPopup:{minArgs:0,maxArgs:0},setBadgeBackgroundColor:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0},setBadgeText:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0},setIcon:{minArgs:1,maxArgs:1},setPopup:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0},setTitle:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0}},browsingData:{remove:{minArgs:2,maxArgs:2},removeCache:{minArgs:1,maxArgs:1},removeCookies:{minArgs:1,maxArgs:1},removeDownloads:{minArgs:1,maxArgs:1},removeFormData:{minArgs:1,maxArgs:1},removeHistory:{minArgs:1,maxArgs:1},removeLocalStorage:{minArgs:1,maxArgs:1},removePasswords:{minArgs:1,maxArgs:1},removePluginData:{minArgs:1,maxArgs:1},settings:{minArgs:0,maxArgs:0}},commands:{getAll:{minArgs:0,maxArgs:0}},contextMenus:{remove:{minArgs:1,maxArgs:1},removeAll:{minArgs:0,maxArgs:0},update:{minArgs:2,maxArgs:2}},cookies:{get:{minArgs:1,maxArgs:1},getAll:{minArgs:1,maxArgs:1},getAllCookieStores:{minArgs:0,maxArgs:0},remove:{minArgs:1,maxArgs:1},set:{minArgs:1,maxArgs:1}},devtools:{inspectedWindow:{eval:{minArgs:1,maxArgs:2,singleCallbackArg:!1}},panels:{create:{minArgs:3,maxArgs:3,singleCallbackArg:!0},elements:{createSidebarPane:{minArgs:1,maxArgs:1}}}},downloads:{cancel:{minArgs:1,maxArgs:1},download:{minArgs:1,maxArgs:1},erase:{minArgs:1,maxArgs:1},getFileIcon:{minArgs:1,maxArgs:2},open:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0},pause:{minArgs:1,maxArgs:1},removeFile:{minArgs:1,maxArgs:1},resume:{minArgs:1,maxArgs:1},search:{minArgs:1,maxArgs:1},show:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0}},extension:{isAllowedFileSchemeAccess:{minArgs:0,maxArgs:0},isAllowedIncognitoAccess:{minArgs:0,maxArgs:0}},history:{addUrl:{minArgs:1,maxArgs:1},deleteAll:{minArgs:0,maxArgs:0},deleteRange:{minArgs:1,maxArgs:1},deleteUrl:{minArgs:1,maxArgs:1},getVisits:{minArgs:1,maxArgs:1},search:{minArgs:1,maxArgs:1}},i18n:{detectLanguage:{minArgs:1,maxArgs:1},getAcceptLanguages:{minArgs:0,maxArgs:0}},identity:{launchWebAuthFlow:{minArgs:1,maxArgs:1}},idle:{queryState:{minArgs:1,maxArgs:1}},management:{get:{minArgs:1,maxArgs:1},getAll:{minArgs:0,maxArgs:0},getSelf:{minArgs:0,maxArgs:0},setEnabled:{minArgs:2,maxArgs:2},uninstallSelf:{minArgs:0,maxArgs:1}},notifications:{clear:{minArgs:1,maxArgs:1},create:{minArgs:1,maxArgs:2},getAll:{minArgs:0,maxArgs:0},getPermissionLevel:{minArgs:0,maxArgs:0},update:{minArgs:2,maxArgs:2}},pageAction:{getPopup:{minArgs:1,maxArgs:1},getTitle:{minArgs:1,maxArgs:1},hide:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0},setIcon:{minArgs:1,maxArgs:1},setPopup:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0},setTitle:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0},show:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0}},permissions:{contains:{minArgs:1,maxArgs:1},getAll:{minArgs:0,maxArgs:0},remove:{minArgs:1,maxArgs:1},request:{minArgs:1,maxArgs:1}},runtime:{getBackgroundPage:{minArgs:0,maxArgs:0},getPlatformInfo:{minArgs:0,maxArgs:0},openOptionsPage:{minArgs:0,maxArgs:0},requestUpdateCheck:{minArgs:0,maxArgs:0},sendMessage:{minArgs:1,maxArgs:3},sendNativeMessage:{minArgs:2,maxArgs:2},setUninstallURL:{minArgs:1,maxArgs:1}},sessions:{getDevices:{minArgs:0,maxArgs:1},getRecentlyClosed:{minArgs:0,maxArgs:1},restore:{minArgs:0,maxArgs:1}},storage:{local:{clear:{minArgs:0,maxArgs:0},get:{minArgs:0,maxArgs:1},getBytesInUse:{minArgs:0,maxArgs:1},remove:{minArgs:1,maxArgs:1},set:{minArgs:1,maxArgs:1}},managed:{get:{minArgs:0,maxArgs:1},getBytesInUse:{minArgs:0,maxArgs:1}},sync:{clear:{minArgs:0,maxArgs:0},get:{minArgs:0,maxArgs:1},getBytesInUse:{minArgs:0,maxArgs:1},remove:{minArgs:1,maxArgs:1},set:{minArgs:1,maxArgs:1}}},tabs:{captureVisibleTab:{minArgs:0,maxArgs:2},create:{minArgs:1,maxArgs:1},detectLanguage:{minArgs:0,maxArgs:1},discard:{minArgs:0,maxArgs:1},duplicate:{minArgs:1,maxArgs:1},executeScript:{minArgs:1,maxArgs:2},get:{minArgs:1,maxArgs:1},getCurrent:{minArgs:0,maxArgs:0},getZoom:{minArgs:0,maxArgs:1},getZoomSettings:{minArgs:0,maxArgs:1},goBack:{minArgs:0,maxArgs:1},goForward:{minArgs:0,maxArgs:1},highlight:{minArgs:1,maxArgs:1},insertCSS:{minArgs:1,maxArgs:2},move:{minArgs:2,maxArgs:2},query:{minArgs:1,maxArgs:1},reload:{minArgs:0,maxArgs:2},remove:{minArgs:1,maxArgs:1},removeCSS:{minArgs:1,maxArgs:2},sendMessage:{minArgs:2,maxArgs:3},setZoom:{minArgs:1,maxArgs:2},setZoomSettings:{minArgs:1,maxArgs:2},update:{minArgs:1,maxArgs:2}},topSites:{get:{minArgs:0,maxArgs:0}},webNavigation:{getAllFrames:{minArgs:1,maxArgs:1},getFrame:{minArgs:1,maxArgs:1}},webRequest:{handlerBehaviorChanged:{minArgs:0,maxArgs:0}},windows:{create:{minArgs:0,maxArgs:1},get:{minArgs:1,maxArgs:2},getAll:{minArgs:0,maxArgs:1},getCurrent:{minArgs:0,maxArgs:1},getLastFocused:{minArgs:0,maxArgs:1},remove:{minArgs:1,maxArgs:1},update:{minArgs:2,maxArgs:2}}};if(0===Object.keys(b).length)throw new Error("api-metadata.json has not been included in browser-polyfill");class c extends WeakMap{constructor(a,b=void 0){super(b),this.createItem=a}get(a){return this.has(a)||this.set(a,this.createItem(a)),super.get(a)}}const d=a=>a&&"object"==typeof a&&"function"==typeof a.then,e=(b,c)=>(...d)=>{a.runtime.lastError?b.reject(new Error(a.runtime.lastError.message)):c.singleCallbackArg||1>=d.length&&!1!==c.singleCallbackArg?b.resolve(d[0]):b.resolve(d)},f=a=>1==a?"argument":"arguments",g=(a,b)=>function(c,...d){if(d.length<b.minArgs)throw new Error(Expected at least ${b.minArgs} ${f(b.minArgs)} for ${a}(), got ${d.length});if(d.length>b.maxArgs)throw new Error(Expected at most ${b.maxArgs} ${f(b.maxArgs)} for ${a}(), got ${d.length});return new Promise((f,g)=>{if(b.fallbackToNoCallback)try{c[a](...d,e({resolve:f,reject:g},b))}catch(e){console.warn(${a} API method doesn't seem to support the callback parameter, +"falling back to call it without a callback: ",e),c[a](...d),b.fallbackToNoCallback=!1,b.noCallback=!0,f()}else b.noCallback?(c[a](...d),f()):c[a](...d,e({resolve:f,reject:g},b))})},h=(a,b,c)=>new Proxy(b,{apply(b,d,e){return c.call(d,a,...e)}});let i=Function.call.bind(Object.prototype.hasOwnProperty);const j=(a,b={},c={})=>{let d=Object.create(null),e=Object.create(a);return new Proxy(e,{has(b,c){return c in a||c in d},get(e,f){if(f in d)return d[f];if(!(f in a))return;let k=a[f];if("function"==typeof k){if("function"==typeof b[f])k=h(a,a[f],b[f]);else if(i(c,f)){let b=g(f,c[f]);k=h(a,a[f],b)}else k=k.bind(a);}else if("object"==typeof k&&null!==k&&(i(b,f)||i(c,f)))k=j(k,b[f],c[f]);else if(i(c,"*"))k=j(k,b[f],c["*"]);else return Object.defineProperty(d,f,{configurable:!0,enumerable:!0,get(){return a[f]},set(b){a[f]=b}}),k;return d[f]=k,k},set(b,c,e){return c in d?d[c]=e:a[c]=e,!0},defineProperty(a,b,c){return Reflect.defineProperty(d,b,c)},deleteProperty(a,b){return Reflect.deleteProperty(d,b)}})},k=a=>({addListener(b,c,...d){b.addListener(a.get(c),...d)},hasListener(b,c){return b.hasListener(a.get(c))},removeListener(b,c){b.removeListener(a.get(c))}}),l=new c(a=>"function"==typeof a?function(b){const c=j(b,{},{getContent:{minArgs:0,maxArgs:0}});a(c)}:a),m=new c(a=>"function"==typeof a?function(b,c,e){let f,g,h=!1,i=new Promise(a=>{f=function(b){h=!0,a(b)}});try{g=a(b,c,f)}catch(a){g=Promise.reject(a)}const j=!0!==g&&d(g);if(!0!==g&&!j&&!h)return!1;const k=a=>{a.then(a=>{e(a)},a=>{let b;b=a&&(a instanceof Error||"string"==typeof a.message)?a.message:"An unexpected error occurred",e({__mozWebExtensionPolyfillReject__:!0,message:b})}).catch(a=>{console.error("Failed to send onMessage rejected reply",a)})};return j?k(g):k(i),!0}:a),n=({reject:b,resolve:c},d)=>{a.runtime.lastError?a.runtime.lastError.message==="The message port closed before a response was received."?c():b(new Error(a.runtime.lastError.message)):d&&d.__mozWebExtensionPolyfillReject__?b(new Error(d.message)):c(d)},o=(a,b,c,...d)=>{if(d.length<b.minArgs)throw new Error(Expected at least ${b.minArgs} ${f(b.minArgs)} for ${a}(), got ${d.length});if(d.length>b.maxArgs)throw new Error(Expected at most ${b.maxArgs} ${f(b.maxArgs)} for ${a}(), got ${d.length});return new Promise((a,b)=>{const e=n.bind(null,{resolve:a,reject:b});d.push(e),c.sendMessage(...d)})},p={devtools:{network:{onRequestFinished:k(l)}},runtime:{onMessage:k(m),onMessageExternal:k(m),sendMessage:o.bind(null,"sendMessage",{minArgs:1,maxArgs:3})},tabs:{sendMessage:o.bind(null,"sendMessage",{minArgs:2,maxArgs:3})}},q={clear:{minArgs:1,maxArgs:1},get:{minArgs:1,maxArgs:1},set:{minArgs:1,maxArgs:1}};return b.privacy={network:{"*":q},services:{"*":q},websites:{"*":q}},j(a,p,b)})(chrome)}else a.exports=globalThis.browser}); //# sourceMappingURL=browser-polyfill.min.js.map

// webextension-polyfill v.0.12.0 (https://github.com/mozilla/webextension-polyfill)

/* This Source Code Form is subject to the terms of the Mozilla Public

  • License, v. 2.0. If a copy of the MPL was not distributed with this
  • file, You can obtain one at http://mozilla.org/MPL/2.0/. */

................................

waitOnInjection failed 594644218 Contesto _generated_background_page.html Analisi dello stack background.js:2521 (funzione anonima)

} catch ( error ) {
cleanup();

// line 2521
console.error('waitOnInjection failed', tabId);
r(false);
}

................................

Uncaught (in promise) TypeError: frames is not iterable Contesto _generated_background_page.html Analisi dello stack background.js:2380 (funzione anonima)

delete options.allFrames;

return new Promise(async r => {
let frames = await browser.webNavigation.getAllFrames({tabId: tabId});

// line 2380
for ( let frame of frames ) {
await executeScripts(tabId, Object.assign({}, options, {frameId: frame.frameId}), checkHasRun);
}

Image Image Image

Parvares avatar Jan 31 '25 19:01 Parvares

Is Zimbra webmail the only site you're seeing this behavior?

ssborbis avatar Jan 31 '25 20:01 ssborbis

Yes, only in Zimbra webmail, excluding Firefox browser, where Zimbra Webmail. works fine.

Parvares avatar Feb 01 '25 00:02 Parvares

Hi Mike, I I had Claude examine these two files of your extension: background.js and inject.js. I hope thi bug report can help you:

Description Context Search Web Ext fails to capture selected text and open searches when used inside iframes, particularly in webmail clients like Zimbra. The context menu appears, but clicking on search engines does nothing.

Environment Browser: Firefox / Chrome Extension Version: 1.48.4.0 Affected pages: Any page with iframes, specifically tested on Zimbra webmail (https://mail.bibliotechediroma.it)

Root Cause The injectContentScripts() function in background.js only injects scripts into the main frame (frameId = 0). When users interact with content inside iframes: The content scripts (inject.js, utils.js, etc.) are not injected into the iframe Selection events and context menu handlers are not registered in the iframe Context Search cannot read the selected text from the iframe Background script receives empty/null selection text

Steps to Reproduce Open a page with iframes (e.g., Zimbra webmail) Select text inside an email body (which is rendered in an iframe) Right-click to open context menu Click on any Context Search engine Expected: New tab opens with search results Actual: Nothing happens

Console Output [Background] No selection text received from frame

The iframe structure in Zimbra:

<iframe id="zv__TV-main__MSG__body__iframe" src="about:blank">
  <!-- Email content here -->
</iframe>

Proposed Fix Modify the injectContentScripts() function in background.js to automatically inject scripts into all frames, not just the main frame.

Location File: background.js Function: async function injectContentScripts(tab, frameId = 0) Approximate line: 1850-1900 Current Code

Current Code

async function injectContentScripts(tab, frameId = 0) {

	// skip frames without host permissions
	// checked again in executeScripts() but also skips CSS injection
	if ( !await isTabScriptable(tab.id, frameId || 0) ) return false;

	// inject into any frame
	// used with init_content.js to only inject when window receives focus
	await executeScripts(tab.id, {
		files: [
			"/utils.js", // for isTextBox
			"/nodes.js", // for shortcuts
			"/Shortcuts.js",
			"/inject.js",
			"/contexts.js",
			"/tools.js" // for shortcuts
		], frameId: frameId, runAt: "document_end"
	}, true);

	_insertCSS({tabId: tab.id, frameId: frameId, file: "/inject.css"})
	
	// ... rest of the code

Proposed Change Add this code immediately after the executeScripts() call and before the _insertCSS() call:

async function injectContentScripts(tab, frameId = 0) {

	// skip frames without host permissions
	// checked again in executeScripts() but also skips CSS injection
	if ( !await isTabScriptable(tab.id, frameId || 0) ) return false;

	// inject into any frame
	// used with init_content.js to only inject when window receives focus
	await executeScripts(tab.id, {
		files: [
			"/utils.js", // for isTextBox
			"/nodes.js", // for shortcuts
			"/Shortcuts.js",
			"/inject.js",
			"/contexts.js",
			"/tools.js" // for shortcuts
		], frameId: frameId, runAt: "document_end"
	}, true);

	// ============ FIX: Inject into all iframes ============
	// Recursively inject content scripts into all frames on the page
	if ( frameId === 0 ) { // Only run from main frame to avoid infinite recursion
		try {
			let frames = await browser.webNavigation.getAllFrames({tabId: tab.id});
			for ( let frame of frames ) {
				if ( frame.frameId === 0 ) continue; // Skip main frame (already injected)
				
				console.log('[CS-IFRAME] Injecting into frame:', frame.frameId, frame.url);
				
				// Recursively call for each iframe
				await injectContentScripts(tab, frame.frameId);
			}
		} catch (error) {
			console.error('[CS-IFRAME] Error injecting into frames:', error);
		}
	}
	// ============ END FIX ============

	_insertCSS({tabId: tab.id, frameId: frameId, file: "/inject.css"})
	
	// ... rest of the code

Why This Fix Works Detects all frames: Uses browser.webNavigation.getAllFrames() to get all iframes on the page Recursive injection: CallsinjectContentScripts()for each frame, ensuring all scripts are loaded Prevents infinite loop: Only runs from frameId === 0 (main frame) to avoid recursion Preserves existing logic: All permission checks and scriptability tests are still performed per-frame Backward compatible: Doesn't break existing functionality for pages without iframes

Testing After applying the fix: Open browser console (F12) Navigate to Zimbra webmail or any page with iframes Look for console messages: [CS-IFRAME] Injecting into frame: <frameId> <url> Select text inside an iframe Right-click → Context Search → Select search engine ✅ New tab should open with search results

Additional Notes This fix is already implemented in other parts of background.js (see the mark case in the notify() function around line 400, which uses a similar pattern) The webNavigation permission is already declared in manifest.json, so no additional permissions are needed Cross-origin iframes may still fail due to browser security restrictions, but same-origin iframes (like Zimbra) will work

Parvares avatar Oct 21 '25 14:10 Parvares

You're on an older version. Please try the latest 1.48.4.8 and see if the issue is resolved. The injection scripting has been changed since v1.48.4.0

ssborbis avatar Oct 22 '25 03:10 ssborbis

Looking at your detailed report, I'm wondering if the root issue on Zimbra webmail is the use of src=about:blank in the email content iframe. I need an account to test, but I'll keep looking if you're willing to provide further feedback.

ssborbis avatar Oct 22 '25 03:10 ssborbis

I forget which version was last problematic, but there was an issue where content scripts were not injected into iframes at all. That issue was addressed.

However, because the iframe url is about:blank, this extension currently ignores injection. This is done for several reasons, but is also the reason the injection is not working in Zimbra.

I'm testing the following modification to the manifest, but I may need to explore alternative methods of allowing injection into about:blank pages.

"content_scripts": [
		{
			"matches": ["<all_urls>"],
			"js": ["lib/browser-polyfill.min.js","init_content.js"],
			"all_frames": true,
			"run_at": "document_start",
			"match_about_blank": true
		}
	],

ssborbis avatar Oct 22 '25 04:10 ssborbis

There's a fix to test in the master branch if you're able to sideload

ssborbis avatar Oct 22 '25 04:10 ssborbis

Hi Mike, Neither the latest version from the store (1.48.4.8) nor the sideloaded version works. I’ve tested the sideloaded version ContextSearch-web-ext-master (“version”: “1.48.4.8”), which seems to have this difference in the manifest compared to the version published on the Chrome Web Store.

Store version:

"content_scripts": [
  {
    "matches": ["<all_urls>"],
    "js": ["lib/browser-polyfill.min.js", "init_content.js"],
    "all_frames": true,
    "run_at": "document_start"
  }
],

Sideloaded version:

"content_scripts": [
  {
    "matches": ["<all_urls>"],
    "js": ["lib/browser-polyfill.min.js","init_content.js"],
    "all_frames": true,
    "run_at": "document_start",
    "match_about_blank": true
  }
],

Parvares avatar Oct 23 '25 08:10 Parvares

👉UPDATE!

I made some changese in init_content.js and background.js and now CS seems working consistently in Zimbra webmail where previously it only worked rarely and unpredictably (approximately 5% of attempts).

BACKGROUND.JS MODIFICATIONS Added Function (after global variables):

// Aggiungi QUESTA funzione - Gestione about:blank
async function handleAboutBlankInjection(tabId, frameId) {
    try {
        const tab = await browser.tabs.get(tabId);
        if (tab && tab.url) {
            await injectContentScripts(tab, frameId);
        }
    } catch (error) {
        console.log('About:blank injection failed:', error);
    }
}

Modified "injectContentScripts" case in notify function: BEFORE:

case "injectContentScripts":

    while ( isLoadingUserOptions )
        await new Promise(r => setTimeout(r, 50));
    if ( isAllowedURL(sender.tab.url)) {
        injectContentScripts(sender.tab, sender.frameId);
    } else {
        console.log("blacklisted", sender.tab.url);
    }
    break;

AFTER:

case "injectContentScripts":

    while ( isLoadingUserOptions )
        await new Promise(r => setTimeout(r, 50));
    
    // Ottieni informazioni sul frame corrente
    const frameInfo = await browser.webNavigation.getFrame({
        tabId: sender.tab.id,
        frameId: sender.frameId
    }).catch(() => null);
    
    // Se è un frame about:blank o un frame normale, procedi con l'iniezione
    if (frameInfo && (frameInfo.url === 'about:blank' || isAllowedURL(frameInfo.url))) {
        await injectContentScripts(sender.tab, sender.frameId);
    } else if (!frameInfo && isAllowedURL(sender.tab.url)) {
        // Fallback per frame senza informazioni
        await injectContentScripts(sender.tab, sender.frameId);
    } else {
        console.log("blacklisted or invalid frame", sender.tab.url);
    }
    break;

Added WebNavigation Listeners (after onInstalled listener):

// Aggiungi QUESTI listener - Gestione frame about:blank
browser.webNavigation.onCreatedNavigationTarget.addListener(async (details) => {
    if (details.url === 'about:blank') {
        // Aspetta che il frame sia pronto
        setTimeout(async () => {
            try {
                const tab = await browser.tabs.get(details.tabId);
                await injectContentScripts(tab, details.frameId);
            } catch (error) {
                console.log('Navigation target injection failed:', error);
            }
        }, 100);
    }
});

// Listener per frame commits (importante per Zimbra)
browser.webNavigation.onCommitted.addListener(async (details) => {
    if (details.frameId !== 0 && details.url === 'about:blank') {
        try {
            const tab = await browser.tabs.get(details.tabId);
            await injectContentScripts(tab, details.frameId);
        } catch (error) {
            console.log('Frame commit injection failed:', error);
        }
    }
});

Modified isTabScriptable function: BEFORE:

async function isTabScriptable(tabId, frameId = 0) {
    try {
        let result = await _executeScript({func: () => true, tabId: tabId, frameId: frameId, injectImmediately: true});
        return result ? true : false;
    } catch ( error ) {
        return false;
    }
}

AFTER:

async function isTabScriptable(tabId, frameId = 0) {
    try {
        // Per about:blank frames, prova comunque l'iniezione
        const frameInfo = await browser.webNavigation.getFrame({
            tabId: tabId,
            frameId: frameId
        }).catch(() => null);
        
        if (frameInfo && frameInfo.url === 'about:blank') {
            return true; // Consenti l'iniezione per about:blank
        }
        
        // Test normale per altri frame
        let result = await _executeScript({
            func: () => true, 
            tabId: tabId, 
            frameId: frameId, 
            injectImmediately: true
        });
        return result ? true : false;
    } catch (error) {
        // Per about:blank, potremmo comunque voler provare
        return false;
    }
}

INIT_CONTENT.JS - COMPLETE REPLACEMENT BEFORE:

if ( window && window !== window.top ) {
    window.addEventListener('mousedown', e => {
        console.log('iframe received focus');
        browser.runtime.sendMessage({action:"injectContentScripts"});
    }, {once: true});
} else {
    // Skip about:blank on tpo frames for now. Allow on iframes (see Zimba webmail)
    if (window?.location?.href !== 'about:blank');
        browser.runtime.sendMessage({action:"injectContentScripts"});
}

AFTER:

// Inject content scripts for all frames, including about:blank
(function() {
    'use strict';
    
    console.log('ContextSearch: init_content.js loaded for', window.location.href, 'in frame', window !== window.top ? 'iframe' : 'top frame');
    
    function injectContentScripts() {
        // Send message to background to inject content scripts
        try {
            browser.runtime.sendMessage({action: "injectContentScripts"})
                .catch(error => {
                    console.log('ContextSearch: Injection message failed, will retry on interaction', error);
                });
        } catch (error) {
            console.log('ContextSearch: Runtime not available, will retry on interaction', error);
        }
    }
    
    // Inject immediately for all frames
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', () => {
            injectContentScripts();
        });
    } else {
        injectContentScripts();
    }
    
    // Additional injection on user interaction for frames
    if (window !== window.top) {
        window.addEventListener('mousedown', e => {
            console.log('ContextSearch: iframe received focus - injecting scripts');
            injectContentScripts();
        }, {once: true});
        
        // Also try to inject on load for iframes
        window.addEventListener('load', () => {
            console.log('ContextSearch: iframe loaded - injecting scripts');
            injectContentScripts();
        }, {once: true});
    }
})();

KEY CHANGES SUMMARY: Enhanced about:blank detection using webNavigation.getFrame() Multiple injection triggers - DOMContentLoaded, load, and user interaction events Better error handling with try-catch blocks WebNavigation listeners to catch dynamically created about:blank frames Modified permission checking to allow injection into about:blank frames

Would it be possible implementing similar about:blank frame handling in the official version? Of course, you would implement it in whatever way you think is best.

Parvares avatar Oct 23 '25 10:10 Parvares

What i really need is access to a Zimbra Webmail account so I can see the code in action. I can see how the code you posted injects the content scripts several different ways, but I'd like to find a simpler solution if possible. Are you hosting your own Zimbra suite, or are you using webmail as a client?

ssborbis avatar Oct 24 '25 07:10 ssborbis

Thank you for your response! To answer your questions: I don't host my own Zimbra, I'm just using it as a web client through https://mail.bibliotechediroma.it/ I don't have account credentials to share since it's my work/organization email account

I found some detailed console logs that show exactly what's happening with Zimbra's iframe structure. I hope this might help you.

Zimbra's iframe structure:

document.querySelectorAll('iframe[src="about:blank"]')
// Returns:
NodeList(2) [
  iframe#zv__TV-main__MSG__body__iframe, 
  iframe#zv__MSG-72574__MSG__body__iframe
]

Parvares avatar Oct 24 '25 08:10 Parvares

Hi, I found a clean, minimal solution for the Zimbra webmail compatibility issue. The fix involves adding just 2 lines to init_content.js to handle about:blank iframes universally:

// Enhanced iframe handling in init_content.js
if (window !== window.top && window.location.href === 'about:blank') {
    browser.runtime.sendMessage({action:"injectContentScripts"}).catch(() => {});
}
// ...plus existing click handler for other iframes

Complete fixed init_content.js file:

// ContextSearch web-ext - Enhanced iframe compatibility
(function() {
    'use strict';
    
    if (window !== window.top) {
        // For all iframes: inject on click (original behavior)
        window.addEventListener('mousedown', e => {
            console.log('iframe received focus');
            browser.runtime.sendMessage({action:"injectContentScripts"});
        }, {once: true});
        
        // Additional: immediate injection for about:blank iframes (Zimbra fix)
        if (window.location.href === 'about:blank') {
            browser.runtime.sendMessage({action:"injectContentScripts"})
                .catch(error => console.log('ContextSearch: about:blank injection failed', error));
        }
    } else {
        // For top frames: skip about:blank (original behavior)
        if (window?.location?.href !== 'about:blank') {
            browser.runtime.sendMessage({action:"injectContentScripts"})
                .catch(error => console.log('ContextSearch: Injection failed', error));
        }
    }
})();

This approach works for Zimbra and any other sites using about:blank iframes, maintaining all existing behavior while resolving the injection issue. Would this solution be suitable for incorporation into the official version?

Parvares avatar Oct 24 '25 09:10 Parvares

Nice! Okay, so the issue is waiting on injection until the frame receives focus for about:blank pages. I'll add this fix to the init_content.js, and I'll also add a user preferences to enable it in case it causes issues elsewhere. I'll post again when I have something in the repo

ssborbis avatar Oct 25 '25 04:10 ssborbis

There doesn't appear to be any slowdown in the benchmark. I'll probably leave the code as you posted it. The repo is updated. Thanks for the help!

ssborbis avatar Oct 25 '25 04:10 ssborbis

Awesome! Glad to hear everything runs smoothly. Appreciate you testing it and updating the repo! I’m looking forward to trying out the official version.

Parvares avatar Oct 25 '25 07:10 Parvares