├── dist └── .gitkeep ├── .nvmrc ├── src ├── css │ └── .gitkeep ├── js │ ├── .prettierignore │ ├── content │ │ ├── .prettierignore │ │ ├── form_errors.js │ │ ├── context_menu.js │ │ ├── context.js │ │ └── extract_instrumentation.js │ ├── debug │ │ ├── .prettierignore │ │ └── logger.js │ ├── global │ │ ├── .prettierignore │ │ ├── state.js │ │ ├── crypto.js │ │ ├── storage.js │ │ ├── i18n.js │ │ ├── rule.js │ │ ├── jsonf.js │ │ └── workflows.js │ ├── options │ │ ├── .prettierignore │ │ ├── help.js │ │ └── chrome_bootstrap.js │ ├── popup │ │ └── .prettierignore │ └── background │ │ ├── .prettierignore │ │ ├── hotkeys.js │ │ ├── alarm.js │ │ ├── badge.js │ │ ├── context_menu.js │ │ ├── screenshooter.js │ │ ├── on_install.js │ │ ├── notification.js │ │ └── changelog.js ├── html │ ├── options │ │ ├── tutorial │ │ │ ├── _tour2_en.html │ │ │ ├── _tour12_en.html │ │ │ ├── _tour9_en.html │ │ │ ├── _tour10_en.html │ │ │ ├── _tour1_en.html │ │ │ ├── _tour13_en.html │ │ │ ├── _tour3_en.html │ │ │ ├── _tour5_en.html │ │ │ └── _tour6_en.html │ │ ├── _modaldeletetab_en.html │ │ ├── help-content │ │ │ ├── _help-editor_en.html │ │ │ ├── _help-automaticrematch_en.html │ │ │ ├── _help-capture_en.html │ │ │ ├── _help-workflows_en.html │ │ │ ├── _help-setupcontent_en.html │ │ │ ├── _help-ownlibraries_en.html │ │ │ ├── _help-settingsremoterules_en.html │ │ │ ├── _help-iframe_en.html │ │ │ └── _help-filling_en.html │ │ ├── _modalimportall_en.html │ │ ├── _modalimportallencrypted_en.html │ │ ├── _help_en.html │ │ └── _importexport_en.html │ └── popup.html ├── sass │ ├── _introjs_overwrites.scss │ ├── _chrome_bootstrap_overwrites.scss │ ├── _navigation.scss │ ├── _import_export.scss │ ├── _changelog.scss │ ├── _usage_report.scss │ ├── _rule_summary.scss │ ├── content.scss │ ├── _notice.scss │ ├── _settings.scss │ ├── _tutorial.scss │ ├── _editor.scss │ ├── options.scss │ ├── _tabs.scss │ ├── _workflows.scss │ ├── popup.scss │ └── _help.scss ├── images │ ├── icon_128.png │ ├── icon_16.png │ ├── icon_19.png │ ├── icon_250.png │ ├── icon_48.png │ ├── icon_19_working.png │ ├── help │ │ ├── popup-rematch.png │ │ ├── button-no-match.png │ │ ├── extension-rematch.png │ │ ├── remote-rules-popup.png │ │ ├── workflow-in-popup.png │ │ ├── form-extract-overlay.png │ │ ├── form-filling-one-match.png │ │ ├── form-extract-notification.png │ │ ├── form-filling-select-rule.png │ │ ├── form-filling-error-notification.png │ │ └── form-extract-finished-notification.png │ └── options │ │ └── thenoseman.png ├── fonts │ └── formofill.woff └── vendor │ ├── ace │ └── theme-clouds.js │ └── html5sortable │ └── html.sortable.min.js ├── .prettierignore ├── test ├── support │ ├── jquery.js │ ├── chrome_api.js │ └── integration_helper.js ├── mocha.opts ├── integration │ ├── .eslintrc │ ├── before_function_scene.js │ ├── workflow_scene.js │ ├── shared_rules_scene.js │ ├── popup_scene.js │ ├── options_scene.js │ ├── test_setup_scene.js │ ├── complex_scene.js │ ├── all_types_scene.js │ ├── form_filling_scene.js │ └── form_extraction_scene.js ├── .eslintrc ├── spec_helper.js └── global │ ├── i18n_spec.js │ └── rule_spec.js ├── assets ├── icon_250.psd ├── ext-store │ ├── 440x280.png │ ├── 440x280.psd │ ├── 920x680.png │ ├── 920x680.psd │ ├── 1400x560.png │ ├── 1400x560.psd │ ├── screenshots │ │ ├── screenshot-00.png │ │ ├── screenshot-01.png │ │ ├── screenshot-02.png │ │ ├── screenshot-03.png │ │ ├── screenshot-04.png │ │ └── screenshot-05.png │ ├── final-store-screenshots-exported │ │ ├── screenshot-00.jpg │ │ ├── screenshot-01.jpg │ │ ├── screenshot-02.jpg │ │ ├── screenshot-03.jpg │ │ ├── screenshot-04.jpg │ │ ├── screenshot-05.jpg │ │ └── screenshot-06.jpg │ └── description.txt ├── fonts │ ├── OpenSans-Bold.ttf │ ├── OpenSans-Light.ttf │ └── OpenSans-Regular.ttf ├── available-in-chrome-store.png ├── how-to-access-bg-page-english.png ├── how-to-access-bg-page-german.png ├── how-to-access-extensions-english.png ├── how-to-install-dev-version-english.png ├── uml │ ├── plantuml-what-happens-when-page-is-visited.png │ └── plantuml-what-happens-when-page-is-visited.txt └── icomoon │ └── howto.md ├── testcases └── docroot-for-testing │ └── form-o-fill-testing │ ├── json.json │ ├── 22-complex-2-remote-lib.js │ ├── 22-complex-iframe.html │ ├── 21-reeval-rules.js │ ├── 23-iframe-3.html │ ├── 23-iframe-2.html │ ├── 22-complex-iframe2-click-handler.html │ ├── 22-complex.js │ ├── 28-inpage-workflow-2.html │ ├── 18-after-function.html │ ├── 16-workflow-2.html │ ├── 16-workflow-3.html │ ├── fof-complete-complex-2.js │ ├── 08-options-import-export.html │ ├── 17-workflow-storage-2.html │ ├── styles.css │ ├── 06-no-matching-rules.html │ ├── 09-options.html │ ├── 04-filling-all-matched-fields.html │ ├── 03-before-function-libs-usage.html │ ├── 02-before-context.html │ ├── 14-value-function-helper.html │ ├── 15-value-function-storage.html │ ├── 19-setup-content.html │ ├── 11-shared-rules-broken.html │ ├── 26-run-value-function-on-every-element.html │ ├── 12-shared-rules.html │ ├── 21-reeval-rules.html │ ├── 25-trigger-dom-events.html │ ├── 17-workflow-storage-1.html │ ├── 24-auto-reload.html │ ├── 07-only-empty-flag.html │ ├── 20-teardown-content.html │ ├── 16-workflow-1.html │ ├── 10-screenshots.html │ ├── 28-inpage-workflow-1.html │ ├── 23-iframe-1.html │ ├── fof-complete-format-error.js │ └── fof-complete.js ├── .tern-project ├── es2015 ├── .eslintignore ├── .gitignore ├── .babelrc ├── notes.js ├── update_vendor.sh ├── webpack.config.js ├── LICENSE ├── bundles.txt └── package.json /dist/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 6.9.1 2 | -------------------------------------------------------------------------------- /src/css/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/js/.prettierignore: -------------------------------------------------------------------------------- 1 | *.js 2 | *.html 3 | -------------------------------------------------------------------------------- /src/js/content/.prettierignore: -------------------------------------------------------------------------------- 1 | ../.prettierignore -------------------------------------------------------------------------------- /src/js/debug/.prettierignore: -------------------------------------------------------------------------------- 1 | ../.prettierignore -------------------------------------------------------------------------------- /src/js/global/.prettierignore: -------------------------------------------------------------------------------- 1 | ../.prettierignore -------------------------------------------------------------------------------- /src/js/options/.prettierignore: -------------------------------------------------------------------------------- 1 | ../.prettierignore -------------------------------------------------------------------------------- /src/js/popup/.prettierignore: -------------------------------------------------------------------------------- 1 | ../.prettierignore -------------------------------------------------------------------------------- /src/js/background/.prettierignore: -------------------------------------------------------------------------------- 1 | ../.prettierignore -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | src/js/**/*.js 2 | src/html/**/*.html 3 | -------------------------------------------------------------------------------- /test/support/jquery.js: -------------------------------------------------------------------------------- 1 | ../../src/vendor/jquery/jquery.min.js -------------------------------------------------------------------------------- /src/html/options/tutorial/_tour2_en.html: -------------------------------------------------------------------------------- 1 |
2 | -------------------------------------------------------------------------------- /src/sass/_introjs_overwrites.scss: -------------------------------------------------------------------------------- 1 | .introjs-overlay.hidden { 2 | display: none; 3 | } 4 | 5 | -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --require test/spec_helper 2 | --reporter spec 3 | --recursive 4 | --timeout 99999 5 | -------------------------------------------------------------------------------- /assets/icon_250.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/form-o-fill/form-o-fill-chrome-extension/HEAD/assets/icon_250.psd -------------------------------------------------------------------------------- /src/images/icon_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/form-o-fill/form-o-fill-chrome-extension/HEAD/src/images/icon_128.png -------------------------------------------------------------------------------- /src/images/icon_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/form-o-fill/form-o-fill-chrome-extension/HEAD/src/images/icon_16.png -------------------------------------------------------------------------------- /src/images/icon_19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/form-o-fill/form-o-fill-chrome-extension/HEAD/src/images/icon_19.png -------------------------------------------------------------------------------- /src/images/icon_250.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/form-o-fill/form-o-fill-chrome-extension/HEAD/src/images/icon_250.png -------------------------------------------------------------------------------- /src/images/icon_48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/form-o-fill/form-o-fill-chrome-extension/HEAD/src/images/icon_48.png -------------------------------------------------------------------------------- /src/fonts/formofill.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/form-o-fill/form-o-fill-chrome-extension/HEAD/src/fonts/formofill.woff -------------------------------------------------------------------------------- /assets/ext-store/440x280.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/form-o-fill/form-o-fill-chrome-extension/HEAD/assets/ext-store/440x280.png -------------------------------------------------------------------------------- /assets/ext-store/440x280.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/form-o-fill/form-o-fill-chrome-extension/HEAD/assets/ext-store/440x280.psd -------------------------------------------------------------------------------- /assets/ext-store/920x680.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/form-o-fill/form-o-fill-chrome-extension/HEAD/assets/ext-store/920x680.png -------------------------------------------------------------------------------- /assets/ext-store/920x680.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/form-o-fill/form-o-fill-chrome-extension/HEAD/assets/ext-store/920x680.psd -------------------------------------------------------------------------------- /assets/ext-store/1400x560.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/form-o-fill/form-o-fill-chrome-extension/HEAD/assets/ext-store/1400x560.png -------------------------------------------------------------------------------- /assets/ext-store/1400x560.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/form-o-fill/form-o-fill-chrome-extension/HEAD/assets/ext-store/1400x560.psd -------------------------------------------------------------------------------- /assets/fonts/OpenSans-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/form-o-fill/form-o-fill-chrome-extension/HEAD/assets/fonts/OpenSans-Bold.ttf -------------------------------------------------------------------------------- /assets/fonts/OpenSans-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/form-o-fill/form-o-fill-chrome-extension/HEAD/assets/fonts/OpenSans-Light.ttf -------------------------------------------------------------------------------- /src/images/icon_19_working.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/form-o-fill/form-o-fill-chrome-extension/HEAD/src/images/icon_19_working.png -------------------------------------------------------------------------------- /assets/fonts/OpenSans-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/form-o-fill/form-o-fill-chrome-extension/HEAD/assets/fonts/OpenSans-Regular.ttf -------------------------------------------------------------------------------- /src/images/help/popup-rematch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/form-o-fill/form-o-fill-chrome-extension/HEAD/src/images/help/popup-rematch.png -------------------------------------------------------------------------------- /src/images/options/thenoseman.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/form-o-fill/form-o-fill-chrome-extension/HEAD/src/images/options/thenoseman.png -------------------------------------------------------------------------------- /assets/available-in-chrome-store.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/form-o-fill/form-o-fill-chrome-extension/HEAD/assets/available-in-chrome-store.png -------------------------------------------------------------------------------- /src/images/help/button-no-match.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/form-o-fill/form-o-fill-chrome-extension/HEAD/src/images/help/button-no-match.png -------------------------------------------------------------------------------- /testcases/docroot-for-testing/form-o-fill-testing/json.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": "value by json.json via jQuery.getJSON", 3 | "data2": "data" 4 | } 5 | -------------------------------------------------------------------------------- /src/images/help/extension-rematch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/form-o-fill/form-o-fill-chrome-extension/HEAD/src/images/help/extension-rematch.png -------------------------------------------------------------------------------- /src/images/help/remote-rules-popup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/form-o-fill/form-o-fill-chrome-extension/HEAD/src/images/help/remote-rules-popup.png -------------------------------------------------------------------------------- /src/images/help/workflow-in-popup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/form-o-fill/form-o-fill-chrome-extension/HEAD/src/images/help/workflow-in-popup.png -------------------------------------------------------------------------------- /assets/how-to-access-bg-page-english.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/form-o-fill/form-o-fill-chrome-extension/HEAD/assets/how-to-access-bg-page-english.png -------------------------------------------------------------------------------- /assets/how-to-access-bg-page-german.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/form-o-fill/form-o-fill-chrome-extension/HEAD/assets/how-to-access-bg-page-german.png -------------------------------------------------------------------------------- /src/images/help/form-extract-overlay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/form-o-fill/form-o-fill-chrome-extension/HEAD/src/images/help/form-extract-overlay.png -------------------------------------------------------------------------------- /assets/how-to-access-extensions-english.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/form-o-fill/form-o-fill-chrome-extension/HEAD/assets/how-to-access-extensions-english.png -------------------------------------------------------------------------------- /src/images/help/form-filling-one-match.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/form-o-fill/form-o-fill-chrome-extension/HEAD/src/images/help/form-filling-one-match.png -------------------------------------------------------------------------------- /assets/ext-store/screenshots/screenshot-00.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/form-o-fill/form-o-fill-chrome-extension/HEAD/assets/ext-store/screenshots/screenshot-00.png -------------------------------------------------------------------------------- /assets/ext-store/screenshots/screenshot-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/form-o-fill/form-o-fill-chrome-extension/HEAD/assets/ext-store/screenshots/screenshot-01.png -------------------------------------------------------------------------------- /assets/ext-store/screenshots/screenshot-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/form-o-fill/form-o-fill-chrome-extension/HEAD/assets/ext-store/screenshots/screenshot-02.png -------------------------------------------------------------------------------- /assets/ext-store/screenshots/screenshot-03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/form-o-fill/form-o-fill-chrome-extension/HEAD/assets/ext-store/screenshots/screenshot-03.png -------------------------------------------------------------------------------- /assets/ext-store/screenshots/screenshot-04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/form-o-fill/form-o-fill-chrome-extension/HEAD/assets/ext-store/screenshots/screenshot-04.png -------------------------------------------------------------------------------- /assets/ext-store/screenshots/screenshot-05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/form-o-fill/form-o-fill-chrome-extension/HEAD/assets/ext-store/screenshots/screenshot-05.png -------------------------------------------------------------------------------- /assets/how-to-install-dev-version-english.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/form-o-fill/form-o-fill-chrome-extension/HEAD/assets/how-to-install-dev-version-english.png -------------------------------------------------------------------------------- /src/images/help/form-extract-notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/form-o-fill/form-o-fill-chrome-extension/HEAD/src/images/help/form-extract-notification.png -------------------------------------------------------------------------------- /src/images/help/form-filling-select-rule.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/form-o-fill/form-o-fill-chrome-extension/HEAD/src/images/help/form-filling-select-rule.png -------------------------------------------------------------------------------- /.tern-project: -------------------------------------------------------------------------------- 1 | { 2 | "libs": [ 3 | "browser", 4 | "jquery", 5 | "ecma6" 6 | ], 7 | "loadEagerly": [ 8 | "src/js/**/*.js" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /src/images/help/form-filling-error-notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/form-o-fill/form-o-fill-chrome-extension/HEAD/src/images/help/form-filling-error-notification.png -------------------------------------------------------------------------------- /es2015: -------------------------------------------------------------------------------- 1 | chrome version supported: 2 | ------------------------- 3 | - class : 49+ 4 | - promise : 33+ 5 | - static : 49+ 6 | - Obj.assign : 45+ 7 | - let : 49+ 8 | - Arrow : 45+ 9 | -------------------------------------------------------------------------------- /src/images/help/form-extract-finished-notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/form-o-fill/form-o-fill-chrome-extension/HEAD/src/images/help/form-extract-finished-notification.png -------------------------------------------------------------------------------- /assets/uml/plantuml-what-happens-when-page-is-visited.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/form-o-fill/form-o-fill-chrome-extension/HEAD/assets/uml/plantuml-what-happens-when-page-is-visited.png -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | **/ace/*.js 2 | **/chance.js/*.js 3 | **/chrome-bootstrap/*.js 4 | **/jquery/*.js 5 | **/js-beautifier/*.js 6 | **/moment.js/*.js 7 | **/html5sortable/*.js 8 | **/intro.js/intro.min.js 9 | -------------------------------------------------------------------------------- /testcases/docroot-for-testing/form-o-fill-testing/22-complex-2-remote-lib.js: -------------------------------------------------------------------------------- 1 | /*global jQuery */ 2 | window.aLib = function(target) { 3 | jQuery(target).val("Used aLib '" + target + "'"); 4 | }; 5 | -------------------------------------------------------------------------------- /assets/ext-store/final-store-screenshots-exported/screenshot-00.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/form-o-fill/form-o-fill-chrome-extension/HEAD/assets/ext-store/final-store-screenshots-exported/screenshot-00.jpg -------------------------------------------------------------------------------- /assets/ext-store/final-store-screenshots-exported/screenshot-01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/form-o-fill/form-o-fill-chrome-extension/HEAD/assets/ext-store/final-store-screenshots-exported/screenshot-01.jpg -------------------------------------------------------------------------------- /assets/ext-store/final-store-screenshots-exported/screenshot-02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/form-o-fill/form-o-fill-chrome-extension/HEAD/assets/ext-store/final-store-screenshots-exported/screenshot-02.jpg -------------------------------------------------------------------------------- /assets/ext-store/final-store-screenshots-exported/screenshot-03.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/form-o-fill/form-o-fill-chrome-extension/HEAD/assets/ext-store/final-store-screenshots-exported/screenshot-03.jpg -------------------------------------------------------------------------------- /assets/ext-store/final-store-screenshots-exported/screenshot-04.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/form-o-fill/form-o-fill-chrome-extension/HEAD/assets/ext-store/final-store-screenshots-exported/screenshot-04.jpg -------------------------------------------------------------------------------- /assets/ext-store/final-store-screenshots-exported/screenshot-05.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/form-o-fill/form-o-fill-chrome-extension/HEAD/assets/ext-store/final-store-screenshots-exported/screenshot-05.jpg -------------------------------------------------------------------------------- /assets/ext-store/final-store-screenshots-exported/screenshot-06.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/form-o-fill/form-o-fill-chrome-extension/HEAD/assets/ext-store/final-store-screenshots-exported/screenshot-06.jpg -------------------------------------------------------------------------------- /src/js/background/hotkeys.js: -------------------------------------------------------------------------------- 1 | // "hotkey-action-fill-with-rule-1" 2 | // "hotkey-action-show-extract-overlay" 3 | chrome.commands.onCommand.addListener(function(command) { 4 | console.log("Command:", command); 5 | }); 6 | -------------------------------------------------------------------------------- /src/sass/_chrome_bootstrap_overwrites.scss: -------------------------------------------------------------------------------- 1 | .chrome-bootstrap { 2 | font-family: 'Open Sans', Arial,sans-serif; 3 | } 4 | 5 | .chrome-bootstrap .frame .view, .chrome-bootstrap .frame .content { 6 | width: 100%; 7 | } 8 | 9 | -------------------------------------------------------------------------------- /src/sass/_navigation.scss: -------------------------------------------------------------------------------- 1 | ul.menu li.tutorials a { 2 | display: inline; 3 | padding: 4px; 4 | border: 1px solid #ddd; 5 | } 6 | 7 | .current-rule-info { 8 | border: 1px solid #ddd; 9 | padding: 4px; 10 | } 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.crx 2 | *.pem 3 | node_modules 4 | build 5 | dist/*.zip 6 | assets/ext-store/screenshots-psd/ 7 | src/css/*.css 8 | src/css/*.css.map 9 | src/js/background.js 10 | src/js/content.js 11 | *.map 12 | src/js/*.js 13 | -------------------------------------------------------------------------------- /src/sass/_import_export.scss: -------------------------------------------------------------------------------- 1 | #importexport { 2 | label { 3 | width: 65px; 4 | display: inline-block; 5 | margin-bottom: 15px; 6 | } 7 | 8 | .all-button-export-crypt { 9 | margin-top: 15px; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "targets": { 5 | "browsers": ["Chrome >= 49"] 6 | }, 7 | "modules": false, 8 | "useBuiltIns": true, 9 | "debug": false 10 | }] 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /test/integration/.eslintrc: -------------------------------------------------------------------------------- 1 | # vim: set ft=yaml 2 | # eslint config file 3 | --- 4 | globals: 5 | browser: true 6 | element: true 7 | by: true 8 | manifest: true 9 | $: true 10 | $$: true 11 | protractor: true 12 | Tests: true 13 | sleep: true 14 | -------------------------------------------------------------------------------- /src/sass/_changelog.scss: -------------------------------------------------------------------------------- 1 | #changelog ul { 2 | border-bottom: 1px solid #ddd; 3 | } 4 | 5 | #changelog li { 6 | text-indent: -26px; 7 | margin-left: 38px; 8 | margin-bottom: 5px; 9 | } 10 | 11 | #changelog li:before { 12 | font-family: formofill; 13 | content: "\e600"; 14 | margin-right: 10px; 15 | } 16 | 17 | -------------------------------------------------------------------------------- /testcases/docroot-for-testing/form-o-fill-testing/22-complex-iframe.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Form-O-Fill Testpage 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /notes.js: -------------------------------------------------------------------------------- 1 | // How to add snippets to ace: 2 | var snippetManager = ace.require("ace/snippets").snippetManager; 3 | var snippets = snippetManager.parseSnippetFile("snippet test\n TEST!"); 4 | snippets.push({ 5 | content: "hello ${1:world}...!", 6 | name: "hello", 7 | tabTrigger: "h" 8 | }); 9 | snippetManager.register(snippets, "javascript"); 10 | 11 | -------------------------------------------------------------------------------- /testcases/docroot-for-testing/form-o-fill-testing/21-reeval-rules.js: -------------------------------------------------------------------------------- 1 | /* global jQuery */ 2 | jQuery(function() { 3 | jQuery(".clickme").on("click", function() { 4 | jQuery('h1').append('
'); 5 | }); 6 | 7 | jQuery(".clickme2").on("click", function() { 8 | window.location.hash="some/hash/url"; 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /testcases/docroot-for-testing/form-o-fill-testing/23-iframe-3.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Form-O-Fill Testpage 6 | 7 | 8 | 9 |

