Skip to content

Commit

Permalink
Implmenet favicon restoration using on-demand webpage fetch
Browse files Browse the repository at this point in the history
It's a little slow, will improve speed later. Also added "observers" to report on the changes of title and favicon. Just for debugging.
  • Loading branch information
ahmad-PH committed Feb 12, 2024
1 parent dd926e6 commit 73f91b5
Show file tree
Hide file tree
Showing 6 changed files with 98 additions and 32 deletions.
6 changes: 5 additions & 1 deletion src/contentScript/backgroundScriptApi.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import log from "../log";
import { getLogger } from "../log";
import { TabSignature } from "../types";

let log = getLogger('BackgroundScriptAPI', 'debug');

class BackgroundScriptAPI {
/**
* @param {TabSignature} signature
*/
async saveSignature(signature) {
log.debug('saveSignature called with signature:', signature);
log.debug('Current stack trace:', new Error().stack);
try {
await chrome.runtime.sendMessage({command: "save_signature", signature});
} catch (error) {
Expand Down
28 changes: 27 additions & 1 deletion src/contentScript/components/App/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,32 @@ export default function App() {
useEffect(() => {
const debugFunction = async () => {
log.debug('storage:', await chrome.storage.sync.get(null));

async function getFavicon(url) {
// Fetch the HTML of the webpage
const response = await fetch(url);
const html = await response.text();

// Parse the HTML
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');

// Find the favicon link
// let faviconLink = doc.querySelector('link[rel="shortcut icon"], link[rel="icon"]');
let faviconLink = doc.querySelector('head > link[rel~="icon"]');

// If no favicon link is found, use /favicon.ico
let faviconUrl;
if (faviconLink) {
faviconUrl = new URL(faviconLink.href, url).href;
} else {
faviconUrl = new URL('/favicon.ico', url).href;
}

return faviconUrl
}
log.debug('Fetched url:');
log.debug(await getFavicon(document.URL));
};
document.body.addEventListener('click', debugFunction);

Expand All @@ -118,7 +144,7 @@ export default function App() {
newDocumentTitle,
newDocumentFavicon,
tab.signature.originalTitle,
tab.signature.originalFaviconUrl
await tab.getFavicon(document.URL)
));
if (!newDocumentTitle) {
tab.restoreTitle();
Expand Down
23 changes: 14 additions & 9 deletions src/contentScript/initializationContentScript.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,16 @@ const olog = getLogger('Observer', 'debug');

let originalTitle = document.title;
let originalFaviconUrl = await bgScriptApi.getFaviconUrl();
log.debug('document.title:', originalTitle, 'faviconUrl:', (originalFaviconUrl ? (originalFaviconUrl.substring(0, 30) + '...') : null));

const newSignature = new TabSignature(title, favicon, originalTitle, originalFaviconUrl);
log.debug('setting signature to:', newSignature);
// await tab.setSignature(newSignature, false, false);





// Title Observer:
let titleMutationObserver = new MutationObserver((mutations) => {
olog.debug('titleMutationObserver callback called', mutations);
// olog.debug('titleMutationObserver callback called', mutations);
mutations.forEach((mutation) => {
if (mutation.target.nodeName === 'TITLE') {
const newTitle = document.title;
Expand All @@ -55,17 +53,17 @@ const olog = getLogger('Observer', 'debug');

// Favicon Observer:
let faviconMutationObserver = new MutationObserver((mutations) => {
olog.debug('faviconMutationObserver callback called', mutations);
// olog.debug('faviconMutationObserver callback called', mutations);
mutations.forEach((mutation) => {
if (mutation.type === 'childList' && mutation.target === document.head) {
olog.debug('Children of <head> have changed');
['addedNodes', 'removedNodes'].forEach((nodeType) => {
mutation[nodeType].forEach((node) => {
if (node.nodeName === 'LINK') {
olog.debug(nodeType + ' LINK detected:');
olog.debug('LINK mutation detected, of type: ', nodeType);
const newHref = node.href;
if (newHref.length > 100) {
olog.debug('LINK href:', newHref.substring(0, 40) + '...');
if (newHref.includes('data:')) {
olog.debug('LINK href:', newHref.substring(0, 30) + '...');
} else {
olog.debug('LINK href:', newHref);
}
Expand All @@ -74,11 +72,18 @@ const olog = getLogger('Observer', 'debug');

});
}

const target = mutation.target;
if (target instanceof HTMLLinkElement) {
if (target.nodeName === 'LINK' && target.rel.includes('icon')) {
olog.debug('LINK mutation detect, of type: direct mutation.', target.href.substring(0, 10) + '...');
}
}
});
});

const headElement = document.querySelector('head');
olog.debug('head element:', headElement);
// olog.debug('head element:', headElement);
if (headElement) {
faviconMutationObserver.observe(headElement, { subtree: true, childList: true, attributes: true });
}
Expand Down
33 changes: 30 additions & 3 deletions src/contentScript/tab.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ export class Tab {
* This will not restore them to their original values.
*/
async setSignature(signature, preserve = true, save = true) {
log.debug('setDocumentSignature called with signature:', signature);
log.debug('setSignature called with signature:', signature);
if (signature.title) {
this.setTitle(signature.title, preserve);
} else {
Expand All @@ -118,7 +118,7 @@ export class Tab {
if (save) {
await bgScriptApi.saveSignature(signature);
}

this.signature = signature;
}

Expand Down Expand Up @@ -166,11 +166,14 @@ export class Tab {
}
}

preserveFavicon() {
// empty
}

/**
* @param {string} emojiDataURL
*/
preserveFavicon(emojiDataURL) {
preserveFaviconLegacy(emojiDataURL) {
// Disconnect the previous observer if it exists, to avoid infinite loop.
plog.debug('preserveFavicon called with emojiDataURL:', emojiDataURL.substring(0, 10) + '...');
this.disconnectFaviconPreserver();
Expand Down Expand Up @@ -204,6 +207,30 @@ export class Tab {
this.faviconMutationObserver.disconnect();
}
}

async getFavicon(url) {
// Fetch the HTML of the webpage
log.debug('Fetching: ', url);
const response = await fetch(url);
const html = await response.text();

// Parse the HTML
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');

// Find the favicon link
let faviconLink = doc.querySelector('head > link[rel~="icon"]');

// If no favicon link is found, use /favicon.ico
let faviconUrl;
if (faviconLink) {
faviconUrl = new URL(faviconLink.href, url).href;
} else {
faviconUrl = new URL('/favicon.ico', url).href;
}

return faviconUrl
}
}

const tab = new Tab();
Expand Down
11 changes: 8 additions & 3 deletions tests/e2e/driverUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ class DriverUtils {
await this.openRenameDialog();
const renameBox = await this.driver.findElement(By.id(INPUT_BOX_ID));
await renameBox.clear();
await renameBox.sendKeys(newTabTitle, Key.ENTER);
await renameBox.sendKeys(newTabTitle);
await this.submitRenameDialog();
}

async openEmojiPicker() {
Expand All @@ -31,8 +32,7 @@ class DriverUtils {
const emojiElement = await this.driver.findElement(By.xpath(xpath));
await emojiElement.click();

const renameBox = await this.driver.findElement(By.id(INPUT_BOX_ID));
await renameBox.sendKeys(Key.ENTER);
await this.submitRenameDialog();
}

async setSignature(title, favicon) {
Expand Down Expand Up @@ -123,8 +123,13 @@ class DriverUtils {
await emojiPicker.click();
const emojiRemoveButton = await this.driver.findElement(By.id(EMOJI_REMOVE_BUTTON_ID));
emojiRemoveButton.click();
await this.submitRenameDialog();
}

async submitRenameDialog() {
const renameBox = await this.driver.findElement(By.id(INPUT_BOX_ID));
await renameBox.sendKeys(Key.ENTER);
await this.driver.wait(until.elementIsNotVisible(renameBox));
}

}
Expand Down
29 changes: 14 additions & 15 deletions tests/e2e/seleniumTests.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ describe('Selenium UI Tests', () => {
}

beforeEach(async () => {
// It's better to put the deletion here, because sometimes afterEach gets messed up.
await fs.rm('/tmp/chrome-profile', { recursive: true, force: true });

await createNewDriver();
});

Expand All @@ -68,12 +71,12 @@ describe('Selenium UI Tests', () => {
const extensionLogs = logs.filter(log => (
log.message.startsWith(extensionPrefix) && log.message.includes('#')
)).map(log => log.message.replace(extensionPrefix, ''));
console.log('extension logs:', extensionLogs);
// console.log('extension logs:', extensionLogs);
await driver.quit();
}
driver = null;
driverUtils = null;
await fs.rm('/tmp/chrome-profile', { recursive: true, force: true });
// await fs.rm('/tmp/chrome-profile', { recursive: true, force: true });
}

afterEach(tearDown);
Expand Down Expand Up @@ -214,17 +217,14 @@ describe('Selenium UI Tests', () => {
await driverUtils.renameTab('');
const actualTitle = await driverUtils.getTitle();
expect(actualTitle).toBe(originalTitle);
})
});

test('Restores original favicon when empty favicon passed', async () => {
await driver.get(googleUrl);
// await driver.sleep(1 * SECONDS);
await driverUtils.setFavicon('🎂');
await driverUtils.removeFavicon();
// await driver.sleep(2 * SECONDS);
// await driver.sleep(20 * MINUTES);
await driverUtils.assertFaviconUrl(googleFavicon);
}, 30 * MINUTES);
});

test('Title Preserver keeps the title the same', async () => {
// This test is mainly written to emulate what Facebook does (revert the title to its original)
Expand All @@ -237,12 +237,11 @@ describe('Selenium UI Tests', () => {
expect(actualTitle).toBe('New title');
});


test("Title won't flash to the original, when switching between pages", async () => {
await driver.get(urls[0]);
await driverUtils.renameTab('New title');
await driver.sleep(5 * SECONDS);
await driver.get(urls[1]);
await driver.sleep(5 * SECONDS);
}, 100 * SECONDS);
// test("Title won't flash to the original, when switching between pages", async () => {
// await driver.get(urls[0]);
// await driverUtils.renameTab('New title');
// await driver.sleep(5 * SECONDS);
// await driver.get(urls[1]);
// await driver.sleep(5 * SECONDS);
// }, 100 * SECONDS);
});

0 comments on commit 73f91b5

Please sign in to comment.