From 4bbce7cb9f8a0469e4764d9b51c365dce799826d Mon Sep 17 00:00:00 2001 From: tophf Date: Sun, 23 Feb 2020 18:43:26 +0300 Subject: [PATCH] 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 --- _locales/bg_BG/messages.json | 14 +- _locales/cs/messages.json | 6 +- _locales/de/messages.json | 14 +- _locales/en/messages.json | 16 +- _locales/en_GB/messages.json | 6 +- _locales/es/messages.json | 14 +- _locales/et/messages.json | 14 +- _locales/fr/messages.json | 14 +- _locales/he/messages.json | 10 +- _locales/hu/messages.json | 6 +- _locales/ja/messages.json | 14 +- _locales/nl/messages.json | 14 +- _locales/pl/messages.json | 14 +- _locales/pt_PT/messages.json | 14 +- _locales/ro/messages.json | 14 +- _locales/ru/messages.json | 14 +- _locales/sv/messages.json | 14 +- _locales/zh_CN/messages.json | 14 +- _locales/zh_TW/messages.json | 14 +- background/background.js | 166 ++--------------- background/icon-manager.js | 101 ++++++++++ background/style-via-api.js | 4 +- background/tab-manager.js | 43 +++++ background/usercss-helper.js | 111 ++++++----- content/install-hook-usercss.js | 135 ++------------ install-usercss.html | 8 +- install-usercss/install-usercss.css | 14 +- install-usercss/install-usercss.js | 273 +++++++++++++--------------- js/messaging.js | 2 + manage/import-export.js | 15 +- manifest.json | 26 +-- package.json | 4 +- 32 files changed, 401 insertions(+), 741 deletions(-) create mode 100644 background/icon-manager.js create mode 100644 background/tab-manager.js diff --git a/_locales/bg_BG/messages.json b/_locales/bg_BG/messages.json index 1c50a3da..103246fa 100644 --- a/_locales/bg_BG/messages.json +++ b/_locales/bg_BG/messages.json @@ -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" @@ -369,4 +357,4 @@ "message": "Само Потребителскиcss стилове", "description": "Checkbox to show only Usercss styles" } -} \ No newline at end of file +} diff --git a/_locales/cs/messages.json b/_locales/cs/messages.json index 5fb14ad4..ad840a21 100644 --- a/_locales/cs/messages.json +++ b/_locales/cs/messages.json @@ -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" @@ -1310,4 +1306,4 @@ "message": "Nahrávání souboru…", "description": "" } -} \ No newline at end of file +} diff --git a/_locales/de/messages.json b/_locales/de/messages.json index 3a1e2fb2..3a0b486f 100644 --- a/_locales/de/messages.json +++ b/_locales/de/messages.json @@ -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" @@ -1596,4 +1584,4 @@ "message": "Lade Styles hoch...", "description": "" } -} \ No newline at end of file +} diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 55098a94..73b20638 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -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" diff --git a/_locales/en_GB/messages.json b/_locales/en_GB/messages.json index d3aaf68e..7a783bc2 100644 --- a/_locales/en_GB/messages.json +++ b/_locales/en_GB/messages.json @@ -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" @@ -84,4 +80,4 @@ "message": "The style was updated or deleted after the configuration dialogue was shown. These variables were not saved to avoid corrupting the style's metadata:", "description": "" } -} \ No newline at end of file +} diff --git a/_locales/es/messages.json b/_locales/es/messages.json index 6132ed9e..8445c761 100644 --- a/_locales/es/messages.json +++ b/_locales/es/messages.json @@ -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" @@ -1520,4 +1508,4 @@ "message": "Subiendo el archivo....", "description": "" } -} \ No newline at end of file +} diff --git a/_locales/et/messages.json b/_locales/et/messages.json index 1cc8bc29..fe40b705 100644 --- a/_locales/et/messages.json +++ b/_locales/et/messages.json @@ -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" @@ -1494,4 +1482,4 @@ "message": "Faili üleslaadimine...", "description": "" } -} \ No newline at end of file +} diff --git a/_locales/fr/messages.json b/_locales/fr/messages.json index f7e61a1c..8675e535 100644 --- a/_locales/fr/messages.json +++ b/_locales/fr/messages.json @@ -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 l’onglet 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 « s’applique à »", "description": "Label for the checkbox that toggles applies-to favicons in the new UI on manage page" @@ -1528,4 +1516,4 @@ "message": "Envoi du fichier…", "description": "" } -} \ No newline at end of file +} diff --git a/_locales/he/messages.json b/_locales/he/messages.json index f733997d..9d6b4448 100644 --- a/_locales/he/messages.json +++ b/_locales/he/messages.json @@ -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" @@ -961,4 +953,4 @@ "message": "הקישור הנוכחי", "description": "Text for link in toolbar pop-up to write a new style for the current URL" } -} \ No newline at end of file +} diff --git a/_locales/hu/messages.json b/_locales/hu/messages.json index c81db59d..00c53ead 100644 --- a/_locales/hu/messages.json +++ b/_locales/hu/messages.json @@ -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" @@ -1608,4 +1604,4 @@ "message": "Fájl feltöltése...", "description": "" } -} \ No newline at end of file +} diff --git a/_locales/ja/messages.json b/_locales/ja/messages.json index 07c52d1e..68676092 100644 --- a/_locales/ja/messages.json +++ b/_locales/ja/messages.json @@ -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" @@ -1632,4 +1620,4 @@ "message": "スタイルをアップロード中...", "description": "" } -} \ No newline at end of file +} diff --git a/_locales/nl/messages.json b/_locales/nl/messages.json index 0ca7c49b..0f45d229 100644 --- a/_locales/nl/messages.json +++ b/_locales/nl/messages.json @@ -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" @@ -1616,4 +1604,4 @@ "message": "Bestand uploaden...", "description": "" } -} \ No newline at end of file +} diff --git a/_locales/pl/messages.json b/_locales/pl/messages.json index 32a7e6eb..a7805659 100644 --- a/_locales/pl/messages.json +++ b/_locales/pl/messages.json @@ -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" @@ -1640,4 +1628,4 @@ "message": "Wysyłanie stylów...", "description": "" } -} \ No newline at end of file +} diff --git a/_locales/pt_PT/messages.json b/_locales/pt_PT/messages.json index 934ec925..fdf5c29c 100644 --- a/_locales/pt_PT/messages.json +++ b/_locales/pt_PT/messages.json @@ -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" @@ -1236,4 +1224,4 @@ "message": "este URL", "description": "Text for link in toolbar pop-up to write a new style for the current URL" } -} \ No newline at end of file +} diff --git a/_locales/ro/messages.json b/_locales/ro/messages.json index 18215336..0913abec 100644 --- a/_locales/ro/messages.json +++ b/_locales/ro/messages.json @@ -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" @@ -1152,4 +1140,4 @@ "message": "acest URL", "description": "Text for link in toolbar pop-up to write a new style for the current URL" } -} \ No newline at end of file +} diff --git a/_locales/ru/messages.json b/_locales/ru/messages.json index 7bc65b0b..cf3c8b5b 100644 --- a/_locales/ru/messages.json +++ b/_locales/ru/messages.json @@ -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" @@ -1552,4 +1540,4 @@ "message": "Загрузка файла...", "description": "" } -} \ No newline at end of file +} diff --git a/_locales/sv/messages.json b/_locales/sv/messages.json index fa9fb0cf..276572c0 100644 --- a/_locales/sv/messages.json +++ b/_locales/sv/messages.json @@ -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" @@ -1534,4 +1522,4 @@ "message": "Skickar filen...", "description": "" } -} \ No newline at end of file +} diff --git a/_locales/zh_CN/messages.json b/_locales/zh_CN/messages.json index 8d8f9b30..e080586e 100644 --- a/_locales/zh_CN/messages.json +++ b/_locales/zh_CN/messages.json @@ -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" @@ -1548,4 +1536,4 @@ "message": "正在上传文件...", "description": "" } -} \ No newline at end of file +} diff --git a/_locales/zh_TW/messages.json b/_locales/zh_TW/messages.json index eebab00a..13a17c63 100644 --- a/_locales/zh_TW/messages.json +++ b/_locales/zh_TW/messages.json @@ -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" @@ -1640,4 +1628,4 @@ "message": "正在上傳檔案……", "description": "" } -} \ No newline at end of file +} diff --git a/background/background.js b/background/background.js index 8d608971..6cfc6f07 100644 --- a/background/background.js +++ b/background/background.js @@ -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; diff --git a/background/icon-manager.js b/background/icon-manager.js new file mode 100644 index 00000000..71a8f29d --- /dev/null +++ b/background/icon-manager.js @@ -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); + } + } +})(); diff --git a/background/style-via-api.js b/background/style-via-api.js index 79f5c289..12eecfc8 100644 --- a/background/style-via-api.js +++ b/background/style-via-api.js @@ -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}) { diff --git a/background/tab-manager.js b/background/tab-manager.js new file mode 100644 index 00000000..bcd7901a --- /dev/null +++ b/background/tab-manager.js @@ -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(); + }, + }; +})(); diff --git a/background/usercss-helper.js b/background/usercss-helper.js index 546f1172..ea083ae9 100644 --- a/background/usercss-helper.js +++ b/background/usercss-helper.js @@ -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); - }); - } })(); diff --git a/content/install-hook-usercss.js b/content/install-hook-usercss.js index f52d5a0f..80e837ea 100644 --- a/content/install-hook-usercss.js +++ b/content/install-hook-usercss.js @@ -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 diff --git a/install-usercss.html b/install-usercss.html index c6a00b2c..d168a346 100644 --- a/install-usercss.html +++ b/install-usercss.html @@ -58,19 +58,17 @@

- +

diff --git a/install-usercss/install-usercss.css b/install-usercss/install-usercss.css index a86ef582..88d47bd4 100644 --- a/install-usercss/install-usercss.css +++ b/install-usercss/install-usercss.css @@ -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; } diff --git a/install-usercss/install-usercss.js b/install-usercss/install-usercss.js index afbd6419..c4417571 100644 --- a/install-usercss/install-usercss.js +++ b/install-usercss/install-usercss.js @@ -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 */ + 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}); + }); + } } })(); diff --git a/js/messaging.js b/js/messaging.js index 6efd992c..e0b5e49d 100644 --- a/js/messaging.js +++ b/js/messaging.js @@ -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: diff --git a/manage/import-export.js b/manage/import-export.js index 3815de8d..18abd480 100644 --- a/manage/import-export.js +++ b/manage/import-export.js @@ -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); }); diff --git a/manifest.json b/manifest.json index 7d2e4fe3..b411d1b8 100644 --- a/manifest.json +++ b/manifest.json @@ -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": { diff --git a/package.json b/package.json index 52df771e..c17d875f 100644 --- a/package.json +++ b/package.json @@ -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"