Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
{
"manifest_version": 3,
"name": "Mailsuite Signature Stripper",
"version": "0.1.0",
"version": "0.2.0",
"description": "Removes the Mailsuite/Mailtrack promotional signature from your outgoing Gmail messages. Read tracking keeps working.",
"permissions": ["storage"],
"host_permissions": ["https://mail.google.com/*"],
Expand Down
4 changes: 4 additions & 0 deletions src/detect.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@
*/
const SIGNATURE_MARKERS = [
'#mt-signature',
/* Gmail rewrites ids when it quotes or renders a message, turning
id="mt-signature" into id="m_-1234...mt-signature", which is why
Mailsuite ships its own un-prefixing helper. Match on contains. */
'[id*="mt-signature"]',
'[data-signature-template]',
'[class*="mt-signature"]',
'[class*="mt-old-signature"]',
Expand Down
39 changes: 26 additions & 13 deletions src/strip.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,23 @@
const detect = globalThis.MailSuiteFreeDetect;
if (!detect) return;

/* Gmail's compose body, across layouts and locales. */
const COMPOSE = [
'div[g_editable="true"]',
'div.Am.Al.editable',
'div[contenteditable="true"][role="textbox"]',
'div[aria-label="Message Body"]',
].join(',');

/*
* Scan the whole page, not a list of guessed compose selectors.
*
* The first version scoped every scan to div[g_editable="true"] and friends.
* That put a guess about Gmail's internal class names on the critical path:
* if the compose root did not match, nothing was ever examined and the
* extension silently did nothing.
*
* It does not need to be that fragile. #mt-signature and
* data-signature-template are Mailsuite's own labels and appear on nothing
* else, so if one is in the page it is theirs and it can go. Scanning from
* document.body removes an entire class of failure.
*
* Side effect worth knowing: this also hides the signature on Mailtrack mail
* you receive. Purely visual, in your browser, and it changes nothing about
* what anybody sent.
*/
const settings = { enabled: true, keepUnsubscribe: true, removed: 0 };

chrome.storage.local.get(settings).then((stored) => Object.assign(settings, stored));
Expand All @@ -26,14 +35,14 @@
});

const scrubAll = () => {
if (!settings.enabled) return 0;
let removed = 0;
for (const root of document.querySelectorAll(COMPOSE)) {
removed += detect.scrubRoot(root, { keepUnsubscribe: settings.keepUnsubscribe });
}
if (!settings.enabled || !document.body) return 0;
const removed = detect.scrubRoot(document.body, {
keepUnsubscribe: settings.keepUnsubscribe,
});
if (removed) {
settings.removed += removed;
chrome.storage.local.set({ removed: settings.removed });
console.log(`[MailSuiteFree] removed ${removed} signature block(s)`);
}
return removed;
};
Expand Down Expand Up @@ -81,5 +90,9 @@
true,
);

/* Says the content script is alive. If this line is missing from the Gmail
console, the extension is not loaded, which is a different problem from the
extension not working. */
console.log('[MailSuiteFree] active on', location.host);
scrubAll();
})();
11 changes: 11 additions & 0 deletions test/detect.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,17 @@ test('a beacon inside the real signature survives it', () => {
assert.equal(root.querySelectorAll('img[src*="/trace/mail/"]').length, 1);
});

test('catches the signature after Gmail has prefixed the id', () => {
/* Gmail rewrites ids when it renders or quotes a message, which is why
Mailsuite ships its own un-prefixing helper. */
const root = compose(
'<div>Body</div>' + realSignature(18).replace('id="mt-signature"', 'id="m_-8891234567890mt-signature"'),
);

assert.equal(detect.scrubRoot(root), 1);
assert.equal(root.querySelector('[id*="mt-signature"]'), null);
});

test('markedBlocks returns only the outermost match', () => {
const root = compose(realSignature(17).replace('alt="Mailsuite"', 'alt="Mailsuite" class="mt-signature-logo"'));
const blocks = detect.markedBlocks(root);
Expand Down
Loading