INNER IFRAME - EXTERNAL

10 | Inner field:
11 | 12 | 13 | -------------------------------------------------------------------------------- /src/js/global/state.js: -------------------------------------------------------------------------------- 1 | /* eslint no-unused-vars: 0 */ 2 | var State = function State() { 3 | this.testingMode = false; 4 | this.lastActiveTab = null; 5 | this.currentRuleMetadata = null; 6 | this.optionSettings = {}; 7 | this.decryptionPassword = null; 8 | this.forceRunOnLoad = false; 9 | this.ruleRuntime = { 10 | triggered: "manually", 11 | partOfWorkflow: false 12 | }; 13 | }; 14 | 15 | var state = new State(); 16 | -------------------------------------------------------------------------------- /src/sass/_usage_report.scss: -------------------------------------------------------------------------------- 1 | .usage-report-preview { 2 | display: none; 3 | 4 | &.visible { 5 | display: block; 6 | } 7 | 8 | th, td { 9 | padding: 4px; 10 | } 11 | 12 | th { 13 | text-align: left; 14 | background-color: #999; 15 | color: #fff; 16 | } 17 | 18 | tr.section td { 19 | background-color: #aaa; 20 | } 21 | 22 | tr:nth-child(odd) { 23 | background-color: #eee; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /assets/icomoon/howto.md: -------------------------------------------------------------------------------- 1 | # How to create / use icons in this extension 2 | 3 | All icons in this extension were created using the fabulous app at http://icomoon.io/app/ 4 | 5 | Open that page and drag-drop the selection.json file in there. 6 | 7 | Please only use icons from the "Entypo" set. 8 | 9 | Download the file (press "Font >" at the bottom and extract it). 10 | 11 | Place the icomoon.woff file into src/fonts/ and the selection.json here. 12 | -------------------------------------------------------------------------------- /testcases/docroot-for-testing/form-o-fill-testing/23-iframe-2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Form-O-Fill Testpage 6 | 7 | 8 | 9 |

INNER IFRAME

10 | Inner field:
11 |
copyValue
12 | 13 | 14 | -------------------------------------------------------------------------------- /test/integration/before_function_scene.js: -------------------------------------------------------------------------------- 1 | require("../support/integration_helper"); 2 | 3 | describe("using before functions", function() { 4 | this.timeout(9999); 5 | 6 | it("can use HTML found in the content page", function (done) { 7 | Tests.visit("02-before-context") 8 | .getValue("#target", function (err, value) { 9 | expect(value).to.eq("
SOME CONTENT
"); 10 | }) 11 | .call(done); 12 | }); 13 | }); 14 | 15 | -------------------------------------------------------------------------------- /src/sass/_rule_summary.scss: -------------------------------------------------------------------------------- 1 | .rule-summary { 2 | border: 1px solid #999; 3 | margin: 4px 8px 4px 0; 4 | overflow: hidden; 5 | border-collapse: collapse; 6 | word-break: break-word; 7 | 8 | td { 9 | padding: 4px; 10 | } 11 | 12 | .rule-name { 13 | color: #fff; 14 | background-color: #999; 15 | height: 40px; 16 | overflow: hidden; 17 | } 18 | 19 | .yes { 20 | color: Green; 21 | } 22 | } 23 | 24 | .hidden { 25 | display: none; 26 | } 27 | -------------------------------------------------------------------------------- /test/integration/workflow_scene.js: -------------------------------------------------------------------------------- 1 | require("../support/integration_helper"); 2 | 3 | describe("using workflows", function() { 4 | 5 | it("works", function (done) { 6 | Tests.visit("16-workflow-1") 7 | .click(".popup-html .select-workflow") 8 | .pause(global.pause) 9 | .url().then(function(currentUrl) { 10 | expect(currentUrl.value).to.eql("http://localhost:9292/form-o-fill-testing/16-workflow-3.html?"); 11 | }) 12 | .call(done); 13 | }); 14 | }); 15 | 16 | -------------------------------------------------------------------------------- /test/.eslintrc: -------------------------------------------------------------------------------- 1 | # vim: set ft=yaml 2 | # eslint config file 3 | --- 4 | env: 5 | node: true 6 | amd: true 7 | globals: 8 | describe: true 9 | xdescribe: true 10 | it: true 11 | xit: true 12 | expect: true 13 | beforeEach: true 14 | before: true 15 | afterEach: true 16 | after: true 17 | sinon: true 18 | jQuery: true 19 | jsdom: true 20 | sources: true 21 | testRoot: true 22 | rules: 23 | no-unused-expressions: 0 24 | max-nested-callbacks: 0 25 | handle-callback-err: 0 26 | -------------------------------------------------------------------------------- /src/js/global/crypto.js: -------------------------------------------------------------------------------- 1 | /*global sjcl */ 2 | 3 | // Small wrapper around sjcl 4 | var Crypto = function(password) { 5 | this.password = password; 6 | }; 7 | 8 | Crypto.prototype.encrypt = function(data) { 9 | return btoa(sjcl.encrypt(this.password, data)); 10 | }; 11 | 12 | // returns null if an error occured 13 | // otherwise the cleartext 14 | Crypto.prototype.decrypt = function(data) { 15 | try { 16 | return sjcl.decrypt(this.password, atob(data)); 17 | } catch (e) { 18 | return null; 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /src/js/content/form_errors.js: -------------------------------------------------------------------------------- 1 | var FormError = function(selector, value, message) { 2 | this.selector = selector; 3 | this.value = value; 4 | this.message = message; 5 | }; 6 | 7 | var FormErrors = function(rule) { 8 | this._errors = []; 9 | this.rule = rule; 10 | }; 11 | 12 | FormErrors.prototype.add = function(selector, value, message) { 13 | var formError = new FormError(selector, value, message); 14 | this._errors.push(formError); 15 | return this; 16 | }; 17 | 18 | FormErrors.prototype.get = function() { 19 | return this._errors; 20 | }; 21 | -------------------------------------------------------------------------------- /test/support/chrome_api.js: -------------------------------------------------------------------------------- 1 | // This will include a stub for all necessary chrome* apis 2 | // usage: 3 | // var chromeApi = require("./chrome_api"); 4 | // chromeApi.setConfig("someThing", "someReturnValue"); 5 | var chrome = { 6 | config: {}, 7 | setConfig: function(key, value) { 8 | this.config[key] = value; 9 | }, 10 | i18n: { 11 | getUILanguage: function() { 12 | return chrome.config.uiLanguage || "en_US"; 13 | } 14 | }, 15 | runtime: { 16 | getURL: function(url) { 17 | return "chrome://" + url; 18 | } 19 | } 20 | }; 21 | 22 | module.exports = chrome; 23 | -------------------------------------------------------------------------------- /update_vendor.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Updates: 3 | # moment.js 4 | # chance.js 5 | # 6 | curl -o src/vendor/moment.js/moment-with-locales.min.js -LO "https://momentjs.com/downloads/moment-with-locales.min.js" 7 | curl -o src/vendor/chance.js/chance.js -LO "https://raw.githubusercontent.com/chancejs/chancejs/master/dist/chance.min.js" 8 | curl -o src/vendor/optimal-select/optimal-select.js -LO "https://raw.githubusercontent.com/Autarc/optimal-select/master/dist/optimal-select.min.js" 9 | # curl -o src/vendor/math.js/math.min.js -LO "https://raw.githubusercontent.com/josdejong/mathjs/master/dist/math.min.js" 10 | -------------------------------------------------------------------------------- /testcases/docroot-for-testing/form-o-fill-testing/22-complex-iframe2-click-handler.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | simple_link 5 | 6 | 7 | 8 | simple link 9 | 10 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/html/options/_modaldeletetab_en.html: -------------------------------------------------------------------------------- 1 |
2 |

Delete this tab?

3 |
4 |
5 |

6 | Deleting this tab will delete all rules stored inside. 7 |

8 |

9 | Are you sure you want to delete the tab? 10 |

11 |
12 |
13 |
14 |
15 | 16 | 17 |
18 |
19 |
20 |
21 | -------------------------------------------------------------------------------- /src/html/options/help-content/_help-editor_en.html: -------------------------------------------------------------------------------- 1 | 2 |

The rule editor

3 |

4 | The rule editor is where you create the rule definitions for all you forms. 5 |

6 |

7 | You can organize your rules into up to 6 tabs.
8 | Clicking on a tab activates that tab and clicking again allows you to rename that tab.
9 | Press the green floppy disc symbol to save your tab settings. 10 |

11 |

12 | Remove a tab (and its rules!) by clicking on the minus icon on the right of the tab.
13 | Add a new tab by clicking on the plus symbol on the last tab. 14 |

15 | -------------------------------------------------------------------------------- /src/js/content/context_menu.js: -------------------------------------------------------------------------------- 1 | /* global extractRules */ 2 | var lastRightClickedElement = null; 3 | document.addEventListener("mousedown", function(event){ 4 | // right click 5 | if (event.button === 2 && typeof event.target.form !== "undefined") { 6 | lastRightClickedElement = event.target; 7 | } 8 | }, true); 9 | 10 | // When we receive the message to extract a form 11 | // from bg.js we can just extract the form from the last saved element 12 | chrome.extension.onMessage.addListener(function(message) { 13 | if (message.action === "extractLastClickedForm") { 14 | extractRules(lastRightClickedElement.form); 15 | } 16 | }); 17 | -------------------------------------------------------------------------------- /src/js/background/alarm.js: -------------------------------------------------------------------------------- 1 | /* global Logger Utils */ 2 | var Alarm = { 3 | create: function() { 4 | // Create an "alarm" which will be called every 15 minutes or so 5 | // https://developer.chrome.com/extensions/alarms 6 | chrome.alarms.clear(Utils.alarmName); 7 | Logger.info("[bg.js] Installing alarm to trigger every " + Utils.alarmIntervalInMinutes + " minutes"); 8 | chrome.alarms.create(Utils.alarmName, { delayInMinutes: Utils.alarmIntervalInMinutes, periodInMinutes: Utils.alarmIntervalInMinutes}); 9 | } 10 | }; 11 | 12 | // REMOVE START 13 | if (typeof exports === "object") { 14 | module.exports = Alarm; 15 | } 16 | // REMOVE END 17 | -------------------------------------------------------------------------------- /src/js/content/context.js: -------------------------------------------------------------------------------- 1 | /*global JSONF */ 2 | /*eslint no-unused-vars: 0 */ 3 | // This is not the same context as in background.js 4 | // Currently it only allows to read storage values set by bg.js but 5 | // you can set value for all value functions to access 6 | var context = { 7 | storage: { 8 | get: function(key) { 9 | var value = window.sessionStorage.getItem(key); 10 | if (typeof value !== "undefined") { 11 | return JSONF.parse(value); 12 | } 13 | return value; 14 | }, 15 | set: function(key, value) { 16 | // set it in localstorage 17 | window.sessionStorage.setItem(key, JSONF.stringify(value)); 18 | } 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /testcases/docroot-for-testing/form-o-fill-testing/22-complex.js: -------------------------------------------------------------------------------- 1 | /* global jQuery */ 2 | jQuery(function() { 3 | jQuery(".clickme").on("click", function() { 4 | jQuery("form").append( 5 | '
' 6 | ); 7 | }); 8 | 9 | jQuery("#target4").on("click", function() { 10 | jQuery(this).val("clicked"); 11 | }); 12 | 13 | jQuery("#target21").on("click", function() { 14 | jQuery(this).html("clicked"); 15 | }); 16 | }); 17 | 18 | /*eslint-disable no-unused-vars */ 19 | var demoVar = { 20 | data: 1, 21 | func: function(a) { 22 | return a; 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /testcases/docroot-for-testing/form-o-fill-testing/28-inpage-workflow-2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Form-O-Fill Testpage 6 | 7 | 8 | 9 |
10 |       Inpage workflow 2:
11 |       Both fields should be filled.
12 |     
13 | 14 | Target 3: ("inpage WF 3")
15 | Target 4: ("inpage WF 4")
16 |
17 | 18 | 19 | -------------------------------------------------------------------------------- /testcases/docroot-for-testing/form-o-fill-testing/18-after-function.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Form-O-Fill Testpage 6 | 7 | 8 | 9 |
10 |   TODO 
11 |   
12 | 13 | 14 |

TESTING SERVER RUNNING

15 | Input field:
16 | Rules Import: 18 | 19 | 20 | 21 |
22 | 23 | 24 | -------------------------------------------------------------------------------- /testcases/docroot-for-testing/form-o-fill-testing/16-workflow-2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Form-O-Fill Testpage 6 | 7 | 8 | 9 |

TESTING SERVER RUNNING 2

10 |
11 | 1. Input field:
12 | 2. Input field:
13 |
14 | 15 |
16 | 17 | 18 | -------------------------------------------------------------------------------- /testcases/docroot-for-testing/form-o-fill-testing/16-workflow-3.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Form-O-Fill Testpage 6 | 7 | 8 | 9 |

TESTING SERVER RUNNING 3

10 |
11 | 1. Input field:
12 | 2. Input field:
13 |
14 | 15 |
16 | 17 | 18 | -------------------------------------------------------------------------------- /testcases/docroot-for-testing/form-o-fill-testing/fof-complete-complex-2.js: -------------------------------------------------------------------------------- 1 | { 2 | "workflows": [ 3 | { 4 | "flags": {}, 5 | "id": 1, 6 | "name": "remote URL workflow", 7 | "steps": [ 8 | "Complex - 2 - remote URL" 9 | ] 10 | } 11 | ], 12 | "rules": { 13 | "tabSettings": [ 14 | { 15 | "id": "1", 16 | "name": "remote import URL" 17 | } 18 | ], 19 | "rules": [ 20 | { 21 | "code": "var rules = [{\n name: \"Complex - 2 - remote URL\",\n color: \"Orange\",\n url: /22-complex-2\\.html/,\n fields: [{\n selector: \"#target11\",\n value: \"remote import!\"\n }]\n}\n];", 22 | "tabId": 1 23 | } 24 | ] 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/js/options/help.js: -------------------------------------------------------------------------------- 1 | /* global jQuery, I18n */ 2 | 3 | // Lets experiment with jQuery custom events :) 4 | // See also i18n.js 5 | jQuery(document).on("i18n-loaded", function (event, pageName) { 6 | if (pageName.indexOf("help_") > -1) { 7 | I18n.loadPages([ 8 | "help-capture", 9 | "help-filling", 10 | "help-editor", 11 | "help-basic", 12 | "help-iframe", 13 | "help-functions", 14 | "help-screenshot", 15 | "help-libraries", 16 | "help-ownlibraries", 17 | "help-before", 18 | "help-beforecontext", 19 | "help-sharedrules", 20 | "help-workflows", 21 | "help-setupcontent", 22 | "help-automaticrematch", 23 | "help-settingsremoterules"], "help-content"); 24 | } 25 | }); 26 | -------------------------------------------------------------------------------- /testcases/docroot-for-testing/form-o-fill-testing/08-options-import-export.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Form-O-Fill Testpage 6 | 7 | 8 | 9 |

TESTING SERVER RUNNING

10 |
11 | Rules Import: 25 | 26 | 27 | -------------------------------------------------------------------------------- /testcases/docroot-for-testing/form-o-fill-testing/17-workflow-storage-2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Form-O-Fill Testpage 6 | 7 | 8 | 9 |

TESTING SERVER RUNNING 2

10 |
11 | Input field:
12 | 13 |
14 | Rules Import: 15 | 17 |
18 | 19 | 20 | -------------------------------------------------------------------------------- /src/html/options/_modalimportall_en.html: -------------------------------------------------------------------------------- 1 |
2 |

Import workflows & rules

3 |
4 |
5 |

6 | Importing all data will replace all currently present workflows and rules. 7 |

8 |

9 | 10 |

11 |

12 | Form-O-Fill can only import JSON files. Please choose a JSON file. 13 |

14 |
15 |
16 |
17 |
18 | 19 | 20 |
21 |
22 |
23 |
24 | -------------------------------------------------------------------------------- /testcases/docroot-for-testing/form-o-fill-testing/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: Sans Serif, Helvetica; 3 | } 4 | 5 | table { 6 | border-spacing: 0; 7 | border : 1px solid #bbb; 8 | } 9 | 10 | td { 11 | padding: 10px; 12 | border-right : 1px solid #bbb; 13 | border-bottom : 1px solid #bbb; 14 | } 15 | 16 | tr td:last-child { 17 | border-right: 0; 18 | width: 80%; 19 | font-size: 11px; 20 | } 21 | 22 | tr:last-child td { 23 | border-bottom: 0; 24 | } 25 | 26 | td:nth-child(2) { 27 | font-family: monospace; 28 | } 29 | 30 | .head { 31 | background-color: #bbb; 32 | color: #fff; 33 | } 34 | 35 | input { 36 | margin: 20px 0; 37 | padding: 5px; 38 | font-size: 18px; 39 | width: 50%; 40 | } 41 | 42 | body.types input { 43 | font-size: 11px; 44 | padding: 0; 45 | width: auto; 46 | } 47 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const HtmlWebpackPluginOptions = require('html-webpack-plugin'); 3 | 4 | module.exports = { 5 | context: path.resolve(__dirname, "source/js"), 6 | entry: { 7 | //background: "background/background.js", 8 | //popup: "popup/popup.js", 9 | //content: "content/content.js", 10 | //global: "./source/js/global/global.js" 11 | options: "./options/options.js" 12 | }, 13 | 14 | devtool: "cheap-eval-source-map", 15 | 16 | output: { 17 | path: path.resolve(__dirname, "build-ext/js"), 18 | filename: "[name].bundle.js" 19 | }, 20 | 21 | module: { 22 | rules: [ 23 | { test: /\.js$/, loader: "babel-loader" } 24 | ] 25 | }, 26 | 27 | plugins: [ 28 | new HtmlWebpackPluginOptions({ 29 | }) 30 | ] 31 | }; 32 | -------------------------------------------------------------------------------- /testcases/docroot-for-testing/form-o-fill-testing/06-no-matching-rules.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Form-O-Fill Testpage 6 | 7 | 8 | 9 |

TESTING SERVER RUNNING

10 | Input field: (Icon : "N/A")
11 | Rules Import: 27 |
28 | 29 | 30 | -------------------------------------------------------------------------------- /src/html/options/_modalimportallencrypted_en.html: -------------------------------------------------------------------------------- 1 |
2 |

Import encrypted workflows & rules

3 |
4 |
5 |

6 | Importing all data will replace all currently present workflows and rules. 7 |

8 |

9 | Please enter the decryption password: 10 |

11 |

12 | 13 |

14 |

15 | Form-O-Fill was unable to decrypt the data. Sorry. 16 |

17 |
18 |
19 |
20 |
21 | 22 | 23 |
24 |
25 |
26 |
27 | -------------------------------------------------------------------------------- /src/js/background/badge.js: -------------------------------------------------------------------------------- 1 | /*global Testing */ 2 | var Badge = function() { 3 | this.defaultBadgeBgColor = [0, 136, 255, 200]; 4 | this.intervalBadgeBgColor = [43, 206, 7, 255]; 5 | this.useBadgeBgColor = this.defaultBadgeBgColor; 6 | }; 7 | 8 | Badge.prototype.setText = function(txt, tabId) { 9 | chrome.browserAction.setBadgeText({"text": txt, "tabId": tabId}); 10 | chrome.browserAction.setBadgeBackgroundColor({"color": this.useBadgeBgColor, "tabId": tabId}); 11 | 12 | Testing.setVar("browser-action-badge-text", txt, "Browser action badge text"); 13 | }; 14 | 15 | Badge.prototype.refreshMatchCounter = function(tab, count) { 16 | var txt = chrome.i18n.getMessage("no_match_available"); 17 | if (count && count > 0) { 18 | txt = count.toString(); 19 | } 20 | this.setText(txt, tab.id); 21 | }; 22 | 23 | Badge.prototype.setBgColor = function(bgColor, tabId) { 24 | chrome.browserAction.setBadgeBackgroundColor({"color": bgColor, "tabId": tabId}); 25 | }; 26 | -------------------------------------------------------------------------------- /test/spec_helper.js: -------------------------------------------------------------------------------- 1 | // Expectation Framework 2 | global.chai = require("chai"); 3 | global.chai.use(require("sinon-chai")); 4 | global.chai.use(require("chai-as-promised")); 5 | global.expect = require("chai").expect; 6 | 7 | // Sinon for stubbing/mocking 8 | global.sinon = require("sinon"); 9 | 10 | // Simulate the DOM inside node 11 | global.jsdom = require("jsdom"); 12 | 13 | // A stubbed chrome API 14 | global.chrome = require("./support/chrome_api.js"); 15 | 16 | // jQuery loaded with the jsDOM window 17 | global.jQuery = require("./support/jquery.js")(jsdom.jsdom().defaultView); 18 | 19 | global.document = jsdom.jsdom(); 20 | 21 | // Stub out the normal logger from ogger.js 22 | global.Logger = { 23 | info: function() {}, 24 | debug: function() {}, 25 | error: function() {} 26 | }; 27 | 28 | // Replacement for chromes promise API 29 | global.Promise = require("promise"); 30 | 31 | global.Storage = require("../src/js/global/storage.js"); 32 | global.Utils = require("../src/js/global/utils.js"); 33 | -------------------------------------------------------------------------------- /src/sass/content.scss: -------------------------------------------------------------------------------- 1 | @-webkit-keyframes working0 { 2 | from { transform: scale(1); } 3 | to { transform: scale(1.05); } 4 | } 5 | 6 | .form-o-fill-overlay-form { 7 | cursor: pointer; 8 | position: absolute; 9 | background-color: #ff0000; 10 | opacity: .5; 11 | z-index: 10000001; 12 | border-radius: 5px; 13 | border: 1px solid #fff; 14 | } 15 | 16 | 17 | .form-o-fill-overlay-text { 18 | position: absolute; 19 | top: 0; 20 | left: 0; 21 | font-family: "Open sans", Arial, Helvetica; 22 | font-size: 12px; 23 | color: #fff; 24 | padding: 10px; 25 | } 26 | 27 | #form-o-fill-working-overlay { 28 | position: fixed; 29 | top: 50%; 30 | left: 50%; 31 | width: 20%; 32 | margin-left: -10%; 33 | background-color: #000; 34 | opacity: 0.5; 35 | color: #fff; 36 | font-family: Arial, Helvetica, sans serif; 37 | text-align: center; 38 | vertical-align: middle; 39 | padding: 20px; 40 | border-radius: 5px; 41 | font-size: 20px; 42 | -webkit-animation: working0 750ms 40 alternate; 43 | box-shadow: #999 5px 5px 5px; 44 | } 45 | -------------------------------------------------------------------------------- /src/js/background/context_menu.js: -------------------------------------------------------------------------------- 1 | /* Much to be learned here. 2 | * Simple things can take much longer than expected. 3 | * The documentation for using the chrome.contextMenus is simple aweful and wrong in some places. 4 | * You MUST supply an "id" field or the contextmenu will not be shown. Its NOT optional as the doc says. 5 | * */ 6 | var ctxHandleExtractClick = function(menuItem) { 7 | if (menuItem.menuItemId === "ctxMain") { 8 | var message = { 9 | "action": "extractLastClickedForm" 10 | }; 11 | 12 | // Send message to content script 13 | // to extract last clicked form 14 | chrome.tabs.query({"active": true, "lastFocusedWindow": true}, function(tabs) { 15 | chrome.tabs.sendMessage(tabs[0].id, message); 16 | }); 17 | } 18 | }; 19 | 20 | chrome.contextMenus.onClicked.addListener(ctxHandleExtractClick); 21 | 22 | chrome.runtime.onInstalled.addListener(function () { 23 | chrome.contextMenus.create({ 24 | "title": "Form-O-Fill: Save Form", 25 | "contexts": ["editable", "page", "frame"], 26 | "id": "ctxMain" 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /test/integration/shared_rules_scene.js: -------------------------------------------------------------------------------- 1 | require("../support/integration_helper"); 2 | 3 | describe("form filling with shared rules", function () { 4 | this.timeout(9999); 5 | 6 | it("fills the form correctly", function (done) { 7 | Tests.visit("12-shared-rules") 8 | .getValue("#i1", function (err, value) { 9 | expect(value).to.eq("filled by original"); 10 | }) 11 | .getValue("#i2", function (err, value) { 12 | expect(value).to.eq("shared rule 2"); 13 | }) 14 | .getValue("#i3", function (err, value) { 15 | expect(value).to.eq("shared rule 3"); 16 | }) 17 | .call(done); 18 | }); 19 | 20 | it("reports an error if a 'import' definition is not found", function (done) { 21 | Tests.visit("11-shared-rules-broken") 22 | .getText(".notification-html", function (err, text) { 23 | expect(text).to.eq("Found an 'import' statement without matching rule. Click here to see more info."); 24 | }) 25 | .click(".extension-options-url a") 26 | .pause(global.pause) 27 | .call(done); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /src/sass/_notice.scss: -------------------------------------------------------------------------------- 1 | .notice:last-child { 2 | margin-bottom: 0; 3 | } 4 | 5 | .notice { 6 | display: none; 7 | border: 1px solid #EBCCD1; 8 | padding: 10px 25px 10px 36px; 9 | border-radius: 5px; 10 | vertical-align: middle; 11 | font-size: 14px; 12 | position: relative; 13 | background-color: #F2DEDE; 14 | margin-bottom: 10px; 15 | } 16 | 17 | .notice:before { 18 | font-family: "formofill"; 19 | font-size: 24px; 20 | vertical-align: middle; 21 | content: "\e602"; 22 | color: rgb(197, 25, 25); 23 | position: absolute; 24 | top: 6px; 25 | left: 7px; 26 | } 27 | 28 | .notice a.cmd-close-notice:after { 29 | font-family: "formofill"; 30 | font-size: 18px; 31 | content: "\e607"; 32 | padding: 2px; 33 | position: absolute; 34 | top: 0; 35 | right: 0; 36 | color: #999; 37 | cursor: pointer; 38 | } 39 | 40 | .notice .code { 41 | font-family: "droid sans mono", monospace, "courier new", courier, sans-serif; 42 | background-color: #bbb; 43 | padding: 2px 4px; 44 | } 45 | 46 | .notice.form-fill-errors span { 47 | text-decoration: underline; 48 | } 49 | 50 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 form-o-fill 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /src/html/options/tutorial/_tour12_en.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 |
6 |

7 | This is very simple. Since the before function is only resolved once resolve() is called we can call other functions before that. 8 |

9 |
10 | 11 |
12 |

13 | In this case its a simple call to prompt which blocks until the user has dismissed the dialog. 14 |

15 |
16 | 17 |
18 |

19 | Then we simply resolve the value. 20 |

21 |
22 | 23 |
24 |

25 | That it for now! Have a good one. 26 |

27 |
28 | 29 |
30 | -------------------------------------------------------------------------------- /src/sass/_settings.scss: -------------------------------------------------------------------------------- 1 | #settings { 2 | .comment { 3 | line-height: 20px; 4 | margin-left: 16px; 5 | color: #999; 6 | } 7 | 8 | .tabcontainer { 9 | margin-bottom: 15px; 10 | } 11 | 12 | .tab.current input { 13 | width: auto; 14 | } 15 | 16 | li.info { 17 | display: inline-block; 18 | color: #22AD19; 19 | margin-left: 15px; 20 | } 21 | 22 | span { 23 | color: #666; 24 | } 25 | 26 | .text, .checkbox { 27 | margin-bottom: 10px; 28 | border-bottom: 1px solid #999; 29 | padding-bottom: 10px; 30 | 31 | &.no-cb { 32 | .comment, input { 33 | margin-left: 0; 34 | } 35 | } 36 | } 37 | 38 | input[type=url], input[type=password] { 39 | margin-left: 15px; 40 | padding: 6px; 41 | width: 400px; 42 | } 43 | 44 | button.save:before { 45 | font-family: "formofill"; 46 | font-size: 16px; 47 | vertical-align: middle; 48 | margin-right: 4px; 49 | } 50 | 51 | .grouped { 52 | .text, .checkbox { 53 | border-bottom: 0; 54 | } 55 | 56 | div:nth-child(1n+2) { 57 | margin-left: 15px; 58 | } 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /testcases/docroot-for-testing/form-o-fill-testing/09-options.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Form-O-Fill Testpage 6 | 7 | 8 | 9 |

TESTING SERVER RUNNING

10 | 11 |
12 |     Clicking -> Error notification -> click that -> rule editor. 
13 |     
14 | 15 | Input field DATE:
16 | Input field:
17 |
18 | Rules Import: 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/sass/_tutorial.scss: -------------------------------------------------------------------------------- 1 | .introjs-prevbutton { 2 | visibility: hidden; 3 | width: 0; 4 | display: none; 5 | } 6 | 7 | .introjs-skipbutton { 8 | float: left; 9 | } 10 | 11 | .tuts { 12 | display: none; 13 | } 14 | 15 | .introjs-helperLayer.hidden { 16 | display: none; 17 | } 18 | 19 | .introjs-showElement { 20 | z-index: 99599999!important; 21 | } 22 | 23 | #ruleeditor-ace .ace_marker-layer .ace_active-line.ace_highlight-line { 24 | background-color: #EEE; 25 | border: 2px solid #3EBB3A; 26 | margin-top: -3px; 27 | border-radius: 5px; 28 | padding-top: 1px; 29 | box-shadow: #aaa 2px 2px; 30 | } 31 | 32 | .introjs-tooltip[data-width=xl] { 33 | min-width: 400px 34 | } 35 | 36 | .introjs-tooltip code { 37 | background-color: #eee; 38 | color: #AF2F2F; 39 | } 40 | 41 | [class^="icon-"], [class*=" icon-"] { 42 | font-family: 'formofill'; 43 | speak: none; 44 | font-style: normal; 45 | font-weight: normal; 46 | font-variant: normal; 47 | text-transform: none; 48 | -webkit-font-smoothing: antialiased; 49 | -moz-osx-font-smoothing: grayscale; 50 | } 51 | 52 | .icon-cascade:before { 53 | content: "\e608"; 54 | } 55 | 56 | .select-workflow { 57 | color: #192E98; 58 | } 59 | -------------------------------------------------------------------------------- /src/js/background/screenshooter.js: -------------------------------------------------------------------------------- 1 | /*global state Utils */ 2 | var Screenshooter = function() { 3 | }; 4 | 5 | Screenshooter.prototype.generateFilename = function(metadata) { 6 | var ruleNameAsFilename = metadata.name.replace(/[^a-z0-9-_]/gi, '_') + ".jpg"; 7 | return "fof-screenshot-" + metadata.ruleId.replace(/([0-9]+)-([0-9]+)/, "tab-$1-rule-$2-field-") + metadata.fieldIndex + "_" + ruleNameAsFilename; 8 | }; 9 | 10 | // Takes screenshot of a window 11 | // and downloads it to disk 12 | Screenshooter.prototype.takeScreenshot = function(windowId, ruleMetadata, potentialFilename) { 13 | var quality = parseInt(state.optionSettings.jpegQuality, 10) || 60; 14 | var fName; 15 | 16 | // force download of the image 17 | if (typeof potentialFilename === "string") { 18 | // use user defined name 19 | fName = potentialFilename.replace(/[^a-z0-9-_]/gi, '_') + ".jpg"; 20 | } else if (ruleMetadata) { 21 | // use generated name 22 | fName = this.generateFilename(ruleMetadata); 23 | } else { 24 | return; 25 | } 26 | 27 | chrome.tabs.captureVisibleTab(windowId, { format: "jpeg", quality: quality}, function(screenshotDataUri) { 28 | Utils.downloadImage(screenshotDataUri, fName); 29 | }); 30 | }; 31 | -------------------------------------------------------------------------------- /testcases/docroot-for-testing/form-o-fill-testing/04-filling-all-matched-fields.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Form-O-Fill Testpage 6 | 7 | 8 | 9 |

TESTING SERVER RUNNING

10 | Input field: (Should be "filled")
11 | Input field: (Should be "filled")
12 | Input field: (Should be "filled")
13 | Rules Import: 28 |
29 | 30 | 31 | -------------------------------------------------------------------------------- /testcases/docroot-for-testing/form-o-fill-testing/03-before-function-libs-usage.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Form-O-Fill Testpage 6 | 7 | 8 | 9 |

TESTING SERVER RUNNING

10 | Input field: (Click : Throbber with "custom halting message")
11 |
SOME CONTENT
12 | Rules Import: 26 |
27 | 28 | 29 | -------------------------------------------------------------------------------- /testcases/docroot-for-testing/form-o-fill-testing/02-before-context.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Form-O-Fill Testpage 6 | 7 | 8 | 9 |

TESTING SERVER RUNNING

10 | Input field: (Should be <div id="form-o-fill-some-content">SOME CONTENT</div>)
11 |
SOME CONTENT
12 | Rules Import: 26 |
27 | 28 | 29 | -------------------------------------------------------------------------------- /src/html/options/tutorial/_tour9_en.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |

5 | The context is passed into the before function as the second parameter. 6 |

7 |

8 | Then the rule simply resolves the .url value making it available as data inside value functions. 9 |

10 |
11 | 12 |
13 |

14 | This value function simply formats the data and returns it. 15 |

16 |
17 | 18 |
19 |

20 | If you want to change the data based on the page url that is the way to go. 21 |

22 |
23 | 24 |
25 |

26 | Let's look at context.findHtml(). 27 |

28 |

29 | Please click here to open the second part. 30 |

31 |
32 | 33 |
34 | -------------------------------------------------------------------------------- /src/html/options/help-content/_help-automaticrematch_en.html: -------------------------------------------------------------------------------- 1 | 2 |

Automatic rematch of rules

3 |

4 | Form-O-Fill searches for matching rules whenever you visit a webpage or the URL changes.
5 | This is usually enough but for those cases where you need a re-matching of the rules without
6 | one of those two events mentioned above you can use a feature called "Automatic rematch". 7 |

8 |

9 | If enabled Form-O-Fill will search for matching rules every two seconds. 10 |

11 |

12 | This is very useful for apps that change the DOM on the fly.
13 | Those changes will not be detected if automatic rematch is turned off. 14 |

15 |

16 | There are two ways to toggle automatic rematch on or off.
17 | You can use the option in the settings panel or click "Automatic rematch" in the extension popup:
18 |
19 | The popup text will turn red if not active and turn green if the automatic rematch is active. 20 |

21 |

22 | Once active the extension icon text background will also turn green:
23 |
24 |

25 | -------------------------------------------------------------------------------- /testcases/docroot-for-testing/form-o-fill-testing/14-value-function-helper.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Form-O-Fill Testpage 6 | 7 | 8 | 9 | 10 |

TESTING SERVER RUNNING

11 | 12 |
13 |     This tests Libs.h.click.
14 | 
15 |     Click extension icon -> "button was clicked"
16 |     
17 | Input field:
18 | Button:
19 | Rules Import: 33 |
34 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /testcases/docroot-for-testing/form-o-fill-testing/15-value-function-storage.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Form-O-Fill Testpage 6 | 7 | 8 | 9 |

TESTING SERVER RUNNING

10 | 11 |
12 |     This tests context.storage.set/get in value functions.
13 | 
14 |     click -> "Set by before function"
15 |     
16 | 17 | Input field:
18 | Rules Import: 36 |
37 | 38 | 39 | -------------------------------------------------------------------------------- /testcases/docroot-for-testing/form-o-fill-testing/19-setup-content.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Form-O-Fill Testpage 6 | 7 | 8 | 9 |

TESTING SERVER RUNNING

10 | 11 |
12 |     Load page -> autorun -> Row with "Created by setupContent : filled by field def"  
13 |     
14 | 15 |
16 | Input field:
17 |
18 | Rules Import: 37 | 38 | 39 |
40 | 41 | 42 | -------------------------------------------------------------------------------- /testcases/docroot-for-testing/form-o-fill-testing/11-shared-rules-broken.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Form-O-Fill Testpage 6 | 7 | 8 | 9 |

TESTING SERVER RUNNING

10 | 11 |
12 |     Clock on button -> Notification "import statement with missign rule" -> click -> rule editor with message.
13 |     
14 | 15 | Input field:
16 | Rules Import: 30 |
31 | 32 | 33 | -------------------------------------------------------------------------------- /bundles.txt: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | context: __dirname + "/src/js", 3 | entry: { 4 | background: "background/background.js", 5 | popup: "popup/popup.js", 6 | content: "content/content.js", 7 | global: "global/global.js", 8 | options: "options/options.js", 9 | }, 10 | output: { 11 | path: "src/js", 12 | filename: "[name].js", 13 | pathinfo: true 14 | }, 15 | module: { 16 | loaders: [ 17 | { test: /\.js$/, loader: "babel-loader" } 18 | ] 19 | }, 20 | resolve: { 21 | extensions: ["", ".js", ".json"] 22 | } 23 | }; 24 | 25 | Bundles 26 | ------- 27 | 28 | background.js 29 | - background.js 30 | - changelog.js 31 | - context_menu.js 32 | - form_util.js 33 | - notification.js 34 | - testing.js 35 | - tutorial.js 36 | 37 | popup.js 38 | - popup.js 39 | 40 | content.js 41 | - content.js 42 | - context.js 43 | - context_menu.js 44 | - extract_instrumentation.js 45 | - form_errors.js 46 | - form_extractor.js 47 | - form_filler.js 48 | - testing.js 49 | 50 | global.js 51 | - utils.js 52 | - jsonf.js 53 | - storage.js 54 | - rule.js 55 | - rules.js 56 | - i18n.js 57 | - libs.js 58 | - workflow.js 59 | 60 | options.js 61 | - editor.js 62 | - chrome_bootstrap.js 63 | - tabs.js 64 | - import_export.js 65 | - tutorial.js 66 | - options.js 67 | - help.js 68 | - workflow.js 69 | - settings.js 70 | - rule_summary.js 71 | 72 | options.css 73 | - options.scss 74 | 75 | content.css 76 | - content.scss 77 | 78 | popup.css 79 | - popup.scss 80 | -------------------------------------------------------------------------------- /testcases/docroot-for-testing/form-o-fill-testing/26-run-value-function-on-every-element.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Form-O-Fill Testpage 6 | 7 | 8 | 9 |

TESTING SERVER RUNNING

10 |
11 |       Run value function separatly on every dom element
12 |       https://github.com/form-o-fill/form-o-fill-chrome-extension/issues/101
13 |     
14 |
15 | 16 | 17 |
18 |
19 |
20 |
21 | Rules Import: 40 | 41 | 42 |
43 | 44 | 45 | -------------------------------------------------------------------------------- /test/support/integration_helper.js: -------------------------------------------------------------------------------- 1 | /*global beforeEach browser*/ 2 | // This file is run before all other specs 3 | // mocha is already global here 4 | global.chai = require("chai"); 5 | global.chai.use(require("sinon-chai")); 6 | global.expect = require("chai").expect; 7 | global.manifest = require('../../src/manifest'); 8 | 9 | var webdriverio = require('webdriverio'); 10 | 11 | var options = { 12 | desiredCapabilities: { 13 | browserName: 'chrome', 14 | chromeOptions: { 15 | 'args': [ 16 | 'load-extension=src', 17 | 'enable-embedded-extension-options' 18 | ] 19 | } 20 | } 21 | }; 22 | 23 | global.browser = webdriverio.remote(options).init(); 24 | global.pause = 1000; 25 | 26 | // 27 | // HELPER function to make life easier: 28 | // 29 | 30 | var Tests = { 31 | // Go to a testing URL and give the extension some time to inject its HTML 32 | visit: function(htmlPage) { 33 | var page = browser 34 | .url("http://localhost:9292/form-o-fill-testing/" + htmlPage + ".html"); 35 | // Import rules if present 36 | page.isExisting("#form-o-fill-testing-import-submit", function (err, isExisting) { 37 | if (isExisting) { 38 | page = page.click("#form-o-fill-testing-import-submit"); 39 | } 40 | }); 41 | return page.pause(global.pause); 42 | } 43 | }; 44 | 45 | global.Tests = Tests; 46 | 47 | // 48 | // Global hooks for mocha 49 | // 50 | //afterEach(function () { 51 | //browser.endAll(); 52 | //browser.init(); 53 | //}); 54 | 55 | -------------------------------------------------------------------------------- /src/html/popup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Form-O-Fill Popup 5 | 6 | 7 | 8 | 9 |

Found n matches:

10 | 11 |
12 | Cancel workflows 13 | Extract 14 | Options 15 | Automatic rematch 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/sass/_editor.scss: -------------------------------------------------------------------------------- 1 | #ruleeditor { 2 | height: 100%; 3 | width: 80%; 4 | } 5 | 6 | #ruleeditor-ace { 7 | width: 800px; 8 | height: 100%; 9 | min-height: 500px; 10 | border: 1px solid #909090; 11 | } 12 | 13 | .editor .menu { 14 | margin-bottom: 10px; 15 | } 16 | 17 | .menu button { 18 | vertical-align: bottom; 19 | padding: 0 6px; 20 | width: auto; 21 | outline: none; 22 | } 23 | 24 | .menu button:before { 25 | font-family: "formofill"; 26 | font-size: 16px; 27 | vertical-align: middle; 28 | margin-right: 4px; 29 | } 30 | 31 | button.save:before { 32 | content: "\e603"; 33 | } 34 | 35 | button.reload:before { 36 | content: "\e60a"; 37 | } 38 | 39 | button.format:before { 40 | content: "\e608"; 41 | } 42 | 43 | button.empty:before { 44 | content: "\e600"; 45 | } 46 | 47 | button.export:before, .all-button-export:before { 48 | content: "\e605"; 49 | } 50 | 51 | button.import:before, .rl-button-import:before, .all-button-import:before { 52 | content: "\e604"; 53 | } 54 | 55 | .editor .menu .info, #workflows span.info { 56 | color: #999; 57 | line-height: 32px; 58 | vertical-align: middle; 59 | margin-left: 10px; 60 | font-size: 11px; 61 | } 62 | .form-fill-errors table { 63 | margin-top: 10px; 64 | font-family: monospace, Arial, Helvetica; 65 | border: 1px solid #bbb; 66 | } 67 | 68 | .form-fill-errors th { 69 | text-align: left; 70 | background-color: transparent; 71 | } 72 | 73 | .form-fill-errors tr:first-child { 74 | background-color: #ddd; 75 | } 76 | 77 | .form-fill-errors td { 78 | padding: 0 10px 0 0; 79 | } 80 | 81 | -------------------------------------------------------------------------------- /test/integration/popup_scene.js: -------------------------------------------------------------------------------- 1 | require("../support/integration_helper"); 2 | 3 | describe("the popup HTML", function() { 4 | this.timeout(99999); 5 | 6 | describe("when rules match", function() { 7 | it("contains selectable
  • elements", function(done){ 8 | Tests.visit("13-simple") 9 | .click("#form-o-fill-testing-import-submit") 10 | .pause(500) 11 | .getTagName(".popup-html li.select-rule", function (err, tagNames) { 12 | expect(tagNames.length).to.eql(10); 13 | }) 14 | .call(done); 15 | }); 16 | 17 | it("contains an link to the options page and the extract overlay", function (done){ 18 | Tests.visit("13-simple") 19 | .getText(".popup-html a.cmd-show-extract-overlay", function (err, text) { 20 | expect(text).to.eql("Extract"); 21 | }) 22 | .getText(".popup-html a.to-options", function (err, text) { 23 | expect(text).to.eql("Options"); 24 | }) 25 | .call(done); 26 | }); 27 | }); 28 | 29 | describe("when no rules match", function() { 30 | 31 | it("contains a link to extract a rules", function (done){ 32 | Tests.visit("06-no-matching-rules") 33 | .click("#form-o-fill-testing-import-submit") 34 | .pause(500) 35 | .getText(".popup-html h3", function (err, text) { 36 | expect(text).to.match(/Found no matching rules./); 37 | }) 38 | .getText(".popup-html a.cmd-show-extract-overlay:first-child", function (err, text) { 39 | expect(text).to.eql("Create one ?"); 40 | }) 41 | .call(done); 42 | }); 43 | 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /src/html/options/tutorial/_tour10_en.html: -------------------------------------------------------------------------------- 1 |
    2 | 3 | 4 | 5 |
    6 |

    7 | Here you see the usage of the findHtml function. 8 |

    9 |

    10 | First it finds the selected input element. 11 |

    12 |

    13 | The function you put inside the .then() receives the HTML of the element as a string. 14 |

    15 |
    16 | 17 |
    18 |

    19 | The rule then fills the local variable direction with either left or right. 20 |

    21 |
    22 | 23 |
    24 |

    25 | Then it resolves the data as either {direction: "left"} or {direction: "right"}. 26 |

    27 |
    28 | 29 |
    30 |

    31 | The value function then prints the value. 32 |

    33 |

    34 | Last up is context.storage which is most useful in workflows. 35 |

    36 |

    37 | Please click here to open the last part. 38 |

    39 |
    40 | 41 |
    42 | -------------------------------------------------------------------------------- /testcases/docroot-for-testing/form-o-fill-testing/12-shared-rules.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Form-O-Fill Testpage 6 | 7 | 8 | 9 |

    TESTING SERVER RUNNING

    10 | 11 |
    12 |     First input : "filled by original"
    13 |     Second input : "shared rule 2"
    14 |     Third input : "shared rule 3"
    15 |     
    16 | 17 | Input field:
    18 | Input field:
    19 | Input field:
    20 | Rules Import: 34 |
    35 | 36 | 37 | -------------------------------------------------------------------------------- /src/js/background/on_install.js: -------------------------------------------------------------------------------- 1 | /*global Utils Logger Rules Alarm loadSettings*/ 2 | // Checks to see if tabSettings are initialized 3 | // If not creates a default tabSetting 4 | var initializeTabSettings = function() { 5 | // Check if tabs are saved or we start from scratch 6 | Storage.load(Utils.keys.tabs).then(function (tabSettings) { 7 | // No tab settings found, create one 8 | if (typeof tabSettings === "undefined") { 9 | Logger.info("[bg.js] Creating default tab setting"); 10 | Storage.save([{ 11 | "id": 1, 12 | "name": chrome.i18n.getMessage("tabs_default_name") 13 | }], Utils.keys.tabs); 14 | 15 | // Initialize rules 16 | Rules.save(Utils.defaultRule, 1); 17 | } 18 | }); 19 | }; 20 | 21 | // Fires when the extension is install or updated 22 | chrome.runtime.onInstalled.addListener(function (details) { 23 | Logger.info("[bg.js] chrome.runtime.inInstalled triggered"); 24 | 25 | // Called on very first install 26 | if (details.reason === "install") { 27 | Notification.create(chrome.i18n.getMessage("first_install_notification"), null, function () { 28 | Utils.openOptions("#help"); 29 | }); 30 | } 31 | 32 | // Initialize tab settings 33 | initializeTabSettings(); 34 | 35 | // remove log entries 36 | Logger.delete(); 37 | 38 | // Check if there are notifications to display 39 | if (Utils.version.indexOf(".") > -1) { 40 | Notification.forVersion(Utils.version); 41 | } 42 | 43 | // load and set settings. Uses defaults if non present. 44 | loadSettings(); 45 | 46 | // This will trigger a re-import of the remote rules set in settings 47 | Alarm.create(); 48 | }); 49 | -------------------------------------------------------------------------------- /src/js/global/storage.js: -------------------------------------------------------------------------------- 1 | /*global Utils, Logger, JSONF */ 2 | /* eslint no-undef: 0, no-unused-vars: 0 */ 3 | var Storage = { 4 | load: function(keyToLoadFrom) { 5 | var key = keyToLoadFrom || Utils.keys.rules; 6 | return new Promise(function storageLoad(resolve) { 7 | chrome.storage.local.get(key, function prStorageLoad(persistedData) { 8 | Logger.debug("[storage.js] loaded '" + key + "'", JSONF.stringify(persistedData)); 9 | resolve(persistedData[key]); 10 | }); 11 | }); 12 | }, 13 | save: function (rulesCode, keyToSaveTo) { 14 | return new Promise(function (resolve, reject) { 15 | var value = {}; 16 | var key = keyToSaveTo || Utils.keys.rules; 17 | value[key] = rulesCode; 18 | chrome.storage.local.set(value, function storageSave() { 19 | if (typeof chrome.runtime.lastError === "undefined") { 20 | Logger.debug("[storage.js] Saved '" + key + "'", JSONF.stringify(value[key])); 21 | resolve(true); 22 | } else { 23 | reject(new Error(chrome.runtime.lastError)); 24 | } 25 | }); 26 | }); 27 | }, 28 | delete: function (key) { 29 | return new Promise(function (resolve, reject) { 30 | chrome.storage.local.remove(key, function storageDelete() { 31 | if (typeof chrome.runtime.lastError === "undefined") { 32 | Logger.debug("[storage.js] Removed key '" + key + "'"); 33 | resolve(true); 34 | } else { 35 | reject(new Error(chrome.runtime.lastError)); 36 | } 37 | }); 38 | }); 39 | } 40 | }; 41 | 42 | // REMOVE START 43 | if (typeof exports === "object") { 44 | module.exports = Storage; 45 | } 46 | // REMOVE END 47 | -------------------------------------------------------------------------------- /src/html/options/tutorial/_tour1_en.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 | This is the rule editor. 4 |
    5 | 6 |
    7 |

    8 | Here you can insert the rules you just extracted from the <form> 9 |

    10 |
    11 | 12 |
    13 |

    14 | When there are extracted rules present, you will see this notification. 15 |

    16 |

    17 | Pressing the "here" link in the notification will insert the extracted rules into the current tab. 18 |

    19 |

    20 | Do that now. 21 |

    22 |
    23 | 24 |
    25 |

    26 | The extracted rules are now copied to the rule editor. 27 |

    28 |
    29 | 30 |
    31 |

    32 | Pressing the "Format" button will tidy up those rules. 33 |

    34 |

    35 | Do that now. 36 |

    37 |
    38 | 39 |
    40 |

    41 | To finish press the "Save" button. 42 |

    43 |

    This concludes the tour of the simple form extraction in Form-O-Fill.

    44 |
    45 | 46 |
    47 | -------------------------------------------------------------------------------- /src/html/options/help-content/_help-capture_en.html: -------------------------------------------------------------------------------- 1 | 2 |

    Capturing forms

    3 |

    4 | To capture the state of a form you are currently seeing follow these simple steps: 5 |

    6 | 38 | -------------------------------------------------------------------------------- /src/html/options/tutorial/_tour13_en.html: -------------------------------------------------------------------------------- 1 |
    2 | 3 | 4 | 5 |
    6 |

    7 | The first rule defines two fields. This is basic Form-O-Fill. 8 |

    9 |
    10 | 11 |
    12 |

    13 | The rule for the second page uses the import keyword to reference the first rule. 14 |

    15 |

    16 | References are always the name of the rule. 17 |

    18 |

    19 | If you reference a non-existing rule the extension will complain :) 20 |

    21 |
    22 | 23 |
    24 |

    25 | You can place the import everywhere inside field definitions and they will be imported in place. 26 |

    27 |
    28 | 29 |
    30 |

    31 | If you want to you can also use multiple imports. 32 |

    33 |

    34 | Just queue them up as multiple
    { "import": "some rule name" } blocks. 35 |

    36 |
    37 | 38 | 39 |
    40 |

    41 | Shared rules can remove duplication. We all like DRY don't we? 42 |

    43 |

    44 | That's all. Have fun. 45 |

    46 |
    47 | 48 |
    49 | -------------------------------------------------------------------------------- /src/js/global/i18n.js: -------------------------------------------------------------------------------- 1 | /*global jQuery, Utils */ 2 | /* eslint no-unused-vars: 0 */ 3 | // 4 | // Small abstraction over i18n supplied by chrome API 5 | // 6 | var I18n = { 7 | supportedLanguages: function() { 8 | return ["en"]; 9 | }, 10 | currentLocale: function() { 11 | var uiLang = this.userLocale(); 12 | var chosenLang = this.supportedLanguages().filter(function (supportedLanguage) { 13 | return supportedLanguage === uiLang; 14 | }); 15 | if (chosenLang.length === 1) { 16 | this._lang = chosenLang[0]; 17 | } else { 18 | this._lang = this.supportedLanguages()[0]; 19 | } 20 | return this._lang; 21 | }, 22 | userLocale: function() { 23 | return chrome.i18n.getUILanguage().replace(/_.*$/, "").toLowerCase(); 24 | }, 25 | loadPages: function(pages, prefix) { 26 | var i18n = this; 27 | var path = []; 28 | var appendDomSelector = ""; 29 | pages.forEach(function (pageName) { 30 | appendDomSelector = "#" + pageName; 31 | path = [ "html", "options" ]; 32 | if (typeof prefix !== "undefined") { 33 | path.push(prefix); 34 | appendDomSelector = "#" + prefix + " " + appendDomSelector; 35 | } 36 | path.push("_" + pageName + "_" + i18n.currentLocale() + ".html"); 37 | i18n._getAndInsert(path, appendDomSelector); 38 | }); 39 | }, 40 | _getAndInsert: function(path, appendDomSelector) { 41 | jQuery.get(chrome.runtime.getURL(path.join("/")), function (html) { 42 | jQuery(appendDomSelector).html(html); 43 | jQuery(document).trigger("i18n-loaded", [ path.join("/") ]); 44 | }); 45 | } 46 | }; 47 | 48 | // REMOVE START 49 | if (typeof exports === "object") { 50 | module.exports = I18n; 51 | } 52 | // REMOVE END 53 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "form-o-fill-chrome-extension", 3 | "version": "1.0.0", 4 | "description": "The programmable form filler for developers", 5 | "main": "gulpfile.js", 6 | "private": true, 7 | "devDependencies": { 8 | "gulp-minify-css": "^1.2.4", 9 | "chai": "^3.5.0", 10 | "chai-as-promised": "^6.0.0", 11 | "chalk": "^1.1.3", 12 | "clean-css": "^3.4.20", 13 | "eslint": "^3", 14 | "event-stream": "^3.3.4", 15 | "glob": "^7.1.1", 16 | "gulp": "^3.9.1", 17 | "gulp-cleanhtml": "^1.0.1", 18 | "gulp-concat": "^2.6.0", 19 | "gulp-connect": "^5.0.0", 20 | "gulp-cssnano": "^2.1.2", 21 | "gulp-debug": "^2.1.2", 22 | "gulp-eslint": "^3.0.1", 23 | "gulp-load-plugins": "^1.3.0", 24 | "gulp-replace-task": "^0.11.0", 25 | "gulp-rm": "^1.0.1", 26 | "gulp-sass": "^2.3.2", 27 | "gulp-sourcemaps": "^2.1.1", 28 | "gulp-spawn-mocha": "^3.1.0", 29 | "gulp-strip-debug": "^1.1.0", 30 | "gulp-uglify": "^3.0.2", 31 | "gulp-util": "^3.0.7", 32 | "gulp-webdriver": "^2.0.2", 33 | "gulp-zip": "^3.2.0", 34 | "jsdom": "^9.8.3", 35 | "mocha": "^3.1.2", 36 | "promise": "^8.0.0", 37 | "selenium-standalone": "^5.7.2", 38 | "sinon": "^1.17.6", 39 | "sinon-chai": "^2.8.0", 40 | "through2": "^2.0.1", 41 | "webdriverio": "^4.2.16", 42 | "yargs": "^6.1.1" 43 | }, 44 | "scripts": { 45 | "test": "gulp test" 46 | }, 47 | "repository": { 48 | "type": "git", 49 | "url": "https://github.com/form-o-fill/form-o-fill-chrome-extension.git" 50 | }, 51 | "author": "", 52 | "license": "MIT", 53 | "bugs": { 54 | "url": "https://github.com/form-o-fill/form-o-fill-chrome-extension/issues" 55 | }, 56 | "homepage": "http://form-o-fill.github.io" 57 | } 58 | -------------------------------------------------------------------------------- /src/js/background/notification.js: -------------------------------------------------------------------------------- 1 | /*global Testing, Utils, Changelog */ 2 | /*eslint no-undef:0 */ 3 | var Notification = { 4 | create: function(message, title, onClickCallback) { 5 | if (title === null) { 6 | title = "Form-O-Fill"; 7 | } 8 | chrome.notifications.create(Math.random().toString(), { 9 | "iconUrl": chrome.runtime.getURL("images/icon_48.png"), 10 | "type": "basic", 11 | "title": title, 12 | "message": message, 13 | "isClickable": true, 14 | "requireInteraction": false 15 | }, function(notificationId) { 16 | if (!Utils.isLiveExtension()) { 17 | Testing.setVar("notification-html", message, "Last Notification HTML"); 18 | Testing.setVar("notification-status", "visible", "Last Notification status"); 19 | Testing.setVar("notification-callback", onClickCallback.toString(), "Last Notification click callback"); 20 | } 21 | setTimeout( function() { 22 | chrome.notifications.clear(notificationId); 23 | }, Utils.notificationTimeoutMs); 24 | }); 25 | 26 | chrome.notifications.onClicked.addListener(function () { 27 | if (!Utils.isLiveExtension()) { 28 | Testing.setVar("notification-status", "clicked", "Last Notification status"); 29 | } 30 | onClickCallback(); 31 | }); 32 | }, 33 | forVersion: function(version) { 34 | var notificationContent = Changelog.findForVersion(version); 35 | if (notificationContent) { 36 | Notification.create(notificationContent.message, notificationContent.title, function() { 37 | if (notificationContent.target.indexOf("http") === 0) { 38 | chrome.tabs.create({url: notificationContent.target}); 39 | } else { 40 | Utils.openOptions(notificationContent.target); 41 | } 42 | }); 43 | } 44 | } 45 | }; 46 | -------------------------------------------------------------------------------- /src/html/options/help-content/_help-workflows_en.html: -------------------------------------------------------------------------------- 1 | 2 |

    Workflows

    3 |

    4 | Workflows allow you to chain rules together to form sequences of actions. 5 |

    6 |

    7 | While a single rule can fill forms on a single page, workflows continue to execute even after a 8 | page reload. 9 |

    10 |

    11 | Use the "Workflows" menu on the left side to create 12 | workflows and add existing rules in an arbitrary order.
    13 | You can drag and drop those workflow steps to reorder them. 14 |

    15 |

    16 | Notice that a workflow by itself doesn't submit any forms or changes or reloads the page url.
    17 | This is something you must do at the end of every rule. 18 |

    19 |

    20 | Usually you can archieve this by using a value function that clicks 21 | on a link or button. 22 |

    23 |

    24 | Example rule that clicks on a submit button: 25 |

    26 |
    27 |   {
    28 |     name: "a clicking rule",
    29 |     url: /some-url/,
    30 |     fields: [{
    31 |       selector: "form input[type=submit]",
    32 |       value: Libs.h.click
    33 |     }]
    34 |   }
    35 | 
    36 |

    37 | If you run a workflow and strange things happen (like you can't execute rules after that), try the 38 | button in the 39 | Workflows panel. 40 |

    41 |

    Executing workflows

    42 |

    43 | Once you have defined a workflow it will be shown in the rules popup 44 | if the condition of the first rule in the workflow matches.
    45 | Click on the workflow will execute it
    46 | This is what the popup will look like: 47 |

    48 |

    49 | 50 |

    51 | -------------------------------------------------------------------------------- /test/integration/options_scene.js: -------------------------------------------------------------------------------- 1 | require("../support/integration_helper"); 2 | 3 | describe("the options panel", function () { 4 | this.timeout(9999); 5 | 6 | it("is contains all essential parts", function (done){ 7 | Tests.visit("09-options") 8 | .click(".extension-options-url a") 9 | .pause(global.pause) 10 | .getValue("li.tab.current input", function (err, text) { 11 | expect(text).to.eql(["dummy", "Workflows", "Settings"]); 12 | }) 13 | .isVisible("li.tab.more", function (err, isVisible) { 14 | // "More" tab 15 | expect(isVisible).to.eq(true); 16 | }) 17 | .isVisible("#rules-overview", function (err, isVisible) { 18 | // Quickjump menu 19 | expect(isVisible).to.eq(true); 20 | }) 21 | .isVisible(".menu button.save:first-child", function (err, isVisible) { 22 | // Action buttons 23 | expect(isVisible).to.eq(true); 24 | }) 25 | .isVisible(".menu button.reload", function (err, isVisible) { 26 | expect(isVisible).to.eq(true); 27 | }) 28 | .isVisible(".menu button.format", function (err, isVisible) { 29 | expect(isVisible).to.eq(true); 30 | }) 31 | .getElementSize("#ruleeditor-ace", function (err, size) { 32 | // Editor window 33 | expect(size.height).to.be.above(200); 34 | expect(size.width).to.be.above(200); 35 | }) 36 | .getText(".navigation .menu li", function (err, text) { 37 | // Navigation links 38 | expect(text).to.contain("Rule Editor"); 39 | expect(text).to.contain("Workflows"); 40 | expect(text).to.contain("Import / Export"); 41 | expect(text).to.contain("Tutorials"); 42 | expect(text).to.contain("About"); 43 | expect(text).to.contain("Changelog"); 44 | expect(text).to.contain("Settings"); 45 | }) 46 | .close() 47 | .call(done); 48 | }); 49 | 50 | }); 51 | -------------------------------------------------------------------------------- /testcases/docroot-for-testing/form-o-fill-testing/21-reeval-rules.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Form-O-Fill Testpage 6 | 7 | 8 | 9 |

    TESTING SERVER RUNNING

    10 | 11 |
    12 |     This demonstrates "automatic rematch".
    13 | 
    14 |     - Import
    15 |     - Deactivate (if active) "automatic rematch"
    16 |     - No matches
    17 |     - enable "autmatic rematch"
    18 |     - click "Insert target DOM element"
    19 |     - 1 match
    20 |     - click "Change URL"
    21 |     - 2 matches
    22 |     
    23 | 24 | 25 | 26 | 27 | 28 |
    29 | Rules Import: 48 | 49 | 50 |
    51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /src/sass/options.scss: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'formofill'; 3 | src: url('../fonts/formofill.woff?-n1boxl') format('woff'); 4 | font-weight: normal; 5 | font-style: normal; 6 | } 7 | 8 | body { 9 | font-family: 'Open Sans', Arial,sans-serif; 10 | color: #2c2c2c; 11 | margin: 0; 12 | padding: 10px; 13 | } 14 | 15 | .navigation h1:before { 16 | font-family: "formofill"; 17 | content: "\e600"; 18 | position: absolute; 19 | color: #d0d0d0; 20 | top: 7px; 21 | font-size: 3rem; 22 | z-index: -1; 23 | left: 40px; 24 | } 25 | 26 | .overlay .close-button { 27 | cursor: pointer; 28 | } 29 | 30 | .modalimport { 31 | display: none; 32 | } 33 | 34 | .only-json, .decryption-error { 35 | display: none; 36 | } 37 | 38 | #rules-overview { 39 | float: right; 40 | margin-top: 7px; 41 | } 42 | 43 | span.version { 44 | position: absolute; 45 | top: 20px; 46 | right: 10px; 47 | color: #ccc; 48 | display: block; 49 | } 50 | 51 | h3 span.unsaved-changes { 52 | float: right; 53 | font-size: 11px; 54 | color: Red; 55 | display: none; 56 | } 57 | 58 | .beta { 59 | color: #f00000; 60 | transform: rotate(45deg); 61 | position: absolute; 62 | } 63 | 64 | .unpacked-only { 65 | margin: 10px 0; 66 | } 67 | 68 | .modalimport { 69 | .content-sidebar { 70 | display: inline-block; 71 | padding-left: 15px; 72 | vertical-align: top; 73 | 74 | + .content-area { 75 | display: inline-block; 76 | } 77 | } 78 | 79 | .tldr { 80 | border: 1px solid #999; 81 | background-color: #eee; 82 | padding: 10px; 83 | } 84 | 85 | } 86 | 87 | @import "editor"; 88 | @import "workflows"; 89 | @import "help"; 90 | @import "tabs"; 91 | @import "tutorial"; 92 | @import "notice"; 93 | @import "chrome_bootstrap_overwrites"; 94 | @import "introjs_overwrites"; 95 | @import "changelog"; 96 | @import "settings"; 97 | @import "rule_summary"; 98 | @import "import_export"; 99 | -------------------------------------------------------------------------------- /src/sass/_tabs.scss: -------------------------------------------------------------------------------- 1 | .tabcontainer { 2 | position: relative; 3 | width: 100%; 4 | } 5 | 6 | .tabs { 7 | border-bottom: 1px solid #ddd; 8 | width: 100%; 9 | } 10 | 11 | .tabs .tab { 12 | border-top: 1px solid #ddd; 13 | border-left: 1px solid #ddd; 14 | border-right: 1px solid #ddd; 15 | padding: 6px; 16 | display: inline-block; 17 | margin-left: 5px; 18 | margin-bottom: -1px; 19 | border-radius: 3px 3px 0 0; 20 | cursor: pointer; 21 | height: 21px; 22 | position: relative; 23 | } 24 | 25 | .tabs .tab:first-child { 26 | margin-left: 0 27 | } 28 | 29 | .tabs .tab input { 30 | cursor: pointer; 31 | width: 75px; 32 | color: #bbb; 33 | } 34 | 35 | .tabs .tab a { 36 | display: inline-block; 37 | text-decoration: none; 38 | vertical-align: middle; 39 | } 40 | 41 | .tabs .tab a:after { 42 | font-family: formofill; 43 | content: "\e609"; 44 | font-size: 16px; 45 | margin-left: 6px; 46 | color: #bbb; 47 | } 48 | 49 | .tabs .tab:first-child a { 50 | visibility: hidden; 51 | } 52 | 53 | .tabs .tab a.edit { 54 | visibility: visible; 55 | } 56 | 57 | .tabs .tab a.edit:after { 58 | content: "\e603"; 59 | color: #008000; 60 | } 61 | 62 | .tabs .tab a.edit:hover:after { 63 | content: "\e603"; 64 | color: #008000; 65 | } 66 | 67 | .tabs .tab a:hover:after { 68 | color: #ddd; 69 | } 70 | 71 | .tabs .tab:last-child input { 72 | width: 10px; 73 | } 74 | 75 | .tabs .tab:last-child a:after { 76 | content: "\e606"; 77 | } 78 | 79 | .tabs .tab.current { 80 | border-bottom: 1px solid #fff; 81 | .rule-count { 82 | color: #111; 83 | } 84 | } 85 | 86 | .tabs .tab.current input { 87 | color: #111 88 | } 89 | 90 | .tabs .tab input[type=text] { 91 | border: 0; 92 | padding: 0; 93 | } 94 | 95 | .tabs .tab .rule-count { 96 | vertical-align: top; 97 | font-size: 11px; 98 | margin-left: 5px; 99 | } 100 | -------------------------------------------------------------------------------- /src/js/debug/logger.js: -------------------------------------------------------------------------------- 1 | var Logger = { 2 | storageKey: "form-o-fill-logs", 3 | out: function(level, msg, obj) { 4 | // Port to background.js 5 | if (typeof msg !== "undefined" && typeof obj !== "undefined") { 6 | console[level]("[*FOF*] %s %O", msg, obj); 7 | return; 8 | } 9 | 10 | if (typeof msg !== "undefined") { 11 | console[level]("[*FOF*] %s", msg); 12 | } 13 | }, 14 | info: function(msg, obj) { 15 | this.out("info", msg, obj); 16 | }, 17 | debug: function(msg, obj) { 18 | this.out("debug", msg, obj); 19 | }, 20 | warn: function(msg, obj) { 21 | this.out("warn", msg, obj); 22 | }, 23 | error: function(msg, obj) { 24 | this.out("error", msg, obj); 25 | }, 26 | delete: function() { 27 | chrome.storage.local.remove(Logger.storageKey); 28 | }, 29 | load: function() { 30 | return new Promise(function(resolve) { 31 | chrome.storage.local.get(Logger.storageKey, function(storage) { 32 | if (typeof storage[Logger.storageKey] === "undefined") { 33 | resolve([]); 34 | return; 35 | } 36 | resolve(storage[Logger.storageKey]); 37 | }); 38 | }); 39 | }, 40 | _dateOptions: function() { 41 | return { 42 | year: "numeric", 43 | month: "numeric", 44 | day: "numeric", 45 | hour: "numeric", 46 | minute: "numeric", 47 | second: "numeric", 48 | hour12: false, 49 | }; 50 | }, 51 | store: function(msg) { 52 | this.load().then(function(entries) { 53 | var parts = msg.match(/\[(.*?)\](.*)/); 54 | 55 | entries = entries.slice(-25); 56 | 57 | if (parts !== null) { 58 | entries.push({ 59 | createdAt: new Date().toLocaleString(), 60 | location: parts[1].trim(), 61 | message: msg, 62 | }); 63 | } 64 | 65 | var a = {}; 66 | a[Logger.storageKey] = entries; 67 | chrome.storage.local.set(a); 68 | }); 69 | }, 70 | }; 71 | -------------------------------------------------------------------------------- /src/vendor/ace/theme-clouds.js: -------------------------------------------------------------------------------- 1 | define("ace/theme/clouds",["require","exports","module","ace/lib/dom"],function(e,t,n){t.isDark=!1,t.cssClass="ace-clouds",t.cssText='.ace-clouds .ace_gutter {background: #ebebeb;color: #333}.ace-clouds .ace_print-margin {width: 1px;background: #e8e8e8}.ace-clouds {background-color: #FFFFFF;color: #000000}.ace-clouds .ace_cursor {color: #000000}.ace-clouds .ace_marker-layer .ace_selection {background: #BDD5FC}.ace-clouds.ace_multiselect .ace_selection.ace_start {box-shadow: 0 0 3px 0px #FFFFFF;}.ace-clouds .ace_marker-layer .ace_step {background: rgb(255, 255, 0)}.ace-clouds .ace_marker-layer .ace_bracket {margin: -1px 0 0 -1px;border: 1px solid #BFBFBF}.ace-clouds .ace_marker-layer .ace_active-line {background: #FFFBD1}.ace-clouds .ace_gutter-active-line {background-color : #dcdcdc}.ace-clouds .ace_marker-layer .ace_selected-word {border: 1px solid #BDD5FC}.ace-clouds .ace_invisible {color: #BFBFBF}.ace-clouds .ace_keyword,.ace-clouds .ace_meta,.ace-clouds .ace_support.ace_constant.ace_property-value {color: #AF956F}.ace-clouds .ace_keyword.ace_operator {color: #484848}.ace-clouds .ace_keyword.ace_other.ace_unit {color: #96DC5F}.ace-clouds .ace_constant.ace_language {color: #39946A}.ace-clouds .ace_constant.ace_numeric {color: #46A609}.ace-clouds .ace_constant.ace_character.ace_entity {color: #BF78CC}.ace-clouds .ace_invalid {background-color: #FF002A}.ace-clouds .ace_fold {background-color: #AF956F;border-color: #000000}.ace-clouds .ace_storage,.ace-clouds .ace_support.ace_class,.ace-clouds .ace_support.ace_function,.ace-clouds .ace_support.ace_other,.ace-clouds .ace_support.ace_type {color: #C52727}.ace-clouds .ace_string {color: #5D90CD}.ace-clouds .ace_comment {color: #BCC8BA}.ace-clouds .ace_entity.ace_name.ace_tag,.ace-clouds .ace_entity.ace_other.ace_attribute-name {color: #606060}.ace-clouds .ace_indent-guide {background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAE0lEQVQImWP4////f4bLly//BwAmVgd1/w11/gAAAABJRU5ErkJggg==") right repeat-y}';var r=e("../lib/dom");r.importCssString(t.cssText,t.cssClass)}) -------------------------------------------------------------------------------- /src/html/options/help-content/_help-setupcontent_en.html: -------------------------------------------------------------------------------- 1 | 2 |

    Prepare the webpage for rule execution

    3 |

    4 | While before functions are useful for preparing data they cannot access the webpage the user is seeing (aka. content page).
    5 | This is a basic premise when using chrome extensions and actually a very good thing security wise.
    6 |

    7 |

    8 | This is where setupContent comes into play. It is executed before the form filling starts in the context of the content page and thus has full access to the DOM. 9 |

    10 | 11 |
    12 |
    13 |
    14 |
    15 | var rules = [{ 16 |
    17 |
    18 | 19 |
    20 |
    21 |
    22 |
    23 |   setupContent: function() { 24 |
    25 |
    26 | 27 | 28 |
    29 |
    30 | You can use jQuery here.
    31 |
    32 |
    33 |     jQuery(".some-dom-element").trigger("click"); 34 |
    35 |
    36 | 37 |
    38 |
    39 | Or you can also use the context object.
    40 | It is the same object as available in value functions. 41 |
    42 |
    43 |     jQuery(".nice").val(context.storage.get("some-key-set-in-value-function")); 44 |
    45 |
    46 | 47 |
    48 |
    49 |
    50 |
    51 |   } 52 |
    53 |
    54 | 55 |
    56 |
    57 |
    58 |
    59 | }]; 60 |
    61 |
    62 | 63 |

    64 | There also is a teardownContent method with the same call signature that is run after the rule finished executing. 65 |

    66 | -------------------------------------------------------------------------------- /src/html/options/help-content/_help-ownlibraries_en.html: -------------------------------------------------------------------------------- 1 | 2 |

    Creating your own library of functions

    3 |

    4 | Besides using moment.js and chance.js to create testdata you call always roll your own functions.
    5 | Have a before function duplicated in multiple rules? Abstract that away into library functions. 6 |

    7 |

    8 | This is how to create your own functions: 9 |

    10 |
    11 |
    12 | Create a new tab and open up an array of objects: 13 |
    14 |
    15 | var libs = [{ 16 |
    17 |
    18 |
    19 |
    20 | The name of the function. Later you can access this function via 21 | Libs.fName() 22 |
    23 |
    24 |   export: "yourFunctionName", 25 |
    26 |
    27 |
    28 |
    29 | The functions code: 30 |
    31 |
    32 |   lib: function () { 33 |
    34 |
    35 |
    36 |
    37 | Your code goes here... 38 |
    39 |
    40 |     return "Hello from the library function 'yourFunctionName'"; 41 |
    42 |
    43 |
    44 |
    45 |   46 |
    47 |
    48 |   } 49 |
    50 |
    51 |
    52 |
    53 |   54 |
    55 |
    56 | }]; 57 |
    58 |
    59 |

    60 | All functions declared in this way (even those declared inside normal rules) are globally accessible.
    61 | Use them in you before- or value functions using Libs.yourFunctionName(). 62 |

    63 |

    64 | If you define a function with the same name multiple times it's quite unpredictable which one will get executed. So name you exports uniquely. 65 |

    66 | -------------------------------------------------------------------------------- /testcases/docroot-for-testing/form-o-fill-testing/25-trigger-dom-events.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Form-O-Fill Testpage 6 | 7 | 8 | 9 |

    TESTING SERVER RUNNING

    10 |
    11 |       DOM Events need to be triggerd by FoF
    12 |     
    13 |
    14 | 15 |
    16 |
    17 |
    18 |
    19 | Rules Import: 49 | 50 | 51 |
    52 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /src/html/options/tutorial/_tour3_en.html: -------------------------------------------------------------------------------- 1 |
    2 | 3 |
    4 |

    5 | This is how a value function looks like. 6 |

    7 |

    8 | Instead of using a simple value it defines a function. 9 |

    10 |
    11 | 12 |
    13 |

    14 | The value function will receive two parameters. 15 |

    16 |

    17 | The first is the DOM element that will be filled by the rule. 18 |

    19 |

    20 | In this case it is the <input type="text"> element. 21 |

    22 |

    23 | To make working with this element easier, Form-O-Fill will convert this native DOM element into a jQuery object so you can do things like 24 | $e.addClass("some-class"); 25 |

    26 |
    27 | 28 |
    29 |

    30 | The second parameter (data) contains the data returned from before functions (See Tour 4) 31 |

    32 |

    33 | This can be used to fill the field with pre-generated or calculated data. 34 |

    35 |
    36 | 37 |
    38 |

    39 | Whatever you return from the value function will be filled into the form field. 40 |

    41 |

    42 | If you return null the field will be left untouched and will not be filled. 43 |

    44 |
    45 | 46 |
    47 |

    48 | That concludes tour 3 of the Form-O-Fill tutorials. See ya! 49 |

    50 |
    51 | 52 |
    53 | -------------------------------------------------------------------------------- /test/integration/test_setup_scene.js: -------------------------------------------------------------------------------- 1 | require("../support/integration_helper"); 2 | 3 | describe("test setup", function() { 4 | it("installs the extension", function(done){ 5 | // The correct (but chrome intern) url for the installed extensions is 6 | // not chrome://extensions but this: 7 | browser 8 | .url("chrome://extensions-frame/") 9 | .pause(1000) 10 | .getText(".extension-title", function(err, text) { 11 | var isInstalled = text.some(function (t) { 12 | return t === "Form-O-Fill - The programmable form filler"; 13 | }); 14 | expect(isInstalled).to.eql(true); 15 | }) 16 | .call(done); 17 | }); 18 | 19 | it("starts a simple webserver on port 9292", function(done) { 20 | browser 21 | .url("localhost:9292/form-o-fill-testing/simple.html") 22 | .getTitle(function (err, title) { 23 | expect(title).to.eql("Form-O-Fill Testpage"); 24 | }).call(done); 25 | }); 26 | 27 | it("imports rules and shows some meta infos", function(done) { 28 | var page = Tests.visit("13-simple"); 29 | 30 | page.getText(".extension-id", function (err, text) { 31 | expect(text).to.match(/[a-z0-9]{32}/); 32 | }); 33 | 34 | page.getText(".extension-version", function (err, text) { 35 | expect(text).to.eql("##VERSION##"); 36 | }); 37 | 38 | page.getText(".tab-id", function (err, text) { 39 | expect(text).to.match(/[0-9]+/); 40 | }); 41 | 42 | page.getText(".extension-version", function (err, text) { 43 | expect(text).to.eql("##VERSION##"); 44 | }); 45 | 46 | page.getText(".testing-mode", function (err, text) { 47 | expect(text).to.eql("true"); 48 | }); 49 | 50 | page.getText(".browser-action-badge-text", function (err, text) { 51 | expect(text).to.eql("10"); 52 | }); 53 | 54 | page.getText(".matching-rules-count", function (err, text) { 55 | expect(text).to.eql("10"); 56 | }); 57 | 58 | page.getHTML(".popup-html", function (err, text) { 59 | expect(text).to.match(/

    Found 10 matches<\/h3>/); 60 | }); 61 | 62 | page.call(done); 63 | 64 | }); 65 | }); 66 | -------------------------------------------------------------------------------- /testcases/docroot-for-testing/form-o-fill-testing/17-workflow-storage-1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Form-O-Fill Testpage 6 | 7 | 8 | 9 |

    TESTING SERVER RUNNING 1

    10 | 11 |
    12 |     This tests context.storage.set/get in before functions in a workflow.
    13 | 
    14 |     Click the blue workflow icon : reach 17-workflow-storage-2.html
    15 |     Input field content: "Set by workflow step 1"
    16 |     
    17 | 18 |
    19 | Input field:
    20 | 21 |
    22 | Rules Import: 23 | 52 |
    53 | 54 | 55 | -------------------------------------------------------------------------------- /src/html/options/help-content/_help-settingsremoterules_en.html: -------------------------------------------------------------------------------- 1 | 2 |

    Using remote rules setup by others

    3 |

    4 | If you want to use Form-O-Fill but do not want to write your own rules you can use rules written 5 | by others (eg. colleagues).
    6 | These are called remote rules.
    7 | The only condition is that these rules must be available via http or https.
    8 | This is how you activate remote rules: 9 |

    10 |

    11 | Step 1:  Have someone build the rules and workflows that you need locally inside 12 | Form-O-Fill.
    13 | Step 2:  Export those rules a .js file
    14 | Step 3:  Put them on a server and make them accessible via http or https
    15 | Step 4:  Take the URL to the .js file and put it in the setting for 16 | "Activate remote rules import"
    17 | Step 5:  Press and Form-O-Fill 18 | will report success or failure. 19 |

    20 |

    21 | Form-O-Fill will keep these rules in a separate storage and will auto-update those rules every 22 | ##Utils.alarmIntervalInMinutes## minutes.
    23 |

    24 |

    25 | Those rules will then also be used when searching for rules and workflow matches.
    26 | You can see which rules are remote rules by looking for a cloud icon in the rules popup:
    27 |

    28 | 29 |

    Using the on-click rules installer

    30 |

    31 | To make using remote rules even easier, you can use 32 | http://form-o-fill.github.io/import-remote-rules/ 35 | to generate a link that will auto-install your remote rules. 36 |

    37 |

    38 | This makes it easier to pass on those remote rules and make them usable for users that might not 39 | find it easy to follow the steps mentioned above. 40 |

    41 |

    42 | Visiting the generated link will import the remote rules and display a notification in the top 43 | right corner with success or failure. 44 |

    45 | -------------------------------------------------------------------------------- /src/html/options/help-content/_help-iframe_en.html: -------------------------------------------------------------------------------- 1 | 2 |

    Selecting forms inside iframes

    3 |

    4 | If the form you want fill is contained inside an <iframe> you can use the within property 5 | of a field definition to scope the selector to the frame. 6 |

    7 |
    8 |





    9 |
    10 | var rules = [{
    11 |   name: "This is my first rule. So excited!",
    12 |   url: /^http.*test.html$/,
    13 |   fields: [{
    14 |
    15 |
    16 |
    17 |
    18 | Form-O-Fill will look for a <iframe> tag but you can use any CSS selector here.
    19 | Just make sure the within selector matches only one element. 20 |
    21 |
    22 |     within: "iframe", 23 |
    24 |
    25 |
    26 |
    27 | The selector to be used inside the iframe. 28 |
    29 |
    30 |     selector: "input[type=text]", 31 |
    32 |
    33 |
    34 |
    35 | The value to fill the field with. 36 |
    37 |
    38 |     value: "Nyan Nyan Nyan", 39 |
    40 |
    41 |
    42 |
    43 |   44 |
    45 |
    46 |   }] 47 |
    48 |
    49 |
    50 |
    51 |   52 |
    53 |
    54 | }]; 55 |
    56 |
    57 |

    58 | If you use within to access an iframe please be aware that the iframe must reside on the same domain as the main page.
    59 | Accessing the DOM of an iframe that is on a different domain is forbidden. 60 |

    61 | -------------------------------------------------------------------------------- /src/html/options/help-content/_help-filling_en.html: -------------------------------------------------------------------------------- 1 | 2 |

    Filling forms

    3 |

    4 | Filling forms is easy. When Form-O-Fill finds a matching rule or workflow for the site you are currently on, it will display the number of matches in it's button. 5 |

    6 | 19 |

    In case of an error

    20 |

    21 | Since you can use value or before functions there may be errors executing those when filling the form.
    22 | Those errors will be reported as an notification.
    23 |
    24 | Clicking on the notification will bring you back to the rules editor and display the error to you: 25 |

    26 | There have been errors while filling the form. The rule used was 'undefined in before function' (url: '/.*test.*/') 27 | 28 | 29 | 30 | 31 | 32 | 33 |
    selectorvalueerror
    Inside before functionfunction (resolve, env) {
        var a = env.notExisting.goAway;
        resolve(env.notExisting);
      }
    Cannot read property 'goAway' of undefined
    34 | 35 |
    36 |

    37 | -------------------------------------------------------------------------------- /src/html/options/_help_en.html: -------------------------------------------------------------------------------- 1 |
    2 |

    Help

    3 |
    4 |
    5 |

    6 | A highly programmable form filler for developers. 7 |

    8 |

    9 | Form-O-Fill is a tool from developers for developers so there is a some knowledge of javascript neccessary to work with this tool. 10 |

    11 | 19 | 26 | 33 |
    34 |
    35 |
    36 |
    37 |
    38 |
    39 |
    40 |
    41 |
    42 |
    43 |
    44 |
    45 |
    46 |
    47 |
    48 |
    49 |
    50 | -------------------------------------------------------------------------------- /test/integration/complex_scene.js: -------------------------------------------------------------------------------- 1 | /*eslint brace-style:0*/ 2 | require("../support/integration_helper"); 3 | 4 | describe("using all features at once", function() { 5 | this.timeout(99999); 6 | 7 | it("works", function (done) { 8 | Tests.visit("22-complex") 9 | .getText("td.settings", function(err, value) { 10 | // Check setting of reevalRules 11 | expect(JSON.parse(value).reevalRules).to.eql(false); 12 | }) 13 | .pause(500) 14 | .click(".cmd-toggle-re-match") 15 | .pause(500) 16 | .getText("td.settings", function(err, value) { 17 | // Check setting of reevalRules 18 | // Should now be "true" 19 | expect(JSON.parse(value).reevalRules).to.eql(true); 20 | }) 21 | .click(".clickme") 22 | .pause(2500) 23 | .click(".select-workflow") 24 | .pause(2500) 25 | .getValue("#target0", function (err, value) { expect(value).to.eql("set by setupContent"); }) 26 | .getValue("#target1", function (err, value) { expect(value).to.eql("value by json.json via jQuery.getJSON"); }) 27 | .getValue("#target2", function (err, value) { expect(value).to.match(/[a-zA-z]+ [a-zA-Z]+/); }) 28 | .getValue("#target3", function (err, value) { expect(value).to.contain("2015-"); }) 29 | .getValue("#target4", function (err, value) { expect(value).to.contain("clicked"); }) 30 | .getValue("#target5", function (err, value) { expect(value).to.contain("returned by customerFunction"); }) 31 | .getValue("#target6", function (err, value) { expect(value).to.contain("localhost"); }) 32 | .getValue("#target7", function (err, value) { expect(value).to.contain("Form-O-Fill Testpage"); }) 33 | .getValue("#target8", function (err, value) { expect(value).to.contain("set by shared rule"); }) 34 | .getValue("#target10", function (err, value) { expect(value).to.contain("Form-O-Fill Testpage"); }) 35 | .getValue("#target12", function (err, value) { expect(value).to.contain("teardownContent"); }) 36 | .getValue("#target13", function (err, value) { expect(value).to.contain("was empty"); }) 37 | .url().then(function(currentUrl) { 38 | expect(currentUrl.value).to.eql("http://localhost:9292/form-o-fill-testing/22-complex-2.html?"); 39 | }) 40 | .call(done); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /src/js/background/changelog.js: -------------------------------------------------------------------------------- 1 | /*eslint no-unused-vars: 0 */ 2 | var Changelog = { 3 | changes: { 4 | "2.0.0": { 5 | title: "2.0.0 : Workflows have arrived!", 6 | message: "Chain rules together to form sequences of actions. Even survives page reload!", 7 | target: "#workflows" 8 | }, 9 | "2.2.1": { 10 | title: "FoF updated: Save data between rules!", 11 | message: "Save data between rule executions. Ideal for workflows.", 12 | target: "#help-before-context" 13 | }, 14 | "2.2.2": { 15 | title: "FoF updated: Unified export/import!", 16 | message: "Unified export/import for rules & workflows. See changelog!", 17 | target: "#changelog" 18 | }, 19 | "2.3.0": { 20 | title: "FoF updated: Live tutorials!", 21 | message: "Learn the features of FoF with the live tutorials. See options.", 22 | target: "#tutorials" 23 | }, 24 | "2.3.1": { 25 | title: "FoF updated: More tutorials!", 26 | message: "Added tutorials for workflows, context and more. Have a look!", 27 | target: "#tutorials" 28 | }, 29 | "2.4.0": { 30 | title: "FoF updated: Screenshot + onlyEmpty", 31 | message: "Take screenshots! Fill only empty fields!", 32 | target: "#help-screenshot" 33 | }, 34 | "2.5.0": { 35 | title: "FoF updated: setupContent + auto. rematch", 36 | message: "prepare function for content! automatic rematch for SPAs!", 37 | target: "#changelog" 38 | }, 39 | "2.6.0": { 40 | title: "FoF updated: remote rules import", 41 | message: "Import remote rules from any URL. Share your rules with your colleagues.", 42 | target: "#help-settingsremoterules" 43 | }, 44 | "3.0.0": { 45 | title: "Major release 3.0", 46 | message: "Faster ruleset searching. Please read the blogpost.", 47 | target: "https://form-o-fill.github.io/releasing-version-3" 48 | }, 49 | "4.0": { 50 | title: "Major release 4.0", 51 | message: "Removal of unwanted permission for compliance", 52 | target: "#changelog" 53 | } 54 | }, 55 | findForVersion: function(version) { 56 | if (typeof this.changes[version] !== "undefined") { 57 | return this.changes[version]; 58 | } 59 | return null; 60 | } 61 | }; 62 | -------------------------------------------------------------------------------- /testcases/docroot-for-testing/form-o-fill-testing/24-auto-reload.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Form-O-Fill Testpage 6 | 7 | 8 | 9 |

    TESTING SERVER RUNNING

    10 | 11 |
    12 |       This runs a single rule continously until the field is submitted with the value "10"
    13 |       in which case it will redirect to "01-all-types.html" 
    14 |     
    15 | 16 |
    17 | 18 | 19 |
    20 | 21 | Rules Import: 48 | 49 | 50 |
    51 | 52 | 53 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /testcases/docroot-for-testing/form-o-fill-testing/07-only-empty-flag.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Form-O-Fill Testpage 6 | 7 | 8 | 9 | 10 |

    TESTING SERVER RUNNING

    11 | 12 |
    13 |     1. Click "Usage of onlyEmpty in rule" -> Fill empty input field.
    14 |     2. Click "Usage of onlyEmpty in field definition" -> Fill empty input field.
    15 |     
    16 | 17 | Empty field: .val()
    18 | Filled field: .val()
    19 | 20 | Empty Textarea: .val()
    21 | Filled Textarea: .val()
    22 |
    23 | Rules Import: 24 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /testcases/docroot-for-testing/form-o-fill-testing/20-teardown-content.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Form-O-Fill Testpage 6 | 7 | 8 | 9 |

    TESTING SERVER RUNNING

    10 | 11 |
    12 |     This demonstrates:
    13 |     - usage of setupContent
    14 |     - usage of context.storage in setupContent/teardownContent/value function
    15 |     - the fact that all three functions have the same context available
    16 | 
    17 |     #target: setBySetupContent
    18 |     #target2: setByValueFunction
    19 |     #target3: set by teardownContent
    20 |     
    21 |     
    22 | 23 | #target:
    24 | #target2:
    25 | #target3:
    26 | 27 | Rules Import: 46 | 47 | 48 |
    49 | 50 | 51 | -------------------------------------------------------------------------------- /testcases/docroot-for-testing/form-o-fill-testing/16-workflow-1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Form-O-Fill Testpage 6 | 7 | 8 | 9 |

    TESTING SERVER RUNNING 1

    10 | 11 |
    12 |     This test workflows.
    13 | 
    14 |     Click extension icon : Blue workflow step visible.
    15 |     Click Workflow : Quickly cycle from workflow-1.html to workflow-2.html to workflow-3.html
    16 |     
    17 |
    18 | 1. Input field:
    19 | 2. Input field:
    20 |
    21 | Rules Import: 22 | 66 |
    67 | 68 | 69 | -------------------------------------------------------------------------------- /src/sass/_workflows.scss: -------------------------------------------------------------------------------- 1 | /* Workflows */ 2 | #workflows .tab input { 3 | width: auto; 4 | } 5 | 6 | #workflows .menu { 7 | margin-top: 7px; 8 | } 9 | 10 | #workfloweditor { 11 | min-height: 100%; 12 | width: 800px; 13 | margin-top: 10px; 14 | border: 1px solid #909090; 15 | overflow: hidden; 16 | padding: 5px; 17 | } 18 | 19 | #workfloweditor .wf-name { 20 | width: 100%; 21 | } 22 | 23 | #workfloweditor select.rulelist { 24 | width: 80%; 25 | margin-right: 10px; 26 | } 27 | 28 | #workfloweditor li { 29 | padding: 0 0 5px 0; 30 | cursor: pointer; 31 | } 32 | 33 | #workfloweditor li { 34 | padding: 5px 5px 5px 0; 35 | cursor: move; 36 | background-color: #FAFAF7; 37 | margin-bottom: 5px; 38 | } 39 | 40 | #workfloweditor li:before { 41 | font-family: "formofill"; 42 | font-size: 24px; 43 | vertical-align: middle; 44 | margin-right: 4px; 45 | content: "\e60b"; 46 | } 47 | 48 | #workfloweditor li button { 49 | margin-left: 10px; 50 | } 51 | 52 | #workfloweditor button:before, .wf-all button:before, .wf-button-cancel:before, .icon:before { 53 | font-family: "formofill"; 54 | font-size: 16px; 55 | vertical-align: middle; 56 | margin-right: 4px; 57 | } 58 | 59 | #workfloweditor .wf-delete-step:before { 60 | content: "\e609"; 61 | } 62 | 63 | #workfloweditor .wf-add-step:before { 64 | content: "\e606"; 65 | } 66 | 67 | .wf-add-wf:before { 68 | content: "\e608"; 69 | } 70 | 71 | #workfloweditor .actions { 72 | overflow: hidden; 73 | } 74 | 75 | #workfloweditor .actions button { 76 | float: left; 77 | margin-right: 5px; 78 | } 79 | 80 | button.wf-button-save:before { 81 | content: "\e603"; 82 | } 83 | 84 | button.wf-button-delete:before { 85 | content: "\e609"; 86 | } 87 | 88 | .wf-button-cancel:before { 89 | content: "\e602"; 90 | } 91 | 92 | button.wf-button-import:before { 93 | content: "\e604"; 94 | } 95 | 96 | .wf-all select { 97 | min-width: 30%; 98 | } 99 | 100 | #workflows #notices { 101 | margin-top: 15px; 102 | } 103 | 104 | #workfloweditor li.has-error { 105 | color: Red; 106 | font-weight: bold; 107 | } 108 | 109 | #workfloweditor li.has-error span.has-error { 110 | margin-left: 15px; 111 | } 112 | 113 | .wf-cancel-hint { 114 | font-size: 10px; 115 | } 116 | 117 | .wf-options { 118 | text-align: right; 119 | padding-right: 10px; 120 | } 121 | -------------------------------------------------------------------------------- /testcases/docroot-for-testing/form-o-fill-testing/10-screenshots.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Form-O-Fill Testpage 6 | 7 | 8 | 9 |

    TESTING SERVER RUNNING

    10 | 11 |
    12 |     All rules : create/download a screenshot on disc.
    13 |     1. "Screenshot (field def Libs)" : screenshot-libs-h.png
    14 |     2. "Screenshot (field def automatic)" : fof-screenshot-tab-1-rule-0-field-0_Screenshot__field_def_automatic_.png
    15 |     3. "Screenshot (field def save as)" : screenshot-save-as.png
    16 |     4 ."Screenshot (rule def automatic)" : fof-screenshot-tab-1-rule-2-field-0_Screenshot__field_def_automatic_.png
    17 |     
    18 | Input field:
    19 | Rules Import: 37 |
    38 | 39 | 40 | -------------------------------------------------------------------------------- /src/html/options/_importexport_en.html: -------------------------------------------------------------------------------- 1 |
    2 |

    Import / Export workflows and rules

    3 |
    4 |
    5 |
    6 |
    7 | Imported  workflow(s) and  tabs containing rules. 8 |
    9 |
    10 | The author of the rules you tried to import has only allowed the use as remote import URL.
    11 | Please use the URL on the settings page. 12 |
    13 |
    14 | 15 |
    16 |

    Import / Export all data

    17 | 18 | 19 |
    20 | 21 |

    22 | When comparing changed rules (eg. in you VCS) use this diffable export of the rules (not importable!) 23 |

    24 | 25 |

    26 | If you are looking for a quick way to pass your rules on to another form-o-fill user,
    27 | you can use the exported rules & workflows file as a source for the remote import feature. 28 |

    29 | 30 |
    31 | 32 |
    33 |

    Export all data in encrypted format

    34 |

    35 | You can export the rules and workflows in an encrypted format (AES encrypted). 36 |

    37 |

    38 | To do that enter the encryption password here: 39 |

    40 |
    41 |
    42 | 43 |
    44 | 45 | 46 |
    47 | 48 |
    49 | 50 | 51 |
    52 | 53 |
    54 | 55 | Limit export to be used only as remote import URL. The exported rules can not be imported directly. 56 |
    57 | 58 | 59 | -------------------------------------------------------------------------------- /test/global/i18n_spec.js: -------------------------------------------------------------------------------- 1 | var I18n = require("../../src/js/global/i18n.js"); 2 | 3 | describe("I18n", function(){ 4 | 5 | describe(".supportedLanguages", function() { 6 | it("includes english in the supported languages", function(){ 7 | expect(I18n.supportedLanguages()).to.include("en"); 8 | }); 9 | }); 10 | 11 | describe(".current_locale", function() { 12 | it("returns the default locale if the user locale isn't covered", sinon.test(function(){ 13 | global.chrome.setConfig("uiLanguage", "xy_zz"); 14 | expect(I18n.currentLocale()).to.eql("en"); 15 | })); 16 | 17 | it("returns the default locale if the user locale isn't covered", sinon.test(function(){ 18 | this.stub(I18n, "supportedLanguages").returns(["en", "de"]); 19 | global.chrome.setConfig("uiLanguage", "de_DE"); 20 | expect(I18n.currentLocale()).to.eql("de"); 21 | })); 22 | }); 23 | 24 | describe(".userLocale", function() { 25 | it("returns the simplyfied version of the browser language", function(){ 26 | global.chrome.setConfig("uiLanguage", "en_GB"); 27 | expect(I18n.userLocale()).to.eq("en"); 28 | }); 29 | 30 | it("downcases the language", function(){ 31 | global.chrome.setConfig("uiLanguage", "EN_GB"); 32 | expect(I18n.userLocale()).to.eq("en"); 33 | }); 34 | }); 35 | 36 | describe(".load_pages", function() { 37 | 38 | beforeEach(function() { 39 | sinon.stub(jQuery, "ajax").yieldsTo("success", "
    "); 40 | }); 41 | 42 | afterEach(function() { 43 | jQuery.ajax.restore(); 44 | }); 45 | 46 | it("loads localized pages and triggers a custom event", sinon.test(function(){ 47 | var triggerSpy = this.spy(jQuery.prototype, "trigger"); 48 | 49 | I18n.loadPages(["about"]); 50 | 51 | expect(triggerSpy).to.have.been.calledWith("i18n-loaded", ["html/options/_about_en.html"]); 52 | })); 53 | 54 | it("inserts the loaded page into the DOM", sinon.test(function(){ 55 | var spy = this.spy(I18n, "_getAndInsert"); 56 | 57 | I18n.loadPages(["about"]); 58 | 59 | expect(spy).to.have.been.calledWith(["html", "options", "_about_en.html"], "#about"); 60 | })); 61 | 62 | it("prefixes pages to be loaded", sinon.test(function(){ 63 | var triggerSpy = this.spy(jQuery.prototype, "trigger"); 64 | 65 | I18n.loadPages(["about"], "prefix"); 66 | 67 | expect(triggerSpy).to.have.been.calledWith("i18n-loaded", ["html/options/prefix/_about_en.html"]); 68 | })); 69 | }); 70 | }); 71 | 72 | -------------------------------------------------------------------------------- /src/js/options/chrome_bootstrap.js: -------------------------------------------------------------------------------- 1 | /*global jQuery */ 2 | /*eslint complexity: 0*/ 3 | var ChromeBootstrap = { 4 | init: function() { 5 | // Menu functionality for chrome-bootstrap 6 | jQuery('.menu').on("click", "a", function(ev) { 7 | if (this.classList.contains("no-click")) { 8 | ev.preventDefault(); 9 | return false; 10 | } 11 | jQuery('.mainview > *').removeClass("selected"); 12 | jQuery('.menu li').removeClass("selected"); 13 | jQuery('.mainview > *:not(.selected)').css('display', 'none'); 14 | 15 | var $parent = jQuery(ev.currentTarget).parent(); 16 | $parent.addClass("selected"); 17 | 18 | var anchor = jQuery(ev.currentTarget).attr('href'); 19 | var currentView = jQuery(anchor); 20 | currentView.css('display', 'block'); 21 | currentView.addClass("selected"); 22 | 23 | $parent.parent()[0].className = "menu " + anchor.substr(1); 24 | 25 | jQuery('body')[0].scrollTop = 0; 26 | return true; 27 | }); 28 | 29 | // Activate navigationitem via hashtag 30 | jQuery(window).on("load", ChromeBootstrap.relocate); 31 | 32 | jQuery(document).on("click", "a", function() { 33 | if (this.href.indexOf("#help-") === -1) { 34 | return; 35 | } 36 | ChromeBootstrap.relocate(this.href.replace(/^.*#/, "#")); 37 | }); 38 | 39 | jQuery(document).on("click", "a.activate-menu", ChromeBootstrap.relocate); 40 | 41 | jQuery(".menu a").on("click", function() { 42 | if (this.classList.contains("no-click")) { 43 | return false; 44 | } 45 | 46 | jQuery(".notice").hide(); 47 | return true; 48 | }); 49 | }, 50 | relocate: function(target) { 51 | var hash = window.location.hash; 52 | 53 | // passed as a parameter 54 | if (typeof target === "string") { 55 | hash = target; 56 | } 57 | 58 | // User hash clicked on a link wit hcalss=activate-menu and only a #hash link 59 | if (typeof this.href !== "undefined") { 60 | var link = this.href.match(/#(.*)$/); 61 | if (link !== null) { 62 | hash = link[0]; 63 | } 64 | } 65 | 66 | if (hash) { 67 | var main = hash.replace(/-.*$/, ""); 68 | var sub = hash.replace(/^.*?-/, ""); 69 | var $nav = jQuery(".navigation a[href='" + main + "']"); 70 | if ($nav.length === 1) { 71 | $nav.trigger("click"); 72 | } 73 | 74 | // Activate sub-item 75 | if (sub !== "") { 76 | window.location.hash = hash; 77 | } 78 | } 79 | } 80 | }; 81 | -------------------------------------------------------------------------------- /src/vendor/html5sortable/html.sortable.min.js: -------------------------------------------------------------------------------- 1 | "use strict";!function(a){var b,c,d=a();a.fn.sortable=function(e){var f=String(e);return e=a.extend({connectWith:!1,placeholder:null,dragImage:null},e),this.each(function(){var g,h=a(this).children(e.items),i=e.handle?h.find(e.handle):h;if("reload"===f&&a(this).children(e.items).off("dragstart.h5s dragend.h5s selectstart.h5s dragover.h5s dragenter.h5s drop.h5s"),/^enable|disable|destroy$/.test(f)){var j=a(this).children(a(this).data("items")).attr("draggable","enable"===f);return void("destroy"===f&&(a(this).off("sortupdate"),a(this).removeData("opts"),j.add(this).removeData("connectWith items").off("dragstart.h5s dragend.h5s dragover.h5s dragenter.h5s drop.h5s").off("sortupdate"),i.off("selectstart.h5s")))}var k=a(this).data("opts");"undefined"==typeof k?a(this).data("opts",e):e=k;var l,m,n=null===e.placeholder?a("<"+(/^ul|ol$/i.test(this.tagName)?"li":"div")+' class="sortable-placeholder"/>'):a(e.placeholder).addClass("sortable-placeholder");a(this).data("items",e.items),d=d.add(n),e.connectWith&&a(e.connectWith).add(this).data("connectWith",e.connectWith),h.attr("role","option"),h.attr("aria-grabbed","false"),i.attr("draggable","true").not("a[href], img").on("selectstart.h5s",function(){return this.dragDrop&&this.dragDrop(),!1}).end(),h.on("dragstart.h5s",function(d){var f=d.originalEvent.dataTransfer;f.effectAllowed="move",f.setData("text",""),e.dragImage&&f.setDragImage&&f.setDragImage(e.dragImage,0,0),g=(b=a(this)).addClass("sortable-dragging").attr("aria-grabbed","true").index(),c=b.outerHeight(),l=a(this).parent(),b.parent().triggerHandler("sortstart",{item:b,startparent:l})}).on("dragend.h5s",function(){b&&(b.removeClass("sortable-dragging").attr("aria-grabbed","false").show(),d.detach(),m=a(this).parent(),(g!==b.index()||l.get(0)!==m.get(0))&&b.parent().triggerHandler("sortupdate",{item:b,oldindex:g,startparent:l,endparent:m}),b=null,c=null)}).add([this,n]).on("dragover.h5s dragenter.h5s drop.h5s",function(f){if(!h.is(b)&&e.connectWith!==a(b).parent().data("connectWith"))return!0;if("drop"===f.type)return f.stopPropagation(),d.filter(":visible").after(b),b.trigger("dragend.h5s"),!1;if(f.preventDefault(),f.originalEvent.dataTransfer.dropEffect="move",h.is(this)){var g=a(this).outerHeight();if(e.forcePlaceholderSize&&n.height(c),g>c){var i=g-c,j=a(this).offset().top;if(n.index()a(this).index()&&f.originalEvent.pageY>j+g-i)return!1}b.hide(),a(this)[n.index() 2 | 3 | 4 | 5 | Form-O-Fill Testpage 6 | 7 | 8 | 9 |
    10 |       Inpage workflow 1:
    11 |       Both fields should be filled then a page change should occur followed by the fields 3+4 being filled.
    12 |     
    13 | 14 | Target 1: ("inpage WF 1")
    15 | Target 2: ("inpage WF 2")
    16 | 17 |
    Continue
    18 | 19 | Rules Import: 65 | 66 | 67 |
    68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /test/integration/all_types_scene.js: -------------------------------------------------------------------------------- 1 | /*eslint brace-style:0*/ 2 | require("../support/integration_helper"); 3 | 4 | describe("filling different types of fields", function() { 5 | this.timeout(99999); 6 | 7 | it("fills all known types", function (done) { 8 | Tests.visit("01-all-types") 9 | .getValue("input[type=text]", function (err, value) { expect(value).to.eql("input[type=text]"); }) 10 | .isSelected("input[type=radio]", function (err, value) { expect(value).to.be_true; }) 11 | .isSelected("input[name='checkbox-value']", function (err, value) { expect(value).to.eql(true); }) 12 | .isSelected("input[name='checkbox-true']", function (err, value) { expect(value).to.eql(true); }) 13 | .isSelected("input[name='checkbox-false']", function (err, value) { expect(value).to.eql(false); }) 14 | .getValue("input[type=button]", function (err, value) { expect(value).to.eql("input[type=button]"); }) 15 | .getAttribute("input[type=image]", "src", function (err, src) { expect(src).to.eql("http://localhost:9292/form-o-fill-testing/animage.png"); }) 16 | .getValue("input[type=password]", function (err, value) { expect(value).to.eql("input[type=password]"); }) 17 | .getValue("input[type=search]", function (err, value) { expect(value).to.eql("input[type=search]"); }) 18 | .getValue("input[type=email]", function (err, value) { expect(value).to.eql("someone example.com"); }) 19 | .getValue("input[type=url]", function (err, value) { expect(value).to.eql("http://form-o-fill.github.io"); }) 20 | .getValue("input[type=tel]", function (err, value) { expect(value).to.eql("491234567890"); }) 21 | .getValue("input[type=range]", function (err, value) { expect(value).to.eql("100"); }) 22 | .getValue("input[type=date]", function (err, value) { expect(value).to.eql("2014-11-04"); }) 23 | .getValue("input[type=month]", function (err, value) { expect(value).to.eql("2014-06"); }) 24 | .getValue("input[type=week]", function (err, value) { expect(value).to.eql("2014-W42"); }) 25 | .getValue("input[type=datetime]", function (err, value) { expect(value).to.eql("1996-12-19T16:39:57-08:00"); }) 26 | .getValue("input[type=datetime-local]", function (err, value) { expect(value).to.eql("1996-12-19T16:39:57.123"); }) 27 | .getValue("input[type=color]", function (err, value) { expect(value).to.eql("#ff0000"); }) 28 | .getValue("select.single", function (err, value) { expect(value).to.eql("option1"); }) 29 | .getValue("select.multiple option:checked", function (err, values) { 30 | expect(values).to.eql(['multiple1', 'multiple2']); 31 | }) 32 | .getValue(".textarea", function (err, value) { expect(value).to.eql("textarea"); }) 33 | .call(done); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /testcases/docroot-for-testing/form-o-fill-testing/23-iframe-1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Form-O-Fill Testpage 6 | 7 | 8 | 9 |

    TESTING SERVER RUNNING

    10 |
    11 |       left iframe : value should be "value of outer frame"
    12 |       right iframe: value shouldn't change but should produce an error
    13 |     
    14 | Outer field:
    15 |
    16 | 17 | 18 |
    19 | Rules Import: 37 | 38 |
    39 | 40 | 41 | -------------------------------------------------------------------------------- /assets/ext-store/description.txt: -------------------------------------------------------------------------------- 1 | FORM-O-FILL is an advanced highly programmable form-filler. 2 | 3 | It provides form content extraction, one-click form filling, advanced multi-page workflows and more. 4 | 5 | Features: 6 | ✓ Extract already entered forms to auto-create the rule definition with the click of a button (or two). 7 | ✓ Use Javascript to program the rules. 8 | ✓ Use static values or dynamically generated values for every field (value functions are just javascript). 9 | ✓ Automatically take screenshots 10 | ✓ Get nice error reporting when applying the rule to the form doesn't work. 11 | ✓ Use before functions to fetch, generate and store data that can then be used in value functions. 12 | ✓ Organize your work into tabs. All tabs are searched for matching rules. 13 | ✓ Use workflows to create multi page rules. 14 | ✓ One click apply of a matching rule or even auto apply the rule. 15 | ✓ Two click apply when there are multiple matching rules or workflows. 16 | ✓ "Automatic execution" configurable for every rule. 17 | ✓ Reuse rules inside other rules (shared rules). 18 | ✓ Share rules via the remote URL import feature. 19 | ✓ Browser notifications to show you what happened. 20 | 21 | Requires: 22 | Chrome 49+ 23 | 24 | ATTENTION! 25 | VERSION 4.0 doesn't require permissions to every visited page anymore. 26 | This was neccessary to comply with google's store rules ¯\_(ツ)_/¯ 27 | If you are running into problems because of that please contact the author! 28 | 29 | Permissions requested: 30 | - Notifications (Show problems and extract notification) 31 | - Communicate with websites (Used for the live tutorials) 32 | 33 | ★ Version 4.0 34 | - Remove unwanted permission to keep FoF in the store. 35 | - Add a privacy policy to comply with chrome store regulations ("Strobe"). No data will be transfered anyway 36 | - Matching rules with "content" AND "url" is now possible 37 | - Remove all traces of the planned (but not implemented) workflow step delay 38 | 39 | ★ Version 3.9.2 40 | - Inline settings page to try to fix settings loading not working 41 | 42 | ★ Version 3.9.1 43 | - Extract input fields that have no type attribute 44 | 45 | ★ Version 3.9.0 46 | - Update jQuery to 3.3.1 47 | - Update chance.js to 1.0.16 48 | - Update math.js to 5.2.0 49 | - Update moment.js to 2.22.2 50 | 51 | ★ Version 1.1.1 - 3.8.3 52 | - See "Changelog" in extension options 53 | 54 | Find the source at 55 | https://github.com/form-o-fill/form-o-fill-chrome-extension 56 | 57 | Pull requests always welcome! 58 | Contact us at formofillextension@gmail.com 59 | 60 | Form-O-Fill icon taken from the Entypo Pictogram suite 61 | (http://www.entypo.com/) 62 | 63 | Licensed under the MIT license. 64 | The software shall be used for Good, not Evil. 65 | -------------------------------------------------------------------------------- /src/js/global/rule.js: -------------------------------------------------------------------------------- 1 | /*global Logger, jQuery */ 2 | 3 | /* A single Rule */ 4 | var Rule = function() { 5 | this.prettyPrint = function() { 6 | var clone = jQuery.extend({}, this); 7 | delete clone.matcher; 8 | delete clone.nameClean; 9 | delete clone.urlClean; 10 | delete clone.id; 11 | delete clone.tabId; 12 | delete clone.type; 13 | delete clone.autorun; 14 | delete clone.screenshot; 15 | delete clone.onlyEmpty; 16 | delete clone.color; 17 | delete clone.shadow; 18 | delete clone._escapeForRegexp; 19 | delete clone.matchBoth; 20 | return JSON.stringify(clone, null, 2); 21 | }; 22 | 23 | this._escapeForRegexp = function(str) { 24 | return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); 25 | }; 26 | }; 27 | 28 | /*eslint-disable complexity*/ 29 | Rule.create = function(options, tabId, ruleIndex) { 30 | delete options.matcher; 31 | delete options.nameClean; 32 | delete options.urlClean; 33 | delete options._escapeForRegexp; 34 | delete options.prettyPrint; 35 | var rule = new Rule(); 36 | 37 | Object.keys(options).forEach(function(key) { 38 | rule[key] = options[key]; 39 | }); 40 | 41 | // RegExp in URL or string? 42 | if (typeof rule.url !== "undefined" && typeof rule.url.test !== "undefined") { 43 | // RegExp 44 | rule.matcher = new RegExp(rule.url); 45 | } else if (typeof rule.url !== "undefined") { 46 | // String (match full url only) 47 | rule.matcher = new RegExp("^" + rule._escapeForRegexp(rule.url) + "$"); 48 | } 49 | 50 | if (typeof rule.url !== "undefined") { 51 | rule.urlClean = rule.url.toString(); 52 | } else { 53 | rule.urlClean = "n/a"; 54 | } 55 | 56 | if (typeof rule.name !== "undefined") { 57 | rule.nameClean = rule.name.replace("<", "<"); 58 | } 59 | 60 | if (typeof rule.id === "undefined") { 61 | rule.id = tabId + "-" + ruleIndex; 62 | } 63 | 64 | if (typeof rule.autorun === "undefined") { 65 | rule.autorun = false; 66 | } 67 | 68 | if (typeof rule.onlyEmpty === "undefined") { 69 | rule.onlyEmpty = false; 70 | } 71 | 72 | if (typeof rule.shadow === "undefined") { 73 | rule.shadow = false; 74 | } 75 | 76 | if (typeof rule.matchBoth === "undefined") { 77 | rule.matchBoth = false; 78 | } 79 | 80 | rule.tabId = tabId; 81 | 82 | // REMOVE START 83 | if (rule.export && rule.lib) { 84 | Logger.debug("[rule.js] created rule (with lib named '" + rule.export + "' )", rule); 85 | } else { 86 | Logger.debug("[rule.js] created rule", rule); 87 | } 88 | // REMOVE END 89 | 90 | return rule; 91 | }; 92 | /*eslint-enable complexity*/ 93 | 94 | // REMOVE START 95 | if (typeof exports === "object") { 96 | module.exports = Rule; 97 | } 98 | // REMOVE END 99 | -------------------------------------------------------------------------------- /test/global/rule_spec.js: -------------------------------------------------------------------------------- 1 | var Rule = require("../../src/js/global/rule.js"); 2 | 3 | describe("Rule", function() { 4 | describe("#prettyPrint", function() { 5 | it("returns a clean formatted representtion of a rule", sinon.test(function(){ 6 | var rule = new Rule(); 7 | rule.stays = 1; 8 | rule.matcher = "goes away"; 9 | rule.nameClean = "goes away"; 10 | rule.urlClean = "goes away"; 11 | rule.id = "goes away"; 12 | 13 | expect(rule.prettyPrint()).to.eql("{\n \"stays\": 1\n}"); 14 | })); 15 | }); 16 | 17 | describe(".create", function() { 18 | it("creates Rule instances", sinon.test(function(){ 19 | var rule = Rule.create({name: "name"}, 1, 1); 20 | expect(rule).to.be.instanceof(Rule); 21 | })); 22 | 23 | it("assigns properties", sinon.test(function(){ 24 | var rule = Rule.create({name: "name", something: true}, 1, 1); 25 | expect(rule.something).to.be.true; 26 | })); 27 | 28 | it("sets the 'matcher' attr to a RegExp when 'url' is a RegExp", sinon.test(function(){ 29 | var rule = Rule.create({name: "name", url: /regexp/}, 1, 1); 30 | expect(rule.matcher).to.eql(/regexp/); 31 | })); 32 | 33 | it("sets the 'matcher' attr to a full URL matching RegExp if 'url' is string", sinon.test(function(){ 34 | var rule = Rule.create({name: "name", url: "http://a?b=1&c=2"}, 1, 1); 35 | expect(rule.matcher).to.eql(/^http:\/\/a\?b=1&c=2$/); 36 | })); 37 | 38 | it("sets the 'urlClean' attr to a string representation of the 'url' attr", sinon.test(function(){ 39 | var rule = Rule.create({name: "name", url: "http://a?b=1&c=2"}, 1, 1); 40 | expect(rule.urlClean).to.eql("http://a?b=1&c=2"); 41 | })); 42 | 43 | it("sets the 'urlClean' attr to 'n/a' if the 'url' attr is undefined", sinon.test(function(){ 44 | var rule = Rule.create({name: "name"}); 45 | expect(rule.urlClean).to.eql("n/a"); 46 | })); 47 | 48 | it("sets the 'nameClean' attr to a slightly escaped version of 'name'", sinon.test(function(){ 49 | var rule = Rule.create({name: "with tag"}); 50 | expect(rule.nameClean).to.eql("with <a> tag"); 51 | })); 52 | 53 | it("sets the 'id' if not present", sinon.test(function(){ 54 | var rule = Rule.create({name: "with tag"}, 1, 2); 55 | expect(rule.id).to.eql("1-2"); 56 | })); 57 | 58 | it("doesn't set the 'id' if already present", sinon.test(function(){ 59 | var rule = Rule.create({name: "with tag", id: "5-5"}, 1, 2); 60 | expect(rule.id).to.eql("5-5"); 61 | })); 62 | 63 | it("sets the autorun property to false by default", sinon.test(function(){ 64 | var rule = Rule.create({name: ""}); 65 | expect(rule.autorun).to.be.false; 66 | })); 67 | }); 68 | }); 69 | -------------------------------------------------------------------------------- /src/sass/popup.scss: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'formofill'; 3 | src: url('../fonts/formofill.woff?-n1boxl') format('woff'); 4 | font-weight: normal; 5 | font-style: normal; 6 | } 7 | 8 | [class^="icon-"]:before, [class*=" icon-"]:before { 9 | font-family: 'formofill'; 10 | speak: none; 11 | font-style: normal; 12 | font-weight: normal; 13 | font-variant: normal; 14 | text-transform: none; 15 | -webkit-font-smoothing: antialiased; 16 | -moz-osx-font-smoothing: grayscale; 17 | } 18 | 19 | .icon-bucket:before { 20 | content: "\e600"; 21 | } 22 | 23 | .icon-archive:before { 24 | content: "\e601"; 25 | } 26 | 27 | .icon-cascade:before { 28 | content: "\e608"; 29 | } 30 | 31 | html { 32 | width: 330px; 33 | min-height: 200px; 34 | min-width: 330px; 35 | margin: 0; 36 | padding: 0; 37 | } 38 | 39 | body { 40 | font-family: 'Open Sans', Arial,sans-serif; 41 | font-size: 13px; 42 | color: #2c2c2c; 43 | margin: 0; 44 | padding: 10px; 45 | min-height: 200px; 46 | min-width: 330px; 47 | } 48 | 49 | h3 { 50 | font-size: 14px; 51 | margin: 0 0 10px 0; 52 | padding: 0 0 10px 0; 53 | border-bottom: 1px solid #2d2c2c; 54 | } 55 | 56 | ul { 57 | margin: 0 0 15px 0; 58 | padding: 0; 59 | } 60 | 61 | li { 62 | list-style: none; 63 | padding: 5px 0 5px 5px; 64 | cursor: pointer; 65 | margin-left: 27px; 66 | text-indent: -27px; 67 | line-height: 18px; 68 | } 69 | 70 | li:hover { 71 | background-color: #ccc; 72 | } 73 | 74 | li:before { 75 | margin-right: 10px; 76 | vertical-align: middle; 77 | } 78 | 79 | li.select-workflow { 80 | color: #192E98; 81 | } 82 | 83 | .select-rule.from-shadow:after, .select-workflow.from-shadow:after { 84 | font-family: 'formofill'; 85 | content: " (\e900)"; 86 | } 87 | 88 | a, a:visited, a:hover, a:active { 89 | outline: none; 90 | color: #2c2c2c; 91 | border-bottom: 1px solid #2c2c2c; 92 | text-decoration: none; 93 | } 94 | 95 | .hidden { 96 | display: none; 97 | } 98 | 99 | a.cmd-show-extract-overlay { 100 | margin-left: 10px; 101 | } 102 | 103 | h3 .cmd-show-extract-overlay { 104 | font-size: inherit; 105 | float: none; 106 | margin-left: 0; 107 | } 108 | 109 | .actions { 110 | overflow: hidden; 111 | position: absolute; 112 | bottom: 0; 113 | right: 0; 114 | width: 100%; 115 | } 116 | 117 | .actions a { 118 | color: #333; 119 | font-size: 9px; 120 | float: right; 121 | margin: 0 10px 10px 0; 122 | 123 | &.on { 124 | color: #11C711; 125 | } 126 | 127 | &.off { 128 | color: Red; 129 | } 130 | } 131 | 132 | .actions .cmd-cancel-workflows { 133 | overflow: hidden; 134 | position: absolute; 135 | bottom: 0; 136 | left: 0; 137 | margin-left: 10px; 138 | float: left; 139 | } 140 | -------------------------------------------------------------------------------- /testcases/docroot-for-testing/form-o-fill-testing/fof-complete-format-error.js: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "tabSettings": [ 4 | { 5 | "id": "1", 6 | "name": "Library usage" 7 | }, 8 | { 9 | "id": "3", 10 | "name": "Before Func" 11 | }, 12 | { 13 | "id": "5", 14 | "name": "Basics" 15 | } 16 | ], 17 | "rules": [ 18 | { 19 | "code": "var rules = [{\n url: /.*test.*/,\n name: \"Using the ENV in a before function\",\n before: function(resolve, env) {\n resolve(\"Hello ENV: \" + JSON.stringify(env));\n },\n fields: [{\n selector: \"input\",\n value: function(e, $data) {\n return $data;\n }\n }]\n}, {\n url: /.*test.*/,\n name: \"Error thrown in before function\",\n before: function(resolve, env) {\n throw new Error(\"error !\");\n resolve(\"never reached!\");\n },\n fields: [{\n selector: \"input\",\n value: \"throw error\"\n }]\n}, {\n url: /.*test.*/,\n name: \"undefined in before function\",\n before: function(resolve, env) {\n var a = env.notExisting.goAway;\n resolve(env.notExisting);\n },\n fields: [{\n selector: \"input\",\n value: \"undefined in before function\"\n }]\n}\n];", 20 | "tabId": 1 21 | }, 22 | { 23 | "code": "var rules = [{\n url: /.*test.*/,\n name: \"Using the ENV in a before function\",\n before: function(resolve, env) {\n resolve(\"Hello ENV: \" + JSON.stringify(env));\n },\n fields: [{\n selector: \"input\",\n value: function(e, $data) {\n return $data;\n }\n }]\n}, {\n url: /.*test.*/,\n name: \"Error thrown in before function\",\n before: function(resolve, env) {\n throw new Error(\"error !\");\n resolve(\"never reached!\");\n },\n fields: [{\n selector: \"input\",\n value: \"throw error\"\n }]\n}, {\n url: /.*test.*/,\n name: \"undefined in before function\",\n before: function(resolve, env) {\n var a = env.notExisting.goAway;\n resolve(env.notExisting);\n },\n fields: [{\n selector: \"input\",\n value: \"undefined in before function\"\n }]\n}\n];", 24 | "tabId": 3 25 | }, 26 | { 27 | "code": "var rules = [{\n content: /TESTING SERVER RUNNING/,\n name: \"Matching by content\",\n fields: [{\n selector: \"input\",\n value: \"found by content\"\n }]\n}, {\n url: /.*test.*/,\n name: \"Matching by URL\",\n fields: [{\n selector: \"input\",\n value: \"found by URL\"\n }]\n}, {\n url: /.*test.*/,\n name: \"Requesting external JSON\",\n before: function(resolve) {\n jQuery.getJSON(\"http://localhost:9292/form-o-fill-testing/json.json\").done(resolve);\n },\n fields: [{\n selector: \"input\",\n value: function($e, data) {\n return data.data;\n }\n }]\n}\n];", 28 | "tabId": 5 29 | } 30 | ] 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /testcases/docroot-for-testing/form-o-fill-testing/fof-complete.js: -------------------------------------------------------------------------------- 1 | { 2 | "workflows": [ 3 | { 4 | "flags": {}, 5 | "id": 1, 6 | "name": "A workflow", 7 | "steps": [] 8 | } 9 | ], 10 | "rules": { 11 | "tabSettings": [ 12 | { 13 | "id": "1", 14 | "name": "Library usage" 15 | }, 16 | { 17 | "id": "3", 18 | "name": "Before Func" 19 | }, 20 | { 21 | "id": "5", 22 | "name": "Basics" 23 | } 24 | ], 25 | "rules": [ 26 | { 27 | "code": "var rules = [{\n url: /.*test.*/,\n name: \"Using the ENV in a before function\",\n before: function(resolve, env) {\n resolve(\"Hello ENV: \" + JSON.stringify(env));\n },\n fields: [{\n selector: \"input\",\n value: function(e, $data) {\n return $data;\n }\n }]\n}, {\n url: /.*test.*/,\n name: \"Error thrown in before function\",\n before: function(resolve, env) {\n throw new Error(\"error !\");\n resolve(\"never reached!\");\n },\n fields: [{\n selector: \"input\",\n value: \"throw error\"\n }]\n}, {\n url: /.*test.*/,\n name: \"undefined in before function\",\n before: function(resolve, env) {\n var a = env.notExisting.goAway;\n resolve(env.notExisting);\n },\n fields: [{\n selector: \"input\",\n value: \"undefined in before function\"\n }]\n}\n];", 28 | "tabId": 1 29 | }, 30 | { 31 | "code": "var rules = [{\n url: /.*test.*/,\n name: \"Using the ENV in a before function\",\n before: function(resolve, env) {\n resolve(\"Hello ENV: \" + JSON.stringify(env));\n },\n fields: [{\n selector: \"input\",\n value: function(e, $data) {\n return $data;\n }\n }]\n}, {\n url: /.*test.*/,\n name: \"Error thrown in before function\",\n before: function(resolve, env) {\n throw new Error(\"error !\");\n resolve(\"never reached!\");\n },\n fields: [{\n selector: \"input\",\n value: \"throw error\"\n }]\n}, {\n url: /.*test.*/,\n name: \"undefined in before function\",\n before: function(resolve, env) {\n var a = env.notExisting.goAway;\n resolve(env.notExisting);\n },\n fields: [{\n selector: \"input\",\n value: \"undefined in before function\"\n }]\n}\n];", 32 | "tabId": 3 33 | }, 34 | { 35 | "code": "var rules = [{\n content: /TESTING SERVER RUNNING/,\n name: \"Matching by content\",\n fields: [{\n selector: \"input\",\n value: \"found by content\"\n }]\n}, {\n url: /.*test.*/,\n name: \"Matching by URL\",\n fields: [{\n selector: \"input\",\n value: \"found by URL\"\n }]\n}, {\n url: /.*test.*/,\n name: \"Requesting external JSON\",\n before: function(resolve) {\n jQuery.getJSON(\"http://localhost:9292/form-o-fill-testing/json.json\").done(resolve);\n },\n fields: [{\n selector: \"input\",\n value: function($e, data) {\n return data.data;\n }\n }]\n}\n];", 36 | "tabId": 5 37 | } 38 | ] 39 | } 40 | } -------------------------------------------------------------------------------- /assets/uml/plantuml-what-happens-when-page-is-visited.txt: -------------------------------------------------------------------------------- 1 | @startuml 2 | title What happens in Form-O-Fill\nwhen a website is visited? 3 | (*) --> "User Visits page" 4 | 5 | "User Visits page" --> "runWorkflowOrRule()" 6 | note right 7 | In background.js triggered by 8 | tab.addListener or tab.onUpdated 9 | end note 10 | 11 | "runWorkflowOrRule()" --> "onTabReadyWorkflow()" 12 | note right 13 | Check if a workflow is active and runs the step 14 | end note 15 | 16 | if "WF active?" then 17 | if " End of WF reached?" 18 | --> [Yes] "Clear current workflow" 19 | --> Set status = finished 20 | --> Return 21 | else 22 | -right-> [No] "Load Rule to be executed" 23 | if "Rule is undefined?" then 24 | note right 25 | Can happen if the user 26 | deletes a rule that is 27 | used in a WF 28 | end note 29 | -right-> [Yes] Set status = "rule_not_found" 30 | --> "Set runRules = false" as rr1 31 | --> "Clear current workflow" as clear2 32 | --> Return 33 | else 34 | --> [No] FormUtil.applyRule 35 | --> Save currentStep 36 | --> Set status = running_workflow 37 | --> "Set runRules = false" as rr2 38 | --> Return 39 | endif 40 | endif 41 | else 42 | --> [No] "onTabReadyRules()" 43 | --> Clear popup HTML 44 | --> Read active tab 45 | --> lastMatchingRules = [] 46 | if "Tab active?" then 47 | --> [Yes] "Load all rules" 48 | --> Select those with "content" matcher attr 49 | --> Match every "content" rule 50 | note right 51 | action: "matchContent" -> content.js 52 | end note 53 | --> lastMatchingRules += "content" rules 54 | --> Match "url" rules 55 | --> lastMatchingRules += "url" rules 56 | --> save lastMatchingRules to Storage 57 | note right 58 | This is done so that 59 | popup.js can load them 60 | end note 61 | --> Find workflows matching lastMatchingRules 62 | note right 63 | Match rule against the 64 | first step of the workflows 65 | end note 66 | --> Save WF matches to storage 67 | note right 68 | This is done so that 69 | popup.js can load them 70 | end note 71 | --> refresh match counter in ext. button 72 | note right 73 | lastMatchingRules + matching WFs 74 | end note 75 | if "More than one rules found?" then 76 | --> [Yes] set HTML for popup window 77 | else 78 | if "autorun set on this rule?" then 79 | --> [Yes] "FormUtil.applyRule" as app2 80 | --> Return 81 | else 82 | --> [No] Return 83 | endif 84 | endif 85 | 86 | else 87 | --> [No] Return 88 | endif 89 | endif 90 | 91 | Return --> (*) 92 | 93 | @enduml 94 | -------------------------------------------------------------------------------- /src/sass/_help.scss: -------------------------------------------------------------------------------- 1 | #help { 2 | 3 | p { 4 | line-height: 18px; 5 | } 6 | 7 | .topics a { 8 | display: inline-block; 9 | padding: 10px; 10 | background-color: #eee; 11 | margin-left: 10px; 12 | margin-top: 10px; 13 | text-decoration: none; 14 | color: #111; 15 | } 16 | 17 | .topics a:first-child { 18 | margin-left: 0; 19 | } 20 | 21 | .topics a.current { 22 | background-color: #ccc; 23 | } 24 | 25 | .topics a:hover { 26 | background-color: #ccc; 27 | } 28 | 29 | .topic { 30 | border-bottom: 1px solid #ccc; 31 | padding: 10px 0; 32 | line-height: 15px; 33 | } 34 | 35 | .topic h3 { 36 | font-size: 2em; 37 | } 38 | 39 | .topic > a[name] { 40 | margin-top: 60px; 41 | display: block; 42 | } 43 | 44 | .topic li .li-block { 45 | display: block; 46 | margin-left: 16px; 47 | } 48 | 49 | .topic li { 50 | text-indent: -14px; 51 | margin-left: 14px; 52 | margin-bottom: 5px; 53 | } 54 | 55 | .topic li:before { 56 | font-family: formofill; 57 | content: "\e600"; 58 | margin-right: 6px; 59 | } 60 | 61 | .topic img { 62 | vertical-align: middle; 63 | padding: 5px; 64 | border: 1px solid #eee; 65 | border-radius: 3px; 66 | } 67 | 68 | .warning:before { 69 | font-family: "formofill"; 70 | vertical-align: middle; 71 | content: "\e602"; 72 | color: rgb(197, 25, 25); 73 | } 74 | 75 | code { 76 | background-color: #eee; 77 | border: 1px solid #ddd; 78 | border-radius: 3px; 79 | padding: 2px; 80 | } 81 | 82 | .block-of-code { 83 | overflow: hidden; 84 | line-height: 18px; 85 | margin-bottom: 1px; 86 | } 87 | 88 | .explaining { 89 | padding: 2px; 90 | float: left; 91 | width: 33%; 92 | background-color: #eee; 93 | font-size: 12px; 94 | margin-right: 10px; 95 | min-height: 24px; 96 | } 97 | 98 | .code { 99 | padding: 5px; 100 | float: right; 101 | font-family: monospace; 102 | width: 64%; 103 | } 104 | 105 | #help-importexport button { 106 | width: 200px; 107 | text-align: left; 108 | } 109 | #help-importexport .block { 110 | float: left; 111 | margin-left: 10px; 112 | } 113 | 114 | #help-importexport .block h3, #help-importexport .block .icon { 115 | color: #888; 116 | } 117 | 118 | #help-importexport .row { 119 | overflow: hidden; 120 | margin-bottom: 40px; 121 | } 122 | 123 | .topic > a[name].anchor { 124 | margin: 0; 125 | height: 0; 126 | } 127 | 128 | button.save:before { 129 | font-family: "formofill"; 130 | font-size: 16px; 131 | vertical-align: middle; 132 | margin-right: 4px; 133 | } 134 | 135 | .anchor { 136 | position: relative; 137 | } 138 | 139 | .floating-hint { 140 | width: 38%; 141 | position: absolute; 142 | top: 0; 143 | right: 0; 144 | border: 1px solid #ddd; 145 | 146 | th { 147 | background-color: #eee; 148 | } 149 | 150 | td { 151 | padding: 5px 5px; 152 | } 153 | } 154 | } 155 | 156 | -------------------------------------------------------------------------------- /src/js/global/jsonf.js: -------------------------------------------------------------------------------- 1 | /*eslint no-new-func:0, complexity: 0*/ 2 | var JSONF = { 3 | // Codified value for "undefined" 4 | _undef: "**JSONF-UNDEFINED**", 5 | _es6ArrowFuncDetect: /\s+=>\s+/, 6 | // A regex for normal "function" 7 | _detectFuncRegex: /^function\s*(\w*)\s*\(([\s\S]*?)\)[\s\S]*?\{([\s\S]*)\}/m, 8 | // Regexes for es2015 arrow functions 9 | _detectArrowFuncRegex: /^\(?(.*?)\)?\s*=>\s*\{([\s\S]*?)\}(?!`)/, 10 | _detectArrowFuncImplRetRegex: /^\((.*?)\)\s*=>\s*([^;\n]*)/, 11 | // A regex for regexes 12 | _detectRegexpRegex: /^\/(.*?)\/$/m, 13 | // Better (formatted) stringify 14 | stringify: function(object) { 15 | return JSON.stringify(object, this._serializer, 2); 16 | }, 17 | parse: function(jsonString) { 18 | return JSON.parse(jsonString, this._deserializer); 19 | }, 20 | _serializer: function(key, value) { 21 | // undefined 22 | if (typeof value === "undefined") { 23 | return JSONF._undef; 24 | } 25 | 26 | // Is a FUNCTION (ES5 or ES6) or REGEXP ? 27 | if (value !== null && (typeof value === "function" || typeof value.test === "function")) { 28 | return value.toString(); 29 | } 30 | return value; 31 | }, 32 | _deserializer: function(key, value) { 33 | 34 | // Undefined? 35 | if (typeof value === "string" && value === JSONF._undef) { 36 | return undefined; 37 | } 38 | 39 | // Return simple value if... 40 | // 1. not a function 41 | // 2. not a regex 42 | // 3. not an es2015 arrow function 43 | if (key === "" && typeof value === "string" && value.indexOf("function") !== 0 && value.indexOf("/") !== 0 && JSONF._es6ArrowFuncDetect.test(value) === false) { 44 | return value; 45 | } 46 | 47 | if (typeof value === "string") { 48 | var match = value.match(JSONF._detectFuncRegex); 49 | 50 | // Function? 51 | if (match) { 52 | var args = match[2].split(',').map(function(arg) { 53 | return arg.replace(/\s+/, ''); 54 | }); 55 | return new Function(args, match[3].trim()); 56 | } 57 | 58 | // ES2015 arrow function with brackets 59 | match = value.match(JSONF._detectArrowFuncRegex); 60 | if (match) { 61 | // 1: function arguments 62 | // 2: function body (without {}) 63 | return new Function(JSONF.cleanArgs(match[1]), match[2].trim()); 64 | } 65 | 66 | // ES2015 arrow function without brackets (implicit return) 67 | match = value.match(JSONF._detectArrowFuncImplRetRegex); 68 | if (match) { 69 | // 1: function arguments 70 | // 2: function body; 71 | return new Function(JSONF.cleanArgs(match[1]), "return " + match[2].trim()); 72 | } 73 | 74 | // RegEx? 75 | match = value.match(JSONF._detectRegexpRegex); 76 | if (match) { 77 | return new RegExp(match[1]); 78 | } 79 | } 80 | return value; 81 | }, 82 | cleanArgs: function(argsAsString) { 83 | return argsAsString.split(',').map(function(arg) { 84 | return arg.replace(/\s+/, ''); 85 | }); 86 | } 87 | }; 88 | // REMOVE START 89 | if (typeof exports === "object") { 90 | module.exports = JSONF; 91 | } 92 | // REMOVE END 93 | -------------------------------------------------------------------------------- /src/html/options/tutorial/_tour5_en.html: -------------------------------------------------------------------------------- 1 |
    2 | 3 | 20 | 21 |
    22 |

    23 | To use the build-in libraries you need to use a value function (see tour 3) for the "value" entry. 24 |

    25 |
    26 | 27 |
    28 |

    29 | Here we read in the current value of the input field. We do that because we want to append to the current value. 30 |

    31 |
    32 | 33 |
    50 | 51 |
    52 | Next let's look at what moment.js has to offer. 53 |
    54 | 55 |
    56 |

    57 | Here we return the current date plus seven days and fill the input type=date field with it. 58 |

    59 |

    60 | As with chance.js the methods that moment.js has to offer are documented on their site. 61 |

    62 |
    63 | 64 |
    65 |

    66 | There is more to libraries in Form-O-Fill. You can even define your own libraries to make common functions available in multiple rules. 67 |

    68 |

    69 | That will be the topic of another tutorial. Until then, have fun! 70 |

    71 |
    72 | 73 |
    74 | -------------------------------------------------------------------------------- /src/html/options/tutorial/_tour6_en.html: -------------------------------------------------------------------------------- 1 |
    2 | 3 | 20 | 21 |
    22 |

    23 | This rule has two field definitions. 24 |

    25 |
    26 | 27 |
    28 |

    29 | The first is for the input[type=text] field. 30 |

    31 |

    32 | Nothing new here. 33 |

    34 |
    35 | 36 |
    37 |

    38 | This field definition is more interesting. 39 |

    40 |

    41 | It specifies the input[type=submit] field as the target of the action. 42 |

    43 |
    44 | 45 |
    46 |

    47 | The value for this field definition is a function that simply clicks on the element it is targeted at thereby submitting the form. 48 |

    49 |
    50 | 51 |
    52 |

    53 | This is the common way to submit forms. 54 |

    55 |

    56 | Because writing that value function is tedious, Form-O-Fill has a helper function that simplifies the usage. 57 |

    58 |
    59 | 60 |
    61 |

    62 | I changed the rule to one using the helper function. 63 |

    64 |
    65 | 66 |
    67 |

    68 | Instead of writing a function() you can simply use Lib.h.click as the value for the "value". 69 |

    70 |

    71 | Note the missing round brackets at the end. 72 |

    73 |
    74 | 75 |
    76 |

    77 | This is the most important feature you will need for workflows to function correctly. 78 |

    79 |

    80 | But that is a topic for later ... 81 |

    82 |

    83 | Until then ... enjoy! 84 |

    85 |
    86 | 87 |
    88 | -------------------------------------------------------------------------------- /src/js/content/extract_instrumentation.js: -------------------------------------------------------------------------------- 1 | /*global jQuery, FormExtractor, Storage, Logger, Utils, JSONF*/ 2 | 3 | // Create HTML overlays for form masking 4 | var getOverlays = function getOverlays() { 5 | var overlays = []; 6 | jQuery("form").each(function formEach(index) { 7 | var $form = jQuery(this); 8 | 9 | // Add an index so we can find the form later 10 | $form.attr("data-form-o-fill-id", index); 11 | 12 | // Dimensions 13 | var offset = $form.offset(); 14 | var height = $form.height(); 15 | var width = $form.width(); 16 | 17 | // HTML 18 | var overlay = "
    Form-O-Fill:
    " + chrome.i18n.getMessage("extract_click_here") + "
    "; 19 | overlays.push(overlay); 20 | }); 21 | return overlays.join(); 22 | }; 23 | 24 | var cleanupOverlays = function cleanupOverlays() { 25 | // cleanup 26 | jQuery("form").each(function formEach() { 27 | jQuery(this).removeAttr("form-o-fill-id"); 28 | }); 29 | jQuery(".form-o-fill-overlay-form").remove(); 30 | jQuery(document).off("click", ".form-o-fill-overlay-form").off("click", "body"); 31 | }; 32 | 33 | var extractRules = function extractRules(targetForm) { 34 | // looks good, start extraction 35 | var ruleCode = FormExtractor.extract(targetForm); 36 | Logger.info("[extract_instr.js] Extracted: " + JSON.stringify(ruleCode)); 37 | 38 | // Save Rule and goto options.html 39 | Storage.save(ruleCode, Utils.keys.extractedRule); 40 | 41 | chrome.runtime.sendMessage({ "action": "extractFinishedNotification"}); 42 | }; 43 | 44 | // Show the extract overlay and bind handlers 45 | var showExtractOverlay = function showExtractOverlay() { 46 | // Add event listener to DOM 47 | jQuery(document).on("click", ".form-o-fill-overlay-form", function clickFofOverlay(e) { 48 | e.preventDefault(); 49 | e.stopPropagation(); 50 | 51 | // This is the form we must extract 52 | var targetForm = document.querySelector("form[data-form-o-fill-id='" + this.dataset.formOFillId + "']"); 53 | 54 | // remove overlays etc 55 | cleanupOverlays(); 56 | 57 | if (targetForm) { 58 | extractRules(targetForm); 59 | } 60 | }).on("click", "body", cleanupOverlays) 61 | .on("keyup", function keyUp(e) { 62 | if (e.which === 27) { 63 | cleanupOverlays(); 64 | } 65 | }); 66 | 67 | // Attach overlays to DOM 68 | jQuery("body").append(getOverlays()); 69 | }; 70 | 71 | // This is a one-off message listener 72 | chrome.runtime.onMessage.addListener(function extractInstOnMessage(message, sender, responseCallback) { 73 | 74 | // Request to start extracting a form to rules 75 | if (message && message.action === "showExtractOverlay") { 76 | showExtractOverlay(); 77 | } 78 | 79 | // Request to match rules against content 80 | // Done here to not send the whole HTML to bg.js 81 | if (message && message.action === "matchContent" && message.rules) { 82 | var content = document.querySelector("body").outerHTML; 83 | var matches = []; 84 | var rules = JSONF.parse(message.rules); 85 | rules.forEach(function forEach(rule) { 86 | // content is a regexp 87 | if (typeof rule.content.test === "function" && rule.content.test(content)) { 88 | matches.push(rule.id); 89 | } 90 | //TODO: allow content match via indexOf ? So content: "some string" works? (FS, 2019-08-04) 91 | }); 92 | Logger.info("[extract_instr.js] Matched content against " + rules.length + " rules with " + matches.length + " content matches"); 93 | responseCallback(JSONF.stringify(matches)); 94 | } 95 | }); 96 | -------------------------------------------------------------------------------- /src/js/global/workflows.js: -------------------------------------------------------------------------------- 1 | /* global Storage, Utils, Logger, JSONF $ */ 2 | /*eslint no-unused-vars:0 */ 3 | var Workflows = { 4 | all: function() { 5 | return new Promise(function (resolve) { 6 | Promise.all([Storage.load(Utils.keys.workflows), Storage.load(Utils.keys.shadowStorage)]).then(function prWfLoad(workflowsAndShadow) { 7 | if (typeof workflowsAndShadow === "undefined") { 8 | resolve([]); 9 | } else { 10 | var workflows = workflowsAndShadow[0]; 11 | 12 | // No workflows in active rule set? 13 | if (typeof workflows === "undefined") { 14 | workflows = []; 15 | } 16 | 17 | // Do we have shadow workflows? 18 | if (typeof workflowsAndShadow[1] !== "undefined" && typeof workflowsAndShadow[1].workflows !== "undefined") { 19 | workflows = workflows.concat(workflowsAndShadow[1].workflows.map(function(workflow) { 20 | workflow.shadow = true; 21 | return workflow; 22 | })); 23 | } 24 | 25 | resolve(workflows); 26 | } 27 | }); 28 | }); 29 | }, 30 | findById: function(id) { 31 | return new Promise(function (resolve) { 32 | Workflows.all().then(function prFindById(wfs) { 33 | resolve(wfs.filter(function (wf) { 34 | /*eslint-disable eqeqeq*/ 35 | return wf.id == id; 36 | /*eslint-enable eqeqeq*/ 37 | })[0]); 38 | }); 39 | }); 40 | }, 41 | save: function(workflowData) { 42 | Logger.info("[g/workflow.js] Saving " + workflowData.length + " workflows"); 43 | return Storage.save(workflowData, Utils.keys.workflows); 44 | }, 45 | delete: function(workflowId) { 46 | this.load().then(function(workflows) { 47 | var newWfs = workflows.filter(function wfFilterDel(wf) { 48 | /*eslint-disable eqeqeq*/ 49 | return wf.id != workflowId; 50 | /*eslint-enable eqeqeq*/ 51 | }); 52 | Workflows.save(newWfs); 53 | }); 54 | }, 55 | matchesForRules: function(rules) { 56 | return new Promise(function (resolve) { 57 | // Find workflows that match those rules (start only) 58 | var matchingRuleNames = rules.map(function cbStepsMap(rule) { 59 | return rule.name; 60 | }); 61 | 62 | Workflows.all().then(function prMatchesForRules(workflows) { 63 | var matchingWorkflows = []; 64 | if (typeof workflows !== "undefined") { 65 | matchingWorkflows = workflows.filter(function cbWfFilter(workflow) { 66 | return matchingRuleNames.indexOf(workflow.steps[0]) > -1; 67 | }); 68 | } 69 | resolve(matchingWorkflows); 70 | }); 71 | }); 72 | }, 73 | saveMatches: function(workflows) { 74 | return Storage.save(JSONF.stringify(workflows), Utils.keys.lastMatchingWorkflows); 75 | }, 76 | loadMatches: function() { 77 | return new Promise(function (resolve) { 78 | Storage.load(Utils.keys.lastMatchingWorkflows).then(function (rawMatches) { 79 | resolve(JSONF.parse(rawMatches)); 80 | }); 81 | }); 82 | }, 83 | exportDataJson: function() { 84 | return new Promise(function (resolve) { 85 | Storage.load(Utils.keys.workflows).then(function(workflowData) { 86 | 87 | // Not workflow saved yet. 88 | if (typeof workflowData === "undefined" || typeof workflowData.map !== "function") { 89 | resolve([]); 90 | } 91 | 92 | workflowData = workflowData.map(function cbWfDataMap(workflow) { 93 | workflow.steps = $.makeArray(workflow.steps); 94 | return workflow; 95 | }); 96 | 97 | resolve(workflowData); 98 | }); 99 | }); 100 | } 101 | }; 102 | 103 | -------------------------------------------------------------------------------- /test/integration/form_filling_scene.js: -------------------------------------------------------------------------------- 1 | /*eslint brace-style:0 */ 2 | require("../support/integration_helper"); 3 | 4 | describe("the form filling", function() { 5 | this.timeout(9999); 6 | 7 | var importAndExecute = function(ruleToImport, expectedText, done) { 8 | var ruleName = ruleToImport.replace(/ /g, "-").toLowerCase(); 9 | 10 | Tests.visit("13-simple") 11 | .click("#form-o-fill-testing-import-submit") 12 | .pause(500) 13 | .click("li.select-rule[data-rule-name='" + ruleName + "']") 14 | .pause(500) 15 | .getValue("#target", function (err, text) { 16 | expect(text).to.eql(expectedText); 17 | }) 18 | .call(done); 19 | }; 20 | 21 | describe("simple rule matching", function() { 22 | 23 | it("works for a rule that is matched by content", function (done) { 24 | importAndExecute("Matching by content", "found by content", done); 25 | }); 26 | 27 | it("works when requesting JSON via jQuery", function (done) { 28 | importAndExecute("Requesting external JSON", "value by json.json via jQuery.getJSON", done); 29 | }); 30 | 31 | it("works for a rule that is matched by url", function (done) { 32 | importAndExecute("Matching by URL", "found by URL", done); 33 | }); 34 | 35 | it("works for a rule that uses a library function", function (done) { 36 | importAndExecute("Library function", "Hello from a library function!", done); 37 | }); 38 | 39 | it("works for a rule that uses the ENV in a before function", function (done) { 40 | importAndExecute("Using the ENV in a before function", 'Hello ENV: {"url":{"url":"http://localhost:9292/form-o-fill-testing/13-simple.html","protocol":"http:","host":"localhost","port":"9292","path":"/form-o-fill-testing/13-simple.html","query":"","hash":""},"storage":{"base":{}}}', done); 41 | }); 42 | 43 | }); 44 | 45 | describe("filling all matched fields", function() { 46 | it("fills all matched fields with the same value", function (done) { 47 | Tests.visit("04-filling-all-matched-fields") 48 | .pause(500) 49 | .getValue("#i1", function (err, attr) { expect(attr).to.eq("filled"); }) 50 | .getValue("#i2", function (err, attr) { expect(attr).to.eq("filled"); }) 51 | .getValue("#i3", function (err, attr) { expect(attr).to.eq("filled"); }) 52 | .call(done); 53 | }); 54 | }); 55 | 56 | describe("when errors occur while executing the rule", function() { 57 | it("reports before function errors as notifications", function (done) { 58 | Tests.visit("13-simple") 59 | .click("li.select-rule[data-rule-name='error-thrown-in-before-function']") 60 | .pause(500) 61 | .getValue("#target", function (err, text) { 62 | expect(text).to.eql("throw error"); 63 | }) 64 | .getText(".notification-html", function (err, text) { 65 | expect(text).to.eql("An error occured while executing a before function. Click here to view it."); 66 | }) 67 | .getText(".notification-status", function (err, text) { 68 | expect(text).to.eql("visible"); 69 | }) 70 | .call(done); 71 | }); 72 | 73 | it("reports undefined as errors in a notifications", function (done) { 74 | Tests.visit("13-simple") 75 | .click("li.select-rule[data-rule-name='undefined-in-before-function']") 76 | .pause(500) 77 | .getValue("#target", function (err, text) { 78 | expect(text).to.eql("undefined in before function"); 79 | }) 80 | .getText(".notification-html", function (err, text) { 81 | expect(text).to.eql("An error occured while executing a before function. Click here to view it."); 82 | }) 83 | .getText(".notification-status", function (err, text) { 84 | expect(text).to.eql("visible"); 85 | }) 86 | .call(done); 87 | }); 88 | }); 89 | }); 90 | -------------------------------------------------------------------------------- /test/integration/form_extraction_scene.js: -------------------------------------------------------------------------------- 1 | /* global editor */ 2 | require("../support/integration_helper"); 3 | 4 | describe("the form extraction", function() { 5 | this.timeout(99999); 6 | 7 | it("shows the extraction overlay", function(done) { 8 | Tests.visit("05-form-extraction") 9 | .refresh() // need to refresh here ... ?! 10 | .pause(global.pause) 11 | .click(".popup-html a.cmd-show-extract-overlay") 12 | .isVisible("div.form-o-fill-overlay-form", function (err, isVisible) { 13 | expect(isVisible).to.be_true; 14 | }).call(done); 15 | }); 16 | 17 | it("shows a notification when the user clicks the overlay", function(done) { 18 | Tests.visit("05-form-extraction") 19 | .click(".popup-html a.cmd-show-extract-overlay") 20 | .click("div.form-o-fill-overlay-form") 21 | .pause(global.pause) 22 | .getText(".notification-html", function (err, text) { 23 | expect(text).to.eql("Extracted your form. Click here to check the options panel for more info."); 24 | }) 25 | .call(done); 26 | }); 27 | 28 | it("inserts extracted rules into the editor", function(done) { 29 | Tests.visit("05-form-extraction") 30 | .click(".popup-html a.cmd-show-extract-overlay") 31 | .pause(global.pause) 32 | .click("div.form-o-fill-overlay-form") 33 | .pause(global.pause) 34 | .click(".extension-options-url a") 35 | .pause(4000) 36 | .click("a.cmd-append-extracted") 37 | .pause(4000) 38 | .execute(function () { 39 | return editor.getValue(); 40 | }, function (err, ret) { 41 | var fail = function(expected, actual) { 42 | expect(JSON.stringify(expected)).to.eql(JSON.stringify(actual)); 43 | }; 44 | var rule = JSON.parse(ret.value); 45 | 46 | fail("A rule for http://localhost:9292/form-o-fill-testing/form-extraction.html#", rule.name); 47 | fail("http://localhost:9292/form-o-fill-testing/form-extraction.html#", rule.url); 48 | fail({ selector: 'input[name=\'text\']', value: 'text' }, rule.fields[0]); 49 | fail({ selector: 'input[name=\'checkbox-value\']', value: true }, rule.fields[1]); 50 | fail({ selector: 'input[name=\'image\']', value: '' }, rule.fields[2]); 51 | fail({ selector: 'input[name=\'password\']', value: 'password' }, rule.fields[3]); 52 | fail({ selector: 'input[name=\'radio\']', value: 'radio' }, rule.fields[4]); 53 | fail({ selector: 'input[name=\'search\']', value: 'search' }, rule.fields[5]); 54 | fail({ selector: 'input[name=\'email\']', value: 'a@example.com' }, rule.fields[6]); 55 | fail({ selector: 'input[name=\'url\']', value: 'http://www.example.com' }, rule.fields[7]); 56 | fail({ selector: 'input[name=\'tel\']', value: '+49123456' }, rule.fields[8]); 57 | fail({ selector: 'input[name=\'number\']', value: '123' }, rule.fields[9]); 58 | fail({ selector: 'input[name=\'range\']', value: '100' }, rule.fields[10]); 59 | fail({ selector: 'input[name=\'date\']', value: '2014-12-31' }, rule.fields[11]); 60 | fail({ selector: 'input[name=\'month\']', value: '2014-06' }, rule.fields[12]); 61 | fail({ selector: 'input[name=\'week\']', value: '2014-W42' }, rule.fields[13]); 62 | fail({ selector: 'input[name=\'time\']', value: '12:01:02.123' }, rule.fields[14]); 63 | fail({ selector: 'input[name=\'datetime\']', value: '1996-12-19T16:39:57-08:00' }, rule.fields[15]); 64 | fail({ selector: 'input[name=\'datetime-local\']', value: '1996-12-19T16:39:57.123' }, rule.fields[16]); 65 | fail({ selector: 'input[name=\'color\']', value: '#ff0000' }, rule.fields[17]); 66 | fail({ selector: 'textarea[name=\'textarea\']', value: 'textarea' }, rule.fields[18]); 67 | fail({ selector: 'select[name=\'select\']', value: 'option2' }, rule.fields[19]); 68 | fail({ selector: 'select[name=\'selectmultiple\']', "value": ["multiple1", "multiple2"]}, rule.fields[20]); 69 | }) 70 | .close() 71 | .call(done); 72 | }); 73 | }); 74 | 75 | --------------------------------------------------------------------------------