Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(32)

Side by Side Diff: third_party/WebKit/Source/devtools/front_end/sdk/InspectorBackend.js

Issue 2600323002: DevTools: extract protocol module (Closed)
Patch Set: move inspector backend commands.js Created 3 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 /*
2 * Copyright (C) 2011 Google Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
6 * met:
7 *
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
13 * distribution.
14 * * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31 var Protocol = {};
32
33 /** @typedef {string} */
34 Protocol.Error;
35
36 /**
37 * @unrestricted
38 */
39 var InspectorBackendClass = class {
40 constructor() {
41 this._agentPrototypes = {};
42 this._dispatcherPrototypes = {};
43 this._initialized = false;
44 }
45
46 /**
47 * @param {string} error
48 * @param {!Object} messageObject
49 */
50 static reportProtocolError(error, messageObject) {
51 console.error(error + ': ' + JSON.stringify(messageObject));
52 }
53
54 /**
55 * @return {boolean}
56 */
57 isInitialized() {
58 return this._initialized;
59 }
60
61 /**
62 * @param {string} domain
63 */
64 _addAgentGetterMethodToProtocolTargetPrototype(domain) {
65 var upperCaseLength = 0;
66 while (upperCaseLength < domain.length && domain[upperCaseLength].toLowerCas e() !== domain[upperCaseLength])
67 ++upperCaseLength;
68
69 var methodName = domain.substr(0, upperCaseLength).toLowerCase() + domain.sl ice(upperCaseLength) + 'Agent';
70
71 /**
72 * @this {Protocol.TargetBase}
73 */
74 function agentGetter() {
75 return this._agents[domain];
76 }
77
78 Protocol.TargetBase.prototype[methodName] = agentGetter;
79
80 /**
81 * @this {Protocol.TargetBase}
82 */
83 function registerDispatcher(dispatcher) {
84 this.registerDispatcher(domain, dispatcher);
85 }
86
87 Protocol.TargetBase.prototype['register' + domain + 'Dispatcher'] = register Dispatcher;
88 }
89
90 /**
91 * @param {string} domain
92 * @return {!InspectorBackendClass._AgentPrototype}
93 */
94 _agentPrototype(domain) {
95 if (!this._agentPrototypes[domain]) {
96 this._agentPrototypes[domain] = new InspectorBackendClass._AgentPrototype( domain);
97 this._addAgentGetterMethodToProtocolTargetPrototype(domain);
98 }
99
100 return this._agentPrototypes[domain];
101 }
102
103 /**
104 * @param {string} domain
105 * @return {!InspectorBackendClass._DispatcherPrototype}
106 */
107 _dispatcherPrototype(domain) {
108 if (!this._dispatcherPrototypes[domain])
109 this._dispatcherPrototypes[domain] = new InspectorBackendClass._Dispatcher Prototype();
110 return this._dispatcherPrototypes[domain];
111 }
112
113 /**
114 * @param {string} method
115 * @param {!Array.<!Object>} signature
116 * @param {!Array.<string>} replyArgs
117 * @param {boolean} hasErrorData
118 */
119 registerCommand(method, signature, replyArgs, hasErrorData) {
120 var domainAndMethod = method.split('.');
121 this._agentPrototype(domainAndMethod[0]).registerCommand(domainAndMethod[1], signature, replyArgs, hasErrorData);
122 this._initialized = true;
123 }
124
125 /**
126 * @param {string} type
127 * @param {!Object} values
128 */
129 registerEnum(type, values) {
130 var domainAndName = type.split('.');
131 var domain = domainAndName[0];
132 if (!Protocol[domain])
133 Protocol[domain] = {};
134
135 Protocol[domain][domainAndName[1]] = values;
136 this._initialized = true;
137 }
138
139 /**
140 * @param {string} eventName
141 * @param {!Object} params
142 */
143 registerEvent(eventName, params) {
144 var domain = eventName.split('.')[0];
145 this._dispatcherPrototype(domain).registerEvent(eventName, params);
146 this._initialized = true;
147 }
148
149 /**
150 * @param {function(T)} clientCallback
151 * @param {string} errorPrefix
152 * @param {function(new:T,S)=} constructor
153 * @param {T=} defaultValue
154 * @return {function(?string, S)}
155 * @template T,S
156 */
157 wrapClientCallback(clientCallback, errorPrefix, constructor, defaultValue) {
158 /**
159 * @param {?string} error
160 * @param {S} value
161 * @template S
162 */
163 function callbackWrapper(error, value) {
164 if (error) {
165 console.error(errorPrefix + error);
166 clientCallback(defaultValue);
167 return;
168 }
169 if (constructor)
170 clientCallback(new constructor(value));
171 else
172 clientCallback(value);
173 }
174 return callbackWrapper;
175 }
176 };
177
178 InspectorBackendClass._ConnectionClosedErrorCode = -32000;
179 InspectorBackendClass.DevToolsStubErrorCode = -32015;
180
181
182 var InspectorBackend = new InspectorBackendClass();
183
184 /**
185 * @interface
186 */
187 InspectorBackendClass.Connection = function() {};
188
189 InspectorBackendClass.Connection.prototype = {
190 /**
191 * @param {string} message
192 */
193 sendMessage(message) {},
194
195 /**
196 * @return {!Promise}
197 */
198 disconnect() {},
199 };
200
201 /**
202 * @typedef {!{
203 * onMessage: function((!Object|string)),
204 * onDisconnect: function(string)
205 * }}
206 */
207 InspectorBackendClass.Connection.Params;
208
209 /**
210 * @typedef {function(!InspectorBackendClass.Connection.Params):!InspectorBacken dClass.Connection}
211 */
212 InspectorBackendClass.Connection.Factory;
213
214 /**
215 * @unrestricted
216 */
217 Protocol.TargetBase = class {
218 /**
219 * @param {!InspectorBackendClass.Connection.Factory} connectionFactory
220 */
221 constructor(connectionFactory) {
222 this._connection =
223 connectionFactory({onMessage: this._onMessage.bind(this), onDisconnect: this._onDisconnect.bind(this)});
224 this._lastMessageId = 1;
225 this._pendingResponsesCount = 0;
226 this._agents = {};
227 this._dispatchers = {};
228 this._callbacks = {};
229 this._initialize(InspectorBackend._agentPrototypes, InspectorBackend._dispat cherPrototypes);
230 if (!InspectorBackendClass.deprecatedRunAfterPendingDispatches)
231 InspectorBackendClass.deprecatedRunAfterPendingDispatches = this._deprecat edRunAfterPendingDispatches.bind(this);
232 if (!InspectorBackendClass.sendRawMessageForTesting)
233 InspectorBackendClass.sendRawMessageForTesting = this._sendRawMessageForTe sting.bind(this);
234 }
235
236 /**
237 * @param {!Object.<string, !InspectorBackendClass._AgentPrototype>} agentProt otypes
238 * @param {!Object.<string, !InspectorBackendClass._DispatcherPrototype>} disp atcherPrototypes
239 */
240 _initialize(agentPrototypes, dispatcherPrototypes) {
241 for (var domain in agentPrototypes) {
242 this._agents[domain] = Object.create(agentPrototypes[domain]);
243 this._agents[domain].setTarget(this);
244 }
245
246 for (var domain in dispatcherPrototypes)
247 this._dispatchers[domain] = Object.create(dispatcherPrototypes[domain]);
248 }
249
250 /**
251 * @return {number}
252 */
253 _nextMessageId() {
254 return this._lastMessageId++;
255 }
256
257 /**
258 * @param {string} domain
259 * @return {!InspectorBackendClass._AgentPrototype}
260 */
261 _agent(domain) {
262 return this._agents[domain];
263 }
264
265 /**
266 * @param {string} domain
267 * @param {string} method
268 * @param {?Object} params
269 * @param {?function(*)} callback
270 */
271 _wrapCallbackAndSendMessageObject(domain, method, params, callback) {
272 if (!this._connection) {
273 if (callback)
274 this._dispatchConnectionErrorResponse(domain, method, callback);
275 return;
276 }
277
278 var messageObject = {};
279 var messageId = this._nextMessageId();
280 messageObject.id = messageId;
281 messageObject.method = method;
282 if (params)
283 messageObject.params = params;
284
285 var wrappedCallback = this._wrap(callback, domain, method);
286 var message = JSON.stringify(messageObject);
287
288 if (InspectorBackendClass.Options.dumpInspectorProtocolMessages)
289 this._dumpProtocolMessage('frontend: ' + message);
290
291 this._connection.sendMessage(message);
292 ++this._pendingResponsesCount;
293 this._callbacks[messageId] = wrappedCallback;
294 }
295
296 /**
297 * @param {?function(*)} callback
298 * @param {string} method
299 * @param {string} domain
300 * @return {function(*)}
301 */
302 _wrap(callback, domain, method) {
303 if (!callback)
304 callback = function() {};
305
306 callback.methodName = method;
307 callback.domain = domain;
308 if (InspectorBackendClass.Options.dumpInspectorTimeStats)
309 callback.sendRequestTime = Date.now();
310
311 return callback;
312 }
313
314 /**
315 * @param {string} method
316 * @param {?Object} params
317 * @param {?function(...*)} callback
318 */
319 _sendRawMessageForTesting(method, params, callback) {
320 var domain = method.split('.')[0];
321 this._wrapCallbackAndSendMessageObject(domain, method, params, callback);
322 }
323
324 /**
325 * @param {!Object|string} message
326 */
327 _onMessage(message) {
328 if (InspectorBackendClass.Options.dumpInspectorProtocolMessages)
329 this._dumpProtocolMessage('backend: ' + ((typeof message === 'string') ? m essage : JSON.stringify(message)));
330
331 var messageObject = /** @type {!Object} */ ((typeof message === 'string') ? JSON.parse(message) : message);
332
333 if ('id' in messageObject) { // just a response for some request
334 var callback = this._callbacks[messageObject.id];
335 if (!callback) {
336 InspectorBackendClass.reportProtocolError('Protocol Error: the message w ith wrong id', messageObject);
337 return;
338 }
339
340 var timingLabel = 'time-stats: ' + callback.methodName;
341 if (InspectorBackendClass.Options.dumpInspectorTimeStats)
342 console.time(timingLabel);
343
344 this._agent(callback.domain).dispatchResponse(messageObject, callback.meth odName, callback);
345 --this._pendingResponsesCount;
346 delete this._callbacks[messageObject.id];
347
348 if (InspectorBackendClass.Options.dumpInspectorTimeStats)
349 console.timeEnd(timingLabel);
350
351 if (this._scripts && !this._pendingResponsesCount)
352 this._deprecatedRunAfterPendingDispatches();
353 } else {
354 if (!('method' in messageObject)) {
355 InspectorBackendClass.reportProtocolError('Protocol Error: the message w ithout method', messageObject);
356 return;
357 }
358
359 var method = messageObject.method.split('.');
360 var domainName = method[0];
361 if (!(domainName in this._dispatchers)) {
362 InspectorBackendClass.reportProtocolError(
363 'Protocol Error: the message ' + messageObject.method + ' is for non -existing domain \'' + domainName +
364 '\'',
365 messageObject);
366 return;
367 }
368
369 this._dispatchers[domainName].dispatch(method[1], messageObject);
370 }
371 }
372
373 /**
374 * @param {string} domain
375 * @param {!Object} dispatcher
376 */
377 registerDispatcher(domain, dispatcher) {
378 if (!this._dispatchers[domain])
379 return;
380
381 this._dispatchers[domain].setDomainDispatcher(dispatcher);
382 }
383
384 /**
385 * @param {function()=} script
386 */
387 _deprecatedRunAfterPendingDispatches(script) {
388 if (!this._scripts)
389 this._scripts = [];
390
391 if (script)
392 this._scripts.push(script);
393
394 // Execute all promises.
395 setTimeout(function() {
396 if (!this._pendingResponsesCount)
397 this._executeAfterPendingDispatches();
398 else
399 this._deprecatedRunAfterPendingDispatches();
400 }.bind(this), 0);
401 }
402
403 _executeAfterPendingDispatches() {
404 if (!this._pendingResponsesCount) {
405 var scripts = this._scripts;
406 this._scripts = [];
407 for (var id = 0; id < scripts.length; ++id)
408 scripts[id].call(this);
409 }
410 }
411
412 /**
413 * @param {string} message
414 */
415 _dumpProtocolMessage(message) {
416 console.log(message); // eslint-disable-line no-console
417 }
418
419 /**
420 * @param {string} reason
421 */
422 _onDisconnect(reason) {
423 this._connection = null;
424 this._runPendingCallbacks();
425 this.dispose();
426 }
427
428 /**
429 * @protected
430 */
431 dispose() {
432 }
433
434 /**
435 * @return {boolean}
436 */
437 isDisposed() {
438 return !this._connection;
439 }
440
441 _runPendingCallbacks() {
442 var keys = Object.keys(this._callbacks).map(function(num) {
443 return parseInt(num, 10);
444 });
445 for (var i = 0; i < keys.length; ++i) {
446 var callback = this._callbacks[keys[i]];
447 this._dispatchConnectionErrorResponse(callback.domain, callback.methodName , callback);
448 }
449 this._callbacks = {};
450 }
451
452 /**
453 * @param {string} domain
454 * @param {string} methodName
455 * @param {function(*)} callback
456 */
457 _dispatchConnectionErrorResponse(domain, methodName, callback) {
458 var error = {
459 message: 'Connection is closed, can\'t dispatch pending ' + methodName,
460 code: InspectorBackendClass._ConnectionClosedErrorCode,
461 data: null
462 };
463 var messageObject = {error: error};
464 setTimeout(
465 InspectorBackendClass._AgentPrototype.prototype.dispatchResponse.bind(
466 this._agent(domain), messageObject, methodName, callback),
467 0);
468 }
469 };
470
471 /**
472 * @unrestricted
473 */
474 InspectorBackendClass._AgentPrototype = class {
475 /**
476 * @param {string} domain
477 */
478 constructor(domain) {
479 this._replyArgs = {};
480 this._hasErrorData = {};
481 this._domain = domain;
482 }
483
484 /**
485 * @param {!Protocol.TargetBase} target
486 */
487 setTarget(target) {
488 this._target = target;
489 }
490
491 /**
492 * @param {string} methodName
493 * @param {!Array.<!Object>} signature
494 * @param {!Array.<string>} replyArgs
495 * @param {boolean} hasErrorData
496 */
497 registerCommand(methodName, signature, replyArgs, hasErrorData) {
498 var domainAndMethod = this._domain + '.' + methodName;
499
500 /**
501 * @param {...*} vararg
502 * @this {InspectorBackendClass._AgentPrototype}
503 * @return {!Promise.<*>}
504 */
505 function sendMessagePromise(vararg) {
506 var params = Array.prototype.slice.call(arguments);
507 return InspectorBackendClass._AgentPrototype.prototype._sendMessageToBacke ndPromise.call(
508 this, domainAndMethod, signature, params);
509 }
510
511 this[methodName] = sendMessagePromise;
512
513 /**
514 * @param {...*} vararg
515 * @this {InspectorBackendClass._AgentPrototype}
516 */
517 function invoke(vararg) {
518 var params = [domainAndMethod].concat(Array.prototype.slice.call(arguments ));
519 InspectorBackendClass._AgentPrototype.prototype._invoke.apply(this, params );
520 }
521
522 this['invoke_' + methodName] = invoke;
523
524 this._replyArgs[domainAndMethod] = replyArgs;
525 if (hasErrorData)
526 this._hasErrorData[domainAndMethod] = true;
527 }
528
529 /**
530 * @param {string} method
531 * @param {!Array.<!Object>} signature
532 * @param {!Array.<*>} args
533 * @param {boolean} allowExtraUndefinedArg
534 * @param {function(string)} errorCallback
535 * @return {?Object}
536 */
537 _prepareParameters(method, signature, args, allowExtraUndefinedArg, errorCallb ack) {
538 var params = {};
539 var hasParams = false;
540 for (var i = 0; i < signature.length; ++i) {
541 var param = signature[i];
542 var paramName = param['name'];
543 var typeName = param['type'];
544 var optionalFlag = param['optional'];
545
546 if (!args.length && !optionalFlag) {
547 errorCallback(
548 'Protocol Error: Invalid number of arguments for method \'' + method +
549 '\' call. It must have the following arguments \'' + JSON.stringify( signature) + '\'.');
550 return null;
551 }
552
553 var value = args.shift();
554 if (optionalFlag && typeof value === 'undefined')
555 continue;
556
557 if (typeof value !== typeName) {
558 errorCallback(
559 'Protocol Error: Invalid type of argument \'' + paramName + '\' for method \'' + method +
560 '\' call. It must be \'' + typeName + '\' but it is \'' + typeof val ue + '\'.');
561 return null;
562 }
563
564 params[paramName] = value;
565 hasParams = true;
566 }
567
568 if (args.length === 1 && (!allowExtraUndefinedArg || (typeof args[0] !== 'un defined'))) {
569 errorCallback(
570 'Protocol Error: Optional callback argument for method \'' + method +
571 '\' call must be a function but its type is \'' + typeof args[0] + '\' .');
572 return null;
573 }
574
575 if (args.length > 1) {
576 errorCallback('Protocol Error: Extra ' + args.length + ' arguments in a ca ll to method \'' + method + '\'.');
577 return null;
578 }
579
580 return hasParams ? params : null;
581 }
582
583 /**
584 * @param {string} method
585 * @param {!Array.<!Object>} signature
586 * @param {!Array.<*>} args
587 * @return {!Promise.<*>}
588 */
589 _sendMessageToBackendPromise(method, signature, args) {
590 var errorMessage;
591 /**
592 * @param {string} message
593 */
594 function onError(message) {
595 console.error(message);
596 errorMessage = message;
597 }
598 var userCallback = (args.length && typeof args.peekLast() === 'function') ? args.pop() : null;
599 var params = this._prepareParameters(method, signature, args, !userCallback, onError);
600 if (errorMessage)
601 return Promise.reject(new Error(errorMessage));
602 else
603 return new Promise(promiseAction.bind(this));
604
605 /**
606 * @param {function(?)} resolve
607 * @param {function(!Error)} reject
608 * @this {InspectorBackendClass._AgentPrototype}
609 */
610 function promiseAction(resolve, reject) {
611 /**
612 * @param {...*} vararg
613 */
614 function callback(vararg) {
615 var result = userCallback ? userCallback.apply(null, arguments) : undefi ned;
616 resolve(result);
617 }
618 this._target._wrapCallbackAndSendMessageObject(this._domain, method, param s, callback);
619 }
620 }
621
622 /**
623 * @param {string} method
624 * @param {?Object} args
625 * @param {?function(*)} callback
626 */
627 _invoke(method, args, callback) {
628 this._target._wrapCallbackAndSendMessageObject(this._domain, method, args, c allback);
629 }
630
631 /**
632 * @param {!Object} messageObject
633 * @param {string} methodName
634 * @param {function(*)|function(?Protocol.Error, ?Object)} callback
635 */
636 dispatchResponse(messageObject, methodName, callback) {
637 if (messageObject.error && messageObject.error.code !== InspectorBackendClas s._ConnectionClosedErrorCode &&
638 messageObject.error.code !== InspectorBackendClass.DevToolsStubErrorCode &&
639 !InspectorBackendClass.Options.suppressRequestErrors) {
640 var id = InspectorBackendClass.Options.dumpInspectorProtocolMessages ? ' w ith id = ' + messageObject.id : '';
641 console.error('Request ' + methodName + id + ' failed. ' + JSON.stringify( messageObject.error));
642 }
643
644 var argumentsArray = [];
645 argumentsArray[0] = messageObject.error ? messageObject.error.message : null ;
646
647 if (this._hasErrorData[methodName])
648 argumentsArray[1] = messageObject.error ? messageObject.error.data : null;
649
650 if (messageObject.result) {
651 var paramNames = this._replyArgs[methodName] || [];
652 for (var i = 0; i < paramNames.length; ++i)
653 argumentsArray.push(messageObject.result[paramNames[i]]);
654 }
655
656 callback.apply(null, argumentsArray);
657 }
658 };
659
660 /**
661 * @unrestricted
662 */
663 InspectorBackendClass._DispatcherPrototype = class {
664 constructor() {
665 this._eventArgs = {};
666 this._dispatcher = null;
667 }
668
669 /**
670 * @param {string} eventName
671 * @param {!Object} params
672 */
673 registerEvent(eventName, params) {
674 this._eventArgs[eventName] = params;
675 }
676
677 /**
678 * @param {!Object} dispatcher
679 */
680 setDomainDispatcher(dispatcher) {
681 this._dispatcher = dispatcher;
682 }
683
684 /**
685 * @param {string} functionName
686 * @param {!Object} messageObject
687 */
688 dispatch(functionName, messageObject) {
689 if (!this._dispatcher)
690 return;
691
692 if (!(functionName in this._dispatcher)) {
693 InspectorBackendClass.reportProtocolError(
694 'Protocol Error: Attempted to dispatch an unimplemented method \'' + m essageObject.method + '\'',
695 messageObject);
696 return;
697 }
698
699 if (!this._eventArgs[messageObject.method]) {
700 InspectorBackendClass.reportProtocolError(
701 'Protocol Error: Attempted to dispatch an unspecified method \'' + mes sageObject.method + '\'',
702 messageObject);
703 return;
704 }
705
706 var params = [];
707 if (messageObject.params) {
708 var paramNames = this._eventArgs[messageObject.method];
709 for (var i = 0; i < paramNames.length; ++i)
710 params.push(messageObject.params[paramNames[i]]);
711 }
712
713 var timingLabel = 'time-stats: ' + messageObject.method;
714 if (InspectorBackendClass.Options.dumpInspectorTimeStats)
715 console.time(timingLabel);
716
717 this._dispatcher[functionName].apply(this._dispatcher, params);
718
719 if (InspectorBackendClass.Options.dumpInspectorTimeStats)
720 console.timeEnd(timingLabel);
721 }
722 };
723
724 InspectorBackendClass.Options = {
725 dumpInspectorTimeStats: false,
726 dumpInspectorProtocolMessages: false,
727 suppressRequestErrors: false
728 };
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698