├── .gitignore ├── LICENSE.md ├── README.md ├── chrome ├── devtools.html ├── devtools.js ├── manifest.json ├── promises-frontend.js └── scripts │ ├── commonjs.js │ ├── ext.js │ ├── main.js │ ├── runtime.js │ └── storage.js ├── firefox ├── data │ └── empty.txt ├── lib │ ├── ext.js │ ├── loader.js │ ├── main.js │ ├── runtime.js │ ├── storage.js │ └── target-logger.js ├── package.json └── test │ └── test-main.js ├── init.bat ├── init.sh └── shared ├── bootstrap.js ├── css └── style.css ├── ext_modules ├── backend.js ├── ext.js ├── prefs.js └── storage.js ├── fw ├── cui │ ├── loader.debug.js │ ├── loader.js │ ├── loader2.js │ ├── modules.js │ └── modules │ │ ├── ajax.js │ │ ├── basic.js │ │ ├── events.js │ │ ├── lib.js │ │ ├── parser.js │ │ ├── templates-utils.js │ │ ├── ui.js │ │ └── utils.js ├── lib │ ├── sync.ie.js │ ├── sync.js │ └── sync.pointers.js └── modules │ ├── checkbox.js │ ├── main.js │ ├── panel.js │ └── settings.js ├── prefs.json ├── promises-backend.js ├── promises-panel.html ├── promises-panel.js ├── providers └── es6.js └── ui ├── css ├── semantic.css └── semantic.min.css ├── fonts ├── basic.icons.eot ├── basic.icons.svg ├── basic.icons.ttf ├── basic.icons.woff ├── icons.eot ├── icons.otf ├── icons.svg ├── icons.ttf └── icons.woff └── images ├── loader-large-inverted.gif ├── loader-large.gif ├── loader-medium-inverted.gif ├── loader-medium.gif ├── loader-mini-inverted.gif ├── loader-mini.gif ├── loader-small-inverted.gif └── loader-small.gif /.gitignore: -------------------------------------------------------------------------------- 1 | firefox/data/shared 2 | chrome/shared 3 | node_modules/ 4 | *.notes.txt -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Arthur Stolyar 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Promises Debugger Extension 2 | 3 | ## Hello 4 | 5 | Promises Debugger Extension allow you debug native Promises (ES6) right now in current and higher versions of Chrome and Firefox. 6 | 7 | Please see video for details. Extension is in really early version, but soon it will have more features. 8 | 9 | ## Video 10 | 11 | IMAGE ALT TEXT HERE 14 | 15 | ## How to use 16 | 17 | * First you need to run ```init.bat``` if you are on Window, or ```init.sh``` if you are on Linux/OS X 18 | * To run Firefox extension you should have Addons-SDK installed, then go to ```firefox``` subfolder and run ```cfx run``` 19 | * To run Chrome extension, just load it as an unpacket extension from subfolder ```chrome``` 20 | 21 | __**Note:** Doesn't catch Promise errors from scripts which are produced before document is completely parsed. 22 | By some reason scripts cannot be injected before it, at least in Chrome. -------------------------------------------------------------------------------- /chrome/devtools.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /chrome/devtools.js: -------------------------------------------------------------------------------- 1 | var panel, 2 | panelWindow, 3 | panelPending = [], 4 | watchListenersRegistered; 5 | 6 | chrome.devtools.panels.sources.createSidebarPane("My Sidebar", function(sidebar) { 7 | console.log(sidebar); 8 | // sidebar initialization code here 9 | sidebar.setObject({ some_data: "Some data to show" }); 10 | }); 11 | 12 | chrome.devtools.panels.create('Promises', 'shared/images/promises.png', 'shared/promises-panel.html', function(promisesPanel) { 13 | panel = promisesPanel; 14 | 15 | panel.onShown.addListener(function waitShow(win) { 16 | panelWindow = win; 17 | 18 | var arr = panelPending; 19 | 20 | panelPending = []; 21 | 22 | arr.forEach(function(arr) { 23 | UIAction(arr[0], arr[1]); 24 | }); 25 | 26 | panel.onShown.removeListener(waitShow); 27 | 28 | registerWatchListeners(); 29 | listenUICommands(); 30 | 31 | isWatchingPagePromise.then(function() { 32 | if (isWatchingPage) { 33 | UIAction('show_not_attached'); 34 | } else { 35 | UIAction('show_need_reload'); 36 | } 37 | }); 38 | }); 39 | }); 40 | 41 | var port = chrome.runtime.connect({ 42 | name: 'DEV_TOOLS' 43 | }); 44 | 45 | var requestWatchTab = function(callback, options) { 46 | port.postMessage({ 47 | action: 'start_watch_page', 48 | data: { 49 | tabId: chrome.devtools.inspectedWindow.tabId, 50 | attach: options && !!options.attach 51 | } 52 | }); 53 | 54 | var waitForWatchStart = function(message, port) { 55 | if (message.action !== 'watch_started') return; 56 | 57 | // here we need reload 58 | // show reload view 59 | port.onMessage.removeListener(waitForWatchStart); 60 | startWaitReloads(); 61 | 62 | if (callback) { 63 | callback(); 64 | } 65 | }; 66 | 67 | port.onMessage.addListener(waitForWatchStart); 68 | }, 69 | requestStopWatch = function() { 70 | stopWaitReloads(); 71 | 72 | port.postMessage({ 73 | action: 'stop_watch_page', 74 | data: { 75 | tabId: chrome.devtools.inspectedWindow.tabId 76 | } 77 | }); 78 | }, 79 | registerWatchListeners = function() { 80 | console.log('registerWatchListeners'); 81 | 82 | if (!watchListenersRegistered) { 83 | port.onMessage.addListener(function(message) { 84 | if (message.action === 'front_end_event') { 85 | handleFrontEndEvent(message.data); 86 | } 87 | }); 88 | } 89 | 90 | watchListenersRegistered = true; 91 | }, 92 | startWaitReloads = function() { 93 | // request wait reloads 94 | port.onMessage.addListener(onReload); 95 | }, 96 | stopWaitReloads = function() { 97 | // request wait reloads 98 | port.onMessage.removeListener(onReload); 99 | }, 100 | handleFrontEndEvent = function(message) { 101 | // console.log('handleFrontEndEvent', message); 102 | 103 | UIAction(message.action, message.data); 104 | }, 105 | frontEndCommand = function(command) { 106 | chrome.devtools.inspectedWindow.eval(command + '();', { 107 | useContentScriptContext: true 108 | }); 109 | }, 110 | UIAction = function(action, message) { 111 | if (panelWindow) { 112 | panelWindow.postMessage({ 113 | action: action, 114 | message: message 115 | }, '*'); 116 | } else { 117 | panelPending.push([action, message]); 118 | } 119 | }, 120 | listenUICommands = function() { 121 | if (!panelWindow) return; 122 | 123 | panelWindow.addEventListener('message', function(e) { 124 | var data = e.data; 125 | 126 | // console.log('UI command', data); 127 | 128 | if (data && data.serviceAction && 129 | serviceActions.hasOwnProperty(data.serviceAction) 130 | ) { 131 | serviceActions[data.serviceAction](data.message); 132 | } 133 | }); 134 | }, 135 | onReloadHandler = function() { 136 | console.log('on reload handle'); 137 | UIAction('show_main'); 138 | UIAction('reload'); 139 | 140 | // console.log('reload'); 141 | }, 142 | onReload = function(message, port) { 143 | if (message.action !== 'reload') return; 144 | 145 | onReloadHandler(); 146 | }; 147 | 148 | var serviceActions = { 149 | attach: function() { 150 | isAttached = true; 151 | 152 | var wasWatched = isWatchingPage; 153 | 154 | requestWatchTab(function() { 155 | if (wasWatched) { 156 | frontEndCommand('attachToBackend'); 157 | } 158 | }, { 159 | attach: !isWatchingPage 160 | }); 161 | 162 | onReloadHandler(); 163 | }, 164 | reload_and_attach: function() { 165 | isAttached = true; 166 | 167 | requestWatchTab(function() { 168 | chrome.devtools.inspectedWindow.reload(); 169 | }); 170 | }, 171 | open_resource: function(message) { 172 | if (chrome.devtools.panels.openResource) { 173 | chrome.devtools.panels.openResource(message.file, message.line - 1); 174 | } 175 | }, 176 | detach: function() { 177 | console.log('detach'); 178 | requestStopWatch(); 179 | frontEndCommand('detachFromBackend'); 180 | isAttached = false; 181 | } 182 | }; 183 | 184 | port.onMessage.addListener(function(message) { 185 | if (message.action !== 'front_end_started') return; 186 | 187 | isWatchingPage = true; 188 | }); 189 | 190 | port.postMessage({ 191 | action: 'prepare_front_end', 192 | data: { 193 | tabId: chrome.devtools.inspectedWindow.tabId 194 | } 195 | }); 196 | 197 | var isWatchingPage, 198 | isAttached, 199 | makeIsFalse = function() { 200 | isWatchingPage = false; 201 | isAttached = false; 202 | }; 203 | 204 | var isWatchingPagePromise = new Promise(function(resolve) { 205 | var WAIT_LIMIT = 5000, 206 | currentWait = 0; 207 | 208 | var doEval = function() { 209 | chrome.devtools.inspectedWindow.eval( 210 | '({ isWatchingPage: this.isWatchingPage, isAttached: this.isAttached })', { 211 | useContentScriptContext: true 212 | }, function(result, exception) { 213 | if (exception) { 214 | if (exception.isError && exception.code === 'E_NOTFOUND') { 215 | if (currentWait < 5000) { 216 | currentWait += 100; 217 | setTimeout(doEval, 100); 218 | return; 219 | } else { 220 | makeIsFalse(); 221 | } 222 | } else { 223 | makeIsFalse(); 224 | } 225 | } else if (result) { 226 | isAttached = result.isAttached; 227 | isWatchingPage = result.isWatchingPage 228 | } else { 229 | makeIsFalse(); 230 | } 231 | 232 | console.log('isWatchingPage', isWatchingPage); 233 | console.log('isAttached', isAttached); 234 | 235 | resolve(); 236 | } 237 | ); 238 | }; 239 | 240 | doEval(); 241 | }); -------------------------------------------------------------------------------- /chrome/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | 4 | "name": "Promises Debugger", 5 | "description": "", 6 | "version": "1.0", 7 | 8 | "permissions": [ 9 | "tabs", 10 | "storage", 11 | "activeTab", 12 | "webNavigation", 13 | "http://*/", 14 | "https://*/" 15 | ], 16 | "devtools_page": "devtools.html", 17 | "background": { 18 | "scripts": [ 19 | "scripts/commonjs.js", 20 | "scripts/main.js" 21 | ], 22 | "persistent": false 23 | }, 24 | "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'" 25 | } -------------------------------------------------------------------------------- /chrome/promises-frontend.js: -------------------------------------------------------------------------------- 1 | ;(function(global) { 2 | console.log('inject isWatchingPage:', global.isWatchingPage); 3 | 4 | if (global.isWatchingPage) return; 5 | global.isWatchingPage = true; 6 | 7 | var port, 8 | messageBuffer = [], 9 | isChrome = true; 10 | 11 | var onDisconnect = function() { 12 | window.removeEventListener('message', backendListener); 13 | port = null; 14 | messageBuffer = []; 15 | global.isAttached = false; 16 | }; 17 | 18 | var handleEverStack = function(stack) { 19 | var lines = stack.split(/(?:\n+|\->)/), 20 | line, 21 | i = 0, 22 | newLines = [], 23 | firstLine = lines[0], 24 | message; 25 | 26 | if (isChrome && firstLine && 27 | firstLine.search(/\bat\b/) === -1 && firstLine.search(/error/i) !== -1) { 28 | message = firstLine; 29 | lines = lines.slice(1); 30 | } 31 | 32 | if (true) { 33 | while (i < lines.length) { 34 | line = lines[i]; 35 | ++i; 36 | 37 | if ( 38 | line && ( 39 | line.indexOf('(native)') !== -1 || 40 | line.indexOf('(:') !== -1 || 41 | line.indexOf('resource://') !== -1 || 42 | line.indexOf('jar:file://') !== -1 43 | ) 44 | ) { 45 | continue; 46 | } 47 | 48 | if (line) { 49 | newLines.push(line); 50 | } 51 | } 52 | } else { 53 | newLines = lines; 54 | } 55 | 56 | if (!newLines.length) { 57 | return null; 58 | } 59 | 60 | return { 61 | lines: newLines, 62 | message: message 63 | }; 64 | }; 65 | 66 | global.registerDevToolsListeners = function(name) { 67 | console.log('registerDevToolsListeners:', name); 68 | 69 | if (port) return; 70 | 71 | port = chrome.runtime.connect({ 72 | name: name 73 | }); 74 | 75 | global.isAttached = true; 76 | 77 | port.onDisconnect.addListener(onDisconnect); 78 | 79 | if (messageBuffer.length) { 80 | messageBuffer.forEach(function(message) { 81 | // console.log('resend messages'); 82 | port.postMessage(message); 83 | }); 84 | 85 | messageBuffer = null; 86 | } 87 | 88 | console.log(port); 89 | }; 90 | 91 | var backendListener = function(e) { 92 | if (e.source !== window) { 93 | console.warn('bad source'); 94 | return; 95 | } 96 | 97 | var data = e.data; 98 | 99 | if (!data || !data.PromisesDebugger) return; 100 | 101 | if (data.method === 'requestUpdate') { 102 | var message = { 103 | action: 'update_data', 104 | data: data.message 105 | }; 106 | 107 | 108 | if (port) { 109 | port.postMessage(message); 110 | } else { 111 | messageBuffer.push(message); 112 | } 113 | } else if (data.method === 'reportError') { 114 | global.reportError(data.message); 115 | } 116 | }; 117 | 118 | global.attachToBackend = function() { 119 | global.registerDevToolsListeners('FRONT_END__TO__DEV_TOOLS'); 120 | window.addEventListener('message', backendListener); 121 | }; 122 | 123 | global.detachFromBackend = function() { 124 | if (port) { 125 | port.disconnect(); 126 | } 127 | 128 | onDisconnect(); 129 | }; 130 | 131 | global.attachToBackend(); 132 | global.evalBackend = function(code) { 133 | var script = document.createElement('script'); 134 | 135 | script.textContent = code; 136 | document.documentElement.appendChild(script); 137 | document.documentElement.removeChild(script); 138 | }; 139 | 140 | global.reportError = function(message) { 141 | var error = message.error, 142 | provider = message.provider || ''; 143 | 144 | if (!error || !error.value) return; 145 | 146 | var value = error.value, 147 | message, 148 | stack; 149 | 150 | message = (value.name ? value.name + ': ' : '') + value.message; 151 | stack = value.stack ? handleEverStack(value.stack) : null; 152 | 153 | var showProvider = false; 154 | 155 | console.groupCollapsed( 156 | '%cPromise reject:' + (showProvider ? '[' + provider + ']:' : '') + ' ' + ((stack && stack.message) || message || ''), 157 | 'color: red;' 158 | ); 159 | 160 | if (stack && stack.lines.length) { 161 | stack.lines.forEach(function(line) { 162 | console.log('%c' + line.trim(), 'color: red;'); 163 | }); 164 | } else { 165 | console.log('%c', 'color: red;') 166 | } 167 | 168 | console.groupEnd(); 169 | }; 170 | 171 | (function() { 172 | console.log("hello from injected code"); 173 | 174 | 175 | if (typeof backendCode !== 'undefined') { 176 | global.evalBackend(backendCode); 177 | } 178 | }()); 179 | }(this)); -------------------------------------------------------------------------------- /chrome/scripts/commonjs.js: -------------------------------------------------------------------------------- 1 | (function(global) { 2 | var modules = new Map(); 3 | 4 | var LIB_URL = chrome.runtime.getURL('/scripts/'); 5 | var SHARED_URL = chrome.runtime.getURL('/shared/ext_modules/'); 6 | 7 | var loadModule = function(path) { 8 | var xhr = new XMLHttpRequest(); 9 | 10 | xhr.open('GET', path, false); 11 | xhr.send(); 12 | 13 | try { 14 | var response = xhr.response; 15 | } catch (e) {} 16 | 17 | if (!response) return; 18 | 19 | var module = { 20 | exports: {}, 21 | name: path 22 | }; 23 | 24 | response = '"use strict";\n\n' + response; 25 | 26 | var fn = new Function('module', 'exports', response); 27 | fn.call(global, module, module.exports); 28 | 29 | return module; 30 | }; 31 | 32 | global.require = function(path) { 33 | path = path.replace(/(?:\.js)?$/i, '.js'); 34 | 35 | if (!path.indexOf('chrome-extension://')) { 36 | // do nothing 37 | } else if (!path.indexOf('shared/')) { 38 | path = SHARED_URL + path.replace('shared/', ''); 39 | } else if (!path.indexOf('lib/')) { 40 | path = LIB_URL + path.replace('lib/', ''); 41 | } else if (path[0] === '.' || path[0] === '/') { 42 | path = chrome.runtime.getURL(path); 43 | } else { 44 | // relative 45 | path = LIB_URL + path; 46 | } 47 | 48 | if (!path) return null; 49 | 50 | if (modules.has(path)) { 51 | return modules.get(path).exports; 52 | } 53 | 54 | var module = loadModule(path); 55 | 56 | if (!module) return null; 57 | 58 | console.log('[require]:', path); 59 | 60 | // same as firefox sdk does 61 | Object.freeze(module.exports); 62 | modules.set(path, module); 63 | 64 | return module.exports; 65 | }; 66 | }(this)); -------------------------------------------------------------------------------- /chrome/scripts/ext.js: -------------------------------------------------------------------------------- 1 | var getURL = function(path) { 2 | return chrome.runtime.getURL(path); 3 | }; 4 | 5 | exports.getURL = getURL; 6 | 7 | exports.paths = { 8 | 'data/': getURL('data/'), 9 | 'lib/': getURL('scripts/'), 10 | 'shared/': getURL('shared/') 11 | }; 12 | 13 | exports.loadSync = function(url) { 14 | var xhr = new XMLHttpRequest(); 15 | 16 | xhr.open('GET', this.url(url), false); 17 | xhr.send(); 18 | 19 | return xhr.response; 20 | }; 21 | 22 | exports.load = function(url) { 23 | var self = this; 24 | 25 | return new Promise(function(resolve, reject) { 26 | var xhr = new XMLHttpRequest(); 27 | var loaded = false; 28 | 29 | xhr.onload = function() { 30 | loaded = true; 31 | resolve(xhr.response); 32 | }; 33 | 34 | xhr.onloadend = function() { 35 | if (loaded) return; 36 | reject(); 37 | }; 38 | 39 | xhr.open('GET', self.url(url), true); 40 | xhr.send(); 41 | }) 42 | }; -------------------------------------------------------------------------------- /chrome/scripts/main.js: -------------------------------------------------------------------------------- 1 | require('lib/runtime.js'); -------------------------------------------------------------------------------- /chrome/scripts/runtime.js: -------------------------------------------------------------------------------- 1 | var prefs = new (require('shared/prefs.js').Prefs); 2 | var ext = require('shared/ext.js'); 3 | var backend = require('shared/backend.js'); 4 | var global = this; 5 | 6 | prefs.ready.then(function() { 7 | console.log(prefs.getAll()); 8 | }); 9 | 10 | var inspections = new (global.Set || global.WeakSet), 11 | inspectionsMap = {}, 12 | webNavigationWanting = false; 13 | 14 | var onBeforeNavigate = function(details) { 15 | if (details.frameId !== 0) return; 16 | 17 | var inspectData = inspectionsMap[details.tabId]; 18 | if (!inspectData) return; 19 | 20 | attachToTarget(inspectData); 21 | 22 | inspectData = null; 23 | }, 24 | onCommitted = function(details) { 25 | if (details.frameId !== 0) return; 26 | 27 | var inspectData = inspectionsMap[details.tabId]; 28 | if (!inspectData) return; 29 | 30 | attachToTarget(inspectData); 31 | 32 | inspectData.port.postMessage({ 33 | action: 'reload' 34 | }); 35 | 36 | inspectData = null; 37 | }; 38 | 39 | var startWebNavigationWatch = function() { 40 | webNavigationWanting = true; 41 | 42 | chrome.webNavigation.onBeforeNavigate.addListener(onBeforeNavigate); 43 | chrome.webNavigation.onCommitted.addListener(onCommitted); 44 | }, 45 | stopWebNavigationWatch = function() { 46 | webNavigationWanting = false; 47 | 48 | chrome.webNavigation.onBeforeNavigate.removeListener(onBeforeNavigate); 49 | chrome.webNavigation.onCommitted.removeListener(onCommitted); 50 | }, 51 | attachToTarget = function(inspectData, callback) { 52 | chrome.tabs.executeScript(inspectData.tabId, { 53 | code: injectCode, 54 | runAt: 'document_start' 55 | }, function() { 56 | if (callback) { 57 | callback(); 58 | } 59 | }); 60 | }; 61 | 62 | chrome.runtime.onConnect.addListener(function(port) { 63 | if (port.name !== 'DEV_TOOLS') return; 64 | 65 | console.log('port connect'); 66 | 67 | var inspectData = { 68 | port: port 69 | }; 70 | 71 | var messageListener = function(message, port) { 72 | if (!('tabId' in inspectData)) { 73 | handleTabId(message, port); 74 | } 75 | 76 | if (message.action === 'start_watch_page') { 77 | startWatchHandler(message, port); 78 | } 79 | 80 | if (message.action === 'stop_watch_page') { 81 | stopWatchHandler(message, port); 82 | } 83 | 84 | if (message.action === 'prepare_front_end') { 85 | prepareFrontEnd(message, port); 86 | } 87 | }; 88 | 89 | var startWatchHandler = function(message, port) { 90 | if (!webNavigationWanting) { 91 | // first call from dev_tools 92 | // start watching webNavigation 93 | startWebNavigationWatch(); 94 | } 95 | 96 | inspectData.tab.then(function() { 97 | if (message.data.attach) { 98 | attachToTarget(inspectData); 99 | } 100 | 101 | port.postMessage({ 102 | action: 'watch_started' 103 | }); 104 | }); 105 | }, 106 | stopWatchHandler = function(message, port) { 107 | if (webNavigationWanting) { 108 | // first call from dev_tools 109 | // start watching webNavigation 110 | stopWebNavigationWatch(); 111 | } 112 | }, 113 | prepareFrontEnd = function(message, port) { 114 | chrome.tabs.executeScript(inspectData.tabId, { 115 | code: ';(function() {}());', 116 | runAt: 'document_start' 117 | }, function() { 118 | 119 | }); 120 | }, 121 | handleTabId = function(message, port) { 122 | var handleTab = function(tab) { 123 | inspectData.tab = tab; 124 | }, 125 | handleMap = function(id) { 126 | console.log('got tab id'); 127 | 128 | inspectData.tabId = id; 129 | inspectionsMap[id] = inspectData; 130 | }; 131 | 132 | // port.sender.id always exists 133 | // always self extension id? 134 | if (port.sender.tab) { 135 | // debug extension, not sure only self or not 136 | // message.data.tabId is undefined 137 | // but send.tab and port.sender.url exists 138 | handleMap(port.sender.tab.id); 139 | handleTab(Promise.resolve(port.sender.tab)); 140 | } else if (message.data && 'tabId' in message.data) { 141 | // debug web page 142 | // tab object is not exists, only tabId in message.data 143 | // need to get tab object 144 | handleMap(message.data.tabId); 145 | 146 | handleTab(new Promise(function(resolve) { 147 | chrome.tabs.get(message.data.tabId, function(tab) { 148 | resolve(tab); 149 | }); 150 | })); 151 | } 152 | }; 153 | 154 | inspections.add(inspectData); 155 | port.onMessage.addListener(messageListener); 156 | 157 | port.onDisconnect.addListener(function() { 158 | console.log('port disconnect'); 159 | port.onMessage.removeListener(messageListener); 160 | 161 | stopWatchHandler(); 162 | 163 | if (inspectData && inspectData.tabId) { 164 | inspectionsMap[inspectData.tabId] = null; 165 | } 166 | 167 | inspections.delete(inspectData); 168 | }); 169 | }); 170 | 171 | chrome.runtime.onConnect.addListener(function(port) { 172 | if (port.name !== 'FRONT_END__TO__DEV_TOOLS') return; 173 | 174 | var inspectData = inspectionsMap[port.sender.tab.id]; 175 | 176 | // console.log('BOUND TO FRONT_END', inspectData); 177 | if (!inspectData) return; 178 | 179 | 180 | inspectData.port.postMessage({ 181 | action: 'front_end_started' 182 | }); 183 | 184 | var messageListener = function(message) { 185 | // console.log('resend message'); 186 | inspectData.port.postMessage({ 187 | action: 'front_end_event', 188 | data: message 189 | }); 190 | }; 191 | 192 | var disconnectHandler = function() { 193 | port.onMessage.removeListener(messageListener); 194 | port.onDisconnect.removeListener(disconnectHandler); 195 | }; 196 | 197 | inspectData.port.onDisconnect.addListener(function() { 198 | disconnectHandler(); 199 | port.disconnect(); 200 | }); 201 | 202 | port.onMessage.addListener(messageListener); 203 | port.onDisconnect.addListener(function() { 204 | // console.log('FRONT_END__TO__DEV_TOOLS disconnect'); 205 | }); 206 | 207 | port.onDisconnect.addListener(disconnectHandler); 208 | }); 209 | 210 | var debugFrontendCode = ext.loadSync('promises-frontend.js'); 211 | var debugBackendCode = backend.getCode(); 212 | var injectCode = debugBackendCode + debugFrontendCode; -------------------------------------------------------------------------------- /chrome/scripts/storage.js: -------------------------------------------------------------------------------- 1 | var lastError = function() { 2 | return chrome.runtime.lastError; 3 | }; 4 | 5 | module.exports = { 6 | set: function(key, value) { 7 | var pass = {}; 8 | 9 | pass[key] = value; 10 | 11 | return new Promise(function(resolve, reject) { 12 | chrome.storage.local.set(pass, function() { 13 | if (lastError()) { 14 | reject(lastError()); 15 | return; 16 | } 17 | 18 | resolve(); 19 | }); 20 | }); 21 | }, 22 | get: function(key) { 23 | if (typeof key !== 'string') { 24 | return Promise.reject(new TypeError('Bad argument')); 25 | } 26 | 27 | return new Promise(function(resolve, reject) { 28 | chrome.storage.local.get(key, function(items) { 29 | if (lastError()) { 30 | reject(lastError()); 31 | return; 32 | } 33 | 34 | resolve(items[key]); 35 | }); 36 | }); 37 | }, 38 | remove: function(key) { 39 | return new Promise(function(resolve, reject) { 40 | chrome.storage.local.remove(key, function() { 41 | if (lastError()) { 42 | reject(lastError()); 43 | return; 44 | } 45 | 46 | resolve(); 47 | }); 48 | }); 49 | }, 50 | getAll: function() { 51 | return new Promise(function(resolve, reject) { 52 | chrome.storage.get(null, function(items) { 53 | if (lastError()) { 54 | reject(lastError()); 55 | return; 56 | } 57 | 58 | resolve(items); 59 | }); 60 | }); 61 | }, 62 | }; -------------------------------------------------------------------------------- /firefox/data/empty.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NekR/PromisesDebuggerExtension/b95a1f0cf7e2bb26ebb6a605786213e669b06bbb/firefox/data/empty.txt -------------------------------------------------------------------------------- /firefox/lib/ext.js: -------------------------------------------------------------------------------- 1 | const { extRoot } = require('lib/loader'); 2 | const { readURI, readURISync } = require('sdk/net/url'); 3 | 4 | let getURL = function(path) { 5 | if (typeof path !== 'string') { 6 | throw new TypeError('Bad argument'); 7 | } 8 | 9 | if (!path.indexOf('/')) { 10 | return extRoot + path.replace(/^\/+/, ''); 11 | } 12 | 13 | return extRoot + path.replace(/^([\/\.])*/, ''); 14 | }; 15 | 16 | exports.getURL = getURL; 17 | exports.paths = { 18 | 'lib/': getURL('lib/'), 19 | 'data/': getURL('data/'), 20 | 'shared/': getURL('data/shared/') 21 | }; 22 | 23 | exports.loadSync = function(url) { 24 | return readURISync(this.url(url)); 25 | }; 26 | 27 | exports.load = function(url) { 28 | url = this.url(url); 29 | return readURI(url); 30 | }; -------------------------------------------------------------------------------- /firefox/lib/loader.js: -------------------------------------------------------------------------------- 1 | const self = require('sdk/self'); 2 | const { Loader, Require } = require('toolkit/loader'); 3 | 4 | let options = require('@loader/options'); 5 | 6 | let extRoot = options.prefixURI + options.name + '/'; 7 | 8 | options.paths['shared/'] = self.data.url('shared/ext_modules/'); 9 | options.paths['lib/'] = options.paths['./']; 10 | options.paths['data/'] = self.data.url(''); 11 | 12 | let replacemntLoader = Loader(options); 13 | let replacemntRequire = Require(replacemntLoader); 14 | 15 | exports.extRoot = extRoot; 16 | exports.require = replacemntRequire; -------------------------------------------------------------------------------- /firefox/lib/main.js: -------------------------------------------------------------------------------- 1 | const loader = require('loader.js'); 2 | require = loader.require; 3 | 4 | require('lib/runtime.js'); -------------------------------------------------------------------------------- /firefox/lib/runtime.js: -------------------------------------------------------------------------------- 1 | const { Cc, Ci, Cu, Cr } = require("chrome"); 2 | const self = require("sdk/self"); 3 | 4 | const { gDevTools } = Cu.import("resource:///modules/devtools/gDevTools.jsm", {}); 5 | const { DevToolsUtils } = Cu.import("resource://gre/modules/devtools/DevToolsUtils.jsm", {}); 6 | const devtools = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools; 7 | 8 | const events = require("sdk/event/core"); 9 | const { on, once, off, emit } = events; 10 | const { setTimeout, clearTimeout } = require('sdk/timers'); 11 | 12 | const { TargetLogger } = require('lib/target-logger.js'); 13 | const prefs = new (require('shared/prefs.js').Prefs); 14 | const backend = require('shared/backend.js'); 15 | const ext = require('shared/ext.js'); 16 | 17 | prefs.ready.then(function() { 18 | console.log(prefs.getAll()); 19 | }); 20 | 21 | console.log('Start'); 22 | 23 | var getDesc = Object.getOwnPropertyDescriptor; 24 | 25 | var log = { 26 | prefix: '[PromisesDebugger]:', 27 | warn: function(...args) { 28 | args.unshift(this.prefix); 29 | console.warn(...args); 30 | } 31 | }; 32 | 33 | var buildProgressQI = function(obj) { 34 | obj.QueryInterface = function(aIID) { 35 | if ( 36 | aIID.equals(Ci.nsIWebProgressListener) || 37 | aIID.equals(Ci.nsISupportsWeakReference) || 38 | aIID.equals(Ci.nsIXULBrowserWindow) || 39 | aIID.equals(Ci.nsISupports) 40 | ) { 41 | return this; 42 | } 43 | 44 | throw Cr.NS_NOINTERFACE; 45 | } 46 | 47 | return obj; 48 | }; 49 | 50 | var buildProgressListener = function(obj, methods) { 51 | var listener = buildProgressQI({}); 52 | 53 | Object.keys(methods).forEach((key) => { 54 | listener[key] = methods[key].bind(obj); 55 | }); 56 | 57 | return listener; 58 | }; 59 | 60 | let promisesBackendCode = backend.getCode(); 61 | 62 | var PromisesPanel = function(window, toolbox) { 63 | this.toolbox = toolbox; 64 | this.panelWindow = window; 65 | this.target = toolbox.target; 66 | this.webProgress = this.target.tab.linkedBrowser.webProgress; 67 | // this.inspectedWindow = this.webProgress.DOMWindow; 68 | 69 | 70 | this.waitUICommands(); 71 | 72 | if (this.isDebuggerAttached()) { 73 | this.UIAction('show_not_attached'); 74 | } else { 75 | this.waitAttachRequest(); 76 | } 77 | 78 | this.toolbox.loadTool('jsdebugger').then((Debugger) => { 79 | let DebuggerController = Debugger._controller; 80 | let SourceScripts = DebuggerController.SourceScripts; 81 | 82 | SourceScripts.debuggerClient.addListener("newSource", function(e, data) { 83 | let source = data.source; 84 | 85 | if ( 86 | source.addonPath && 87 | source.addonPath.indexOf('promises-debugger-extension') !== -1 88 | ) { 89 | let panelWin = Debugger.panelWin; 90 | let onPaused = () => { 91 | panelWin.off(panelWin.EVENTS.SOURCE_SHOWN, onPaused); 92 | 93 | setTimeout(function() { 94 | SourceScripts.setBlackBoxing(source, true); 95 | }, 100); 96 | }; 97 | 98 | panelWin.on(panelWin.EVENTS.SOURCE_SHOWN, onPaused); 99 | } 100 | }); 101 | }); 102 | 103 | this.logger = this.toolbox.loadTool('webconsole').then(WebConsolePanel => { 104 | let ui = WebConsolePanel.hud.ui; 105 | return new TargetLogger(ui); 106 | }); 107 | }; 108 | 109 | PromisesPanel.prototype = { 110 | get inspectedWindow() { 111 | return this.webProgress.DOMWindow; 112 | }, 113 | isDebuggerAttached: function() { 114 | return !!this.inspectedWindow.__PromisesDebuggerAttached__; 115 | }, 116 | attachToTarget: function() { 117 | if (this.isDebuggerAttached()) return; 118 | 119 | var inspectedWindow = this.inspectedWindow; 120 | 121 | inspectedWindow.__PromisesDebuggerAttached__ = true; 122 | 123 | // here promisesBackendCode must be present 124 | // if it's possible need to 'then' promise and add 125 | // destroy handle to toolbox 126 | if (!promisesBackendCode) { 127 | log.warn(' is not present'); 128 | return; 129 | } 130 | 131 | if (this.target.isLocalTab) { 132 | inspectedWindow.eval(promisesBackendCode); 133 | } else { 134 | this.attachToRemote(promisesBackendCode); 135 | } 136 | }, 137 | attachToRemote: function(promisesBackendCode) { 138 | this.toolbox.loadTool('jsdebugger').then((Debugger) => { 139 | var DebuggerController = Debugger._controller; 140 | 141 | var doEval = () => { 142 | this.toolbox.loadTool('webconsole').then(function(WebConsole) { 143 | WebConsole.hud.jsterm.requestEvaluation(promisesBackendCode).then(function() { 144 | console.log('zzz', arguments); 145 | DebuggerController.activeThread.resume(function() { 146 | console.log('resume', arguments); 147 | }); 148 | }, function() { 149 | console.log('xxx', arguments); 150 | }); 151 | }); 152 | }; 153 | 154 | if (DebuggerController.activeThread.state === 'paused') { 155 | doEval(); 156 | } else { 157 | DebuggerController.activeThread.interrupt(function(res) { 158 | console.log('interrupt:', res); 159 | 160 | doEval() 161 | }); 162 | } 163 | }); 164 | }, 165 | startWaitReloads: function() { 166 | if (!this.target.isLocalTab) { 167 | this.target.on('will-navigate', () => { 168 | this.onReload(); 169 | }); 170 | 171 | return; 172 | } 173 | 174 | let webProgress = this.webProgress; 175 | 176 | // not really need buildListener with arrow functions 177 | this.progressListener = buildProgressListener(this, { 178 | onLocationChange: (aWebProgress, aRequest, aLocationURI, aFlags) => { 179 | if (aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT | 180 | aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_ERROR_PAGE) { 181 | return; 182 | } 183 | 184 | this.onReload() 185 | } 186 | }); 187 | 188 | webProgress.addProgressListener( 189 | this.progressListener, 190 | webProgress.NOTIFY_STATE_WINDOW | webProgress.NOTIFY_LOCATION 191 | ); 192 | }, 193 | stopWaitReloads: function() { 194 | let webProgress = this.webProgress; 195 | 196 | if (this.progressListener) { 197 | webProgress.removeProgressListener(this.progressListener); 198 | this.progressListener = null; 199 | } 200 | }, 201 | startWatchBackend: function() { 202 | this.backendWatchListener = (e) => { 203 | var data = e.data; 204 | 205 | if (!data || !data.PromisesDebugger) return; 206 | 207 | if (data.method === 'requestUpdate') { 208 | // console.log('PromisesDebugger:UpdateData'); 209 | 210 | this.UIAction('update_data', data.message); 211 | } else if (data.method === 'reportError') { 212 | this.reportError(data.message); 213 | } 214 | }; 215 | 216 | this.inspectedWindow.addEventListener('message', this.backendWatchListener); 217 | }, 218 | stopWatchBackend: function() { 219 | this.inspectedWindow.removeEventListener('message', this.backendWatchListener); 220 | }, 221 | UIAction: function(action, message) { 222 | this.panelWindow.postMessage({ 223 | action: action, 224 | message: message 225 | }, '*'); 226 | }, 227 | waitAttachRequest: function() { 228 | // XXX change to show_need_attach 229 | this.UIAction('show_need_reload'); 230 | }, 231 | waitUICommands: function() { 232 | var serviceActions = { 233 | attach: () => { 234 | this.startWaitReloads(); 235 | 236 | this.onReload(); 237 | }, 238 | reload_and_attach: () => { 239 | this.startWaitReloads(); 240 | 241 | this.inspectedWindow.location.reload(); 242 | }, 243 | open_resource: (message) => { 244 | var toolbox = this.toolbox; 245 | const WAIT_LIMIT = 5000; 246 | 247 | toolbox.selectTool('jsdebugger').then(function(Debugger) { 248 | let perform = () => { 249 | let DebuggerView = Debugger._view; 250 | let waiting = 0; 251 | 252 | if (DebuggerView.Sources.containsValue(message.file)) { 253 | DebuggerView.setEditorLocation(message.file, +message.line, { 254 | align: 'center', 255 | charOffset: +message.col 256 | }); 257 | } else if (waiting < WAIT_LIMIT) { 258 | waiting += 70; 259 | setTimeout(perform, 70); 260 | } 261 | }; 262 | 263 | if (Debugger.isReady) { 264 | perform(); 265 | } else { 266 | toolbox.on('jsdebugger-ready', perform); 267 | } 268 | }); 269 | }, 270 | detach: () => { 271 | this.stopWaitReloads(); 272 | this.stopWatchBackend(); 273 | } 274 | }; 275 | 276 | this.panelWindow.addEventListener('message', function(e) { 277 | var data = e.data; 278 | 279 | // console.log('got service action', data); 280 | 281 | if (data && data.serviceAction && 282 | serviceActions.hasOwnProperty(data.serviceAction) 283 | ) { 284 | serviceActions[data.serviceAction](data.message); 285 | } 286 | }); 287 | }, 288 | reportError: function(data) { 289 | this.logger.then((logger) => { 290 | logger.promiseError(data); 291 | }); 292 | }, 293 | 294 | onReload: function() { 295 | this.UIAction('show_main'); 296 | this.UIAction('reload'); 297 | 298 | this.attachToTarget(); 299 | this.startWatchBackend(); 300 | }, 301 | 302 | get target() { 303 | return this.toolbox.target; 304 | }, 305 | destroy: function() { 306 | this.stopWaitReloads(); 307 | this.stopWatchBackend(); 308 | } 309 | }; 310 | 311 | /*gDevTools.on('promises-destroy', function() { 312 | console.log('promises-destroy'); 313 | });*/ 314 | 315 | gDevTools.registerTool({ 316 | id: 'promises', 317 | url: self.data.url('shared/promises-panel.html'), 318 | label: 'Promises', 319 | tooltip: 'Promises Debugger', 320 | icon: ext.getURL('data/shared/images/promises.png'), 321 | isTargetSupported: target => target.isLocalTab, 322 | build: (window, toolbox) => { 323 | /*toolbox.on('destroy', function() { 324 | console.log('toolbox destroy'); 325 | });*/ 326 | 327 | return new PromisesPanel(window, toolbox); 328 | } 329 | }); 330 | 331 | // DebuggerView.setEditorLocation(where.url, where.line); 332 | // selectFrame: function(aDepth) { 333 | // DebuggerView.updateEditor 334 | // Debugger._view.setEditorLocation(); -------------------------------------------------------------------------------- /firefox/lib/storage.js: -------------------------------------------------------------------------------- 1 | var storage = require('sdk/simple-storage').storage; 2 | 3 | module.exports = { 4 | set: function(key, value) { 5 | try { 6 | storage[key] = value; 7 | return Promise.resolve(); 8 | } catch (e) { 9 | return Promise.reejct(e); 10 | } 11 | }, 12 | get: function(key) { 13 | try { 14 | var val = storage[key]; 15 | return Promise.resolve(val); 16 | } catch (e) { 17 | return Promise.reejct(e); 18 | } 19 | }, 20 | remove: function(key) { 21 | try { 22 | delete storage[key]; 23 | return Promise.resolve(); 24 | } catch (e) { 25 | return Promise.reejct(); 26 | } 27 | }, 28 | getAll: function() { 29 | return Promise.resolve(storage); 30 | } 31 | }; -------------------------------------------------------------------------------- /firefox/lib/target-logger.js: -------------------------------------------------------------------------------- 1 | const XHTML_NS = "http://www.w3.org/1999/xhtml"; 2 | const isChrome = false; 3 | 4 | var handleEverStack = function(stack) { 5 | var lines = stack.split(/(?:\n+|\->)/), 6 | line, 7 | i = 0, 8 | newLines = [], 9 | firstLine = lines[0], 10 | message; 11 | 12 | if (isChrome && firstLine && 13 | firstLine.search(/\bat\b/) === -1 && firstLine.search(/error/i) !== -1) { 14 | message = firstLine; 15 | lines = lines.slice(1); 16 | } 17 | 18 | if (true) { 19 | while (i < lines.length) { 20 | line = lines[i]; 21 | ++i; 22 | 23 | if ( 24 | line && ( 25 | line.indexOf('(native)') !== -1 || 26 | line.indexOf('(:') !== -1 || 27 | line.indexOf('resource://') !== -1 || 28 | line.indexOf('jar:file://') !== -1 29 | ) 30 | ) { 31 | continue; 32 | } 33 | 34 | if (line) { 35 | newLines.push(line); 36 | } 37 | } 38 | } else { 39 | newLines = lines; 40 | } 41 | 42 | if (!newLines.length) { 43 | return null; 44 | } 45 | 46 | return { 47 | lines: newLines, 48 | message: message 49 | }; 50 | }; 51 | 52 | var makeMessage = function(document, data) { 53 | let value = data.error.value; 54 | let provider = data.provider; 55 | let name = value.name; 56 | let title = 'Promise reject: ' + (name ? name + ': ' : '') + 57 | (value.message || ''); 58 | 59 | let messageNode = document.createElementNS(XHTML_NS, 'div'); 60 | 61 | messageNode.style.display = 'block'; 62 | messageNode.style.color = '#CD2929'; 63 | 64 | let groupTitle = makeGroupTitle(document, title); 65 | messageNode.appendChild(groupTitle); 66 | 67 | let groupContainer = makeGroupContainer(document); 68 | messageNode.appendChild(groupContainer); 69 | 70 | groupTitle.addEventListener('click', function() { 71 | groupContainer.hidden = !groupContainer.hidden; 72 | 73 | var display = groupTitle.cornerOpened.style.display === 'inline-block' ? 74 | 'none' : 'inline-block'; 75 | 76 | groupTitle.cornerClosed.style.display = 77 | (groupTitle.cornerOpened.style.display = display) === 'inline-block' ? 78 | 'none' : 'inline-block'; 79 | }); 80 | 81 | let stack = value.stack ? handleEverStack(value.stack) : null; 82 | 83 | if (stack && stack.lines.length) { 84 | stack.lines.forEach(function(line) { 85 | let lineNode = makeLogItem(document, line); 86 | 87 | groupContainer.appendChild(lineNode); 88 | }); 89 | } else { 90 | let lineNode = makeLogItem(document, ''); 91 | 92 | groupContainer.appendChild(lineNode); 93 | } 94 | 95 | return messageNode; 96 | }, 97 | makeGroupTitle = function(document, title) { 98 | let groupTitle = document.createElementNS(XHTML_NS, 'div'); 99 | 100 | // groupTitle.style.position = 'relative'; 101 | groupTitle.style.cursor = 'pointer'; 102 | 103 | let cornerClosed = document.createElementNS(XHTML_NS, 'span'); 104 | 105 | cornerClosed.style.display = 'inline-block'; 106 | cornerClosed.style.width = 0; 107 | cornerClosed.style.height = 0; 108 | cornerClosed.style.borderTop = '4px solid transparent' 109 | cornerClosed.style.borderBottom = '4px solid transparent' 110 | cornerClosed.style.borderLeft = '6px solid #777'; 111 | cornerClosed.style.verticalAlign = 'middle'; 112 | cornerClosed.style.margin = '0 10px 0 0'; 113 | 114 | groupTitle.appendChild(cornerClosed); 115 | 116 | let cornerOpened = document.createElementNS(XHTML_NS, 'span'); 117 | 118 | cornerOpened.style.display = 'none'; 119 | cornerOpened.style.width = 0; 120 | cornerOpened.style.height = 0; 121 | cornerOpened.style.borderLeft = '4px solid transparent' 122 | cornerOpened.style.borderRight = '4px solid transparent' 123 | cornerOpened.style.borderTop = '6px solid #777'; 124 | cornerOpened.style.verticalAlign = 'middle'; 125 | cornerOpened.style.margin = '1px 8px 1px 0'; 126 | 127 | groupTitle.appendChild(cornerOpened); 128 | 129 | groupTitle.cornerOpened = cornerOpened; 130 | groupTitle.cornerClosed = cornerClosed; 131 | 132 | let text = document.createElementNS(XHTML_NS, 'span'); 133 | 134 | text.textContent = title; 135 | text.style.fontWeight = 'bold'; 136 | text.style.verticalAlign = 'middle'; 137 | 138 | groupTitle.appendChild(text); 139 | 140 | return groupTitle; 141 | }, 142 | makeGroupContainer = function(document) { 143 | let groupContainer = document.createElementNS(XHTML_NS, 'div'); 144 | 145 | groupContainer.hidden = true; 146 | groupContainer.style.margin = '5px 0 5px 15px'; 147 | groupContainer.style.paddingLeft = '14px'; 148 | groupContainer.style.borderLeft = '1px solid #777'; 149 | 150 | return groupContainer; 151 | }, 152 | makeLogItem = function(document, text) { 153 | let logItem = document.createElementNS(XHTML_NS, 'div'); 154 | 155 | logItem.textContent = text; 156 | logItem.style.margin = '5px 0'; 157 | 158 | return logItem; 159 | }; 160 | 161 | exports.Logger = function(webConsoleUI) { 162 | this.ui = webConsoleUI; 163 | }; 164 | 165 | exports.Logger.prototype = { 166 | promiseError: function(data) { 167 | let ui = this.ui; 168 | let message = makeMessage(ui.document, data); 169 | 170 | message = ui.createMessageNode(3, 0, message, '', 0, ''); 171 | ui.outputMessage(3, message); 172 | } 173 | }; -------------------------------------------------------------------------------- /firefox/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "promises-debugger-extension", 3 | "title": "Promises Debugger", 4 | "id": "jid1-2kvMqwPuHcvUDw", 5 | "description": "", 6 | "author": "", 7 | "license": "MPL 2.0", 8 | "version": "0.1" 9 | } 10 | -------------------------------------------------------------------------------- /firefox/test/test-main.js: -------------------------------------------------------------------------------- 1 | var main = require("./main"); 2 | 3 | exports["test main"] = function(assert) { 4 | assert.pass("Unit test running!"); 5 | }; 6 | 7 | exports["test main async"] = function(assert, done) { 8 | assert.pass("async Unit test running!"); 9 | done(); 10 | }; 11 | 12 | require("sdk/test").run(exports); 13 | -------------------------------------------------------------------------------- /init.bat: -------------------------------------------------------------------------------- 1 | rmdir .\chrome\shared 2 | rmdir .\firefox\data\shared 3 | 4 | cd .\chrome 5 | mklink /D .\shared ..\shared 6 | cd .. 7 | cd .\firefox\data 8 | mklink /D .\shared ..\..\shared 9 | cd ..\.. -------------------------------------------------------------------------------- /init.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | unlink ./chrome/shared 4 | unlink ./firefox/data/shared 5 | 6 | ln -s ./shared ./chrome/shared 7 | ln -s ./shared ./firefox/data/shared -------------------------------------------------------------------------------- /shared/bootstrap.js: -------------------------------------------------------------------------------- 1 | modules.ready(function() { 2 | modules.require('parser').parse(document.body); 3 | }); 4 | 5 | modules.load({ 6 | version: 1, 7 | deps: [ 8 | 'fw/lib/sync.js', 9 | 'fw/cui/modules/lib.js', 10 | 'fw/cui/modules/events.js', 11 | 'fw/cui/modules/utils.js', 12 | 'fw/cui/modules/ui.js', 13 | 'fw/cui/modules/parser.js', 14 | 15 | 'fw/modules/panel.js', 16 | 'fw/modules/checkbox.js', 17 | 'fw/modules/settings.js', 18 | 'fw/modules/main.js' 19 | ], 20 | url: 'fw/cui/modules.js', 21 | path: './', 22 | debug: false, 23 | packaged: false, 24 | webapp: false, 25 | cache: false 26 | }); -------------------------------------------------------------------------------- /shared/css/style.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | font-size: 15px; 4 | } 5 | 6 | body { 7 | margin: 0; 8 | width: 100%; 9 | height: 100%; 10 | font-family: "Open Sans", "Helvetica Neue", "Helvetica", "Arial", sans-serif; 11 | overflow-y: scroll; 12 | background: #f0f0f0; 13 | } 14 | 15 | #promises-table { 16 | /*table-layout: fixed;*/ 17 | } 18 | 19 | #head { 20 | position: fixed; 21 | top: 0; 22 | left: 0; 23 | right: 0; 24 | z-index: 100; 25 | } 26 | 27 | #content { 28 | padding-top: 74px; 29 | } 30 | 31 | #need-reload { 32 | padding-top: 74px; 33 | } 34 | 35 | .attach-toggle { 36 | display: inline-block; 37 | height: 20px; 38 | width: 40px; 39 | background: rgb(196, 196, 196); 40 | vertical-align: middle; 41 | margin-left: 20px; 42 | border-radius: 10px; 43 | cursor: pointer; 44 | box-shadow: inset 0 0 5px 0px rgba(0, 0, 0, 0.3); 45 | } 46 | 47 | @-moz-document url-prefix() { 48 | .attach-toggle { 49 | box-shadow: inset 0 0 5px -1px rgba(0, 0, 0, 0.3); 50 | } 51 | } 52 | 53 | .attach-toggle[selected] .attach-toggle-ball { 54 | transform: translateX(0); 55 | background-color: rgb(211, 89, 89); 56 | box-shadow: 0 0 1px 1px rgba(0, 0, 0, 0.3); 57 | } 58 | 59 | .attach-toggle-ball { 60 | width: 20px; 61 | height: 20px; 62 | border-radius: 50%; 63 | 64 | transform: translateX(20px); 65 | background-color: rgb(211, 210, 210); 66 | box-shadow: 0 0 3px 0px rgba(0, 0, 0, 0.3); 67 | 68 | transition: background-color 0.3s, transform 0.3s; 69 | } 70 | 71 | .pd-table-cell { 72 | cursor: default; 73 | } 74 | 75 | .pd-table-cell-header { 76 | background-color: #f0f0f0 !important; 77 | } 78 | 79 | .pd-show-error { 80 | cursor: pointer; 81 | width: 100%; 82 | } 83 | 84 | .cell-id { 85 | text-align: center; 86 | } 87 | 88 | .resource-link { 89 | font-size: 13px; 90 | color: #333; 91 | text-decoration: underline; 92 | } 93 | 94 | .primitive-value { 95 | word-wrap: nowrap; 96 | overflow-wrap: nowrap; 97 | white-space: nowrap; 98 | overflow: hidden; 99 | text-overflow: ellipsis; 100 | } 101 | 102 | .keys-value { 103 | max-height: 100px; 104 | overflow: hidden; 105 | } 106 | 107 | 108 | .pd-promise-chain { 109 | border-left: 1px solid black; 110 | padding-left: 10px; 111 | margin-top: 10px; 112 | 113 | } 114 | 115 | .pd-promise-chain-item { 116 | position: relative; 117 | } 118 | 119 | .pd-promise-chain-item::before { 120 | content: ''; 121 | height: 1px; 122 | background-color: black; 123 | position: absolute; 124 | top: 50%; 125 | left: -10px; 126 | width: 10px; 127 | margin-top: -0.5px; 128 | } 129 | 130 | 131 | .pd-table-body { 132 | margin-bottom: 10px; 133 | } 134 | 135 | .tbody-pending .pd-table-cell { 136 | // background-color: #fafafa; 137 | background-color: white; 138 | } 139 | 140 | .pd-table-extend .ui.segment, 141 | .pd-table-extend .ui.message { 142 | background-color: #fafafa; 143 | box-shadow: inset 0px 0px 0px 1px rgba(0, 0, 0, 0.1); 144 | border-radius: 0; 145 | } 146 | 147 | .pd-header-icons { 148 | height: 20px; 149 | float: right; 150 | margin-top: 2px; 151 | } 152 | 153 | .pd-header-icons .icon { 154 | height: 20px; 155 | font-size: 19px; 156 | line-height: 19px; 157 | padding: 0 !important; 158 | margin: 0 !important; 159 | display: inline-block !important; 160 | margin-left: 10px !important; 161 | width: 30px !important; 162 | padding-left: 10px !important; 163 | border-left: 1px solid #ccc; 164 | } 165 | 166 | .pd-main-header { 167 | z-index: 10; 168 | border-bottom: 1px solid #cacaca !important; 169 | line-height: 1.40 !important; 170 | } 171 | 172 | [data-control="checkbox"][data-checked="true"] { 173 | color: green; 174 | } 175 | 176 | 177 | /* ####################### Table ###################### */ 178 | 179 | 180 | /* Prototype */ 181 | .pd-table { 182 | width: 100%; 183 | border-collapse: collapse; 184 | } 185 | 186 | /* Table Content */ 187 | .pd-table-cell, 188 | .pd-table-cell-header { 189 | border-collapse: collapse; 190 | -webkit-box-sizing: border-box; 191 | -moz-box-sizing: border-box; 192 | -ms-box-sizing: border-box; 193 | box-sizing: border-box; 194 | -webkit-transition: all 0.1s ease-out; 195 | -moz-transition: all 0.1s ease-out; 196 | transition: all 0.1s ease-out; 197 | 198 | flex-grow: 2; 199 | width: 25%; 200 | display: flex; 201 | align-items: center; 202 | } 203 | 204 | .pd-table-cell:nth-child(1), 205 | .pd-table-cell-header:nth-child(1) { 206 | width: 50px; 207 | /*padding-right: 0; 208 | padding-left: 0;*/ 209 | justify-content: center; 210 | } 211 | 212 | .pd-table-cell:nth-child(2), 213 | .pd-table-cell-header:nth-child(2) { 214 | flex-grow: 3; 215 | width: 40%; 216 | } 217 | 218 | .pd-table-cell:nth-child(3), 219 | .pd-table-cell-header:nth-child(3) { 220 | flex-grow: 1; 221 | width: 15%; 222 | justify-content: center; 223 | } 224 | 225 | .pd-table-cell:nth-child(4), 226 | .pd-table-cell-header:nth-child(4) { 227 | flex-grow: 4; 228 | width: 40%; 229 | } 230 | 231 | .pd-table-cell:nth-child(5), 232 | .pd-table-cell-header:nth-child(5) { 233 | width: 70px; 234 | /*padding-right: 0; 235 | padding-left: 0;*/ 236 | justify-content: center; 237 | } 238 | 239 | /* Headers */ 240 | .pd-table-head { 241 | // border-bottom: 1px solid rgba(0, 0, 0, 0.03); 242 | } 243 | 244 | .pd-table-foot .pd-table-cell { 245 | background-color: rgba(0, 0, 0, 0.03); 246 | } 247 | 248 | .pd-table-cell-header { 249 | cursor: auto; 250 | background-color: rgba(0, 0, 0, 0.05); 251 | text-align: left; 252 | color: rgba(0, 0, 0, 0.8); 253 | padding: 0.5em 0.7em; 254 | vertical-align: middle; 255 | font-weight: bold; 256 | } 257 | 258 | .pd-dialog { 259 | background-color: rgba(0, 0, 0, 0.4); 260 | position: fixed; 261 | top: 0; 262 | left: 0; 263 | height: 100%; 264 | width: 100%; 265 | z-index: 1000; 266 | } 267 | 268 | .pd-dialog .ui.modal { 269 | display: initial !important; 270 | top: 50px !important; 271 | box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.5) !important; 272 | } 273 | 274 | .pd-branches { 275 | margin-bottom: 0 !important; 276 | border-radius: 0 !important; 277 | box-shadow: inset 0px 0px 0px 1px rgba(0, 0, 0, 0.1) !important; 278 | background: #FFF !important; 279 | } 280 | 281 | .pd-branches-title { 282 | width: auto !important; 283 | float: right !important; 284 | color: rgba(0, 0, 0, 0.4) !important; 285 | } 286 | 287 | .pd-branch-item { 288 | width: 40px; 289 | text-align: center; 290 | } 291 | 292 | .pd-branch-item::before { 293 | display: block !important; 294 | left: auto !important; 295 | right: 0px !important; 296 | } 297 | 298 | /*.pd-table-head .pd-table-cell-header:first-child { 299 | border-radius: 5px 0px 0px 0px; 300 | } 301 | 302 | .pd-table-head .pd-table-cell-header:last-child { 303 | border-radius: 0px 5px 0px 0px; 304 | } 305 | 306 | .pd-table-foot .pd-table-cell-header:first-child { 307 | border-radius: 0px 0px 0px 5px; 308 | } 309 | 310 | .pd-table-foot .pd-table-cell-header:last-child { 311 | border-radius: 0px 0px 5px 0px; 312 | } 313 | 314 | .pd-table-foot .pd-table-cell-header:only-child { 315 | border-radius: 0px 0px 5px 5px; 316 | }*/ 317 | /* Table Cells */ 318 | .pd-table-cell { 319 | padding: 0.40em 0.7em; 320 | vertical-align: middle; 321 | } 322 | /* Footer */ 323 | .pd-table-foot { 324 | border-top: 1px solid rgba(0, 0, 0, 0.03); 325 | } 326 | .pd-table-foot .pd-table-cell-header { 327 | font-weight: normal; 328 | font-style: italic; 329 | } 330 | /* Table Striping */ 331 | .pd-table .pd-table-body .pd-table-cell-header:nth-child(2n) { 332 | background-color: rgba(0, 0, 50, 0.02); 333 | } 334 | /* Icons */ 335 | .pd-table .icon { 336 | vertical-align: baseline; 337 | } 338 | .pd-table .icon:only-child { 339 | margin: 0em; 340 | } 341 | /* Table Segment */ 342 | .pd-table.segment:after { 343 | display: none; 344 | } 345 | .pd-table.segment.stacked:after { 346 | display: block; 347 | } 348 | 349 | /******************************* 350 | States 351 | *******************************/ 352 | /*-------------- 353 | Hover 354 | ---------------*/ 355 | /* Sortable */ 356 | .pd-table.sortable .pd-table-cell-header.disabled:hover { 357 | cursor: auto; 358 | text-align: left; 359 | font-weight: bold; 360 | color: #333333; 361 | color: rgba(0, 0, 0, 0.8); 362 | } 363 | .pd-table.sortable .pd-table-head .pd-table-cell-header:hover { 364 | background-color: rgba(0, 0, 0, 0.13); 365 | color: rgba(0, 0, 0, 0.8); 366 | } 367 | /* Inverted Sortable */ 368 | .ui.inverted.sortable.table .pd-table-head .pd-table-cell-header:hover { 369 | background-color: rgba(255, 255, 255, 0.13); 370 | color: #ffffff; 371 | } 372 | /*-------------- 373 | Positive 374 | ---------------*/ 375 | .pd-table .pd-table-row-cont.positive, 376 | .pd-table .pd-table-cell.positive { 377 | -webkit-box-shadow: 2px 0px 0px #119000 inset; 378 | box-shadow: 2px 0px 0px #119000 inset; 379 | } 380 | .pd-table .pd-table-row-cont.positive .pd-table-cell, 381 | .pd-table .pd-table-cell.positive { 382 | background-color: #F2F8F0 !important; 383 | color: #119000 !important; 384 | } 385 | .pd-table.celled .pd-table-row-cont.positive:hover .pd-table-cell, 386 | .pd-table.celled .pd-table-row-cont:hover .pd-table-cell.positive, 387 | .pd-table .pd-table-row-cont.positive:hover .pd-table-cell, 388 | .pd-table .pd-table-cell:hover.positive, 389 | .pd-table .pd-table-cell-header:hover.positive { 390 | background-color: #ECF5E9 !important; 391 | color: #119000 !important; 392 | } 393 | /*-------------- 394 | Negative 395 | ---------------*/ 396 | .pd-table .pd-table-row-cont.negative, 397 | .pd-table .pd-table-cell.negative { 398 | -webkit-box-shadow: 2px 0px 0px #CD2929 inset; 399 | box-shadow: 2px 0px 0px #CD2929 inset; 400 | } 401 | .pd-table .pd-table-row-cont.negative .pd-table-cell, 402 | .pd-table .pd-table-cell.negative { 403 | background-color: #F9F4F4; 404 | color: #CD2929 !important; 405 | } 406 | .pd-table.celled .pd-table-row-cont.negative:hover .pd-table-cell, 407 | .pd-table.celled .pd-table-row-cont:hover .pd-table-cell.negative, 408 | .pd-table .pd-table-row-cont.negative:hover .pd-table-cell, 409 | .pd-table .pd-table-cell:hover.negative, 410 | .pd-table .pd-table-cell-header:hover.negative { 411 | background-color: #F2E8E8; 412 | color: #CD2929; 413 | } 414 | /*-------------- 415 | Error 416 | ---------------*/ 417 | .pd-table .pd-table-row-cont.error, 418 | .pd-table .pd-table-cell.error { 419 | -webkit-box-shadow: 2px 0px 0px #CD2929 inset; 420 | box-shadow: 2px 0px 0px #CD2929 inset; 421 | } 422 | .pd-table .pd-table-row-cont.error .pd-table-cell, 423 | .pd-table .pd-table-cell.error, 424 | .pd-table .pd-table-cell-header.error { 425 | background-color: #F9F4F4; 426 | color: #CD2929; 427 | } 428 | .pd-table.celled .pd-table-row-cont.error:hover .pd-table-cell, 429 | .pd-table.celled .pd-table-row-cont:hover .pd-table-cell.error, 430 | .pd-table .pd-table-row-cont.error:hover .pd-table-cell, 431 | .pd-table .pd-table-cell:hover.error, 432 | .pd-table .pd-table-cell-header:hover.error { 433 | background-color: #F2E8E8; 434 | color: #CD2929; 435 | } 436 | /*-------------- 437 | Warning 438 | ---------------*/ 439 | .pd-table .pd-table-row-cont.warning, 440 | .pd-table .pd-table-cell.warning { 441 | -webkit-box-shadow: 2px 0px 0px #7D6C00 inset; 442 | box-shadow: 2px 0px 0px #7D6C00 inset; 443 | } 444 | .pd-table .pd-table-row-cont.warning .pd-table-cell, 445 | .pd-table .pd-table-cell.warning, 446 | .pd-table .pd-table-cell-header.warning { 447 | background-color: #FBF6E9; 448 | color: #7D6C00; 449 | } 450 | .pd-table.celled .pd-table-row-cont.warning:hover .pd-table-cell, 451 | .pd-table.celled .pd-table-row-cont:hover .pd-table-cell.warning, 452 | .pd-table .pd-table-row-cont.warning:hover .pd-table-cell, 453 | .pd-table .pd-table-cell:hover.warning, 454 | .pd-table .pd-table-cell-header:hover.warning { 455 | background-color: #F3EDDC; 456 | color: #7D6C00; 457 | } 458 | /*-------------- 459 | Active 460 | ---------------*/ 461 | .pd-table .pd-table-row-cont.active, 462 | .pd-table .pd-table-cell.active { 463 | -webkit-box-shadow: 2px 0px 0px rgba(50, 50, 50, 0.9) inset; 464 | box-shadow: 2px 0px 0px rgba(50, 50, 50, 0.9) inset; 465 | } 466 | .pd-table .pd-table-row-cont.active .pd-table-cell, 467 | .pd-table .pd-table-row-cont .pd-table-cell.active { 468 | background-color: #E0E0E0; 469 | color: rgba(50, 50, 50, 0.9); 470 | /* border-color: rgba(0, 0, 0, 0.15) !important; */ 471 | } 472 | /*-------------- 473 | Disabled 474 | ---------------*/ 475 | .pd-table .pd-table-row-cont.disabled .pd-table-cell, 476 | .pd-table .pd-table-row-cont .pd-table-cell.disabled, 477 | .pd-table .pd-table-row-cont.disabled:hover .pd-table-cell, 478 | .pd-table .pd-table-row-cont:hover .pd-table-cell.disabled { 479 | color: rgba(150, 150, 150, 0.3); 480 | } 481 | /******************************* 482 | Variations 483 | *******************************/ 484 | /*-------------- 485 | Celled 486 | ---------------*/ 487 | .pd-table.celled { 488 | color: rgba(0, 0, 0, 0.8); 489 | } 490 | .pd-table.celled .pd-table-body .pd-table-row-cont, 491 | .pd-table.celled .pd-table-foot .pd-table-row-cont { 492 | border: none; 493 | } 494 | .pd-table.celled .pd-table-cell-header, 495 | .pd-table.celled .pd-table-cell { 496 | border: 1px solid rgba(0, 0, 0, 0.1); 497 | } 498 | /* Coupling with segment */ 499 | .pd-table.celled.segment .pd-table-cell-header:first-child, 500 | .pd-table.celled.segment .pd-table-cell:first-child { 501 | border-left: none; 502 | } 503 | .pd-table.celled.segment .pd-table-cell-header:last-child, 504 | .pd-table.celled.segment .pd-table-cell:last-child { 505 | border-right: none; 506 | } 507 | /*-------------- 508 | Sortable 509 | ---------------*/ 510 | .pd-table.sortable .pd-table-head .pd-table-cell-header { 511 | cursor: pointer; 512 | white-space: nowrap; 513 | } 514 | .pd-table.sortable .pd-table-head .pd-table-cell-header.sorted, 515 | .pd-table.sortable .pd-table-head .pd-table-cell-header.sorted:hover { 516 | -webkit-user-select: none; 517 | -moz-user-select: none; 518 | -ms-user-select: none; 519 | user-select: none; 520 | } 521 | .pd-table.sortable .pd-table-head .pd-table-cell-header:after { 522 | display: inline-block; 523 | content: ''; 524 | width: 1em; 525 | opacity: 0.8; 526 | margin: 0em 0em 0em 0.5em; 527 | font-family: 'Icons'; 528 | font-style: normal; 529 | font-weight: normal; 530 | text-decoration: inherit; 531 | } 532 | .pd-table.sortable .pd-table-head .pd-table-cell-header.ascending:after { 533 | content: '\25b4'; 534 | } 535 | .pd-table.sortable .pd-table-head .pd-table-cell-header.descending:after { 536 | content: '\25be'; 537 | } 538 | /*-------------- 539 | Inverted 540 | ---------------*/ 541 | /* Text Color */ 542 | .pd-table.inverted .pd-table-cell { 543 | color: rgba(255, 255, 255, 0.9); 544 | } 545 | .pd-table.inverted .pd-table-cell-header { 546 | background-color: rgba(0, 0, 0, 0.15); 547 | color: rgba(255, 255, 255, 0.9); 548 | } 549 | /* Stripes */ 550 | .pd-table.inverted .pd-table-body .pd-table-row-cont:nth-child(2n) { 551 | background-color: rgba(255, 255, 255, 0.06); 552 | } 553 | /*-------------- 554 | Definition 555 | ---------------*/ 556 | .pd-table.definition .pd-table-cell:first-child { 557 | font-weight: bold; 558 | } 559 | /*-------------- 560 | Collapsing 561 | ---------------*/ 562 | .pd-table.collapsing { 563 | width: auto; 564 | } 565 | /*-------------- 566 | Basic 567 | ---------------*/ 568 | .pd-table.basic .pd-table-cell-header { 569 | background-color: transparent; 570 | padding: 0.5em; 571 | } 572 | .pd-table.basic .pd-table-body .pd-table-row-cont { 573 | border-bottom: 1px solid rgba(0, 0, 0, 0.03); 574 | } 575 | .pd-table.basic .pd-table-cell { 576 | padding: 0.8em 0.5em; 577 | } 578 | .pd-table.basic .pd-table-body .pd-table-row-cont:nth-child(2n) { 579 | background-color: transparent !important; 580 | } 581 | /*-------------- 582 | Padded 583 | ---------------*/ 584 | .pd-table.padded .pd-table-cell-header, 585 | .pd-table.padded .pd-table-cell { 586 | padding: 0.8em 1em; 587 | } 588 | .pd-table.compact .pd-table-cell-header { 589 | padding: 0.3em 0.5em; 590 | } 591 | .pd-table.compact .pd-table-cell { 592 | padding: 0.2em 0.5em; 593 | } 594 | /*-------------- 595 | Sizes 596 | ---------------*/ 597 | /* Small */ 598 | .pd-table.small { 599 | font-size: 0.875em; 600 | } 601 | /* Standard */ 602 | .pd-table { 603 | font-size: 1em; 604 | } 605 | /* Large */ 606 | .pd-table.large { 607 | font-size: 1.1em; 608 | } 609 | 610 | .pd-table-row-cont { 611 | display: flex; 612 | } -------------------------------------------------------------------------------- /shared/ext_modules/backend.js: -------------------------------------------------------------------------------- 1 | var ext = require('shared/ext.js'); 2 | var providers = [ 3 | 'es6' 4 | ]; 5 | var backendCode; 6 | 7 | exports.providers = providers; 8 | exports.getCode = function() { 9 | if (backendCode) return backendCode; 10 | 11 | var debugBackendCode = ext.loadSync('shared/promises-backend.js'), 12 | debugProvidersCode = ''; 13 | 14 | providers.forEach(function(provider) { 15 | var code = ext.loadSync('shared/providers/' + provider + '.js'); 16 | 17 | debugProvidersCode += ';(function(PromisesDebugger, global) {' + 18 | code + '}(PromisesDebugger, this));'; 19 | }); 20 | 21 | debugBackendCode = debugBackendCode + debugProvidersCode; 22 | backendCode = ';var backendCode = ' + JSON.stringify(debugBackendCode) + ';'; 23 | 24 | return backendCode; 25 | }; -------------------------------------------------------------------------------- /shared/ext_modules/ext.js: -------------------------------------------------------------------------------- 1 | var ext = require('lib/ext'); 2 | var keys = Object.keys(ext.paths).sort(); 3 | 4 | var extUrl = function(url) { 5 | if (typeof url !== 'string') { 6 | throw new TypeError('Bad argument'); 7 | } 8 | 9 | var pathFound = keys.some(function(path) { 10 | if (!url.indexOf(path)) { 11 | var prefix = ext.paths[path]; 12 | 13 | url = prefix + url.slice(path.length); 14 | return true; 15 | } 16 | }); 17 | 18 | if (pathFound) { 19 | return url; 20 | } 21 | 22 | return ext.getURL(url); 23 | }; 24 | 25 | module.exports = { 26 | url: extUrl, 27 | load: ext.load, 28 | loadSync: ext.loadSync, 29 | paths: ext.paths, 30 | getURL: ext.getURL 31 | }; -------------------------------------------------------------------------------- /shared/ext_modules/prefs.js: -------------------------------------------------------------------------------- 1 | var storage = require('shared/storage.js'); 2 | var ext = require('shared/ext.js'); 3 | 4 | var PREFS_KEY = 'prefs'; 5 | var PREFS_FILE = 'shared/prefs.json'; 6 | 7 | exports.Prefs = function(url) { 8 | this.url = url || PREFS_FILE; 9 | this.init(); 10 | }; 11 | 12 | exports.Prefs.prototype = { 13 | _prefs: null, 14 | init: function() { 15 | var self = this; 16 | 17 | this.ready = storage.get(PREFS_KEY).then(function(prefs) { 18 | if (!prefs) return self.load(); 19 | 20 | return prefs; 21 | }, function(err) { 22 | // do error log here 23 | 24 | return self.load(); 25 | }).then(function(prefs) { 26 | self.local = prefs; 27 | }); 28 | }, 29 | get: function(key) { 30 | return this.local[key]; 31 | }, 32 | set: function(key, val) { 33 | this.local[key] = val; 34 | 35 | storage.set(PREFS_KEY, this.local); 36 | }, 37 | getAll: function() { 38 | return this.local; 39 | }, 40 | load: function() { 41 | return ext.load(this.url).then(function(res) { 42 | if (!res) return null; 43 | 44 | res = res.replace(/\/\/([\s\S]+?)$/mg, '').trim(); 45 | 46 | return JSON.parse(res); 47 | }); 48 | } 49 | }; -------------------------------------------------------------------------------- /shared/ext_modules/storage.js: -------------------------------------------------------------------------------- 1 | module.exports = require('lib/storage'); -------------------------------------------------------------------------------- /shared/fw/cui/loader.debug.js: -------------------------------------------------------------------------------- 1 | (function(win) { 2 | var LOADER_VERSION = 'LOADER_VERSION', 3 | LOADER_MODULES = 'LOADER_MODULES', 4 | LOCAL_STORAGE = 'localStorage', 5 | GET_ITEM = 'getItem', 6 | FUNCTION = 'Function', 7 | DOCUMENT = 'document', 8 | SET_TIMEOUT = 'setTimeout', 9 | ASYNC = 'async', 10 | PUSH = 'push', 11 | SCRIPT_TAG = 'script', 12 | LOADER = 'loader', 13 | R_EVAL = /\$\{([^\=\&^\}]*?)\}/gi; 14 | 15 | var domReady = [], 16 | doc = win[DOCUMENT], 17 | domReadyHandler = function() { 18 | for (var i = 0, len = domReady.length; i < len; i++) { 19 | win[SET_TIMEOUT](domReady[i], 1); 20 | } 21 | 22 | domReady = 1; 23 | }; 24 | 25 | var modules = win.modules = { 26 | dom: function(fn) { 27 | if (domReady === 1) { 28 | win[SET_TIMEOUT](fn, 1); 29 | } else { 30 | domReady[PUSH](fn); 31 | } 32 | }, 33 | ready: function(fn) { 34 | modules.readyList[PUSH](fn); 35 | }, 36 | load: function(loader) { 37 | loader.debug = true; 38 | modules.loader = loader; 39 | 40 | if ((win[LOCAL_STORAGE][GET_ITEM](LOADER_VERSION) | 0) === loader.version) { 41 | domReady[PUSH](new win[FUNCTION](win[LOCAL_STORAGE][GET_ITEM](LOADER_MODULES))); 42 | } else { 43 | var url = loader.url.replace(R_EVAL, function(input, key) { 44 | return (new Function(LOADER, 'with(' + LOADER + ') {(' + loader[key] + ')}'))(loader); 45 | }); 46 | 47 | doc.write(''); 48 | } 49 | }, 50 | readyList: [], 51 | LOADER_VERSION: LOADER_VERSION, 52 | LOADER_MODULES: LOADER_MODULES, 53 | PACKAGED_CALLBACK: '_PCB' + +(new Date), // Packaged CallBack 54 | R_EVAL: R_EVAL 55 | }; 56 | 57 | doc.addEventListener('DOMContentLoaded', domReadyHandler, !1); 58 | }(this)); -------------------------------------------------------------------------------- /shared/fw/cui/loader.js: -------------------------------------------------------------------------------- 1 | (function(win) { 2 | var LOADER_VERSION = 'LOADER_VERSION', 3 | LOADER_MODULES = 'LOADER_MODULES', 4 | LOCAL_STORAGE = 'localStorage', 5 | GET_ITEM = 'getItem', 6 | FUNCTION = 'Function', 7 | DOCUMENT = 'document', 8 | SET_TIMEOUT = 'setTimeout', 9 | ASYNC = 'async', 10 | PUSH = 'push', 11 | SCRIPT_TAG = 'script', 12 | LOADER = 'loader', 13 | R_EVAL = /\$\{([^\=\&^\}]*?)\}/gi; 14 | 15 | var domReady = [], 16 | doc = win[DOCUMENT], 17 | domReadyHandler = function() { 18 | for (var i = 0, len = domReady.length; i < len; i++) { 19 | win[SET_TIMEOUT](domReady[i], 1); 20 | } 21 | 22 | domReady = 1; 23 | }; 24 | 25 | var modules = win.modules = { 26 | dom: function(fn) { 27 | if (domReady === 1) { 28 | win[SET_TIMEOUT](fn, 1); 29 | } else { 30 | domReady[PUSH](fn); 31 | } 32 | }, 33 | ready: function(fn) { 34 | modules.readyList[PUSH](fn); 35 | }, 36 | load: function(loader) { 37 | var script = doc.createElement(SCRIPT_TAG), 38 | first = doc.querySelector(SCRIPT_TAG), 39 | insert = function() { 40 | first.parentNode.insertBefore(script, first); 41 | }; 42 | 43 | modules.loader = loader; 44 | 45 | if ((win[LOCAL_STORAGE][GET_ITEM](LOADER_VERSION) | 0) === loader.version) { 46 | domReady[PUSH](new win[FUNCTION](win[LOCAL_STORAGE][GET_ITEM](LOADER_MODULES))); 47 | } else { 48 | script.src = loader.url.replace(R_EVAL, function(input, key) { 49 | return (new Function(LOADER, 'with(' + LOADER + ') {(' + loader[key] + ')}'))(loader); 50 | }); 51 | 52 | if (ASYNC in script) { 53 | script[ASYNC] = !0; 54 | insert(); 55 | } else { 56 | domReady[PUSH](insert); 57 | } 58 | } 59 | }, 60 | readyList: [], 61 | LOADER_VERSION: LOADER_VERSION, 62 | LOADER_MODULES: LOADER_MODULES, 63 | PACKAGED_CALLBACK: '_PCB' + +(new Date), // Packaged CallBack 64 | R_EVAL: R_EVAL 65 | }; 66 | 67 | doc.addEventListener('DOMContentLoaded', domReadyHandler, !1); 68 | }(this)); 69 | 70 | /*modules.load({ 71 | version: 1, 72 | url: '/modules.js?deps=${deps}&v=${version}&init=${!modules.inited}&callback=${modules.PACKAGED_CALLBACK}' 73 | });*/ -------------------------------------------------------------------------------- /shared/fw/cui/loader2.js: -------------------------------------------------------------------------------- 1 | (function(win) { 2 | var LOADER_VERSION = 'LOADER_VERSION', 3 | LOADER_MODULES = 'LOADER_MODULES', 4 | LOCAL_STORAGE = 'localStorage', 5 | GET_ITEM = 'getItem', 6 | FUNCTION = 'Function', 7 | DOCUMENT = 'document', 8 | SET_TIMEOUT = 'setTimeout', 9 | ADD_EVENT_LISTENER = 'addEventListner', 10 | REPLACE = 'replace'; 11 | 12 | var domReady = [], 13 | version = +'', 14 | deps = '', 15 | url = '', 16 | doc = win[DOCUMENT], 17 | domReadyHandler = function() { 18 | for (var i = 0, len = domReady.length; i < len; i++) { 19 | win[SET_TIMEOUT](domReady[i], 1); 20 | } 21 | 22 | domReady = 1; 23 | }, 24 | cached; 25 | 26 | win.modules = { 27 | dom: function(fn) { 28 | if (domReady === 1) { 29 | win[SET_TIMEOUT](fn, 1); 30 | } else { 31 | domReady.push(fn); 32 | } 33 | }, 34 | loader: { 35 | version: version, 36 | deps: deps, 37 | url: url, 38 | VERSION_KEY: LOADER_VERSION, 39 | MODULES_KEY: LOADER_MODULES 40 | }, 41 | ready: function(fn) { 42 | modules.readyList.push(fn); 43 | }, 44 | readyList: [] 45 | }; 46 | 47 | if (doc[ADD_EVENT_LISTENER]) { 48 | doc[ADD_EVENT_LISTENER]('DOMContentLoaded', domReadyHandler, !1); 49 | } else { 50 | win.attachEvent('onload', domReadyHandler); 51 | } 52 | 53 | if (cached = (win[LOCAL_STORAGE][GET_ITEM](VERSION_KEY) | 0) === version) { 54 | (new win[FUNCTION]([LOCAL_STORAGE][GET_ITEM](MODULES_KEY)))(); 55 | } 56 | 57 | doc.write(''); 58 | }(this)); 59 | 60 | modules.ready(function(mods) { 61 | var parser = mods.parser, 62 | events = mods.events; 63 | 64 | events.fire(document.body, parser.events.parse); 65 | }); -------------------------------------------------------------------------------- /shared/fw/cui/modules.js: -------------------------------------------------------------------------------- 1 | ;(function initModules(window, modules) { 2 | "use strict"; 3 | 4 | if (modules.inited) return; 5 | 6 | if (typeof window.console === 'undefined') { 7 | window.console = {}; 8 | 9 | window.console.log = 10 | window.console.debug = 11 | window.console.dir = 12 | window.console.inspect = 13 | window.console.info = 14 | window.console.error = 15 | window.console.time = 16 | window.console.timeEnd = function() {}; 17 | } 18 | 19 | var apply = Function.prototype.apply; 20 | 21 | // window.debug = apply.bind(console.log, console); 22 | window.debug = function() { 23 | apply.call(console.log, console, arguments); 24 | }; 25 | 26 | var setStorageItem = function(key, value) { 27 | if (!window.localStorage || !localStorage.setItem) { 28 | debug('warn: no local storage'); 29 | return true; 30 | } 31 | 32 | try { 33 | localStorage.setItem(key, value); 34 | return true; 35 | } catch (e) { 36 | return false; 37 | } 38 | }, 39 | isArray = Array.isArray, 40 | isObj = function(val) { 41 | return val && !isArray(val) && 42 | // bad regexp checking 43 | // but better than monkey checking 44 | typeof val === 'object' && !(val instanceof RegExp); 45 | }; 46 | 47 | var list = {}, 48 | pages = {}, 49 | loader = modules.loader, 50 | hasOwn = {}.hasOwnProperty, 51 | caching = loader.cache !== false; 52 | 53 | var callReadyHandlers = function() { 54 | var readyList = modules.readyList; 55 | 56 | modules.readyList = null; 57 | modules.ready = call; 58 | 59 | readyList.forEach(call); 60 | }, 61 | call = function(fn) { 62 | fn(); 63 | }; 64 | 65 | modules.loader = null; 66 | modules.inited = true; 67 | 68 | modules.load = function(loader) { 69 | if (!loader) return; 70 | 71 | var packaged = loader.packaged, 72 | deps = loader.deps; 73 | 74 | var loadPackage = function(url, loadedModules) { 75 | var diffDeps = caching ? deps.filter(function(key) { 76 | return !(hasOwn.call(loadedModules, key)); 77 | }) : deps.concat(); 78 | 79 | var end = function(needSave) { 80 | loading.forEach(function(mod) { 81 | modules.execute(mod); 82 | }); 83 | 84 | callReadyHandlers(); 85 | 86 | caching && needSave && setTimeout(function() { 87 | modules.saveModules(); 88 | }, 0); 89 | }; 90 | 91 | if (!diffDeps.length) { 92 | end(); 93 | return; 94 | } 95 | 96 | var newLoader = { 97 | deps: diffDeps 98 | }; 99 | 100 | Object.keys(loader).forEach(function(key) { 101 | if (key !== 'deps') { 102 | newLoader[key] = loader[key]; 103 | } 104 | }); 105 | 106 | url = url.replace(modules.R_EVAL, function(input, key) { 107 | return (new Function('loader', 'with(loader) {(' + 108 | newLoader[key] + ')}'))(newLoader); 109 | }); 110 | 111 | var first = document.querySelector('script'), 112 | script = document.createElement('script'); 113 | 114 | script.src = url; 115 | script.async = true; 116 | 117 | setTimeout(function() { 118 | first.parentNode.insertBefore(script, first); 119 | }, 1); 120 | }; 121 | 122 | if (packaged) { 123 | modules.loadModules(function(loadedModules) { 124 | loadPackage(packaged, loadedModules); 125 | }); 126 | 127 | return; 128 | } 129 | 130 | if (loader.optimized) { 131 | modules.loadModules(function(loadedModules) { 132 | loadPackage(loader.url, loadedModules); 133 | }); 134 | 135 | return; 136 | } 137 | 138 | var splitPath = function(path) { 139 | var split = path.match(/(?:^|\/)([^\/]+)\.\w+?(?:\?(\d*))?$/); 140 | 141 | if (!split) return {}; 142 | 143 | var name = split[1], 144 | version = split[2]; 145 | 146 | if (!isFinite(version)) { 147 | version = void 0; 148 | } else { 149 | version = +version; 150 | } 151 | 152 | return { 153 | name: name, 154 | version: version 155 | }; 156 | }, 157 | pacakgeDeps = typeof loader.package === 'function' ? 158 | loader.package : function(deps) { 159 | if (isArray(deps)) { 160 | return deps.map(function(dep) { 161 | if (isObj(dep)) { 162 | return { 163 | file: dep.file, 164 | name: dep.name, 165 | version: dep.version 166 | }; 167 | } else { 168 | var split = splitPath(dep); 169 | 170 | return { 171 | file: dep, 172 | name: split.name, 173 | version: split.version 174 | }; 175 | } 176 | }); 177 | } else { 178 | return Object.keys(deps).map(function(key) { 179 | var dep = deps[key]; 180 | 181 | return { 182 | file: key, 183 | name: dep.name, 184 | version: dep.version 185 | }; 186 | }); 187 | } 188 | }; 189 | 190 | if (loader.debug) { 191 | (function() { 192 | var loading = pacakgeDeps(deps), 193 | first = document.querySelector('script');; 194 | 195 | var loadNext = function() { 196 | if (!loading.length) { 197 | callReadyHandlers(); 198 | return; 199 | } 200 | 201 | var dep = loading.shift(), 202 | file = dep.file, 203 | script = document.createElement('script'); 204 | 205 | script.src = (loader.path || '') + file; 206 | script.onload = function() { 207 | loadNext(); 208 | }; 209 | 210 | setTimeout(function() { 211 | first.parentNode.insertBefore(script, first); 212 | }, 1); 213 | }; 214 | 215 | loadNext(); 216 | }()); 217 | 218 | return; 219 | } 220 | 221 | modules.loadModules(function(loadedModules) { 222 | var end = function() { 223 | loading.forEach(function(mod) { 224 | modules.execute(mod); 225 | modules.addModule(mod); 226 | }); 227 | 228 | callReadyHandlers(); 229 | 230 | caching && setTimeout(function() { 231 | modules.saveModules(); 232 | }, 0); 233 | }; 234 | 235 | var loadedLength = 0, 236 | hasPrending; 237 | 238 | var loading = pacakgeDeps(deps).map(function(dep) { 239 | var file = dep.file, 240 | name = dep.name, 241 | version = dep.version; 242 | 243 | useCache: if (caching && hasOwn.call(loadedModules, name)) { 244 | var loaded = loadedModules[name]; 245 | 246 | if (loaded.version != version) { 247 | delete loadedModules[name]; 248 | break useCache; 249 | } 250 | 251 | return loaded; 252 | } 253 | 254 | var xhr = new XMLHttpRequest(), 255 | mod = { 256 | name: name, 257 | version: version 258 | }; 259 | 260 | if (!hasPrending) { 261 | hasPrending = true; 262 | } 263 | 264 | xhr.open('GET', (loader.path || '') + file, true); 265 | xhr.overrideMimeType('text/plain; charset="utf-8"'); 266 | xhr.responseType = 'text'; 267 | 268 | xhr.onload = function() { 269 | mod.code = this.responseText; 270 | 271 | if (++loadedLength >= loading.length) { 272 | end(); 273 | } 274 | }; 275 | 276 | setTimeout(function() { 277 | xhr.send(); 278 | }, 1); 279 | 280 | return mod; 281 | }); 282 | 283 | if (!hasPrending) { 284 | end(); 285 | } 286 | }); 287 | }; 288 | 289 | var extend = { 290 | packagedCallback: function(mods) { 291 | var end = function() { 292 | callReadyHandlers(); 293 | 294 | setTimeout(function() { 295 | modules.saveModules(); 296 | }, 0); 297 | }; 298 | 299 | if (!isArray(mods)) { 300 | end(); 301 | return; 302 | } 303 | 304 | mods.forEach(function(mod) { 305 | modules.execute(mod); 306 | modules.addModule(mod); 307 | }); 308 | 309 | end(); 310 | }, 311 | handleModule: function(name, obj) { 312 | list[name] = obj; 313 | obj.exports.__moduleName__ = name; 314 | return obj; 315 | }, 316 | require: function(name) { 317 | if (typeof name !== 'string') throw new TypeError('Bad argument'); 318 | return hasOwn.call(list, name) ? list[name].exports : null; 319 | }, 320 | define: function(name, fn, version) { 321 | if (typeof name !== 'string' || typeof fn !== 'function') return; 322 | 323 | var module = Object.create(null, { 324 | name: { 325 | get: function() { 326 | return name; 327 | } 328 | }, 329 | version: { 330 | get: function() { 331 | return version 332 | } 333 | }, 334 | exports: (function() { 335 | var exports = {}; 336 | 337 | return { 338 | get: function() { 339 | return exports; 340 | }, 341 | set: function(val) { 342 | if (!isObj(val)) { 343 | return; 344 | } 345 | 346 | exports = val; 347 | } 348 | } 349 | }()) 350 | }), 351 | exports = module.exports; 352 | 353 | modules.push(); 354 | 355 | var result = fn.call( 356 | window, 357 | modules.require, 358 | modules, 359 | module, 360 | exports 361 | ); 362 | 363 | // debugger; 364 | 365 | if (result && result !== exports && 366 | module.exports === exports && isObj(result)) { 367 | module.exports = result; 368 | } 369 | 370 | exports = result = null; 371 | 372 | modules.handleModule(name, module); 373 | modules.pop(); 374 | 375 | return module; 376 | }, 377 | getList: function() { 378 | return list; 379 | }, 380 | execute: function(mod) { 381 | var name = mod && mod.name, 382 | code = mod && mod.code, 383 | version = mod && mod.version; 384 | 385 | if (!mod || !code || hasOwn.call(list, mod.name)) { 386 | return false; 387 | } 388 | 389 | var args = [ 390 | 'require', 391 | 'modules', 392 | 'module', 393 | 'exports' 394 | ].join(','), 395 | code = [ 396 | '"use strict";', 397 | modules.evalGlobalRequire(), 398 | code, 399 | ';\rreturn exports;' 400 | ].join(''); 401 | 402 | if (modules.evalScriptTag) { 403 | var script = document.createElement('script'), 404 | body = [ 405 | '(function() {', 406 | 'var fn = function(' + args + ') {' + code + '};', 407 | 'modules.define("' + mod.name + '", fn, ' + version + ');', 408 | '}());' 409 | ].join(''); 410 | 411 | script.textContent = body; 412 | document.head.appendChild(script); 413 | } else { 414 | var fn = new Function(args, code); 415 | modules.define(mod.name, fn, version); 416 | } 417 | 418 | return true; 419 | }, 420 | getModule: function(name) { 421 | return hasOwn.call(list, name) ? list[name] : null; 422 | }, 423 | addModule: function(mod, callback) { 424 | modules.loadModules(function(loadedModules) { 425 | if (!hasOwn.call(loadedModules, mod.name)) { 426 | loadedModules[mod.name] = mod; 427 | } 428 | 429 | if (callback) { 430 | callback(); 431 | } 432 | }); 433 | }, 434 | loadModules: function(callback) { 435 | var loadedModules = modules.loadedModules; 436 | 437 | if (loadedModules) { 438 | callback(loadedModules); 439 | return; 440 | } 441 | 442 | var error = function() { 443 | callback(modules.loadedModules = {}); 444 | }; 445 | 446 | storage.get(modules.STORAGE_VERSION_KEY, function(version) { 447 | if (+loader.version !== +version) { 448 | storage.remove(modules.STORAGE_VERSION_KEY); 449 | storage.remove(modules.STORAGE_CODE_KEY); 450 | error(); 451 | return; 452 | } 453 | 454 | storage.get(modules.STORAGE_CODE_KEY, function(mods) { 455 | if (isObj(mods)) { 456 | modules.loadedModules = mods; 457 | callback(mods); 458 | } else { 459 | error(); 460 | } 461 | }, error); 462 | }, error); 463 | }, 464 | saveModules: function(callback, errback) { 465 | storage.set(modules.STORAGE_VERSION_KEY, loader.version); 466 | 467 | storage.set( 468 | modules.STORAGE_CODE_KEY, 469 | modules.loadedModules, 470 | callback, 471 | errback 472 | ); 473 | }, 474 | push: function() {}, 475 | pop: function() {}, 476 | clearCache: function() { 477 | localStorage.removeItem(modules.LOADER_MODULES); 478 | localStorage.removeItem(modules.LOADER_VERSION); 479 | storage.remove(modules.STORAGE_VERSION_KEY); 480 | storage.remove(modules.STORAGE_CODE_KEY); 481 | }, 482 | useGlobals: function() { 483 | var global = modules.globalRequire, 484 | keys = Object.keys(global); 485 | 486 | if (keys.length) { 487 | keys.forEach(function(key) { 488 | if (modules.globalsUsed[key]) return; 489 | modules.globalsUsed[key] = window[key] = modules.require(global[key]); 490 | }); 491 | } 492 | }, 493 | evalGlobalRequire: function() { 494 | var global = modules.globalRequire, 495 | keys = Object.keys(global), 496 | code = ';'; 497 | 498 | if (keys.length) { 499 | keys.forEach(function(key) { 500 | code += 'var ' + key + ' = require("' + global[key] + '");'; 501 | }); 502 | } 503 | 504 | return code; 505 | }, 506 | 507 | globalsUsed: {}, 508 | globalRequire: {}, 509 | loadedModules: null, 510 | STORAGE_CODE_KEY: '_storage_modules_code_', 511 | STORAGE_VERSION_KEY: '_storage_modules_version_' 512 | }; 513 | 514 | for (var prop in extend) { 515 | if (hasOwn.call(extend, prop)) { 516 | modules[prop] = extend[prop]; 517 | } 518 | } 519 | 520 | var source = 'modules.fromCache = true; (' + initModules + '(this, modules))'; 521 | 522 | caching && setTimeout(function() { 523 | setStorageItem(modules.LOADER_MODULES, source); 524 | setStorageItem(modules.LOADER_VERSION, loader.version); 525 | }); 526 | 527 | modules.define('_storage', defineStorage); 528 | var storage = modules.require('_storage'); 529 | 530 | if (loader.optimized) { 531 | window[modules.PACKAGED_CALLBACK] = function(mods) { 532 | window[modules.PACKAGED_CALLBACK] = null; 533 | modules.packagedCallback(mods); 534 | }; 535 | } 536 | 537 | if (loader && !loader.optimized || modules.fromCache) { 538 | modules.load(loader); 539 | } 540 | 541 | // Code from LocalForge library 542 | // https://github.com/mozilla/localForage/blob/master/LICENSE 543 | // Copyright 2014 Mozilla 544 | // Licensed under the Apache License, Version 2.0 545 | 546 | function defineStorage(require, modules, module, exports) { 547 | // Initialize IndexedDB; fall back to vendor-prefixed versions if needed. 548 | var indexedDB = window.indexedDB || window.webkitIndexedDB || 549 | window.mozIndexedDB || window.msIndexedDB; 550 | 551 | module.exports = indexedDB ? initIDBStorage(indexedDB) : initLocalStorage(); 552 | } 553 | 554 | function initIDBStorage(indexedDB) { 555 | // Originally found in https://github.com/mozilla-b2g/gaia/blob/e8f624e4cc9ea945727278039b3bc9bcb9f8667a/shared/js/async_storage.js 556 | 557 | var db = null, 558 | exports = {}; 559 | 560 | var DBNAME = '_mod_storage_', 561 | DBVERSION = 1, 562 | STORENAME = 'keyvaluepairs'; 563 | 564 | function withStore(type, f, reject) { 565 | if (db) { 566 | f(db.transaction(STORENAME, type).objectStore(STORENAME)); 567 | } else { 568 | var openreq = indexedDB.open(DBNAME, DBVERSION); 569 | 570 | if (reject) { 571 | openreq.onerror = function withStoreOnError() { 572 | reject(openreq.error); 573 | }; 574 | } 575 | 576 | openreq.onupgradeneeded = function withStoreOnUpgradeNeeded() { 577 | // First time setup: create an empty object store 578 | openreq.result.createObjectStore(STORENAME); 579 | }; 580 | 581 | openreq.onsuccess = function withStoreOnSuccess() { 582 | db = openreq.result; 583 | f(db.transaction(STORENAME, type).objectStore(STORENAME)); 584 | }; 585 | } 586 | } 587 | 588 | function getItem(key, callback, errback) { 589 | withStore('readonly', function getItemBody(store) { 590 | var req = store.get(key); 591 | 592 | req.onsuccess = function getItemOnSuccess() { 593 | var value = req.result; 594 | 595 | if (value === undefined) { 596 | value = null; 597 | } 598 | 599 | if (callback) { 600 | callback(value); 601 | } 602 | }; 603 | 604 | if (errback) { 605 | req.onerror = function getItemOnError() { 606 | errback(req.error); 607 | }; 608 | } 609 | }, errback); 610 | } 611 | 612 | function setItem(key, value, callback, errback) { 613 | withStore('readwrite', function setItemBody(store) { 614 | // Cast to undefined so the value passed to callback/promise is 615 | // the same as what one would get out of `getItem()` later. 616 | // This leads to some weirdness (setItem('foo', undefined) will 617 | // return "null"), but it's not my fault localStorage is our 618 | // baseline and that it's weird. 619 | if (value === undefined) { 620 | value = null; 621 | } 622 | 623 | var req = store.put(value, key); 624 | 625 | req.onsuccess = function setItemOnSuccess() { 626 | if (callback) { 627 | callback(value); 628 | } 629 | }; 630 | 631 | if (errback) { 632 | req.onerror = function setItemOnError() { 633 | errback(req.error); 634 | }; 635 | } 636 | }, errback); 637 | } 638 | 639 | function removeItem(key, callback, errback) { 640 | withStore('readwrite', function removeItemBody(store) { 641 | var req = store.delete(key); 642 | 643 | req.onsuccess = function removeItemOnSuccess() { 644 | if (callback) { 645 | callback(); 646 | } 647 | }; 648 | 649 | if (errback) { 650 | req.onerror = function removeItemOnError() { 651 | errback(req.error); 652 | }; 653 | } 654 | }); 655 | } 656 | 657 | return { 658 | get: getItem, 659 | set: setItem, 660 | remove: removeItem 661 | }; 662 | } 663 | 664 | function initLocalStorage() { 665 | var localStorage = null, 666 | exports = {}; 667 | 668 | // If the app is running inside a Google Chrome packaged webapp, or some 669 | // other context where localStorage isn't available, we don't use 670 | // localStorage. This feature detection is preferred over the old 671 | // `if (window.chrome && window.chrome.runtime)` code. 672 | // See: https://github.com/mozilla/localForage/issues/68 673 | try { 674 | // Initialize localStorage and create a variable to use throughout 675 | // the code. 676 | localStorage = window.localStorage; 677 | } catch (e) { 678 | var noop = function() {}; 679 | return { 680 | get: noop, 681 | set: noop, 682 | remove: noop 683 | }; 684 | } 685 | 686 | // Retrieve an item from the store. Unlike the original async_storage 687 | // library in Gaia, we don't modify return values at all. If a key's value 688 | // is `undefined`, we pass that value to the callback function. 689 | function getItem(key, callback, errback) { 690 | try { 691 | var result = localStorage.getItem(key); 692 | 693 | // If a result was found, parse it from serialized JSON into a 694 | // JS object. If result isn't truthy, the key is likely 695 | // undefined and we'll pass it straight to the callback. 696 | if (result) { 697 | result = JSON.parse(result); 698 | } 699 | } catch (e) { 700 | errback(e); 701 | return; 702 | } 703 | 704 | if (callback) { 705 | callback(result); 706 | } 707 | } 708 | 709 | // Remove an item from the store, nice and simple. 710 | function removeItem(key, callback) { 711 | localStorage.removeItem(key); 712 | 713 | if (callback) { 714 | callback(); 715 | } 716 | } 717 | 718 | // Set a key's value and run an optional callback once the value is set. 719 | // Unlike Gaia's implementation, the callback function is passed the value, 720 | // in case you want to operate on that value only after you're sure it 721 | // saved, or something like that. 722 | function setItem(key, value, callback, errback) { 723 | // Convert undefined values to null. 724 | // https://github.com/mozilla/localForage/pull/42 725 | if (value === undefined) { 726 | value = null; 727 | } 728 | 729 | // Save the original value to pass to the callback. 730 | var originalValue = value; 731 | 732 | try { 733 | value = JSON.stringify(value); 734 | } catch (e) { 735 | errback(e); 736 | } 737 | 738 | localStorage.setItem(key, value); 739 | 740 | if (callback) { 741 | callback(originalValue); 742 | } 743 | } 744 | 745 | return { 746 | get: getItem, 747 | set: setItem, 748 | remove: removeItem 749 | }; 750 | } 751 | }(this, modules)); -------------------------------------------------------------------------------- /shared/fw/cui/modules/ajax.js: -------------------------------------------------------------------------------- 1 | var exports = {}, 2 | lib = require('lib'), 3 | utils = require('utils'), 4 | hasOwn = Object.prototype.hasOwnProperty; 5 | 6 | exports.eventsArr = [ 7 | 'load', 8 | 'error', 9 | 'progress', 10 | 'timeout', 11 | 'end', 12 | 'abort', 13 | 'result' 14 | ]; 15 | 16 | exports.events = events.getMap(module.name, exports.eventsArr); 17 | exports.events.load = exports.events.result; 18 | 19 | var AjaxError = function AjaxError(reason, status, transport) { 20 | Error.call(this); 21 | 22 | this.reason = reason; 23 | this.status = status; 24 | this.transport = transport; 25 | 26 | if (Error.captureStackTrace) { 27 | Error.captureStackTrace(this, AjaxError); 28 | } 29 | }, 30 | ajax = exports; 31 | 32 | AjaxError.prototype = Object.create(Error.prototype); 33 | 34 | var JSONPCount = 0, 35 | FN_KEY = '_jsonpCallback', 36 | DEFAULT_JSONP_CALLBACK = 'callback'; 37 | 38 | var requestTypes = { 39 | urlencoded: 'application/x-www-form-urlencoded', 40 | json: 'application/json', 41 | text: 'text/plain' 42 | }, 43 | requestTypeHandlers = { 44 | urlencoded: function(data) { 45 | return Sync.escape(data); 46 | }, 47 | json: function(data) { 48 | return JSON.stringify(data); 49 | }, 50 | text: function(data) { 51 | return data; 52 | } 53 | }, 54 | responseTypes = { 55 | json: function(text) { 56 | return text ? JSON.parse(text) : null; 57 | } 58 | }, 59 | responseTypesMap = { 60 | 'application/json': 'json' 61 | }; 62 | 63 | var appendQuery = function(url, query) { 64 | if (url.indexOf('?') !== -1) { 65 | return url + '&' + query; 66 | } else { 67 | return url + '?' + query; 68 | } 69 | }, 70 | extendPromise = function(promise, map) { 71 | events.wrap(promise, { 72 | noEventForward: true 73 | }); 74 | 75 | exports.eventsArr.forEach(function(eventKey) { 76 | promise[eventKey] = function(val) { 77 | if (typeof val === 'function') { 78 | events.on(promise, exports.events[eventKey], val); 79 | return this; 80 | } 81 | 82 | if (map && hasOwn.call(map, eventKey)) { 83 | map[eventKey].call(promise, val); 84 | } else { 85 | events.fire(promise, exports.events[eventKey], val); 86 | } 87 | 88 | return this; 89 | }; 90 | }); 91 | }, 92 | getAjaxError = function(reason, status, transport, name) { 93 | var error = new AjaxError(reason, status, transport); 94 | 95 | error.transportName = name; 96 | error[name] = transport; 97 | 98 | return error; 99 | }; 100 | 101 | ajax.send = function(options) { 102 | var xhr = new XMLHttpRequest(), 103 | method = typeof options.method === 'string' ? 104 | options.method.toUpperCase() : 'GET', 105 | contentType = typeof options.contentType === 'string' ? 106 | options.contentType.toLowerCase() : 'urlencoded', 107 | data = options.data, 108 | url = options.url, 109 | responseType = typeof options.responseType === 'string' ? 110 | options.responseType.toLowerCase() : ''; 111 | 112 | var promise = new Promise(function(resolve, reject) { 113 | var fireError = function(reason, status, event) { 114 | if (errorFired) return; 115 | errorFired = true; 116 | 117 | var error = getAjaxError(reason, status, xhr, 'xhr'); 118 | 119 | if (event) { 120 | event.fire(promise, exports.events[event], error); 121 | } else { 122 | events.fire(promise, exports.events.error, error); 123 | } 124 | 125 | reject(error); 126 | }, 127 | errorFired; 128 | 129 | if (typeof data === 'object' && data) { 130 | data = requestTypeHandlers[method === 'POST' ? 131 | contentType : 'urlencoded'](data); 132 | } 133 | 134 | typeof data === 'string' || (data = ''); 135 | 136 | if (method !== 'POST' && data) { 137 | url = appendQuery(url, data); 138 | data = null; 139 | } 140 | 141 | xhr.url = url; 142 | xhr.open(method, url, /*async*/ true); 143 | 144 | if (responseType) { 145 | xhr.responseType = responseType; 146 | } 147 | 148 | if (options.withCredentials) { 149 | xhr.withCredentials = true; 150 | } 151 | 152 | xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); 153 | 154 | if (method === 'POST' || contentType !== 'urlencoded') { 155 | xhr.setRequestHeader('Content-Type', requestTypes[contentType]); 156 | } 157 | 158 | if (options.headers) { 159 | Sync.each(options.headers, function(val, key) { 160 | xhr.setRequestHeader(key, val); 161 | }); 162 | } 163 | 164 | events.on(xhr, 'load', function(e) { 165 | var response = xhr.response, 166 | responseType = xhr.responseType, 167 | hasError; 168 | 169 | if (!responseType && response) { 170 | responseType = xhr.getResponseHeader('Content-Type'); 171 | responseType = responseType.split(';', 1)[0]; 172 | 173 | if (hasOwn.call(responseTypesMap, responseType)) { 174 | responseType = responseTypesMap[responseType]; 175 | var handleResponse = responseTypes[responseType]; 176 | } 177 | } 178 | 179 | if (handleResponse) { 180 | try { 181 | response = response ? handleResponse(response) : ''; 182 | } catch (e) { 183 | response = ''; 184 | hasError = 'parseerror'; 185 | } 186 | } 187 | 188 | if (hasError) { 189 | fireError(hasError); 190 | return; 191 | } 192 | 193 | var status = xhr.status, 194 | scheme = url.match(/^(\w+?\:)\/\//), 195 | isFsReq; 196 | 197 | if ((scheme && scheme[1] === 'file:') || 198 | !scheme && window.location.protocol === 'file:') { 199 | isFsReq = true; 200 | } 201 | 202 | if ((status >= 200 && status < 300) || !status && isFsReq) { 203 | if (!status) { 204 | status = 200; 205 | } 206 | 207 | events.fire(promise, exports.events.result, response, status); 208 | 209 | resolve({ 210 | response: response, 211 | status: status, 212 | xhr: xhr 213 | }); 214 | } else if (status >= 400/* && status < 600*/) { 215 | fireError('httperror', status); 216 | } else if (status === 304) { 217 | // handle http cache here 218 | // if no cached content: 219 | fireError('cacheerror'); 220 | } 221 | }); 222 | 223 | events.on(xhr, 'error', function() { 224 | fireError('neterror'); 225 | }); 226 | 227 | events.on(xhr, 'progress', function(e) { 228 | events.fire(promise, exports.events.progress, { 229 | lengthComputable: e.lengthComputable, 230 | loaded: e.loaded, 231 | total: e.total 232 | }); 233 | }); 234 | 235 | events.on(xhr, 'abort', function() { 236 | // events.fire(promise, exports.events.abort); 237 | fireError('abort', void 0, 'timeout'); 238 | }); 239 | 240 | events.on(xhr, 'timeout', function() { 241 | // events.fire(promise, exports.events.timeout); 242 | fireError('timeout', void 0, 'timeout'); 243 | }); 244 | 245 | events.on(xhr, 'loadend', function() { 246 | events.fire(promise, exports.events.end, xhr); 247 | }); 248 | 249 | xhr.send(data); 250 | }); 251 | 252 | extendPromise(promise, { 253 | abort: function() { 254 | xhr.abort(); 255 | } 256 | }); 257 | 258 | return promise; 259 | }; 260 | 261 | ajax.script = function(options) { 262 | var url = typeof options === 'string' ? options : options.url; 263 | 264 | if (typeof url !== 'string' || !url) { 265 | return Promise.reject(getAjaxError('nourl', void 0, null, 'script')); 266 | // return Promise.reject(new AjaxError('nourl')); 267 | } 268 | 269 | return new Promise(function(resolve, reject) { 270 | var script = loadScript(url, function(err) { 271 | if (err) { 272 | var error = getAjaxError('error', void 0, script, 'script'); 273 | // var error = new AjaxError('error'); 274 | 275 | reject(error); 276 | } else { 277 | resolve(script) 278 | } 279 | }); 280 | }); 281 | }; 282 | 283 | ajax.jsonp = function(options) { 284 | var url = options.url, 285 | data = options.data || '', 286 | callbackKey = options.callbackKey || DEFAULT_JSONP_CALLBACK, 287 | aborted, 288 | abortMethod; 289 | 290 | var promise = new Promise(function(resolve, reject) { 291 | var fn = FN_KEY + (JSONPCount++), 292 | executed = false, 293 | loadedData; 294 | 295 | var fireError = function(reason, status, event) { 296 | if (errorFired) return; 297 | errorFired = true; 298 | 299 | var error = getAjaxError(reason, status, script, 'jsonp'); 300 | 301 | if (event) { 302 | event.fire(promise, exports.events[event], error); 303 | } else { 304 | events.fire(promise, exports.events.error, error); 305 | } 306 | 307 | reject(error); 308 | }, 309 | errorFired; 310 | 311 | if (typeof data === 'object') { 312 | data[callbackKey] = fn; 313 | data = Sync.escape(data); 314 | } else { 315 | data += (data ? '&' : '') + callbackKey + '=' + fn; 316 | } 317 | 318 | url = appendQuery(url, data); 319 | 320 | window[fn] = function(data) { 321 | executed = true; 322 | loadedData = data; 323 | }; 324 | 325 | abortMethod = function() { 326 | if (aborted) return; 327 | 328 | window[fn] = null; 329 | aborted = true; 330 | script.remove(); 331 | 332 | fireError('abort', void 0, 'abort'); 333 | /*events.fire(promise, exports.events.abort); 334 | reject(new AjaxError('abort'));*/ 335 | }; 336 | 337 | var script = loadScript(url, function(err) { 338 | if (aborted) return; 339 | 340 | window[fn] = null; 341 | 342 | if (!executed || err) {/* 343 | var error = new AjaxError('error'); 344 | events.fire(promise, exports.events.error, error);*/ 345 | 346 | fireError('error'); 347 | 348 | reject(error); 349 | } else { 350 | events.fire(promise, exports.events.result, loadedData); 351 | 352 | resolve({ 353 | response: loadedData 354 | }); 355 | } 356 | }); 357 | }); 358 | 359 | extendPromise(promise, { 360 | abort: abortMethod 361 | }); 362 | 363 | return promise; 364 | }; 365 | 366 | var FRAME_OPTIONS = { 367 | method: 'GET', 368 | enctype: 'application/x-www-form-urlencoded', 369 | action: '', 370 | global: false, 371 | target: '', 372 | timeout: 0 373 | }; 374 | 375 | ajax.frame = function(options) { 376 | if (!options) { 377 | // return Promise.reject(new AjaxError('noarg')); 378 | return Promise.reject(getAjaxError('noarg', void 0, null, 'frame')); 379 | } 380 | 381 | var nodeName = options.nodeName; 382 | 383 | if (typeof nodeName === 'string' && nodeName.toLowerCase() === 'form') { 384 | form = options; 385 | 386 | options = lib.extend({}, FRAME_OPTIONS, arguments[1]); 387 | 388 | [ 389 | 'action', 390 | 'method', 391 | 'enctype', 392 | 'global', 393 | 'target' 394 | ].forEach(function(key) { 395 | var val = form.getAttribute(key); 396 | 397 | if (val) { 398 | options[key] = val; 399 | } 400 | }); 401 | } else { 402 | options = lib.extend({}, FRAME_OPTIONS, options); 403 | } 404 | 405 | var target = document.body || document.head || document.documentElement, 406 | data, 407 | url, 408 | method, 409 | form, 410 | autoRemoveForm; 411 | 412 | var sendAsync = function() { 413 | var iframe, 414 | submit, 415 | key, 416 | ignoreLoad, 417 | pendingTimer, 418 | abortMethod; 419 | 420 | var promise = new Promise(function(resolve, reject) { 421 | var loadHandler = function() { 422 | if (ignoreLoad) return; 423 | 424 | setTimeout(cleanUp, 1); 425 | 426 | try { 427 | var win = this.contentWindow; 428 | } catch (e) {} 429 | 430 | if (win) { 431 | var response = options.responseHandler; 432 | 433 | if (response) { 434 | response = response(win); 435 | } else { 436 | response = win; 437 | } 438 | 439 | events.fire(promise, exports.events.result, response); 440 | 441 | resolve({ 442 | response: response, 443 | win: win 444 | }); 445 | } else { 446 | fireError('error'); 447 | } 448 | }, 449 | cleanUp = function() { 450 | try { 451 | target.removeChild(iframe); 452 | } catch (e) {}; 453 | 454 | iframe = target = null; 455 | clearTimeout(pendingTimer); 456 | }; 457 | 458 | var fireError = function(reason, status, event) { 459 | if (errorFired) return; 460 | errorFired = true; 461 | 462 | var error = getAjaxError(reason, status, iframe, 'frame'); 463 | 464 | if (event) { 465 | event.fire(promise, exports.events[event], error); 466 | } else { 467 | events.fire(promise, exports.events.error, error); 468 | } 469 | 470 | reject(error); 471 | }, 472 | errorFired; 473 | 474 | iframe = document.createElement('iframe'); 475 | iframe.style.display = 'none'; 476 | iframe.src = 'javascript: true'; 477 | 478 | abortMethod = function() { 479 | ignoreLoad = true; 480 | 481 | try { 482 | if (this.contentWindow.stop) { 483 | this.contentWindow.stop(); 484 | } else { 485 | this.contentWindow.document.execCommand('Stop'); 486 | } 487 | } catch (e) { 488 | this.src = 'about:blank'; 489 | } 490 | 491 | cleanUp(); 492 | fireError('abort', void 0, 'abort'); 493 | }; 494 | 495 | isFinite(options.timeout) && (pendingTimer = setTimeout(function() { 496 | ignoreLoad = true; 497 | cleanUp(); 498 | fireError('timeout', void 0, 'timeout'); 499 | }, options.timeout)); 500 | 501 | if (form) { 502 | submit = form.constructor.prototype.submit; 503 | key = 'iframe' + Date.now(); 504 | iframe.id = iframe.name = form.target = key; 505 | 506 | events.once(iframe, 'load', function() { 507 | events.once(iframe, 'load', function() { 508 | loadHandler.apply(this, arguments); 509 | 510 | if (form.parentNode === target && autoRemoveForm) { 511 | target.removeChild(form); 512 | } 513 | 514 | form.target = null; 515 | form = null; 516 | }); 517 | 518 | setTimeout(function() { 519 | submit.call(form); 520 | }, 1); 521 | }); 522 | } else { 523 | events.on(iframe, 'load', loadHandler); 524 | iframe.src = url; 525 | } 526 | 527 | target.appendChild(iframe); 528 | }); 529 | 530 | extendPromise(promise, { 531 | abort: abortMethod 532 | }); 533 | 534 | return promise; 535 | }, 536 | sendGlobal = function() { 537 | if (form) { 538 | var submit = form.constructor.prototype.submit; 539 | 540 | if (options.target) { 541 | form.target = options.target; 542 | } 543 | 544 | setTimeout(function() { 545 | submit.call(form); 546 | }, 1); 547 | } else { 548 | utils.goLink(url, options.target); 549 | } 550 | 551 | return Promise.resolve(); 552 | }; 553 | 554 | url = (options.action || options.url || window.location.href); 555 | method = (options.method + '').toUpperCase(); 556 | 557 | if (method === 'POST' && !form) { 558 | form = document.createElement('form'); 559 | form.style.display = 'none'; 560 | form.action = url; 561 | form.method = 'POST'; 562 | form.setAttribute('enctype', options.enctype); 563 | 564 | data = options.data; 565 | data && (data = lib.unescape(lib.escape(data), true)); 566 | 567 | if (typeof data === 'object') { 568 | var fragment = document.createDocumentFragment(); 569 | 570 | lib.each(data, function(val, key) { 571 | var input = document.createElement('input'); 572 | 573 | input.type = 'hidden'; 574 | input.name = key; 575 | input.value = val; 576 | 577 | fragment.append(input); 578 | }); 579 | 580 | form.append(fragment); 581 | } 582 | 583 | autoRemoveForm = !options.global; 584 | target.append(form); 585 | } else if (method === 'GET') { 586 | data = options.data; 587 | data = data ? (typeof data === 'object' ? lib.escape(data) : data) : ''; 588 | 589 | if (data = data.trim()) { 590 | url += (url.indexOf('?') !== -1 ? '&' : '?') + data; 591 | } 592 | } 593 | 594 | if (options.global) { 595 | return sendGlobal(); 596 | } 597 | 598 | return sendAsync(); 599 | }; 600 | 601 | function loadScript(url, callback) { 602 | var script = document.createElement('script'), 603 | target = document.head, 604 | called = false; 605 | 606 | var end = function(e) { 607 | if (!called) { 608 | typeof callback === 'function' && 609 | callback.call(script, e.type === 'error'); 610 | 611 | called = true; 612 | target.removeChild(script); 613 | } 614 | }; 615 | 616 | script.async = true; 617 | script.onerror = script.onload = end; 618 | script.src = url; 619 | 620 | return target.appendChild(script); 621 | }; -------------------------------------------------------------------------------- /shared/fw/cui/modules/basic.js: -------------------------------------------------------------------------------- 1 | var ui = require('ui'), 2 | parser = require('parser'), 3 | utils = require('utils'), 4 | touch = require('touch'), 5 | uiStates = ui.states; 6 | 7 | var exports = { 8 | events: events.getMap(module.name, [ 9 | 'deactiveControl' 10 | ]) 11 | }; 12 | 13 | var statesMap = { 14 | hover: { 15 | allow: ['none', 'active', 'focus'], 16 | from: ['none'] 17 | }, 18 | active: { 19 | blocks: ['hover'], 20 | from: ['none', 'hover'] 21 | }, 22 | focus: { 23 | blocks: ['hover', 'active'], 24 | from: ['none', 'hover', 'active'] 25 | }, 26 | error: { 27 | blocks: ['none', 'hover', 'active', 'focus', 'selected'] 28 | }, 29 | selected: { 30 | blocks: ['none', 'hover', 'active', 'focus'], 31 | from: ['none', 'hover', 'active', 'focus'] 32 | }, 33 | loading: { 34 | blocks: ['none', 'hover', 'active', 'focus', 'selected'] 35 | }, 36 | readonly: { 37 | blocks: ['none', 'hover', 'active', 'focus', 'selected', 'loading', 'error'] 38 | }, 39 | disabled: { 40 | blocks: ['none', 'hover', 'active', 'focus', 'selected', 'loading', 'error', 'readonly'] 41 | } 42 | }, 43 | originalStateSet = parser.OriginalElement.properties.state.set; 44 | 45 | ui.define('live-element', { 46 | mixins: ['touch-element'], 47 | handler: function() { 48 | var startValue, 49 | self = this, 50 | view = this.view, 51 | node = this.node, 52 | beforeActive = ui.states.none, 53 | deactiveConst = 'mouseup mousedown', 54 | docDeactiveConst = deactiveConst, 55 | activeConst = 'mousedown'; 56 | 57 | if (utils.hasTouch) { 58 | activeConst = 'touchstart'; 59 | deactiveConst = 'touchend touchcancel touchleave'; 60 | docDeactiveConst = 'touchend touchcancel'; 61 | } 62 | 63 | var deactiveHandler = function(e) { 64 | if (!self) return; 65 | 66 | if (self.get('state') === ui.states.active) {console.log('deactive', e.type, e.currentTarget.nodeName); 67 | if (e.currentTarget === document || e.target === view || view.contains(e.target)) { 68 | if (!utils.hasTouch) { 69 | self.set('state', ui.states.none, { 70 | silent: false, 71 | direct: false 72 | }); 73 | } 74 | 75 | self.set('state', beforeActive, { 76 | direct: true 77 | }); 78 | } else if (beforeActive === ui.states.hover) { 79 | self.set('state', ui.states.none, { 80 | direct: true 81 | }); 82 | } 83 | 84 | console.log(self.get('state'), self.view.getAttribute('data-state')); 85 | if (self.get('state') !== self.view.getAttribute('data-state')) { 86 | debugger; 87 | } 88 | } 89 | 90 | events.remove(view, deactiveConst + ' ' + ui.events.destroy, deactiveHandler); 91 | events.remove(document, docDeactiveConst, deactiveHandler); 92 | }, 93 | activeHandler = function(e) { 94 | var state = self.get('state'); 95 | 96 | if (state !== ui.states.none && state !== ui.states.hover) return; 97 | 98 | console.log('activate'); 99 | 100 | beforeActive = state; 101 | self.set('state', ui.states.active, { 102 | direct: true 103 | }); 104 | 105 | events.once(document, 'mousedown', function() { 106 | events.on(document, docDeactiveConst, deactiveHandler); 107 | }); 108 | events.on(view, 109 | deactiveConst + ' ' + ui.events.destroy, deactiveHandler); 110 | }; 111 | 112 | events.on(self, ui.events.destroy, function() { 113 | self = view = node = null; 114 | }); 115 | 116 | events.on(view, 'mouseenter mouseleave', function(e) { 117 | var state = self.get('state'); 118 | 119 | if (state !== ui.states.none && state !== ui.states.hover) return; 120 | 121 | if (e.type === 'mouseenter') { 122 | state = ui.states.hover; 123 | } else { 124 | state = ui.states.none; 125 | } 126 | 127 | self.set('state', state, { 128 | direct: true 129 | }); 130 | }); 131 | 132 | events.on(view, activeConst, activeHandler); 133 | events.on(view, exports.events.deactiveControl, deactiveHandler); 134 | 135 | events.on(self, ui.events.changeState, function(e, data) { 136 | if (data.state === ui.states.disabled) { 137 | events.fire(self, ui.events.disabled); 138 | } else if (data.state === ui.states.none && self.get('disabled')) { 139 | events.fire(self, ui.events.enabled); 140 | } 141 | }); 142 | 143 | events.on(self, ui.events.disabled, function() { 144 | utils.makeUnselectable(node, { 145 | preventTab: true 146 | }); 147 | 148 | self.set('disabled', true); 149 | node.blur(); 150 | }); 151 | 152 | events.on(self, ui.events.enabled, function() { 153 | utils.restoreSelectable(node); 154 | self.set('disabled', false); 155 | }); 156 | }, 157 | properties: { 158 | state: { 159 | type: 'string', 160 | set: function(control, name, state, params) { 161 | if (params) { 162 | var direct = params.direct, 163 | silent = params.silent; 164 | } 165 | 166 | var current = control.get(name), 167 | currentMap = statesMap[current], 168 | newMap = statesMap[state]; 169 | 170 | if (state === current) { 171 | return state; 172 | } 173 | 174 | if (direct) { 175 | var iter = function(state) { 176 | return uiStates[state] === current; 177 | }; 178 | 179 | if ((currentMap && (currentMap = currentMap.blocks) && 180 | currentMap.some(iter)) || 181 | (newMap && (newMap = newMap.from) && !newMap.some(iter))) { 182 | return; 183 | } 184 | } 185 | 186 | // console.log('state: ', state === '' ? '(empty)' : state); 187 | 188 | originalStateSet.call(this, control, name, state, { 189 | state: state, 190 | direct: direct, 191 | silent: silent 192 | }); 193 | 194 | // control.set(this.type + ':' + name, state); 195 | } 196 | }, 197 | disabled: { 198 | type: 'boolean', 199 | set: function(control, name, value) { 200 | control.set('boolean:' + name, value); 201 | control.node.disabled = value; 202 | } 203 | } 204 | } 205 | }); 206 | 207 | ui.define('focusable', { 208 | handler: function() { 209 | var self = this, 210 | node = this.node; 211 | 212 | var getValue = function(control) { 213 | return control.get('value'); 214 | }, 215 | listenInput = function() { 216 | if (getValue(self) !== startValue) { 217 | self.set('state', ui.states.none, { 218 | silent: false, 219 | direct: false 220 | }); 221 | 222 | self.set('state', ui.states.focus); 223 | 224 | events.remove(self, ui.events.change, listenInput); 225 | } 226 | }; 227 | 228 | events.on(node, 'focus blur', function(e) { 229 | var state = self.get('state'); 230 | 231 | if (state === ui.states.error/* || 232 | state === ui.states.loading || 233 | state === ui.states.disabled*/) return; 234 | 235 | self.set('state', e.type === 'focus' ? ui.states.focus : ui.states.none, { 236 | direct: true 237 | }); 238 | }); 239 | 240 | events.on(self, ui.events.changeState, function(e, data) { 241 | if (data.state !== ui.states.error) return; 242 | 243 | events.on(node, 'focus', function() { 244 | startValue = getValue(self); 245 | events.on(self, ui.events.change, listenInput); 246 | }); 247 | }); 248 | } 249 | }); 250 | 251 | /*ui.define('live-element', { 252 | mixins: ['touch-element'], 253 | handler: function() { 254 | var startValue, 255 | self = this, 256 | view = this.view, 257 | node = this.node, 258 | beforeActive = ui.states.none, 259 | deactiveConst = 'mouseup mousedown', 260 | docDeactiveConst = deactiveConst, 261 | activeConst = 'mousedown'; 262 | 263 | if (utils.hasTouch) { 264 | activeConst = 'touchstart'; 265 | deactiveConst = 'touchend touchcancel touchleave'; 266 | docDeactiveConst = 'touchend touchcancel'; 267 | } 268 | 269 | var deactiveHandler = function(e) { 270 | if (!self) return; 271 | 272 | if (self.get('state') === ui.states.active) {console.log('deactive', e.type, e.currentTarget.nodeName); 273 | if (e.currentTarget === document || e.target === view || view.contains(e.target)) { 274 | if (!utils.hasTouch) { 275 | self.set('state', ui.states.none, { 276 | silent: false, 277 | direct: false 278 | }); 279 | } 280 | 281 | self.set('state', beforeActive, { 282 | direct: true 283 | }); 284 | } else if (beforeActive === ui.states.hover) { 285 | self.set('state', ui.states.none, { 286 | direct: true 287 | }); 288 | } 289 | 290 | console.log(self.get('state'), self.view.getAttribute('data-state')); 291 | if (self.get('state') !== self.view.getAttribute('data-state')) { 292 | debugger; 293 | } 294 | } 295 | 296 | events.remove(view, deactiveConst + ' ' + ui.events.destroy, deactiveHandler); 297 | events.remove(document, docDeactiveConst, deactiveHandler); 298 | }, 299 | activeHandler = function(e) { 300 | var state = self.get('state'); 301 | 302 | if (state !== ui.states.none && state !== ui.states.hover) return; 303 | 304 | console.log('activate'); 305 | 306 | beforeActive = state; 307 | self.set('state', ui.states.active, { 308 | direct: true 309 | }); 310 | 311 | events.once(document, 'mousedown', function() { 312 | events.on(document, docDeactiveConst, deactiveHandler); 313 | }); 314 | events.on(view, 315 | deactiveConst + ' ' + ui.events.destroy, deactiveHandler); 316 | }; 317 | 318 | events.on(self, ui.events.destroy, function() { 319 | self = view = node = null; 320 | }); 321 | 322 | events.on(view, 'mouseenter mouseleave', function(e) { 323 | var state = self.get('state'); 324 | 325 | if (state !== ui.states.none && state !== ui.states.hover) return; 326 | 327 | if (e.type === 'mouseenter') { 328 | state = ui.states.hover; 329 | } else { 330 | state = ui.states.none; 331 | } 332 | 333 | self.set('state', state, { 334 | direct: true 335 | }); 336 | }); 337 | 338 | events.on(view, activeConst, activeHandler); 339 | events.on(view, exports.events.deactiveControl, deactiveHandler); 340 | 341 | events.on(self, ui.events.changeState, function(e, data) { 342 | if (data.state === ui.states.disabled) { 343 | events.fire(self, ui.events.disabled); 344 | } else if (data.state === ui.states.none && self.get('disabled')) { 345 | events.fire(self, ui.events.enabled); 346 | } 347 | }); 348 | }, 349 | properties: { 350 | state: { 351 | type: 'string', 352 | set: function(control, name, state, params) { 353 | if (params) { 354 | var direct = params.direct, 355 | silent = params.silent; 356 | } 357 | 358 | var current = control.get(name), 359 | currentMap = statesMap[current], 360 | newMap = statesMap[state]; 361 | 362 | if (state === current) { 363 | return state; 364 | } 365 | 366 | if (direct) { 367 | var iter = function(state) { 368 | return uiStates[state] === current; 369 | }; 370 | 371 | if ( 372 | (currentMap && (currentMap = currentMap.blocks) && currentMap.some(iter)) || 373 | (newMap && (newMap = newMap.from) && !newMap.some(iter)) 374 | ) { 375 | return; 376 | } 377 | } 378 | 379 | // console.log('state: ', state === '' ? '(empty)' : state); 380 | 381 | originalStateSet.call(this, control, name, state, { 382 | state: state, 383 | direct: direct, 384 | silent: silent 385 | }); 386 | 387 | // control.set(this.type + ':' + name, state); 388 | } 389 | } 390 | } 391 | });*/ 392 | 393 | /*utils.boxWidth(node, { 394 | inner: true 395 | });*/ -------------------------------------------------------------------------------- /shared/fw/cui/modules/events.js: -------------------------------------------------------------------------------- 1 | var lib = require('lib'), 2 | libEvents = lib.events, 3 | slice = Array.prototype.slice, 4 | isArray = Array.isArray, 5 | hasOwn = Object.prototype.hasOwnProperty, 6 | defaultOptions = {}; 7 | 8 | var EVENTS_TARGET_KEY = 'events_node_target', 9 | EVENTS_NAMESPACE = 'mod_events', 10 | EVENTS_CACHE_KEY = 'events_center', 11 | EVENT_TARGET_TAG = 'event-target', 12 | EVENTS_SC_GROUP_HANDLERS = 'events_sc_group_handlers'; 13 | 14 | var EventsCenter = function(node, options) { 15 | if (!node) return null; 16 | 17 | if (!node.addEventListener) { 18 | var cache = lib.cache(node); 19 | 20 | if (!(node = cache[EVENTS_TARGET_KEY])) { 21 | node = cache[EVENTS_TARGET_KEY] = document.createElement(EVENT_TARGET_TAG); 22 | } 23 | } 24 | 25 | options || (options = defaultOptions); 26 | 27 | this.eventLast = options.eventLast; 28 | this.eventForward = !options.noEventForward; 29 | this.queued = options.eventsQueue; 30 | this.node = node; 31 | }; 32 | 33 | EventsCenter.init = function(node, options) { 34 | var cache = lib.cache(node), 35 | center = cache[EVENTS_CACHE_KEY]; 36 | 37 | if (!center) { 38 | center = cache[EVENTS_CACHE_KEY] = new EventsCenter(node, options); 39 | } 40 | 41 | return center; 42 | }; 43 | 44 | EventsCenter.addMethod = function(name, method) { 45 | EventsCenter.prototype[name] = function selfMethod(arg) { 46 | if (!arg) return this; 47 | 48 | if (typeof arg === 'object' && !isArray(arg)) { 49 | return selfMethod.apply(EventsCenter.init(arg), slice.call(arguments, 1)); 50 | } 51 | 52 | if (typeof arg === 'string') { 53 | arg = arg.split(' '); 54 | if (arg.length === 1) { 55 | arg = arg[0]; 56 | } 57 | } 58 | 59 | if (isArray(arg)) { 60 | var args = slice.call(arguments, 1); 61 | arg.forEach(function(arg) { 62 | method.apply(this, [arg].concat(args)); 63 | }, this); 64 | 65 | return this; 66 | } 67 | 68 | return method.apply(this, arguments); 69 | }; 70 | }; 71 | 72 | EventsCenter.prototype = { 73 | all: function(nodes, event, callback, capture) { 74 | var len = nodes.length, 75 | node; 76 | 77 | for (var i = len; i--;) { 78 | (node = nodes[i]) && this.once(node, event, function() { 79 | if (!--len) { 80 | callback(); 81 | } 82 | }, capture); 83 | } 84 | }, 85 | group: function(nodes, event, callback, capture) { 86 | for (var i = nodes.length; i--;) { 87 | nodes[i] && this.on(nodes[i], event, callback, capture); 88 | } 89 | }, 90 | proxy: function(what, who, params) { 91 | if (!isArray(what) || !isArray(who)) return this; 92 | var self = this; 93 | 94 | this.on(what[0], what[1], function(e, data) { 95 | self.fire(who[0], who[1], data, params); 96 | }); 97 | }, 98 | clean: function(node) { 99 | var cache = lib.cache(node), 100 | center = cache[EVENTS_CACHE_KEY]; 101 | 102 | libEvents.clean(node, EVENTS_NAMESPACE); 103 | cache[EVENTS_CACHE_KEY] = null; 104 | cache[EVENTS_TARGET_KEY] = null; 105 | if (center) { 106 | center.node = null; 107 | } 108 | } 109 | }; 110 | 111 | lib.each({ 112 | action: function(event, callback, useCaptures) { 113 | var eventForward = this.eventForward, 114 | eventLast = this.eventLast, 115 | node = this.node, 116 | self = this; 117 | 118 | libEvents.add(node, event, { 119 | handler: function(e) { 120 | var args = e.detail || [], 121 | actionsDesc = args.length ? args[0] : null, 122 | actions = actionsDesc && actionsDesc.actions, 123 | actionKeys = actions && Object.keys(actions), 124 | actionEnd = actionsDesc && actionsDesc.end, 125 | actionCalled; 126 | 127 | if (actionKeys) { 128 | var newActions = {}; 129 | 130 | actionKeys.forEach(function(key) { 131 | var action = actions[key]; 132 | 133 | newActions[key] = function() { 134 | actionCalled = true; 135 | e.stopImmediatePropagation(); 136 | return action.apply(this, arguments); 137 | }; 138 | }); 139 | 140 | actions = newActions; 141 | newActions = null; 142 | } 143 | 144 | args = [actionsDesc && actionsDesc.data, actions]; 145 | 146 | if (eventForward) { 147 | args = self.eventLast ? (args.push(e), args) : [e].concat(args); 148 | } 149 | 150 | if (callback.apply(node, args) === false) { 151 | e.stopPropagation(); 152 | e.preventDefault(); 153 | } 154 | 155 | if (actionKeys) { 156 | actionKeys.forEach(function() { 157 | actions[key] = null; 158 | }); 159 | 160 | /*actionsDesc.actions = */actions = null; 161 | } 162 | 163 | if (!actionCalled && typeof actionEnd === 'function') { 164 | actionEnd(); 165 | } 166 | 167 | actionEnd = null; 168 | }, 169 | callback: callback, 170 | capture: !!useCapture, 171 | namespace: EVENTS_NAMESPACE 172 | }); 173 | }, 174 | on: function(event, callback, useCapture) { 175 | var eventForward = this.eventForward, 176 | eventLast = this.eventLast, 177 | node = this.node, 178 | self = this; 179 | 180 | if (event == null) { 181 | // console.warn('Attempt to listen undefined event'); 182 | throw new Error('Attempt to listen undefined event'); 183 | } 184 | 185 | libEvents.add(node, event, { 186 | handler: function(e) { 187 | var args = e.detail || []; 188 | 189 | if (eventForward) { 190 | args = self.eventLast ? (args.push(e), args) : [e].concat(args); 191 | } 192 | 193 | if (callback.apply(node, args) === false) { 194 | e.stopPropagation(); 195 | e.preventDefault(); 196 | } 197 | }, 198 | callback: callback, 199 | capture: !!useCapture, 200 | namespace: EVENTS_NAMESPACE 201 | }); 202 | 203 | return this; 204 | }, 205 | remove: function(event, callback, useCapture) { 206 | if (event == null) { 207 | // console.warn('Attempt to remove undefined event'); 208 | throw new Error('Attempt to remove undefined event'); 209 | } 210 | 211 | if (typeof callback !== 'function') { 212 | if (typeof callback === 'boolean') { 213 | useCapture = callback; 214 | } 215 | 216 | var node = this.node; 217 | 218 | libEvents.removeAll(node, event, { 219 | capture: !!useCapture, 220 | namespace: EVENTS_NAMESPACE 221 | }); 222 | 223 | return this; 224 | } 225 | 226 | var node = this.node; 227 | 228 | libEvents.remove(node, event, { 229 | callback: callback, 230 | capture: !!useCapture, 231 | namespace: EVENTS_NAMESPACE 232 | }); 233 | 234 | return this; 235 | }, 236 | fire: function(event) { 237 | var node = this.node; 238 | 239 | if (event == null) { 240 | // console.warn('Attempt to fire undefined event'); 241 | throw new Error('Attempt to fire undefined event'); 242 | } 243 | 244 | libEvents.dispatch(node, event, { 245 | options: { 246 | bubbles: false, 247 | cancelable: true, 248 | detail: slice.call(arguments, 1) 249 | }, 250 | type: 'CustomEvent' 251 | }); 252 | 253 | return this; 254 | }, 255 | dispatch: function(event) { 256 | var node = this.node; 257 | 258 | if (event == null) { 259 | // console.warn('Attempt to dispatch undefined event'); 260 | throw new Error('Attempt to dispatch undefined event'); 261 | } 262 | 263 | libEvents.dispatch(node, event, { 264 | options: { 265 | bubbles: true, 266 | cancelable: true, 267 | detail: slice.call(arguments, 1) 268 | }, 269 | type: 'CustomEvent' 270 | }); 271 | 272 | return this; 273 | }, 274 | once: function(event, callback) { 275 | var self = this, 276 | node = this.node, 277 | autoRemove = function() { 278 | callback.apply(this, arguments); 279 | self.remove(node, event, autoRemove); 280 | }; 281 | 282 | return this.on(event, autoRemove); 283 | }, 284 | delegate: function(event, selector, callback) { 285 | var eventForward = this.eventForward, 286 | node = this.node; 287 | 288 | libEvents.add(node, event, { 289 | handler: function(e) { 290 | var target = e.target; 291 | 292 | do { 293 | if (target.matches(selector)) { 294 | var args = e.detail || [], 295 | control = target.uiControl; 296 | 297 | if (!control) { 298 | break; 299 | } 300 | 301 | if (eventForward) { 302 | args = [e, control].concat(args); 303 | } 304 | 305 | if (callback.apply(node, args) === false) { 306 | e.stopPropagation(); 307 | e.preventDefault(); 308 | } 309 | 310 | break; 311 | } 312 | } while (target !== this && (target = target.parentNode) && 313 | target.nodeType !== Node.DOCUMENT_NODE); 314 | }, 315 | callback: callback, 316 | capture: false, 317 | namespace: EVENTS_NAMESPACE 318 | }); 319 | 320 | return this; 321 | }, 322 | expr: function(event, fn) { 323 | var self = this, 324 | node = this.node; 325 | 326 | this.on(node, event, function exprHandler() { 327 | if (fn.apply(this, arguments)) { 328 | self.remove(event, exprHandler); 329 | } 330 | }); 331 | } 332 | }, function(method, name) { 333 | EventsCenter.addMethod(name, method); 334 | }); 335 | 336 | var events = new EventsCenter( 337 | document.createElement(EVENT_TARGET_TAG), { 338 | noEventForward: true 339 | } 340 | ); 341 | 342 | events.EventsCenter = EventsCenter; 343 | 344 | events.getMap = function(mod, list) { 345 | return list.reduce(function(map, event) { 346 | map[event] = event + '.' + mod; 347 | 348 | return map; 349 | }, {}) 350 | }; 351 | 352 | events.wrap = function(node, options) { 353 | return EventsCenter.init(node, options); 354 | }; 355 | 356 | var exports = events; 357 | 358 | if (modules && modules.globalRequire) { 359 | modules.globalRequire[module.name] = module.name; 360 | } -------------------------------------------------------------------------------- /shared/fw/cui/modules/lib.js: -------------------------------------------------------------------------------- 1 | var exports = { 2 | extend: Sync.extend, 3 | each: Sync.each, 4 | cache: Sync.cache, 5 | escape: Sync.escape, 6 | unescape: Sync.unescape, 7 | events: { 8 | add: Sync.events.addEvent, 9 | remove: Sync.events.removeEvent, 10 | clean: Sync.events.cleanEvents, 11 | removeAll: Sync.events.removeEventAll, 12 | dispatch: Sync.events.dispatchEvent 13 | } 14 | }; -------------------------------------------------------------------------------- /shared/fw/cui/modules/templates-utils.js: -------------------------------------------------------------------------------- 1 | 2 | ui.define('compiler', { 3 | parent: 'element', 4 | properties: { 5 | template: ['Default'] 6 | }, 7 | handler: function() { 8 | var self = this, 9 | compile; 10 | 11 | this.compile = function(data, sanitization) { 12 | var config = { 13 | params: Sync.extend({}, self.get('params')) 14 | }; 15 | 16 | compile || (compile = template(self.get('template'))); 17 | 18 | if (typeof sanitization === 'undefined' || sanitization === true) { 19 | sanitize(data, 'request', config); 20 | } else { 21 | if (Array.isArray(data)) { 22 | data = data.concat(); 23 | } else if (typeof data === 'object') { 24 | data = Sync.extend({}, data); 25 | } 26 | 27 | config.request = data; 28 | } 29 | 30 | events.fire(self.node, exports.events.beforeCompile, config); 31 | 32 | return compile(config); 33 | }; 34 | } 35 | }); 36 | 37 | ui.define('inline-template', { 38 | parent: 'compiler', 39 | handler: function() { 40 | var template = this.view.innerHTML, 41 | self = this; 42 | 43 | // IE wtf 44 | template = template.replace(/='([\s\S]*?)'/g, function(str, pattern) { 45 | return '="' + utils.escapeHTML(pattern) + '"'; 46 | }); 47 | 48 | this.set('template', template); 49 | this.view.innerHTML = ''; 50 | 51 | events.on(this.view, ui.events.destroy, function() { 52 | self.view.innerHTML = template; 53 | }); 54 | 55 | var node = exports.currentNode(); 56 | 57 | if (node) { 58 | node.children = []; 59 | } 60 | } 61 | }); 62 | 63 | //John Resig template pattern 64 | //http://ejohn.org/blog/javascript-micro-templating/ 65 | function template(str, fn) { 66 | if (str.nodeType) { 67 | return template(str.innerHTML); 68 | } else { 69 | str = str.replace(/[\r\t\n]/g, " ") 70 | .split("<%").join("\t") 71 | .replace(/((^|%>)[^\t]*)'/g, "$1\r") 72 | .replace(/\t=(.*?)%>/g, "',$1,'") 73 | .split("\t").join("');") 74 | .split("%>").join("p.push('") 75 | .split("\r").join("\\'"); 76 | 77 | fn = new Function("var p=[],print=function(){p.push.apply(p,arguments);};" + 78 | "with(this){p.push('" + str + "');}return p.join('');"); 79 | }; 80 | 81 | return function(data) { 82 | return fn.call(data); 83 | }; 84 | } 85 | 86 | function sanitize(value, key, self) { 87 | if (value && typeof value === 'string') { 88 | value = utils.escapeHTML(value); 89 | } else if (Array.isArray(value)) { 90 | (value = value.concat()).forEach(sanitize); 91 | } else if (value && typeof value === 'object' && 92 | !(ui.is(value, 'element')) && !value.nodeType) { 93 | Sync.each(value = Sync.extend({}, value), sanitize); 94 | } 95 | 96 | self[key] = value; 97 | } 98 | 99 | function generateTemplate(text, name) { 100 | return (text && name) ? ui.create('compiler', { 101 | properties: { 102 | template: text, 103 | templateName: name 104 | } 105 | }) : null; 106 | } 107 | 108 | function generateFromMap(value, tplName) { 109 | var tmp, 110 | templates = modules.require('templates'); 111 | 112 | value = Sync.extend({}, value); 113 | 114 | if (tplName) { 115 | return templates.hasTemplate(tmp = value[tplName]) ? 116 | generateTemplate(templates.getTemplate(tmp), tmp) : null; 117 | } else { 118 | Object.keys(value).forEach(function(tpl) { 119 | if (templates.hasTemplate(tmp = value[tpl])) { 120 | value[tpl] = generateTemplate(templates.getTemplate(tmp), tmp); 121 | } else { 122 | delete value[tpl]; 123 | } 124 | }); 125 | 126 | return value; 127 | } 128 | } 129 | 130 | ui.type.define('tpl', { 131 | factory: function(data) { 132 | return { 133 | _default: data || '' 134 | } 135 | }, 136 | get: function(control, name, tplName) { 137 | var cache = typedCache(this.type, control, name), 138 | templates = modules.require('templates'), 139 | value, 140 | tmp = null; 141 | 142 | if (cache) { 143 | cache = cache.valueOf(); 144 | 145 | if (cache instanceof ui.controls.Compiler && !tplName) { 146 | return generateTemplate(cache.get('template'), cache.get('templateName')); 147 | } else if (typeof cache === 'object' && !isArray(cache)) { 148 | return generateFromMap(cache, tplName); 149 | } else if (typeof cache === 'string' && !tplName) { 150 | return templates.hasTemplate(cache) ? 151 | generateTemplate(templates.getTemplate(cache), cache) : null; 152 | } 153 | } 154 | 155 | value = ui.types.json.get.call(this, control, name); 156 | 157 | if (value && typeof value === 'object' && !Array.isArray(value)) { 158 | tmp = generateFromMap(value, tplName); 159 | } else if (value = ui.types.string.get.call(this, control, name)) { 160 | tmp = templates.hasTemplate(value) ? 161 | generateTemplate(templates.getTemplate(value), value) : null; 162 | } 163 | 164 | return tmp; 165 | }, 166 | set: function(control, name, value) { 167 | if (!value) return; 168 | 169 | if (typeof value === 'string') { 170 | var templates = modules.require('templates'); 171 | 172 | if (templates.hasTemplate(value)) { 173 | return typedCache(this.type, control, name, value); 174 | } 175 | 176 | } else if (typeof value === 'object' && 177 | value instanceof ui.controls.Compiler || !isArray(value)) { 178 | return typedCache(this.type, control, name, value); 179 | } 180 | } 181 | }); -------------------------------------------------------------------------------- /shared/fw/cui/modules/ui.js: -------------------------------------------------------------------------------- 1 | var utils = require('utils'), 2 | lib = require('lib'); 3 | 4 | var exports = { 5 | events: events.getMap(module.name, [ 6 | 'childControl', 7 | 'changeState', 8 | 'idControl', 9 | 'change', 10 | 'select', 11 | 'disabled', 12 | 'enabled', 13 | 'destroy', 14 | 'load', 15 | 'error', 16 | 'open', 17 | 'close', 18 | 'show', 19 | 'hide', 20 | 'remove', 21 | 'attach', 22 | 'detach', 23 | 'activate', 24 | 'resize', 25 | ]), 26 | mixins: {}, 27 | controls: {}, 28 | states: { 29 | hover: 'hover', 30 | active: 'active', 31 | focus: 'focus', 32 | none: '', 33 | blur: 'blur', 34 | error: 'error', 35 | click: 'click', 36 | disabled: 'disabled', 37 | loading: 'loading', 38 | selected: 'selected', 39 | readonly: 'readonly' 40 | }, 41 | elements: {}, 42 | types: {}, 43 | keys: { 44 | DELETE: 46, 45 | DOWN: 40, 46 | RIGHT: 39, 47 | UP: 38, 48 | LEFT: 37, 49 | ESC: 27, 50 | ENTER: 13, 51 | BACKSPACE: 8, 52 | SPACE: 32, 53 | END: 35, 54 | HOME: 36 55 | }, 56 | cache: function(object, key, value) { 57 | var cahceObj = libCache(object, UI_CACHE_KEY); 58 | 59 | if (!object || !cahceObj) return null; 60 | 61 | if (typeof key === 'undefined') { 62 | return cahceObj; 63 | } 64 | 65 | if (key === null) { 66 | libCache(object, UI_CACHE_KEY, null); 67 | } 68 | 69 | if (typeof value !== 'undefined') { 70 | return (cahceObj[key] = value); 71 | } else { 72 | return cahceObj[key]; 73 | } 74 | }, 75 | is: function(control, type) { 76 | if (!control) return false; 77 | 78 | // type = utils.type2class(type); 79 | 80 | return typeof controls[type] === 'function' && 81 | control instanceof controls[type]; 82 | }, 83 | get: function(key) { 84 | if (hasOwn.call(elements, key)) { 85 | return elements[key]; 86 | } 87 | 88 | return null; 89 | }, 90 | rect: function(element) { 91 | var cache = uiCache(element), 92 | data = cache[RECT_DATA_KEY]; 93 | 94 | if (!data) { 95 | data = cache[RECT_DATA_KEY] = { 96 | rect: element.getBoundingClientRect(), 97 | scrollTop: window.pageYOffset, 98 | scrollLeft: window.pageXOffset 99 | }; 100 | 101 | var handler = function() { 102 | events.remove(element, exports.events.resize, handler); 103 | events.remove(window, 'resize', handler); 104 | cache[RECT_DATA_KEY] = null; 105 | }; 106 | 107 | events.on(element, exports.events.resize, handler); 108 | events.on(window, 'resize', handler); 109 | 110 | return data.rect; 111 | } 112 | 113 | var rect = data.rect, 114 | offsetLeft = (data.scrollLeft - window.pageXOffset), 115 | offsetTop = (data.scrollTop - window.pageYOffset); 116 | 117 | return { 118 | left: rect.left + offsetLeft, 119 | top: rect.top + offsetTop, 120 | right: rect.right + offsetLeft, 121 | bottom: rect.bottom + offsetTop, 122 | width: rect.width, 123 | height: rect.height 124 | }; 125 | }, 126 | create: function createControl(type, options, node) { 127 | var instance; 128 | 129 | if (typeof controls[type] === 'undefined') { 130 | return null; 131 | } 132 | 133 | (options || (options = {})).type = type; 134 | type = controls[type]; 135 | 136 | node || (node = document.createElement('div')); 137 | instance = new type(node, options); 138 | 139 | return instance; 140 | }, 141 | define: function defineControl(type, options) { 142 | var parent, 143 | proto, 144 | meta, 145 | properties, 146 | mixins; 147 | 148 | options || (options = {}); 149 | 150 | parent = options.parent; 151 | parent = parent && hasOwn.call(controls, parent) ? parent : 'element'; 152 | 153 | if (parent !== type && hasOwn.call(controls, type)) { 154 | throw new Error('Cannot define duplicate control: ' + type); 155 | } 156 | 157 | parent = controls[parent]; 158 | 159 | if (isArray(mixins = options.mixins) && mixins.length) { 160 | mixins = mixins.map(function(mixin) { 161 | return controls[mixin]; 162 | }); 163 | } else { 164 | mixins = null; 165 | } 166 | 167 | if (options.contains || options.base /* real dom node*/) { 168 | proto = {}; 169 | 170 | if (isArray(options.contains)) { 171 | proto.contains = options.contains/*.map(function(type) { 172 | return utils.type2class(type); 173 | })*/; 174 | } 175 | 176 | if (isArray(options.base /* real dom node*/)) { 177 | proto.base /* real dom node*/ = options.base /* real dom node*/; 178 | } 179 | } 180 | 181 | properties = options.properties; 182 | 183 | if (typeof properties === 'object' && !isArray(properties)) { 184 | meta = { 185 | properties: handleProperties(properties) 186 | }; 187 | } 188 | 189 | return (controls[type] = utils.inherits({ 190 | parent: parent, 191 | handler: options.handler, 192 | meta: meta, 193 | proto: proto, 194 | mixins: mixins, 195 | name: type 196 | })); 197 | }, 198 | control: function(viewOrBase, control) { 199 | var control = uiCache(viewOrBase, UI_CONTROL_CACHE_KEY, control); 200 | return control; 201 | }, 202 | DEFAULT_TYPE: '', 203 | DEFAULT_PROP: '' 204 | }; 205 | 206 | var hasOwn = Object.prototype.hasOwnProperty, 207 | slice = Array.prototype.slice, 208 | isArray = Array.isArray, 209 | css2dom = utils.css2dom, 210 | controls = exports.controls, 211 | elements = exports.elements, 212 | types = exports.types, 213 | states = exports.states, 214 | extend = lib.extend, 215 | libCache = lib.cache, 216 | uiCache = exports.cache, 217 | uiControl = exports.control, 218 | idInc = 0; 219 | 220 | var RECT_DATA_KEY = 'rect_data', 221 | UI_CACHE_KEY = 'ui_cache', 222 | UI_CONTROL_CACHE_KEY = 'ui_control_cache', 223 | DEFAULT_TYPE = exports.DEFAULT_TYPE, 224 | DEFAULT_PROP = exports.DEFAULT_PROP; 225 | 226 | var destroy = function(params) { 227 | if (this.zombie) { 228 | console.log('destroy return by the zombie'); 229 | return; 230 | } 231 | 232 | var parent = this.parent, 233 | view = this.view, 234 | node = this.node, 235 | i, 236 | children = this.children.concat(), 237 | name = this.get('name'), 238 | id = this.get('id'), 239 | marker, 240 | parentChildren; 241 | 242 | if (!params.onlyChildren) { 243 | if (params.remove) { 244 | view.remove(); 245 | } 246 | 247 | if (parent && (parentChildren = parent.children)) { 248 | i = parentChildren.indexOf(this); 249 | i !== -1 && parentChildren.splice(i, 1); 250 | parentChildren[name] = null; 251 | 252 | parentChildren.forEach(function(child, index) { 253 | child.index = index; 254 | }); 255 | } 256 | 257 | if (id) { 258 | elements[id] = null; 259 | } 260 | 261 | if ((marker = this.get('_attachMarker')) && marker.parentNode) { 262 | marker.parentNode.removeChild(marker); 263 | } 264 | 265 | view && events.clean(view); 266 | node && events.clean(node); 267 | 268 | uiCache(view, null); 269 | uiCache(node, null); 270 | 271 | uiControl(view, null); 272 | 273 | this.view = 274 | this.node = 275 | this.parent = 276 | this.children = null; 277 | 278 | this.zombie = true; 279 | // Object.freeze(this); 280 | } 281 | 282 | children.forEach(function(child) { 283 | child.destroy(); 284 | }); 285 | }, 286 | getControl = function(type, callback, byInstance) { 287 | var compare = byInstance ? exports.is : function(control, type) { 288 | return control.type === type; 289 | }; 290 | 291 | if (typeof callback !== 'function') { 292 | return this.children.filter(function(child) { 293 | return compare(child, type); 294 | }); 295 | } else if (this.view.parsed) { 296 | this.children.forEach(function(child) { 297 | if (compare(child, type)) { 298 | callback(child); 299 | } 300 | }); 301 | } else { 302 | events.expr(this.view, exports.events.childControl, function(e, child) { 303 | if (compare(child, type)) { 304 | callback(child); 305 | return true; 306 | } 307 | }); 308 | } 309 | }, 310 | generateUID = function() { 311 | return ++idInc; 312 | }, 313 | accessors = function(control, accessor, args) { 314 | if (control.zombie) return; 315 | 316 | var tmp = control.constructor.properties, 317 | args = slice.call(args, 0), 318 | name = args[0].split(':'), 319 | type; 320 | 321 | if (name.length > 1) { 322 | type = name[0]; 323 | args[0] = name = name[1]; 324 | 325 | if (!hasOwn.call(types, type) || !hasOwn.call(types[type], accessor)) { 326 | type = DEFAULT_TYPE; 327 | } 328 | 329 | tmp = exports.type.getDefaultInstance(type); 330 | } else { 331 | name = name[0]; 332 | type = tmp[name]; 333 | 334 | if (type && hasOwn.call(type, accessor)) { 335 | tmp = type; 336 | } else { 337 | tmp = tmp[DEFAULT_PROP]; 338 | } 339 | } 340 | 341 | // args.unshift(control); 342 | return tmp[accessor].apply(tmp, [control].concat(args)); 343 | }, 344 | handleProperties = function(properties) { 345 | return Object.keys(properties).reduce(function(result, key) { 346 | var val = result[key], 347 | type = '', 348 | args, 349 | accessors; 350 | 351 | if (typeof val === 'string') { 352 | type = val; 353 | } else if (isArray(val)) { 354 | type = (val[0] + ''); 355 | args = val.slice(1); 356 | } else if (typeof val === 'object') { 357 | type = (val.type + ''); 358 | args = val.args; 359 | accessors = { 360 | get: val.get, 361 | set: val.set 362 | }; 363 | }/* else { 364 | val = null; 365 | }*/ 366 | 367 | type = type.toLowerCase(); 368 | 369 | if (type && hasOwn.call(types, type)) { 370 | result[key] = exports.type.create(type, args, accessors); 371 | } else { 372 | if (typeof val !== 'object' || isArray(val)) { 373 | val = null; 374 | } 375 | 376 | result[key] = val; 377 | } 378 | 379 | return result; 380 | }, properties); 381 | }; 382 | 383 | exports.handleProperties = handleProperties; 384 | exports.type = { 385 | define: function(type, config) { 386 | types[type] = config; 387 | }, 388 | create: function(type, args, accessors) { 389 | var config = types[type], 390 | factory = config.factory, 391 | instance; 392 | 393 | accessors || (accessors = config); 394 | 395 | instance = factory ? factory.apply(null, args) : {}; 396 | instance.get = accessors.get || config.get; 397 | instance.set = accessors.set || config.set; 398 | 399 | instance.factory = factory; 400 | instance.type = type; 401 | 402 | return instance; 403 | }, 404 | getDefaultInstance: function(type) { 405 | var config = types[type]; 406 | 407 | return config.instance || (config.instance = exports.type.create(type)); 408 | }, 409 | get: function(type) { 410 | return types[type]; 411 | } 412 | }; 413 | 414 | exports.type.define(DEFAULT_TYPE, { 415 | get: function(control, name) { 416 | return uiCache(control.view, name); 417 | }, 418 | set: function(control, name, value) { 419 | return uiCache(control.view, name, value); 420 | } 421 | }); 422 | 423 | controls.element = utils.inherits({ 424 | handler: function(node, params) { 425 | // Expected params 426 | // Changed to manual assignment for performance improvements 427 | if (params) { 428 | typeof params.type !== 'undefined' && 429 | (this.type = params.type); 430 | typeof params.parent !== 'undefined' && 431 | (this.parent = params.parent); 432 | typeof params.view !== 'undefined' && 433 | (view = this.view = params.view); 434 | 435 | if (typeof params.index !== 'undefined') { 436 | var index = params.index; 437 | } 438 | } 439 | 440 | this.node = node; 441 | this.base = node; 442 | this.capture = params && params.capture || {}; 443 | 444 | var parent = this.parent, 445 | self = this, 446 | normalPosition = true, 447 | view, 448 | children = this.children = [], 449 | contains = parent && parent.contains, 450 | properties = params && params.properties; 451 | 452 | view || (view = this.view = node); 453 | 454 | events.on(view, exports.events.destroy, function() { 455 | view = children = parent = self = node = null; 456 | }); 457 | 458 | if (contains && contains.length) { 459 | normalPosition = false; 460 | 461 | var len = contains.length, 462 | i = 0; 463 | 464 | for (; i < len; i++) { 465 | if (exports.is(self, control)) { 466 | normalPosition = true; 467 | break; 468 | } 469 | } 470 | } 471 | 472 | if (properties) { 473 | lib.each(properties, function(val, key) { 474 | self.set(key, val); 475 | }); 476 | } 477 | 478 | if (normalPosition) { 479 | var id = this.get('id'); 480 | 481 | if (id && elements[id]) { 482 | debug('duplicates id: ', id, ' of control ', view, elements[id].view); 483 | // throw new Error(); 484 | } else if (id) { 485 | elements[id] = this; 486 | // wtf here first arg 487 | events.fire(exports.events.idControl, this.node, this); 488 | } else { 489 | this.set('id', generateUID()); 490 | } 491 | 492 | 493 | events.on(view, exports.events.childControl, function(e, child, index) { 494 | var name = child.get('name'); 495 | 496 | // child.index = children.push(child) - 1; 497 | // child.parentControl = self; 498 | 499 | if (isFinite(index)) { 500 | child.index = index; 501 | 502 | children.forEach(function(iterChild) { 503 | if (iterChild.index >= index) { 504 | iterChild.index++; 505 | } 506 | }); 507 | 508 | children.splice(index, 0, child); 509 | } else { 510 | child.index = children.push(child) - 1; 511 | } 512 | 513 | if (name && !isFinite(name)) { 514 | name = css2dom(name); 515 | 516 | if (!hasOwn.call(children, name)) { 517 | children[name] = child; 518 | } else { 519 | debug('duplicates named control ', child, ' of ', 520 | self, ' parent', child.view, children[name]); 521 | // throw new Error(); 522 | } 523 | } 524 | 525 | e.stopPropagation(); // fire only for first parent 526 | }); 527 | 528 | // after all call event -- new child 529 | if (parent) { 530 | events.fire(parent.view, exports.events.childControl, this, index); 531 | } 532 | 533 | uiControl(view, this); 534 | } else { 535 | debug('warning: wrong control tree position', view); 536 | // throw new Error(); 537 | } 538 | } 539 | }); 540 | 541 | 542 | controls.element.prototype = { 543 | type: 'element', 544 | get: function getProperty() { 545 | return accessors(this, 'get', arguments); 546 | }, 547 | set: function setProperty() { 548 | return accessors(this, 'set', arguments); 549 | }, 550 | detach: function() { 551 | if (this.get('_attachMarker') || !this.view.parentNode) return false; 552 | 553 | var marker = document.createComment('attach marker: ' + this.get('id')); 554 | 555 | this.view.parentNode.replaceChild(marker, this.view); 556 | events.fire(this.view, exports.events.detach); 557 | 558 | return this.set('_attachMarker', marker); 559 | }, 560 | attach: function() { 561 | var marker = this.get('_attachMarker'); 562 | 563 | if (!marker) return false; 564 | 565 | if (!marker.parentNode) { 566 | throw new Error('Cannot attach back control to tree without marker in'); 567 | } 568 | 569 | marker.replace(this.view); 570 | events.fire(this.view, exports.events.attach); 571 | 572 | this.set('_attachMarker', null); 573 | 574 | return true; 575 | }, 576 | task: function(fn, delay) { 577 | var timer, 578 | self = this, 579 | removeHandler = function() { 580 | clearTimeout(timer); 581 | }; 582 | 583 | events.on(self.view, exports.events.destroy, removeHandler); 584 | 585 | timer = setTimeout(function() { 586 | if (self.zombie) { 587 | console.warn('fired control task after control became zombie'); 588 | return; 589 | } 590 | 591 | events.remove(self.view, exports.events.destroy, removeHandler); 592 | fn.call(self); 593 | }, delay || 1); 594 | }, 595 | frame: function(fn) { 596 | var frame = { 597 | fn: fn 598 | }; 599 | 600 | 601 | }, 602 | remove: function() { 603 | this.destroy({ 604 | remove: true, 605 | onlyChildren: false 606 | }); 607 | }, 608 | destroy: function(options) { 609 | options = options || {}; 610 | 611 | if (!options.onlyChildren) { 612 | events.fire(this.view, exports.events.destroy, options); 613 | } else if (!options.remove) { 614 | events.fire(this.view, exports.events.destroyChildren, options); 615 | } 616 | 617 | if (options.remove) { 618 | events.fire(this.view, exports.events.remove, options); 619 | } 620 | 621 | destroy.call(this, options); 622 | } 623 | }; 624 | 625 | [ 626 | 'addEventListener', 627 | 'removeEventListener', 628 | 'dispatchEvent' 629 | ].forEach(function(key) { 630 | this[key] = function(event, fn, capture) { 631 | var view = this.view; 632 | 633 | if (view) { 634 | return key === 'dispatchEvent' ? 635 | view[key].call(view, event) : 636 | view[key].call(view, event, fn, capture); 637 | } 638 | }; 639 | }, controls.element.prototype); 640 | 641 | var properties = { 642 | hidden: { 643 | get: function(control, name) { 644 | /*if ((cache = lib.cache(control.view)) && name in cache) { 645 | return cache[name]; 646 | }*/ 647 | 648 | var display = window.getComputedStyle(control.view).display, 649 | //hiddenValue = control.view.hidden, 650 | //style = control.view.style, 651 | hidden = false; 652 | 653 | if (display === 'none') { 654 | hidden = true; 655 | } 656 | 657 | return hidden; 658 | }, 659 | set: function(control, name, value) { 660 | // value = !!value; // to boolean 661 | 662 | var hidden = this.get(control, name), 663 | view = control.view, 664 | hiddenValue = view.hidden, 665 | display, 666 | style, 667 | cache = uiCache(view), 668 | computed, 669 | lastDisplay; 670 | 671 | if (value && !hidden) { 672 | cache[name + '_lastDisplay'] = 673 | display = (style = view.style).display; 674 | 675 | style.display = 'none'; 676 | 677 | hidden = true; 678 | 679 | events.fire(view, exports.events.hide); 680 | } else if (!value && hidden) { 681 | display = (style = control.view.style).display; 682 | lastDisplay = cache[name + '_lastDisplay'] || ''; 683 | 684 | if (display !== 'none' && hiddenValue) { 685 | view.hidden = false; 686 | } else if (display === 'none') { 687 | style.display = lastDisplay ? lastDisplay : ''; 688 | display = ''; 689 | } 690 | 691 | hidden = this.get(control, name); 692 | 693 | if (hidden) { 694 | style.display = (display ? display : 'block') + ' !important'; 695 | hidden = false; 696 | } 697 | 698 | events.fire(control.view, exports.events.show); 699 | } 700 | 701 | cache[name] = hidden; 702 | 703 | return hidden; 704 | } 705 | }, 706 | attached: { 707 | get: function(control) { 708 | return !control.get('_attachMarker'); 709 | } 710 | }, 711 | child: { 712 | get: function(control, name, type, callback) { 713 | return getControl.call(control, type, callback, 1); 714 | } 715 | }, 716 | capture: { 717 | get: function(control, name, key) { 718 | var capture = control.capture; 719 | 720 | do { 721 | if (hasOwn.call(capture, key)) { 722 | return capture[key]; 723 | } 724 | } while ((control = control.parent) && (capture = control.capture)); 725 | }, 726 | set: function(control, name, key, value) { 727 | control.capture[key] = value; 728 | } 729 | }, 730 | state: { 731 | type: DEFAULT_TYPE, 732 | set: function(control, name, state, params) { 733 | control.set(this.type + ':' + name, state); 734 | control.set(DEFAULT_TYPE + ':' + name, state); 735 | 736 | if (!params || !params.silent) { 737 | events.fire(control, exports.events.changeState, params); 738 | } 739 | 740 | return state; 741 | } 742 | } 743 | }; 744 | 745 | properties[DEFAULT_PROP] = DEFAULT_TYPE; 746 | 747 | extend(controls.element, { 748 | properties: properties 749 | }); -------------------------------------------------------------------------------- /shared/fw/cui/modules/utils.js: -------------------------------------------------------------------------------- 1 | var lib = require('lib'); 2 | 3 | var selectableEvents = [ 4 | 'selectionstart', 5 | 'selectstart', 6 | 'dragstart' 7 | // 'mousedown', 8 | //'touchstart' 9 | ], 10 | focusableEvents = [ 11 | 'DOMActivate', 12 | 'activate', 13 | 'beforeactivate', 14 | 'focus', 15 | 'focusin' 16 | ], 17 | defaultSelectOptions = { 18 | preventTab: false, 19 | onlySelf: false, 20 | useCapture: true 21 | }; 22 | 23 | var isArray = Array.isArray, 24 | extend = lib.extend, 25 | call = Function.call, 26 | hasOwn = Object.prototype.hasOwnProperty, 27 | hasTouch = 'ontouchstart' in document; 28 | 29 | var R_TYPE_2_CLASS = /(?:\b)(\w)([\w\-]+)(?:\b)/ig, 30 | R_CSS_2_DOM = /-([a-z]|[0-9])/ig, 31 | R_MS_PREFIX = /^-ms-/, 32 | R_CAMEL_2_CSS = /[A-Z](?=\w)/g, 33 | R_URL = /^(?:(http(?:s?)\:)?\/\/([a-zA-Z_\-а-яА-ЯёЁ0-9\.]+))?(\:\d+)?(\/(?:[^?#]*)?)?(\?(?:[^#]*)?)?(#[\S]*)?/g, 34 | UNSELECTABLED_KEY = '_unselectabledObject'; 35 | 36 | var toUpperCase = function(str, letter) { 37 | return (letter + '').toUpperCase(); 38 | }, 39 | type2class = function(str, p1, p2) { 40 | return (p1 + '').toUpperCase() + exports.css2dom(p2); 41 | }, 42 | camel2css = function(w) { 43 | return ('-' + w).toLowerCase(); 44 | }; 45 | 46 | var exports = { 47 | escapeHTML: function escapeHTML(value) { 48 | return value && (value + '') 49 | .replace(/&/gi, '&') 50 | .replace(//gi, '>') 52 | .replace(/"/gi, '"') 53 | .replace(/'/gi, ''') 54 | .replace(/\//gi, '/') || ''; 55 | }, 56 | makeUnselectable: function(node, options) { 57 | options = extend({}, defaultSelectOptions, options); 58 | 59 | var bindEvents = options.preventTab ? 60 | selectableEvents.concat(focusableEvents) : selectableEvents, 61 | cache = lib.cache(node), 62 | unselectables = cache[UNSELECTABLED_KEY] || (cache[UNSELECTABLED_KEY] = []), 63 | listeners = {}, 64 | send = { 65 | options: options, 66 | listeners: listeners 67 | }; 68 | 69 | bindEvents.forEach(function(key) { 70 | var listener = function(e) { 71 | var target = e.target; 72 | 73 | if (options.onlySelf && target !== node) return; 74 | 75 | e.preventDefault(); 76 | e.stopPropagation(); 77 | e.stopImmediatePropagation(); 78 | 79 | return false; 80 | }; 81 | 82 | events.on(node, key, listener, options.useCapture); 83 | listeners[key] = listener; 84 | }); 85 | 86 | events.on(node, 'touchstart', listeners['touchstart'] = function() {}, false); 87 | 88 | node.setAttribute('unselectable', 'on'); 89 | node.setAttribute('draggable', 'false'); 90 | 91 | if (options.preventTab) { 92 | send.tabIndex = node.tabIndex | 0; 93 | node.tabIndex = -1; 94 | } 95 | 96 | unselectables.push(send); 97 | }, 98 | restoreSelectable: function(node) { 99 | var unselectables = lib.cache(node)[UNSELECTABLED_KEY], 100 | send, 101 | options, 102 | useCapture, 103 | listeners; 104 | 105 | if (!unselectables || !unselectables.length) return; 106 | 107 | send = unselectables.pop(), 108 | listeners = send.listeners, 109 | options = send.options, 110 | useCapture = options.useCapture; 111 | 112 | lib.each(listeners, function(val, key) { 113 | events.remove(node, key, val, useCapture); 114 | }); 115 | 116 | if (!unselectables.length) { 117 | node.removeAttribute('unselectable'); 118 | node.removeAttribute('draggable'); 119 | } 120 | 121 | if (options.preventTab) { 122 | var tabIndex = send.tabIndex; 123 | if (isFinite(tabIndex)) { 124 | node.tabIndex = tabIndex; 125 | } 126 | } 127 | }, 128 | getBox: function(node, options, params) { 129 | var computed = window.getComputedStyle(node), 130 | data = {}, 131 | offset = 0; 132 | 133 | options = { 134 | border: typeof options.border === 'boolean' ? options.border : false, 135 | metric: typeof options.metric === 'boolean' ? options.metric : true, 136 | inner: typeof options.inner === 'boolean' ? options.inner : true, 137 | outer: typeof options.outer === 'boolean' ? options.outer : false 138 | }; 139 | 140 | if (options.inner) { 141 | options.border = false; 142 | options.outer = false; 143 | offset = -(parseInt(computed[params.padding[0]]) + parseInt(computed[params.padding[1]])); 144 | } 145 | 146 | if (options.outer) { 147 | offset = parseInt(computed[params.margin[0]]) + parseInt(computed[params.margin[1]]); 148 | } 149 | 150 | return (options.metric ? (options.border ? node[params.metric[0]] : node[params.metric[1]]) : 0) + offset; 151 | }, 152 | boxHeight: function(node, options) { 153 | return exports.getBox(node, options, { 154 | margin: ['marginTop', 'marginBottom'], 155 | padding: ['paddingTop', 'paddingBottom'], 156 | metric: ['offsetHeight', 'clientHeight'] 157 | }); 158 | }, 159 | boxWidth: function(node, options) { 160 | return exports.getBox(node, options, { 161 | margin: ['marginLeft', 'marginRight'], 162 | padding: ['paddingLeft', 'paddingRight'], 163 | metric: ['offsetWidth', 'clientWidth'] 164 | }); 165 | }, 166 | css2dom: function(str) { 167 | return str.replace(R_MS_PREFIX, 'ms-').replace(R_CSS_2_DOM, toUpperCase); 168 | }, 169 | type2class: function(type) { 170 | return (type || '').replace(R_TYPE_2_CLASS, type2class); 171 | }, 172 | camel2css: function(str) { 173 | return str.replace(R_CAMEL_2_CSS, camel2css); 174 | }, 175 | checkIntoView: function(node, height) { 176 | var rect = node.nodeType ? rect = node.getBoundingClientRect() : node; 177 | height || (height = window.innerHeight); 178 | 179 | var topHeight = rect.top + rect.height, 180 | isBetween = (rect.top > 0 && topHeight < height), 181 | isAbove = (rect.top < 0 && topHeight < 0 && topHeight < height), 182 | isBelow = (rect.top > 0 && rect.top > height && topHeight > height), 183 | isOutflow = (rect.top < 0 && rect.top + rect.height > height), 184 | singleOutflow = !isBelow && !isAbove && !isBetween && !isOutflow, 185 | isHidden = !rect.width && !rect.height; 186 | 187 | return { 188 | isIntoView: isHidden ? false : 189 | isBetween || (!isAbove && !isBelow) || isOutflow, 190 | isBetween: isBetween, 191 | isAbove: isAbove, 192 | isBelow: isBelow, 193 | isOutflow: isOutflow, 194 | isOutflowBottom: singleOutflow && rect.top > 0, 195 | isOutflowTop: singleOutflow && rect.top < 0, 196 | rect: rect, 197 | topHeight: topHeight, 198 | height: height 199 | }; 200 | }, 201 | html2fragment: function(string, iterator) { 202 | var div = document.createElement('div'), 203 | child, 204 | iterate = typeof iterator === 'function', 205 | fragment = document.createDocumentFragment(); 206 | 207 | div.innerHTML = string; 208 | 209 | while (child = div.firstChild) { 210 | if (!iterate || iterator(child) !== true) { 211 | fragment.appendChild(child); 212 | } 213 | } 214 | 215 | div = null; 216 | return fragment; 217 | }, 218 | attr: function(node, attr, value) { 219 | if (!node || !attr) return null; 220 | 221 | if (value !== void 0 || value === null) { 222 | node.setAttribute(attr, value); 223 | return value; 224 | } 225 | 226 | // return node.hasAttribute(attr) ? node.getAttribute(attr) : void 0; 227 | attr = node.getAttribute(attr); 228 | return attr == null ? void 0 : attr; 229 | }, 230 | debounce: function(fn, delay, thisArg) { 231 | var timer, 232 | throttle = function() { 233 | if (timer) clearTimeout(timer); 234 | var args = arguments, 235 | _this = thisArg || this; 236 | 237 | timer = setTimeout(function() { 238 | fn.apply(_this || null, args); 239 | }, delay); 240 | }; 241 | 242 | throttle.stop = function() { 243 | if (timer) clearTimeout(timer); 244 | }; 245 | 246 | return throttle; 247 | }, 248 | goLink: function(url, target) { 249 | var go = function() { 250 | try { 251 | window.location.assign(url); 252 | } catch (e) { 253 | window.location.href = url; 254 | } 255 | }; 256 | 257 | //if (target && target !== '_self') { 258 | var a = document.createElement('a'); 259 | 260 | a.href = url || ''; 261 | a.target = target || ''; 262 | document.body.appendChild(a); 263 | 264 | //setTimeout(function() { 265 | if (a.click) { 266 | try { 267 | a.click(); 268 | } catch (e) { 269 | go(); 270 | } 271 | 272 | window.focus(); 273 | } else { 274 | go(); 275 | } 276 | 277 | setTimeout(function() { 278 | document.body.removeChild(a); 279 | a = null; 280 | }, 1); 281 | //}, 1); 282 | /*} else { 283 | go(); 284 | }*/ 285 | } 286 | }; 287 | 288 | var ParsedURL = function(url) { 289 | url = url.trim(); 290 | R_URL.lastIndex = 0; 291 | 292 | var match = R_URL.exec(url); 293 | 294 | if (!match) { 295 | return null; 296 | } 297 | 298 | if (!match[0]) { 299 | match[4] = location.pathname.replace(/(\/)[^\/?#]*?$/, '$1' + url); 300 | } 301 | 302 | var protocol = match[1] || location.protocol, 303 | hostname = match[2] || location.hostname, 304 | port = match[3] || '', 305 | pathname = match[4] || location.pathname, 306 | search = match[5] || '', 307 | hash = match[6] || '', 308 | origin = protocol + '//' + hostname + port; 309 | 310 | this.protocol = protocol, 311 | this.hostname = hostname, 312 | this.host = hostname + port, 313 | this.port = port, 314 | this.pathname = pathname, 315 | this.path = pathname + search, 316 | this.search = search, 317 | this.hash = hash, 318 | this.origin = origin, 319 | this.href = origin + pathname + search + hash; 320 | }; 321 | 322 | exports.parseUrl = function(url) { 323 | if (typeof url !== 'string') { 324 | return url instanceof ParsedURL ? url : null 325 | } 326 | 327 | return new ParsedURL(url); 328 | }; 329 | 330 | exports.inherits = function(options) { 331 | var handler = options.handler, 332 | parent = options.parent, 333 | proto = options.proto, 334 | meta = options.meta, 335 | mixins = options.mixins, 336 | name = options.name || 'unnamed' + Date.now(), 337 | parentMixins, 338 | handlers, 339 | classProto; 340 | 341 | if (parent) { 342 | classProto = Class.prototype = Object.create(parent.prototype); 343 | parentMixins = parent.__mixins__; 344 | } else { 345 | classProto = Class.prototype; 346 | } 347 | 348 | if (isArray(mixins) && mixins.length) { 349 | mixins = mixins.filter(function(mix) { 350 | if (!mix || parentMixins && 351 | parentMixins.indexOf(mix) !== -1) return false; 352 | 353 | extend(true, Class, mix); 354 | extend(classProto, mix.prototype); 355 | 356 | return true; 357 | }); 358 | } 359 | 360 | handlers = (parent && parent.__handlers__ || []).concat(mixins ? mixins.reduce(function(result, mix) { 361 | return result.concat(mix.__handlers__); 362 | }, []) : []).filter(function(handler, i, arr) { 363 | return arr.indexOf(handler) === i; 364 | }); 365 | 366 | handler && handlers.push(handler); 367 | 368 | extend(true, Class, parent, meta); 369 | extend(classProto, proto); 370 | 371 | classProto.constructor = Class; 372 | 373 | if (parent) { 374 | classProto.__super__ = parent; 375 | classProto.__parent__ = parent.prototype; 376 | } 377 | 378 | Class.toString = function() { 379 | return '[User Class ' + name + ']'; 380 | }; 381 | 382 | classProto.toString = function() { 383 | return '[User Object ' + name + ']'; 384 | }; 385 | 386 | Class.__name__ = name; 387 | Class.__mixins__ = mixins; 388 | Class.__handlers__ = handlers; 389 | 390 | function Class() { 391 | var self = this; 392 | 393 | if (parent && !(this instanceof parent)) { 394 | self = Object.create(parent.prototype); 395 | } 396 | 397 | for (var i = 0, len = handlers.length, handler; i < len; i++) { 398 | handler = handlers[i]; 399 | /*mix && */handler.apply(self, arguments); 400 | } 401 | 402 | return self; 403 | } 404 | 405 | return Class; 406 | }; -------------------------------------------------------------------------------- /shared/fw/modules/checkbox.js: -------------------------------------------------------------------------------- 1 | var ui = require('ui'), 2 | parser = require('parser'); 3 | 4 | var exports = { 5 | events: events.getMap(module.name, [ 6 | 'checked' 7 | ]) 8 | }; 9 | 10 | ui.define('checkbox', { 11 | base: ['input[type="checkbox"]'], 12 | handler: function() { 13 | var self = this, 14 | selfChecked = self.get('checked'), 15 | baseChecked = self.node.checked; 16 | 17 | if (selfChecked && !baseChecked) { 18 | self.node.checked = true; 19 | } else if (!selfChecked && baseChecked) { 20 | self.set('boolean:checked', true); 21 | } 22 | 23 | events.on(self.node, 'click change', function() { 24 | var checked = self.node.checked; 25 | 26 | if (self.get('checked') !== checked) { 27 | self.set('boolean:checked', checked); 28 | events.fire(self, exports.events.checked); 29 | } 30 | }); 31 | }, 32 | properties: { 33 | checked: ['boolean', false, { 34 | set: function(control, name, value) { 35 | this.node.checked = value; 36 | } 37 | }] 38 | } 39 | }); -------------------------------------------------------------------------------- /shared/fw/modules/main.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NekR/PromisesDebuggerExtension/b95a1f0cf7e2bb26ebb6a605786213e669b06bbb/shared/fw/modules/main.js -------------------------------------------------------------------------------- /shared/fw/modules/panel.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | ['updateSetting'].forEach(function(method) { 4 | exports[method] = PromisesPanel[method].bind(PromisesPanel); 5 | }); -------------------------------------------------------------------------------- /shared/fw/modules/settings.js: -------------------------------------------------------------------------------- 1 | var ui = require('ui'), 2 | parser = require('parser'), 3 | checkbox = require('checkbox'), 4 | panel = require('panel'); 5 | 6 | ui.define('settings', { 7 | handler: function() { 8 | var self = this; 9 | 10 | events.on(self, parser.events.parsed, function() { 11 | self.children.forEach(function(child) { 12 | events.on(child, checkbox.events.checked, function() { 13 | panel.updateSetting(child.get('string:setting'), child.get('checked')); 14 | }); 15 | }); 16 | }); 17 | } 18 | }); -------------------------------------------------------------------------------- /shared/prefs.json: -------------------------------------------------------------------------------- 1 | { 2 | // log errors in promises 3 | "log_errors": true, 4 | // show full stack means show stack from extensions, 5 | // useful for this ext. dev 6 | "show_full_stack": true, 7 | // ignore promises which are auto created by then, but not handled (yet?) 8 | "ignore_trailing_promises": true, 9 | 10 | "log_caught_rejections": true, 11 | // pause on errors in promise which has at least one catch handler 12 | "pause_on_caught_errors": false, 13 | 14 | // attach backend to target on DevTools open 15 | "start_watch_on_start": false 16 | } -------------------------------------------------------------------------------- /shared/promises-backend.js: -------------------------------------------------------------------------------- 1 | console.log('PromisesDebugger inside'); 2 | 3 | (function(global) { 4 | "use strict"; 5 | 6 | if (global.PromisesDebugger) return; 7 | 8 | var hasOwn = Object.prototype.hasOwnProperty; 9 | 10 | var PromisesDebugger = { 11 | promiseToRecord: new WeakMap(), 12 | nextId: 0, 13 | providers: {}, 14 | 15 | get: function(promise) { 16 | return PromisesDebugger.promiseToRecord.get(promise); 17 | }, 18 | requestUpdate: function(diffData) { 19 | setTimeout(function() { 20 | 21 | // console.log('requestUpdate'); 22 | 23 | window.postMessage({ 24 | PromisesDebugger: true, 25 | method: 'requestUpdate', 26 | message: diffData 27 | }, '*'); 28 | }, 0); 29 | }, 30 | registerProvider: function(name, config) { 31 | var provider = new Provider(name, config); 32 | 33 | PromisesDebugger.providers[name] = provider; 34 | 35 | return provider; 36 | }, 37 | reportError: function(provider, error) { 38 | var errValue = error.value; 39 | 40 | if (errValue instanceof Error) { 41 | var value = { 42 | stack: error.value.stack, 43 | message: error.value.message, 44 | name: error.value.name 45 | }; 46 | } else { 47 | var value = { 48 | message: errValue + '' 49 | }; 50 | } 51 | 52 | setTimeout(function() { 53 | window.postMessage({ 54 | PromisesDebugger: true, 55 | method: 'reportError', 56 | message: { 57 | error: { 58 | value: value 59 | }, 60 | provider: provider.name 61 | } 62 | }, '*'); 63 | }, 0); 64 | } 65 | }; 66 | 67 | var Provider = function(name, config) { 68 | this.name = name; 69 | this.config = config; 70 | }; 71 | 72 | Provider.prototype = { 73 | get: function(promise) { 74 | return PromisesDebugger.promiseToRecord.get(promise); 75 | }, 76 | reportError: function(error) { 77 | PromisesDebugger.reportError(this, error); 78 | }, 79 | register: function(promise, params) { 80 | var provider = this; 81 | 82 | var registeredData = { 83 | promise: promise, 84 | setValue: function(val) { 85 | var value = val.value, 86 | sendData = { 87 | id: registeredData.id, 88 | state: val.type 89 | }; 90 | 91 | if (value && typeof value === 'object' && !Array.isArray(value)) { 92 | value = value.valueOf(); 93 | } 94 | 95 | var isPromise; 96 | 97 | if (provider.isPromise && (isPromise = provider.isPromise(value))) { 98 | var record = provider.get(isPromise); 99 | 100 | sendData.value = { 101 | type: 'promise', 102 | id: record.id 103 | }; 104 | } else if ( 105 | typeof value === 'string' || 106 | typeof value === 'number' || 107 | typeof value === 'boolean' 108 | ) { 109 | // truncate long strings 110 | if (typeof value === 'string' && value.length > 100) { 111 | value = value.slice(0, 100); 112 | } 113 | 114 | sendData.value = { 115 | type: 'primitive', 116 | value: value, 117 | primitive: typeof value 118 | }; 119 | } else if (value instanceof Error) { 120 | sendData.value = { 121 | type: 'error', 122 | error: { 123 | stack: value.stack, 124 | message: value.message, 125 | code: value.code 126 | } 127 | }; 128 | } else if (Array.isArray(value)) { 129 | sendData.value = { 130 | type: 'array' 131 | }; 132 | } else if (value && typeof value === 'object') { 133 | var keys = Object.keys(value), 134 | allKeys = Object.getOwnPropertyNames(value), 135 | objStr = value + '', 136 | objName = objStr.match(/^\[object (\w+?)\]$/); 137 | 138 | objName = objName ? objName[1] : objStr; 139 | 140 | if (keys && (keys.length || allKeys.length)) { 141 | sendData.value = { 142 | type: 'keys', 143 | keys: keys, 144 | allKeys: allKeys 145 | }; 146 | } else { 147 | sendData.value = { 148 | type: 'object', 149 | object: value + '' 150 | }; 151 | } 152 | } else if (typeof value === 'function') { 153 | var strVal = value + '', 154 | nameEnd = strVal.indexOf(')'), 155 | isNative = strVal.indexOf('[native code]') !== -1; 156 | 157 | sendData.value = { 158 | type: 'function', 159 | function: isNative ? strVal : strVal.slice(0, nameEnd + 1) 160 | }; 161 | } else { 162 | // console.log('value unknown:', value); 163 | sendData.value = { 164 | type: 'unknown', 165 | typeOf: typeof value 166 | }; 167 | } 168 | 169 | this.value = value; 170 | this.state = val.type; 171 | 172 | PromisesDebugger.requestUpdate({ 173 | event: 'value', 174 | data: sendData 175 | }); 176 | }, 177 | id: PromisesDebugger.nextId++, 178 | chaining: [], 179 | stack: params && params.stack, 180 | topLevel: params ? params.topLevel !== false : true, 181 | provider: provider.name 182 | }, 183 | topLevel = registeredData.topLevel, 184 | parentPromise, 185 | name = params && params.name || '', 186 | caller = params.caller; 187 | 188 | if (topLevel) { 189 | 190 | } else { 191 | params.parent.chaining.push(registeredData); 192 | parentPromise = params.parent.id; 193 | } 194 | 195 | PromisesDebugger.requestUpdate({ 196 | event: 'create', 197 | data: { 198 | id: registeredData.id, 199 | topLevel: topLevel, 200 | stack: registeredData.stack, 201 | parentPromise: parentPromise, 202 | name: name, 203 | caller: caller 204 | } 205 | }); 206 | 207 | if (params && hasOwn.call(params, 'value')) { 208 | registeredData.setValue(params.value); 209 | } 210 | 211 | PromisesDebugger.promiseToRecord.set(promise, registeredData); 212 | promise.__recordData__ = registeredData; 213 | 214 | return registeredData; 215 | }, 216 | }; 217 | 218 | PromisesDebugger.Provider = Provider; 219 | 220 | window.PromisesDebugger = PromisesDebugger; 221 | }(this)); -------------------------------------------------------------------------------- /shared/promises-panel.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 46 | 66 | 74 |
75 | 121 | 122 | 123 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /shared/promises-panel.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var reloadButton = document.getElementById('reload'), 3 | attachButton = document.getElementById('attach'), 4 | clearButton = document.getElementById('clear'), 5 | toggleAttach = document.getElementById('toggle-attach'), 6 | settingsButton = document.getElementById('settings-button'); 7 | 8 | var isChrome = typeof chrome !== 'undefined'; 9 | 10 | reloadButton.addEventListener('click', function() { 11 | sendServiceAction('reload_and_attach'); 12 | toggleAttach.setAttribute('selected', ''); 13 | }); 14 | 15 | attachButton.addEventListener('click', function() { 16 | doAttach(); 17 | }); 18 | 19 | clearButton.addEventListener('click', function() { 20 | doClear(); 21 | }); 22 | 23 | toggleAttach.addEventListener('click', function() { 24 | var selected = this.hasAttribute('selected'); 25 | 26 | if (!selected) { 27 | doAttach(); 28 | } else { 29 | doDetach(); 30 | } 31 | }); 32 | 33 | settingsButton.addEventListener('click', function() { 34 | var settings = document.getElementById('settings'); 35 | 36 | settings.hidden = false; 37 | settings.querySelector('.close').onclick = function() { 38 | this.onclick = null; 39 | settings.hidden = true; 40 | }; 41 | }); 42 | 43 | var doAttach = function() { 44 | sendServiceAction('attach'); 45 | toggleAttach.setAttribute('selected', ''); 46 | }, 47 | doDetach = function() { 48 | sendServiceAction('detach'); 49 | toggleAttach.removeAttribute('selected'); 50 | }; 51 | 52 | var createPromiseRow = function() { 53 | var item = document.createElement('div'), 54 | cont = document.createElement('div'), 55 | name = document.createElement('div'), 56 | status = document.createElement('div'), 57 | value = document.createElement('div'), 58 | id = document.createElement('div'), 59 | extend = document.createElement('div'), 60 | chain = document.createElement('div'); 61 | 62 | item.className = 'pd-table-row'; 63 | cont.className = 'pd-table-row-cont'; 64 | 65 | name.className = 'pd-table-cell'; 66 | id.className = 'pd-table-cell'; 67 | status.className = 'pd-table-cell'; 68 | value.className = 'pd-table-cell'; 69 | chain.className = 'pd-table-cell'; 70 | 71 | extend.className = 'pd-table-extend'; 72 | 73 | id.classList.add('cell-id'); 74 | 75 | item.appendChild(cont); 76 | item.appendChild(extend); 77 | 78 | cont.appendChild(id); 79 | cont.appendChild(name); 80 | cont.appendChild(status); 81 | cont.appendChild(value); 82 | cont.appendChild(chain); 83 | 84 | return { 85 | chain: chain, 86 | extend: extend, 87 | cont: cont, 88 | item: item, 89 | name: name, 90 | status: status, 91 | value: value, 92 | id: id 93 | }; 94 | }, 95 | toggleExtendBlock = function(extend, block) { 96 | var currentBlock = extend.currentBlock; 97 | 98 | if (currentBlock) { 99 | extend.removeChild(currentBlock); 100 | } 101 | 102 | if (currentBlock !== block) { 103 | extend.appendChild(block); 104 | extend.currentBlock = block; 105 | } else { 106 | extend.currentBlock = null; 107 | } 108 | }, 109 | checkTbody = function(tbody) { 110 | if (tbody.childElementCount) { 111 | tbody.hidden = false; 112 | } else { 113 | tbody.hidden = true; 114 | } 115 | }, 116 | extractUrl = function(input) { 117 | var R_URL = /((?:\w+?\:\/\/)[\s\S]*?):(\d+):(\d+)/g; 118 | var R_FX_ANONYMOUR_URL = /((?:\w+?\:\/\/)[\s\S]*?) line (\d+) > (?:[\s\S]*)/g; 119 | 120 | var urlMatch, 121 | usedReg; 122 | 123 | while (urlMatch = R_FX_ANONYMOUR_URL.exec(input)) { 124 | usedReg = R_FX_ANONYMOUR_URL; 125 | break; 126 | } 127 | 128 | if (!urlMatch) { 129 | while (urlMatch = R_URL.exec(input)) { 130 | usedReg = R_URL; 131 | break; 132 | } 133 | } 134 | 135 | if (urlMatch) { 136 | return { 137 | match: urlMatch, 138 | usedReg: usedReg 139 | }; 140 | } else { 141 | return null; 142 | } 143 | }, 144 | parseAndGerRerouceLink = function(input, url) { 145 | if (!url) { 146 | url = extractUrl(input); 147 | } 148 | 149 | var urlMatch = url && url.match, 150 | usedReg = url && url.usedReg; 151 | 152 | if (urlMatch) { 153 | var a = document.createElement('a'), 154 | url = urlMatch[0], 155 | prefix = input.slice(0, usedReg.lastIndex - url.length), 156 | postfix = input.slice(usedReg.lastIndex); 157 | 158 | a.className = 'resource-link'; 159 | a.textContent = url; 160 | a.href = url; 161 | a.addEventListener('click', function(e) { 162 | e.preventDefault(); 163 | e.stopPropagation(); 164 | 165 | sendServiceAction('open_resource', { 166 | file: urlMatch[1], 167 | line: urlMatch[2], 168 | col: urlMatch[3] 169 | }); 170 | }); 171 | } 172 | 173 | var span = document.createElement('span'); 174 | 175 | if (urlMatch) { 176 | span.appendChild(document.createTextNode(prefix)); 177 | span.appendChild(a); 178 | span.appendChild(document.createTextNode(postfix)); 179 | } else { 180 | span.textContent = input; 181 | } 182 | 183 | return span; 184 | }, 185 | getNameFromStackOld = function(stack, i) { 186 | var lines = stack.split(/(?:\n+|\->)/), 187 | name, 188 | url; 189 | 190 | while ( 191 | (name = lines[i]) && (name.indexOf('(native)') !== -1 || 192 | name.indexOf('(:') !== -1) && 193 | i < lines.length 194 | ) { 195 | ++i; 196 | } 197 | 198 | var resourceLink = parseAndGerRerouceLink(name); 199 | 200 | return resourceLink; 201 | }, 202 | getNameFromStack = function(handledStack, i, name) { 203 | var stackName = handledStack.lines[0], 204 | url = extractUrl(stackName), 205 | urlMatch = url && url.match; 206 | 207 | if (name) { 208 | stackName += ' with ' + name; 209 | } 210 | 211 | var resourceLink = parseAndGerRerouceLink(stackName); 212 | 213 | return resourceLink; 214 | }, 215 | handleStack = function(stack) { 216 | return handleEverStack(stack); 217 | 218 | if (isChrome) { 219 | return handleChromeStack(stack); 220 | } else { 221 | return handleFxStack(stack); 222 | } 223 | }, 224 | handleEverStack = function(stack) { 225 | var lines = stack.split(/(?:\n+|\->)/), 226 | line, 227 | i = 0, 228 | newLines = [], 229 | firstLine = lines[0], 230 | message; 231 | 232 | if (isChrome && firstLine && 233 | firstLine.search(/\bat\b/) === -1 && firstLine.search(/error/i) !== -1) { 234 | message = firstLine; 235 | lines = lines.slice(1); 236 | } 237 | 238 | if (!PromisesPanel.settings.show_full_stack) { 239 | while (i < lines.length) { 240 | line = lines[i]; 241 | ++i; 242 | 243 | if ( 244 | line && ( 245 | line.indexOf('(native)') !== -1 || 246 | line.indexOf('(:') !== -1 || 247 | line.indexOf('resource://') !== -1 || 248 | line.indexOf('jar:file://') !== -1 249 | ) 250 | ) { 251 | continue; 252 | } 253 | 254 | if (line) { 255 | newLines.push(line); 256 | } 257 | } 258 | } else { 259 | newLines = lines; 260 | } 261 | 262 | if (!newLines.length) { 263 | return null; 264 | } 265 | 266 | return { 267 | lines: newLines, 268 | message: message 269 | }; 270 | }, 271 | prepend = function(container, child) { 272 | if (container.firstChild) { 273 | container.insertBefore(child, container.firstChild); 274 | } else { 275 | container.appendChild(child); 276 | } 277 | }, 278 | doClear = function(force) { 279 | if (!force) { 280 | // stack not used promises for future use 281 | Object.keys(promises).forEach(function(key) { 282 | var promiseRecord = promisesStash[key] = promises[key]; 283 | 284 | promiseRecord.row = null; 285 | }); 286 | } else { 287 | promisesStash = {}; 288 | } 289 | 290 | promises = {}; 291 | 292 | [].slice.call(table.querySelectorAll('.pd-table-body')).forEach(function(tbody) { 293 | tbody.innerHTML = ''; 294 | }); 295 | }, 296 | getTopLevelParent = function(promiseRecord) { 297 | while (promiseRecord.parent) { 298 | promiseRecord = promiseRecord.parent; 299 | } 300 | 301 | return promiseRecord; 302 | }, 303 | sendServiceAction = function(action, message) { 304 | window.postMessage({ 305 | serviceAction: action, 306 | message: message 307 | }, '*'); 308 | }; 309 | 310 | var table = document.getElementById('promises-table'), 311 | tBodies = table.querySelectorAll('.pd-table-body'), 312 | tbodyErrors = table.querySelector('.tbody-errors'), 313 | tbodyChainErrors = table.querySelector('.tbody-chain-errors'), 314 | tbodyPending = table.querySelector('.tbody-pending'), 315 | tbodySuccess = table.querySelector('.tbody-success'); 316 | 317 | var promises = {}, 318 | promisesStash = {}; 319 | 320 | var actions = { 321 | show_need_reload: function() { 322 | var needReload = document.getElementById('need-reload'), 323 | content = document.getElementById('content'); 324 | 325 | toggleAttach.removeAttribute('selected'); 326 | 327 | needReload.hidden = false; 328 | content.hidden = true; 329 | }, 330 | show_not_attached: function() { 331 | toggleAttach.removeAttribute('selected'); 332 | }, 333 | show_main: function() { 334 | var needReload = document.getElementById('need-reload'), 335 | content = document.getElementById('content'); 336 | 337 | needReload.hidden = true; 338 | content.hidden = false; 339 | }, 340 | update_data: function(message) { 341 | var event = message.event; 342 | 343 | if (dataUpdates.hasOwnProperty(event)) { 344 | dataUpdates[event](message); 345 | } 346 | }, 347 | reload: function() { 348 | doClear(true); 349 | } 350 | }, 351 | dataUpdates = { 352 | create: function(message) { 353 | var data = message.data; 354 | 355 | var topLevel = !!data.topLevel; 356 | 357 | var handledStack = data.handledStack = handleStack(data.stack); 358 | 359 | if (!handledStack) { 360 | // this is due this bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1085124 361 | // in short firefox calls Promise.then from native code with native 362 | // functions in the end promises chain (for debug?) 363 | return; 364 | } 365 | 366 | var row = createPromiseRow(), 367 | name = getNameFromStack(data.handledStack, 2, data.name), 368 | stackCont = document.createElement('div'), 369 | chainCont = document.createElement('div'); 370 | 371 | var promiseRecord = promises[data.id] = { 372 | id: data.id, 373 | row: row, 374 | data: data, 375 | topLevel: topLevel, 376 | branches: [], 377 | _chaingLength: 0, 378 | get chaingLength() { 379 | return this._chaingLength; 380 | }, 381 | set chaingLength(val) { 382 | var parentRecord = this.parent; 383 | 384 | this._chaingLength = val; 385 | 386 | if (parentRecord && parentRecord.chaingLength < val + 1) { 387 | parentRecord.chaingLength = val + 1; 388 | parentRecord.mostChaing = this; 389 | } 390 | 391 | if (this.branches.length > 1) { 392 | val = val + ' [' + this.branches.length + ']'; 393 | } 394 | 395 | this.row.chain.textContent = val; 396 | }, 397 | _mostChaing: null, 398 | set mostChaing(val) { 399 | this._mostChaing = val; 400 | }, 401 | get mostChaing() { 402 | return this._mostChaing || this.branches[0] || null; 403 | } 404 | }; 405 | 406 | if (!topLevel) { 407 | var parentRecord = promiseRecord.parent = promises[data.parentPromise]; 408 | 409 | if (parentRecord) { 410 | parentRecord.branches.push(promiseRecord); 411 | 412 | if (!parentRecord.chaingLength) { 413 | parentRecord.chaingLength = 1; 414 | } else if (parentRecord.branches.length > 1) { 415 | parentRecord.row.chain.textContent = parentRecord.chaingLength + 416 | ' [' + parentRecord.branches.length + ']'; 417 | } 418 | } 419 | } 420 | 421 | // chainCont.hidden = true; 422 | // row.extend.appendChild(chainCont) 423 | 424 | row.chain.style.cursor = 'pointer'; 425 | row.chain.addEventListener('click', function(e) { 426 | if (!promiseRecord.chaingLength) return; 427 | 428 | e.preventDefault(); 429 | e.stopPropagation(); 430 | 431 | chainCont.innerHTML = ''; 432 | 433 | var wrap = document.createElement('div'); 434 | 435 | wrap.className = 'ui segment'; 436 | 437 | var branches = document.createElement('div'); 438 | branches.className = 'ui menu pd-branches'; 439 | 440 | var branchesTitle = document.createElement('a'); 441 | branchesTitle.className = 'item pd-branch-item pd-branches-title disabled'; 442 | branchesTitle.textContent = 'Branches'; 443 | 444 | branches.appendChild(branchesTitle); 445 | 446 | var lastActiveItem; 447 | 448 | promiseRecord.branches.forEach(function(promise, i) { 449 | var item = document.createElement('a'); 450 | item.className = 'item pd-branch-item' + 451 | (promise === promiseRecord.mostChaing ? ' active' : ''); 452 | item.textContent = (promise.chaingLength | 0) + 1; 453 | branches.appendChild(item); 454 | 455 | if (promise === promiseRecord.mostChaing) { 456 | lastActiveItem = item; 457 | } 458 | 459 | item.addEventListener('click', function() { 460 | if (item.classList.contains('active')) return; 461 | 462 | lastActiveItem.classList.remove('active'); 463 | lastActiveItem = item; 464 | item.classList.add('active'); 465 | 466 | drawBranch(promise); 467 | }); 468 | }); 469 | 470 | wrap.appendChild(branches); 471 | 472 | var branchTarget = document.createElement('div'); 473 | wrap.appendChild(branchTarget); 474 | 475 | var drawBranch = function(useRecord) { 476 | branchTarget.innerHTML = ''; 477 | 478 | do { 479 | branchTarget.appendChild(useRecord.row.item); 480 | } while (useRecord = useRecord.mostChaing); 481 | }; 482 | 483 | drawBranch(promiseRecord.mostChaing); 484 | 485 | chainCont.appendChild(wrap); 486 | 487 | toggleExtendBlock(row.extend, chainCont); 488 | }); 489 | 490 | // stackCont.hidden = true; 491 | stackCont.innerHTML = '
'; 492 | (function(cont) { 493 | // var stack = data.stack.split(/\n/).slice(1), 494 | var fragment = document.createDocumentFragment(); 495 | 496 | handledStack.lines.forEach(function(line) { 497 | var resource = parseAndGerRerouceLink(line), 498 | div = document.createElement('div'); 499 | 500 | div.appendChild(resource); 501 | 502 | fragment.appendChild(div); 503 | }); 504 | 505 | cont.appendChild(fragment); 506 | }(stackCont.firstChild)); 507 | 508 | // row.extend.appendChild(stackCont); 509 | 510 | row.name.style.cursor = 'pointer'; 511 | row.name.addEventListener('click', function(e) { 512 | e.preventDefault(); 513 | e.stopPropagation(); 514 | 515 | toggleExtendBlock(row.extend, stackCont); 516 | // stackCont.hidden = !stackCont.hidden; 517 | }); 518 | 519 | row.id.textContent = data.id; 520 | row.name.appendChild(name); 521 | row.status.textContent = 'pending'; 522 | row.value.textContent = 'none' 523 | // row.chain.textContent = promiseRecord.chaingLength; 524 | 525 | // if (topLevel) { 526 | prepend(tbodyPending, row.item); 527 | checkTbody(tbodyPending); 528 | // } 529 | }, 530 | value: function(message) { 531 | var data = message.data; 532 | 533 | var promise = promises[data.id]; 534 | 535 | if (!promise || !promise.row) return; 536 | 537 | if (data.state === 'error') { 538 | promise.row.cont.classList.add('negative'); 539 | promise.row.status.textContent = 'rejected'; 540 | 541 | if (promise.topLevel) { 542 | prepend(tbodyErrors, promise.row.item); 543 | checkTbody(tbodyErrors); 544 | } else { 545 | var topLevelParent = getTopLevelParent(promise); 546 | 547 | if (topLevelParent.state !== 'error') { 548 | topLevelParent.row.cont.classList.remove('positive'); 549 | topLevelParent.row.cont.classList.add('warning'); 550 | prepend(tbodyChainErrors, topLevelParent.row.item); 551 | checkTbody(tbodyChainErrors); 552 | } 553 | } 554 | } else { 555 | promise.row.cont.classList.add('positive'); 556 | promise.row.status.textContent = 'fullfiled'; 557 | 558 | if (promise.topLevel) { 559 | prepend(tbodySuccess, promise.row.item); 560 | checkTbody(tbodySuccess); 561 | } 562 | } 563 | 564 | if (!promise.topLevel) { 565 | try { 566 | tbodyPending.removeChild(promise.row.item); 567 | checkTbody(tbodyPending); 568 | } catch (e) {} 569 | } 570 | 571 | promise.value = data.value; 572 | promise.state = data.state; 573 | 574 | var textVal, 575 | htmlVal; 576 | 577 | switch (data.value.type) { 578 | case 'primitive': { 579 | textVal = data.value.primitive + ': ' + data.value.value; 580 | 581 | var div = document.createElement('div'); 582 | 583 | div.className = 'primitive-value'; 584 | div.textContent = textVal; 585 | 586 | promise.row.value.innerHTML = ''; 587 | promise.row.value.appendChild(div); 588 | }; break; 589 | case 'keys': { 590 | var keys = data.value.allKeys; 591 | 592 | textVal = 'Object: { ' + keys.join(', ') + ' }'; 593 | 594 | var div = document.createElement('div'); 595 | 596 | div.className = 'keys-value'; 597 | div.textContent = textVal; 598 | 599 | promise.row.value.innerHTML = ''; 600 | promise.row.value.appendChild(div); 601 | }; break; 602 | case 'object': { 603 | textVal = data.value.object; 604 | promise.row.value.textContent = textVal; 605 | }; break; 606 | case 'error': (function() { 607 | var handledStack = handleStack(data.value.error.stack), 608 | errMessage = (handledStack.message || data.value.error.message || 'Error:'); 609 | 610 | var val = ' ' + errMessage + ' ', 611 | wrap = document.createElement('div'), 612 | errorCont = document.createElement('div'); 613 | 614 | wrap.role = 'button'; 615 | wrap.className = 'pd-show-error'; 616 | wrap.innerHTML = val; 617 | 618 | wrap.appendChild(getNameFromStack(handledStack, 1)); 619 | 620 | wrap.style.cursor = 'pointer'; 621 | wrap.addEventListener('click', function(e) { 622 | e.preventDefault(); 623 | e.stopPropagation(); 624 | 625 | // errorCont.hidden = !errorCont.hidden; 626 | toggleExtendBlock(promise.row.extend, errorCont); 627 | }); 628 | 629 | // errorCont.hidden = true; 630 | errorCont.innerHTML = '
'; 631 | 632 | (function(cont) { 633 | var fragment = document.createDocumentFragment(); 634 | 635 | var firstDiv = document.createElement('div'); 636 | 637 | firstDiv.textContent = errMessage; 638 | fragment.appendChild(firstDiv); 639 | 640 | handledStack.lines.forEach(function(line) { 641 | var resource = parseAndGerRerouceLink(line), 642 | div = document.createElement('div'); 643 | 644 | div.style.marginLeft = '20px'; 645 | div.appendChild(resource); 646 | 647 | fragment.appendChild(div); 648 | }); 649 | 650 | cont.appendChild(fragment); 651 | }(errorCont.firstChild)); 652 | 653 | // promise.row.extend.appendChild(errorCont); 654 | 655 | promise.row.value.innerHTML = ''; 656 | promise.row.value.appendChild(wrap); 657 | 658 | promise.row.value.classList.add('error'); 659 | }()); break; 660 | case 'function': { 661 | textVal = data.value.function; 662 | promise.row.value.textContent = textVal; 663 | }; break; 664 | default: { 665 | // textVal = JSON.stringify(data.value); 666 | textVal = data.value.type; 667 | promise.row.value.textContent = textVal; 668 | } 669 | } 670 | }, 671 | shoot_toplevel: function(message) { 672 | var data = message.data; 673 | var promise = promises[data.id]; 674 | 675 | console.log('shoot_toplevel', promise.topLevel); 676 | 677 | if (!promise || !promise.topLevel) return; 678 | 679 | promise.topLevel = false; 680 | 681 | var parentElement = promise.row.item.parentElement; 682 | 683 | if (parentElement) { 684 | parentElement.removeChild(promise.row.item); 685 | checkTbody(parentElement); 686 | } 687 | } 688 | }; 689 | 690 | window.addEventListener('message', function(e) { 691 | var data = e.data; 692 | 693 | if (data && data.action && actions[data.action]) { 694 | actions[data.action](data.message); 695 | } 696 | }); 697 | 698 | window.PromisesPanel = { 699 | updateSetting: function(setting, value) { 700 | var settingsActors = PromisesPanel.settingsActors; 701 | 702 | if (settingsActors.hasOwnProperty(setting)) { 703 | settingsActors[setting](value); 704 | } 705 | }, 706 | settingsActors: { 707 | show_full_stack: function(value) { 708 | PromisesPanel.settings.show_full_stack = !!value; 709 | } 710 | }, 711 | settings: { 712 | show_full_stack: false 713 | } 714 | }; 715 | }()); -------------------------------------------------------------------------------- /shared/providers/es6.js: -------------------------------------------------------------------------------- 1 | if (!global.Promise) return false; 2 | 3 | var isChrome = typeof chrome !== 'undefined'; 4 | 5 | var getDesc = Object.getOwnPropertyDescriptor, 6 | hasOwn = Object.prototype.hasOwnProperty, 7 | recurciveGetDesc = function(object, prop) { 8 | do { 9 | var result = getDesc(object, prop); 10 | } while (!result && (object = Object.getPrototypeOf(object))); 11 | 12 | return result; 13 | }; 14 | 15 | var promiseByProxy = new WeakMap(), 16 | provider = PromisesDebugger.registerProvider('es6', {}); 17 | 18 | var promiseDesc = recurciveGetDesc(global, 'Promise'), 19 | originalPromise = global.Promise, 20 | originalPromiseResolve = recurciveGetDesc(originalPromise, 'resolve'), 21 | originalPromiseReject = recurciveGetDesc(originalPromise, 'reject'), 22 | originalPromiseAll = recurciveGetDesc(originalPromise, 'all'), 23 | originalPromiseRace = recurciveGetDesc(originalPromise, 'race'), 24 | PromiseForInstanceCheck; 25 | 26 | if (!global.Proxy) { 27 | var PromiseProxy = (function() { 28 | var PromiseConstructor; 29 | var PromiseProxy = function(target, params) { 30 | if (typeof target === 'function') { 31 | return (PromiseConstructor = ProxyPromiseConstructor(target, params)); 32 | } else if (target instanceof originalPromise) { 33 | return ProxyPromiseInstance(target, params); 34 | } 35 | }; 36 | 37 | var ProxyPromiseInstance = function(target, params) { 38 | // target is instance of Promise 39 | 40 | var proxy = Object.create(PromiseConstructor.prototype); 41 | // target.proxyInstance = proxy; 42 | proxy.promise = target; 43 | 44 | if (params.get) { 45 | var thenDesc = recurciveGetDesc(target, 'then'), 46 | catchDesc = recurciveGetDesc(target, 'catch'); 47 | 48 | Object.defineProperties(proxy, { 49 | then: { 50 | enumerable: thenDesc.enumerable, 51 | configurable: thenDesc.configurable, 52 | get: function() { 53 | return params.get(target, 'then') 54 | } 55 | }, 56 | catch: { 57 | enumerable: catchDesc.enumerable, 58 | configurable: catchDesc.configurable, 59 | get: function() { 60 | return params.get(target, 'catch') 61 | } 62 | } 63 | }); 64 | } 65 | 66 | return proxy; 67 | }; 68 | 69 | var ProxyPromiseConstructor = function(target, params) { 70 | if (params.construct) { 71 | // This is proxy constructor 72 | var proxy = function PromiseProxy(executor) { 73 | var proxyInstance = params.construct(target, arguments); 74 | 75 | if (proxyInstance.promise) { 76 | this.promise = proxyInstance.promise; 77 | } 78 | 79 | return proxyInstance; 80 | }; 81 | } else { 82 | var proxy = function() {}; 83 | } 84 | 85 | if (params.get) { 86 | var PromiseConstructorPropGetter = function(prop) { 87 | var desc = recurciveGetDesc(target, prop); 88 | 89 | return { 90 | get: function() { 91 | return params.get(target, prop, target); 92 | }, 93 | configurable: desc.configurable, 94 | enumerable: desc.enumerable 95 | }; 96 | }; 97 | 98 | Object.defineProperties(proxy, { 99 | resolve: PromiseConstructorPropGetter('resolve'), 100 | reject: PromiseConstructorPropGetter('reject'), 101 | all: PromiseConstructorPropGetter('all'), 102 | race: PromiseConstructorPropGetter('race') 103 | }); 104 | } 105 | 106 | return proxy; 107 | }; 108 | 109 | return PromiseProxy; 110 | }()); 111 | } 112 | 113 | var makeProxy = function(target, params) { 114 | if (global.Proxy) { 115 | return new Proxy(target, params); 116 | } else { 117 | return PromiseProxy(target, params); 118 | } 119 | }; 120 | 121 | var promiseWrap = function(promise, registeredData) { 122 | var doThen = function(onResolve, onReject, caller) { 123 | var resolve = function(val) { 124 | chaingRegistered.setValue({ 125 | type: 'value', 126 | value: val 127 | }); 128 | 129 | return val; 130 | }, 131 | reject = function(val) { 132 | chaingRegistered.setValue({ 133 | type: 'error', 134 | value: val 135 | }); 136 | 137 | provider.reportError({ 138 | value: val 139 | }); 140 | 141 | return val; 142 | }; 143 | 144 | if (onResolve == null) { 145 | onResolve = function() {}; 146 | } else if (typeof onResolve !== 'function' && false) { 147 | throw new TypeError('...'); 148 | } 149 | 150 | if (onReject == null) { 151 | onReject = function() {}; 152 | } else if (typeof onReject !== 'function' && false) { 153 | throw new TypeError('...'); 154 | } 155 | 156 | function onResolveWrap(val) { 157 | if (isChrome) { 158 | var ret = new originalPromise(function(retResolve, DONT_USE) { 159 | setTimeout(function() { 160 | ret.then(function(a) { 161 | resolve(a); 162 | }, function(b) { 163 | reject(b); 164 | }); 165 | }, 0); 166 | 167 | var retVal = onResolve.call(promise, val); 168 | 169 | retResolve(retVal); 170 | }); 171 | 172 | return ret; 173 | } 174 | 175 | var retVal = onResolve.call(promise, val); 176 | 177 | // resolve(retVal); 178 | return retVal; 179 | } 180 | 181 | function onRejectWrap(val) { 182 | if (isChrome) { 183 | var ret = new originalPromise(function(retResolve, DONT_USE) { 184 | setTimeout(function() { 185 | ret.then(function(a) { 186 | resolve(a); 187 | }, function(b) { 188 | reject(b); 189 | }); 190 | }, 0); 191 | 192 | var retVal = onReject.call(promise, val); 193 | 194 | retResolve(retVal); 195 | }); 196 | 197 | return ret; 198 | } 199 | 200 | var val = onReject.call(promise, val); 201 | // reject(val); 202 | return val; 203 | } 204 | 205 | var result = promise.then(onResolveWrap, onRejectWrap); 206 | 207 | try { 208 | throw new Error(); 209 | } catch (e) { 210 | var stack = e.stack; 211 | } 212 | 213 | if (!isChrome) { 214 | result.then(resolve, reject); 215 | } 216 | 217 | caller = 'Promise.' + caller + '()'; 218 | 219 | var chaingRegistered = provider.register(result, { 220 | topLevel: false, 221 | stack: stack, 222 | parent: registeredData, 223 | caller: caller 224 | }); 225 | 226 | return promiseWrap(result, chaingRegistered); 227 | }, 228 | thenWrap = function(onResolve, onReject) { 229 | return doThen.call(this, onResolve, onReject, 'then'); 230 | }, 231 | catchWrap = function(onReject) { 232 | return doThen.call(this, null, onReject, 'catch'); 233 | }; 234 | 235 | var proxy = makeProxy(promise, { 236 | get: function(target, name) { 237 | if (name === 'then') return thenWrap; 238 | if (name === 'catch') return catchWrap; 239 | 240 | return target[name]; 241 | } 242 | }); 243 | 244 | promiseByProxy.set(proxy, promise); 245 | 246 | return proxy; 247 | }; 248 | 249 | Object.defineProperty(global, 'Promise', { 250 | value: PromiseForInstanceCheck = makeProxy(originalPromise, { 251 | construct: function(Promise, args) { 252 | var executor = args[0], 253 | registerValue, 254 | name = executor.name; 255 | 256 | if (typeof executor !== 'function') throw new TypeError('...'); 257 | 258 | var promise = new Promise(function(resolve, reject) { 259 | var result = executor.call(this, function resolveWrap(val) { 260 | /*var value = { 261 | type: 'value', 262 | value: val 263 | }; 264 | 265 | if (registeredData) { 266 | registeredData.setValue(value); 267 | } else { 268 | registerValue = value; 269 | }*/ 270 | 271 | if (registerValue) return; 272 | 273 | return resolve.call(this, val); 274 | }, function rejectWrap(val) { 275 | /*var value = { 276 | type: 'error', 277 | value: val 278 | }; 279 | 280 | if (registeredData) { 281 | registeredData.setValue(value); 282 | } else { 283 | registerValue = value; 284 | }*/ 285 | 286 | if (registerValue) return; 287 | 288 | return reject.call(this, val); 289 | }); 290 | 291 | return result; 292 | }); 293 | 294 | try { 295 | throw new Error(); 296 | } catch (e) { 297 | var stack = e.stack; 298 | } 299 | 300 | var caller = 'new Promise()'; 301 | 302 | var registeredData = provider.register(promise, { 303 | stack: stack, 304 | caller: caller, 305 | name: name 306 | }); 307 | 308 | promise.then(function(val) { 309 | var value = { 310 | type: 'value', 311 | value: val 312 | }; 313 | 314 | registerValue = value; 315 | 316 | registeredData.setValue(value); 317 | }, function(val) { 318 | var value = { 319 | type: 'error', 320 | value: val 321 | }; 322 | 323 | registerValue = value; 324 | 325 | provider.reportError({ 326 | value: val 327 | }); 328 | 329 | registeredData.setValue(value); 330 | }); 331 | 332 | /*if (registerValue) { 333 | registeredData.setValue(registerValue); 334 | }*/ 335 | 336 | return promiseWrap(promise, registeredData); 337 | }, 338 | get: function(target, name) { 339 | // console.log('get', arguments); 340 | 341 | if (fake.hasOwnProperty(name)) { 342 | return fake[name]; 343 | } 344 | 345 | return target[name]; 346 | } 347 | }), 348 | enumerable: promiseDesc.enumerable, 349 | configurable: promiseDesc.configurable, 350 | writable: promiseDesc.writable 351 | }); 352 | 353 | var fake = {}; 354 | 355 | fake.resolve = function(val) { 356 | var result = originalPromiseResolve.value.call(originalPromise, val); 357 | 358 | try { 359 | throw new Error(); 360 | } catch (e) { 361 | var stack = e.stack; 362 | } 363 | 364 | var registeredData = provider.register(result, { 365 | stack: stack, 366 | caller: 'Promise.resolve()' 367 | }); 368 | 369 | result.then(function(val) { 370 | registeredData.setValue({ 371 | type: 'value', 372 | value: val 373 | }); 374 | }); 375 | 376 | return promiseWrap(result, registeredData); 377 | }; 378 | 379 | fake.reject = function(val) { 380 | var result = originalPromiseReject.value.call(originalPromise, val); 381 | 382 | try { 383 | throw new Error(); 384 | } catch (e) { 385 | var stack = e.stack; 386 | } 387 | 388 | var registeredData = provider.register(result, { 389 | stack: stack, 390 | caller: 'Promise.reject()' 391 | }); 392 | 393 | result.then(null, function(val) { 394 | registeredData.setValue({ 395 | type: 'error', 396 | value: val 397 | }); 398 | 399 | provider.reportError({ 400 | value: val 401 | }); 402 | }); 403 | 404 | return promiseWrap(result, registeredData); 405 | }; 406 | 407 | fake.all = function(arr) { 408 | arr = arr.map(function(proxy) { 409 | return promiseByProxy.get(proxy); 410 | }); 411 | 412 | var result = originalPromiseAll.value.call(originalPromise, arr); 413 | 414 | try { 415 | throw new Error(); 416 | } catch (e) { 417 | var stack = e.stack; 418 | } 419 | 420 | var registeredData = provider.register(result, { 421 | stack: stack, 422 | caller: 'Promise.all()' 423 | }); 424 | 425 | result.then(function(val) { 426 | registeredData.setValue({ 427 | type: 'value', 428 | value: val 429 | }); 430 | }, function(val) { 431 | registeredData.setValue({ 432 | type: 'error', 433 | value: val 434 | }); 435 | }); 436 | 437 | return promiseWrap(result, registeredData); 438 | }; 439 | 440 | fake.race = function(arr) { 441 | arr = arr.map(function(proxy) { 442 | return promiseByProxy.get(proxy); 443 | }); 444 | 445 | var result = originalPromiseRace.value.call(originalPromise, arr); 446 | 447 | try { 448 | throw new Error(); 449 | } catch (e) { 450 | var stack = e.stack; 451 | } 452 | 453 | var registeredData = provider.register(result, { 454 | stack: stack, 455 | caller: 'Promise.race()' 456 | }); 457 | 458 | result.then(function(val) { 459 | registeredData.setValue({ 460 | type: 'value', 461 | value: val 462 | }); 463 | }, function(val) { 464 | registeredData.setValue({ 465 | type: 'error', 466 | value: val 467 | }); 468 | }); 469 | 470 | return promiseWrap(result, registeredData); 471 | }; 472 | 473 | provider.isPromise = function(value) { 474 | if (value instanceof originalPromise || 475 | value instanceof PromiseForInstanceCheck 476 | ) { 477 | value = promiseByProxy.get(value) || value; 478 | return value; 479 | } 480 | }; -------------------------------------------------------------------------------- /shared/ui/fonts/basic.icons.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NekR/PromisesDebuggerExtension/b95a1f0cf7e2bb26ebb6a605786213e669b06bbb/shared/ui/fonts/basic.icons.eot -------------------------------------------------------------------------------- /shared/ui/fonts/basic.icons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NekR/PromisesDebuggerExtension/b95a1f0cf7e2bb26ebb6a605786213e669b06bbb/shared/ui/fonts/basic.icons.ttf -------------------------------------------------------------------------------- /shared/ui/fonts/basic.icons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NekR/PromisesDebuggerExtension/b95a1f0cf7e2bb26ebb6a605786213e669b06bbb/shared/ui/fonts/basic.icons.woff -------------------------------------------------------------------------------- /shared/ui/fonts/icons.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NekR/PromisesDebuggerExtension/b95a1f0cf7e2bb26ebb6a605786213e669b06bbb/shared/ui/fonts/icons.eot -------------------------------------------------------------------------------- /shared/ui/fonts/icons.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NekR/PromisesDebuggerExtension/b95a1f0cf7e2bb26ebb6a605786213e669b06bbb/shared/ui/fonts/icons.otf -------------------------------------------------------------------------------- /shared/ui/fonts/icons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NekR/PromisesDebuggerExtension/b95a1f0cf7e2bb26ebb6a605786213e669b06bbb/shared/ui/fonts/icons.ttf -------------------------------------------------------------------------------- /shared/ui/fonts/icons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NekR/PromisesDebuggerExtension/b95a1f0cf7e2bb26ebb6a605786213e669b06bbb/shared/ui/fonts/icons.woff -------------------------------------------------------------------------------- /shared/ui/images/loader-large-inverted.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NekR/PromisesDebuggerExtension/b95a1f0cf7e2bb26ebb6a605786213e669b06bbb/shared/ui/images/loader-large-inverted.gif -------------------------------------------------------------------------------- /shared/ui/images/loader-large.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NekR/PromisesDebuggerExtension/b95a1f0cf7e2bb26ebb6a605786213e669b06bbb/shared/ui/images/loader-large.gif -------------------------------------------------------------------------------- /shared/ui/images/loader-medium-inverted.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NekR/PromisesDebuggerExtension/b95a1f0cf7e2bb26ebb6a605786213e669b06bbb/shared/ui/images/loader-medium-inverted.gif -------------------------------------------------------------------------------- /shared/ui/images/loader-medium.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NekR/PromisesDebuggerExtension/b95a1f0cf7e2bb26ebb6a605786213e669b06bbb/shared/ui/images/loader-medium.gif -------------------------------------------------------------------------------- /shared/ui/images/loader-mini-inverted.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NekR/PromisesDebuggerExtension/b95a1f0cf7e2bb26ebb6a605786213e669b06bbb/shared/ui/images/loader-mini-inverted.gif -------------------------------------------------------------------------------- /shared/ui/images/loader-mini.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NekR/PromisesDebuggerExtension/b95a1f0cf7e2bb26ebb6a605786213e669b06bbb/shared/ui/images/loader-mini.gif -------------------------------------------------------------------------------- /shared/ui/images/loader-small-inverted.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NekR/PromisesDebuggerExtension/b95a1f0cf7e2bb26ebb6a605786213e669b06bbb/shared/ui/images/loader-small-inverted.gif -------------------------------------------------------------------------------- /shared/ui/images/loader-small.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NekR/PromisesDebuggerExtension/b95a1f0cf7e2bb26ebb6a605786213e669b06bbb/shared/ui/images/loader-small.gif --------------------------------------------------------------------------------