├── .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 | [](https://travis-ci.org/mozilla/voicefill)
4 | [](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 = ``;
36 |
37 | // When submitting, this markup is passed in
38 | const SUBMISSION_MARKUP = `
39 |
40 |
41 |
44 | `;
47 |
48 | // When Selecting, this markup is passed in
49 | const SELECTION_MARKUP = ``;
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 = "";
517 | data.forEach((item, index) => {
518 | if (index === 0) {
519 | firstChoice = item;
520 | } else if (index < 5) {
521 | const confidence = escapeHTML(item.confidence);
522 | const text = escapeHTML(item.text);
523 | html += `${text} `;
525 | }
526 | });
527 | 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 |
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 |
--------------------------------------------------------------------------------