fix and simplify .user.css URL installer

This commit is contained in:
tophf 2020-02-17 11:26:58 +03:00
parent c3b7657433
commit bef87f200e
30 changed files with 246 additions and 636 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"
@ -369,4 +357,4 @@
"message": "Само Потребителскиcss стилове",
"description": "Checkbox to show only Usercss styles"
}
}
}

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"
@ -1310,4 +1306,4 @@
"message": "Nahrávání souboru…",
"description": ""
}
}
}

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"
@ -1596,4 +1584,4 @@
"message": "Lade Styles hoch...",
"description": ""
}
}
}

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 installed style on external changes.",
"description": "The label of live-reload feature"
},
"liveReloadInstallHintFF": {
"message": "Keep the original tab open too as it's needed for local file:// URLs in Firefox 68 and newer.",
"description": "The extra hint of live-reload feature shown only for file:// URLs in Firefox 68+"
},
"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"
@ -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": ""
}
}
}

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"
@ -1520,4 +1508,4 @@
"message": "Subiendo el archivo....",
"description": ""
}
}
}

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"
@ -1494,4 +1482,4 @@
"message": "Faili üleslaadimine...",
"description": ""
}
}
}

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"
@ -1528,4 +1516,4 @@
"message": "Envoi du fichier…",
"description": ""
}
}
}

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"
@ -961,4 +953,4 @@
"message": "הקישור הנוכחי",
"description": "Text for link in toolbar pop-up to write a new style for the current URL"
}
}
}

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"
@ -1608,4 +1604,4 @@
"message": "Fájl feltöltése...",
"description": ""
}
}
}

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"
@ -1632,4 +1620,4 @@
"message": "スタイルをアップロード中...",
"description": ""
}
}
}

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"
@ -1616,4 +1604,4 @@
"message": "Bestand uploaden...",
"description": ""
}
}
}

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"
@ -1640,4 +1628,4 @@
"message": "Wysyłanie stylów...",
"description": ""
}
}
}

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"
@ -1236,4 +1224,4 @@
"message": "este URL",
"description": "Text for link in toolbar pop-up to write a new style for the current URL"
}
}
}

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"
@ -1152,4 +1140,4 @@
"message": "acest URL",
"description": "Text for link in toolbar pop-up to write a new style for the current URL"
}
}
}

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"
@ -1552,4 +1540,4 @@
"message": "Загрузка файла...",
"description": ""
}
}
}

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"
@ -1534,4 +1522,4 @@
"message": "Skickar filen...",
"description": ""
}
}
}

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"
@ -1548,4 +1536,4 @@
"message": "正在上传文件...",
"description": ""
}
}
}

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"
@ -1640,4 +1628,4 @@
"message": "正在上傳檔案……",
"description": ""
}
}
}

View File

