diff options
-rw-r--r-- | .github/workflows/ci.yml | 3 | ||||
-rw-r--r-- | .github/workflows/publish-firefox-development.yml | 3 | ||||
-rw-r--r-- | .vscode/settings.json | 3 | ||||
-rw-r--r-- | CONTRIBUTING.md | 48 | ||||
-rw-r--r-- | README.md | 348 | ||||
-rw-r--r-- | SECURITY.md | 5 | ||||
-rw-r--r-- | dev/data/manifest-variants.json | 38 | ||||
-rw-r--r-- | docs/permissions.md | 9 | ||||
-rw-r--r-- | ext/css/structured-content.css | 2 | ||||
-rw-r--r-- | ext/js/background/backend.js | 80 | ||||
-rw-r--r-- | ext/js/background/offscreen-main.js | 25 | ||||
-rw-r--r-- | ext/js/background/offscreen.js | 74 | ||||
-rw-r--r-- | ext/js/display/search-display-controller.js | 2 | ||||
-rw-r--r-- | ext/offscreen.html | 39 |
14 files changed, 467 insertions, 212 deletions
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4a767541..0254cbaa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -45,7 +45,8 @@ jobs: run: npm run build - name: Validate manifest.json of the extension - uses: cardinalby/schema-validator-action@c2da05377e89dd0c9b7be9420da0b3534b1efcce # pin@v1 + uses: cardinalby/schema-validator-action@76c68bfc941bd2dc82859f2528984999d1df36a4 # v3.1.0 with: file: ext/manifest.json schema: "https://json.schemastore.org/chrome-manifest.json" + fixSchemas: true diff --git a/.github/workflows/publish-firefox-development.yml b/.github/workflows/publish-firefox-development.yml index 32b0532a..c956b76c 100644 --- a/.github/workflows/publish-firefox-development.yml +++ b/.github/workflows/publish-firefox-development.yml @@ -75,7 +75,7 @@ jobs: "updates": [ { "version": "${{ github.event.release.name }}", - "update_link": "${{ steps.uploadReleaseAsset.browser_download_url }}" + "update_link": "${{ steps.uploadReleaseAsset.outputs.browser_download_url }}" } ] } @@ -84,6 +84,7 @@ jobs: EOF - name: Commit files + continue-on-error: true run: | git config --local user.email "github-actions[bot]@users.noreply.github.com" git config --local user.name "github-actions[bot]" diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..903c74ae --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "markdown.extension.toc.levels": "1..3" +}
\ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0b0d722c..2b19e68e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,22 +2,22 @@ Issues reported on [GitHub](https://github.com/themoeway/yomitan/issues) should include information about: -* What the problem, question, or request is. -* What browser is being used. -* What version of Yomitan is being used. -* If applicable, an export of the settings file. +- What the problem, question, or request is. +- What browser is being used. +- What version of Yomitan is being used. +- If applicable, an export of the settings file. # Development Contributions are welcome from any developers who would like to help out. Below are a few guidelines to ensure contributions have a good level of quality and consistency: -* Open GitHub issues to discuss large features before writing code. -* Follow the [conventions and style](#style) of the existing code. -* Test changes using the continuous integration tests included in the repository. -* Write clean, modern ES6 code (`const`/`let`, `async`/`await`, arrow functions, etc.) -* Large pull requests without a clear scope will not be merged. -* Incomplete or non-standalone features will not be merged. +- Open GitHub issues to discuss large features before writing code. +- Follow the [conventions and style](#style) of the existing code. +- Test changes using the continuous integration tests included in the repository. +- Write clean, modern ES6 code (`const`/`let`, `async`/`await`, arrow functions, etc.) +- Large pull requests without a clear scope will not be merged. +- Incomplete or non-standalone features will not be merged. ## Setup @@ -37,23 +37,23 @@ loaded as an unpacked extension by Chrome. This way, development does not requir and most changes will be automatically updated by the browser. Depending on what files were changed, the extension may sometimes need to be reloaded before the changes take effect. - There are two scripts to build the extension to a packaged file for various build targets: -* [build.bat](build.bat) on Windows -* [build.sh](build.sh) on Linux + +- [build.bat](build.bat) on Windows +- [build.sh](build.sh) on Linux Both of these files are convenience scripts which invoke <code>node [dev/build.js](dev/build.js)</code>. The build script can produce several different build files based on manifest configurations defined in [manifest-variants.json](dev/data/manifest-variants.json). Several command line arguments are available for these scripts: -* `[target]` - Builds a specific target. -* `--all` - Builds all targets specified in [manifest-variants.json](dev/data/manifest-variants.json). -* `--default` - Restores the default manifest file. -* `--manifest <target>` - Overwrites `ext/manifest.json` with the manifest variant for the specified build target. -* `--dry-run` - Runs the full build process (excluding zip building), checking that the configuration is valid. -* `--dry-run-build-zip` - If `--dry-run` is also specified, zip building will also be performed in memory; no files are created. -* `--yomitan-version <version>` - Sets the version number in the extension manifest. Defaults to 0.0.0.0 if not set. +- `[target]` - Builds a specific target. +- `--all` - Builds all targets specified in [manifest-variants.json](dev/data/manifest-variants.json). +- `--default` - Restores the default manifest file. +- `--manifest <target>` - Overwrites `ext/manifest.json` with the manifest variant for the specified build target. +- `--dry-run` - Runs the full build process (excluding zip building), checking that the configuration is valid. +- `--dry-run-build-zip` - If `--dry-run` is also specified, zip building will also be performed in memory; no files are created. +- `--yomitan-version <version>` - Sets the version number in the extension manifest. Defaults to 0.0.0.0 if not set. If no arguments are specified, the command is equivalent to `build.bat --all`. @@ -75,6 +75,8 @@ The generated `ext/manfiest.json` should not be committed. Linting rules are defined for a few types of files, and validation is performed as part of the standard tests run by `npm test` and the continuous integration process. -* [.eslintrc.json](.eslintrc.json) rules are used for JavaScript files. -* [.stylelintrc.json](.stylelintrc.json) rules are used for CSS files. -* [.htmlvalidate.json](.htmlvalidate.json) rules are used for HTML files. +- [.eslintrc.json](.eslintrc.json) rules are used for JavaScript files. +- [.stylelintrc.json](.stylelintrc.json) rules are used for CSS files. +- [.htmlvalidate.json](.htmlvalidate.json) rules are used for HTML files. + +In addition, the [Markdown All in One VSCode extension](https://github.com/yzhang-gh/vscode-markdown) is used for formatting markdown files and automatically updating the table of contents. @@ -1,7 +1,7 @@ -# Yomitan +# Yomitan <!-- omit from toc --> -[![Chrome Release (Testing)](https://img.shields.io/chrome-web-store/v/glnaenfapkkecknnmginabpmgkenenml?label=chrome%20release%20(testing))](https://chrome.google.com/webstore/detail/yomitan-development-build/glnaenfapkkecknnmginabpmgkenenml) -[![Firefox Release (Testing)](https://img.shields.io/github/v/release/themoeway/yomitan?filter=*&label=firefox%20release%20(testing))](https://github.com/themoeway/yomitan/releases) +[![Chrome Release (Testing)](<https://img.shields.io/chrome-web-store/v/glnaenfapkkecknnmginabpmgkenenml?label=chrome%20release%20(testing)>)](https://chrome.google.com/webstore/detail/yomitan-development-build/glnaenfapkkecknnmginabpmgkenenml) +[![Firefox Release (Testing)](<https://img.shields.io/github/v/release/themoeway/yomitan?filter=*&label=firefox%20release%20(testing)>)](https://github.com/themoeway/yomitan/releases) [![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/themoeway/yomitan/badge)](https://securityscorecards.dev/viewer/?uri=github.com/themoeway/yomitan) [![Discord Server](https://dcbadge.vercel.app/api/server/UGNPMDE7zC?style=flat)](https://discord.gg/UGNPMDE7zC)\ **Important note:** Yomitan is still not recommended for use, as we are still trying to complete the transition to Manifest V3 (which is required of all new browser extensions), and there are [some critical bugs remaining](https://github.com/themoeway/yomitan/issues?q=is%3Aopen+is%3Aissue+label%3Apriority%2Fhigh). This is also why there is no stable release of yomitan yet. @@ -9,12 +9,11 @@ > :wave: **This project is a community fork of yomichan** (which was [sunset](https://foosoft.net/posts/sunsetting-the-yomichan-project/) by its owner on Feb 26 2023). > > The primary goal is to **keep the project alive and working on latest browser versions and doing bugfixes**. (Feature improvements are considered secondary.) -> -> Since the owner requested forks be uniquely named, we have chosen a new name: *yomitan*. (*-tan* is an honorific used for anthropomorphic moe characters.) While we've renamed the codebase and made judicious use of find-and-replace, it is entirely the hard work of foosoft and numerous other open source contributors from 2016-2023 and we do not claim any credit. +> +> Since the owner requested forks be uniquely named, we have chosen a new name: _yomitan_. (_-tan_ is an honorific used for anthropomorphic moe characters.) While we've renamed the codebase and made judicious use of find-and-replace, it is entirely the hard work of foosoft and numerous other open source contributors from 2016-2023 and we do not claim any credit. > > Since this is a distributed effort, we highly welcome new contributors! Feel free to browse the issue tracker, and you can find us on [TheMoeWay Discord](https://discord.gg/UGNPMDE7zC) at [#yomitan](https://discord.com/channels/617136488840429598/1081538711742844980) - Yomitan turns your web browser into a tool for building Japanese language literacy by helping you to decipher texts which would be otherwise too difficult tackle. This extension is similar to [10ten Japanese Reader (formerly Rikaichamp)](https://addons.mozilla.org/en-US/firefox/addon/10ten-ja-reader/) for Firefox and @@ -23,50 +22,52 @@ stands apart in its goal of being an all-encompassing learning tool as opposed t Yomitan provides advanced features not available in other browser-based dictionaries: -* Interactive popup definition window for displaying search results. -* On-demand audio playback for select dictionary definitions. -* Kanji stroke order diagrams are just a click away for most characters. -* Custom search page for easily executing custom search queries. -* Support for multiple dictionary formats including [EPWING](https://ja.wikipedia.org/wiki/EPWING) via the [Yomichan Import](https://foosoft.net/projects/yomichan-import) tool. -* Automatic note creation for the [Anki](https://apps.ankiweb.net/) flashcard program via the [AnkiConnect](https://foosoft.net/projects/anki-connect) plugin. -* Clean, modern code makes it easy for developers to [contribute](https://github.com/themoeway/yomitan/blob/master/CONTRIBUTING.md) new features. +- Interactive popup definition window for displaying search results. +- On-demand audio playback for select dictionary definitions. +- Kanji stroke order diagrams are just a click away for most characters. +- Custom search page for easily executing custom search queries. +- Support for multiple dictionary formats including [EPWING](https://ja.wikipedia.org/wiki/EPWING) via the [Yomichan Import](https://foosoft.net/projects/yomichan-import) tool. +- Automatic note creation for the [Anki](https://apps.ankiweb.net/) flashcard program via the [AnkiConnect](https://foosoft.net/projects/anki-connect) plugin. +- Clean, modern code makes it easy for developers to [contribute](https://github.com/themoeway/yomitan/blob/master/CONTRIBUTING.md) new features. [![Term definitions](img/ss-terms-thumb.png)](img/ss-terms.png) [![Kanji information](img/ss-kanji-thumb.png)](img/ss-kanji.png) [![Dictionary options](img/ss-dictionaries-thumb.png)](img/ss-dictionaries.png) [![Anki options](img/ss-anki-thumb.png)](img/ss-anki.png) -## Table of Contents - -* [Installation](#installation) -* [Migrating from Yomichan](#migrating-from-yomichan) -* [Dictionaries](#dictionaries) -* [Basic Usage](#basic-usage) - * [Importing Dictionaries](#importing-dictionaries) -* [Custom Dictionaries](#custom-dictionaries) -* [Anki Integration](#anki-integration) - * [Flashcard Configuration](#flashcard-configuration) - * [Flashcard Creation](#flashcard-creation) -* [Keyboard Shortcuts](#keyboard-shortcuts) -* [Frequently Asked Questions](#frequently-asked-questions) -* [Licenses](#licenses) -* [Third-Party Libraries](#third-party-libraries) +## Table of Contents<!-- omit from toc --> + +- [Installation](#installation) +- [Migrating from Yomichan](#migrating-from-yomichan) +- [Dictionaries](#dictionaries) +- [Basic Usage](#basic-usage) + - [Importing Dictionaries](#importing-dictionaries) + - [Importing and Exporting Personal Configuration](#importing-and-exporting-personal-configuration) +- [Custom Dictionaries](#custom-dictionaries) +- [Anki Integration](#anki-integration) + - [Flashcard Configuration](#flashcard-configuration) + - [Flashcard Creation](#flashcard-creation) +- [Keyboard Shortcuts](#keyboard-shortcuts) +- [Frequently Asked Questions](#frequently-asked-questions) +- [Licenses](#licenses) +- [Third-Party Libraries](#third-party-libraries) ## Installation -Yomitan comes in two flavors: *stable* and *testing*. Over the years, this extension has evolved to contain many +Yomitan comes in two flavors: _stable_ and _testing_. Over the years, this extension has evolved to contain many complex features which have become increasingly difficult to test across different browsers, versions, and environments. -New changes are initially introduced into the *testing* version, and after some time spent ensuring that they are -relatively bug free, they will be promoted to the *stable* version. If you are technically savvy and don't mind -submitting issues on GitHub, try the *testing* version; otherwise, the *stable* version will be your best bet. +New changes are initially introduced into the _testing_ version, and after some time spent ensuring that they are +relatively bug free, they will be promoted to the _stable_ version. If you are technically savvy and don't mind +submitting issues on GitHub, try the _testing_ version; otherwise, the _stable_ version will be your best bet. -* **Google Chrome** - * stable (not released yet) - * [testing](https://chrome.google.com/webstore/detail/yomitan-development-build/glnaenfapkkecknnmginabpmgkenenml) +- **Google Chrome** -* **Mozilla Firefox** - * stable (not released yet) - * [testing](https://github.com/themoeway/yomitan/releases) ※ + - stable (not released yet) + - [testing](https://chrome.google.com/webstore/detail/yomitan-development-build/glnaenfapkkecknnmginabpmgkenenml) + +- **Mozilla Firefox** + - stable (not released yet) + - [testing](https://github.com/themoeway/yomitan/releases) ※ ※ NOTE: Unlike Chrome, Firefox does not allow extensions meant for testing to be hosted in the marketplace. You will have to download a desired version and side-load it yourself. You only need to do this once and will get @@ -114,30 +115,29 @@ Import](https://foosoft.net/projects/yomichan-import) page to learn how to conve Be aware that the non-English dictionaries contain fewer entries than their English counterparts. Even if your primary language is not English, you may consider also importing the English version for better coverage. -* **[JMdict](https://www.edrdg.org/jmdict/edict_doc.html)** (Japanese vocabulary) - * [jmdict\_dutch.zip](https://github.com/themoeway/yomitan/raw/dictionaries/jmdict_dutch.zip) - * [jmdict\_english.zip](https://github.com/themoeway/yomitan/raw/dictionaries/jmdict_english.zip) - * [jmdict\_french.zip](https://github.com/themoeway/yomitan/raw/dictionaries/jmdict_french.zip) - * [jmdict\_german.zip](https://github.com/themoeway/yomitan/raw/dictionaries/jmdict_german.zip) - * [jmdict\_hungarian.zip](https://github.com/themoeway/yomitan/raw/dictionaries/jmdict_hungarian.zip) - * [jmdict\_russian.zip](https://github.com/themoeway/yomitan/raw/dictionaries/jmdict_russian.zip) - * [jmdict\_slovenian.zip](https://github.com/themoeway/yomitan/raw/dictionaries/jmdict_slovenian.zip) - * [jmdict\_spanish.zip](https://github.com/themoeway/yomitan/raw/dictionaries/jmdict_spanish.zip) - * [jmdict\_swedish.zip](https://github.com/themoeway/yomitan/raw/dictionaries/jmdict_swedish.zip) -* **[JMnedict](https://www.edrdg.org/enamdict/enamdict_doc.html)** (Japanese names) - * [jmnedict.zip](https://github.com/themoeway/yomitan/raw/dictionaries/jmnedict.zip) -* **KireiCake (upstream project dead)** (Japanese slang) - * [kireicake.zip](https://github.com/themoeway/yomitan/raw/dictionaries/kireicake.zip) -* **[KANJIDIC](http://nihongo.monash.edu/kanjidic2/index.html)** (Japanese kanji) - * [kanjidic\_english.zip](https://github.com/themoeway/yomitan/raw/dictionaries/kanjidic_english.zip) - * [kanjidic\_french.zip](https://github.com/themoeway/yomitan/raw/dictionaries/kanjidic_french.zip) - * [kanjidic\_portuguese.zip](https://github.com/themoeway/yomitan/raw/dictionaries/kanjidic_portuguese.zip) - * [kanjidic\_spanish.zip](https://github.com/themoeway/yomitan/raw/dictionaries/kanjidic_spanish.zip) -* **[Innocent Corpus](https://web.archive.org/web/20190309073023/https://forum.koohii.com/thread-9459.html#pid168613)** (Term and kanji frequencies across 5000+ novels) - * [innocent\_corpus.zip](https://github.com/themoeway/yomitan/raw/dictionaries/innocent_corpus.zip) -* **[Kanjium](https://github.com/mifunetoshiro/kanjium)** (Pitch dictionary, see [related project page](https://github.com/toasted-nutbread/yomichan-pitch-accent-dictionary) for details) - * [kanjium_pitch_accents.zip](https://github.com/themoeway/yomitan/raw/dictionaries/kanjium_pitch_accents.zip) - +- **[JMdict](https://www.edrdg.org/jmdict/edict_doc.html)** (Japanese vocabulary) + - [jmdict_dutch.zip](https://github.com/themoeway/yomitan/raw/dictionaries/jmdict_dutch.zip) + - [jmdict_english.zip](https://github.com/themoeway/yomitan/raw/dictionaries/jmdict_english.zip) + - [jmdict_french.zip](https://github.com/themoeway/yomitan/raw/dictionaries/jmdict_french.zip) + - [jmdict_german.zip](https://github.com/themoeway/yomitan/raw/dictionaries/jmdict_german.zip) + - [jmdict_hungarian.zip](https://github.com/themoeway/yomitan/raw/dictionaries/jmdict_hungarian.zip) + - [jmdict_russian.zip](https://github.com/themoeway/yomitan/raw/dictionaries/jmdict_russian.zip) + - [jmdict_slovenian.zip](https://github.com/themoeway/yomitan/raw/dictionaries/jmdict_slovenian.zip) + - [jmdict_spanish.zip](https://github.com/themoeway/yomitan/raw/dictionaries/jmdict_spanish.zip) + - [jmdict_swedish.zip](https://github.com/themoeway/yomitan/raw/dictionaries/jmdict_swedish.zip) +- **[JMnedict](https://www.edrdg.org/enamdict/enamdict_doc.html)** (Japanese names) + - [jmnedict.zip](https://github.com/themoeway/yomitan/raw/dictionaries/jmnedict.zip) +- **KireiCake (upstream project dead)** (Japanese slang) + - [kireicake.zip](https://github.com/themoeway/yomitan/raw/dictionaries/kireicake.zip) +- **[KANJIDIC](http://nihongo.monash.edu/kanjidic2/index.html)** (Japanese kanji) + - [kanjidic_english.zip](https://github.com/themoeway/yomitan/raw/dictionaries/kanjidic_english.zip) + - [kanjidic_french.zip](https://github.com/themoeway/yomitan/raw/dictionaries/kanjidic_french.zip) + - [kanjidic_portuguese.zip](https://github.com/themoeway/yomitan/raw/dictionaries/kanjidic_portuguese.zip) + - [kanjidic_spanish.zip](https://github.com/themoeway/yomitan/raw/dictionaries/kanjidic_spanish.zip) +- **[Innocent Corpus](https://web.archive.org/web/20190309073023/https://forum.koohii.com/thread-9459.html#pid168613)** (Term and kanji frequencies across 5000+ novels) + - [innocent_corpus.zip](https://github.com/themoeway/yomitan/raw/dictionaries/innocent_corpus.zip) +- **[Kanjium](https://github.com/mifunetoshiro/kanjium)** (Pitch dictionary, see [related project page](https://github.com/toasted-nutbread/yomichan-pitch-accent-dictionary) for details) + - [kanjium_pitch_accents.zip](https://github.com/themoeway/yomitan/raw/dictionaries/kanjium_pitch_accents.zip) ## Basic Usage @@ -145,10 +145,10 @@ language is not English, you may consider also importing the English version for <img src="resources/images/browser-action-popup1.png" alt=""> - * The <img src="ext/images/cog.svg" alt="" width="16" height="16"> _cog_ button will open the Settings page. - * The <img src="ext/images/magnifying-glass.svg" alt="" width="16" height="16"> _magnifying glass_ button will open the Search page. - * The <img src="ext/images/question-mark-circle.svg" alt="" width="16" height="16"> _question mark_ button will open the Information page. - * The <img src="ext/images/profile.svg" alt="" width="16" height="16"> _profile_ button will appear when multiple profiles exist, allowing the current profile to be quickly changed. + - The <img src="ext/images/cog.svg" alt="" width="16" height="16"> _cog_ button will open the Settings page. + - The <img src="ext/images/magnifying-glass.svg" alt="" width="16" height="16"> _magnifying glass_ button will open the Search page. + - The <img src="ext/images/question-mark-circle.svg" alt="" width="16" height="16"> _question mark_ button will open the Information page. + - The <img src="ext/images/profile.svg" alt="" width="16" height="16"> _profile_ button will appear when multiple profiles exist, allowing the current profile to be quickly changed. 2. Import the dictionaries you wish to use for term and kanji searches. If you do not have any dictionaries installed or enabled, Yomitan will warn you that it is not ready for use by displaying an orange exclamation mark over its @@ -188,7 +188,7 @@ Yomitan also supports exporting and importing your entire collection of dictiona - Click `Export Dictionary Collection` from the backup section of Yomitan's settings page - It will show you a progress report as it exports the data then initiates a - download for a file named something like `yomitan-dictionaries-YYYY-MM-DD-HH-mm-ss.json` + download for a file named something like `yomitan-dictionaries-YYYY-MM-DD-HH-mm-ss.json` (e.g. `yomitan-dictionaries-2023-07-05-02-42-04.json`) ### Importing and Exporting Personal Configuration @@ -225,9 +225,9 @@ flashcards that Yomitan creates through AnkiConnect. Flashcard fields can be configured with the following steps: -1. Open the Yomitan options page and scroll down to the section labeled *Anki Options*. -2. Tick the checkbox labeled *Enable Anki integration* (Anki must be running with [AnkiConnect](https://foosoft.net/projects/anki-connect) installed). -3. Select the type of template to configure by clicking on either the *Terms* or *Kanji* tabs. +1. Open the Yomitan options page and scroll down to the section labeled _Anki Options_. +2. Tick the checkbox labeled _Enable Anki integration_ (Anki must be running with [AnkiConnect](https://foosoft.net/projects/anki-connect) installed). +3. Select the type of template to configure by clicking on either the _Terms_ or _Kanji_ tabs. 4. Select the Anki deck and model to use for new creating new flashcards of this type. 5. Fill the model fields with markers corresponding to the information you wish to include (several can be used at once). Advanced users can also configure the actual [Handlebars](https://handlebarsjs.com/) templates used to create @@ -235,62 +235,62 @@ Flashcard fields can be configured with the following steps: #### Markers for Term Cards - Marker | Description - -------|------------ - `{audio}` | Audio sample of a native speaker's pronunciation in MP3 format (if available). - `{clipboard-image}` | An image which is stored in the system clipboard, if present. - `{clipboard-text}` | Text which is stored in the system clipboard, if present. - `{cloze-body}` | Raw, inflected term as it appeared before being reduced to dictionary form by Yomitan. - `{cloze-prefix}` | Fragment of the containing `{sentence}` starting at the beginning of `{sentence}` until the beginning of `{cloze-body}`. - `{cloze-suffix}` | Fragment of the containing `{sentence}` starting at the end of `{cloze-body}` until the end of `{sentence}`. - `{conjugation}` | Conjugation path from the raw inflected term to the source term. - `{dictionary}` | Name of the dictionary from which the card is being created (unavailable in *grouped* mode). - `{document-title}` | Title of the web page that the term appeared in. - `{expression}` | Term expressed as kanji (will be displayed in kana if kanji is not available). - `{frequencies}` | Frequency information for the term. - `{furigana}` | Term expressed as kanji with furigana displayed above it (e.g. <ruby>日本語<rt>にほんご</rt></ruby>). - `{furigana-plain}` | Term expressed as kanji with furigana displayed next to it in brackets (e.g. 日本語[にほんご]). - `{glossary}` | List of definitions for the term (output format depends on whether running in *grouped* mode). - `{glossary-brief}` | List of definitions for the term in a more compact format. - `{glossary-no-dictionary}` | List of definitions for the term, except the dictionary tag is omitted. - `{part-of-speech}` | Part of speech information for the term. - `{pitch-accents}` | List of pitch accent downstep notations for the term. - `{pitch-accent-graphs}` | List of pitch accent graphs for the term. - `{pitch-accent-positions}` | List of accent downstep positions for the term as a number. - `{reading}` | Kana reading for the term (empty for terms where the expression is the reading). - `{screenshot}` | Screenshot of the web page taken at the time the term was added. - `{search-query}` | The full search query shown on the search page. - `{selection-text}` | The selected text on the search page or popup. - `{sentence}` | Sentence, quote, or phrase that the term appears in from the source content. - `{sentence-furigana}` | Sentence, quote, or phrase that the term appears in from the source content, with furigana added. - `{tags}` | Grammar and usage tags providing information about the term (unavailable in *grouped* mode). - `{url}` | Address of the web page in which the term appeared in. + | Marker | Description | + | -------------------------- | ------------------------------------------------------------------------------------------------------------------------ | + | `{audio}` | Audio sample of a native speaker's pronunciation in MP3 format (if available). | + | `{clipboard-image}` | An image which is stored in the system clipboard, if present. | + | `{clipboard-text}` | Text which is stored in the system clipboard, if present. | + | `{cloze-body}` | Raw, inflected term as it appeared before being reduced to dictionary form by Yomitan. | + | `{cloze-prefix}` | Fragment of the containing `{sentence}` starting at the beginning of `{sentence}` until the beginning of `{cloze-body}`. | + | `{cloze-suffix}` | Fragment of the containing `{sentence}` starting at the end of `{cloze-body}` until the end of `{sentence}`. | + | `{conjugation}` | Conjugation path from the raw inflected term to the source term. | + | `{dictionary}` | Name of the dictionary from which the card is being created (unavailable in _grouped_ mode). | + | `{document-title}` | Title of the web page that the term appeared in. | + | `{expression}` | Term expressed as kanji (will be displayed in kana if kanji is not available). | + | `{frequencies}` | Frequency information for the term. | + | `{furigana}` | Term expressed as kanji with furigana displayed above it (e.g. <ruby>日本語<rt>にほんご</rt></ruby>). | + | `{furigana-plain}` | Term expressed as kanji with furigana displayed next to it in brackets (e.g. 日本語[にほんご]). | + | `{glossary}` | List of definitions for the term (output format depends on whether running in _grouped_ mode). | + | `{glossary-brief}` | List of definitions for the term in a more compact format. | + | `{glossary-no-dictionary}` | List of definitions for the term, except the dictionary tag is omitted. | + | `{part-of-speech}` | Part of speech information for the term. | + | `{pitch-accents}` | List of pitch accent downstep notations for the term. | + | `{pitch-accent-graphs}` | List of pitch accent graphs for the term. | + | `{pitch-accent-positions}` | List of accent downstep positions for the term as a number. | + | `{reading}` | Kana reading for the term (empty for terms where the expression is the reading). | + | `{screenshot}` | Screenshot of the web page taken at the time the term was added. | + | `{search-query}` | The full search query shown on the search page. | + | `{selection-text}` | The selected text on the search page or popup. | + | `{sentence}` | Sentence, quote, or phrase that the term appears in from the source content. | + | `{sentence-furigana}` | Sentence, quote, or phrase that the term appears in from the source content, with furigana added. | + | `{tags}` | Grammar and usage tags providing information about the term (unavailable in _grouped_ mode). | + | `{url}` | Address of the web page in which the term appeared in. | #### Markers for Kanji Cards - Marker | Description - -------|------------ - `{character}` | Unicode glyph representing the current kanji. - `{clipboard-image}` | An image which is stored in the system clipboard, if present. - `{clipboard-text}` | Text which is stored in the system clipboard, if present. - `{cloze-body}` | Raw, inflected parent term as it appeared before being reduced to dictionary form by Yomitan. - `{cloze-prefix}` | Fragment of the containing `{sentence}` starting at the beginning of `{sentence}` until the beginning of `{cloze-body}`. - `{cloze-suffix}` | Fragment of the containing `{sentence}` starting at the end of `{cloze-body}` until the end of `{sentence}`. - `{dictionary}` | Name of the dictionary from which the card is being created. - `{document-title}` | Title of the web page that the kanji appeared in. - `{frequencies}` | Frequency information for the kanji. - `{glossary}` | List of definitions for the kanji. - `{kunyomi}` | Kunyomi (Japanese reading) for the kanji expressed as katakana. - `{onyomi}` | Onyomi (Chinese reading) for the kanji expressed as hiragana. - `{screenshot}` | Screenshot of the web page taken at the time the kanji was added. - `{search-query}` | The full search query shown on the search page. - `{selection-text}` | The selected text on the search page or popup. - `{sentence}` | Sentence, quote, or phrase that the character appears in from the source content. - `{sentence-furigana}` | Sentence, quote, or phrase that the character appears in from the source content, with furigana added. - `{stroke-count}` | Number of strokes that the kanji character has. - `{url}` | Address of the web page in which the kanji appeared in. - -When creating your model for Yomitan, *make sure that you pick a unique field to be first*; fields that will + | Marker | Description | + | --------------------- | ------------------------------------------------------------------------------------------------------------------------ | + | `{character}` | Unicode glyph representing the current kanji. | + | `{clipboard-image}` | An image which is stored in the system clipboard, if present. | + | `{clipboard-text}` | Text which is stored in the system clipboard, if present. | + | `{cloze-body}` | Raw, inflected parent term as it appeared before being reduced to dictionary form by Yomitan. | + | `{cloze-prefix}` | Fragment of the containing `{sentence}` starting at the beginning of `{sentence}` until the beginning of `{cloze-body}`. | + | `{cloze-suffix}` | Fragment of the containing `{sentence}` starting at the end of `{cloze-body}` until the end of `{sentence}`. | + | `{dictionary}` | Name of the dictionary from which the card is being created. | + | `{document-title}` | Title of the web page that the kanji appeared in. | + | `{frequencies}` | Frequency information for the kanji. | + | `{glossary}` | List of definitions for the kanji. | + | `{kunyomi}` | Kunyomi (Japanese reading) for the kanji expressed as katakana. | + | `{onyomi}` | Onyomi (Chinese reading) for the kanji expressed as hiragana. | + | `{screenshot}` | Screenshot of the web page taken at the time the kanji was added. | + | `{search-query}` | The full search query shown on the search page. | + | `{selection-text}` | The selected text on the search page or popup. | + | `{sentence}` | Sentence, quote, or phrase that the character appears in from the source content. | + | `{sentence-furigana}` | Sentence, quote, or phrase that the character appears in from the source content, with furigana added. | + | `{stroke-count}` | Number of strokes that the kanji character has. | + | `{url}` | Address of the web page in which the kanji appeared in. | + +When creating your model for Yomitan, _make sure that you pick a unique field to be first_; fields that will contain `{expression}` or `{character}` are ideal candidates for this. Anki does not allow duplicate flashcards to be added to a deck by default; it uses the first field in the model to check for duplicates. For example, if you have `{reading}` configured to be the first field in your model and <ruby>橋<rt>はし</rt></ruby> is already in your deck, you will not @@ -301,40 +301,40 @@ be able to create a flashcard for <ruby>箸<rt>はし</rt></ruby> because they s Once Yomitan is configured, it becomes trivial to create new flashcards with a single click. You will see the following icons next to term definitions: -* Clicking ![](img/btn-add-expression.png) adds the current expression as kanji (e.g. 食べる). -* Clicking ![](img/btn-add-reading.png) adds the current expression as hiragana or katakana (e.g. たべる). +- Clicking ![](img/btn-add-expression.png) adds the current expression as kanji (e.g. 食べる). +- Clicking ![](img/btn-add-reading.png) adds the current expression as hiragana or katakana (e.g. たべる). Below are some troubleshooting tips you can try if you are unable to create new flashcards: -* Individual icons will appear grayed out if a flashcard cannot be created for the current definition (e.g. it already exists in the deck). -* If all of the buttons appear grayed out, then you should double-check your deck and model configuration settings. -* If no icons appear at all, make sure that Anki is running in the background and that [AnkiConnect](https://foosoft.net/projects/anki-connect) has been installed. +- Individual icons will appear grayed out if a flashcard cannot be created for the current definition (e.g. it already exists in the deck). +- If all of the buttons appear grayed out, then you should double-check your deck and model configuration settings. +- If no icons appear at all, make sure that Anki is running in the background and that [AnkiConnect](https://foosoft.net/projects/anki-connect) has been installed. ## Keyboard Shortcuts The following shortcuts are globally available: -Shortcut | Action ----------|------- -<kbd>Alt</kbd> + <kbd>Insert</kbd> | Open search page. -<kbd>Alt</kbd> + <kbd>Delete</kbd> | Toggle extension on/off. +| Shortcut | Action | +| ---------------------------------- | ------------------------ | +| <kbd>Alt</kbd> + <kbd>Insert</kbd> | Open search page. | +| <kbd>Alt</kbd> + <kbd>Delete</kbd> | Toggle extension on/off. | The following shortcuts are available on search results: -Shortcut | Action ----------|------- -<kbd>Esc</kbd> | Cancel current search. -<kbd>Alt</kbd> + <kbd>PgUp</kbd> | Page up through results. -<kbd>Alt</kbd> + <kbd>PgDn</kbd> | Page down through results. -<kbd>Alt</kbd> + <kbd>End</kbd> | Go to last result. -<kbd>Alt</kbd> + <kbd>Home</kbd> | Go to first result. -<kbd>Alt</kbd> + <kbd>Up</kbd> | Go to previous result. -<kbd>Alt</kbd> + <kbd>Down</kbd> | Go to next result. -<kbd>Alt</kbd> + <kbd>b</kbd> | Go to back to source term. -<kbd>Alt</kbd> + <kbd>e</kbd> | Add current term as expression to Anki. -<kbd>Alt</kbd> + <kbd>r</kbd> | Add current term as reading to Anki. -<kbd>Alt</kbd> + <kbd>p</kbd> | Play audio for current term. -<kbd>Alt</kbd> + <kbd>k</kbd> | Add current kanji to Anki. +| Shortcut | Action | +| -------------------------------- | --------------------------------------- | +| <kbd>Esc</kbd> | Cancel current search. | +| <kbd>Alt</kbd> + <kbd>PgUp</kbd> | Page up through results. | +| <kbd>Alt</kbd> + <kbd>PgDn</kbd> | Page down through results. | +| <kbd>Alt</kbd> + <kbd>End</kbd> | Go to last result. | +| <kbd>Alt</kbd> + <kbd>Home</kbd> | Go to first result. | +| <kbd>Alt</kbd> + <kbd>Up</kbd> | Go to previous result. | +| <kbd>Alt</kbd> + <kbd>Down</kbd> | Go to next result. | +| <kbd>Alt</kbd> + <kbd>b</kbd> | Go to back to source term. | +| <kbd>Alt</kbd> + <kbd>e</kbd> | Add current term as expression to Anki. | +| <kbd>Alt</kbd> + <kbd>r</kbd> | Add current term as reading to Anki. | +| <kbd>Alt</kbd> + <kbd>p</kbd> | Play audio for current term. | +| <kbd>Alt</kbd> + <kbd>k</kbd> | Add current kanji to Anki. | ## Frequently Asked Questions @@ -345,18 +345,18 @@ everything "just works" in Chrome, depending on settings, Firefox users can run Yomitan catches errors and tries to offer suggestions about how to work around Firefox issues, but in general at least one of the following solutions should work for you: -* Make sure you have cookies enabled. It appears that disabling them also disables IndexedDB for some reason. You - can still have cookies be disabled on other sites; just make sure to add the Yomitan extension to the whitelist of - whatever tool you are using to restrict cookies. You can get the extension "URL" by looking at the address bar when - you have the search page open. -* Make sure that you have sufficient disk space available on the drive Firefox uses to store your user profile. - Firefox limits the amount of space that can be used by IndexedDB to a small fraction of the disk space actually - available on your computer. -* Make sure that you have history set to "Remember history" enabled in your privacy settings. When this option is - set to "Never remember history", IndexedDB access is once again disabled for an inexplicable reason. -* As a last resort, try using the [Refresh Firefox](https://support.mozilla.org/en-US/kb/reset-preferences-fix-problems) - feature to reset your user profile. It appears that the Firefox profile system can corrupt itself preventing - IndexedDB from being accessible to Yomitan. +- Make sure you have cookies enabled. It appears that disabling them also disables IndexedDB for some reason. You + can still have cookies be disabled on other sites; just make sure to add the Yomitan extension to the whitelist of + whatever tool you are using to restrict cookies. You can get the extension "URL" by looking at the address bar when + you have the search page open. +- Make sure that you have sufficient disk space available on the drive Firefox uses to store your user profile. + Firefox limits the amount of space that can be used by IndexedDB to a small fraction of the disk space actually + available on your computer. +- Make sure that you have history set to "Remember history" enabled in your privacy settings. When this option is + set to "Never remember history", IndexedDB access is once again disabled for an inexplicable reason. +- As a last resort, try using the [Refresh Firefox](https://support.mozilla.org/en-US/kb/reset-preferences-fix-problems) + feature to reset your user profile. It appears that the Firefox profile system can corrupt itself preventing + IndexedDB from being accessible to Yomitan. **Will you add support for online dictionaries?** @@ -367,13 +367,13 @@ experience. **Is it possible to use Yomitan with files saved locally on my computer with Chrome?** -In order to use Yomitan with local files in Chrome, you must first tick the *Allow access to file URLs* checkbox +In order to use Yomitan with local files in Chrome, you must first tick the _Allow access to file URLs_ checkbox for Yomitan on the extensions page. Due to the restrictions placed on browser addons in the WebExtensions model, it will likely never be possible to use Yomitan with PDF files. **Is it possible to delete individual dictionaries without purging the database?** -Yomitan is able to delete individual dictionaries, but keep in mind that this process can be *very* slow and can +Yomitan is able to delete individual dictionaries, but keep in mind that this process can be _very_ slow and can cause the browser to become unresponsive. The time it takes to delete a single dictionary can sometimes be roughly the same as the time it originally took to import, which can be significant for certain large dictionaries. @@ -394,24 +394,24 @@ new languages will be declined, allowing Yomitan's focus to remain Japanese-cent Required licensing notices for this project follow below: -* **EDRDG License** \ - This package uses the [EDICT](https://www.edrdg.org/jmdict/edict.html) and - [KANJIDIC](https://www.edrdg.org/wiki/index.php/KANJIDIC_Project) dictionary files. These files are the property of - the [Electronic Dictionary Research and Development Group](https://www.edrdg.org/), and are used in conformance with - the Group's [license](https://www.edrdg.org/edrdg/licence.html). +- **EDRDG License** \ + This package uses the [EDICT](https://www.edrdg.org/jmdict/edict.html) and + [KANJIDIC](https://www.edrdg.org/wiki/index.php/KANJIDIC_Project) dictionary files. These files are the property of + the [Electronic Dictionary Research and Development Group](https://www.edrdg.org/), and are used in conformance with + the Group's [license](https://www.edrdg.org/edrdg/licence.html). -* **Kanjium License** \ - The pitch accent notation, verb particle data, phonetics, homonyms and other additions or modifications to EDICT, - KANJIDIC or KRADFILE were provided by Uros Ozvatic through his free database. +- **Kanjium License** \ + The pitch accent notation, verb particle data, phonetics, homonyms and other additions or modifications to EDICT, + KANJIDIC or KRADFILE were provided by Uros Ozvatic through his free database. ## Third-Party Libraries Yomitan uses several third-party libraries to function. Below are links to homepages, snapshots, and licenses of the exact versions packaged. -* Handlebars: To retain Handlebars templating without unsafe-eval, a fork of handlebars by Kibana is being used. [homepage](https://github.com/Aquafina-water-bottle/handlebars-noeval) - [snapshot](https://raw.githubusercontent.com/Aquafina-water-bottle/handlebars-noeval/10efaac28d8d2e8d7e503347281eff1a90aa3f31/handlebars.min.js) - [license](https://github.com/Aquafina-water-bottle/handlebars-noeval/blob/10efaac28d8d2e8d7e503347281eff1a90aa3f31/src/hb/LICENSE) -* JSZip: [homepage](https://stuk.github.io/jszip/) - [snapshot](https://github.com/Stuk/jszip/blob/v3.9.1/dist/jszip.min.js) - [license](https://github.com/Stuk/jszip/blob/v3.9.1/LICENSE.markdown) -* WanaKana: [homepage](https://wanakana.com/) - [snapshot](https://unpkg.com/wanakana@4.0.2/umd/wanakana.min.js) - [license](https://github.com/WaniKani/WanaKana/blob/4.0.2/LICENSE) -* parse5: [homepage](https://github.com/inikulin/parse5) - [snapshot](https://github.com/inikulin/parse5/tree/v7.1.2/packages/parse5) - [license](https://github.com/inikulin/parse5/blob/v7.1.2/LICENSE) _(Only used in MV3 build)_ -* Dexie: [homepage](https://dexie.org/) - [snapshot](https://unpkg.com/dexie@3.2.4/dist/dexie.min.js) - [license](https://unpkg.com/dexie@3.2.4/LICENSE) -* dexie-export-import: [homepage](https://www.npmjs.com/package/dexie-export-import) - [snapshot](https://unpkg.com/dexie-export-import@4.0.7/dist/dexie-export-import.js) - [license](https://unpkg.com/dexie-export-import@4.0.7/LICENSE) +- Handlebars: To retain Handlebars templating without unsafe-eval, a fork of handlebars by Kibana is being used. [homepage](https://github.com/Aquafina-water-bottle/handlebars-noeval) - [snapshot](https://raw.githubusercontent.com/Aquafina-water-bottle/handlebars-noeval/10efaac28d8d2e8d7e503347281eff1a90aa3f31/handlebars.min.js) - [license](https://github.com/Aquafina-water-bottle/handlebars-noeval/blob/10efaac28d8d2e8d7e503347281eff1a90aa3f31/src/hb/LICENSE) +- JSZip: [homepage](https://stuk.github.io/jszip/) - [snapshot](https://github.com/Stuk/jszip/blob/v3.9.1/dist/jszip.min.js) - [license](https://github.com/Stuk/jszip/blob/v3.9.1/LICENSE.markdown) +- WanaKana: [homepage](https://wanakana.com/) - [snapshot](https://unpkg.com/wanakana@4.0.2/umd/wanakana.min.js) - [license](https://github.com/WaniKani/WanaKana/blob/4.0.2/LICENSE) +- parse5: [homepage](https://github.com/inikulin/parse5) - [snapshot](https://github.com/inikulin/parse5/tree/v7.1.2/packages/parse5) - [license](https://github.com/inikulin/parse5/blob/v7.1.2/LICENSE) _(Only used in MV3 build)_ +- Dexie: [homepage](https://dexie.org/) - [snapshot](https://unpkg.com/dexie@3.2.4/dist/dexie.min.js) - [license](https://unpkg.com/dexie@3.2.4/LICENSE) +- dexie-export-import: [homepage](https://www.npmjs.com/package/dexie-export-import) - [snapshot](https://unpkg.com/dexie-export-import@4.0.7/dist/dexie-export-import.js) - [license](https://unpkg.com/dexie-export-import@4.0.7/LICENSE) diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000..2f42f913 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,5 @@ +# Security Policy + +## Reporting a Vulnerability + +Please report vulnerabilties using GitHub's built-in functionality: https://github.com/themoeway/yomitan/security/advisories/new diff --git a/dev/data/manifest-variants.json b/dev/data/manifest-variants.json index 26d91d26..07c98b91 100644 --- a/dev/data/manifest-variants.json +++ b/dev/data/manifest-variants.json @@ -80,9 +80,9 @@ "storage", "clipboardWrite", "unlimitedStorage", - "webRequest", "declarativeNetRequest", - "scripting" + "scripting", + "offscreen" ], "optional_permissions": [ "clipboardRead", @@ -228,7 +228,7 @@ "value": { "gecko": { "id": "{cb7c0bec-7085-4f84-8422-7b55a7c4467c}", - "strict_min_version": "102.0" + "strict_min_version": "115.0" } } }, @@ -249,11 +249,36 @@ ] }, { + "action": "add", + "path": [ + "permissions" + ], + "items": [ + "webRequest" + ] + }, + { + "action": "add", + "path": [ + "permissions" + ], + "items": [ + "webRequestBlocking" + ] + }, + { "action": "remove", "path": [ "permissions" ], "item": "declarativeNetRequest" + }, + { + "action": "remove", + "path": [ + "permissions" + ], + "item": "offscreen" } ], "excludeFiles": [ @@ -329,6 +354,13 @@ "item": "webRequestBlocking" }, { + "action": "remove", + "path": [ + "permissions" + ], + "item": "offscreen" + }, + { "action": "delete", "path": [ "content_scripts", diff --git a/docs/permissions.md b/docs/permissions.md index b337bb31..10046210 100644 --- a/docs/permissions.md +++ b/docs/permissions.md @@ -9,12 +9,12 @@ `unlimitedStorage` is used to help prevent web browsers from unexpectedly deleting dictionary data. -* `webRequest` and `webRequestBlocking` _(Manifest V2 only)_ <br> +* `webRequest` and `webRequestBlocking` _(Firefox only)_ <br> Yomichan uses these permissions to ensure certain requests have valid and secure headers. This sometimes involves removing or changing the `Origin` request header, as this can be used to fingerprint browser configuration. -* `declarativeNetRequest` _(Manifest V3 only)_ <br> +* `declarativeNetRequest` _(Chrome only)_ <br> Yomichan uses this permission to ensure certain requests have valid and secure headers. This sometimes involves removing or changing the `Origin` request header, as this can be used to fingerprint browser configuration. @@ -23,6 +23,11 @@ Yomichan will sometimes need to inject stylesheets into webpages in order to properly display the search popup. +* `offscreen` __(Chrome only)_ <br> + Yomitan uses this permission to create a secondary backend document that has DOM access, given that Manifest v3 + service workers do not. Service workers can then reach out to out to this document in order to complete + actions that require access to DOM APIs, such as any that require clipboard access. + * `clipboardWrite` <br> Yomichan supports simulating the `Ctrl+C` (copy to clipboard) keyboard shortcut when a definitions popup is open and focused. diff --git a/ext/css/structured-content.css b/ext/css/structured-content.css index 9c894008..1d4677b5 100644 --- a/ext/css/structured-content.css +++ b/ext/css/structured-content.css @@ -201,7 +201,7 @@ /* Links */ .gloss-link-text { - vertical-align: middle; + vertical-align: baseline; } .gloss-link-external-icon { display: inline-block; diff --git a/ext/js/background/backend.js b/ext/js/background/backend.js index 755010a3..8e8e6945 100644 --- a/ext/js/background/backend.js +++ b/ext/js/background/backend.js @@ -57,12 +57,21 @@ class Backend { }); this._anki = new AnkiConnect(); this._mecab = new Mecab(); - this._clipboardReader = new ClipboardReader({ - // eslint-disable-next-line no-undef - document: (typeof document === 'object' && document !== null ? document : null), - pasteTargetSelector: '#clipboard-paste-target', - richContentPasteTargetSelector: '#clipboard-rich-content-paste-target' - }); + + if (!chrome.offscreen) { + this._clipboardReader = new ClipboardReader({ + // eslint-disable-next-line no-undef + document: (typeof document === 'object' && document !== null ? document : null), + pasteTargetSelector: '#clipboard-paste-target', + richContentPasteTargetSelector: '#clipboard-rich-content-paste-target' + }); + } else { + this._clipboardReader = { + getText: this._getTextOffscreen.bind(this), + getImage: this._getImageOffscreen.bind(this) + }; + } + this._clipboardMonitor = new ClipboardMonitor({ japaneseUtil: this._japaneseUtil, clipboardReader: this._clipboardReader @@ -97,6 +106,8 @@ class Backend { this._permissions = null; this._permissionsUtil = new PermissionsUtil(); + this._creatingOffscreen = null; + this._messageHandlers = new Map([ ['requestBackendReadySignal', {async: false, contentScript: true, handler: this._onApiRequestBackendReadySignal.bind(this)}], ['optionsGet', {async: false, contentScript: true, handler: this._onApiOptionsGet.bind(this)}], @@ -218,6 +229,9 @@ class Backend { await this._requestBuilder.prepare(); await this._environment.prepare(); + if (chrome.offscreen) { + await this._setupOffscreenDocument(); + } this._clipboardReader.browser = this._environment.getInfo().browser; try { @@ -1610,6 +1624,20 @@ class Backend { return await (json ? response.json() : response.text()); } + _sendMessagePromise(...args) { + return new Promise((resolve, reject) => { + const callback = (response) => { + try { + resolve(this._getMessageResponseResult(response)); + } catch (error) { + reject(error); + } + }; + + chrome.runtime.sendMessage(...args, callback); + }); + } + _sendMessageIgnoreResponse(...args) { const callback = () => this._checkLastError(chrome.runtime.lastError); chrome.runtime.sendMessage(...args, callback); @@ -2212,6 +2240,14 @@ class Backend { return results; } + async _getTextOffscreen(useRichText) { + return this._sendMessagePromise({action: 'clipboardGetTextOffscreen', params: {useRichText}}); + } + + async _getImageOffscreen() { + return this._sendMessagePromise({action: 'clipboardGetImageOffscreen'}); + } + _onApiOpenCrossFramePort({targetTabId, targetFrameId}, sender) { const sourceTabId = (sender && sender.tab ? sender.tab.id : null); if (typeof sourceTabId !== 'number') { @@ -2254,4 +2290,36 @@ class Backend { return {targetTabId, targetFrameId}; } + + // https://developer.chrome.com/docs/extensions/reference/offscreen/ + async _setupOffscreenDocument() { + if (await this._hasOffscreenDocument()) { + return; + } + if (this._creatingOffscreen) { + await this._creatingOffscreen; + return; + } + + this._creatingOffscreen = chrome.offscreen.createDocument({ + url: 'offscreen.html', + reasons: ['CLIPBOARD'], + justification: 'Access to the clipboard' + }); + await this._creatingOffscreen; + this._creatingOffscreen = null; + } + async _hasOffscreenDocument() { + const offscreenUrl = chrome.runtime.getURL('offscreen.html'); + if (!chrome.runtime.getContexts) { // chrome version <116 + const matchedClients = await clients.matchAll(); + return await matchedClients.some((client) => client.url === offscreenUrl); + } + + const contexts = await chrome.runtime.getContexts({ + contextTypes: ['OFFSCREEN_DOCUMENT'], + documentUrls: [offscreenUrl] + }); + return !!contexts.length; + } } diff --git a/ext/js/background/offscreen-main.js b/ext/js/background/offscreen-main.js new file mode 100644 index 00000000..808e7766 --- /dev/null +++ b/ext/js/background/offscreen-main.js @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2020-2022 Yomichan Authors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +/* global + * Offscreen + */ + +(() => { + new Offscreen(); +})(); diff --git a/ext/js/background/offscreen.js b/ext/js/background/offscreen.js new file mode 100644 index 00000000..bc41d189 --- /dev/null +++ b/ext/js/background/offscreen.js @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2016-2022 Yomichan Authors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +/* global + * ClipboardReader + */ + +/** + * This class controls the core logic of the extension, including API calls + * and various forms of communication between browser tabs and external applications. + */ +class Offscreen { + /** + * Creates a new instance. + */ + constructor() { + this._clipboardReader = new ClipboardReader({ + // eslint-disable-next-line no-undef + document: (typeof document === 'object' && document !== null ? document : null), + pasteTargetSelector: '#clipboard-paste-target', + richContentPasteTargetSelector: '#clipboard-rich-content-paste-target' + }); + + this._messageHandlers = new Map([ + ['clipboardGetTextOffscreen', {async: true, contentScript: true, handler: this._getTextHandler.bind(this)}], + ['clipboardGetImageOffscreen', {async: true, contentScript: true, handler: this._getImageHandler.bind(this)}] + ]); + + const onMessage = this._onMessage.bind(this); + chrome.runtime.onMessage.addListener(onMessage); + } + + _getTextHandler({useRichText}) { + return this._clipboardReader.getText(useRichText); + } + + _getImageHandler() { + return this._clipboardReader.getImage(); + } + + _onMessage({action, params}, sender, callback) { + const messageHandler = this._messageHandlers.get(action); + if (typeof messageHandler === 'undefined') { return false; } + this._validatePrivilegedMessageSender(sender); + + return invokeMessageHandler(messageHandler, params, callback, sender); + } + + _validatePrivilegedMessageSender(sender) { + let {url} = sender; + if (typeof url === 'string' && yomichan.isExtensionUrl(url)) { return; } + const {tab} = url; + if (typeof tab === 'object' && tab !== null) { + ({url} = tab); + if (typeof url === 'string' && yomichan.isExtensionUrl(url)) { return; } + } + throw new Error('Invalid message sender'); + } +} diff --git a/ext/js/display/search-display-controller.js b/ext/js/display/search-display-controller.js index 25d9d6c2..5a271e05 100644 --- a/ext/js/display/search-display-controller.js +++ b/ext/js/display/search-display-controller.js @@ -44,7 +44,7 @@ class SearchDisplayController { this._clipboardMonitor = new ClipboardMonitor({ japaneseUtil, clipboardReader: { - getText: async () => (await yomichan.api.clipboardGet()) + getText: yomichan.api.clipboardGet.bind(yomichan.api) } }); this._messageHandlers = new Map(); diff --git a/ext/offscreen.html b/ext/offscreen.html new file mode 100644 index 00000000..f773e5b1 --- /dev/null +++ b/ext/offscreen.html @@ -0,0 +1,39 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width,initial-scale=1"> + <title>Offscreen</title> + <link rel="icon" type="image/png" href="/images/icon16.png" sizes="16x16"> + <link rel="icon" type="image/png" href="/images/icon19.png" sizes="19x19"> + <link rel="icon" type="image/png" href="/images/icon32.png" sizes="32x32"> + <link rel="icon" type="image/png" href="/images/icon38.png" sizes="38x38"> + <link rel="icon" type="image/png" href="/images/icon48.png" sizes="48x48"> + <link rel="icon" type="image/png" href="/images/icon64.png" sizes="64x64"> + <link rel="icon" type="image/png" href="/images/icon128.png" sizes="128x128"> + <link rel="stylesheet" type="text/css" href="/css/background.css"> +</head> +<body> + +<textarea id="clipboard-paste-target"></textarea> + +<!-- Scripts --> +<script src="/js/core.js"></script> + +<script src="/js/yomichan.js"></script> + +<script src="/js/comm/clipboard-reader.js"></script> + +<script src="/js/background/offscreen.js"></script> +<script src="/js/background/offscreen-main.js"></script> + +<!-- + Due to a Firefox bug, this next element is purposefully terminated incorrectly. + This element must appear directly inside the <body> element, and it must not be closed properly. + https://bugzilla.mozilla.org/show_bug.cgi?id=1603985 +--> +<!-- [html-validate-disable close-order] --> +<div id="clipboard-rich-content-paste-target" contenteditable="true"> + +</body> +</html> |