├── .eslintignore ├── .eslintrc.yml ├── .gitignore ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── bin ├── build-addon.sh └── postinstall.sh ├── docs ├── acceptance.md └── metrics.md ├── extension ├── assets │ ├── animations │ │ ├── Done.json │ │ ├── Error.json │ │ ├── Spinning.json │ │ └── Start.json │ └── images │ │ ├── feedback.svg │ │ ├── ff-logo.png │ │ ├── icon settings.svg │ │ ├── icon-48.png │ │ ├── icon-96.png │ │ ├── icon-close.svg │ │ ├── icon-done-rtl.svg │ │ ├── icon-done.svg │ │ ├── icon-mic.svg │ │ ├── icon-redo.svg │ │ ├── mic.svg │ │ └── smileface.svg ├── css │ ├── main.css │ └── options.css ├── js │ ├── background.js │ ├── content.js │ ├── languages.json │ ├── metrics.js │ └── options.js ├── manifest.json └── views │ └── options.html └── package.json /.eslintignore: -------------------------------------------------------------------------------- 1 | extension/js/vendor/ 2 | -------------------------------------------------------------------------------- /.eslintrc.yml: -------------------------------------------------------------------------------- 1 | env: 2 | browser: true 3 | es6: true 4 | node: true 5 | webextensions: true 6 | 7 | extends: 8 | - eslint:recommended 9 | - plugin:mozilla/recommended 10 | 11 | plugins: 12 | - mozilla 13 | 14 | globals: 15 | Metrics: true 16 | SpeakToMe: true 17 | TestPilotGA: true 18 | bodymovin: true 19 | 20 | rules: 21 | eqeqeq: error 22 | no-console: off # TODO: Set back to "warn" 23 | no-unused-vars: [error, {vars: all, args: none, ignoreRestSiblings: false}] 24 | no-var: error 25 | no-warning-comments: warn 26 | prefer-const: error 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.tgz 3 | *~ 4 | extension/js/vendor/ 5 | extension/views/CHANGELOG.html 6 | node_modules 7 | npm-debug.log 8 | package-lock.json 9 | web-ext-artifacts 10 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Voice Fill Changelog 2 | 3 | ## [Unreleased] 4 | ### Added 5 | - Buttons now have tooltips 6 | ### Changed 7 | ### Removed 8 | ### Fixed 9 | - Bing support 10 | - search.yahoo.com subdomain support 11 | - Element positioning on RTL pages 12 | - Issue where microphone button could be erroneously disabled temporarily 13 | - Huge speed ups! 14 | 15 | ## [1.4.3] - 2018-10-22 16 | ### Added 17 | - Show changelog after upgrade 18 | - Language option in settings 19 | - Search provider option in settings 20 | ### Changed 21 | ### Removed 22 | ### Fixed 23 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Community Participation Guidelines 2 | 3 | This repository is governed by Mozilla's code of conduct and etiquette guidelines. 4 | For more details, please read the 5 | [Mozilla Community Participation Guidelines](https://www.mozilla.org/about/governance/policies/participation/). 6 | 7 | ## How to Report 8 | For more information on how to report violations of the Community Participation Guidelines, please read our '[How to Report](https://www.mozilla.org/about/governance/policies/participation/reporting/)' page. 9 | 10 | 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Voice Fill 2 | 3 | [![Travis](https://img.shields.io/travis/mozilla/voicefill.svg)](https://travis-ci.org/mozilla/voicefill) 4 | [![Mozilla Add-on](https://img.shields.io/amo/v/voice-fill.svg)](https://addons.mozilla.org/en-US/firefox/addon/voice-fill/) 5 | This is a simple WebExtension that adds support to use Speech To Text 6 | as an input method in web pages. 7 | 8 | ## Usage 9 | 10 | Install the `web-ext` npm module: `npm install --global web-ext` and 11 | run `web-ext run` from the `extension` subdirectory. 12 | -------------------------------------------------------------------------------- /bin/build-addon.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -ex 4 | 5 | npm install 6 | 7 | if [[ -z $TESTPILOT_AMO_USER || -z $TESTPILOT_AMO_SECRET ]]; then 8 | echo "No vars set. skipping build..." 9 | exit 1 10 | else 11 | rm -f ./web-ext-artifacts/*.xpi 12 | ./node_modules/.bin/web-ext sign \ 13 | --source-dir extension \ 14 | --api-key $TESTPILOT_AMO_USER \ 15 | --api-secret $TESTPILOT_AMO_SECRET 16 | mv ./web-ext-artifacts/*.xpi ./signed-addon.xpi 17 | fi 18 | 19 | -------------------------------------------------------------------------------- /bin/postinstall.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -ex 4 | 5 | # Generate changelog for extension 6 | scriptdir=$(dirname "$0") 7 | "${scriptdir}/../node_modules/.bin/md2html" \ 8 | "${scriptdir}/../CHANGELOG.md" > "${scriptdir}/../extension/views/CHANGELOG.html" 9 | 10 | # Copy in scripts from node modules 11 | mkdir -p "${scriptdir}/../extension/js/vendor" 12 | cp \ 13 | "${scriptdir}/../node_modules/bodymovin/build/player/bodymovin.min.js" \ 14 | "${scriptdir}/../extension/js/vendor/" 15 | cp \ 16 | "${scriptdir}/../node_modules/testpilot-ga/dist/index.js" \ 17 | "${scriptdir}/../extension/js/vendor/testpilot-ga.js" 18 | cp \ 19 | "${scriptdir}/../node_modules/speaktome-api/build/stm_web.min.js" \ 20 | "${scriptdir}/../extension/js/vendor/" 21 | cp \ 22 | "${scriptdir}/../node_modules/webextension-polyfill/dist/browser-polyfill.min.js" \ 23 | "${scriptdir}/../extension/js/vendor/" 24 | -------------------------------------------------------------------------------- /docs/acceptance.md: -------------------------------------------------------------------------------- 1 | The following list reflects UI requirements this experiment must meet in order to ship in Test Pilot 2 | 3 | ## Firefox Content Pages 4 | - [ ] It should let users use voice to conduct a search on about:home 5 | - [ ] It should let users use voice to conduct a search on about:newtab 6 | - [ ] It should work in all release versions of Firefox (54-56) 7 | - [ ] It should replace the search arrow with a focusable and click-able microphone icon 8 | - [ ] It should swap the default icon back in place if the user types the search input 9 | - [ ] It should be trigger-able by a click or enter event on the microphone icon 10 | 11 | ## Popular SERP Pages (Google, Yahoo, DDG) [Stretch] 12 | - [ ] It should let users conduct voice search on popular search engine pages 13 | - [ ] It should modify the UI on these pages to include audio input similar to that on about:home and about:newtab 14 | 15 | ## Context Click UI 16 | - [ ] It should create a context menu item for text input on appropriate fields 17 | - [ ] It should modify the UI on these pages to include audio input similar to that on 18 | - [ ] It should *not* auto-submit any forms. 19 | - [ ] It should have an explicit confirmation button to complete input into the original field 20 | 21 | ## Voice Input Panel 22 | - [ ] It should not interfere with any underlying page content (no styles inherited in overlay from underlying page content) 23 | - [ ] It should clearly display in UI that audio is being recorded 24 | - [ ] It should provide continuous visual feedback as long as speech is being recorded 25 | - [ ] it should provide an audio confirmation that a search has been completed/submitted 26 | - [ ] It should be dismiss-able should the user want to opt out of the search 27 | - [ ] Since our model returns multiple results, it should let users select and modify low-confidence substrings before submission. 28 | - [ ] It should be clear that the interface is built by Firefox, and not provided by the underlying page content. 29 | - [ ] It should have an option to automatically submit the highest confidence result and initiate a search. Note: this option should be disabled for context-click initiated searches. 30 | 31 | ## A11y 32 | - [ ] All buttons and links should have visible focus states 33 | - [ ] All buttons and links should be accessible via keyed entry (tab selection) 34 | - [ ] All form elements should include appropriate label attributes 35 | - [ ] All grouped buttons should be nested in a `
` and described with a legend 36 | - [ ] All UI should be verified to use A11y friendly contrast ratios 37 | -------------------------------------------------------------------------------- /docs/metrics.md: -------------------------------------------------------------------------------- 1 | # Voice Fill Metrics 2 | The metrics collection and analysis plan for Voice Fill, a forthcoming Test Pilot experiment. 3 | 4 | ## Definitions 5 | - **Session** - an atomic unit of interaction with Voice Fill, containing 1 or more attempts. Begins when the user initiates recording, and ends when either 1) the user submits that form, or 2) the user exits the session without accepting a Voice Fill suggestion. 6 | - **Attempt** – the subset of a session between when a user initiates recording and either 1) accepts or rejects the suggestions, or 2) cancels the session. If the attempt is not cancelled before receiving a response from the speech-to-text service, an attempt will contain 0 or more suggestions. 7 | - **Suggestion** – a single suggested string of text, as returned from the speech-to-text engine. Has a paired confidence level. 8 | 9 | ## Analysis 10 | Data collected by Voice Fill will be used to answer the following high-level questions: 11 | 12 | - Is the Voice Fill experience compelling? 13 | - How often do users reject suggestions and try again? 14 | - How often do users accept and modify suggestions? 15 | - What are users’ tolerance for inaccuracy? 16 | - Where do users use Voice Fill? 17 | - In what contexts are they most likely to initiate? 18 | - What methods do users use to initiate? 19 | - Might users be better-served by different or additional controls? 20 | - What is the value in showing more than one result? 21 | - Is the most-confident suggestion sufficiently accurate? 22 | 23 | ## Collection 24 | Data will be collected with Google Analytics and follow [Test Pilot standards](https://github.com/mozilla/testpilot/blob/master/docs/experiments/ga.md) for reporting. Voice Fill sessions will be marked by [Google Analytics sessions](https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#session), beginning and ending according to the definition above. 25 | 26 | ### Custom Metrics 27 | - `cm1` - the number of attempts made in a session. 28 | - `cm2` - the confidence level of the accepted suggestion, if one was accepted; otherwise omitted. Integer between `1` and `100`, inclusive. 29 | - `cm3` - the index of an accepted suggestion, if one was accepted; otherwise omitted. 30 | - `cm4` - the elapsed time in ms spent recording an attempt. 31 | - `cm5` - the elapsed time in ms waiting for a response from the speech-to-text engine. 32 | 33 | ### Custom Dimensions 34 | - `cd1` - the outcome of a session or attempt. One of `default accepted`,`accepted`, `rejected`, and `reset`. 35 | - `cd2` - the location from which a session is initiated. One of `google`, `duckduckgo`, `Yahoo`, `generic`. 36 | - `cd3` - the UI element from which the session was initiated. One of `button`, `context menu`, `keyboard`. 37 | - `cd4` - whether the accepted submission was modified before being submitted. One of `true`, `false`. 38 | - `cd5` - whether the user viewed additional suggestions. 39 | 40 | ### Events 41 | 42 | #### `session` 43 | Triggered when a session ends. Includes: 44 | 45 | - `ec` - `voice fill` 46 | - `ea` - `session` 47 | - `sc` - `end` 48 | - `cm1` 49 | - `cm2` 50 | - `cm3` 51 | - `cm4` (for the attempt from which a suggestion was accepted, if a suggestion was accepted; otherwise omitted) 52 | - `cm5` (for the attempt from which a suggestion was accepted, if a suggestion was accepted; otherwise omitted) 53 | - `cd1` (for the session) 54 | - `cd2` 55 | - `cd3` 56 | - `cd4` (for the attempt from which a suggestion was accepted, if a suggestion was accepted; otherwise omitted) 57 | 58 | #### `attempt` 59 | Triggered whenever an attempt is acted upon. Includes: 60 | 61 | - `ec` - `voice fill` 62 | - `ea` - `attempt` 63 | - `cm2` 64 | - `cm3` 65 | - `cm4` 66 | - `cm5` 67 | - `cd1` (for this attempt) 68 | - `cd5` 69 | 70 | ## Additional Collection 71 | The uploaded voice samples and the JSON response containing the suggestions are 72 | saved together and identified by a random UUID. We do not collect any 73 | identifying information or the selected search suggestion. This information is 74 | used to improve the voice recognition software and is kept as long as it is 75 | useful for that purpose. 76 | -------------------------------------------------------------------------------- /extension/assets/animations/Error.json: -------------------------------------------------------------------------------- 1 | {"v":"4.7.0","fr":60,"ip":437,"op":458,"w":400,"h":300,"nm":"speaker","ddd":0,"assets":[],"fonts":{"list":[{"fName":"FiraSans-BoldItalic","fFamily":"Fira Sans","fStyle":"Bold Italic","ascent":81.8987426757812}]},"layers":[{"ddd":0,"ind":1,"ty":5,"nm":"!!","ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":451,"s":[236.885,52.048,0],"e":[236.885,47.048,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":453,"s":[236.885,47.048,0],"e":[236.885,52.048,0],"to":[0,0,0],"ti":[0,0,0]},{"t":457}]},"a":{"a":0,"k":[7.885,6.548,0]},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"n":["0p667_1_0p333_0","0p667_1_0p333_0","0p667_1_0p333_0"],"t":451,"s":[100,0,100],"e":[100,121,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"n":["0p667_1_0p333_0","0p667_1_0p333_0","0p667_1_0p333_0"],"t":453,"s":[100,121,100],"e":[100,100,100]},{"t":457}]}},"ao":0,"t":{"d":{"k":[{"s":{"s":32,"f":"FiraSans-BoldItalic","t":"!!","j":0,"tr":0,"lh":38.4,"ls":-6,"fc":[1,0.15,0.34]},"t":0}]},"p":{},"m":{"g":1,"a":{"a":0,"k":[0,0]}},"a":[]},"ip":443,"op":2420,"st":0,"bm":0,"sr":1},{"ddd":0,"ind":2,"ty":4,"nm":"Mouth","parent":6,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[-0.253,11.175,0]},"a":{"a":0,"k":[-2.25,-25.25,0]},"s":{"a":0,"k":[101.01,101.01,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":447,"s":[{"i":[[0,0],[4.5,0],[0,0]],"o":[[0,0],[-4.5,0],[0,0]],"v":[[3.25,-27.5],[-2.5,-23],[-7.75,-27.5]],"c":false}],"e":[{"i":[[0,0],[4.5,0],[0,0]],"o":[[0,0],[-4.5,0],[0,0]],"v":[[3.25,-26.906],[-2.5,-30.328],[-7.75,-26.906]],"c":false}]},{"t":452}]},"nm":"Path 1","mn":"ADBE Vector Shape - Group"},{"ty":"st","c":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":437,"s":[0,0.7843137,0.8431373,1],"e":[1,0.4078431,0.3490196,1]},{"t":441}]},"o":{"a":0,"k":100},"w":{"a":0,"k":4},"lc":2,"lj":1,"ml":4,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke"},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":2,"cix":2,"ix":1,"mn":"ADBE Vector Group"}],"ip":0,"op":2420,"st":0,"bm":0,"sr":1},{"ddd":0,"ind":3,"ty":4,"nm":"Eye right","parent":6,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[13.131,-7.142,0]},"a":{"a":0,"k":[11,-43.384,0]},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"n":["0p667_1_0p333_0","0p667_1_0p333_0","0p667_1_0p333_0"],"t":436,"s":[101.01,119.01,100],"e":[146.01,22.01,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"n":["0p667_1_0p333_0","0p667_1_0p333_0","0p667_1_0p333_0"],"t":448,"s":[146.01,22.01,100],"e":[101.01,101.01,100]},{"t":454}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-1.2],[0,0],[1.206,0],[0,1.202],[0,0],[-1.212,0]],"o":[[0,0],[0,1.202],[-1.204,0],[0,0],[0,-1.2],[1.206,0]],"v":[[13.184,-50.44],[13.184,-45.56],[11,-43.384],[8.816,-45.56],[8.816,-50.44],[11,-52.616]],"c":true}},"nm":"Path 1","mn":"ADBE Vector Shape - Group"},{"ty":"fl","c":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":437,"s":[0,0.7843137,0.8431373,1],"e":[1,0.4078431,0.3490196,1]},{"t":441}]},"o":{"a":0,"k":100},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill"},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":2,"cix":2,"ix":1,"mn":"ADBE Vector Group"}],"ip":0,"op":2420,"st":0,"bm":0,"sr":1},{"ddd":0,"ind":4,"ty":4,"nm":"Eye left","parent":6,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[-16.414,-7.142,0]},"a":{"a":0,"k":[11,-43.384,0]},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"n":["0p667_1_0p333_0","0p667_1_0p333_0","0p667_1_0p333_0"],"t":436,"s":[101.01,119.01,100],"e":[146.01,22.01,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"n":["0p667_1_0p333_0","0p667_1_0p333_0","0p667_1_0p333_0"],"t":448,"s":[146.01,22.01,100],"e":[101.01,101.01,100]},{"t":454}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-1.2],[0,0],[1.206,0],[0,1.202],[0,0],[-1.212,0]],"o":[[0,0],[0,1.202],[-1.204,0],[0,0],[0,-1.2],[1.206,0]],"v":[[13.184,-50.44],[13.184,-45.56],[11,-43.384],[8.816,-45.56],[8.816,-50.44],[11,-52.616]],"c":true}},"nm":"Path 1","mn":"ADBE Vector Shape - Group"},{"ty":"fl","c":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":437,"s":[0,0.7843137,0.8431373,1],"e":[1,0.4627451,0.4078431,1]},{"t":441}]},"o":{"a":0,"k":100},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill"},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":2,"cix":2,"ix":1,"mn":"ADBE Vector Group"}],"ip":0,"op":2420,"st":0,"bm":0,"sr":1},{"ddd":0,"ind":5,"ty":4,"nm":"Shine","ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":440,"s":[169.09,56.697,0],"e":[169.09,70.697,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":448,"s":[169.09,70.697,0],"e":[169.09,56.697,0],"to":[0,0,0],"ti":[0,0,0]},{"t":454}]},"a":{"a":0,"k":[-27.16,-51.365,0]},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":440,"s":[100,100,100],"e":[20,69,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":448,"s":[20,69,100],"e":[100,100,100]},{"t":454}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[15.879,0],[0,0],[0,-2.882],[0,0],[-7.533,0],[0,15.824],[0,0]],"o":[[0,0],[-21.42,0],[0,0],[0,19.191],[15.879,0],[0,0],[0,-15.824]],"v":[[-1.593,-51.365],[-1.593,-51.365],[-27.161,-31.448],[-27.161,26.369],[-1.593,51.365],[27.161,22.715],[27.161,-22.713]],"c":true}},"nm":"Path 1","mn":"ADBE Vector Shape - Group"},{"ty":"fl","c":{"a":0,"k":[1,1,1,1]},"o":{"a":0,"k":100},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill"},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":2,"cix":2,"ix":1,"mn":"ADBE Vector Group"}],"ip":0,"op":2420,"st":0,"bm":0,"sr":1},{"ddd":0,"ind":6,"ty":4,"nm":"Face","ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":1,"k":[{"i":{"x":0.833,"y":1},"o":{"x":0.138,"y":0},"n":"0p833_1_0p138_0","t":126,"s":[198.5,113,0],"e":[199,113,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.137,"y":1},"o":{"x":0.138,"y":0},"n":"0p137_1_0p138_0","t":161,"s":[199,113,0],"e":[199,111,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"n":"0p667_1_0p333_0","t":189,"s":[199,111,0],"e":[199,113,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.137,"y":1},"o":{"x":0.333,"y":0},"n":"0p137_1_0p333_0","t":210,"s":[199,113,0],"e":[199,111,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"n":"0p667_1_0p333_0","t":238,"s":[199,111,0],"e":[199,113,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.137,"y":1},"o":{"x":0.167,"y":0},"n":"0p137_1_0p167_0","t":259,"s":[199,113,0],"e":[199,111,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"n":"0p667_1_0p333_0","t":287,"s":[199,111,0],"e":[199,113,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.137,"y":1},"o":{"x":0.167,"y":0},"n":"0p137_1_0p167_0","t":307,"s":[199,113,0],"e":[199,111,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.747,"y":1},"o":{"x":0.333,"y":0},"n":"0p747_1_0p333_0","t":437,"s":[199,111,0],"e":[199,117,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0},"n":"0_1_0p167_0","t":448,"s":[199,117,0],"e":[199,113,0],"to":[0,0,0],"ti":[0,0,0]},{"t":454}]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[99,99,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[18.479,0],[0,18.415],[0,0],[-18.479,0],[0,-18.415],[0,0]],"o":[[-18.479,0],[0,0],[0,-18.415],[18.479,0],[0,0],[0,18.415]],"v":[[-0.001,62.305],[-33.513,28.909],[-33.513,-28.909],[-0.001,-62.305],[33.513,-28.909],[33.513,28.909]],"c":true}},"nm":"Path 1","mn":"ADBE Vector Shape - Group"},{"ty":"gs","o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":153,"s":[0],"e":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":158,"s":[100],"e":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":436,"s":[100],"e":[0]},{"t":441}]},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":153,"s":[4],"e":[5]},{"t":158}]},"g":{"p":3,"k":{"a":0,"k":[0,0,0.996,1,0.5,0,0.89,0.922,1,0,0.784,0.843]}},"s":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":158,"s":[33.38,0.04],"e":[0.012,-61.911],"to":[0,0],"ti":[0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":165,"s":[0.012,-61.911],"e":[-33.141,-0.42],"to":[0,0],"ti":[0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":172,"s":[-33.141,-0.42],"e":[0.755,63.098],"to":[0,0],"ti":[0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":179,"s":[0.755,63.098],"e":[33.38,0.04],"to":[0,0],"ti":[0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":186,"s":[33.38,0.04],"e":[0.012,-61.911],"to":[0,0],"ti":[0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":193,"s":[0.012,-61.911],"e":[-33.141,-0.42],"to":[0,0],"ti":[0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":200,"s":[-33.141,-0.42],"e":[0.755,63.098],"to":[0,0],"ti":[0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":207,"s":[0.755,63.098],"e":[0.012,-61.911],"to":[0,0],"ti":[0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":214,"s":[0.012,-61.911],"e":[-33.141,-0.42],"to":[0,0],"ti":[0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":221,"s":[-33.141,-0.42],"e":[0.755,63.098],"to":[0,0],"ti":[0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":228,"s":[0.755,63.098],"e":[33.38,0.04],"to":[0,0],"ti":[0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":235,"s":[33.38,0.04],"e":[0.012,-61.911],"to":[0,0],"ti":[0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":242,"s":[0.012,-61.911],"e":[-33.141,-0.42],"to":[0,0],"ti":[0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":249,"s":[-33.141,-0.42],"e":[0.755,63.098],"to":[0,0],"ti":[0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":256,"s":[0.755,63.098],"e":[33.38,0.04],"to":[0,0],"ti":[0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":263,"s":[33.38,0.04],"e":[0.012,-61.911],"to":[0,0],"ti":[0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":270,"s":[0.012,-61.911],"e":[-33.141,-0.42],"to":[0,0],"ti":[0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":277,"s":[-33.141,-0.42],"e":[0.755,63.098],"to":[0,0],"ti":[0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":284,"s":[0.755,63.098],"e":[33.38,0.04],"to":[0,0],"ti":[0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":291,"s":[33.38,0.04],"e":[0.012,-61.911],"to":[0,0],"ti":[0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":298,"s":[0.012,-61.911],"e":[-33.141,-0.42],"to":[0,0],"ti":[0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":305,"s":[-33.141,-0.42],"e":[0.755,63.098],"to":[0,0],"ti":[0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":312,"s":[0.755,63.098],"e":[33.38,0.04],"to":[0,0],"ti":[0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":319,"s":[33.38,0.04],"e":[33.38,0.04],"to":[0,0],"ti":[0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":431,"s":[33.38,0.04],"e":[0.012,-61.911],"to":[0,0],"ti":[0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":438,"s":[0.012,-61.911],"e":[-33.141,-0.42],"to":[0,0],"ti":[0,0]},{"t":445}]},"e":{"a":0,"k":[0.506,0.997]},"t":2,"h":{"a":0,"k":0},"a":{"a":0,"k":0},"lc":1,"lj":1,"ml":4,"nm":"Gradient Stroke 1","mn":"ADBE Vector Graphic - G-Stroke"},{"ty":"st","c":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":437,"s":[0,0.7843137,0.8431373,1],"e":[1,0.145098,0.3411765,1]},{"t":441}]},"o":{"a":0,"k":100},"w":{"a":0,"k":4},"lc":1,"lj":1,"ml":4,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke"},{"ty":"fl","c":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":437,"s":[0.8980392,0.9960784,1,1],"e":[1,0.9568627,0.9529412,1]},{"t":441}]},"o":{"a":0,"k":100},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill"},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":4,"cix":2,"ix":1,"mn":"ADBE Vector Group"}],"ip":0,"op":2420,"st":0,"bm":0,"sr":1},{"ddd":0,"ind":8,"ty":4,"nm":"Body","ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[199,188.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"hasMask":true,"masksProperties":[{"inv":false,"mode":"s","pt":{"a":0,"k":{"i":[[2.857,0],[0,2.849],[0,0],[0,0],[0,27.572],[-2.859,0],[0,-2.849],[-24.619,0],[0,24.529],[-2.859,0],[0,-2.849],[27.449,-3.427],[0,0],[0,0],[0.811,-0.036],[-0.014,-0.299],[-0.248,-0.04],[-0.041,0.002],[-1.119,0.137],[0,0]],"o":[[-2.859,0],[0,0],[0,0],[-27.445,-3.429],[0,-2.849],[2.859,0],[0,24.529],[24.619,0],[0,-2.849],[2.859,0],[0,27.572],[0,0],[0,0],[-1.143,0.142],[-0.301,0.013],[0.012,0.26],[0.039,0.006],[0.814,-0.037],[0,0],[0,2.849]],"v":[[-0.411,46.172],[-5.188,43.695],[-5.188,10.723],[-6.88,10.513],[-55.019,-43.863],[-49.833,-49.031],[-44.646,-43.863],[-0.002,0.622],[44.646,-43.863],[49.833,-49.031],[55.019,-43.863],[6.872,10.513],[5.18,10.725],[5.68,11.051],[2.103,11.039],[2.083,11.914],[2.035,12.116],[2.651,12.432],[5.18,11.837],[5.18,43.695]],"c":true}},"o":{"a":0,"k":100},"x":{"a":0,"k":0},"nm":"Mask 1"}],"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,28.941],[4.991,0],[0,-4.973],[22.488,0],[0,22.404],[4.991,0],[0,-4.973],[-28.527,-4.4],[0,0],[-1.513,-1.613],[-2.605,0],[-1.655,1.796],[0,2.348],[0,0]],"o":[[0,-4.973],[-4.991,0],[0,22.404],[-22.487,0],[0,-4.973],[-4.991,0],[0,28.939],[0,0],[0,2.377],[1.653,1.762],[2.634,0],[1.48,-1.607],[0,0],[28.531,-4.397]],"v":[[58.885,-44.054],[49.833,-53.074],[40.78,-44.054],[-0.002,-3.422],[-40.78,-44.054],[-49.833,-53.074],[-58.885,-44.054],[-9.054,13.906],[-9.054,43.504],[-13.701,51.278],[-0.002,52.525],[12.878,51.278],[9.046,43.504],[9.046,13.908]],"c":true}},"nm":"Path 1","mn":"ADBE Vector Shape - Group"},{"ty":"fl","c":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":437,"s":[0,0.7843137,0.8431373,1],"e":[1,0.2196078,0.3960784,1]},{"t":444}]},"o":{"a":0,"k":100},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill"},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"ix":1,"mn":"ADBE Vector Group"}],"ip":0,"op":2420,"st":0,"bm":0,"sr":1},{"ddd":0,"ind":9,"ty":4,"nm":"Shape Layer 9","parent":8,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[-1,0.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"hasMask":true,"masksProperties":[{"inv":false,"mode":"s","pt":{"a":0,"k":{"i":[[0,0],[0,0],[-0.167,2.488],[0,0],[-0.715,-2.882]],"o":[[0,0],[0,0],[0,0],[0,0],[0.888,3.578]],"v":[[1.681,48.31],[-1.3,48.31],[-3.507,45.937],[-3.507,12.859],[2.396,16.067]],"c":true}},"o":{"a":0,"k":100},"x":{"a":0,"k":0},"nm":"Mask 1"}],"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[3.936,0],[0,-3.918],[23.555,0],[0,23.467],[3.932,0],[0,-3.918],[-28.052,-3.504],[0,0],[-3.932,0],[0,3.92],[0,0],[0,28.885]],"o":[[-3.934,0],[0,23.467],[-23.553,0],[0,-3.918],[-3.932,0],[0,28.884],[0,0],[0,3.92],[3.93,0],[0,0],[28.055,-3.504],[0,-3.918]],"v":[[49.832,-50.873],[42.714,-43.779],[-0.001,-1.221],[-42.714,-43.779],[-49.832,-50.873],[-56.951,-43.779],[-7.12,12.507],[-7.12,43.781],[-0.001,50.873],[7.114,43.781],[7.114,12.509],[56.951,-43.779]],"c":true}},"nm":"Path 1","mn":"ADBE Vector Shape - Group"},{"ty":"fl","c":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":437,"s":[0.8980392,0.9960784,1,1],"e":[1,0.9568627,0.9529412,1]},{"t":444}]},"o":{"a":0,"k":100},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill"},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"ix":1,"mn":"ADBE Vector Group"}],"ip":0,"op":2420,"st":0,"bm":0,"sr":1},{"ddd":0,"ind":10,"ty":4,"nm":"Floor","ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[197.82,242.488,0]},"a":{"a":0,"k":[-120.18,-27.512,0]},"s":{"a":1,"k":[{"i":{"x":[0.656,0.656,0.656],"y":[0.954,0.954,0.816]},"o":{"x":[0.157,0.157,0.157],"y":[0,0,0]},"n":["0p656_0p954_0p157_0","0p656_0p954_0p157_0","0p656_0p816_0p157_0"],"t":78,"s":[7.72,7.72,100],"e":[113.419,113.419,100]},{"i":{"x":[0.8,0.8,0.8],"y":[1,1,1]},"o":{"x":[0.437,0.437,0.437],"y":[-0.167,-0.167,0.138]},"n":["0p8_1_0p437_-0p167","0p8_1_0p437_-0p167","0p8_1_0p437_0p138"],"t":95,"s":[113.419,113.419,100],"e":[91.72,91.72,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"n":["0p667_1_0p167_0","0p667_1_0p167_0","0p667_1_0p167_0"],"t":105,"s":[91.72,91.72,100],"e":[91.72,91.72,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"n":["0p667_1_0p333_0","0p667_1_0p333_0","0p667_1_0p333_0"],"t":289,"s":[91.72,91.72,100],"e":[65,65,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"n":["0p667_1_0p333_0","0p667_1_0p333_0","0p667_1_0p333_0"],"t":317,"s":[65,65,100],"e":[91.72,91.72,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"n":["0p833_1_0p167_0","0p833_1_0p167_0","0p833_1_0p167_0"],"t":439,"s":[91.72,91.72,100],"e":[164.72,164.72,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"n":["0p667_1_0p167_0","0p667_1_0p167_0","0p667_1_0p167_0"],"t":448,"s":[164.72,164.72,100],"e":[91.72,91.72,100]},{"t":454}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[101.18,18.844]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse"},{"ty":"fl","c":{"a":0,"k":[0.9764706,0.9764706,0.9803922,1]},"o":{"a":0,"k":100},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill"},{"ty":"tr","p":{"a":0,"k":[-120.18,-27.512],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":2,"cix":2,"ix":1,"mn":"ADBE Vector Group"}],"ip":0,"op":2420,"st":0,"bm":0,"sr":1},{"ddd":0,"ind":11,"ty":4,"nm":"Shape Layer 3","ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[210,150,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[422.803,306.309]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect"},{"ty":"fl","c":{"a":0,"k":[1,1,1,1]},"o":{"a":0,"k":100},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill"},{"ty":"tr","p":{"a":0,"k":[-2.599,-1.846],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"ix":1,"mn":"ADBE Vector Group"}],"ip":0,"op":2420,"st":0,"bm":0,"sr":1}],"chars":[{"ch":"!","size":32,"style":"Bold Italic","w":23.1,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[17.725,-25.681],[25.983,-69.791],[9.265,-69.791],[5.035,-25.681]],"o":[[17.725,-25.681],[25.983,-69.791],[9.265,-69.791],[5.035,-25.681]],"v":[[17.725,-25.681],[25.983,-69.791],[9.265,-69.791],[5.035,-25.681]],"c":true}},"nm":"!","mn":"ADBE Vector Shape - Group"},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[14.905,-16.516],[0.201,-11.783],[3.525,1.712],[18.228,-2.921]],"o":[[4.934,-16.516],[0.201,-1.913],[13.596,1.712],[18.228,-12.891]],"v":[[10.272,-16.516],[0.201,-6.546],[8.258,1.712],[18.228,-8.359]],"c":true}},"nm":"!","mn":"ADBE Vector Shape - Group"}],"nm":"!","np":5,"cix":2,"ix":1,"mn":"ADBE Vector Group"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[40.823,-25.681],[49.081,-69.791],[32.364,-69.791],[28.134,-25.681]],"c":true}},"nm":"!","mn":"ADBE Vector Shape - Group"},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[4.633,0],[0,-5.237],[-4.733,0],[0,5.438]],"o":[[-5.338,0],[0,4.633],[5.338,0],[0,-4.532]],"v":[[33.371,-16.516],[23.3,-6.546],[31.357,1.712],[41.327,-8.359]],"c":true}},"nm":"!","mn":"ADBE Vector Shape - Group"},{"ty":"mm","mm":1,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge"}],"nm":"!","np":5,"cix":2,"ix":2,"mn":"ADBE Vector Group"}]},"fFamily":"Fira Sans"}]} -------------------------------------------------------------------------------- /extension/assets/images/feedback.svg: -------------------------------------------------------------------------------- 1 | Combined Shape -------------------------------------------------------------------------------- /extension/assets/images/ff-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/voicefill/783690048caf5fcb8b09cd3e921a184b5d1019ee/extension/assets/images/ff-logo.png -------------------------------------------------------------------------------- /extension/assets/images/icon settings.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 14 | 15 | -------------------------------------------------------------------------------- /extension/assets/images/icon-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/voicefill/783690048caf5fcb8b09cd3e921a184b5d1019ee/extension/assets/images/icon-48.png -------------------------------------------------------------------------------- /extension/assets/images/icon-96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/voicefill/783690048caf5fcb8b09cd3e921a184b5d1019ee/extension/assets/images/icon-96.png -------------------------------------------------------------------------------- /extension/assets/images/icon-close.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /extension/assets/images/icon-done-rtl.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | path-1_1_ 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /extension/assets/images/icon-done.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | path-1_1_ 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /extension/assets/images/icon-mic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /extension/assets/images/icon-redo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /extension/assets/images/mic.svg: -------------------------------------------------------------------------------- 1 | mic 2 | -------------------------------------------------------------------------------- /extension/assets/images/smileface.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Favicon 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /extension/css/main.css: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | #stm-popup { 6 | align-items: stretch; 7 | animation-fill-mode: forwards; 8 | background-color: white; 9 | background-repeat: no-repeat; 10 | background-size: 102px 38px; 11 | box-shadow: 0 0 1rem #979797; 12 | box-sizing: border-box; 13 | display: none; 14 | flex-direction: column; 15 | font: message-box; 16 | height: 310px; 17 | justify-content: center; 18 | left: 0; 19 | opacity: 0; 20 | padding: 2em 2em 4em; 21 | position: fixed; 22 | right: 0; 23 | top: -999px; 24 | transition: height 500ms; 25 | will-change: left, opacity; 26 | z-index: 9999999; 27 | } 28 | 29 | #stm-popup.rtl { 30 | background-position: bottom 2em right 2em; 31 | } 32 | 33 | #stm-popup:not(.rtl) { 34 | background-position: bottom 2em left 2em; 35 | } 36 | 37 | #stm-popup:not(.stm-drop-out) { 38 | animation-duration: 250ms; 39 | animation-name: stm-drop-in; 40 | animation-timing-function: ease-out; 41 | } 42 | 43 | #stm-popup.stm-drop-out { 44 | animation-duration: 500ms; 45 | animation-name: stm-drop-out; 46 | animation-timing-function: ease-in; 47 | } 48 | 49 | #stm-inject { 50 | align-items: center; 51 | display: flex; 52 | flex-direction: column; 53 | flex: 1 0; 54 | justify-content: center; 55 | position: relative; 56 | width: 100%; 57 | top: -9px; 58 | } 59 | 60 | .stm-done-animation { 61 | transform-origin: center 100%; 62 | animation: leave 450ms forwards; 63 | } 64 | 65 | #stm-header { 66 | width: 100%; 67 | flex: 20px auto; 68 | } 69 | 70 | #stm-footer { 71 | text-align: center; 72 | font-size: 10px; 73 | color: #888; 74 | width: 100%; 75 | position: absolute; 76 | bottom: 2em; 77 | } 78 | 79 | #stm-close { 80 | background-position: center center; 81 | background-repeat: no-repeat; 82 | background-size: 20px 20px; 83 | border-radius: 50%; 84 | cursor: pointer; 85 | height: 40px; 86 | margin-top: -10; 87 | position: relative; 88 | transition: background-color 50ms; 89 | width: 40px; 90 | z-index: 10; 91 | } 92 | 93 | #stm-close.rtl { 94 | float: left; 95 | } 96 | 97 | #stm-close:not(.rtl) { 98 | float: right; 99 | } 100 | 101 | #stm-close:hover { 102 | background-color: #f0f0f0; 103 | } 104 | 105 | #stm-feedback { 106 | background-color: #00c8d7; 107 | background-position: 2px 4px; 108 | background-repeat: no-repeat; 109 | background-size: 18px; 110 | border: 1px solid #00c8d7; 111 | border-radius: 3px; 112 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); 113 | color: #fff; 114 | cursor: pointer; 115 | display: block; 116 | float: right; 117 | font-size: 12px; 118 | line-height: 12px; 119 | padding: 5px; 120 | position: absolute; 121 | bottom: 2em; 122 | z-index: 11; 123 | opacity: 0.7; 124 | overflow: hidden; 125 | width: 12px; 126 | text-indent: 17px; 127 | transition: all 150ms ease-in-out; 128 | box-sizing: content-box; 129 | } 130 | 131 | #stm-feedback.rtl { 132 | left: 2em; 133 | } 134 | 135 | #stm-feedback:not(.rtl) { 136 | right: 2em; 137 | } 138 | 139 | #stm-feedback:hover, 140 | #stm-feedback:focus { 141 | background-color: #00b8c7; 142 | text-decoration: none; 143 | width: 57px; 144 | text-indent: 2px; 145 | padding: 5px 5px 5px 20px; 146 | transition: all 150ms ease-in-out; 147 | } 148 | 149 | #stm-feedback:active { 150 | background-color: #00b8c7; 151 | text-decoration: none; 152 | } 153 | 154 | #stm-animation-wrapper { 155 | background: transparent; 156 | height: 150px; 157 | position: relative; 158 | width: 400px; 159 | top: -15px; 160 | } 161 | 162 | .stm-start-animation { 163 | opacity: 0; 164 | transform: translateY(-20px); 165 | animation: drop 500ms forwards 250ms; 166 | } 167 | 168 | #stm-levels-wrapper { 169 | box-sizing: border-box; 170 | height: 310px; 171 | position: absolute; 172 | top: -52px; 173 | width: 100%; 174 | z-index: 2; 175 | } 176 | 177 | #stm-levels { 178 | display: block; 179 | height: 310px; 180 | margin: 0 auto; 181 | width: 720px; 182 | } 183 | 184 | #stm-box { 185 | background: none; 186 | height: 150px; 187 | overflow: hidden; 188 | position: relative; 189 | width: 400px; 190 | } 191 | 192 | #stm-content { 193 | color: #737373; 194 | display: block; 195 | font-size: 22px; 196 | height: 24px; 197 | position: relative; 198 | font-weight: 300; 199 | letter-spacing: 0.5px; 200 | top: -15px; 201 | } 202 | 203 | #stm-intro-text { 204 | color: #0c0c0d; 205 | } 206 | 207 | #stm-listening-text { 208 | opacity: 0.4; 209 | animation: fade-in 1000ms alternate infinite linear; 210 | } 211 | 212 | #stm-selection-wrapper { 213 | align-items: center; 214 | display: flex; 215 | height: 40px; 216 | justify-content: space-around; 217 | } 218 | 219 | #stm-list-wrapper { 220 | position: relative; 221 | flex: 1; 222 | } 223 | 224 | #stm-input { 225 | animation: pop forwards 300ms ease-in; 226 | background: #99fb85; 227 | border: none; 228 | box-sizing: border-box; 229 | color: #0c0c0d; 230 | display: block; 231 | font-size: 28px; 232 | height: 40px; 233 | opacity: 0; 234 | padding: 4px 8px; 235 | position: relative; 236 | transition: background 75ms, box-shadow 75ms; 237 | max-width: 600px; 238 | } 239 | 240 | #stm-input:focus { 241 | background: #f9f9fa; 242 | box-shadow: 0 -2px 0 #33f70c inset; 243 | } 244 | 245 | #stm-list { 246 | animation: scale 150ms forwards ease-in-out; 247 | animation-delay: 450ms; 248 | background: #fff; 249 | border-radius: 1px; 250 | border: 1px solid #e1e1e5; 251 | border-top: 0; 252 | box-sizing: border-box; 253 | color: #737373; 254 | display: block; 255 | font-size: 16px; 256 | line-height: 16px; 257 | opacity: 0; 258 | position: absolute; 259 | transform-origin: top; 260 | transform: scaleY(0); 261 | width: 100%; 262 | } 263 | 264 | #stm-list.close { 265 | animation: scale 150ms backwards ease-in-out; 266 | } 267 | 268 | .stm-list-inner { 269 | box-sizing: border-box; 270 | list-style: none; 271 | margin: 0; 272 | padding: 0px; 273 | width: 100%; 274 | } 275 | 276 | .stm-list-inner li { 277 | cursor: pointer; 278 | opacity: 0.8; 279 | padding: 10px 7px; 280 | transition: background 50ms, opacity 50ms; 281 | } 282 | 283 | .stm-list-inner li:hover, 284 | .stm-list-inner li:focus { 285 | background: #e1e1e5; 286 | opacity: 1; 287 | } 288 | 289 | #stm-reset-button { 290 | animation: pop forwards 300ms ease-in; 291 | animation-delay: 150ms; 292 | background-color: transparent; 293 | background-position: center center; 294 | background-repeat: no-repeat; 295 | background-size: 16px 16px; 296 | border-radius: 50%; 297 | border: 1px solid #f0f0f0; 298 | cursor: pointer; 299 | height: 40px; 300 | margin: 0 24px 0 40px; 301 | opacity: 0; 302 | transition: background-color 50ms; 303 | width: 40px; 304 | } 305 | 306 | #stm-reset-button:hover { 307 | background-color: #f0f0f0; 308 | } 309 | 310 | #stm-reset-button:hover { 311 | background-color: #eaeaea; 312 | } 313 | 314 | #stm-submit-button { 315 | animation: pop forwards 300ms ease-in; 316 | animation-delay: 300ms; 317 | background-color: #00c8d7; 318 | background-position: center center; 319 | background-repeat: no-repeat; 320 | background-size: 32px 28px; 321 | border-radius: 50%; 322 | border: none; 323 | box-shadow: transparent; 324 | cursor: pointer; 325 | height: 64px; 326 | opacity: 0; 327 | top: -12px; 328 | transition: background-color 50ms, opacity 50ms, box-shadow 100ms; 329 | width: 64px; 330 | } 331 | 332 | .stm-icon { 333 | background-color: #00c8d7; 334 | background-position: center center; 335 | background-repeat: no-repeat; 336 | background-size: 24px 24px; 337 | border-radius: 50%; 338 | border: none; 339 | box-shadow: transparent; 340 | box-sizing: border-box; 341 | height: 48px; 342 | position: absolute; 343 | top: -1px; 344 | transition: background-color 50ms, opacity 50ms, box-shadow 100ms; 345 | width: 48px; 346 | z-index: 9999999; 347 | } 348 | 349 | .stm-icon.rtl { 350 | left: -64px; 351 | } 352 | 353 | .stm-icon:not(.rtl) { 354 | right: -64px; 355 | } 356 | 357 | .stm-icon:not(.stm-hidden) { 358 | cursor: pointer; 359 | opacity: 0; 360 | animation: pop 300ms forwards ease-out; 361 | } 362 | 363 | .stm-icon.stm-hidden { 364 | opacity: 0.1; 365 | pointer-events: none; 366 | } 367 | 368 | .stm-icon:hover:not(.stm-hidden), 369 | .stm-icon:focus:not(.stm-hidden), 370 | #stm-submit-button:hover, 371 | #stm-submit-button:focus { 372 | background-color: #00b8c7; 373 | } 374 | 375 | .stm-icon:active:not(.stm-hidden), 376 | #stm-submit-button:active { 377 | background-color: #00a8b7; 378 | box-shadow: 0 0 0 2px #00a8b7; 379 | } 380 | 381 | @keyframes fade-in { 382 | 0% { 383 | opacity: 0.4; 384 | } 385 | 100% { 386 | opacity: 1; 387 | } 388 | } 389 | 390 | @keyframes stm-drop-in { 391 | 100% { 392 | opacity: 1; 393 | top: 0; 394 | } 395 | } 396 | 397 | @keyframes stm-drop-out { 398 | 0% { 399 | opacity: 1; 400 | top: 0; 401 | } 402 | 100% { 403 | opacity: 0; 404 | top: -999px; 405 | } 406 | } 407 | 408 | @keyframes pop { 409 | 0% { 410 | opacity: 0; 411 | transform: scale(1); 412 | } 413 | 66% { 414 | opacity: 1; 415 | transform: scale(1.04); 416 | } 417 | 100% { 418 | opacity: 1; 419 | transform: scale(1); 420 | } 421 | } 422 | 423 | @keyframes scale { 424 | 0% { 425 | opacity: 0; 426 | transform: scaleY(0); 427 | } 428 | 100% { 429 | opacity: 1; 430 | transform: scaleY(1); 431 | } 432 | } 433 | 434 | @keyframes drop { 435 | 0% { 436 | opacity: 0; 437 | transform: translateY(-20px); 438 | } 439 | 50% { 440 | opacity: 1; 441 | } 442 | 100% { 443 | opacity: 1; 444 | transform: translateY(0px); 445 | } 446 | } 447 | 448 | @keyframes leave { 449 | 0% { 450 | opacity: 1; 451 | transform: scaleY(1); 452 | } 453 | 30% { 454 | opacity: 1; 455 | transform: scaleY(1); 456 | } 457 | 458 | 80% { 459 | opacity: 1; 460 | transform: scaleY(0.9); 461 | } 462 | 100% { 463 | opacity: 0; 464 | transform: scaleY(1.1); 465 | transform: translateY(-100px); 466 | } 467 | } 468 | -------------------------------------------------------------------------------- /extension/css/options.css: -------------------------------------------------------------------------------- 1 | form { 2 | display: grid; 3 | grid-gap: 1rem; 4 | grid-template-columns: 10rem 20rem; 5 | } 6 | 7 | label { 8 | grid-column: 1 / 2; 9 | } 10 | 11 | select { 12 | grid-column: 2 / 3; 13 | height: 2rem; 14 | } 15 | -------------------------------------------------------------------------------- /extension/js/background.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | if (typeof browser.runtime.getBrowserInfo === "function") { 6 | const TRACKING_ID = "UA-35433268-80"; 7 | 8 | const analytics = new TestPilotGA({ 9 | tid: TRACKING_ID, 10 | ds: "addon", 11 | an: "Voice Fill", 12 | aid: "voicefill@mozilla.com", 13 | av: "1.4.3", 14 | }); 15 | 16 | browser.runtime.onMessage.addListener((event) => { 17 | console.log("[metrics] Event successfully sent. Calling analytics."); 18 | 19 | analytics 20 | .sendEvent("voice fill", event.type, event.content) 21 | .then((response) => { 22 | console.log("[metrics] Event successfully sent", response); 23 | }) 24 | .catch((response, err) => { 25 | console.error("[metrics] Event failed while sending", response, err); 26 | }); 27 | }); 28 | } 29 | 30 | browser.browserAction.onClicked.addListener(function() { 31 | browser.storage.sync 32 | .get("searchProvider") 33 | .then((result) => { 34 | const url = result.searchProvider || "https://www.google.com"; 35 | 36 | return browser.tabs.create({url}); 37 | }) 38 | .then((tab) => { 39 | const intervalConnection = setInterval(() => { 40 | browser.tabs 41 | .sendMessage(tab.id, { 42 | msg: "background script syn", 43 | }) 44 | .then((response) => { 45 | clearInterval(intervalConnection); 46 | }) 47 | .catch((error) => { 48 | // console.error(`Not connected yet. Retrying ${error}`); 49 | }); 50 | }, 100); 51 | }) 52 | .catch((error) => { 53 | console.log(`Error: ${error}`); 54 | }); 55 | }); 56 | 57 | // Chrome won't accept an SVG as the toolbar icon, so we'll just draw it 58 | // manually. 59 | (function() { 60 | const canvas = document.createElement("canvas"); 61 | canvas.height = 16; 62 | canvas.width = 16; 63 | 64 | const image = new Image(16, 16); 65 | image.onload = () => { 66 | const context = canvas.getContext("2d"); 67 | context.drawImage(image, 0, 0, 16, 16); 68 | chrome.browserAction.setIcon({ 69 | imageData: context.getImageData(0, 0, 16, 16), 70 | }); 71 | }; 72 | image.src = "/assets/images/mic.svg"; 73 | })(); 74 | -------------------------------------------------------------------------------- /extension/js/content.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | (function() { 6 | console.log("Speak To Me starting up..."); 7 | 8 | const LOCAL_TEST = false; 9 | 10 | const DONE_ANIMATION = 11 | browser.extension.getURL("/assets/animations/Done.json"); 12 | const SPINNING_ANIMATION = 13 | browser.extension.getURL("/assets/animations/Spinning.json"); 14 | const START_ANIMATION = 15 | browser.extension.getURL("/assets/animations/Start.json"); 16 | const ERROR_ANIMATION = 17 | browser.extension.getURL("/assets/animations/Error.json"); 18 | 19 | // Encapsulation of the popup we use to provide our UI. 20 | const POPUP_WRAPPER_MARKUP = `
21 |
22 |
23 |
24 |
25 | 31 | 33 | Feedback 34 | 35 |
`; 36 | 37 | // When submitting, this markup is passed in 38 | const SUBMISSION_MARKUP = `
39 | 40 |
41 |
42 |
43 |
44 |
45 |
Warming up...
46 |
`; 47 | 48 | // When Selecting, this markup is passed in 49 | const SELECTION_MARKUP = `
50 |
51 | 52 |
53 |
54 | 55 | 56 |
`; 57 | 58 | const metrics = new Metrics(); 59 | let languages = {}; 60 | let language; 61 | let stm; 62 | let audioContext; 63 | let sourceNode; 64 | let analyzerNode; 65 | let outputNode; 66 | let listening = false; 67 | 68 | const languagePromise = fetch(browser.extension.getURL("/js/languages.json")) 69 | .then((response) => { 70 | return response.json(); 71 | }) 72 | .then((l) => { 73 | languages = l; 74 | return browser.storage.sync.get("language"); 75 | }) 76 | .then((item) => { 77 | if (!item.language) { 78 | throw new Error("Language not set"); 79 | } 80 | 81 | language = item.language; 82 | }) 83 | .catch(() => { 84 | language = languages.hasOwnProperty(navigator.language) 85 | ? navigator.language 86 | : "en-US"; 87 | }) 88 | .then(() => { 89 | stm = SpeakToMe({ 90 | listener, 91 | serverURL: "https://speaktome-2.services.mozilla.com", 92 | timeout: 6000, 93 | language, 94 | productTag: "vf", 95 | // maxsilence: 1500, 96 | }); 97 | }); 98 | 99 | if (!navigator.mediaDevices || 100 | !(navigator.mediaDevices.getUserMedia || 101 | navigator.webkitGetUserMedia || 102 | navigator.mozGetUserMedia)) { 103 | console.error( 104 | "You need a browser with getUserMedia support to use Speak To Me, sorry!" 105 | ); 106 | return; 107 | } 108 | 109 | const escapeHTML = (str) => { 110 | // Note: string cast using String; may throw if `str` is non-serializable, 111 | // e.g. a Symbol. Most often this is not the case though. 112 | return String(str) 113 | .replace(/&/g, "&") 114 | .replace(/"/g, """) 115 | .replace(/'/g, "'") 116 | .replace(//g, ">"); 118 | }; 119 | 120 | // eslint-disable-next-line complexity 121 | const getSTMAnchors = (documentDomain) => { 122 | if (documentDomain.endsWith(".search.yahoo.com")) { 123 | return { 124 | input: "yschsp", 125 | anchor: "sbx", 126 | }; 127 | } 128 | 129 | switch (documentDomain) { 130 | case "www.google.com": 131 | case "www.google.ca": 132 | case "www.google.tn": 133 | case "www.google.fr": 134 | case "www.google.ad": 135 | case "www.google.ae": 136 | case "www.google.com.af": 137 | case "www.google.com.ag": 138 | case "www.google.com.ai": 139 | case "www.google.al": 140 | case "www.google.am": 141 | case "www.google.co.ao": 142 | case "www.google.com.ar": 143 | case "www.google.as": 144 | case "www.google.at": 145 | case "www.google.com.au": 146 | case "www.google.az": 147 | case "www.google.ba": 148 | case "www.google.com.bd": 149 | case "www.google.be": 150 | case "www.google.bf": 151 | case "www.google.bg": 152 | case "www.google.com.bh": 153 | case "www.google.bi": 154 | case "www.google.bj": 155 | case "www.google.com.bn": 156 | case "www.google.com.bo": 157 | case "www.google.com.br": 158 | case "www.google.bs": 159 | case "www.google.bt": 160 | case "www.google.co.bw": 161 | case "www.google.by": 162 | case "www.google.com.bz": 163 | case "www.google.com.kh": 164 | case "www.google.cc": 165 | case "www.google.cd": 166 | case "www.google.cf": 167 | case "www.google.cg": 168 | case "www.google.ch": 169 | case "www.google.ci": 170 | case "www.google.co.ck": 171 | case "www.google.cl": 172 | case "www.google.cm": 173 | case "www.google.cn": 174 | case "www.google.com.co": 175 | case "www.google.co.cr": 176 | case "www.google.com.cu": 177 | case "www.google.cv": 178 | case "www.google.com.cy": 179 | case "www.google.cz": 180 | case "www.google.de": 181 | case "www.google.dj": 182 | case "www.google.dk": 183 | case "www.google.dm": 184 | case "www.google.com.do": 185 | case "www.google.dz": 186 | case "www.google.com.ec": 187 | case "www.google.ee": 188 | case "www.google.com.eg": 189 | case "www.google.es": 190 | case "www.google.com.et": 191 | case "www.google.fi": 192 | case "www.google.com.fj": 193 | case "www.google.fm": 194 | case "www.google.ga": 195 | case "www.google.ge": 196 | case "www.google.gf": 197 | case "www.google.gg": 198 | case "www.google.com.gh": 199 | case "www.google.com.gi": 200 | case "www.google.gl": 201 | case "www.google.gm": 202 | case "www.google.gp": 203 | case "www.google.gr": 204 | case "www.google.com.gt": 205 | case "www.google.gy": 206 | case "www.google.com.hk": 207 | case "www.google.hn": 208 | case "www.google.hr": 209 | case "www.google.ht": 210 | case "www.google.hu": 211 | case "www.google.co.id": 212 | case "www.google.iq": 213 | case "www.google.ie": 214 | case "www.google.co.il": 215 | case "www.google.im": 216 | case "www.google.co.in": 217 | case "www.google.is": 218 | case "www.google.it": 219 | case "www.google.je": 220 | case "www.google.com.jm": 221 | case "www.google.jo": 222 | case "www.google.co.jp": 223 | case "www.google.co.ke": 224 | case "www.google.ki": 225 | case "www.google.kg": 226 | case "www.google.co.kr": 227 | case "www.google.com.kw": 228 | case "www.google.kz": 229 | case "www.google.la": 230 | case "www.google.com.lb": 231 | case "www.google.li": 232 | case "www.google.lk": 233 | case "www.google.co.ls": 234 | case "www.google.lt": 235 | case "www.google.lu": 236 | case "www.google.lv": 237 | case "www.google.com.ly": 238 | case "www.google.co.ma": 239 | case "www.google.md": 240 | case "www.google.me": 241 | case "www.google.mg": 242 | case "www.google.mk": 243 | case "www.google.ml": 244 | case "www.google.com.mm": 245 | case "www.google.mn": 246 | case "www.google.ms": 247 | case "www.google.com.mt": 248 | case "www.google.mu": 249 | case "www.google.mv": 250 | case "www.google.mw": 251 | case "www.google.com.mx": 252 | case "www.google.com.my": 253 | case "www.google.co.mz": 254 | case "www.google.com.na": 255 | case "www.google.ne": 256 | case "www.google.ng": 257 | case "www.google.com.ng": 258 | case "www.google.com.ni": 259 | case "www.google.nl": 260 | case "www.google.no": 261 | case "www.google.com.np": 262 | case "www.google.nr": 263 | case "www.google.nu": 264 | case "www.google.co.nz": 265 | case "www.google.com.pk": 266 | case "www.google.com.pa": 267 | case "www.google.com.pe": 268 | case "www.google.com.ph": 269 | case "www.google.pl": 270 | case "www.google.com.pg": 271 | case "www.google.pn": 272 | case "www.google.com.pr": 273 | case "www.google.ps": 274 | case "www.google.pt": 275 | case "www.google.com.py": 276 | case "www.google.com.qa": 277 | case "www.google.ro": 278 | case "www.google.rs": 279 | case "www.google.ru": 280 | case "www.google.rw": 281 | case "www.google.com.sa": 282 | case "www.google.com.sb": 283 | case "www.google.sc": 284 | case "www.google.se": 285 | case "www.google.com.sg": 286 | case "www.google.sh": 287 | case "www.google.si": 288 | case "www.google.sk": 289 | case "www.google.com.sl": 290 | case "www.google.sn": 291 | case "www.google.sm": 292 | case "www.google.so": 293 | case "www.google.st": 294 | case "www.google.sr": 295 | case "www.google.com.sv": 296 | case "www.google.td": 297 | case "www.google.tg": 298 | case "www.google.co.th": 299 | case "www.google.com.tj": 300 | case "www.google.tk": 301 | case "www.google.tl": 302 | case "www.google.tm": 303 | case "www.google.to": 304 | case "www.google.com.tr": 305 | case "www.google.tt": 306 | case "www.google.com.tw": 307 | case "www.google.co.tz": 308 | case "www.google.com.ua": 309 | case "www.google.co.ug": 310 | case "www.google.com.uy": 311 | case "www.google.co.uz": 312 | case "www.google.com.vc": 313 | case "www.google.co.ve": 314 | case "www.google.vg": 315 | case "www.google.co.vi": 316 | case "www.google.com.vn": 317 | case "www.google.vu": 318 | case "www.google.ws": 319 | case "www.google.co.za": 320 | case "www.google.co.zm": 321 | case "www.google.co.zw": 322 | case "www.google.co.uk": 323 | case "encrypted.google.com": 324 | if (document.getElementById("sfdiv")) { 325 | return { 326 | input: "lst-ib", 327 | anchor: "sfdiv", 328 | }; 329 | } 330 | return { 331 | input: "q", 332 | anchor: "RNNXgb", 333 | }; 334 | case "duckduckgo.com": 335 | case "start.duckduckgo.com": 336 | if (document.body.classList.contains("body--serp")) { 337 | return { 338 | input: "search_form_input", 339 | anchor: "search_form", 340 | }; 341 | } 342 | return { 343 | input: "search_form_input_homepage", 344 | anchor: "search_form_homepage", 345 | }; 346 | case "ca.yahoo.com": 347 | case "uk.yahoo.com": 348 | case "us.yahoo.com": 349 | case "fr.yahoo.com": 350 | case "de.yahoo.com": 351 | case "ie.yahoo.com": 352 | case "in.yahoo.com": 353 | case "it.yahoo.com": 354 | case "se.yahoo.com": 355 | case "www.yahoo.com": 356 | return { 357 | input: "uh-search-box", 358 | anchor: "uh-search-form", 359 | }; 360 | case "search.yahoo.com": 361 | return { 362 | input: "yschsp", 363 | anchor: "sf", 364 | }; 365 | case "www.bing.com": 366 | return { 367 | input: "sb_form_q", 368 | anchor: "b_searchboxForm", 369 | }; 370 | case "tw.yahoo.com": 371 | return { 372 | input: "UHSearchBox", 373 | anchor: "UHSearch", 374 | }; 375 | default: 376 | return null; 377 | } 378 | }; 379 | browser.runtime.onMessage.addListener((request) => { 380 | this.icon.classList.add("stm-hidden"); 381 | document.getElementsByClassName("stm-icon")[0].disabled = true; 382 | metrics.start_session("toolbar"); 383 | SpeakToMePopup.showAt(0, 0); 384 | stmInit(); 385 | return Promise.resolve({response: "content script ack"}); 386 | }); 387 | 388 | const SpeakToMePopup = { 389 | // closeClicked used to skip out of media recording handling 390 | closeClicked: false, 391 | init: () => { 392 | console.log("SpeakToMePopup init"); 393 | const popup = document.createElement("div"); 394 | // eslint-disable-next-line no-unsanitized/property 395 | popup.innerHTML = POPUP_WRAPPER_MARKUP; 396 | document.body.appendChild(popup); 397 | 398 | document.getElementById("stm-popup").style.backgroundImage = 399 | `url("${browser.extension.getURL("/assets/images/ff-logo.png")}")`; 400 | document.getElementById("stm-close").style.backgroundImage = 401 | `url("${browser.extension.getURL("/assets/images/icon-close.svg")}")`; 402 | document.getElementById("stm-feedback").style.backgroundImage = 403 | `url("${browser.extension.getURL("/assets/images/feedback.svg")}")`; 404 | 405 | if (document.dir === "rtl") { 406 | document.getElementById("stm-close").classList.add("rtl"); 407 | document.getElementById("stm-popup").classList.add("rtl"); 408 | document.getElementById("stm-feedback").classList.add("rtl"); 409 | } 410 | 411 | languagePromise.then(() => { 412 | const footer = document.getElementById("stm-footer"); 413 | // eslint-disable-next-line no-unsanitized/property 414 | footer.innerHTML = footer.innerHTML.replace( 415 | "{language}", 416 | languages[language] 417 | ); 418 | }); 419 | 420 | this.inject = document.getElementById("stm-inject"); 421 | this.popup = document.getElementById("stm-popup"); 422 | this.icon = document.getElementsByClassName("stm-icon")[0]; 423 | // eslint-disable-next-line no-unsanitized/property 424 | this.inject.innerHTML = SUBMISSION_MARKUP; 425 | }, 426 | 427 | showAt: (x, y) => { 428 | console.log(`SpeakToMePopup showAt ${x},${y}`); 429 | const style = this.popup.style; 430 | style.display = "flex"; 431 | this.dismissPopup = function(e) { 432 | const key = e.which || e.keyCode; 433 | if (key === 27) { 434 | SpeakToMePopup.cancelFetch = true; 435 | e.preventDefault(); 436 | metrics.end_session(); 437 | SpeakToMePopup.hide(); 438 | stm.stop(); 439 | SpeakToMePopup.closeClicked = true; 440 | } 441 | }; 442 | this.addEventListener("keypress", this.dismissPopup); 443 | }, 444 | 445 | hide: () => { 446 | console.log("SpeakToMePopup hide"); 447 | this.removeEventListener("keypress", this.dismissPopup); 448 | this.popup.classList.add("stm-drop-out"); 449 | 450 | setTimeout(() => { 451 | this.popup.classList.remove("stm-drop-out"); 452 | this.popup.style.display = "none"; 453 | // eslint-disable-next-line no-unsanitized/property 454 | this.inject.innerHTML = SUBMISSION_MARKUP; 455 | this.icon.blur(); 456 | this.icon.classList.remove("stm-hidden"); 457 | this.icon.disabled = false; 458 | }, 500); 459 | }, 460 | 461 | reset: () => { 462 | // eslint-disable-next-line no-unsanitized/property 463 | this.inject.innerHTML = SUBMISSION_MARKUP; 464 | }, 465 | 466 | // Returns a Promise that resolves once the "Stop" button is clicked. 467 | waitForStop: () => { 468 | console.log("SpeakToMePopup waitForStop"); 469 | return new Promise((resolve, reject) => { 470 | const popup = document.getElementById("stm-popup"); 471 | const close = document.getElementById("stm-close"); 472 | popup.addEventListener("click", () => resolve(), {once: true}); 473 | close.addEventListener( 474 | "click", 475 | (e) => { 476 | e.stopPropagation(); 477 | reject(); 478 | }, 479 | {once: true} 480 | ); 481 | }); 482 | }, 483 | 484 | // Returns a Promise that resolves to the chosen text. 485 | chooseItem: (data) => { 486 | console.log("SpeakToMePopup chooseItem"); 487 | // eslint-disable-next-line no-unsanitized/property 488 | this.inject.innerHTML = SELECTION_MARKUP; 489 | const close = document.getElementById("stm-close"); 490 | const form = document.getElementById("stm-selection-wrapper"); 491 | const input = document.getElementById("stm-input"); 492 | const list = document.getElementById("stm-list"); 493 | const listWrapper = document.getElementById("stm-list-wrapper"); 494 | const reset = document.getElementById("stm-reset-button"); 495 | 496 | reset.style.backgroundImage = 497 | `url("${browser.extension.getURL("/assets/images/icon-redo.svg")}")`; 498 | 499 | const submitButton = document.getElementById("stm-submit-button"); 500 | if (document.dir === "rtl") { 501 | submitButton.classList.add("rtl"); 502 | submitButton.style.backgroundImage = 503 | `url("${browser.extension.getURL("/assets/images/icon-done-rtl.svg")}")`; 504 | } else { 505 | submitButton.style.backgroundImage = 506 | `url("${browser.extension.getURL("/assets/images/icon-done.svg")}")`; 507 | } 508 | 509 | let firstChoice; 510 | 511 | return new Promise((resolve, reject) => { 512 | if (data.length === 1) { 513 | firstChoice = data[0]; 514 | listWrapper.removeChild(list); 515 | } else { 516 | let html = ""; 528 | // eslint-disable-next-line no-unsanitized/property 529 | list.innerHTML = html; 530 | } 531 | 532 | input.confidence = escapeHTML(firstChoice.confidence); 533 | input.value = escapeHTML(firstChoice.text); 534 | input.size = Math.max(input.value.length, 10); 535 | input.idx_suggestion = 0; 536 | input.focus(); 537 | 538 | input.addEventListener("keypress", (e) => { 539 | // e.preventDefault(); 540 | if (e.keyCode === 13) { 541 | e.preventDefault(); 542 | list.classList.add("close"); 543 | resolve(input); 544 | } 545 | }); 546 | 547 | input.addEventListener("input", () => { 548 | input.size = Math.max(10, input.value.length); 549 | }); 550 | 551 | form.addEventListener("submit", function _submit_form(e) { 552 | e.preventDefault(); 553 | e.stopPropagation(); 554 | list.classList.add("close"); 555 | form.removeEventListener("submit", _submit_form); 556 | resolve(input); 557 | }); 558 | 559 | list.addEventListener("click", function _choose_item(e) { 560 | e.preventDefault(); 561 | list.removeEventListener("click", _choose_item); 562 | if (e.target instanceof HTMLLIElement) { 563 | const result = []; 564 | result.confidence = e.target.getAttribute("confidence"); 565 | result.value = e.target.textContent; 566 | result.idx_suggestion = e.target.getAttribute("idx_suggestion"); 567 | list.classList.add("close"); 568 | input.value = e.target.textContent; 569 | input.size = input.value.length; 570 | 571 | resolve(result); 572 | } 573 | }); 574 | 575 | list.addEventListener("keypress", function _choose_item(e) { 576 | const key = e.which || e.keyCode; 577 | if (key === 13) { 578 | list.removeEventListener("click", _choose_item); 579 | if (e.target instanceof HTMLLIElement) { 580 | const result = []; 581 | result.confidence = e.target.getAttribute("confidence"); 582 | result.value = e.target.textContent; 583 | result.idx_suggestion = e.target.getAttribute("idx_suggestion"); 584 | list.classList.add("close"); 585 | input.value = e.target.textContent; 586 | input.size = input.value.length; 587 | 588 | resolve(result); 589 | } 590 | } 591 | }); 592 | 593 | reset.addEventListener("click", function _reset_click(e) { 594 | e.preventDefault(); 595 | reset.removeEventListener("click", _reset_click); 596 | reject(e.target.id); 597 | }); 598 | 599 | close.addEventListener("click", function _close_click(e) { 600 | e.preventDefault(); 601 | close.removeEventListener("click", _close_click); 602 | reject(e.target.id); 603 | }); 604 | 605 | close.addEventListener("keypress", function _close_click(e) { 606 | const key = e.which || e.keyCode; 607 | if (key === 13) { 608 | e.preventDefault(); 609 | close.removeEventListener("keypress", _close_click); 610 | reject(e.target.id); 611 | } 612 | }); 613 | }); 614 | }, 615 | }; 616 | 617 | // The icon that we anchor to the currently focused input element. 618 | 619 | class SpeakToMeIcon { 620 | constructor() { 621 | console.log("SpeakToMeIcon constructor"); 622 | const register = getSTMAnchors(document.domain); 623 | this.icon = document.createElement("button"); 624 | this.icon.classList.add("stm-icon"); 625 | this.icon.classList.add("stm-hidden"); 626 | this.icon.disabled = true; 627 | this.icon.title = "Start listening"; 628 | this.icon.style.backgroundImage = 629 | `url("${browser.extension.getURL("/assets/images/icon-mic.svg")}")`; 630 | if (document.dir === "rtl") { 631 | this.icon.classList.add("rtl"); 632 | } 633 | this.hasAnchor = false; 634 | this.input = 635 | document.getElementById(register.input) || 636 | document.getElementsByName(register.input)[0]; 637 | this.anchor = 638 | document.getElementById(register.anchor) || 639 | document.getElementsByClassName(register.anchor)[0]; 640 | 641 | if (this.input.ownerDocument !== document) { 642 | return null; 643 | } 644 | 645 | document.body.appendChild(this.icon); 646 | this.icon.addEventListener("click", onStmIconClick); 647 | this.anchor.style.position = "relative"; 648 | this.anchor.style.overflow = "visible"; 649 | this.anchor.append(this.icon); 650 | this.icon.classList.remove("stm-hidden"); 651 | this.icon.disabled = false; 652 | } 653 | 654 | setInput(text) { 655 | console.log(`SpeakToMeIcon setInput: ${text}`); 656 | this.input.value = text; 657 | this.input.focus(); 658 | this.input.form.submit(); 659 | } 660 | } 661 | 662 | const listener = (msg) => { 663 | switch (msg.state) { 664 | case "ready": { 665 | listening = false; 666 | break; 667 | } 668 | case "error": { 669 | listening = false; 670 | console.error(msg.error); 671 | failGracefully(msg.error.toString()); 672 | break; 673 | } 674 | case "listening": { 675 | const stream = stm.getmediaStream(); 676 | if (!stream) { 677 | return; 678 | } 679 | 680 | // Build the WebAudio graph we'll be using 681 | audioContext = new AudioContext(); 682 | sourceNode = audioContext.createMediaStreamSource(stream); 683 | analyzerNode = audioContext.createAnalyser(); 684 | outputNode = audioContext.createMediaStreamDestination(); 685 | 686 | // make sure we're doing mono everywhere 687 | sourceNode.channelCount = 1; 688 | analyzerNode.channelCount = 1; 689 | outputNode.channelCount = 1; 690 | 691 | // connect the nodes together 692 | sourceNode.connect(analyzerNode); 693 | analyzerNode.connect(outputNode); 694 | 695 | SpeakToMePopup.waitForStop().then( 696 | () => { 697 | stm.stop(); 698 | }, 699 | () => { 700 | stm.stop(); 701 | SpeakToMePopup.closeClicked = true; 702 | metrics.end_session(); 703 | SpeakToMePopup.hide(); 704 | } 705 | ); 706 | 707 | document.getElementById("stm-levels").hidden = false; 708 | visualize(analyzerNode); 709 | 710 | metrics.start_attempt(); 711 | metrics.start_recording(); 712 | 713 | const copy = document.getElementById("stm-content"); 714 | loadAnimation(SPINNING_ANIMATION, true); 715 | copy.innerHTML = `
Listening...
`; 716 | break; 717 | } 718 | case "processing": { 719 | analyzerNode.disconnect(outputNode); 720 | sourceNode.disconnect(analyzerNode); 721 | audioContext.close(); 722 | 723 | const copy = document.getElementById("stm-content"); 724 | copy.innerHTML = `
Processing...
`; 725 | loadAnimation(DONE_ANIMATION, false); 726 | break; 727 | } 728 | case "result": { 729 | metrics.stop_recording(); 730 | 731 | // We stopped the recording, send the content to the STT server. 732 | audioContext = null; 733 | sourceNode = null; 734 | analyzerNode = null; 735 | outputNode = null; 736 | 737 | document.getElementById("stm-levels").hidden = true; 738 | 739 | // handle clicking on close element by dumping recording data 740 | if (SpeakToMePopup.closeClicked) { 741 | SpeakToMePopup.closeClicked = false; 742 | return; 743 | } 744 | 745 | if (SpeakToMePopup.cancelFetch) { 746 | SpeakToMePopup.cancelFetch = false; 747 | return; 748 | } 749 | 750 | if (LOCAL_TEST) { 751 | const json = { 752 | data: [ 753 | { 754 | confidence: 0.807493, 755 | text: "PLEASE ADD MILK TO MY SHOPPING LIST", 756 | }, 757 | { 758 | confidence: 0.906263, 759 | text: "PLEASE AT MILK TO MY SHOPPING LIST", 760 | }, 761 | { 762 | confidence: 0.904414, 763 | text: "PLEASE ET MILK TO MY SHOPPING LIST", 764 | }, 765 | ], 766 | }; 767 | 768 | displayOptions(json.data); 769 | return; 770 | } 771 | 772 | console.log(`Got STT result: ${JSON.stringify(msg)}`); 773 | const container = document.getElementById("stm-box"); 774 | container.classList.add("stm-done-animation"); 775 | setTimeout(() => { 776 | displayOptions(msg.data); 777 | }, 500); 778 | break; 779 | } 780 | } 781 | }; 782 | 783 | // Helper for animation startup 784 | const stmInit = () => { 785 | loadAnimation(START_ANIMATION, false, "stm-start-animation"); 786 | 787 | if (listening) { 788 | stm.stop(); 789 | listening = false; 790 | } 791 | 792 | stm.listen(); 793 | listening = true; 794 | }; 795 | 796 | // Click handler for stm icon 797 | const onStmIconClick = (event) => { 798 | if (SpeakToMePopup.cancelFetch) { 799 | SpeakToMePopup.cancelFetch = false; 800 | } 801 | const type = event.detail ? "button" : "keyboard"; 802 | event.preventDefault(); 803 | metrics.start_session(type); 804 | event.target.classList.add("stm-hidden"); 805 | SpeakToMePopup.showAt(event.clientX, event.clientY); 806 | stmInit(); 807 | }; 808 | 809 | // Helper to handle background visualization 810 | const visualize = (analyzerNode) => { 811 | const MIN_DB_LEVEL = -85; // The dB level that is 0 in the levels display 812 | const MAX_DB_LEVEL = -30; // The dB level that is 100% in the levels display 813 | 814 | // Set up the analyzer node, and allocate an array for its data 815 | // FFT size 64 gives us 32 bins. But those bins hold frequencies up to 816 | // 22kHz or more, and we only care about visualizing lower frequencies 817 | // which is where most human voice lies, so we use fewer bins 818 | analyzerNode.fftSize = 64; 819 | const frequencyBins = new Float32Array(14); 820 | 821 | // Clear the canvas 822 | 823 | const popupWidth = document.getElementById("stm-popup").offsetWidth; 824 | 825 | const levels = document.getElementById("stm-levels"); 826 | const xPos = 827 | popupWidth < levels.offsetWidth 828 | ? popupWidth * 0.5 - 22 829 | : levels.offsetWidth * 0.5; 830 | const yPos = levels.offsetHeight * 0.5; 831 | const context = levels.getContext("2d"); 832 | context.clearRect(0, 0, levels.width, levels.height); 833 | 834 | if (levels.hidden) { 835 | // If we've been hidden, return right away without calling rAF again. 836 | return; 837 | } 838 | 839 | // Get the FFT data 840 | analyzerNode.getFloatFrequencyData(frequencyBins); 841 | 842 | // Display it as a barchart. 843 | // Drop bottom few bins, since they are often misleadingly high 844 | const skip = 2; 845 | const n = frequencyBins.length - skip; 846 | const dbRange = MAX_DB_LEVEL - MIN_DB_LEVEL; 847 | 848 | // Loop through the values and draw the bars 849 | context.strokeStyle = "#d1d2d3"; 850 | 851 | for (let i = 0; i < n; i++) { 852 | const value = frequencyBins[i + skip]; 853 | const diameter = 854 | ((levels.height * (value - MIN_DB_LEVEL)) / dbRange) * 10; 855 | if (diameter < 0) { 856 | continue; 857 | } 858 | // Display a bar for this value. 859 | let alpha = diameter / 500; 860 | if (alpha > 0.2) alpha = 0.2; 861 | else if (alpha < 0.1) alpha = 0.1; 862 | 863 | context.lineWidth = alpha * alpha * 150; 864 | context.globalAlpha = alpha * alpha * 5; 865 | context.beginPath(); 866 | context.ellipse(xPos, yPos, diameter, diameter, 0, 0, 2 * Math.PI); 867 | if (diameter > 90 && diameter < 360) context.stroke(); 868 | } 869 | // Update the visualization the next time we can 870 | requestAnimationFrame(function() { 871 | visualize(analyzerNode); 872 | }); 873 | }; 874 | 875 | // Helper to handle bodymobin 876 | const loadAnimation = (animationType, loop, className) => { 877 | const container = document.getElementById("stm-box"); 878 | container.className = ""; 879 | if (className) { 880 | container.classList.add(className); 881 | } 882 | if (bodymovin) { 883 | bodymovin.destroy(); 884 | } 885 | bodymovin.loadAnimation({ 886 | container, 887 | loop, 888 | renderer: "svg", 889 | autoplay: true, 890 | path: animationType, // the path to the animation json 891 | }); 892 | }; 893 | 894 | const displayOptions = (items) => { 895 | // Filter the array for empty items and normalize the text. 896 | const data = items 897 | .filter((item) => { 898 | return item.text !== ""; 899 | }) 900 | .map((item) => { 901 | return { 902 | confidence: item.confidence, 903 | text: item.text.toLowerCase(), 904 | }; 905 | }); 906 | 907 | if (data.length === 0) { 908 | failGracefully("EMPTYRESULTS"); 909 | return; 910 | } 911 | 912 | const validateResults = function(data) { 913 | if (data.length === 1) { 914 | return true; 915 | } 916 | 917 | const val0 = String(data[0].confidence).substring(0, 4); 918 | const val1 = String(data[1].confidence).substring(0, 4); 919 | 920 | if (val0 - val1 > 0.2) { 921 | return true; 922 | } 923 | return false; 924 | }; 925 | 926 | // if the first result has a high enough confidence, or the distance 927 | // to the second large enough just 928 | // use it directly. 929 | data.sort(function(a, b) { 930 | return b.confidence - a.confidence; 931 | }); 932 | if (validateResults(data)) { 933 | metrics.end_attempt(data[0].confidence, "default accepted", 0); 934 | metrics.end_session(); 935 | stmIcon.setInput(data[0].text); 936 | SpeakToMePopup.hide(); 937 | return; 938 | } 939 | 940 | metrics.set_options_displayed(); 941 | SpeakToMePopup.chooseItem(data).then( 942 | (input) => { 943 | metrics.end_attempt(input.confidence, "accepted", input.idx_suggestion); 944 | metrics.end_session(); 945 | stmIcon.setInput(input.value); 946 | // Once a choice is made, close the popup. 947 | SpeakToMePopup.hide(); 948 | }, 949 | (id) => { 950 | if (id === "stm-reset-button") { 951 | metrics.end_attempt(-1, "reset", -1); 952 | SpeakToMePopup.reset(); 953 | stmInit(); 954 | } else { 955 | metrics.end_attempt(-1, "rejected", -1); 956 | metrics.end_session(); 957 | SpeakToMePopup.hide(); 958 | } 959 | } 960 | ); 961 | }; 962 | 963 | const stmIcon = new SpeakToMeIcon(); 964 | SpeakToMePopup.init(); 965 | 966 | const failGracefully = (errorMsg) => { 967 | if (errorMsg.indexOf("GUM") === 0) { 968 | errorMsg = "Please enable your microphone to use Voice Fill"; 969 | } else if (errorMsg.indexOf("EMPTYRESULTS") === 0) { 970 | errorMsg = "No results found"; 971 | } else { 972 | errorMsg = "Sorry, we encountered an error"; 973 | } 974 | loadAnimation(ERROR_ANIMATION, false); 975 | const copy = document.getElementById("stm-content"); 976 | copy.innerHTML = '
'; 977 | const errorDiv = document.getElementById("stm-listening-text"); 978 | errorDiv.textContent = errorMsg; 979 | setTimeout(() => { 980 | SpeakToMePopup.hide(); 981 | }, 1500); 982 | console.log("ERROR: ", errorMsg); 983 | }; 984 | })(); 985 | -------------------------------------------------------------------------------- /extension/js/languages.json: -------------------------------------------------------------------------------- 1 | { 2 | "af-ZA": "Afrikaans (Suid-Afrika)", 3 | "am-ET": "አማርኛ (ኢትዮጵያ)", 4 | "hy-AM": "Հայ (Հայաստան)", 5 | "az-AZ": "Azərbaycan (Azərbaycan)", 6 | "id-ID": "Bahasa Indonesia (Indonesia)", 7 | "ms-MY": "Bahasa Melayu (Malaysia)", 8 | "bn-BD": "বাংলা (বাংলাদেশ)", 9 | "bn-IN": "বাংলা (ভারত)", 10 | "ca-ES": "Català (Espanya)", 11 | "cs-CZ": "Čeština (Česká republika)", 12 | "da-DK": "Dansk (Danmark)", 13 | "de-DE": "Deutsch (Deutschland)", 14 | "en-AU": "English (Australia)", 15 | "en-CA": "English (Canada)", 16 | "en-GH": "English (Ghana)", 17 | "en-GB": "English (Great Britain)", 18 | "en-IN": "English (India)", 19 | "en-IE": "English (Ireland)", 20 | "en-KE": "English (Kenya)", 21 | "en-NZ": "English (New Zealand)", 22 | "en-NG": "English (Nigeria)", 23 | "en-PH": "English (Philippines)", 24 | "en-ZA": "English (South Africa)", 25 | "en-TZ": "English (Tanzania)", 26 | "en-US": "English (United States)", 27 | "es-AR": "Español (Argentina)", 28 | "es-BO": "Español (Bolivia)", 29 | "es-CL": "Español (Chile)", 30 | "es-CO": "Español (Colombia)", 31 | "es-CR": "Español (Costa Rica)", 32 | "es-EC": "Español (Ecuador)", 33 | "es-SV": "Español (El Salvador)", 34 | "es-ES": "Español (España)", 35 | "es-US": "Español (Estados Unidos)", 36 | "es-GT": "Español (Guatemala)", 37 | "es-HN": "Español (Honduras)", 38 | "es-MX": "Español (México)", 39 | "es-NI": "Español (Nicaragua)", 40 | "es-PA": "Español (Panamá)", 41 | "es-PY": "Español (Paraguay)", 42 | "es-PE": "Español (Perú)", 43 | "es-PR": "Español (Puerto Rico)", 44 | "es-DO": "Español (República Dominicana)", 45 | "es-UY": "Español (Uruguay)", 46 | "es-VE": "Español (Venezuela)", 47 | "eu-ES": "Euskara (Espainia)", 48 | "fil-PH": "Filipino (Pilipinas)", 49 | "fr-CA": "Français (Canada)", 50 | "fr-FR": "Français (France)", 51 | "gl-ES": "Galego (España)", 52 | "ka-GE": "ქართული (საქართველო)", 53 | "gu-IN": "ગુજરાતી (ભારત)", 54 | "hr-HR": "Hrvatski (Hrvatska)", 55 | "zu-ZA": "IsiZulu (Ningizimu Afrika)", 56 | "is-IS": "Íslenska (Ísland)", 57 | "it-IT": "Italiano (Italia)", 58 | "jv-ID": "Jawa (Indonesia)", 59 | "kn-IN": "ಕನ್ನಡ (ಭಾರತ)", 60 | "km-KH": "ភាសាខ្មែរ (កម្ពុជា)", 61 | "lo-LA": "ລາວ (ລາວ)", 62 | "lv-LV": "Latviešu (latviešu)", 63 | "lt-LT": "Lietuvių (Lietuva)", 64 | "hu-HU": "Magyar (Magyarország)", 65 | "ml-IN": "മലയാളം (ഇന്ത്യ)", 66 | "mr-IN": "मराठी (भारत)", 67 | "nl-NL": "Nederlands (Nederland)", 68 | "ne-NP": "नेपाली (नेपाल)", 69 | "nb-NO": "Norsk bokmål (Norge)", 70 | "pl-PL": "Polski (Polska)", 71 | "pt-BR": "Português (Brasil)", 72 | "pt-PT": "Português (Portugal)", 73 | "ro-RO": "Română (România)", 74 | "si-LK": "සිංහල (ශ්රී ලංකාව)", 75 | "sk-SK": "Slovenčina (Slovensko)", 76 | "sl-SI": "Slovenščina (Slovenija)", 77 | "su-ID": "Urang (Indonesia)", 78 | "sw-TZ": "Swahili (Tanzania)", 79 | "sw-KE": "Swahili (Kenya)", 80 | "fi-FI": "Suomi (Suomi)", 81 | "sv-SE": "Svenska (Sverige)", 82 | "ta-IN": "தமிழ் (இந்தியா)", 83 | "ta-SG": "தமிழ் (சிங்கப்பூர்)", 84 | "ta-LK": "தமிழ் (இலங்கை)", 85 | "ta-MY": "தமிழ் (மலேசியா)", 86 | "te-IN": "తెలుగు (భారతదేశం)", 87 | "vi-VN": "Tiếng Việt (Việt Nam)", 88 | "tr-TR": "Türkçe (Türkiye)", 89 | "ur-PK": "اردو (پاکستان)", 90 | "ur-IN": "اردو (بھارت)", 91 | "el-GR": "Ελληνικά (Ελλάδα)", 92 | "bg-BG": "Български (България)", 93 | "ru-RU": "Русский (Россия)", 94 | "sr-RS": "Српски (Србија)", 95 | "uk-UA": "Українська (Україна)", 96 | "he-IL": "עברית (ישראל)", 97 | "ar-IL": "العربية (إسرائيل)", 98 | "ar-JO": "العربية (الأردن)", 99 | "ar-AE": "العربية (الإمارات)", 100 | "ar-BH": "العربية (البحرين)", 101 | "ar-DZ": "العربية (الجزائر)", 102 | "ar-SA": "العربية (السعودية)", 103 | "ar-IQ": "العربية (العراق)", 104 | "ar-KW": "العربية (الكويت)", 105 | "ar-MA": "العربية (المغرب)", 106 | "ar-TN": "العربية (تونس)", 107 | "ar-OM": "العربية (عُمان)", 108 | "ar-PS": "العربية (فلسطين)", 109 | "ar-QA": "العربية (قطر)", 110 | "ar-LB": "العربية (لبنان)", 111 | "ar-EG": "العربية (مصر)", 112 | "fa-IR": "فارسی (ایران)", 113 | "hi-IN": "हिन्दी (भारत)", 114 | "th-TH": "ไทย (ประเทศไทย)", 115 | "ko-KR": "한국어 (대한민국)", 116 | "cmn-Hant-TW": "國語 (台灣)", 117 | "yue-Hant-HK": "廣東話 (香港)", 118 | "ja-JP": "日本語(日本)", 119 | "cmn-Hans-HK": "普通話 (香港)", 120 | "cmn-Hans-CN": "普通话 (中国大陆)" 121 | } 122 | -------------------------------------------------------------------------------- /extension/js/metrics.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | class Metrics { 6 | constructor() { 7 | this.num_attempts = 0; 8 | } 9 | 10 | start_session(triggered_by_uielement) { 11 | this.num_attempts = 0; 12 | this.accepted_confidence = -1; 13 | this.accepted_idx_suggestion = -1; 14 | this.accepted_outcome = ""; 15 | this.triggered_by_uielement = triggered_by_uielement; 16 | } 17 | 18 | end_session() { 19 | // move to library 20 | this.sendMessage("session", { 21 | sc: "end", 22 | cm1: this.num_attempts, // the number of attempts made in a session. 23 | cm2: this.accepted_confidence, // the confidence level of the accepted suggestion, if one was accepted; otherwise omitted. Integer between 1 and 100, inclusive. 24 | cm3: this.accepted_idx_suggestion, // the index of an accepted suggestion, if one was accepted; otherwise omitted. 25 | cm4: this.dt_recording_time, // the elapsed time in ms spent recording an attempt. 26 | cm5: this.dt_response_time, // the elapsed time in ms waiting for a response from the speech-to-text engine. 27 | cd1: this.accepted_outcome, // the outcome of a session or attempt. One of accepted, rejected, and aborted. 28 | cd2: document.domain, // the location from which a session is initiated. Will contain google, duckduckgo, Yahoo, generic. 29 | cd3: this.triggered_by_uielement, // the UI element from which the session was initiated. One of button, context menu, keyboard. 30 | cd4: this.was_result_modified, // whether the accepted submission was modified before being submitted. One of true, false. 31 | }); 32 | } 33 | 34 | start_recording() { 35 | this.dt_recording_time = Date.now(); 36 | } 37 | 38 | stop_recording() { 39 | this.dt_recording_time = Date.now() - this.dt_recording_time; 40 | } 41 | 42 | start_attempt() { 43 | this.num_attempts += 1; 44 | this.options_were_displayed = 0; 45 | } 46 | 47 | start_stt() { 48 | this.dt_response_time = Date.now(); 49 | } 50 | 51 | end_stt() { 52 | this.dt_response_time = Date.now() - this.dt_response_time; 53 | } 54 | 55 | set_options_displayed() { 56 | this.options_were_displayed = 1; 57 | } 58 | 59 | end_attempt( 60 | confidence_level, 61 | attempt_outcome, 62 | idx_suggestion, 63 | result_modified 64 | ) { 65 | console.log("[metrics] metrics.js attempt:", this.num_attempts); 66 | this.accepted_confidence = confidence_level * 100; 67 | this.accepted_idx_suggestion = idx_suggestion; 68 | this.accepted_outcome = attempt_outcome; 69 | this.was_result_modified = result_modified; 70 | 71 | this.sendMessage("attempt", { 72 | cm2: confidence_level * 100, // the confidence level of the accepted suggestion, if one was accepted; otherwise omitted. Integer between 1 and 100, inclusive. 73 | cm3: idx_suggestion, // the index of an accepted suggestion, if one was accepted; otherwise omitted. 74 | cm4: this.dt_recording_time, // the elapsed time in ms spent recording an attempt. 75 | cm5: this.dt_response_time, // the elapsed time in ms waiting for a response from the speech-to-text engine. 76 | cd1: attempt_outcome, // the outcome of a session or attempt. One of accepted, rejected, and aborted. 77 | cd5: this.options_were_displayed, // whether the user viewed additional suggestions. 78 | }); 79 | } 80 | 81 | sendMessage(event, content) { 82 | const message = {type: event, content}; 83 | const sendingMessage = browser.runtime.sendMessage(message); 84 | sendingMessage.then((result) => { 85 | console.log("[metrics] Sent message to background script"); 86 | }); 87 | } 88 | } 89 | 90 | window.Metrics = Metrics; 91 | -------------------------------------------------------------------------------- /extension/js/options.js: -------------------------------------------------------------------------------- 1 | function saveOptions(e) { 2 | e.preventDefault(); 3 | browser.storage.sync.set({ 4 | language: document.querySelector("#language").value, 5 | searchProvider: document.querySelector("#search-provider").value, 6 | }); 7 | } 8 | 9 | function restoreOptions() { 10 | const languageSelect = document.querySelector("#language"); 11 | const providerSelect = document.querySelector("#search-provider"); 12 | let languages; 13 | 14 | fetch(browser.extension.getURL("/js/languages.json")) 15 | .then((response) => { 16 | return response.json(); 17 | }) 18 | .then((l) => { 19 | languages = l; 20 | 21 | Object.entries(languages) 22 | .sort((a, b) => { 23 | return a[1].localeCompare(b[1]); 24 | }) 25 | .map(([code, language]) => { 26 | const option = document.createElement("option"); 27 | option.value = code; 28 | option.innerText = language; 29 | languageSelect.appendChild(option); 30 | }); 31 | 32 | return browser.storage.sync.get("language"); 33 | }) 34 | .then((result) => { 35 | const defaultLanguage = languages.hasOwnProperty(navigator.language) 36 | ? navigator.language 37 | : "en-US"; 38 | 39 | languageSelect.value = result.language || defaultLanguage; 40 | }) 41 | .catch((error) => { 42 | console.log(`Error: ${error}`); 43 | }); 44 | 45 | let manifest; 46 | fetch(browser.extension.getURL("/manifest.json")) 47 | .then((response) => { 48 | return response.json(); 49 | }) 50 | .then((m) => { 51 | manifest = m; 52 | 53 | return browser.storage.sync.get("lastVersion"); 54 | }) 55 | .then((result) => { 56 | if (result.lastVersion !== manifest.version) { 57 | return browser.storage.sync 58 | .set({ 59 | lastVersion: manifest.version, 60 | }) 61 | .then(() => { 62 | browser.tabs.create({ 63 | active: true, 64 | url: browser.extension.getURL("/views/CHANGELOG.html"), 65 | }); 66 | }); 67 | } 68 | 69 | return Promise.resolve(); 70 | }) 71 | .then(() => { 72 | const domains = Array.from( 73 | new Set( 74 | manifest.content_scripts[0].matches.map((d) => { 75 | return d.replace(/\/\*$/, "").replace(/\*\./, ""); 76 | }) 77 | ).values() 78 | ).sort(); 79 | 80 | domains.map((domain) => { 81 | const option = document.createElement("option"); 82 | option.value = domain; 83 | option.innerText = domain; 84 | providerSelect.appendChild(option); 85 | }); 86 | 87 | return browser.storage.sync.get("searchProvider"); 88 | }) 89 | .then((result) => { 90 | const defaultProvider = "https://www.google.com"; 91 | providerSelect.value = result.searchProvider || defaultProvider; 92 | }) 93 | .catch((error) => { 94 | console.log(`Error: ${error}`); 95 | }); 96 | 97 | languageSelect.addEventListener("change", saveOptions); 98 | providerSelect.addEventListener("change", saveOptions); 99 | } 100 | 101 | document.addEventListener("DOMContentLoaded", restoreOptions); 102 | -------------------------------------------------------------------------------- /extension/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | "name": "Voice Fill", 4 | "version": "1.4.3", 5 | "description": "Adds voice input to popular search pages in Firefox. Learn more about Voice Fill at https://testpilot.firefox.com", 6 | "developer": { 7 | "name": "Emerging Technologies Advanced Dev Team", 8 | "url": "https://github.com/mozilla/voicefill" 9 | }, 10 | "icons": { 11 | "48": "assets/images/icon-48.png", 12 | "96": "assets/images/icon-96.png" 13 | }, 14 | "applications": { 15 | "gecko": { 16 | "id": "speaktome@mozilla.com" 17 | } 18 | }, 19 | "content_scripts": [ 20 | { 21 | "matches": [ 22 | "https://www.google.com/*", 23 | "https://www.google.co.uk/*", 24 | "https://www.google.ca/*", 25 | "https://www.google.tn/*", 26 | "https://www.google.fr/*", 27 | "https://www.google.ad/*", 28 | "https://www.google.ae/*", 29 | "https://www.google.com.af/*", 30 | "https://www.google.com.ag/*", 31 | "https://www.google.com.ai/*", 32 | "https://www.google.al/*", 33 | "https://www.google.am/*", 34 | "https://www.google.co.ao/*", 35 | "https://www.google.com.ar/*", 36 | "https://www.google.as/*", 37 | "https://www.google.at/*", 38 | "https://www.google.com.au/*", 39 | "https://www.google.az/*", 40 | "https://www.google.ba/*", 41 | "https://www.google.com.bd/*", 42 | "https://www.google.be/*", 43 | "https://www.google.bf/*", 44 | "https://www.google.bg/*", 45 | "https://www.google.com.bh/*", 46 | "https://www.google.bi/*", 47 | "https://www.google.bj/*", 48 | "https://www.google.com.bn/*", 49 | "https://www.google.com.bo/*", 50 | "https://www.google.com.br/*", 51 | "https://www.google.bs/*", 52 | "https://www.google.bt/*", 53 | "https://www.google.co.bw/*", 54 | "https://www.google.by/*", 55 | "https://www.google.com.bz/*", 56 | "https://www.google.com.kh/*", 57 | "https://www.google.cc/*", 58 | "https://www.google.cd/*", 59 | "https://www.google.cf/*", 60 | "https://www.google.cg/*", 61 | "https://www.google.ch/*", 62 | "https://www.google.ci/*", 63 | "https://www.google.cc/*", 64 | "https://www.google.cl/*", 65 | "https://www.google.cm/*", 66 | "https://www.google.cn/*", 67 | "https://www.google.com.co/*", 68 | "https://www.google.co.cr/*", 69 | "https://www.google.co.ck/*", 70 | "https://www.google.com.cu/*", 71 | "https://www.google.cv/*", 72 | "https://www.google.com.cy/*", 73 | "https://www.google.cz/*", 74 | "https://www.google.de/*", 75 | "https://www.google.dj/*", 76 | "https://www.google.dk/*", 77 | "https://www.google.dm/*", 78 | "https://www.google.com.do/*", 79 | "https://www.google.dz/*", 80 | "https://www.google.com.ec/*", 81 | "https://www.google.ee/*", 82 | "https://www.google.com.eg/*", 83 | "https://www.google.es/*", 84 | "https://www.google.com.et/*", 85 | "https://www.google.fi/*", 86 | "https://www.google.com.fj/*", 87 | "https://www.google.fm/*", 88 | "https://www.google.ga/*", 89 | "https://www.google.ge/*", 90 | "https://www.google.gf/*", 91 | "https://www.google.gg/*", 92 | "https://www.google.com.gh/*", 93 | "https://www.google.com.gi/*", 94 | "https://www.google.gl/*", 95 | "https://www.google.gm/*", 96 | "https://www.google.gp/*", 97 | "https://www.google.gr/*", 98 | "https://www.google.com.gt/*", 99 | "https://www.google.gy/*", 100 | "https://www.google.com.hk/*", 101 | "https://www.google.hn/*", 102 | "https://www.google.hr/*", 103 | "https://www.google.ht/*", 104 | "https://www.google.hu/*", 105 | "https://www.google.co.id/*", 106 | "https://www.google.iq/*", 107 | "https://www.google.ie/*", 108 | "https://www.google.co.il/*", 109 | "https://www.google.im/*", 110 | "https://www.google.co.in/*", 111 | "https://www.google.is/*", 112 | "https://www.google.it/*", 113 | "https://www.google.je/*", 114 | "https://www.google.com.jm/*", 115 | "https://www.google.jo/*", 116 | "https://www.google.co.jp/*", 117 | "https://www.google.co.ke/*", 118 | "https://www.google.ki/*", 119 | "https://www.google.kg/*", 120 | "https://www.google.co.kr/*", 121 | "https://www.google.com.kw/*", 122 | "https://www.google.kz/*", 123 | "https://www.google.la/*", 124 | "https://www.google.com.lb/*", 125 | "https://www.google.li/*", 126 | "https://www.google.lk/*", 127 | "https://www.google.co.ls/*", 128 | "https://www.google.lt/*", 129 | "https://www.google.lu/*", 130 | "https://www.google.lv/*", 131 | "https://www.google.com.ly/*", 132 | "https://www.google.co.ma/*", 133 | "https://www.google.md/*", 134 | "https://www.google.me/*", 135 | "https://www.google.mg/*", 136 | "https://www.google.mk/*", 137 | "https://www.google.ml/*", 138 | "https://www.google.com.mm/*", 139 | "https://www.google.mn/*", 140 | "https://www.google.ms/*", 141 | "https://www.google.com.mt/*", 142 | "https://www.google.mu/*", 143 | "https://www.google.mv/*", 144 | "https://www.google.mw/*", 145 | "https://www.google.com.mx/*", 146 | "https://www.google.com.my/*", 147 | "https://www.google.co.mz/*", 148 | "https://www.google.com.na/*", 149 | "https://www.google.ne/*", 150 | "https://www.google.ng/*", 151 | "https://www.google.com.ng/*", 152 | "https://www.google.com.ni/*", 153 | "https://www.google.nl/*", 154 | "https://www.google.no/*", 155 | "https://www.google.com.np/*", 156 | "https://www.google.nr/*", 157 | "https://www.google.nu/*", 158 | "https://www.google.co.nz/*", 159 | "https://www.google.com.pk/*", 160 | "https://www.google.com.pa/*", 161 | "https://www.google.com.pe/*", 162 | "https://www.google.com.ph/*", 163 | "https://www.google.pl/*", 164 | "https://www.google.com.pg/*", 165 | "https://www.google.pn/*", 166 | "https://www.google.com.pr/*", 167 | "https://www.google.ps/*", 168 | "https://www.google.pt/*", 169 | "https://www.google.com.py/*", 170 | "https://www.google.com.qa/*", 171 | "https://www.google.ro/*", 172 | "https://www.google.rs/*", 173 | "https://www.google.ru/*", 174 | "https://www.google.rw/*", 175 | "https://www.google.com.sa/*", 176 | "https://www.google.com.sb/*", 177 | "https://www.google.sc/*", 178 | "https://www.google.se/*", 179 | "https://www.google.com.sg/*", 180 | "https://www.google.sh/*", 181 | "https://www.google.si/*", 182 | "https://www.google.sk/*", 183 | "https://www.google.com.sl/*", 184 | "https://www.google.sn/*", 185 | "https://www.google.sm/*", 186 | "https://www.google.so/*", 187 | "https://www.google.st/*", 188 | "https://www.google.sr/*", 189 | "https://www.google.com.sv/*", 190 | "https://www.google.td/*", 191 | "https://www.google.tg/*", 192 | "https://www.google.co.th/*", 193 | "https://www.google.com.tj/*", 194 | "https://www.google.tk/*", 195 | "https://www.google.tl/*", 196 | "https://www.google.tm/*", 197 | "https://www.google.to/*", 198 | "https://www.google.com.tr/*", 199 | "https://www.google.tt/*", 200 | "https://www.google.com.tw/*", 201 | "https://www.google.co.tz/*", 202 | "https://www.google.com.ua/*", 203 | "https://www.google.co.ug/*", 204 | "https://www.google.com.uy/*", 205 | "https://www.google.co.uz/*", 206 | "https://www.google.com.vc/*", 207 | "https://www.google.co.ve/*", 208 | "https://www.google.vg/*", 209 | "https://www.google.co.vi/*", 210 | "https://www.google.com.vn/*", 211 | "https://www.google.vu/*", 212 | "https://www.google.ws/*", 213 | "https://www.google.co.za/*", 214 | "https://www.google.co.zm/*", 215 | "https://www.google.co.zw/*", 216 | "https://encrypted.google.com/*", 217 | "https://duckduckgo.com/*", 218 | "https://start.duckduckgo.com/*", 219 | "https://www.yahoo.com/*", 220 | "https://search.yahoo.com/*", 221 | "https://uk.yahoo.com/*", 222 | "https://us.yahoo.com/*", 223 | "https://ca.yahoo.com/*", 224 | "https://fr.yahoo.com/*", 225 | "https://de.yahoo.com/*", 226 | "https://ie.yahoo.com/*", 227 | "https://in.yahoo.com/*", 228 | "https://it.yahoo.com/*", 229 | "https://se.yahoo.com/*", 230 | "https://tw.yahoo.com/*", 231 | "https://*.search.yahoo.com/*", 232 | "https://www.bing.com/*" 233 | ], 234 | "js": [ 235 | "js/vendor/browser-polyfill.min.js", 236 | "js/vendor/bodymovin.min.js", 237 | "js/vendor/stm_web.min.js", 238 | "js/metrics.js", 239 | "js/content.js" 240 | ], 241 | "css": [ 242 | "css/main.css" 243 | ] 244 | } 245 | ], 246 | "background": { 247 | "scripts": [ 248 | "js/vendor/browser-polyfill.min.js", 249 | "js/vendor/testpilot-ga.js", 250 | "js/background.js" 251 | ] 252 | }, 253 | "options_ui": { 254 | "page": "views/options.html" 255 | }, 256 | "web_accessible_resources": [ 257 | "views/CHANGELOG.html", 258 | "js/languages.json", 259 | "assets/animations/Done.json", 260 | "assets/animations/Error.json", 261 | "assets/animations/Start.json", 262 | "assets/animations/Spinning.json", 263 | "assets/images/icon-close.svg", 264 | "assets/images/icon-mic.svg", 265 | "assets/images/icon-redo.svg", 266 | "assets/images/icon-done.svg", 267 | "assets/images/icon-done-rtl.svg", 268 | "assets/images/ff-logo.png", 269 | "assets/images/feedback.svg" 270 | ], 271 | "permissions": [ 272 | "", 273 | "storage" 274 | ], 275 | "browser_action": { 276 | "browser_style": true, 277 | "default_title": "Voice Fill", 278 | "default_icon": "assets/images/mic.svg" 279 | } 280 | } 281 | -------------------------------------------------------------------------------- /extension/views/options.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "voicefill", 3 | "id": "voicefill@mozilla.com", 4 | "description": "This is a simple WebExtension that adds support to use Speech To Text as an input method in web pages.", 5 | "version": "1.4.3", 6 | "author": { 7 | "name": "Andre Natal & Fabrice Desré", 8 | "url": "https://github.com/mozilla/voicefill" 9 | }, 10 | "bugs": { 11 | "url": "https://github.com/mozilla/voicefill/issues" 12 | }, 13 | "devDependencies": { 14 | "eslint": "^5.7.0", 15 | "eslint-plugin-mozilla": "^0.16.1", 16 | "eslint-plugin-no-unsanitized": "^3.0.2", 17 | "markdown": "^0.5.0", 18 | "npm-run-all": "^4.1.3", 19 | "prettier": "^1.14.3", 20 | "web-ext": "^2.9.1" 21 | }, 22 | "dependencies": { 23 | "bodymovin": "^4.13.0", 24 | "speaktome-api": "^0.2.1", 25 | "testpilot-ga": "^0.3.0", 26 | "webextension-polyfill": "^0.3.1" 27 | }, 28 | "homepage": "https://github.com/mozilla/voicefill#readme", 29 | "keywords": [], 30 | "license": "MPL-2.0", 31 | "repository": { 32 | "type": "git", 33 | "url": "git+https://github.com/mozilla/voicefill.git" 34 | }, 35 | "scripts": { 36 | "build": "web-ext build -s extension --overwrite-dest", 37 | "format": "prettier 'extension/*.{js,css}' --tab-width=2 --arrow-parens=always --trailing-comma=es5 --no-bracket-spacing --write", 38 | "lint": "npm-run-all lint:*", 39 | "lint:extension": "web-ext lint -s extension --ignore-files js/vendor/* --self-hosted", 40 | "lint:js": "eslint extension", 41 | "once": "web-ext run -s extension", 42 | "package": "npm run build && mv web-ext-artifacts/*.zip addon.xpi", 43 | "postinstall": "bin/postinstall.sh" 44 | } 45 | } 46 | --------------------------------------------------------------------------------