@ -1,5 +1,5 @@
/* global download prefs openURL FIREFOX CHROME VIVALDI
debounce URLS ignoreChromeError getTab
debounce URLS ignoreChromeError usercssHelper
styleManager msg navigatorUtil iconUtil workerUtil contentScripts sync
findExistingTab createTab activateTab isTabReplaceable getActiveTab */
@ -87,23 +87,7 @@ var browserCommands, contextMenus;
// register all listeners
msg.on(onRuntimeMessage);
navigatorUtil.onUrlChange(({tabId, frameId}, type) => {
if (type === 'committed') {
// styles would be updated when content script is injected.
return;
}
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,9 +106,14 @@ 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));
const tabData = new Map();
const tabDataFor = tabId => {
let data = tabData.get(tabId);
if (!data) tabData.set(tabId, (data = {}));
return data;
};
chrome.tabs.onRemoved.addListener(tabId => tabData.delete(tabId));
chrome.tabs.onReplaced.addListener((added, removed) => tabData.delete(removed));
prefs.subscribe([
'disableAll',
@ -147,12 +136,19 @@ prefs.initializing.then(() => {
refreshAllIcons();
});
navigatorUtil.onUrlChange(({tabId, frameId, transitionQualifiers}, type) => {
if (type === 'committed' && !frameId) {
navigatorUtil.onUrlChange(({tabId, frameId, transitionQualifiers, url}, type) => {
if (type !== 'committed') {
msg.sendTab(tabId, {method: 'urlChanged'}, {frameId})
.catch(msg.ignoreError);
} else if (!frameId) {
if (usercssHelper.testUrl(url) && !`${tabDataFor(tabId).url}`.startsWith(URLS.installUsercss)) {
usercssHelper.openInstallerPage(tabId, url).then(newUrl => {
tabDataFor(tabId).url = newUrl || url;
});
}
// 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);
tabData.set(tabId, {url});
// 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.
@ -293,21 +289,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})
@ -327,8 +308,7 @@ function webNavIframeHelperFF({tabId, frameId}) {
}
function updateIconBadge(tabId, count) {
let tabIcon = tabIcons.get(tabId);
if (!tabIcon) tabIcons.set(tabId, (tabIcon = {}));
const tabIcon = tabDataFor(tabId);
if (tabIcon.count === count) {
return;
}
@ -383,15 +363,15 @@ function refreshIconBadgeColor() {
}
function refreshAllIcons() {
for (const [tabId, icon] of tabIcons) {
refreshIcon(tabId, icon);
for (const [tabId, data] of tabData) {
refreshIcon(tabId, data);
}
refreshIcon(null, {}); // default icon
}
function refreshAllIconsBadgeText() {
for (const [tabId, icon] of tabIcons) {
refreshIconBadgeText(tabId, icon);
for (const [tabId, data] of tabData) {
refreshIconBadgeText(tabId, data);
}
}

View File

@ -1,42 +1,65 @@
/* global API_METHODS usercss chromeLocal styleManager FIREFOX deepCopy openURL
download */
/* global API_METHODS usercss styleManager deepCopy openURL download URLS getTab promisify */
/* exports usercssHelper */
'use strict';
(() => {
// eslint-disable-next-line no-unused-vars
const usercssHelper = (() => {
// detecting FF68 by the added feature as navigator.ua may be spoofed via about:config or devtools
const tabExec = !chrome.app && chrome.storage.managed && promisify(chrome.tabs.executeScript.bind(chrome.tabs));
const downloadSelf = tabExec && {file: '/content/download-self.js'};
const installCodeCache = new Map();
const clearInstallCode = url => installCodeCache.delete(url);
const isPlainCssResponse = r => /^text\/(css|plain)(;.*?)?$/i.test(r.headers.get('content-type'));
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;
}
chrome.storage.local.get(null, storage => {
const leftovers = [];
for (const key in storage) {
if (key.startsWith(TEMP_CODE_PREFIX)) {
leftovers.push(key);
API_METHODS.getUsercssInstallCode = url => {
const {code, timer} = installCodeCache.get(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]);
},
openInstallerPage(tabId, url) {
const isFile = url.startsWith('file:');
const isFileFF = isFile && tabExec;
return Promise.resolve(isFile || fetch(url, {method: 'HEAD'}).then(isPlainCssResponse))
.then(ok => ok && (isFileFF ? tabExec(tabId, downloadSelf) : download(url)))
.then(code => {
if (Array.isArray(code)) code = code[0];
if (!/==userstyle==/i.test(code)) return;
const newUrl = `${URLS.installUsercss}?updateUrl=${encodeURIComponent(url)}`;
if (isFileFF) {
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.set(url, {code, timer});
chrome.tabs.update(tabId, {url: newUrl});
return newUrl;
}
}
if (leftovers.length) {
chrome.storage.local.remove(leftovers);
}
});
}, TEMP_CODE_CLEANUP_DELAY);
}
});
},
};
function buildMeta(style) {
if (style.usercssData) {
@ -156,33 +179,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);
});
}
})();

23
content/download-self.js Normal file
View File

@ -0,0 +1,23 @@
'use strict';
// preventing reinjection by tabs.executeScript, just in case
typeof self.oldCode !== 'string' && // eslint-disable-line no-unused-expressions
chrome.runtime.onConnect.addListener(port => {
if (port.name !== 'downloadSelf') return;
const read = r => r.status === 200 ? r.text() : Promise.reject(r.status);
const wrapError = error => ({error});
const postBack = msg => {
port.postMessage(msg);
self.oldCode = msg.code;
};
port.onMessage.addListener(cmd => {
const oldCode = cmd === 'timer' ? self.oldCode : '';
fetch(location.href, {mode: 'same-origin'})
.then(read)
.then(code => ({code: code === oldCode ? '' : code}), wrapError)
.then(postBack);
});
});
// this assignment also passes the result to tabs.executeScript
self.oldCode = (document.querySelector('body > pre') || document.body).textContent;

View File

@ -1,123 +0,0 @@
/* 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)
.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();
});
}
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);
}
})();

View File

@ -58,19 +58,18 @@
<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" i18n-text="liveReloadInstallHint" hidden></p>
<p id="live-reload-install-hint-ff" i18n-text="liveReloadInstallHintFF" 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,25 @@
/* 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');
if (!initialUrl) throw 'No updateUrl parameter';
let installed = null;
let installedDup = null;
let initialized = false;
let filePort;
const tabId = Number(params.get('tabId'));
let tabUrl;
let port;
const liveReload = initLiveReload();
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);
}
// when this tab is reloaded, bg may no longer have the code as it's kept only for a few seconds
API.getUsercssInstallCode(initialUrl)
.then(code => code || !filePort && download(initialUrl))
.then(code => (code || !filePort) && initSourceCode(code));
const theme = prefs.get('editor.theme');
const cm = CodeMirror($('.main'), {
@ -54,8 +33,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 +48,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 +159,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 +167,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 (!filePort && history.length > 1) {
history.back();
} else {
closeCurrentTab();
}
}
}
window.dispatchEvent(new CustomEvent('installed'));
}
function initSourceCode(sourceCode) {
@ -277,6 +234,8 @@
}
function init({style, dup}) {
initialized = true;
const data = style.usercssData;
const dupData = dup && dup.usercssData;
const versionTest = dup && semverCompare(data.version, dupData.version);
@ -307,17 +266,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 +282,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 +317,80 @@
}
}
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') {
function initLiveReload() {
const DELAY = 500;
let isEnabled = false;
let timer = 0;
let sequence = Promise.resolve();
if (tabId >= 0) {
filePort = chrome.tabs.connect(tabId, {name: 'downloadSelf'});
filePort.postMessage('init');
filePort.onMessage.addListener(onPortMessage);
filePort.onDisconnect.addListener(onPortDisconnect);
}
return {
get enabled() {
return isEnabled;
},
onToggled(e) {
if (e) isEnabled = e.target.checked;
if (installed || installedDup) {
(isEnabled ? start : stop)();
$('.install').disabled = isEnabled;
$('#live-reload-install-hint').hidden = !isEnabled;
$('#live-reload-install-hint-ff').hidden = !isEnabled || !filePort;
}
},
};
function onPortMessage({code, error}) {
if (error) {
messageBox.alert(error, 'pre');
} else if (!initialized) {
initSourceCode(code);
chrome.storage.local.remove(key);
return;
} else {
update(code);
}
// 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);
}
};
chrome.storage.onChanged.addListener(waitForCodeInStorage);
return;
}
function onPortDisconnect() {
chrome.tabs.get(tabId, tab => {
if (chrome.runtime.lastError) {
closeCurrentTab();
} else if (tab.url === initialUrl) {
location.reload();
}
});
}
function check() {
start(true);
if (filePort) {
filePort.postMessage('timer');
} else {
download(initialUrl).then(update, logError);
}
// 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();
}
});
}
function logError(error) {
console.warn(t('liveReloadError', error));
}
function start(reset) {
timer = !reset && timer || setTimeout(check, DELAY);
}
function stop() {
clearTimeout(check);
timer = 0;
}
function update(code) {
if (!code) return logError('EMPTY');
sequence = sequence.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);
});
}
}
})();

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

@ -242,6 +242,7 @@ self.API = self.INJECTED === 1 ? self.API : new Proxy({
// Handlers for these methods need sender.tab.id which is set by `send` as it uses messaging,
// unlike `sendBg` which invokes the background page directly in our own extension tabs
getTabUrlPrefix: true,
getUsercssInstallCode: true,
updateIconBadge: true,
styleViaAPI: true,
}, {

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

@ -92,30 +92,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": {