diff --git a/extension/extension.js b/extension/extension.js --- a/extension/extension.js +++ b/extension/extension.js @@ -488,11 +488,63 @@ // remove properties not in whitelist var filteredTabs = filterArrayObjects(tabs, whitelistedTabProperties); - port.postMessage({ - subsystem: "tabsrunner", - event: "gotTabs", - tabs: filteredTabs - }); + // Shared between the callbacks + var total = tabs.length; + + var sendTabsIfComplete = function() { + if (--total > 0) { + return; + } + + port.postMessage({ + subsystem: "tabsrunner", + event: "gotTabs", + tabs: filteredTabs + }); + }; + + for (let tabIndex in tabs) { + let currentIndex = tabIndex; // Not shared + var favIconUrl = tabs[tabIndex].favIconUrl; + + if (!favIconUrl) { + sendTabsIfComplete(); + } else if (favIconUrl.match(/^data:image/)) { + // Already a data URL + filteredTabs[currentIndex].favIconData = favIconUrl; + sendTabsIfComplete(); + } else { + // Send a request to fill the cache (=no timeout) + let xhrForCache = new XMLHttpRequest(); + xhrForCache.open("GET", favIconUrl); + xhrForCache.send(); + + // Try to fetch from (hopefully) the cache (100ms timeout) + let xhr = new XMLHttpRequest(); + xhr.onreadystatechange = function() { + if (xhr.readyState != 4) { + return; + } + + if (!xhr.response) { + filteredTabs[currentIndex].favIconData = ""; + sendTabsIfComplete(); + return; + } + + var reader = new FileReader(); + reader.onloadend = function() { + filteredTabs[currentIndex].favIconData = reader.result; + sendTabsIfComplete(); + } + reader.readAsDataURL(xhr.response); + }; + xhr.open('GET', favIconUrl); + xhr.responseType = 'blob'; + xhr.timeout = 100; + xhr.send(); + } + } }); }); diff --git a/extension/manifest.json b/extension/manifest.json --- a/extension/manifest.json +++ b/extension/manifest.json @@ -47,6 +47,7 @@ "downloads.shelf", "tabs", "history", + "", "contextMenus" ], diff --git a/tabsrunner/tabsrunner.cpp b/tabsrunner/tabsrunner.cpp --- a/tabsrunner/tabsrunner.cpp +++ b/tabsrunner/tabsrunner.cpp @@ -183,17 +183,30 @@ match.setRelevance(relevance); QString iconName; + QIcon icon; + + const QString favIconData = tab.value(QStringLiteral("favIconData")).toString(); + const int b64start = favIconData.indexOf(','); + if (b64start != -1) { + QByteArray b64 = favIconData.rightRef(favIconData.size() - b64start - 1).toLatin1(); + QByteArray data = QByteArray::fromBase64(b64); + QPixmap pixmap; + if (pixmap.loadFromData(data)) + icon = QIcon(pixmap); + } - if (browser == QLatin1String("chrome")) { - iconName = QStringLiteral("google-chrome"); - } else if (browser == QLatin1String("chromium")) { - iconName = QStringLiteral("chromium-browser"); - } else if (browser == QLatin1String("firefox")) { - iconName = QStringLiteral("firefox"); - } else if (browser == QLatin1String("opera")) { - iconName = QStringLiteral("opera"); - } else if (browser == QLatin1String("vivaldi")) { - iconName = QStringLiteral("vivaldi"); + if (icon.isNull()) { + if (browser == QLatin1String("chrome")) { + iconName = QStringLiteral("google-chrome"); + } else if (browser == QLatin1String("chromium")) { + iconName = QStringLiteral("chromium-browser"); + } else if (browser == QLatin1String("firefox")) { + iconName = QStringLiteral("firefox"); + } else if (browser == QLatin1String("opera")) { + iconName = QStringLiteral("opera"); + } else if (browser == QLatin1String("vivaldi")) { + iconName = QStringLiteral("vivaldi"); + } } if (incognito) { @@ -208,7 +221,11 @@ } } - match.setIconName(iconName); + if (!iconName.isEmpty()) { + match.setIconName(iconName); + } else if(!icon.isNull()) { + match.setIcon(icon); + } matches << match; }