├── src ├── react-app-env.d.ts ├── App.css ├── components │ ├── index.js │ ├── checkbox.tsx │ ├── slider.tsx │ └── radio.tsx ├── setupTests.ts ├── index.tsx ├── types.ts ├── index.css ├── config │ ├── index.js │ └── adclient.js └── App.tsx ├── public ├── robots.txt ├── favicon.ico ├── logo192.png ├── logo512.png ├── manifest.json ├── index.html └── jquery.min.js ├── .prettierrc ├── assets └── readme │ └── 2020-12-04_15-18-20.png ├── .gitmodules ├── .gitignore ├── README.md ├── webpack.config.js ├── tsconfig.json ├── craco.config.js ├── .eslintrc ├── package.json └── rl.js ├── data └── index.js ├── common.js ├── index.js └── useractivity.js /src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | .app { 2 | text-align: center; 3 | width: 400px; 4 | height: 400px; 5 | } 6 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shashigharti/browser-extension-user-privacy/master/public/favicon.ico -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shashigharti/browser-extension-user-privacy/master/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shashigharti/browser-extension-user-privacy/master/public/logo512.png -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "trailingComma": "es5", 5 | "printWidth": 90 6 | } 7 | -------------------------------------------------------------------------------- /assets/readme/2020-12-04_15-18-20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shashigharti/browser-extension-user-privacy/master/assets/readme/2020-12-04_15-18-20.png -------------------------------------------------------------------------------- /src/components/index.js: -------------------------------------------------------------------------------- 1 | import Checkbox from './checkbox' 2 | import Radio from './radio' 3 | import Slider from './slider' 4 | 5 | export { Checkbox, Radio, Slider } 6 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "src/tools/webext-instrumentation"] 2 | path = src/tools/webext-instrumentation 3 | url = git@github.com:shashigharti/openwpm-web-ext-instrumentation.git 4 | -------------------------------------------------------------------------------- /src/setupTests.ts: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | 4 | import './index.css' 5 | import { App } from './App' 6 | 7 | ReactDOM.render( 8 | 9 | 10 | , 11 | document.getElementById('root') 12 | ) 13 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | 2 | export enum Sender { 3 | React, 4 | Content 5 | } 6 | export interface ChromeMessage { 7 | from: Sender, 8 | message: any 9 | } 10 | 11 | // Popup requesting background script for status change 12 | export interface UserData { 13 | type: "USER_DATA"; 14 | data: any; 15 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | .idea 6 | .eslintcache 7 | /.pnp 8 | .pnp.js 9 | 10 | # testing 11 | /coverage 12 | 13 | # production 14 | /build 15 | 16 | # misc 17 | .DS_Store 18 | .env.local 19 | .env.development.local 20 | .env.test.local 21 | .env.production.local 22 | 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | width: 500px; 3 | height: 500px; 4 | margin: 0; 5 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 6 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 7 | sans-serif; 8 | -webkit-font-smoothing: antialiased; 9 | -moz-osx-font-smoothing: grayscale; 10 | } 11 | 12 | code { 13 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 14 | monospace; 15 | } 16 | -------------------------------------------------------------------------------- /src/components/checkbox.tsx: -------------------------------------------------------------------------------- 1 | import React, { ChangeEventHandler } from 'react' 2 | 3 | const Checkbox = ({ 4 | name, 5 | value, 6 | checked, 7 | onChange, 8 | }: { 9 | name: string 10 | value: string 11 | checked: boolean 12 | onChange: ChangeEventHandler 13 | }) => ( 14 | 21 | ) 22 | 23 | export default Checkbox 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Getting Started 2 | 3 | ## 1. How to build this extension: 4 | 5 | ``` 6 | $ npm run build 7 | ``` 8 | 9 | ## 2. How to add it to your Firefox Extension in Developer mode: 10 | 11 | - Type "about:debugging" in url 12 | - Load Temporary Add-on 13 | 14 | Select `/build/manifest.json` 15 | ![add-to-firefox](https://user-images.githubusercontent.com/5582809/213909517-64ced3ea-76dd-4e2d-82e8-ada579dab4d7.png) 16 | 17 | ## References 18 | 19 | - (Code used for user activity tracking) https://github.com/keen/common-web 20 | -------------------------------------------------------------------------------- /src/config/index.js: -------------------------------------------------------------------------------- 1 | const udata = [ 2 | { 3 | name: 'udata', 4 | key: '1', 5 | label: 'Http Requests', 6 | value: 'http_requests', 7 | }, 8 | { 9 | name: 'udata', 10 | key: '2', 11 | label: 'Cookies', 12 | value: 'cookies', 13 | }, 14 | ] 15 | 16 | const modes = [ 17 | { 18 | name: 'mode', 19 | label: 'No Training', 20 | value: 'notraining', 21 | }, 22 | { 23 | name: 'mode', 24 | label: 'Training', 25 | value: 'training', 26 | }, 27 | ] 28 | 29 | export { udata, modes } 30 | -------------------------------------------------------------------------------- /src/components/slider.tsx: -------------------------------------------------------------------------------- 1 | import React, { ChangeEventHandler } from 'react' 2 | const Slider = ({ 3 | value, 4 | maxvalue, 5 | onChange, 6 | }: { 7 | value: number 8 | maxvalue: number 9 | onChange: ChangeEventHandler 10 | }) => { 11 | return ( 12 | 13 | 21 | 22 | ) 23 | } 24 | export default Slider 25 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | /* global require, module, __dirname */ 2 | 3 | const path = require("path"); 4 | 5 | module.exports = { 6 | entry: { 7 | rl:"./rl.js/index.js", 8 | // background: "./background.js/index.js", 9 | }, 10 | output: { 11 | path: path.resolve(__dirname, "./build/static/js"), 12 | filename: "[name].js", 13 | sourceMapFilename: "[name].js.map", 14 | pathinfo: true, 15 | }, 16 | resolve: { 17 | extensions: [".js"], 18 | }, 19 | mode: "development", 20 | devtool: "inline-source-map", 21 | }; 22 | /* eslint-env node */ 23 | -------------------------------------------------------------------------------- /src/components/radio.tsx: -------------------------------------------------------------------------------- 1 | import React, { ChangeEventHandler } from 'react' 2 | import { modes } from '../config' 3 | const Radio = ({ onChange }: { onChange: ChangeEventHandler }) => { 4 | return ( 5 | 6 | 7 | {modes.map((item) => ( 8 | 9 | 10 | {item.label} 11 | 12 | ))} 13 | 14 | ) 15 | } 16 | export default Radio 17 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react", 22 | "types": [ 23 | "firefox-webext-browser" 24 | ] 25 | }, 26 | "include": [ 27 | "src", 28 | "common.js" 29 | ], 30 | "exclude": [ 31 | "src/tools" 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /craco.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | webpack: { 3 | configure: (webpackConfig, { env, paths }) => { 4 | return { 5 | ...webpackConfig, 6 | entry: { 7 | main: [ 8 | env === 'development' && 9 | require.resolve('react-dev-utils/webpackHotDevClient'), 10 | paths.appIndexJs, 11 | ].filter(Boolean), 12 | // rcontent: './src/chrome/rcontent.ts', 13 | // background: './src/chrome/background.ts' 14 | }, 15 | output: { 16 | ...webpackConfig.output, 17 | filename: 'static/js/[name].js', 18 | }, 19 | optimization: { 20 | ...webpackConfig.optimization, 21 | runtimeChunk: false, 22 | }, 23 | } 24 | }, 25 | }, 26 | eslint: { 27 | enable: false, 28 | }, 29 | } 30 | -------------------------------------------------------------------------------- /src/config/adclient.js: -------------------------------------------------------------------------------- 1 | const COLORS = { 2 | 0: 'red', 3 | 1: 'blue', 4 | 2: 'green', 5 | 3: 'orange', 6 | } 7 | 8 | const ALL_UIOPTIONS = ['books', 'movies', 'travel', 'science'] 9 | const ADS = { 10 | 0: ['https://m.media-amazon.com/images/I/51jBeCDwMQL.jpg'], 11 | 1: [ 12 | 'https://en.wikipedia.org/wiki/The_Tomorrow_War#/media/File:The_Tomorrow_War_(2021_film)_official_theatrical_poster.jpg', 13 | ], 14 | 2: [ 15 | 'https://www.vietvisiontravel.com/wp-content/uploads/2019/05/top-5-reasons-to-travel-off-season-in-vietnam.jpg', 16 | ], 17 | 3: [ 18 | 'https://solarsystem.nasa.gov/system/resources/detail_files/895_SMD_FLEET_May_800.jpg', 19 | ], 20 | } 21 | 22 | const META_DATA = { 23 | 6: { 24 | base_url: 'ui-client-ad', 25 | no_of_clients: 2, 26 | dim: 4, 27 | model_name: 'example_6', 28 | }, 29 | } 30 | 31 | export { META_DATA, COLORS, ALL_UIOPTIONS, ADS } 32 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "parserOptions": { 4 | "sourceType": "module", 5 | "ecmaFeatures": { "jsx": true } 6 | }, 7 | "plugins": [ 8 | "@typescript-eslint/eslint-plugin" 9 | ], 10 | "extends": [ 11 | "standard", 12 | "plugin:react/recommended" 13 | ], 14 | "env": { 15 | "webextensions": true 16 | }, 17 | "rules": { 18 | "indent": "off", 19 | "no-unused-vars": "off", 20 | "multiline-ternary": "off", 21 | "import/no-duplicates": "off", 22 | "no-use-before-define": "off", 23 | "no-useless-constructor": "off", 24 | "space-before-function-paren": "off", 25 | "no-dupe-class-members": "off", 26 | "@typescript-eslint/no-unused-vars": ["warn", { "argsIgnorePattern": "^_" }], 27 | "comma-dangle": ["error", { 28 | "arrays": "always-multiline", 29 | "objects": "always-multiline" 30 | }] 31 | }, 32 | "settings": { 33 | "react": { 34 | "version": "detect" 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | "name": "Privacy Web Extension", 4 | "version": "1.0", 5 | "description": "OpenWPM Client Extension", 6 | "browser_action": { 7 | "default_popup": "index.html", 8 | "default_title": "Open the popup" 9 | }, 10 | "icons": { 11 | "16": "logo192.png", 12 | "48": "logo192.png", 13 | "128": "logo192.png" 14 | }, 15 | "background": { 16 | "scripts": [] 17 | }, 18 | "content_scripts": [ 19 | { 20 | "matches": [ 21 | "" 22 | ], 23 | "js": [ 24 | "jquery.min.js", "static/js/main.js", "static/js/rl.js" 25 | ] 26 | } 27 | ], 28 | "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self';", 29 | "permissions": [ 30 | "", 31 | "webRequest", 32 | "webRequestBlocking", 33 | "webNavigation", 34 | "cookies", 35 | "management", 36 | "storage", 37 | "alarms", 38 | "downloads", 39 | "tabs", 40 | "dns" 41 | ] 42 | } -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "privacy-webextension", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@craco/craco": "^5.8.0", 7 | "@material-ui/core": "^4.11.4", 8 | "@tensorflow/tfjs": "^3.14.0", 9 | "@testing-library/jest-dom": "^5.11.4", 10 | "@testing-library/react": "^11.1.0", 11 | "@testing-library/user-event": "^12.1.10", 12 | "@types/jest": "^26.0.15", 13 | "@types/node": "^12.0.0", 14 | "@types/react": "^16.9.53", 15 | "@types/react-dom": "^16.9.8", 16 | "@types/styled-components": "^5.1.7", 17 | "jstat": "^1.9.5", 18 | "react": "^17.0.1", 19 | "react-cookie": "^4.1.1", 20 | "react-dom": "^17.0.1", 21 | "react-router-dom": "^5.2.0", 22 | "react-scripts": "4.0.1", 23 | "styled-components": "^5.2.1", 24 | "uuid": "^8.3.2", 25 | "web-vitals": "^0.2.4" 26 | }, 27 | "scripts": { 28 | "start": "react-scripts start", 29 | "postbuild": "webpack", 30 | "build": "INLINE_RUNTIME_CHUNK=false craco build", 31 | "test": "react-scripts test", 32 | "eject": "react-scripts eject", 33 | "format": "prettier --write '{src,rl.js}/**/*.{js,tsx}*'", 34 | "lint": "prettier --check '{src,rl.js}/**/*.{js,tsx}*'" 35 | }, 36 | "eslintConfig": { 37 | "extends": [ 38 | "react-app", 39 | "react-app/jest" 40 | ] 41 | }, 42 | "browserslist": { 43 | "production": [ 44 | ">0.2%", 45 | "not dead", 46 | "not op_mini all" 47 | ], 48 | "development": [ 49 | "last 1 chrome version", 50 | "last 1 firefox version", 51 | "last 1 safari version" 52 | ] 53 | }, 54 | "devDependencies": { 55 | "@types/chrome": "^0.0.126", 56 | "@types/firefox-webext-browser": "^82.0.1", 57 | "@types/react-router-dom": "^5.1.6", 58 | "@types/uuid": "^8.3.0", 59 | "eslint": "^7.32.0", 60 | "eslint-config-standard": "^16.0.3", 61 | "eslint-plugin-promise": "^5.1.1", 62 | "eslint-plugin-react": "^7.26.1", 63 | "eslint-plugin-standard": "^4.1.0", 64 | "prettier": "^2.8.3", 65 | "typescript": "^3.9.10", 66 | "webpack-cli": "^4.7.2" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import React, { ChangeEvent, useState, useEffect } from 'react' 2 | import { Button } from '@material-ui/core' 3 | // import { udata } from "./config"; 4 | import { Slider } from './components' 5 | import './App.css' 6 | 7 | export const App = () => { 8 | // const [cookies, setCookie] = useCookies(); 9 | const [uData, setUData] = useState([]) 10 | const [maxvalue] = React.useState(100) 11 | const [value, setValue] = React.useState(0) 12 | 13 | // Initialize Variables 14 | useEffect(() => { 15 | function logCookies(cookies: any) { 16 | for (const cookie of cookies) { 17 | console.log(`[UI-WebExtension]${cookie.name} = ${cookie.value}`) 18 | setValue(cookie.value) 19 | } 20 | } 21 | var gettingAll = browser.cookies.getAll({ 22 | name: 'user_privacy_preference_level', 23 | }) 24 | gettingAll.then(logCookies) 25 | }, []) 26 | 27 | const startDataCollection = () => { 28 | browser.runtime.sendMessage({ message: 'start', selectedTypes: uData }) 29 | } 30 | 31 | const updateCookie = (_name: string, _value: string) => { 32 | var getActive = browser.tabs.query({ active: true, currentWindow: true }) 33 | getActive.then(setCookie) 34 | // let value = parseInt(_value) / 100; 35 | 36 | function setCookie(tabs: any) { 37 | console.log('[UI-WebExtension]Set Cookie', _name, _value) 38 | browser.cookies.set({ 39 | url: tabs[0].url, 40 | name: _name, 41 | value: _value, 42 | }) 43 | } 44 | } 45 | 46 | const handleSliderChange = (e: ChangeEvent) => { 47 | setValue(parseInt(e.target.value)) 48 | updateCookie('user_privacy_preference_level', e.target.value) 49 | } 50 | return ( 51 |
52 |

