1/*
2 * Copyright 2014 The Chromium Authors. All rights reserved.
3 * Use of this source code is governed by a BSD-style license that can be
4 * found in the LICENSE file.
5 */
6
7/**
8 * @constructor
9 * @extends {Protocol.Agents}
10 * @param {string} name
11 * @param {!InspectorBackendClass.Connection} connection
12 * @param {function(?WebInspector.Target)=} callback
13 */
14WebInspector.Target = function(name, connection, callback)
15{
16    Protocol.Agents.call(this, connection.agentsMap());
17    this._name = name;
18    this._connection = connection;
19    connection.addEventListener(InspectorBackendClass.Connection.Events.Disconnected, this._onDisconnect, this);
20    this._id = WebInspector.Target._nextId++;
21
22    /** @type {!Map.<!Function, !WebInspector.SDKModel>} */
23    this._modelByConstructor = new Map();
24
25    /** @type {!Object.<string, boolean>} */
26    this._capabilities = {};
27    this.pageAgent().canScreencast(this._initializeCapability.bind(this, WebInspector.Target.Capabilities.CanScreencast, null));
28    this.pageAgent().canEmulate(this._initializeCapability.bind(this, WebInspector.Target.Capabilities.CanEmulate, null));
29    if (Runtime.experiments.isEnabled("timelinePowerProfiler"))
30        this.powerAgent().canProfilePower(this._initializeCapability.bind(this, WebInspector.Target.Capabilities.CanProfilePower, null));
31    this.workerAgent().canInspectWorkers(this._initializeCapability.bind(this, WebInspector.Target.Capabilities.CanInspectWorkers, this._loadedWithCapabilities.bind(this, callback)));
32    if (Runtime.experiments.isEnabled("timelineOnTraceEvents"))
33        this.consoleAgent().setTracingBasedTimeline(true);
34}
35
36/**
37 * @enum {string}
38 */
39WebInspector.Target.Capabilities = {
40    CanScreencast: "CanScreencast",
41    HasTouchInputs: "HasTouchInputs",
42    CanProfilePower: "CanProfilePower",
43    CanInspectWorkers: "CanInspectWorkers",
44    CanEmulate: "CanEmulate"
45}
46
47WebInspector.Target._nextId = 1;
48
49WebInspector.Target.prototype = {
50
51    /**
52     * @return {number}
53     */
54    id: function()
55    {
56        return this._id;
57    },
58
59    /**
60     *
61     * @return {string}
62     */
63    name: function()
64    {
65        return this._name;
66    },
67
68    /**
69     * @param {string} name
70     * @param {function()|null} callback
71     * @param {?Protocol.Error} error
72     * @param {boolean} result
73     */
74    _initializeCapability: function(name, callback, error, result)
75    {
76        this._capabilities[name] = result;
77        if (callback)
78            callback();
79    },
80
81    /**
82     * @param {string} capability
83     * @return {boolean}
84     */
85    hasCapability: function(capability)
86    {
87        return !!this._capabilities[capability];
88    },
89
90    /**
91     * @param {function(?WebInspector.Target)=} callback
92     */
93    _loadedWithCapabilities: function(callback)
94    {
95        if (this._connection.isClosed()) {
96            callback(null);
97            return;
98        }
99
100        /** @type {!WebInspector.ConsoleModel} */
101        this.consoleModel = new WebInspector.ConsoleModel(this);
102
103        /** @type {!WebInspector.NetworkManager} */
104        this.networkManager = new WebInspector.NetworkManager(this);
105
106        /** @type {!WebInspector.ResourceTreeModel} */
107        this.resourceTreeModel = new WebInspector.ResourceTreeModel(this);
108        if (!WebInspector.resourceTreeModel)
109            WebInspector.resourceTreeModel = this.resourceTreeModel;
110
111        /** @type {!WebInspector.NetworkLog} */
112        this.networkLog = new WebInspector.NetworkLog(this);
113        if (!WebInspector.networkLog)
114            WebInspector.networkLog = this.networkLog;
115
116        /** @type {!WebInspector.DebuggerModel} */
117        this.debuggerModel = new WebInspector.DebuggerModel(this);
118        if (!WebInspector.debuggerModel)
119            WebInspector.debuggerModel = this.debuggerModel;
120
121        /** @type {!WebInspector.RuntimeModel} */
122        this.runtimeModel = new WebInspector.RuntimeModel(this);
123        if (!WebInspector.runtimeModel)
124            WebInspector.runtimeModel = this.runtimeModel;
125
126        /** @type {!WebInspector.DOMModel} */
127        this.domModel = new WebInspector.DOMModel(this);
128
129        /** @type {!WebInspector.CSSStyleModel} */
130        this.cssModel = new WebInspector.CSSStyleModel(this);
131        if (!WebInspector.cssModel)
132            WebInspector.cssModel = this.cssModel;
133
134        /** @type {!WebInspector.WorkerManager} */
135        this.workerManager = new WebInspector.WorkerManager(this, this.hasCapability(WebInspector.Target.Capabilities.CanInspectWorkers));
136        if (!WebInspector.workerManager)
137            WebInspector.workerManager = this.workerManager;
138
139        if (this.hasCapability(WebInspector.Target.Capabilities.CanProfilePower))
140            WebInspector.powerProfiler = new WebInspector.PowerProfiler(this);
141
142        /** @type {!WebInspector.TimelineManager} */
143        this.timelineManager = new WebInspector.TimelineManager(this);
144
145        /** @type {!WebInspector.DatabaseModel} */
146        this.databaseModel = new WebInspector.DatabaseModel(this);
147        if (!WebInspector.databaseModel)
148            WebInspector.databaseModel = this.databaseModel;
149
150        /** @type {!WebInspector.DOMStorageModel} */
151        this.domStorageModel = new WebInspector.DOMStorageModel(this);
152        if (!WebInspector.domStorageModel)
153            WebInspector.domStorageModel = this.domStorageModel;
154
155        /** @type {!WebInspector.CPUProfilerModel} */
156        this.cpuProfilerModel = new WebInspector.CPUProfilerModel(this);
157        if (!WebInspector.cpuProfilerModel)
158            WebInspector.cpuProfilerModel = this.cpuProfilerModel;
159
160        /** @type {!WebInspector.HeapProfilerModel} */
161        this.heapProfilerModel = new WebInspector.HeapProfilerModel(this);
162
163        /** @type {!WebInspector.IndexedDBModel} */
164        this.indexedDBModel = new WebInspector.IndexedDBModel(this);
165
166        /** @type {!WebInspector.LayerTreeModel} */
167        this.layerTreeModel = new WebInspector.LayerTreeModel(this);
168
169        if (callback)
170            callback(this);
171    },
172
173    /**
174     * @override
175     * @param {string} domain
176     * @param {!Object} dispatcher
177     */
178    registerDispatcher: function(domain, dispatcher)
179    {
180        this._connection.registerDispatcher(domain, dispatcher);
181    },
182
183    /**
184     * @return {boolean}
185     */
186    isWorkerTarget: function()
187    {
188        return !this.hasCapability(WebInspector.Target.Capabilities.CanInspectWorkers);
189    },
190
191    /**
192     * @return {boolean}
193     */
194    canEmulate: function()
195    {
196        return this.hasCapability(WebInspector.Target.Capabilities.CanEmulate);
197    },
198
199    _onDisconnect: function()
200    {
201        WebInspector.targetManager.removeTarget(this);
202        this._dispose();
203    },
204
205    _dispose: function()
206    {
207        this.debuggerModel.dispose();
208        this.networkManager.dispose();
209        this.cpuProfilerModel.dispose();
210    },
211
212    /**
213     * @return {boolean}
214     */
215    isDetached: function()
216    {
217        return this._connection.isClosed();
218    },
219
220    __proto__: Protocol.Agents.prototype
221}
222
223/**
224 * @constructor
225 * @extends {WebInspector.Object}
226 * @param {!WebInspector.Target} target
227 */
228WebInspector.SDKObject = function(target)
229{
230    WebInspector.Object.call(this);
231    this._target = target;
232}
233
234WebInspector.SDKObject.prototype = {
235    /**
236     * @return {!WebInspector.Target}
237     */
238    target: function()
239    {
240        return this._target;
241    },
242
243    __proto__: WebInspector.Object.prototype
244}
245
246/**
247 * @constructor
248 * @extends {WebInspector.SDKObject}
249 * @param {!Function} modelClass
250 * @param {!WebInspector.Target} target
251 */
252WebInspector.SDKModel = function(modelClass, target)
253{
254    WebInspector.SDKObject.call(this, target);
255    target._modelByConstructor.set(modelClass, this);
256}
257
258WebInspector.SDKModel.prototype = {
259    __proto__: WebInspector.SDKObject.prototype
260}
261
262/**
263 * @constructor
264 * @extends {WebInspector.Object}
265 */
266WebInspector.TargetManager = function()
267{
268    WebInspector.Object.call(this);
269    /** @type {!Array.<!WebInspector.Target>} */
270    this._targets = [];
271    /** @type {!Array.<!WebInspector.TargetManager.Observer>} */
272    this._observers = [];
273    /** @type {!Object.<string, !Array.<{modelClass: !Function, thisObject: (!Object|undefined), listener: function(!WebInspector.Event)}>>} */
274    this._modelListeners = {};
275}
276
277WebInspector.TargetManager.Events = {
278    InspectedURLChanged: "InspectedURLChanged",
279    MainFrameNavigated: "MainFrameNavigated",
280    Load: "Load",
281    WillReloadPage: "WillReloadPage"
282}
283
284WebInspector.TargetManager.prototype = {
285    /**
286     * @return {string}
287     */
288    inspectedPageURL: function()
289    {
290        if (!this._targets.length)
291            return "";
292
293        return this._targets[0].resourceTreeModel.inspectedPageURL();
294    },
295
296    /**
297     * @return {string}
298     */
299    inspectedPageDomain: function()
300    {
301        if (!this._targets.length)
302            return "";
303
304        return this._targets[0].resourceTreeModel.inspectedPageDomain();
305    },
306
307    /**
308     * @param {!WebInspector.Event} event
309     */
310    _redispatchEvent: function(event)
311    {
312        this.dispatchEventToListeners(event.type, event.data);
313    },
314
315    /**
316     * @param {boolean=} ignoreCache
317     */
318    reloadPage: function(ignoreCache)
319    {
320        if (this._targets.length)
321            this._targets[0].resourceTreeModel.reloadPage(ignoreCache);
322    },
323
324    /**
325     * @param {!Function} modelClass
326     * @param {string} eventType
327     * @param {function(!WebInspector.Event)} listener
328     * @param {!Object=} thisObject
329     */
330    addModelListener: function(modelClass, eventType, listener, thisObject)
331    {
332        for (var i = 0; i < this._targets.length; ++i) {
333            var model = this._targets[i]._modelByConstructor.get(modelClass);
334            model.addEventListener(eventType, listener, thisObject);
335        }
336        if (!this._modelListeners[eventType])
337            this._modelListeners[eventType] = [];
338        this._modelListeners[eventType].push({ modelClass: modelClass, thisObject: thisObject, listener: listener });
339    },
340
341    /**
342     * @param {!Function} modelClass
343     * @param {string} eventType
344     * @param {function(!WebInspector.Event)} listener
345     * @param {!Object=} thisObject
346     */
347    removeModelListener: function(modelClass, eventType, listener, thisObject)
348    {
349        if (!this._modelListeners[eventType])
350            return;
351
352        for (var i = 0; i < this._targets.length; ++i) {
353            var model = this._targets[i]._modelByConstructor.get(modelClass);
354            model.removeEventListener(eventType, listener, thisObject);
355        }
356
357        var listeners = this._modelListeners[eventType];
358        for (var i = 0; i < listeners.length; ++i) {
359            if (listeners[i].modelClass === modelClass && listeners[i].listener === listener && listeners[i].thisObject === thisObject)
360                listeners.splice(i--, 1);
361        }
362        if (!listeners.length)
363            delete this._modelListeners[eventType];
364    },
365
366    /**
367     * @param {!WebInspector.TargetManager.Observer} targetObserver
368     */
369    observeTargets: function(targetObserver)
370    {
371        this.targets().forEach(targetObserver.targetAdded.bind(targetObserver));
372        this._observers.push(targetObserver);
373    },
374
375    /**
376     * @param {!WebInspector.TargetManager.Observer} targetObserver
377     */
378    unobserveTargets: function(targetObserver)
379    {
380        this._observers.remove(targetObserver);
381    },
382
383    /**
384     * @param {string} name
385     * @param {!InspectorBackendClass.Connection} connection
386     * @param {function(?WebInspector.Target)=} callback
387     */
388    createTarget: function(name, connection, callback)
389    {
390        var target = new WebInspector.Target(name, connection, callbackWrapper.bind(this));
391
392        /**
393         * @this {WebInspector.TargetManager}
394         * @param {?WebInspector.Target} newTarget
395         */
396        function callbackWrapper(newTarget)
397        {
398            if (newTarget)
399                this.addTarget(newTarget);
400            if (callback)
401                callback(newTarget);
402        }
403    },
404
405    /**
406     * @param {!WebInspector.Target} target
407     */
408    addTarget: function(target)
409    {
410        this._targets.push(target);
411        if (this._targets.length === 1) {
412            target.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.InspectedURLChanged, this._redispatchEvent, this);
413            target.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.MainFrameNavigated, this._redispatchEvent, this);
414            target.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.Load, this._redispatchEvent, this);
415            target.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.WillReloadPage, this._redispatchEvent, this);
416        }
417        var copy = this._observers.slice();
418        for (var i = 0; i < copy.length; ++i)
419            copy[i].targetAdded(target);
420
421        for (var eventType in this._modelListeners) {
422            var listeners = this._modelListeners[eventType];
423            for (var i = 0; i < listeners.length; ++i) {
424                var model = target._modelByConstructor.get(listeners[i].modelClass);
425                model.addEventListener(eventType, listeners[i].listener, listeners[i].thisObject);
426            }
427        }
428    },
429
430    /**
431     * @param {!WebInspector.Target} target
432     */
433    removeTarget: function(target)
434    {
435        this._targets.remove(target);
436        if (this._targets.length === 0) {
437            target.resourceTreeModel.removeEventListener(WebInspector.ResourceTreeModel.EventTypes.InspectedURLChanged, this._redispatchEvent, this);
438            target.resourceTreeModel.removeEventListener(WebInspector.ResourceTreeModel.EventTypes.MainFrameNavigated, this._redispatchEvent, this);
439            target.resourceTreeModel.removeEventListener(WebInspector.ResourceTreeModel.EventTypes.Load, this._redispatchEvent, this);
440            target.resourceTreeModel.removeEventListener(WebInspector.ResourceTreeModel.EventTypes.WillReloadPage, this._redispatchEvent, this);
441        }
442        var copy = this._observers.slice();
443        for (var i = 0; i < copy.length; ++i)
444            copy[i].targetRemoved(target);
445
446        for (var eventType in this._modelListeners) {
447            var listeners = this._modelListeners[eventType];
448            for (var i = 0; i < listeners.length; ++i) {
449                var model = target._modelByConstructor.get(listeners[i].modelClass);
450                model.removeEventListener(eventType, listeners[i].listener, listeners[i].thisObject);
451            }
452        }
453    },
454
455    /**
456     * @return {boolean}
457     */
458    hasTargets: function()
459    {
460        return !!this._targets.length;
461    },
462
463    /**
464     * @return {!Array.<!WebInspector.Target>}
465     */
466    targets: function()
467    {
468        return this._targets.slice();
469    },
470
471    /**
472     * @return {?WebInspector.Target}
473     */
474    mainTarget: function()
475    {
476        return this._targets[0];
477    },
478
479    __proto__: WebInspector.Object.prototype
480}
481
482/**
483 * @interface
484 */
485WebInspector.TargetManager.Observer = function()
486{
487}
488
489WebInspector.TargetManager.Observer.prototype = {
490    /**
491     * @param {!WebInspector.Target} target
492     */
493    targetAdded: function(target) { },
494
495    /**
496     * @param {!WebInspector.Target} target
497     */
498    targetRemoved: function(target) { },
499}
500
501/**
502 * @type {!WebInspector.TargetManager}
503 */
504WebInspector.targetManager = new WebInspector.TargetManager();
505