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