1// Copyright 2014 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5cr.define('serviceworker', function() { 6 'use strict'; 7 8 function initialize() { 9 if (window.location.hash == "#iframe") { 10 // This page is loaded from chrome://inspect. 11 window.addEventListener('message', onMessage.bind(this), false); 12 } 13 update(); 14 } 15 16 function onMessage(event) { 17 if (event.origin != 'chrome://inspect') { 18 return; 19 } 20 sendCommand(event.data.action, event.data.worker); 21 } 22 23 function update() { 24 chrome.send('GetOptions'); 25 chrome.send('getAllRegistrations'); 26 } 27 28 function onOptions(options) { 29 var template; 30 var container = $('serviceworker-options'); 31 if (container.childNodes) { 32 template = container.childNodes[0]; 33 } 34 if (!template) { 35 template = jstGetTemplate('serviceworker-options-template'); 36 container.appendChild(template); 37 } 38 jstProcess(new JsEvalContext(options), template); 39 var inputs = container.querySelectorAll('input[type=\'checkbox\']'); 40 for (var i = 0; i < inputs.length; ++i) { 41 if (!inputs[i].hasClickEvent) { 42 inputs[i].addEventListener('click', (function(event) { 43 chrome.send('SetOption', 44 [event.target.className, event.target.checked]); 45 }).bind(this), false); 46 inputs[i].hasClickEvent = true; 47 } 48 } 49 } 50 51 function progressNodeFor(link) { 52 return link.parentNode.querySelector('.operation-status'); 53 } 54 55 // All commands are completed with 'onOperationComplete'. 56 var COMMANDS = ['stop', 'sync', 'push', 'inspect', 'unregister', 'start']; 57 function commandHandler(command) { 58 return function(event) { 59 var link = event.target; 60 progressNodeFor(link).style.display = 'inline'; 61 sendCommand(command, link.cmdArgs, (function(status) { 62 progressNodeFor(link).style.display = 'none'; 63 }).bind(null, link)); 64 return false; 65 }; 66 }; 67 68 var commandCallbacks = []; 69 function sendCommand(command, args, callback) { 70 var callbackId = 0; 71 while (callbackId in commandCallbacks) { 72 callbackId++; 73 } 74 commandCallbacks[callbackId] = callback; 75 chrome.send(command, [callbackId, args]); 76 } 77 78 // Fired from the backend after the command call has completed. 79 function onOperationComplete(status, callbackId) { 80 var callback = commandCallbacks[callbackId]; 81 delete commandCallbacks[callbackId]; 82 if (callback) { 83 callback(status); 84 } 85 update(); 86 } 87 88 // Send the active ServiceWorker information to chrome://inspect. 89 function sendToInspectPage(live_registrations, 90 partition_id) { 91 var workers = []; 92 live_registrations.forEach(function(registration) { 93 [registration.active, registration.waiting].forEach(function(version) { 94 if (!version || version.running_status != 'RUNNING') { 95 return; 96 } 97 workers.push({ 98 'scope': registration.scope, 99 'url': registration.script_url, 100 'partition_id': partition_id, 101 'version_id': version.version_id, 102 'process_id': version.process_id, 103 'devtools_agent_route_id': 104 version.devtools_agent_route_id 105 }); 106 }); 107 }); 108 window.parent.postMessage( 109 {'partition_id': partition_id, 'workers': workers}, 110 'chrome://inspect'); 111 } 112 113 var allLogMessages = {}; 114 // Set log for a worker version. 115 function fillLogForVersion(partition_id, version) { 116 if (!version) { 117 return; 118 } 119 if (!(partition_id in allLogMessages)) { 120 allLogMessages[partition_id] = {}; 121 } 122 var logMessages = allLogMessages[partition_id]; 123 if (version.version_id in logMessages) { 124 version.log = logMessages[version.version_id]; 125 } else { 126 version.log = ''; 127 } 128 } 129 130 // Get the unregistered workers. 131 // |unregistered_registrations| will be filled with the registrations which 132 // are in |live_registrations| but not in |stored_registrations|. 133 // |unregistered_versions| will be filled with the versions which 134 // are in |live_versions| but not in |stored_registrations| nor in 135 // |live_registrations|. 136 function getUnregisteredWorkers(stored_registrations, 137 live_registrations, 138 live_versions, 139 unregistered_registrations, 140 unregistered_versions) { 141 var registration_id_set = {}; 142 var version_id_set = {}; 143 stored_registrations.forEach(function(registration) { 144 registration_id_set[registration.registration_id] = true; 145 }); 146 [stored_registrations, live_registrations].forEach(function(registrations) { 147 registrations.forEach(function(registration) { 148 [registration.active, registration.waiting].forEach(function(version) { 149 if (version) { 150 version_id_set[version.version_id] = true; 151 } 152 }); 153 }); 154 }); 155 live_registrations.forEach(function(registration) { 156 if (!registration_id_set[registration.registration_id]) { 157 registration.unregistered = true; 158 unregistered_registrations.push(registration); 159 } 160 }); 161 live_versions.forEach(function(version) { 162 if (!version_id_set[version.version_id]) { 163 unregistered_versions.push(version); 164 } 165 }); 166 } 167 168 // Fired once per partition from the backend. 169 function onPartitionData(live_registrations, 170 live_versions, 171 stored_registrations, 172 partition_id, 173 partition_path) { 174 if (window.location.hash == "#iframe") { 175 // This page is loaded from chrome://inspect. 176 sendToInspectPage(live_registrations, partition_id); 177 return; 178 } 179 var unregistered_registrations = []; 180 var unregistered_versions = []; 181 getUnregisteredWorkers(stored_registrations, 182 live_registrations, 183 live_versions, 184 unregistered_registrations, 185 unregistered_versions); 186 var template; 187 var container = $('serviceworker-list'); 188 // Existing templates are keyed by partition_id. This allows 189 // the UI to be updated in-place rather than refreshing the 190 // whole page. 191 for (var i = 0; i < container.childNodes.length; ++i) { 192 if (container.childNodes[i].partition_id == partition_id) { 193 template = container.childNodes[i]; 194 } 195 } 196 // This is probably the first time we're loading. 197 if (!template) { 198 template = jstGetTemplate('serviceworker-list-template'); 199 container.appendChild(template); 200 } 201 var fillLogFunc = fillLogForVersion.bind(this, partition_id); 202 stored_registrations.forEach(function(registration) { 203 [registration.active, registration.waiting].forEach(fillLogFunc); 204 }); 205 unregistered_registrations.forEach(function(registration) { 206 [registration.active, registration.waiting].forEach(fillLogFunc); 207 }); 208 unregistered_versions.forEach(fillLogFunc); 209 jstProcess(new JsEvalContext({ 210 stored_registrations: stored_registrations, 211 unregistered_registrations: unregistered_registrations, 212 unregistered_versions: unregistered_versions, 213 partition_id: partition_id, 214 partition_path: partition_path}), 215 template); 216 for (var i = 0; i < COMMANDS.length; ++i) { 217 var handler = commandHandler(COMMANDS[i]); 218 var links = container.querySelectorAll('button.' + COMMANDS[i]); 219 for (var j = 0; j < links.length; ++j) { 220 if (!links[j].hasClickEvent) { 221 links[j].addEventListener('click', handler, false); 222 links[j].hasClickEvent = true; 223 } 224 } 225 } 226 } 227 228 function onWorkerStarted(partition_id, version_id, process_id, thread_id) { 229 update(); 230 } 231 232 function onWorkerStopped(partition_id, version_id, process_id, thread_id) { 233 update(); 234 } 235 236 function onErrorReported(partition_id, 237 version_id, 238 process_id, 239 thread_id, 240 error_info) { 241 outputLogMessage(partition_id, 242 version_id, 243 'Error: ' + JSON.stringify(error_info) + '\n'); 244 } 245 246 function onConsoleMessageReported(partition_id, 247 version_id, 248 process_id, 249 thread_id, 250 message) { 251 outputLogMessage(partition_id, 252 version_id, 253 'Console: ' + JSON.stringify(message) + '\n'); 254 } 255 256 function onVersionStateChanged(partition_id, version_id) { 257 update(); 258 } 259 260 function onRegistrationStored(scope) { 261 update(); 262 } 263 264 function onRegistrationDeleted(scope) { 265 update(); 266 } 267 268 function outputLogMessage(partition_id, version_id, message) { 269 if (!(partition_id in allLogMessages)) { 270 allLogMessages[partition_id] = {}; 271 } 272 var logMessages = allLogMessages[partition_id]; 273 if (version_id in logMessages) { 274 logMessages[version_id] += message; 275 } else { 276 logMessages[version_id] = message; 277 } 278 279 var logAreas = document.querySelectorAll('textarea.serviceworker-log'); 280 for (var i = 0; i < logAreas.length; ++i) { 281 var logArea = logAreas[i]; 282 if (logArea.partition_id == partition_id && 283 logArea.version_id == version_id) { 284 logArea.value += message; 285 } 286 } 287 } 288 289 return { 290 initialize: initialize, 291 onOptions: onOptions, 292 onOperationComplete: onOperationComplete, 293 onPartitionData: onPartitionData, 294 onWorkerStarted: onWorkerStarted, 295 onWorkerStopped: onWorkerStopped, 296 onErrorReported: onErrorReported, 297 onConsoleMessageReported: onConsoleMessageReported, 298 onVersionStateChanged: onVersionStateChanged, 299 onRegistrationStored: onRegistrationStored, 300 onRegistrationDeleted: onRegistrationDeleted, 301 }; 302}); 303 304document.addEventListener('DOMContentLoaded', serviceworker.initialize); 305