Data Collection

53 | 56 |
57 | 58 | 59 | {value} 60 |
61 |
62 | ) 63 | } 64 | -------------------------------------------------------------------------------- /rl.js/data/index.js: -------------------------------------------------------------------------------- 1 | const ADS = { 2 | 0: 'https://m.media-amazon.com/images/I/51jBeCDwMQL.jpg', 3 | 1: 'https://images-na.ssl-images-amazon.com/images/I/81Kr+YIWjCL.jpg', 4 | 2: 'https://d3nuqriibqh3vw.cloudfront.net/styles/aotw_detail_ir/s3/images/northernPhysics.jpg', 5 | } 6 | const ALL_UIOPTIONS = ['books', 'news', 'travel'] 7 | 8 | const META_DATA = { 9 | 1: { 10 | base_url: 'book-client', 11 | no_of_clients: 3, 12 | dim: 3, 13 | model_name: 'example_1', 14 | has_nested_route: 'false', 15 | change_policy: false, 16 | change_prob_idxs: { 0: [0], 1: [1] }, // indexes for probability value change 17 | change_probs: { 0: [0.8], 1: [0.9] }, // probability value for different indexes given by change_prob_idxs 18 | }, 19 | 2: { 20 | base_url: 'ui-client', 21 | no_of_clients: 1, 22 | dim: 24, 23 | model_name: 'example_2', 24 | description: 'Single client; No preference change', 25 | has_nested_route: 'true', 26 | change_policy: false, 27 | change_prob_idxs: { 0: [0] }, // indexes for probability value change 28 | change_probs: { 0: [0.7] }, // probability value for different indexes given by change_prob_idxs 29 | }, 30 | 3: { 31 | base_url: 'ui-client', 32 | no_of_clients: 1, 33 | dim: 24, 34 | model_name: 'example_3', 35 | description: '(Drift)Single client; Change preference during training', 36 | has_nested_route: 'true', 37 | change_policy: true, 38 | change_prob_idxs: { 0: [0, 2] }, // indexes for probability value change 39 | change_probs: { 0: [0.7, 0.9] }, // probability value for different indexes given by change_prob_idxs 40 | time_interval_for_policy_change: 200, 41 | }, 42 | 4: { 43 | base_url: 'ui-client', 44 | no_of_clients: 2, 45 | dim: 24, 46 | model_name: 'example_4', 47 | description: 48 | '(Diff)Multiple clients with different preferences; No preference change', 49 | has_nested_route: 'true', 50 | change_policy: false, 51 | change_prob_idxs: { 0: [0], 1: [1] }, // indexes for probability value change 52 | change_probs: { 0: [0.8], 1: [0.8] }, // probability value for different indexes given by change_prob_idxs 53 | }, 54 | 5: { 55 | base_url: 'ui-client', 56 | no_of_clients: 2, 57 | dim: 24, 58 | model_name: 'example_5', 59 | description: 60 | '(Drift and Diff)Multiple clients with different preferences; Change preference of first client while training', 61 | has_nested_route: 'true', 62 | change_policy: true, 63 | change_prob_idxs: { 0: [0, 4], 1: [1, 1] }, // indexes for probability value change 64 | change_probs: { 0: [0.7, 0.8], 1: [0.7, 0.7] }, // probability value for different indexes given by change_prob_idxs 65 | time_interval_for_policy_change: 200, 66 | }, 67 | 6: { 68 | base_url: 'web-client', 69 | no_of_clients: 1, 70 | dim: 24, 71 | description: 'Web Client; No preference change and one client only', 72 | has_nested_route: 'false', 73 | model_name: 'example_6', 74 | change_policy: false, 75 | change_prob_idxs: { 0: [0] }, // indexes for probability value change 76 | change_probs: { 0: [0.8] }, // probability value for different indexes given by change_prob_idxs 77 | }, 78 | 7: { 79 | base_url: 'web-client', 80 | no_of_clients: 2, 81 | dim: 24, 82 | description: 83 | 'Web Client; No preference change and two clients with different probabilities', 84 | has_nested_route: 'false', 85 | model_name: 'example_7', 86 | change_policy: false, 87 | change_prob_idxs: { 0: [0, 4], 1: [1, 1] }, // indexes for probability value change 88 | change_probs: { 0: [0.7, 0.8], 1: [0.7, 0.7] }, // probability value for different indexes given by change_prob_idxs 89 | }, 90 | } 91 | 92 | export { ADS, META_DATA, ALL_UIOPTIONS } 93 | -------------------------------------------------------------------------------- /rl.js/common.js: -------------------------------------------------------------------------------- 1 | import * as tf from '@tensorflow/tfjs' 2 | 3 | const binomial_sample = (accept_rate) => (Math.random() < accept_rate ? 1 : 0) 4 | class Simulator { 5 | constructor(rates) { 6 | this.rates = rates 7 | this.action_space = Array(rates.length) 8 | } 9 | simulate(idx) { 10 | console.log('[Content Script ML - Socket]Rates', this.rates) 11 | console.log('[Content Script ML - Socket]Rate', this.rates[idx]) 12 | let choice = binomial_sample(this.rates[idx]) 13 | return choice 14 | } 15 | } 16 | 17 | /** 18 | * Get maximum value 19 | */ 20 | const argMax = (d) => 21 | Object.entries(d).filter((el) => el[1] === Math.max(...Object.values(d)))[0][0] 22 | 23 | /** 24 | * Update alphas and betas - Thompson Sampling 25 | * @param {Tensor1D} rewards 26 | * @param {Tensor1D} samples 27 | * @param {Tensor1D} alphas 28 | * @param {Tensor1D} betas 29 | * @returns {Array} 30 | */ 31 | const banditThompson = (rewards, samples, alphas, betas) => { 32 | console.log('[Content Script ML - Socket]banditThompson') 33 | const prev_alpha = alphas 34 | const prev_beta = betas 35 | 36 | alphas = prev_alpha.add(rewards) 37 | betas = prev_beta.add(samples.sub(rewards)) 38 | return [alphas, betas] 39 | } 40 | 41 | /** 42 | * Calculate gradients 43 | * @param {Tensor1D} alphas 44 | * @param {Tensor1D} betas 45 | * @param {Tensor1D} n_alphas 46 | * @param {Tensor1D} n_betas 47 | * @returns {Array} 48 | */ 49 | const calcGradient = (alphas, betas, n_alphas, n_betas) => { 50 | let d_alphas, d_betas 51 | d_alphas = n_alphas.sub(alphas) 52 | d_betas = n_betas.sub(betas) 53 | return [d_alphas, d_betas] 54 | } 55 | 56 | /** 57 | * Simulate user action 58 | * @param {Array} preferences 59 | * @param {number} option_id 60 | * @returns {number} 61 | */ 62 | const simulate = (simulated_rates, selectedOption) => { 63 | const env = new Simulator(simulated_rates) 64 | return env.simulate(selectedOption) 65 | } 66 | 67 | const actionAndUpdate = (alphasArray, betasArray, selectedOption, reward) => { 68 | let alphas_betas 69 | let rewardVector = Array(alphasArray.length).fill(0) 70 | let sampledVector = Array(alphasArray.length).fill(0) 71 | console.log('[Content Script ML - Socket]Update Selected Option', selectedOption) 72 | console.log('[Content Script ML - Socket]Reward', reward) 73 | console.log('[Content Script ML - Socket]Alphas, Betas', alphasArray, betasArray) 74 | 75 | console.log( 76 | '[Content Script ML - Socket]alphasArray.length == 0 || betasArray.length == 0 || betasArray.length != alphasArray.length', 77 | alphasArray.length === 0 || 78 | betasArray.length === 0 || 79 | betasArray.length !== alphasArray.length 80 | ) 81 | if ( 82 | alphasArray.length === 0 || 83 | betasArray.length === 0 || 84 | betasArray.length !== alphasArray.length 85 | ) 86 | return false 87 | 88 | rewardVector[selectedOption] = reward // reward 89 | sampledVector[selectedOption] = 1 90 | console.log( 91 | '[Content Script ML - Socket]Reward Vector, Sampled Vector', 92 | rewardVector, 93 | sampledVector 94 | ) 95 | console.log( 96 | '[Content Script ML - Socket]tf.tensor(rewardVector)', 97 | tf.tensor(rewardVector) 98 | ) 99 | 100 | alphas_betas = banditThompson( 101 | tf.tensor(rewardVector), 102 | tf.tensor(sampledVector), 103 | tf.tensor(alphasArray), 104 | tf.tensor(betasArray) 105 | ) 106 | console.log('[Content Script ML - Socket]alphas_betas', alphas_betas) 107 | let gradWeights = calcGradient( 108 | tf.tensor(alphasArray), 109 | tf.tensor(betasArray), 110 | alphas_betas[0], 111 | alphas_betas[1] 112 | ) 113 | 114 | console.log('[Content Script ML - Socket]gradWeights', gradWeights) 115 | return [gradWeights, alphas_betas] 116 | } 117 | 118 | /** 119 | * Generate probabilities of size given by dimension. The probabilities sums up to 1 120 | * @param {number} dim 121 | * @returns {Array} 122 | */ 123 | const generateProbabilities = (dim, preference) => { 124 | let probabilities = [] 125 | console.log('[Content Script ML - Socket]Preference', preference) 126 | 127 | let prob_for_remaining = (1 - preference[0]) / dim 128 | for (let i = 0; i < dim; i++) { 129 | if (i === preference[1]) { 130 | probabilities.push(preference[0]) 131 | } else { 132 | probabilities.push(prob_for_remaining) 133 | } 134 | } 135 | return probabilities 136 | } 137 | 138 | /** 139 | * Generate policies dynamically for given number of clients. 140 | * @param {number} no_of_clients 141 | * @returns {Array} 142 | */ 143 | const generatePolicies = (no_of_clients, dim = 24, client_preferences) => { 144 | let policies = [] 145 | for (let i = 0; i < no_of_clients; i++) { 146 | let policy = generateProbabilities(dim, client_preferences[i]) 147 | policies.push(policy) 148 | } 149 | return policies 150 | } 151 | 152 | /** 153 | * Generate client preferences 154 | * @param {number} no_of_clients 155 | * @returns 156 | */ 157 | let clientPreferences = (no_of_clients, index, change_prob_idxs, change_probs) => { 158 | return Array(no_of_clients) 159 | .fill() 160 | .map( 161 | function (x, i) { 162 | return [this.probs[i][this.idx], this.probIdxs[i][this.idx]] 163 | }, 164 | { 165 | probIdxs: change_prob_idxs, 166 | probs: change_probs, 167 | idx: index, 168 | } 169 | ) 170 | } 171 | 172 | export { 173 | clientPreferences, 174 | generatePolicies, 175 | generateProbabilities, 176 | argMax, 177 | banditThompson, 178 | calcGradient, 179 | simulate, 180 | actionAndUpdate, 181 | } 182 | -------------------------------------------------------------------------------- /rl.js/index.js: -------------------------------------------------------------------------------- 1 | import { actionAndUpdate, generatePolicies, clientPreferences } from './common' 2 | import { META_DATA } from './data/' 3 | import { track } from './useractivity' 4 | const id = 6 // Example Id 5 | const API_ENDPOINT = '127.0.0.1:8082' 6 | const url = 'ws://' + API_ENDPOINT + '/fl-server/' + META_DATA[id].model_name 7 | 8 | // Features/parameters that determine the users action 9 | let alphasArray = [], 10 | betasArray = [], 11 | policy = [], 12 | clientId = null, 13 | selectedOption = 0, 14 | cycle = 0, 15 | socket = null, 16 | simulation = false, 17 | user_privacy_preference_level = 0, 18 | random = true, 19 | noOfClients = META_DATA[id].no_of_clients, 20 | probIdx = 0, // It increases by 1 unit if the policy change is set to true 21 | gradWeights, 22 | new_reward, 23 | interval, 24 | endOfCycle = false 25 | 26 | let client_preferences = clientPreferences( 27 | noOfClients, 28 | probIdx, 29 | META_DATA[id].change_prob_idxs, 30 | META_DATA[id].change_probs 31 | ) 32 | let userActionPromiseResolve = undefined, 33 | userActionPromise = null 34 | // User options : 24 types of options 35 | const dim = META_DATA[id].dim, 36 | stopAfter = 100, 37 | policies = generatePolicies(noOfClients, dim, client_preferences) 38 | 39 | function trackingCallback(collection, properties, callback) { 40 | if (collection === 'clicks') { 41 | if (endOfCycle) return 42 | 43 | let elem = document.getElementById('root') 44 | new_reward = parseInt(elem.getAttribute('data-reward')) 45 | endOfCycle = true 46 | handleUserAction() 47 | } 48 | } 49 | 50 | // Resolve after user action 51 | const handleUserAction = () => { 52 | console.log('[Content Script ML]Action Resolved') 53 | userActionPromiseResolve(true) 54 | } 55 | // Inject Noise 56 | const injectNoise = (user_privacy_preference_level) => { 57 | let random_number = Math.random() 58 | console.log( 59 | '[Content Script ML - Socket]Math.random:user_privacy_preference_level', 60 | random_number, 61 | ':', 62 | user_privacy_preference_level / 100 63 | ) 64 | return random_number < user_privacy_preference_level / 100 ? 1 : 0 65 | } 66 | 67 | // Get reward and update weights 68 | const setRewardAndUpdateWeights = async () => { 69 | let alphas, 70 | betas, 71 | clicked = false 72 | userActionPromise = new Promise((resolve) => { 73 | userActionPromiseResolve = resolve 74 | }) 75 | if (cycle >= stopAfter) { 76 | clearInterval(interval) 77 | return 78 | } 79 | 80 | // Raise the useraction event to trigger user action in website 81 | let webelem = document.getElementById('root') 82 | let isWaiting = parseInt(webelem.getAttribute('data-waiting')) // Signals website is waiting for 'useraction' event. 83 | console.log( 84 | '[Content Script ML - Socket]Website is waiting for event', 85 | isWaiting, 86 | isWaiting === 1 87 | ) 88 | 89 | if (!endOfCycle && isWaiting === 0) return 90 | 91 | // Read selected option from the website 92 | selectedOption = webelem.getAttribute('data-option') 93 | console.log('[Content Script ML - Socket]Selected Option in Website', selectedOption) 94 | webelem.dispatchEvent(new CustomEvent('useraction')) 95 | console.log("[Content Script ML - Socket]Trigger 'useraction' Event", cycle) 96 | 97 | console.log( 98 | '[Content Script ML - Socket]Waiting for user action..................................' 99 | ) 100 | clicked = await userActionPromise 101 | 102 | if (clicked === true) { 103 | if (new_reward === 1) { 104 | console.log('[Content Script ML - Socket]Clicked', clicked) 105 | } else { 106 | console.log('[Content Script ML - Socket]Rejected') 107 | } 108 | console.log('[Content Script ML - Socket]New Reward selected', new_reward) 109 | 110 | // Calculate new gradients 111 | let params = actionAndUpdate(alphasArray, betasArray, selectedOption, new_reward) 112 | console.log('[Content Script ML - Socket]Grad Weights', params) 113 | gradWeights = params[0] 114 | alphas = gradWeights[0].dataSync() 115 | betas = gradWeights[1].dataSync() 116 | 117 | // Check if random is true and read the user preference level value to add noise 118 | if (random === true) { 119 | let addNoise = injectNoise(user_privacy_preference_level) 120 | console.log( 121 | '[Content Script ML - Socket]Bernoulli Sampling Result(Toss Result -> Add Noise):', 122 | addNoise 123 | ) 124 | if (addNoise) { 125 | alphas = Object.assign({}, Array(alphasArray.length).fill(0)) 126 | betas = Object.assign({}, Array(alphasArray.length).fill(0)) 127 | console.log('[Content Script ML - Socket]alphas;betas:', alphas, betas) 128 | } 129 | } 130 | console.log('[Content Script ML - Socket]Diff: alphas and betas', alphas, betas) 131 | // Send data to the server 132 | console.log('[Content Script ML - Socket]Sending New Gradients to the Server') 133 | endOfCycle = false 134 | socket.send( 135 | JSON.stringify({ 136 | event: 'update', // 0 -> event 137 | alphas: alphas, // 1 -> alphas 138 | betas: betas, // 2 -> betas 139 | client_id: clientId, 140 | model_name: 'example_' + id, 141 | }) 142 | ) 143 | } 144 | } 145 | 146 | // Initializes socket events 147 | const initialize = (clientId) => { 148 | console.log('[Content Script ML - Socket]Onopen event registered') 149 | // Connect to the server & Get params from server 150 | socket.onopen = (message) => { 151 | console.log('[Content Script ML - Socket]Connecton established') 152 | console.log('[Content Script ML - Socket]Message Received', message) 153 | socket.send( 154 | JSON.stringify({ 155 | event: 'connected', 156 | client_id: clientId, 157 | model_name: 'example_' + id, 158 | }) 159 | ) 160 | } 161 | 162 | console.log('[Content Script ML - Socket]Onmessage event registered') 163 | // Events to receive message from server 164 | socket.onmessage = (event) => { 165 | const message_from_server = JSON.parse(event.data) 166 | // console.log("[Content Script ML - Socket]Message Received", message_from_server); 167 | 168 | if (cycle > stopAfter) return 169 | 170 | let dim_from_server = null 171 | console.log('[Content Script ML - Socket]Message Received', message_from_server) 172 | console.log('[Content Script ML - Socket]Type', message_from_server['type']) 173 | // Sets params with the value received from the server 174 | if (message_from_server['type'] === 'init-params') { 175 | dim_from_server = message_from_server.params['dim'] 176 | // Set the values 177 | if (dim_from_server !== dim) { 178 | console.log('[Content Script ML - Socket]Dimension Doesnot Match. ') 179 | return 180 | } 181 | alphasArray = message_from_server.params['al'] 182 | betasArray = message_from_server.params['bt'] 183 | console.log( 184 | '[Content Script ML - Socket]Params Received Alphas and Betas', 185 | alphasArray, 186 | betasArray 187 | ) 188 | // setRewardAndUpdateWeights(); 189 | } else if (message_from_server['type'] === 'new_weights') { 190 | alphasArray = message_from_server.params['al'] 191 | betasArray = message_from_server.params['bt'] 192 | userActionPromiseResolve = undefined 193 | // Sync with website: Update the id of the root element of the website to 194 | // trigger start of new cycle. 195 | cycle = cycle + 1 196 | let webelem = document.getElementById('root') 197 | webelem.setAttribute('data-cycle', cycle) // Update cycle to website 198 | webelem.dispatchEvent(new CustomEvent('newcycle', { detail: { cycle: cycle } })) 199 | console.log('[Content Script ML - Socket]Trigger Newcycle Event, Cycle =>', cycle) 200 | // setRewardAndUpdateWeights(); 201 | } 202 | } 203 | // Set timer to run reward and update 204 | interval = setInterval(setRewardAndUpdateWeights, 1000) 205 | } 206 | 207 | // Initialization 208 | socket = new WebSocket(url) 209 | clientId = 0 //Math.floor(Math.random() * noOfClients); 210 | policy = policies[clientId] 211 | 212 | // Display Params 213 | console.log('[Content Script ML]Example ID', id) 214 | console.log('[Content Script ML]Client ID:', clientId) 215 | console.log('[Content Script ML]noOfClients', noOfClients) 216 | console.log('[Content Script ML]API_ENDPOINT', API_ENDPOINT) 217 | console.log('[Content Script ML]URL', url) 218 | console.log('[Content Script ML]Simulate', simulation) 219 | console.log('[Content Script ML]Cycle', cycle) 220 | console.log('[Content Script ML]stopAfter', stopAfter) 221 | console.log('[Content Script ML - Socket]Selected Policy:', policy) 222 | 223 | // Initialize the listeners for socket events 224 | initialize(clientId) 225 | track(trackingCallback) 226 | 227 | // Listener for the messages sent from background script. 228 | browser.runtime.onMessage.addListener((data, sender) => { 229 | console.log( 230 | '[Content Script ML - Socket]Message from the background script, sender', 231 | data, 232 | sender 233 | ) 234 | console.log( 235 | '[Content Script ML - Socket]data - mtype:value', 236 | data['_mtype'], 237 | ':', 238 | data['_message'] 239 | ) 240 | user_privacy_preference_level = data['_message'] 241 | 242 | return Promise.resolve({ 243 | status: 'success', 244 | }) 245 | }) 246 | -------------------------------------------------------------------------------- /rl.js/useractivity.js: -------------------------------------------------------------------------------- 1 | export function track(callback) { 2 | var options = { 3 | pageviewsEventName: 'pageviews', 4 | inputChangeEventName: 'input-changes', 5 | clicksEventName: 'clicks', 6 | formSubmissionsEventName: 'form-submissions', 7 | callbackTimeout: 1000, 8 | globalProperties: { 9 | page_url: window.location.href, 10 | referrer_url: document.referrer, 11 | }, 12 | } 13 | // create a common namespace with options 14 | var CommonWeb = { 15 | options: options, 16 | } 17 | 18 | CommonWeb.addGlobalProperties = function (properties) { 19 | $.extend(CommonWeb.options.globalProperties, properties) 20 | } 21 | 22 | // initiate user tracking, using a GUID stored in a cookie 23 | // The user can pass in a custom cookie name and custom GUID, if they would like 24 | CommonWeb.trackSession = function (cookieName, defaultGuid) { 25 | if (typeof cookieName !== 'string') { 26 | cookieName = 'common_web_guid' 27 | } 28 | 29 | // Look for the GUID in the currently set cookies 30 | var cookies = document.cookie.split('; ') 31 | var guid = null 32 | 33 | for (var i = 0; i < cookies.length; i++) { 34 | cookieParts = cookies[i].split('=') 35 | if (cookieParts[0] === cookieName) { 36 | // Got it! 37 | guid = cookieParts[1] 38 | break 39 | } 40 | } 41 | 42 | // We didn't find our guid in the cookies, so we need to generate our own 43 | if (guid === null) { 44 | if (typeof defaultGuid === 'string') { 45 | guid = defaultGuid 46 | } else { 47 | genSub = function () { 48 | return Math.floor((1 + Math.random()) * 0x10000) 49 | .toString(16) 50 | .substring(1) 51 | } 52 | 53 | guid = 54 | genSub() + 55 | genSub() + 56 | '-' + 57 | genSub() + 58 | '-' + 59 | genSub() + 60 | '-' + 61 | genSub() + 62 | '-' + 63 | genSub() + 64 | genSub() + 65 | genSub() 66 | } 67 | 68 | expiration_date = new Date() 69 | expiration_date.setFullYear(expiration_date.getFullYear() + 1) 70 | 71 | cookie_string = 72 | cookieName + '=' + guid + '; path/; expires=' + expiration_date.toGMTString() 73 | document.cookie = cookie_string 74 | } 75 | 76 | CommonWeb.addGlobalProperties({ 77 | guid: guid, 78 | }) 79 | 80 | return guid 81 | } 82 | 83 | // setup pageview tracking hooks, optionally including more properties 84 | // more properties can also be a function 85 | // do not double set along with track! 86 | CommonWeb.trackPageview = function (moreProperties) { 87 | var defaultProperties = CommonWeb.options.globalProperties 88 | var properties = $.extend(true, {}, defaultProperties, toProperties(moreProperties)) 89 | 90 | CommonWeb.Callback(CommonWeb.options.pageviewsEventName, properties) 91 | } 92 | 93 | CommonWeb.trackClicks = function (elements, moreProperties) { 94 | if (typeof elements === 'undefined') { 95 | elements = $('a') 96 | } 97 | $.each(elements, function (index, element) { 98 | $(element).on('click', function (event) { 99 | var timer = CommonWeb.options.callbackTimeout 100 | 101 | // combine local and global moreProperties 102 | var properties = toClickProperties(event, element, moreProperties) 103 | 104 | // check if the page is probably going to unload 105 | var pageWillUnload = 106 | element.href && element.target !== '_blank' && !isMetaKey(event) 107 | var unloadCallback = function () {} 108 | 109 | // if the page will unload, don't let the JS event bubble 110 | // but navigate to the href after the click 111 | if (pageWillUnload) { 112 | unloadCallback = function () { 113 | window.location.href = element.href 114 | } 115 | 116 | setTimeout(function () { 117 | window.location.href = element.href 118 | }, timer) 119 | } 120 | CommonWeb.Callback(options.clicksEventName, properties, unloadCallback) 121 | }) 122 | }) 123 | $.each($('button'), function (index, element) { 124 | $(element).on('click', function (event) { 125 | var properties = toClickProperties(event, element, moreProperties) 126 | CommonWeb.Callback(options.clicksEventName, properties) 127 | }) 128 | }) 129 | } 130 | 131 | // track things that are not links; i.e. don't need any special tricks to 132 | // prevent page unloads 133 | CommonWeb.trackClicksPassive = function (elements, moreProperties) { 134 | $.each(elements, function (index, element) { 135 | $(element).on('click', function (event) { 136 | var properties = toClickProperties(event, element, moreProperties) 137 | CommonWeb.Callback(options.clicksEventName, properties) 138 | }) 139 | }) 140 | } 141 | 142 | // track form submissions 143 | CommonWeb.trackFormSubmissions = function (elements, moreProperties) { 144 | if (typeof elements === 'undefined') { 145 | elements = $('form') 146 | } 147 | 148 | $.each(elements, function (index, element) { 149 | var timer = CommonWeb.options.callbackTimeout 150 | 151 | // use to avoid duplicate submits 152 | var callbackCalled = false 153 | 154 | $(element).on('submit', function (event) { 155 | var properties = toSubmitProperties(event, element, moreProperties) 156 | 157 | // assume true for now in this method 158 | var pageWillUnload = true 159 | var unloadCallback = function () {} 160 | 161 | if (pageWillUnload) { 162 | unloadCallback = function () { 163 | // not the best approach here. 164 | // the form can only be submitted 165 | // once, etc. 166 | if (!callbackCalled) { 167 | callbackCalled = true 168 | element.submit() 169 | } 170 | } 171 | 172 | event.preventDefault() 173 | 174 | // We only want to fire the timeout if 175 | // we know the page will unload. Ajax 176 | // form submissions shouldn't submit. 177 | setTimeout(function () { 178 | callbackCalled = true 179 | element.submit() 180 | }, timer) 181 | } 182 | 183 | CommonWeb.Callback(options.formSubmissionsEventName, properties, unloadCallback) 184 | }) 185 | }) 186 | } 187 | 188 | CommonWeb.trackInputChanges = function (elements, moreProperties) { 189 | if (typeof elements === 'undefined') { 190 | elements = $('input, textarea, select') 191 | } 192 | 193 | $.each(elements, function (index, element) { 194 | var currentValue = $(element).val() 195 | 196 | $(element).on('change', function (event) { 197 | var properties = toChangeProperties(event, element, currentValue, moreProperties) 198 | CommonWeb.Callback(options.inputChangeEventName, properties) 199 | 200 | currentValue = $(element).val() 201 | }) 202 | }) 203 | } 204 | 205 | // define a namespace just for transformations of events and elements to properties 206 | // override as a workaround to add / remove properties 207 | CommonWeb.Transformations = { 208 | eventToProperties: function (event) { 209 | var properties = {} 210 | 211 | properties['timestamp'] = event.timestamp 212 | properties['type'] = event.type 213 | properties['metaKey'] = event.metaKey 214 | 215 | return properties 216 | }, 217 | 218 | elementToProperties: function (element, extraProperties) { 219 | var properties = extraProperties || {} 220 | 221 | // add the tag name 222 | properties.tagName = element.tagName 223 | 224 | // add the inner text for some tag types 225 | if (element.tagName === 'A') { 226 | properties.text = element.innerText 227 | } 228 | 229 | // add each attribute 230 | $(element.attributes).each(function (index, attr) { 231 | properties[attr.nodeName] = attr.value 232 | }) 233 | 234 | // break classes out into an array if any exist 235 | var classes = $(element).attr('class') 236 | if (classes) { 237 | properties['classes'] = classes.split(/\s+/) 238 | } 239 | 240 | properties['path'] = $(element).getPath() 241 | return properties 242 | }, 243 | 244 | formElementToProperties: function (formElement) { 245 | var formValues = {} 246 | 247 | // TODO: remove dependency on jQuery 248 | formValues.form_values = $(formElement).serializeArray() 249 | // simple alias for now, but could do more as 250 | // far as the form values are concerned 251 | return this.elementToProperties(formElement, formValues) 252 | }, 253 | 254 | inputElementToProperties: function (inputElement) { 255 | var inputValues = { 256 | value: $(inputElement).val(), 257 | } 258 | 259 | var parentForm = $(inputElement).closest('form') 260 | if (parentForm.size() > 0) { 261 | inputValues.form = this.elementToProperties(parentForm[0]) 262 | } 263 | 264 | return this.elementToProperties(inputElement, inputValues) 265 | }, 266 | } 267 | 268 | function toClickProperties(event, element, moreProperties) { 269 | var defaultProperties = CommonWeb.options.globalProperties 270 | var properties = $.extend( 271 | true, 272 | {}, 273 | defaultProperties, 274 | toProperties(moreProperties, [event, element]) 275 | ) 276 | 277 | var elementProperties = { 278 | element: CommonWeb.Transformations.elementToProperties(element, null), 279 | } 280 | var eventProperties = { 281 | event: CommonWeb.Transformations.eventToProperties(event), 282 | } 283 | 284 | return $.extend(true, {}, properties, elementProperties, eventProperties) 285 | } 286 | 287 | function toChangeProperties(event, element, previousValue, moreProperties) { 288 | var defaultProperties = CommonWeb.options.globalProperties 289 | var properties = $.extend( 290 | true, 291 | {}, 292 | defaultProperties, 293 | toProperties(moreProperties, [event, element]) 294 | ) 295 | 296 | var elementProperties = { 297 | element: CommonWeb.Transformations.inputElementToProperties(element), 298 | } 299 | if (previousValue && previousValue !== '') { 300 | elementProperties.element.previousValue = previousValue 301 | } 302 | 303 | var eventProperties = { 304 | event: CommonWeb.Transformations.eventToProperties(event), 305 | } 306 | 307 | return $.extend(true, {}, properties, elementProperties, eventProperties) 308 | } 309 | 310 | function toSubmitProperties(event, element, moreProperties) { 311 | var defaultProperties = CommonWeb.options.globalProperties 312 | var properties = $.extend( 313 | true, 314 | {}, 315 | defaultProperties, 316 | toProperties(moreProperties, [event, element]) 317 | ) 318 | 319 | var elementProperties = { 320 | element: CommonWeb.Transformations.formElementToProperties(element), 321 | } 322 | var eventProperties = { 323 | event: CommonWeb.Transformations.eventToProperties(event), 324 | } 325 | 326 | return $.extend(true, {}, properties, elementProperties, eventProperties) 327 | } 328 | 329 | function toProperties(propertiesOrFunction, args) { 330 | if (typeof propertiesOrFunction === 'function') { 331 | return propertiesOrFunction.apply(window, args) 332 | } else { 333 | return propertiesOrFunction 334 | } 335 | } 336 | 337 | function isMetaKey(event) { 338 | return event.metaKey || event.altKey || event.ctrlKey || event.shiftKey 339 | } 340 | 341 | /* 342 | jQuery-GetPath v0.01, by Dave Cardwell. (2007-04-27) 343 | http://davecardwell.co.uk/javascript/jquery/plugins/jquery-getpath/ 344 | Copyright (c)2007 Dave Cardwell. All rights reserved. 345 | Released under the MIT License. 346 | Usage: 347 | var path = $('#foo').getPath(); 348 | */ 349 | jQuery.fn.extend({ 350 | getPath: function (path) { 351 | // The first time this function is called, path won't be defined. 352 | if (typeof path == 'undefined') path = '' 353 | 354 | // If this element is we've reached the end of the path. 355 | if (this.is('html')) return 'html' + path 356 | 357 | // Add the element name. 358 | var cur = this.get(0).nodeName.toLowerCase() 359 | 360 | // Determine the IDs and path. 361 | var id = this.attr('id'), 362 | klass = this.attr('class') 363 | 364 | // Add the #id if there is one. 365 | if (typeof id != 'undefined') cur += '#' + id 366 | 367 | // Add any classes. 368 | if (typeof klass != 'undefined') cur += '.' + klass.split(/[\s\n]+/).join('.') 369 | 370 | // Recurse up the DOM. 371 | return this.parent().getPath(' > ' + cur + path) 372 | }, 373 | }) 374 | 375 | // // backends 376 | // CommonWeb.Endpoint = { 377 | // Debug: true, 378 | // Callback: function(collection, properties, callback) { 379 | // console.log("[Content Script Tracking - Callback]", collection, properties, callback); 380 | // window.postMessage({ 381 | // type: "FROM_PAGE", 382 | // collection: collection 383 | // }); 384 | // console.log("After post message") 385 | // // CommonWeb.Keen.Client.addEvent(collection, properties, function() { 386 | // // if (CommonWeb.Keen.Debug) { 387 | // // console.log(collection + ": " + JSON.stringify(properties)); 388 | // // } 389 | // // if (callback) { 390 | // // callback(); 391 | // // } 392 | // // }); 393 | // } 394 | // }; 395 | 396 | window.CommonWeb = CommonWeb 397 | // window.CommonWeb.Callback = CommonWeb.Endpoint.Callback; 398 | window.CommonWeb.Callback = callback 399 | window.CommonWeb.trackClicks() 400 | window.CommonWeb.trackClicksPassive($('div')) 401 | window.CommonWeb.trackPageview() 402 | } 403 | -------------------------------------------------------------------------------- /public/jquery.min.js: -------------------------------------------------------------------------------- 1 | /*! jQuery v3.6.0 | (c) OpenJS Foundation and other contributors | jquery.org/license */ 2 | !function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.6.0",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e&&e.namespaceURI,n=e&&(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},j=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||D,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,D=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",y.option=!!ce.lastChild;var ge={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function je(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function De(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function qe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Le(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var _t,zt=[],Ut=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=zt.pop()||S.expando+"_"+wt.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Ut.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Ut.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Ut,"$1"+r):!1!==e.jsonp&&(e.url+=(Tt.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,zt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((_t=E.implementation.createHTMLDocument("").body).innerHTML="
",2===_t.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=Fe(y.pixelPosition,function(e,t){if(t)return t=We(e,n),Pe.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0