• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (C) 2007, 2008 Apple Inc.  All rights reserved.
3 * Copyright (C) 2009 Joseph Pecoraro
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * 1.  Redistributions of source code must retain the above copyright
10 *     notice, this list of conditions and the following disclaimer.
11 * 2.  Redistributions in binary form must reproduce the above copyright
12 *     notice, this list of conditions and the following disclaimer in the
13 *     documentation and/or other materials provided with the distribution.
14 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
15 *     its contributors may be used to endorse or promote products derived
16 *     from this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
19 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
22 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29
30WebInspector.DatabasesPanel = function(database)
31{
32    WebInspector.Panel.call(this);
33
34    this.sidebarElement = document.createElement("div");
35    this.sidebarElement.id = "databases-sidebar";
36    this.sidebarElement.className = "sidebar";
37    this.element.appendChild(this.sidebarElement);
38
39    this.sidebarResizeElement = document.createElement("div");
40    this.sidebarResizeElement.className = "sidebar-resizer-vertical";
41    this.sidebarResizeElement.addEventListener("mousedown", this._startSidebarDragging.bind(this), false);
42    this.element.appendChild(this.sidebarResizeElement);
43
44    this.sidebarTreeElement = document.createElement("ol");
45    this.sidebarTreeElement.className = "sidebar-tree";
46    this.sidebarElement.appendChild(this.sidebarTreeElement);
47
48    this.sidebarTree = new TreeOutline(this.sidebarTreeElement);
49
50    this.databasesListTreeElement = new WebInspector.SidebarSectionTreeElement(WebInspector.UIString("DATABASES"), {}, true);
51    this.sidebarTree.appendChild(this.databasesListTreeElement);
52    this.databasesListTreeElement.expand();
53
54    this.localStorageListTreeElement = new WebInspector.SidebarSectionTreeElement(WebInspector.UIString("LOCAL STORAGE"), {}, true);
55    this.sidebarTree.appendChild(this.localStorageListTreeElement);
56    this.localStorageListTreeElement.expand();
57
58    this.sessionStorageListTreeElement = new WebInspector.SidebarSectionTreeElement(WebInspector.UIString("SESSION STORAGE"), {}, true);
59    this.sidebarTree.appendChild(this.sessionStorageListTreeElement);
60    this.sessionStorageListTreeElement.expand();
61
62    this.storageViews = document.createElement("div");
63    this.storageViews.id = "storage-views";
64    this.element.appendChild(this.storageViews);
65
66    this.storageViewStatusBarItemsContainer = document.createElement("div");
67    this.storageViewStatusBarItemsContainer.id = "storage-view-status-bar-items";
68
69    this.reset();
70}
71
72WebInspector.DatabasesPanel.prototype = {
73    toolbarItemClass: "databases",
74
75    get toolbarItemLabel()
76    {
77        return WebInspector.UIString("Databases");
78    },
79
80    get statusBarItems()
81    {
82        return [this.storageViewStatusBarItemsContainer];
83    },
84
85    show: function()
86    {
87        WebInspector.Panel.prototype.show.call(this);
88        this._updateSidebarWidth();
89        this._registerStorageEventListener();
90    },
91
92    reset: function()
93    {
94        if (this._databases) {
95            var databasesLength = this._databases.length;
96            for (var i = 0; i < databasesLength; ++i) {
97                var database = this._databases[i];
98
99                delete database._tableViews;
100                delete database._queryView;
101            }
102        }
103
104        this._databases = [];
105
106        this._unregisterStorageEventListener();
107
108        if (this._domStorage) {
109            var domStorageLength = this._domStorage.length;
110            for (var i = 0; i < domStorageLength; ++i) {
111                var domStorage = this._domStorage[i];
112
113                delete domStorage._domStorageView;
114            }
115        }
116
117        this._domStorage = [];
118
119        this.databasesListTreeElement.removeChildren();
120        this.localStorageListTreeElement.removeChildren();
121        this.sessionStorageListTreeElement.removeChildren();
122        this.storageViews.removeChildren();
123
124        this.storageViewStatusBarItemsContainer.removeChildren();
125    },
126
127    handleKeyEvent: function(event)
128    {
129        this.sidebarTree.handleKeyEvent(event);
130    },
131
132    addDatabase: function(database)
133    {
134        this._databases.push(database);
135
136        var databaseTreeElement = new WebInspector.DatabaseSidebarTreeElement(database);
137        database._databasesTreeElement = databaseTreeElement;
138        this.databasesListTreeElement.appendChild(databaseTreeElement);
139    },
140
141    addDOMStorage: function(domStorage)
142    {
143        this._domStorage.push(domStorage);
144        var domStorageTreeElement = new WebInspector.DOMStorageSidebarTreeElement(domStorage);
145        domStorage._domStorageTreeElement = domStorageTreeElement;
146        if (domStorage.isLocalStorage)
147            this.localStorageListTreeElement.appendChild(domStorageTreeElement);
148        else
149            this.sessionStorageListTreeElement.appendChild(domStorageTreeElement);
150    },
151
152    selectDatabase: function(db)
153    {
154        var database;
155        for (var i = 0, len = this._databases.length; i < len; ++i) {
156            database = this._databases[i];
157            if ( db === database.database ) {
158                this.showDatabase(database);
159                database._databasesTreeElement.select();
160                return;
161            }
162        }
163    },
164
165    selectDOMStorage: function(s)
166    {
167        var isLocalStorage = (s === InspectorController.inspectedWindow().localStorage);
168        for (var i = 0, len = this._domStorage.length; i < len; ++i) {
169            var storage = this._domStorage[i];
170            if ( isLocalStorage === storage.isLocalStorage ) {
171                this.showDOMStorage(storage);
172                storage._domStorageTreeElement.select();
173                return;
174            }
175        }
176    },
177
178    showDatabase: function(database, tableName)
179    {
180        if (!database)
181            return;
182
183        if (this.visibleView)
184            this.visibleView.hide();
185
186        var view;
187        if (tableName) {
188            if (!("_tableViews" in database))
189                database._tableViews = {};
190            view = database._tableViews[tableName];
191            if (!view) {
192                view = new WebInspector.DatabaseTableView(database, tableName);
193                database._tableViews[tableName] = view;
194            }
195        } else {
196            view = database._queryView;
197            if (!view) {
198                view = new WebInspector.DatabaseQueryView(database);
199                database._queryView = view;
200            }
201        }
202
203        view.show(this.storageViews);
204
205        this.visibleView = view;
206
207        this.storageViewStatusBarItemsContainer.removeChildren();
208        var statusBarItems = view.statusBarItems || [];
209        for (var i = 0; i < statusBarItems.length; ++i)
210            this.storageViewStatusBarItemsContainer.appendChild(statusBarItems[i]);
211    },
212
213    showDOMStorage: function(domStorage)
214    {
215        if (!domStorage)
216            return;
217
218        if (this.visibleView)
219            this.visibleView.hide();
220
221        var view;
222        view = domStorage._domStorageView;
223        if (!view) {
224            view = new WebInspector.DOMStorageItemsView(domStorage);
225            domStorage._domStorageView = view;
226        }
227
228        view.show(this.storageViews);
229
230        this.visibleView = view;
231
232        this.storageViewStatusBarItemsContainer.removeChildren();
233        var statusBarItems = view.statusBarItems;
234        for (var i = 0; i < statusBarItems.length; ++i)
235            this.storageViewStatusBarItemsContainer.appendChild(statusBarItems[i]);
236    },
237
238    closeVisibleView: function()
239    {
240        if (this.visibleView)
241            this.visibleView.hide();
242        delete this.visibleView;
243    },
244
245    updateDatabaseTables: function(database)
246    {
247        if (!database || !database._databasesTreeElement)
248            return;
249
250        database._databasesTreeElement.shouldRefreshChildren = true;
251
252        if (!("_tableViews" in database))
253            return;
254
255        var tableNamesHash = {};
256        var tableNames = database.tableNames;
257        var tableNamesLength = tableNames.length;
258        for (var i = 0; i < tableNamesLength; ++i)
259            tableNamesHash[tableNames[i]] = true;
260
261        for (var tableName in database._tableViews) {
262            if (!(tableName in tableNamesHash)) {
263                if (this.visibleView === database._tableViews[tableName])
264                    this.closeVisibleView();
265                delete database._tableViews[tableName];
266            }
267        }
268    },
269
270    dataGridForResult: function(result)
271    {
272        if (!result.rows.length)
273            return null;
274
275        var columns = {};
276
277        var rows = result.rows;
278        for (var columnIdentifier in rows.item(0)) {
279            var column = {};
280            column.width = columnIdentifier.length;
281            column.title = columnIdentifier;
282
283            columns[columnIdentifier] = column;
284        }
285
286        var nodes = [];
287        var length = rows.length;
288        for (var i = 0; i < length; ++i) {
289            var data = {};
290
291            var row = rows.item(i);
292            for (var columnIdentifier in row) {
293                // FIXME: (Bug 19439) We should specially format SQL NULL here
294                // (which is represented by JavaScript null here, and turned
295                // into the string "null" by the String() function).
296                var text = String(row[columnIdentifier]);
297                data[columnIdentifier] = text;
298                if (text.length > columns[columnIdentifier].width)
299                    columns[columnIdentifier].width = text.length;
300            }
301
302            var node = new WebInspector.DataGridNode(data, false);
303            node.selectable = false;
304            nodes.push(node);
305        }
306
307        var totalColumnWidths = 0;
308        for (var columnIdentifier in columns)
309            totalColumnWidths += columns[columnIdentifier].width;
310
311        // Calculate the percentage width for the columns.
312        const minimumPrecent = 5;
313        var recoupPercent = 0;
314        for (var columnIdentifier in columns) {
315            var width = columns[columnIdentifier].width;
316            width = Math.round((width / totalColumnWidths) * 100);
317            if (width < minimumPrecent) {
318                recoupPercent += (minimumPrecent - width);
319                width = minimumPrecent;
320            }
321
322            columns[columnIdentifier].width = width;
323        }
324
325        // Enforce the minimum percentage width.
326        while (recoupPercent > 0) {
327            for (var columnIdentifier in columns) {
328                if (columns[columnIdentifier].width > minimumPrecent) {
329                    --columns[columnIdentifier].width;
330                    --recoupPercent;
331                    if (!recoupPercent)
332                        break;
333                }
334            }
335        }
336
337        // Change the width property to a string suitable for a style width.
338        for (var columnIdentifier in columns)
339            columns[columnIdentifier].width += "%";
340
341        var dataGrid = new WebInspector.DataGrid(columns);
342        var length = nodes.length;
343        for (var i = 0; i < length; ++i)
344            dataGrid.appendChild(nodes[i]);
345
346        return dataGrid;
347    },
348
349    dataGridForDOMStorage: function(domStorage)
350    {
351        if (!domStorage.length)
352            return null;
353
354        var columns = {};
355        columns[0] = {};
356        columns[1] = {};
357        columns[0].title = WebInspector.UIString("Key");
358        columns[0].width = columns[0].title.length;
359        columns[1].title = WebInspector.UIString("Value");
360        columns[1].width = columns[0].title.length;
361
362        var nodes = [];
363
364        var length = domStorage.length;
365        for (var index = 0; index < domStorage.length; index++) {
366            var data = {};
367
368            var key = String(domStorage.key(index));
369            data[0] = key;
370            if (key.length > columns[0].width)
371                columns[0].width = key.length;
372
373            var value = String(domStorage.getItem(key));
374            data[1] = value;
375            if (value.length > columns[1].width)
376                columns[1].width = value.length;
377            var node = new WebInspector.DataGridNode(data, false);
378            node.selectable = true;
379            nodes.push(node);
380        }
381
382        var totalColumnWidths = columns[0].width + columns[1].width;
383        width = Math.round((columns[0].width * 100) / totalColumnWidths);
384        const minimumPrecent = 10;
385        if (width < minimumPrecent)
386            width = minimumPrecent;
387        if (width > 100 - minimumPrecent)
388            width = 100 - minimumPrecent;
389        columns[0].width = width;
390        columns[1].width = 100 - width;
391        columns[0].width += "%";
392        columns[1].width += "%";
393
394        var dataGrid = new WebInspector.DOMStorageDataGrid(columns);
395        var length = nodes.length;
396        for (var i = 0; i < length; ++i)
397            dataGrid.appendChild(nodes[i]);
398        dataGrid.addCreationNode(false);
399        if (length > 0)
400            nodes[0].selected = true;
401        return dataGrid;
402    },
403
404    resize: function()
405    {
406        var visibleView = this.visibleView;
407        if (visibleView && "resize" in visibleView)
408            visibleView.resize();
409    },
410
411    _registerStorageEventListener: function()
412    {
413        var inspectedWindow = InspectorController.inspectedWindow();
414        if (!inspectedWindow || !inspectedWindow.document)
415            return;
416
417        this._storageEventListener = InspectorController.wrapCallback(this._storageEvent.bind(this));
418        inspectedWindow.addEventListener("storage", this._storageEventListener, true);
419    },
420
421    _unregisterStorageEventListener: function()
422    {
423        if (!this._storageEventListener)
424            return;
425
426        var inspectedWindow = InspectorController.inspectedWindow();
427        if (!inspectedWindow || !inspectedWindow.document)
428            return;
429
430        inspectedWindow.removeEventListener("storage", this._storageEventListener, true);
431        delete this._storageEventListener;
432    },
433
434    _storageEvent: function(event)
435    {
436        if (!this._domStorage)
437            return;
438
439        var isLocalStorage = (event.storageArea === InspectorController.inspectedWindow().localStorage);
440        var domStorageLength = this._domStorage.length;
441        for (var i = 0; i < domStorageLength; ++i) {
442            var domStorage = this._domStorage[i];
443            if (isLocalStorage === domStorage.isLocalStorage) {
444                var view = domStorage._domStorageView;
445                if (this.visibleView && view === this.visibleView)
446                    domStorage._domStorageView.update();
447            }
448        }
449    },
450
451    _startSidebarDragging: function(event)
452    {
453        WebInspector.elementDragStart(this.sidebarResizeElement, this._sidebarDragging.bind(this), this._endSidebarDragging.bind(this), event, "col-resize");
454    },
455
456    _sidebarDragging: function(event)
457    {
458        this._updateSidebarWidth(event.pageX);
459
460        event.preventDefault();
461    },
462
463    _endSidebarDragging: function(event)
464    {
465        WebInspector.elementDragEnd(event);
466    },
467
468    _updateSidebarWidth: function(width)
469    {
470        if (this.sidebarElement.offsetWidth <= 0) {
471            // The stylesheet hasn't loaded yet or the window is closed,
472            // so we can't calculate what is need. Return early.
473            return;
474        }
475
476        if (!("_currentSidebarWidth" in this))
477            this._currentSidebarWidth = this.sidebarElement.offsetWidth;
478
479        if (typeof width === "undefined")
480            width = this._currentSidebarWidth;
481
482        width = Number.constrain(width, Preferences.minSidebarWidth, window.innerWidth / 2);
483
484        this._currentSidebarWidth = width;
485
486        this.sidebarElement.style.width = width + "px";
487        this.storageViews.style.left = width + "px";
488        this.storageViewStatusBarItemsContainer.style.left = width + "px";
489        this.sidebarResizeElement.style.left = (width - 3) + "px";
490
491        var visibleView = this.visibleView;
492        if (visibleView && "resize" in visibleView)
493            visibleView.resize();
494    }
495}
496
497WebInspector.DatabasesPanel.prototype.__proto__ = WebInspector.Panel.prototype;
498
499WebInspector.DatabaseSidebarTreeElement = function(database)
500{
501    this.database = database;
502
503    WebInspector.SidebarTreeElement.call(this, "database-sidebar-tree-item", "", "", database, true);
504
505    this.refreshTitles();
506}
507
508WebInspector.DatabaseSidebarTreeElement.prototype = {
509    onselect: function()
510    {
511        WebInspector.panels.databases.showDatabase(this.database);
512    },
513
514    oncollapse: function()
515    {
516        // Request a refresh after every collapse so the next
517        // expand will have an updated table list.
518        this.shouldRefreshChildren = true;
519    },
520
521    onpopulate: function()
522    {
523        this.removeChildren();
524
525        var tableNames = this.database.tableNames;
526        var tableNamesLength = tableNames.length;
527        for (var i = 0; i < tableNamesLength; ++i)
528            this.appendChild(new WebInspector.SidebarDatabaseTableTreeElement(this.database, tableNames[i]));
529    },
530
531    get mainTitle()
532    {
533        return this.database.name;
534    },
535
536    set mainTitle(x)
537    {
538        // Do nothing.
539    },
540
541    get subtitle()
542    {
543        return this.database.displayDomain;
544    },
545
546    set subtitle(x)
547    {
548        // Do nothing.
549    }
550}
551
552WebInspector.DatabaseSidebarTreeElement.prototype.__proto__ = WebInspector.SidebarTreeElement.prototype;
553
554WebInspector.SidebarDatabaseTableTreeElement = function(database, tableName)
555{
556    this.database = database;
557    this.tableName = tableName;
558
559    WebInspector.SidebarTreeElement.call(this, "database-table-sidebar-tree-item small", tableName, "", null, false);
560}
561
562WebInspector.SidebarDatabaseTableTreeElement.prototype = {
563    onselect: function()
564    {
565        WebInspector.panels.databases.showDatabase(this.database, this.tableName);
566    }
567}
568
569WebInspector.SidebarDatabaseTableTreeElement.prototype.__proto__ = WebInspector.SidebarTreeElement.prototype;
570
571WebInspector.DOMStorageSidebarTreeElement = function(domStorage)
572{
573
574    this.domStorage = domStorage;
575
576    WebInspector.SidebarTreeElement.call(this, "domstorage-sidebar-tree-item", domStorage, "", null, false);
577
578    this.refreshTitles();
579}
580
581WebInspector.DOMStorageSidebarTreeElement.prototype = {
582    onselect: function()
583    {
584        WebInspector.panels.databases.showDOMStorage(this.domStorage);
585    },
586
587    get mainTitle()
588    {
589        return this.domStorage.domain;
590    },
591
592    set mainTitle(x)
593    {
594        // Do nothing.
595    },
596
597    get subtitle()
598    {
599        return ""; //this.database.displayDomain;
600    },
601
602    set subtitle(x)
603    {
604        // Do nothing.
605    }
606}
607
608WebInspector.DOMStorageSidebarTreeElement.prototype.__proto__ = WebInspector.SidebarTreeElement.prototype;
609