fix and simplify .user.css URL installer (#856)

* fix and simplify .user.css URL installer

* Refactor: pull out tab-manager and icon-manager

* fixes/cosmetics

* usercss installer url check
* extract downloaders
* simplify tabManager
* rework/split openInstallerPage
* use a simple object instead of map
* trivial bugfixes
* cosmetics

* fixup! updateIconBadge in styleViaAPI

Co-authored-by: eight <eight04@gmail.com>
This commit is contained in:
tophf 2020-02-23 18:43:26 +03:00 committed by GitHub
parent c3b7657433
commit 4bbce7cb9f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 401 additions and 741 deletions

View File

@ -230,10 +230,6 @@
"message": "Провери за обновления",
"description": "Label for the checkbox to save current URL for update check"
},
"installUpdateUnavailable": {
"message": "За да разрешите проверка за обновления, пуснете файла върху лентата с табове, или в метаданните на стила укажете @updateURL.",
"description": ""
},
"license": {
"message": "Лиценз",
"description": "Label for the license"
@ -309,18 +305,10 @@
"message": "Получи се грешка докато наблюдавахме файла",
"description": "The label of live-reload error"
},
"liveReloadInstallHint": {
"message": "Преглед на живо е разрешен, така че инсталирания стил ще бъде обновен автоматично при външни промени докато двата прозореца с кода и оригинала са отворени.",
"description": "The label of live-reload feature"
},
"liveReloadLabel": {
"message": "Преглед на живо",
"description": "The label of live-reload feature"
},
"liveReloadUnavailable": {
"message": "За да разрешите презареждане в реално време, пуснете файла върху лентата с табове (областта, където са показани заглавията на табовете).",
"description": ""
},
"manageFilters": {
"message": "Филтри",
"description": "Label for filters container"

View File

@ -597,10 +597,6 @@
"message": "Při sledování souboru došlo k chybě",
"description": "The label of live-reload error"
},
"liveReloadInstallHint": {
"message": "Živá aktualizace je povolena, takže nainstalovaný styl bude automaticky aktualizován při externích změnách, dokud budou tento list a list zdrojového souboru otevřeny.",
"description": "The label of live-reload feature"
},
"liveReloadLabel": {
"message": "Živá aktualizace",
"description": "The label of live-reload feature"

View File

@ -526,10 +526,6 @@
"message": "Nach Updates suchen",
"description": "Label for the checkbox to save current URL for update check"
},
"installUpdateUnavailable": {
"message": "Ziehe die Datei auf die Tableiste oder definiere @updateURL in den Metadaten des Styles, um automatisch nach Updates zu suchen.",
"description": ""
},
"license": {
"message": "Lizenz",
"description": "Label for the license"
@ -605,18 +601,10 @@
"message": "Bei der Echtzeitaktualisierung der Datei ist ein Fehler aufgetreten",
"description": "The label of live-reload error"
},
"liveReloadInstallHint": {
"message": "Echtzeitaktualisierung ist aktiviert, sodass die Darstellung des jeweiligen Styles automatisch aktualisiert wird, wenn externe Änderungen erfolgen.",
"description": "The label of live-reload feature"
},
"liveReloadLabel": {
"message": "Echtzeitaktualisierung",
"description": "The label of live-reload feature"
},
"liveReloadUnavailable": {
"message": "Ziehe die Datei auf die Tableiste, um die Echtzeitaktualisierung nutzen zu können.",
"description": ""
},
"manageFavicons": {
"message": "Favicons in der \"Gilt für\" Spalte anzeigen",
"description": "Label for the checkbox that toggles applies-to favicons in the new UI on manage page"

View File

@ -290,6 +290,10 @@
"message": "Drop your backup file anywhere on this page to import.",
"description": "Drag'n'drop message"
},
"dragDropUsercssTabstrip": {
"message": "To install the file, drop it on the tab strip (the area where the tab titles are shown).",
"description": "Message popup shown when erroneously dropping a usercss file into the manager page"
},
"editDeleteText": {
"message": "Delete",
"description": "Label for the context menu item in the editor to delete selected text"
@ -542,9 +546,6 @@
"message": "Check for updates",
"description": "Label for the checkbox to save current URL for update check"
},
"installUpdateUnavailable": {
"message": "To enable check for updates, drop the file on the tab strip or specify @updateURL in the style metadata."
},
"license": {
"message": "License",
"description": "Label for the license"
@ -625,16 +626,17 @@
"description": "The label of live-reload error"
},
"liveReloadInstallHint": {
"message": "Live reload is enabled so the installed style will be auto-updated on external changes while both this tab and the source file tab are open.",
"message": "Keep this tab open to auto-update the style on external changes.",
"description": "The label of live-reload feature"
},
"liveReloadInstallHintFF": {
"message": "Keep both this tab and the original tab open to auto-update the style on external changes.",
"description": "The extra hint of live-reload feature shown only for file:// URLs in Firefox"
},
"liveReloadLabel": {
"message": "Live reload",
"description": "The label of live-reload feature"
},
"liveReloadUnavailable": {
"message": "To enable live reload, drop the file on the tab strip (the area where the tab titles are shown)."
},
"manageFavicons": {
"message": "Favicons in applies-to column",
"description": "Label for the checkbox that toggles applies-to favicons in the new UI on manage page"

View File

@ -35,10 +35,6 @@
"message": "Edit style",
"description": "Title of the page for editing styles"
},
"installUpdateUnavailable": {
"message": "To enable checking for updates, drop the file on the tab strip or specify @updateURL in the style metadata.",
"description": ""
},
"license": {
"message": "Licence",
"description": "Label for the license"

View File

@ -518,10 +518,6 @@
"message": "Buscar actualizaciones",
"description": "Label for the checkbox to save current URL for update check"
},
"installUpdateUnavailable": {
"message": "Para habilitar la búsqueda de actualizaciones, suelte el archivo en la pestaña o especifique @updateURL en los metadatos del estilo.",
"description": ""
},
"license": {
"message": "Licencia",
"description": "Label for the license"
@ -597,18 +593,10 @@
"message": "Se ha producido un error al visualizar el archivo",
"description": "The label of live-reload error"
},
"liveReloadInstallHint": {
"message": "La recarga en tiempo real está habilitada, por lo que el estilo instalado se actualizará automáticamente con los cambios externos mientras estén abiertas esta pestaña y la pestaña del archivo de origen.",
"description": "The label of live-reload feature"
},
"liveReloadLabel": {
"message": "Recarga en tiempo real",
"description": "The label of live-reload feature"
},
"liveReloadUnavailable": {
"message": "Para habilitar la recarga en tiempo real, suelte el archivo en la pestaña (el área donde se muestran los títulos de las pestañas).",
"description": ""
},
"manageFavicons": {
"message": "Favicons en la columna 'Se aplica a'",
"description": "Label for the checkbox that toggles applies-to favicons in the new UI on manage page"

View File

@ -538,10 +538,6 @@
"message": "Kontrolli uuendusi",
"description": "Label for the checkbox to save current URL for update check"
},
"installUpdateUnavailable": {
"message": "Uuenduste kontrollimise lubamiseks lohista failid kaartide ribale või määratle stiili metaandmetes @updateURL.",
"description": ""
},
"license": {
"message": "Litsents",
"description": "Label for the license"
@ -621,18 +617,10 @@
"message": "Faili vaatamisel esines viga",
"description": "The label of live-reload error"
},
"liveReloadInstallHint": {
"message": "Reaalajas uuestilaadimine on lubatud, seega paigaldatud stiili uuendatakse väliste muudatuste korral automaatselt, kuniks see kaart ja lähtefaili kaart mõlemad lahti on.",
"description": "The label of live-reload feature"
},
"liveReloadLabel": {
"message": "Reaalajas uuestilaadimine",
"description": "The label of live-reload feature"
},
"liveReloadUnavailable": {
"message": "Reaalajas uuestilaadimise lubamiseks lohista fail kaartide ribale (ala, kus näidatakse kaartide pealkirju).",
"description": ""
},
"manageFavicons": {
"message": "Lehe ikoonid rakendub-tulbas",
"description": "Label for the checkbox that toggles applies-to favicons in the new UI on manage page"

View File

@ -530,10 +530,6 @@
"message": "Rechercher les mises à jour",
"description": "Label for the checkbox to save current URL for update check"
},
"installUpdateUnavailable": {
"message": "Pour activer la vérification des mises à jour, glissez le fichier sur la barre des onglets ou spécifiez @updateURL dans les métadonnées du style.",
"description": ""
},
"license": {
"message": "Licence",
"description": "Label for the license"
@ -609,18 +605,10 @@
"message": "Une erreur est survenue durant la surveillance du fichier",
"description": "The label of live-reload error"
},
"liveReloadInstallHint": {
"message": "Le rechargement automatique est activé, donc le style installé sera mis à jour automatiquement après une modification externe quand à la fois cet onglet et longlet du fichier source sont ouverts.",
"description": "The label of live-reload feature"
},
"liveReloadLabel": {
"message": "Rechargement immédiat",
"description": "The label of live-reload feature"
},
"liveReloadUnavailable": {
"message": "Pour activer le rechargement automatique, glisser le fichier sur la barre des onglets (la zone où les titres des onglets sont affichés).",
"description": ""
},
"manageFavicons": {
"message": "Favicons dans la colonne « sapplique à »",
"description": "Label for the checkbox that toggles applies-to favicons in the new UI on manage page"

View File

@ -449,10 +449,6 @@
"message": "בדוק עדכונים",
"description": "Label for the checkbox to save current URL for update check"
},
"installUpdateUnavailable": {
"message": "על־מנת לאפשר בדיקת עדכונים, אנא שחרר את הקובץ על רצועת הכרטיסיות או ציין @updateURL ב־metadata של העיצוב.",
"description": ""
},
"license": {
"message": "רישיון",
"description": "Label for the license"
@ -519,10 +515,6 @@
"message": "רענון לייב (live)",
"description": "The label of live-reload feature"
},
"liveReloadUnavailable": {
"message": "על־מנת לאפשר רענון לייב (live), אנא שחרר את הקובץ על רצועת הכרטיסיות (האזור בו כותרות הכרטיסיות מוצגות).",
"description": ""
},
"manageFavicons": {
"message": "הצגת אייקונים בעמודת 'חל על'",
"description": "Label for the checkbox that toggles applies-to favicons in the new UI on manage page"

View File

@ -609,10 +609,6 @@
"message": "Hiba történt a fájl figyelése közben",
"description": "The label of live-reload error"
},
"liveReloadInstallHint": {
"message": "A valós idejű újratöltés engedélyezve van, így a telepített stílus automatikusan frissül külső változások során, amíg ez a fül és a forrásfájlt tartalmazó fül nyitva van.",
"description": "The label of live-reload feature"
},
"liveReloadLabel": {
"message": "Valós idejű újratöltés",
"description": "The label of live-reload feature"

View File

@ -538,10 +538,6 @@
"message": "更新をチェック",
"description": "Label for the checkbox to save current URL for update check"
},
"installUpdateUnavailable": {
"message": "更新のチェックを有効にするには、ファイルをタブストリップにドロップするか、スタイルのメタデータで @updateURL を指定してください。",
"description": ""
},
"license": {
"message": "ライセンス",
"description": "Label for the license"
@ -621,18 +617,10 @@
"message": "ファイルの監視中にエラーが発生しました",
"description": "The label of live-reload error"
},
"liveReloadInstallHint": {
"message": "自動リロードが有効になっているため、このタブとソースファイルのタブの両方が開いている間に、外部変更によってインストール済みスタイルが自動更新されることがあります。",
"description": "The label of live-reload feature"
},
"liveReloadLabel": {
"message": "自動リロード",
"description": "The label of live-reload feature"
},
"liveReloadUnavailable": {
"message": "自動リロードを有効にするには、ファイルをタブストリップ(タブのタイトルが表示されている領域)にドロップしてください。",
"description": ""
},
"manageFavicons": {
"message": "適用先欄のファビコン",
"description": "Label for the checkbox that toggles applies-to favicons in the new UI on manage page"

View File

@ -534,10 +534,6 @@
"message": "Controleren op updates",
"description": "Label for the checkbox to save current URL for update check"
},
"installUpdateUnavailable": {
"message": "Als u controle op updates wilt inschakelen, sleep dan het bestand naar de tabbladenstrook of geef een @updateURL op in de metagegevens van de stijl.",
"description": ""
},
"license": {
"message": "Licentie",
"description": "Label for the license"
@ -613,18 +609,10 @@
"message": "Er is een fout opgetreden tijdens het bekijken van het bestand",
"description": "The label of live-reload error"
},
"liveReloadInstallHint": {
"message": "Live herladen is ingeschakeld. De geïnstalleerde stijl zal bij externe wijzigingen automatisch worden bijgewerkt als zowel dit tabblad als het tabblad van het bronbestand zijn geopend.",
"description": "The label of live-reload feature"
},
"liveReloadLabel": {
"message": "Live herladen",
"description": "The label of live-reload feature"
},
"liveReloadUnavailable": {
"message": "Als u live herladen wilt inschakelen, sleep dan het bestand naar de tabbladenstrook (het gebied waar de tabbladtitels worden getoond).",
"description": ""
},
"manageFavicons": {
"message": "Favicons in kolom Van toepassing op",
"description": "Label for the checkbox that toggles applies-to favicons in the new UI on manage page"

View File

@ -546,10 +546,6 @@
"message": "Sprawdź aktualizacje",
"description": "Label for the checkbox to save current URL for update check"
},
"installUpdateUnavailable": {
"message": "Aby włączyć sprawdzanie aktualizacji, upuść plik na pasku kart lub określ @updateURL w metadanych stylu.",
"description": ""
},
"license": {
"message": "Licencja",
"description": "Label for the license"
@ -625,18 +621,10 @@
"message": "Wystąpił błąd podczas oglądania pliku",
"description": "The label of live-reload error"
},
"liveReloadInstallHint": {
"message": "Przeładowanie na żywo jest włączone, więc zainstalowany styl zostanie automatycznie zaktualizowany w przypadku zmian zewnętrznych, gdy ta karta i karta pliku źródłowego są otwarte.",
"description": "The label of live-reload feature"
},
"liveReloadLabel": {
"message": "Przeładuj na żywo",
"description": "The label of live-reload feature"
},
"liveReloadUnavailable": {
"message": "Aby włączyć przeładowanie na żywo, upuść plik na pasku kart (obszar, w którym wyświetlane są tytuły kart).",
"description": ""
},
"manageFavicons": {
"message": "Ikony ulubionych w kolumnie dotyczących",
"description": "Label for the checkbox that toggles applies-to favicons in the new UI on manage page"

View File

@ -506,10 +506,6 @@
"message": "Procurar atualizações",
"description": "Label for the checkbox to save current URL for update check"
},
"installUpdateUnavailable": {
"message": "Para ativar a verificação de atualizações, solte o ficheiro na faixa de separadores ou especifique @updateURL nos metadados de estilo.",
"description": ""
},
"license": {
"message": "Licença",
"description": "Label for the license"
@ -585,18 +581,10 @@
"message": "Ocorreu um erro ao vigiar o arquivo",
"description": "The label of live-reload error"
},
"liveReloadInstallHint": {
"message": "O recarregamento dinâmico está ativado para que o estilo instalado seja atualizado automaticamente em alterações externas enquanto esse separador e o separador do arquivo de origem estiverem abertos.",
"description": "The label of live-reload feature"
},
"liveReloadLabel": {
"message": "Recarregamento dinâmico",
"description": "The label of live-reload feature"
},
"liveReloadUnavailable": {
"message": "Para ativar recarregamento dinâmico, solte o ficheiro na faixa de separadores (a área onde os títulos dos separadores são mostrados).",
"description": ""
},
"manageFavicons": {
"message": "Favicons em colunas de aplica-se a",
"description": "Label for the checkbox that toggles applies-to favicons in the new UI on manage page"

View File

@ -462,10 +462,6 @@
"message": "Verificați update-urile",
"description": "Label for the checkbox to save current URL for update check"
},
"installUpdateUnavailable": {
"message": "Pentru a activa verificarea de updates. trage fișierul pe taburi (zona cu titluri) sau specifica @updateURL în metadata temei.",
"description": ""
},
"license": {
"message": "Licență",
"description": "Label for the license"
@ -537,14 +533,6 @@
"message": "A avut loc o eroare în timpul monitorizării acestui fișier",
"description": "The label of live-reload error"
},
"liveReloadInstallHint": {
"message": "Reload automat este activat deci tema instalată va fi updatată automat când acest tab si fișierul surca sunt deschise.",
"description": "The label of live-reload feature"
},
"liveReloadUnavailable": {
"message": "Pentru a activa live reload (refresh automat), trage fișierul pe taburi (zona unde titlurile temelor sunt afișate) ",
"description": ""
},
"manageFavicons": {
"message": "Favicons în coloana 'se aplică la'",
"description": "Label for the checkbox that toggles applies-to favicons in the new UI on manage page"

View File

@ -534,10 +534,6 @@
"message": "Проверить обновления",
"description": "Label for the checkbox to save current URL for update check"
},
"installUpdateUnavailable": {
"message": "Для проверки обновлений перетяните файл на полоску вкладок или впишите @updateURL в мета-данных стиля.",
"description": ""
},
"license": {
"message": "Лицензия",
"description": "Label for the license"
@ -617,18 +613,10 @@
"message": "Ошибка слежения за файлом",
"description": "The label of live-reload error"
},
"liveReloadInstallHint": {
"message": "Включена автозагрузка изменений установленный стиль будет обновляться автоматически пока открыта эта вкладка и вкладка исходного файла.",
"description": "The label of live-reload feature"
},
"liveReloadLabel": {
"message": "Автозагрузка изменений",
"description": "The label of live-reload feature"
},
"liveReloadUnavailable": {
"message": "Для автозагрузки изменений перетяните файл на полоску вкладок (область, где показываются названия вкладок).",
"description": ""
},
"manageFavicons": {
"message": "Пиктограммы для целевых сайтов",
"description": "Label for the checkbox that toggles applies-to favicons in the new UI on manage page"

View File

@ -522,10 +522,6 @@
"message": "Sök efter uppdateringar",
"description": "Label for the checkbox to save current URL for update check"
},
"installUpdateUnavailable": {
"message": "För att aktivera sök efter uppdateringar, släpp filen på flikremsan eller ange @updateURL i stilmetadatan.",
"description": ""
},
"license": {
"message": "Licens",
"description": "Label for the license"
@ -597,18 +593,10 @@
"message": "Ett fel uppstod medan du tittade på filen",
"description": "The label of live-reload error"
},
"liveReloadInstallHint": {
"message": "Uppdatering i realtid är aktiverat så att den installerade stilen automatiskt uppdateras vid externa ändringar medan både den här fliken och fliken för källfilen är öppna.",
"description": "The label of live-reload feature"
},
"liveReloadLabel": {
"message": "Uppdaterar i realtid",
"description": "The label of live-reload feature"
},
"liveReloadUnavailable": {
"message": "För att aktivera uppdatering i realtid, släpp filen på fliken strip (det område där fliktitlar visas).",
"description": ""
},
"manageFavicons": {
"message": "Ikoner i 'Tillämpad för' kolumnen",
"description": "Label for the checkbox that toggles applies-to favicons in the new UI on manage page"

View File

@ -534,10 +534,6 @@
"message": "检查更新",
"description": "Label for the checkbox to save current URL for update check"
},
"installUpdateUnavailable": {
"message": "若想允许检查更新,请将文件拖动到标签栏上,或在样式的元数据中声明 @updateURL。",
"description": ""
},
"license": {
"message": "许可证",
"description": "Label for the license"
@ -613,18 +609,10 @@
"message": "查看文件时发生错误",
"description": "The label of live-reload error"
},
"liveReloadInstallHint": {
"message": "动态刷新被激活后,当被安装的样式被更新时,只要本网页和目标网页都是开启状态,在样式上进行的更新会实时反映到目标网页上。",
"description": "The label of live-reload feature"
},
"liveReloadLabel": {
"message": "动态刷新",
"description": "The label of live-reload feature"
},
"liveReloadUnavailable": {
"message": "要想激活动态刷新,请将文件拖到标签条(即选项卡的区域)上。",
"description": ""
},
"manageFavicons": {
"message": "显示已应用的图标",
"description": "Label for the checkbox that toggles applies-to favicons in the new UI on manage page"

View File

@ -546,10 +546,6 @@
"message": "檢查更新",
"description": "Label for the checkbox to save current URL for update check"
},
"installUpdateUnavailable": {
"message": "要啟用檢查更新,將檔案拖放到分頁條上或是在樣式詮釋資料中指定 @updateURL。",
"description": ""
},
"license": {
"message": "授權條款",
"description": "Label for the license"
@ -625,18 +621,10 @@
"message": "觀看檔案時發生錯誤",
"description": "The label of live-reload error"
},
"liveReloadInstallHint": {
"message": "即時重新整理已啟用,以便在這個分頁與來源檔案分頁都開啟時自動於有外部變更時自動更新已安裝樣式。",
"description": "The label of live-reload feature"
},
"liveReloadLabel": {
"message": "即時重新整理",
"description": "The label of live-reload feature"
},
"liveReloadUnavailable": {
"message": "要啟用即時重新整理,將檔案托放到分頁條上(分頁標題顯示的區域)。",
"description": ""
},
"manageFavicons": {
"message": "Favicons 要套用到的欄位",
"description": "Label for the checkbox that toggles applies-to favicons in the new UI on manage page"

View File

@ -1,7 +1,8 @@
/* global download prefs openURL FIREFOX CHROME VIVALDI
debounce URLS ignoreChromeError getTab
styleManager msg navigatorUtil iconUtil workerUtil contentScripts sync
findExistingTab createTab activateTab isTabReplaceable getActiveTab */
/* global download prefs openURL FIREFOX CHROME
URLS ignoreChromeError usercssHelper
styleManager msg navigatorUtil workerUtil contentScripts sync
findExistingTab createTab activateTab isTabReplaceable getActiveTab
iconManager tabManager */
'use strict';
@ -49,14 +50,7 @@ window.API_METHODS = Object.assign(window.API_METHODS || {}, {
openEditor,
updateIconBadge(count) {
// TODO: remove once our manifest's minimum_chrome_version is 50+
// Chrome 49 doesn't report own extension pages in webNavigation apparently
// so we do a force update which doesn't use the cache.
if (CHROME && CHROME < 2661 && this.sender.tab.url.startsWith(URLS.ownOrigin)) {
updateIconBadgeForce(this.sender.tab.id, count);
} else {
updateIconBadge(this.sender.tab.id, count);
}
iconManager.updateIconBadge(this.sender.tab.id, count);
return true;
},
@ -87,23 +81,23 @@ var browserCommands, contextMenus;
// register all listeners
msg.on(onRuntimeMessage);
// tell apply.js to refresh styles for non-committed navigation
navigatorUtil.onUrlChange(({tabId, frameId}, type) => {
if (type === 'committed') {
// styles would be updated when content script is injected.
return;
if (type !== 'committed') {
msg.sendTab(tabId, {method: 'urlChanged'}, {frameId})
.catch(msg.ignoreError);
}
});
tabManager.onUpdate(({tabId, url, oldUrl = ''}) => {
if (usercssHelper.testUrl(url) && !oldUrl.startsWith(URLS.installUsercss)) {
usercssHelper.testContents(tabId, url).then(data => {
if (data.code) usercssHelper.openInstallerPage(tabId, url, data);
});
}
msg.sendTab(tabId, {method: 'urlChanged'}, {frameId})
.catch(msg.ignoreError);
});
if (FIREFOX) {
// FF applies page CSP even to content scripts, https://bugzil.la/1267027
navigatorUtil.onCommitted(webNavUsercssInstallerFF, {
url: [
{pathSuffix: '.user.css'},
{pathSuffix: '.user.styl'},
]
});
// FF misses some about:blank iframes so we inject our content script explicitly
navigatorUtil.onDOMContentLoaded(webNavIframeHelperFF, {
url: [
@ -122,46 +116,6 @@ if (chrome.commands) {
chrome.commands.onCommand.addListener(command => browserCommands[command]());
}
const tabIcons = new Map();
chrome.tabs.onRemoved.addListener(tabId => tabIcons.delete(tabId));
chrome.tabs.onReplaced.addListener((added, removed) => tabIcons.delete(removed));
prefs.subscribe([
'disableAll',
'badgeDisabled',
'badgeNormal',
], () => debounce(refreshIconBadgeColor));
prefs.subscribe([
'show-badge'
], () => debounce(refreshAllIconsBadgeText));
prefs.subscribe([
'disableAll',
'iconset',
], () => debounce(refreshAllIcons));
prefs.initializing.then(() => {
refreshIconBadgeColor();
refreshAllIconsBadgeText();
refreshAllIcons();
});
navigatorUtil.onUrlChange(({tabId, frameId, transitionQualifiers}, type) => {
if (type === 'committed' && !frameId) {
// it seems that the tab icon would be reset by navigation. We
// invalidate the cache here so it would be refreshed by `apply.js`.
tabIcons.delete(tabId);
// however, if the tab was swapped in by forward/backward buttons,
// `apply.js` doesn't notify the background to update the icon,
// so we have to refresh it manually.
if (transitionQualifiers.includes('forward_back')) {
msg.sendTab(tabId, {method: 'updateCount'}).catch(msg.ignoreError);
}
}
});
// *************************************************************************
chrome.runtime.onInstalled.addListener(({reason}) => {
// save install type: "admin", "development", "normal", "sideload" or "other"
@ -293,21 +247,6 @@ if (FIREFOX && browser.commands && browser.commands.update) {
msg.broadcastTab({method: 'backgroundReady'});
function webNavUsercssInstallerFF(data) {
const {tabId} = data;
Promise.all([
msg.sendTab(tabId, {method: 'ping'})
.catch(() => false),
// we need tab index to open the installer next to the original one
// and also to skip the double-invocation in FF which assigns tab url later
getTab(tabId),
]).then(([pong, tab]) => {
if (pong !== true && tab.url !== 'about:blank') {
window.API_METHODS.openUsercssInstallPage({direct: true}, {tab});
}
});
}
function webNavIframeHelperFF({tabId, frameId}) {
if (!frameId) return;
msg.sendTab(tabId, {method: 'ping'}, {frameId})
@ -326,75 +265,6 @@ function webNavIframeHelperFF({tabId, frameId}) {
});
}
function updateIconBadge(tabId, count) {
let tabIcon = tabIcons.get(tabId);
if (!tabIcon) tabIcons.set(tabId, (tabIcon = {}));
if (tabIcon.count === count) {
return;
}
const oldCount = tabIcon.count;
tabIcon.count = count;
refreshIconBadgeText(tabId, tabIcon);
if (Boolean(oldCount) !== Boolean(count)) {
refreshIcon(tabId, tabIcon);
}
}
function updateIconBadgeForce(tabId, count) {
refreshIconBadgeText(tabId, {count});
refreshIcon(tabId, {count});
}
function refreshIconBadgeText(tabId, icon) {
iconUtil.setBadgeText({
text: prefs.get('show-badge') && icon.count ? String(icon.count) : '',
tabId
});
}
function refreshIcon(tabId, icon) {
const disableAll = prefs.get('disableAll');
const iconset = prefs.get('iconset') === 1 ? 'light/' : '';
const postfix = disableAll ? 'x' : !icon.count ? 'w' : '';
const iconType = iconset + postfix;
if (icon.iconType === iconType) {
return;
}
icon.iconType = iconset + postfix;
const sizes = FIREFOX || CHROME >= 2883 && !VIVALDI ? [16, 32] : [19, 38];
iconUtil.setIcon({
path: sizes.reduce(
(obj, size) => {
obj[size] = `/images/icon/${iconset}${size}${postfix}.png`;
return obj;
},
{}
),
tabId
});
}
function refreshIconBadgeColor() {
const color = prefs.get(prefs.get('disableAll') ? 'badgeDisabled' : 'badgeNormal');
iconUtil.setBadgeBackgroundColor({
color
});
}
function refreshAllIcons() {
for (const [tabId, icon] of tabIcons) {
refreshIcon(tabId, icon);
}
refreshIcon(null, {}); // default icon
}
function refreshAllIconsBadgeText() {
for (const [tabId, icon] of tabIcons) {
refreshIconBadgeText(tabId, icon);
}
}
function onRuntimeMessage(msg, sender) {
if (msg.method !== 'invokeAPI') {
return;

101
background/icon-manager.js Normal file
View File

@ -0,0 +1,101 @@
/* global prefs debounce iconUtil FIREFOX CHROME VIVALDI tabManager */
/* exported iconManager */
'use strict';
const iconManager = (() => {
const ICON_SIZES = FIREFOX || CHROME >= 2883 && !VIVALDI ? [16, 32] : [19, 38];
prefs.subscribe([
'disableAll',
'badgeDisabled',
'badgeNormal',
], () => debounce(refreshIconBadgeColor));
prefs.subscribe([
'show-badge'
], () => debounce(refreshAllIconsBadgeText));
prefs.subscribe([
'disableAll',
'iconset',
], () => debounce(refreshAllIcons));
prefs.initializing.then(() => {
refreshIconBadgeColor();
refreshAllIconsBadgeText();
refreshAllIcons();
});
return {updateIconBadge};
// FIXME: in some cases, we only have to redraw the badge. is it worth a optimization?
function updateIconBadge(tabId, count, force = true) {
tabManager.set(tabId, 'count', count);
refreshIconBadgeText(tabId);
refreshIcon(tabId, force);
}
function refreshIconBadgeText(tabId) {
const count = tabManager.get(tabId, 'count');
iconUtil.setBadgeText({
text: prefs.get('show-badge') && count ? String(count) : '',
tabId
});
}
function getIconName(count = 0) {
const iconset = prefs.get('iconset') === 1 ? 'light/' : '';
const postfix = prefs.get('disableAll') ? 'x' : !count ? 'w' : '';
return `${iconset}$SIZE$${postfix}`;
}
function refreshIcon(tabId, force = false) {
const oldIcon = tabManager.get(tabId, 'icon');
const newIcon = getIconName(tabManager.get(tabId, 'count'));
if (!force && oldIcon === newIcon) {
return;
}
tabManager.set(tabId, 'icon', newIcon);
iconUtil.setIcon({
path: getIconPath(newIcon),
tabId
});
}
function getIconPath(icon) {
return ICON_SIZES.reduce(
(obj, size) => {
obj[size] = `/images/icon/${icon.replace('$SIZE$', size)}.png`;
return obj;
},
{}
);
}
function refreshGlobalIcon() {
iconUtil.setIcon({
path: getIconPath(getIconName())
});
}
function refreshIconBadgeColor() {
const color = prefs.get(prefs.get('disableAll') ? 'badgeDisabled' : 'badgeNormal');
iconUtil.setBadgeBackgroundColor({
color
});
}
function refreshAllIcons() {
for (const tabId of tabManager.list()) {
refreshIcon(tabId);
}
refreshGlobalIcon();
}
function refreshAllIconsBadgeText() {
for (const tabId of tabManager.list()) {
refreshIconBadgeText(tabId);
}
}
})();

View File

@ -1,4 +1,4 @@
/* global API_METHODS styleManager CHROME prefs updateIconBadge */
/* global API_METHODS styleManager CHROME prefs iconManager */
'use strict';
API_METHODS.styleViaAPI = !CHROME && (() => {
@ -36,7 +36,7 @@ API_METHODS.styleViaAPI = !CHROME && (() => {
throw new Error('we do not count styles for frames');
}
const {frameStyles} = getCachedData(tab.id, frameId);
updateIconBadge(tab.id, Object.keys(frameStyles).length);
iconManager.updateIconBadge(tab.id, Object.keys(frameStyles).length);
}
function styleApply({id = null, ignoreUrlCheck = false}, {tab, frameId, url}) {

43
background/tab-manager.js Normal file
View File

@ -0,0 +1,43 @@
/* global navigatorUtil */
/* exported tabManager */
'use strict';
const tabManager = (() => {
const listeners = [];
const cache = new Map();
chrome.tabs.onRemoved.addListener(tabId => cache.delete(tabId));
chrome.tabs.onReplaced.addListener((added, removed) => cache.delete(removed));
navigatorUtil.onUrlChange(({tabId, frameId, url}) => {
if (frameId) return;
const oldUrl = tabManager.get(tabId, 'url');
tabManager.set(tabId, 'url', url);
for (const fn of listeners) {
try {
fn({tabId, url, oldUrl});
} catch (err) {
console.error(err);
}
}
});
return {
onUpdate(fn) {
listeners.push(fn);
},
get(tabId, key) {
const meta = cache.get(tabId);
return meta && meta[key];
},
set(tabId, key, value) {
let meta = cache.get(tabId);
if (!meta) {
meta = {};
cache.set(tabId, meta);
}
meta[key] = value;
},
list() {
return cache.keys();
},
};
})();

View File

@ -1,42 +1,66 @@
/* global API_METHODS usercss chromeLocal styleManager FIREFOX deepCopy openURL
download */
/* global API_METHODS usercss styleManager deepCopy openURL download URLS getTab */
/* exports usercssHelper */
'use strict';
(() => {
// eslint-disable-next-line no-unused-vars
const usercssHelper = (() => {
const installCodeCache = {};
const clearInstallCode = url => delete installCodeCache[url];
const isResponseText = r => /^text\/(css|plain)(;.*?)?$/i.test(r.headers.get('content-type'));
// in Firefox we have to use a content script to read file://
const fileLoader = !chrome.app && // not relying on navigator.ua which can be spoofed
(tabId => browser.tabs.executeScript(tabId, {file: '/content/install-hook-usercss.js'}).then(r => r[0]));
API_METHODS.installUsercss = installUsercss;
API_METHODS.editSaveUsercss = editSaveUsercss;
API_METHODS.configUsercssVars = configUsercssVars;
API_METHODS.buildUsercss = build;
API_METHODS.openUsercssInstallPage = install;
API_METHODS.findUsercss = find;
const TEMP_CODE_PREFIX = 'tempUsercssCode';
const TEMP_CODE_CLEANUP_DELAY = 60e3;
let tempCodeLastWriteDate = 0;
if (FIREFOX) {
// the temp code is created on direct installation of usercss URLs in FF
// and can be left behind in case the install page didn't open in time before
// the extension was updated/reloaded/disabled or the browser was closed
setTimeout(function poll() {
if (Date.now() - tempCodeLastWriteDate < TEMP_CODE_CLEANUP_DELAY) {
setTimeout(poll, TEMP_CODE_CLEANUP_DELAY);
return;
API_METHODS.getUsercssInstallCode = url => {
// when the installer tab is reloaded after the cache is expired, this will throw intentionally
const {code, timer} = installCodeCache[url];
clearInstallCode(url);
clearTimeout(timer);
return code;
};
return {
testUrl(url) {
return url.includes('.user.') &&
/^(https?|file|ftps?):/.test(url) &&
/\.user\.(css|styl)$/.test(url.split(/[#?]/, 1)[0]);
},
/** @return {Promise<{ code:string, inTab:boolean } | false>} */
testContents(tabId, url) {
const isFile = url.startsWith('file:');
const inTab = isFile && Boolean(fileLoader);
return Promise.resolve(isFile || fetch(url, {method: 'HEAD'}).then(isResponseText))
.then(ok => ok && (inTab ? fileLoader(tabId) : download(url)))
.then(code => /==userstyle==/i.test(code) && {code, inTab});
},
openInstallerPage(tabId, url, {code, inTab} = {}) {
const newUrl = `${URLS.installUsercss}?updateUrl=${encodeURIComponent(url)}`;
if (inTab) {
getTab(tabId).then(tab =>
openURL({
url: `${newUrl}&tabId=${tabId}`,
active: tab.active,
index: tab.index + 1,
openerTabId: tabId,
currentWindow: null,
}));
} else {
const timer = setTimeout(clearInstallCode, 10e3, url);
installCodeCache[url] = {code, timer};
chrome.tabs.update(tabId, {url: newUrl});
}
chrome.storage.local.get(null, storage => {
const leftovers = [];
for (const key in storage) {
if (key.startsWith(TEMP_CODE_PREFIX)) {
leftovers.push(key);
}
}
if (leftovers.length) {
chrome.storage.local.remove(leftovers);
}
});
}, TEMP_CODE_CLEANUP_DELAY);
}
},
};
function buildMeta(style) {
if (style.usercssData) {
@ -156,33 +180,4 @@
}
});
}
function install({url, direct, downloaded, tab}, sender = this.sender) {
tab = tab !== undefined ? tab : sender.tab;
url = url || tab.url;
if (direct && !downloaded) {
prefetchCodeForInstallation(tab.id, url);
}
return openURL({
url: '/install-usercss.html' +
'?updateUrl=' + encodeURIComponent(url) +
'&tabId=' + tab.id +
(direct ? '&direct=yes' : ''),
index: tab.index + 1,
openerTabId: tab.id,
currentWindow: null,
});
}
function prefetchCodeForInstallation(tabId, url) {
const key = TEMP_CODE_PREFIX + tabId;
tempCodeLastWriteDate = Date.now();
Promise.all([
download(url),
chromeLocal.setValue(key, {loading: true}),
]).then(([code]) => {
chromeLocal.setValue(key, code);
setTimeout(() => chromeLocal.remove(key), TEMP_CODE_CLEANUP_DELAY);
});
}
})();

View File

@ -1,123 +1,22 @@
/* global API */
'use strict';
(() => {
// some weird bug in new Chrome: the content script gets injected multiple times
if (typeof window.initUsercssInstall === 'function') return;
if (!/text\/(css|plain)/.test(document.contentType) ||
!/==userstyle==/i.test(document.body.textContent)) {
return;
}
window.initUsercssInstall = () => {};
orphanCheck();
const DELAY = 500;
const url = location.href;
let sourceCode, port, timer;
chrome.runtime.onConnect.addListener(onConnected);
API.openUsercssInstallPage({url})
.catch(err => alert(err));
function onConnected(newPort) {
port = newPort;
port.onDisconnect.addListener(stop);
port.onMessage.addListener(onMessage);
}
function onMessage(msg, port) {
switch (msg.method) {
case 'getSourceCode':
fetchText(url)
.then(text => {
sourceCode = sourceCode || text;
port.postMessage({
method: msg.method + 'Response',
sourceCode,
});
})
.catch(err => port.postMessage({
method: msg.method + 'Response',
error: err.message || String(err),
}));
break;
case 'liveReloadStart':
start();
break;
case 'liveReloadStop':
stop();
break;
}
}
function fetchText(url) {
// XHR throws in Chrome 49
// FIXME: choose a correct version
// https://github.com/openstyles/stylus/issues/560
if (getChromeVersion() <= 49) {
return fetch(url)
// preventing reregistration if reinjected by tabs.executeScript for whatever reason, just in case
if (typeof self.oldCode !== 'string') {
self.oldCode = (document.querySelector('body > pre') || document.body).textContent;
chrome.runtime.onConnect.addListener(port => {
if (port.name !== 'downloadSelf') return;
port.onMessage.addListener(({id, timer}) => {
fetch(location.href, {mode: 'same-origin'})
.then(r => r.text())
.catch(() => fetchTextXHR(url));
}
return fetchTextXHR(url);
}
function fetchTextXHR(url) {
return new Promise((resolve, reject) => {
// you can't use fetch in Chrome under 'file:' protocol
const xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.addEventListener('load', () => resolve(xhr.responseText));
xhr.addEventListener('error', () => reject(xhr));
xhr.send();
.then(code => ({id, code: timer && code === self.oldCode ? null : code}))
.catch(error => ({id, error: error.message || `${error}`}))
.then(msg => {
port.postMessage(msg);
if (msg.code != null) self.oldCode = msg.code;
});
});
}
});
}
function getChromeVersion() {
const match = navigator.userAgent.match(/chrome\/(\d+)/i);
return match ? Number(match[1]) : undefined;
}
function start() {
timer = timer || setTimeout(check, DELAY);
}
function stop() {
clearTimeout(timer);
timer = null;
}
function check() {
fetchText(url)
.then(text => {
if (sourceCode === text) return;
sourceCode = text;
port.postMessage({method: 'sourceCodeChanged', sourceCode});
})
.catch(error => {
console.log(chrome.i18n.getMessage('liveReloadError', error));
})
.then(() => {
timer = null;
start();
});
}
function orphanCheck() {
const eventName = chrome.runtime.id + '-install-hook-usercss';
const orphanCheckRequest = () => {
if (chrome.i18n && chrome.i18n.getUILanguage()) return true;
// In Chrome content script is orphaned on an extension update/reload
// so we need to detach event listeners
removeEventListener(eventName, orphanCheckRequest, true);
try {
chrome.runtime.onConnect.removeListener(onConnected);
} catch (e) {}
};
dispatchEvent(new Event(eventName));
addEventListener(eventName, orphanCheckRequest, true);
}
})();
// passing the result to tabs.executeScript
self.oldCode; // eslint-disable-line no-unused-expressions

View File

@ -58,19 +58,17 @@
<div class="actions">
<h2 class="installed" i18n-text="installButtonInstalled"></h2>
<button class="install" i18n-text="installButton"></button>
<p id="live-reload-install-hint" i18n-text="liveReloadInstallHint" class="hidden"></p>
<p id="live-reload-install-hint" hidden></p>
<label class="set-update-url">
<input type="checkbox">
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
<span class="available-message" i18n-text="installUpdateFromLabel"></span>
<span class="unavailable-message" i18n-text="installUpdateUnavailable"></span>
<span i18n-text="installUpdateFromLabel"></span>
<p></p>
</label>
<label class="live-reload">
<input type="checkbox">
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
<span class="available-message" i18n-text="liveReloadLabel"></span>
<span class="unavailable-message" i18n-text="liveReloadUnavailable"></span>
<span i18n-text="liveReloadLabel"></span>
</label>
</div>
<p class="meta-description"></p>

View File

@ -246,18 +246,6 @@ h2.installed.active {
min-width: 0;
}
.unavailable-message,
.unavailable .available-message,
.unavailable .svg-icon,
.live-reload.unavailable input,
.set-update-url.unavailable input {
display: none;
}
.unavailable .unavailable-message {
display: block;
}
.set-update-url {
flex-wrap: wrap;
}
@ -317,7 +305,7 @@ li {
user-select: auto;
}
label:not(.unavailable) {
label {
padding-left: 16px;
position: relative;
}

View File

@ -1,46 +1,18 @@
/* global CodeMirror semverCompare closeCurrentTab messageBox download
$ $$ $create $createLink t prefs API getTab */
$ $$ $create $createLink t prefs API */
'use strict';
(() => {
const DUMMY_URL = 'foo:';
// TODO: remove .replace(/^\?/, '') when minimum_chrome_version >= 52 (https://crbug.com/601425)
const params = new URLSearchParams(location.search.replace(/^\?/, ''));
let liveReload = false;
const tabId = params.has('tabId') ? Number(params.get('tabId')) : -1;
const initialUrl = params.get('updateUrl');
let installed = null;
let installedDup = null;
const tabId = Number(params.get('tabId'));
let tabUrl;
let port;
if (params.has('direct')) {
setUnavailable('.live-reload');
getCodeDirectly();
} else {
port = chrome.tabs.connect(tabId);
port.postMessage({method: 'getSourceCode'});
port.onMessage.addListener(msg => {
switch (msg.method) {
case 'getSourceCodeResponse':
if (msg.error) {
messageBox.alert(msg.error, 'pre');
} else {
initSourceCode(msg.sourceCode);
}
break;
case 'sourceCodeChanged':
if (msg.error) {
messageBox.alert(msg.error, 'pre');
} else {
liveReloadUpdate(msg.sourceCode);
}
break;
}
});
port.onDisconnect.addListener(onPortDisconnected);
}
const liveReload = initLiveReload();
liveReload.ready.then(initSourceCode, error => messageBox.alert(error, 'pre'));
const theme = prefs.get('editor.theme');
const cm = CodeMirror($('.main'), {
@ -54,8 +26,13 @@
href: `vendor/codemirror/theme/${theme}.css`
}));
}
let liveReloadPending = Promise.resolve();
window.addEventListener('resize', adjustCodeHeight);
// "History back" in Firefox (for now) restores the old DOM including the messagebox,
// which stays after installing since we don't want to wait for the fadeout animation before resolving.
document.addEventListener('visibilitychange', () => {
if (messageBox.element) messageBox.element.remove();
if (installed) liveReload.onToggled();
});
setTimeout(() => {
if (!installed) {
@ -64,35 +41,6 @@
}
}, 200);
getTab(tabId).then(tab => (tabUrl = tab.url));
chrome.tabs.onUpdated.addListener((id, {url}) => {
if (id === tabId && url && url !== tabUrl) {
closeCurrentTab();
}
});
// close the tab in case the port didn't report onDisconnect
chrome.tabs.onRemoved.addListener(id => {
if (id === tabId) {
closeCurrentTab();
}
});
function liveReloadUpdate(sourceCode) {
liveReloadPending = liveReloadPending.then(() => {
const scrollInfo = cm.getScrollInfo();
const cursor = cm.getCursor();
cm.setValue(sourceCode);
cm.setCursor(cursor);
cm.scrollTo(scrollInfo.left, scrollInfo.top);
API.installUsercss({
id: (installed || installedDup).id,
sourceCode
}).then(style => {
updateMeta(style);
}).catch(showError);
});
}
function updateMeta(style, dup = installedDup) {
installedDup = dup;
@ -204,7 +152,7 @@
$$.remove('.warning');
$('button.install').disabled = true;
$('button.install').classList.add('installed');
$('#live-reload-install-hint').classList.toggle('hidden', !liveReload);
$('#live-reload-install-hint').classList.toggle('hidden', !liveReload.enabled);
$('h2.installed').classList.add('active');
$('.set-update-url input[type=checkbox]').disabled = true;
$('.set-update-url').title = style.updateUrl ?
@ -212,16 +160,18 @@
updateMeta(style);
if (!liveReload && !prefs.get('openEditInWindow')) {
chrome.tabs.update({url: '/edit.html?id=' + style.id});
if (!liveReload.enabled && !prefs.get('openEditInWindow')) {
location.href = '/edit.html?id=' + style.id;
} else {
API.openEditor({id: style.id});
if (!liveReload) {
closeCurrentTab();
if (!liveReload.enabled) {
if (tabId < 0 && history.length > 1) {
history.back();
} else {
closeCurrentTab();
}
}
}
window.dispatchEvent(new CustomEvent('installed'));
}
function initSourceCode(sourceCode) {
@ -307,17 +257,11 @@
// set updateUrl
const checker = $('.set-update-url input[type=checkbox]');
// only use the installation URL if not specified in usercss
const installationUrl = (params.get('updateUrl') || '').replace(/^blob.+/, '');
const updateUrl = new URL(style.updateUrl || installationUrl || DUMMY_URL);
const updateUrl = new URL(style.updateUrl || initialUrl);
if (dup && dup.updateUrl === updateUrl.href) {
checker.checked = true;
// there is no way to "unset" updateUrl, you can only overwrite it.
checker.disabled = true;
} else if (updateUrl.href === DUMMY_URL) {
// drag'n'dropped on the manage page and the style doesn't have @updateURL
setUnavailable('.set-update-url');
return;
} else if (updateUrl.protocol !== 'file:') {
checker.checked = true;
style.updateUrl = updateUrl.href;
@ -329,40 +273,13 @@
$('.set-update-url p').textContent = updateUrl.href.length < 300 ? updateUrl.href :
updateUrl.href.slice(0, 300) + '...';
if (!port) {
return;
}
// live reload
const setLiveReload = $('.live-reload input[type=checkbox]');
if (!installationUrl || !installationUrl.startsWith('file:')) {
setLiveReload.parentNode.remove();
if (initialUrl.startsWith('file:')) {
$('.live-reload input').onchange = liveReload.onToggled;
} else {
setLiveReload.addEventListener('change', () => {
liveReload = setLiveReload.checked;
if (installed || installedDup) {
const method = 'liveReload' + (liveReload ? 'Start' : 'Stop');
port.postMessage({method});
$('.install').disabled = liveReload;
$('#live-reload-install-hint').classList.toggle('hidden', !liveReload);
}
});
window.addEventListener('installed', () => {
if (liveReload) {
port.postMessage({method: 'liveReloadStart'});
}
});
$('.live-reload').remove();
}
}
function setUnavailable(label) {
const el = $(label);
el.classList.add('unavailable');
const input = $('input', el);
input.disabled = true;
input.checked = false;
}
function getAppliesTo(style) {
function *_gen() {
for (const section of style.sections) {
@ -391,47 +308,105 @@
}
}
function getCodeDirectly() {
// FF applies page CSP even to content scripts, https://bugzil.la/1267027
// To circumvent that, the bg process downloads the code directly
const key = 'tempUsercssCode' + tabId;
chrome.storage.local.get(key, data => {
const code = data && data[key];
// bg already downloaded the code
if (typeof code === 'string') {
initSourceCode(code);
chrome.storage.local.remove(key);
return;
}
// bg still downloads the code
if (code && code.loading) {
const waitForCodeInStorage = (changes, area) => {
if (area === 'local' && key in changes) {
initSourceCode(changes[key].newValue);
chrome.storage.onChanged.removeListener(waitForCodeInStorage);
chrome.storage.local.remove(key);
function initLiveReload() {
const DELAY = 500;
let isEnabled = false;
let timer = 0;
/** @type function(?options):Promise<string|null> */
let getData = null;
/** @type Promise */
let sequence = null;
if (tabId < 0) {
getData = DirectDownloader();
sequence = API.getUsercssInstallCode(initialUrl).catch(getData);
} else {
getData = PortDownloader();
sequence = getData({timer: false});
}
return {
get enabled() {
return isEnabled;
},
ready: sequence,
onToggled(e) {
if (e) isEnabled = e.target.checked;
if (installed || installedDup) {
(isEnabled ? start : stop)();
$('.install').disabled = isEnabled;
Object.assign($('#live-reload-install-hint'), {
hidden: !isEnabled,
textContent: t(`liveReloadInstallHint${tabId >= 0 ? 'FF' : ''}`),
});
}
},
};
function check() {
getData()
.then(update, logError)
.then(() => {
timer = 0;
start();
});
}
function logError(error) {
console.warn(t('liveReloadError', error));
}
function start() {
timer = timer || setTimeout(check, DELAY);
}
function stop() {
clearTimeout(timer);
timer = 0;
}
function update(code) {
if (code == null) return;
sequence = sequence.catch(console.error).then(() => {
const {id} = installed || installedDup;
const scrollInfo = cm.getScrollInfo();
const cursor = cm.getCursor();
cm.setValue(code);
cm.setCursor(cursor);
cm.scrollTo(scrollInfo.left, scrollInfo.top);
return API.installUsercss({id, sourceCode: code})
.then(updateMeta)
.catch(showError);
});
}
function DirectDownloader() {
let oldCode = null;
const passChangedCode = code => {
const isSame = code === oldCode;
oldCode = code;
return isSame ? null : code;
};
return () => download(initialUrl).then(passChangedCode);
}
function PortDownloader() {
const resolvers = new Map();
const port = chrome.tabs.connect(tabId, {name: 'downloadSelf'});
port.onMessage.addListener(({id, code, error}) => {
const r = resolvers.get(id);
resolvers.delete(id);
if (error) {
r.reject(error);
} else {
r.resolve(code);
}
});
port.onDisconnect.addListener(() => {
chrome.tabs.get(tabId, tab => {
if (chrome.runtime.lastError) {
closeCurrentTab();
} else if (tab.url === initialUrl) {
location.reload();
}
};
chrome.storage.onChanged.addListener(waitForCodeInStorage);
return;
}
// on the off-chance dbExecChromeStorage.getAll ran right after bg download was saved
download(params.get('updateUrl'))
.then(initSourceCode)
.catch(err => messageBox.alert(t('styleInstallFailed', String(err)), 'pre'));
});
}
function onPortDisconnected() {
chrome.tabs.get(tabId, tab => {
if (chrome.runtime.lastError) {
closeCurrentTab();
} else if (tab.url === tabUrl) {
location.reload();
}
});
});
});
return ({timer = true} = {}) => new Promise((resolve, reject) => {
const id = performance.now();
resolvers.set(id, {resolve, reject});
port.postMessage({id, timer});
});
}
}
})();

View File

@ -39,6 +39,8 @@ const URLS = {
OPERA ? 'opera://settings/configureCommands'
: 'chrome://extensions/configureCommands',
installUsercss: chrome.runtime.getURL('install-usercss.html'),
// CWS cannot be scripted in chromium, see ChromeExtensionsClient::IsScriptableURL
// https://cs.chromium.org/chromium/src/chrome/common/extensions/chrome_extensions_client.cc
browserWebStore:

View File

@ -1,4 +1,4 @@
/* global messageBox styleSectionsEqual getOwnTab API onDOMready
/* global messageBox styleSectionsEqual API onDOMready
tryJSONparse scrollElementIntoView $ $$ API $create t animateElement
styleJSONseemsValid */
'use strict';
@ -87,14 +87,11 @@ function importFromFile({fileTypeFilter, file} = {}) {
const text = event.target.result;
const maybeUsercss = !/^[\s\r\n]*\[/.test(text) &&
(text.includes('==UserStyle==') || /==UserStyle==/i.test(text));
(!maybeUsercss ?
importFromString(text) :
getOwnTab().then(tab => {
tab.url = URL.createObjectURL(new Blob([text], {type: 'text/css'}));
return API.openUsercssInstallPage({direct: true, tab})
.then(() => URL.revokeObjectURL(tab.url));
})
).then(numStyles => {
if (maybeUsercss) {
messageBox.alert(t('dragDropUsercssTabstrip'));
return;
}
importFromString(text).then(numStyles => {
document.body.style.cursor = '';
resolve(numStyles);
});

View File

@ -46,6 +46,8 @@
"background/style-manager.js",
"background/navigator-util.js",
"background/icon-util.js",
"background/tab-manager.js",
"background/icon-manager.js",
"background/background.js",
"background/usercss-helper.js",
"background/style-via-api.js",
@ -92,30 +94,6 @@
"run_at": "document_start",
"all_frames": false,
"js": ["content/install-hook-openusercss.js"]
},
{
"matches": [
"*://*/*.user.css",
"*://*/*.user.styl",
"file://*/*.user.css",
"file://*/*.user.styl",
"ftp://*/*.user.css",
"ftp://*/*.user.styl",
"*://*/*.user.css?*",
"*://*/*.user.styl?*",
"file://*/*.user.css?*",
"file://*/*.user.styl?*",
"ftp://*/*.user.css?*",
"ftp://*/*.user.styl?*"
],
"run_at": "document_idle",
"all_frames": false,
"js": ["content/install-hook-usercss.js"]
}
],
"browser_action": {

View File

@ -34,8 +34,8 @@
"update-transifex": "tx push -s",
"build-vendor": "shx rm -rf vendor/* && node tools/build-vendor",
"zip": "node tools/zip.js",
"start": "web-ext run --bc",
"start-chrome": "web-ext run -t chromium --bc",
"start": "web-ext run",
"start-chrome": "web-ext run -t chromium",
"preversion": "npm test",
"version": "sync-version manifest.json && git add .",
"postversion": "npm run zip && git push --follow-tags"