├── 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 |
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 | No
16 | Yes
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/src/html/options/help-content/_help-editor_en.html:
--------------------------------------------------------------------------------
1 |
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 | '
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 | Close
19 | Import and replace everything
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 |
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 | Close
22 | Import and replace everything
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 |
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 | 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: 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 "" 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 | Cancel stuck workflow button in the
39 | 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 | Insert target DOM element
25 | Change URL
26 |
27 |
28 |
29 | Rules Import:
48 | Import
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 |
16 |
17 |
18 |
19 | Rules Import:
49 | Import
50 |
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 |
22 | Rules Import:
23 | Import
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 Validate and import 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 |
7 |
8 | If the number of matches is zero, the extension will display "n/a" ( ).
9 |
10 |
11 | If the number of matches is one, the extension will display "1" ( ).
12 | Depending on the setting for "Always show popup" clicking on the button will fill the matching form with the rule's data or show the popup.
13 |
14 |
15 | If there are multiple matches and you click on the Form-O-Fill button you will be presented with a popup where you can select (by clicking) the appropriate match.
16 |
17 |
18 |
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 |
36 |
37 |
--------------------------------------------------------------------------------
/src/html/options/_help_en.html:
--------------------------------------------------------------------------------
1 |
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 |
20 |
21 | Rules Import:
48 | Import
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 | Import
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 | Import
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 |
21 | Rules Import:
22 | Import
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: 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 .
12 |
13 |
14 |
15 |
16 |
Import / Export all data
17 | Import rules & workflows
18 | Export rules & workflows
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 .
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 | Password:
45 |
46 |
47 |
48 |
49 | Repeat:
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 | Export encrypted rules & workflows
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 |
18 |
19 | Rules Import:
65 | Import
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 | Import
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 |
34 |
35 | Here you see the call to change.js
36 |
37 |
38 | All libraries are available under the Libs namespace.
39 |
40 |
41 | This call to chance.name() returns a new random name.
42 |
43 |
44 | By returning the current plus the random value we tell Form-O-Fill to fill the input field with that combination.
45 |
46 |
47 | All available methods callable on Lib.chance are documented on the chance.js site (opens in a new tab) .
48 |
49 |
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 = "";
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 |
--------------------------------------------------------------------------------