IndexedDBViews.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442
  1. /*
  2. * Copyright (C) 2012 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. * @constructor
  32. * @extends {WebInspector.View}
  33. * @param {WebInspector.IndexedDBModel.Database} database
  34. */
  35. WebInspector.IDBDatabaseView = function(database)
  36. {
  37. WebInspector.View.call(this);
  38. this.registerRequiredCSS("indexedDBViews.css");
  39. this.element.addStyleClass("fill");
  40. this.element.addStyleClass("indexed-db-database-view");
  41. this._headersListElement = this.element.createChild("ol", "outline-disclosure");
  42. this._headersTreeOutline = new TreeOutline(this._headersListElement);
  43. this._headersTreeOutline.expandTreeElementsWhenArrowing = true;
  44. this._securityOriginTreeElement = new TreeElement("", null, false);
  45. this._securityOriginTreeElement.selectable = false;
  46. this._headersTreeOutline.appendChild(this._securityOriginTreeElement);
  47. this._nameTreeElement = new TreeElement("", null, false);
  48. this._nameTreeElement.selectable = false;
  49. this._headersTreeOutline.appendChild(this._nameTreeElement);
  50. this._intVersionTreeElement = new TreeElement("", null, false);
  51. this._intVersionTreeElement.selectable = false;
  52. this._headersTreeOutline.appendChild(this._intVersionTreeElement);
  53. this._stringVersionTreeElement = new TreeElement("", null, false);
  54. this._stringVersionTreeElement.selectable = false;
  55. this._headersTreeOutline.appendChild(this._stringVersionTreeElement);
  56. this.update(database);
  57. }
  58. WebInspector.IDBDatabaseView.prototype = {
  59. /**
  60. * @param {string} name
  61. * @param {string} value
  62. */
  63. _formatHeader: function(name, value)
  64. {
  65. var fragment = document.createDocumentFragment();
  66. fragment.createChild("div", "attribute-name").textContent = name + ":";
  67. fragment.createChild("div", "attribute-value source-code").textContent = value;
  68. return fragment;
  69. },
  70. _refreshDatabase: function()
  71. {
  72. this._securityOriginTreeElement.title = this._formatHeader(WebInspector.UIString("Security origin"), this._database.databaseId.securityOrigin);
  73. this._nameTreeElement.title = this._formatHeader(WebInspector.UIString("Name"), this._database.databaseId.name);
  74. this._stringVersionTreeElement.title = this._formatHeader(WebInspector.UIString("String Version"), this._database.version);
  75. this._intVersionTreeElement.title = this._formatHeader(WebInspector.UIString("Integer Version"), this._database.intVersion);
  76. },
  77. /**
  78. * @param {WebInspector.IndexedDBModel.Database} database
  79. */
  80. update: function(database)
  81. {
  82. this._database = database;
  83. this._refreshDatabase();
  84. },
  85. __proto__: WebInspector.View.prototype
  86. }
  87. /**
  88. * @constructor
  89. * @extends {WebInspector.View}
  90. * @param {WebInspector.IndexedDBModel} model
  91. * @param {WebInspector.IndexedDBModel.DatabaseId} databaseId
  92. * @param {WebInspector.IndexedDBModel.ObjectStore} objectStore
  93. * @param {WebInspector.IndexedDBModel.Index} index
  94. */
  95. WebInspector.IDBDataView = function(model, databaseId, objectStore, index)
  96. {
  97. WebInspector.View.call(this);
  98. this.registerRequiredCSS("indexedDBViews.css");
  99. this._model = model;
  100. this._databaseId = databaseId;
  101. this._isIndex = !!index;
  102. this.element.addStyleClass("indexed-db-data-view");
  103. var editorToolbar = this._createEditorToolbar();
  104. this.element.appendChild(editorToolbar);
  105. this._dataGridContainer = this.element.createChild("div", "fill");
  106. this._dataGridContainer.addStyleClass("data-grid-container");
  107. this._refreshButton = new WebInspector.StatusBarButton(WebInspector.UIString("Refresh"), "refresh-storage-status-bar-item");
  108. this._refreshButton.addEventListener("click", this._refreshButtonClicked, this);
  109. this._clearButton = new WebInspector.StatusBarButton(WebInspector.UIString("Clear object store"), "clear-storage-status-bar-item");
  110. this._clearButton.addEventListener("click", this._clearButtonClicked, this);
  111. this._pageSize = 50;
  112. this._skipCount = 0;
  113. this.update(objectStore, index);
  114. this._entries = [];
  115. }
  116. WebInspector.IDBDataView.prototype = {
  117. /**
  118. * @return {WebInspector.DataGrid}
  119. */
  120. _createDataGrid: function()
  121. {
  122. var keyPath = this._isIndex ? this._index.keyPath : this._objectStore.keyPath;
  123. var columns = [];
  124. columns.push({id: "number", title: WebInspector.UIString("#"), width: "50px"});
  125. columns.push({id: "key", titleDOMFragment: this._keyColumnHeaderFragment(WebInspector.UIString("Key"), keyPath)});
  126. if (this._isIndex)
  127. columns.push({id: "primaryKey", titleDOMFragment: this._keyColumnHeaderFragment(WebInspector.UIString("Primary key"), this._objectStore.keyPath)});
  128. columns.push({id: "value", title: WebInspector.UIString("Value")});
  129. var dataGrid = new WebInspector.DataGrid(columns);
  130. return dataGrid;
  131. },
  132. /**
  133. * @param {string} prefix
  134. * @param {*} keyPath
  135. * @return {DocumentFragment}
  136. */
  137. _keyColumnHeaderFragment: function(prefix, keyPath)
  138. {
  139. var keyColumnHeaderFragment = document.createDocumentFragment();
  140. keyColumnHeaderFragment.appendChild(document.createTextNode(prefix));
  141. if (keyPath === null)
  142. return keyColumnHeaderFragment;
  143. keyColumnHeaderFragment.appendChild(document.createTextNode(" (" + WebInspector.UIString("Key path: ")));
  144. if (keyPath instanceof Array) {
  145. keyColumnHeaderFragment.appendChild(document.createTextNode("["));
  146. for (var i = 0; i < keyPath.length; ++i) {
  147. if (i != 0)
  148. keyColumnHeaderFragment.appendChild(document.createTextNode(", "));
  149. keyColumnHeaderFragment.appendChild(this._keyPathStringFragment(keyPath[i]));
  150. }
  151. keyColumnHeaderFragment.appendChild(document.createTextNode("]"));
  152. } else {
  153. var keyPathString = /** @type {string} */ (keyPath);
  154. keyColumnHeaderFragment.appendChild(this._keyPathStringFragment(keyPathString));
  155. }
  156. keyColumnHeaderFragment.appendChild(document.createTextNode(")"));
  157. return keyColumnHeaderFragment;
  158. },
  159. /**
  160. * @param {string} keyPathString
  161. * @return {DocumentFragment}
  162. */
  163. _keyPathStringFragment: function(keyPathString)
  164. {
  165. var keyPathStringFragment = document.createDocumentFragment();
  166. keyPathStringFragment.appendChild(document.createTextNode("\""));
  167. var keyPathSpan = keyPathStringFragment.createChild("span", "source-code console-formatted-string");
  168. keyPathSpan.textContent = keyPathString;
  169. keyPathStringFragment.appendChild(document.createTextNode("\""));
  170. return keyPathStringFragment;
  171. },
  172. /**
  173. * @return {Element}
  174. */
  175. _createEditorToolbar: function()
  176. {
  177. var editorToolbar = document.createElement("div");
  178. editorToolbar.addStyleClass("status-bar");
  179. editorToolbar.addStyleClass("data-view-toolbar");
  180. this._pageBackButton = editorToolbar.createChild("button", "back-button");
  181. this._pageBackButton.addStyleClass("status-bar-item");
  182. this._pageBackButton.title = WebInspector.UIString("Show previous page.");
  183. this._pageBackButton.disabled = true;
  184. this._pageBackButton.appendChild(document.createElement("img"));
  185. this._pageBackButton.addEventListener("click", this._pageBackButtonClicked.bind(this), false);
  186. editorToolbar.appendChild(this._pageBackButton);
  187. this._pageForwardButton = editorToolbar.createChild("button", "forward-button");
  188. this._pageForwardButton.addStyleClass("status-bar-item");
  189. this._pageForwardButton.title = WebInspector.UIString("Show next page.");
  190. this._pageForwardButton.disabled = true;
  191. this._pageForwardButton.appendChild(document.createElement("img"));
  192. this._pageForwardButton.addEventListener("click", this._pageForwardButtonClicked.bind(this), false);
  193. editorToolbar.appendChild(this._pageForwardButton);
  194. this._keyInputElement = editorToolbar.createChild("input", "key-input");
  195. this._keyInputElement.placeholder = WebInspector.UIString("Start from key");
  196. this._keyInputElement.addEventListener("paste", this._keyInputChanged.bind(this));
  197. this._keyInputElement.addEventListener("cut", this._keyInputChanged.bind(this));
  198. this._keyInputElement.addEventListener("keypress", this._keyInputChanged.bind(this));
  199. this._keyInputElement.addEventListener("keydown", this._keyInputChanged.bind(this));
  200. return editorToolbar;
  201. },
  202. _pageBackButtonClicked: function()
  203. {
  204. this._skipCount = Math.max(0, this._skipCount - this._pageSize);
  205. this._updateData(false);
  206. },
  207. _pageForwardButtonClicked: function()
  208. {
  209. this._skipCount = this._skipCount + this._pageSize;
  210. this._updateData(false);
  211. },
  212. _keyInputChanged: function()
  213. {
  214. window.setTimeout(this._updateData.bind(this, false), 0);
  215. },
  216. /**
  217. * @param {WebInspector.IndexedDBModel.ObjectStore} objectStore
  218. * @param {WebInspector.IndexedDBModel.Index} index
  219. */
  220. update: function(objectStore, index)
  221. {
  222. this._objectStore = objectStore;
  223. this._index = index;
  224. if (this._dataGrid)
  225. this._dataGrid.detach();
  226. this._dataGrid = this._createDataGrid();
  227. this._dataGrid.show(this._dataGridContainer);
  228. this._skipCount = 0;
  229. this._updateData(true);
  230. },
  231. /**
  232. * @param {string} keyString
  233. */
  234. _parseKey: function(keyString)
  235. {
  236. var result;
  237. try {
  238. result = JSON.parse(keyString);
  239. } catch (e) {
  240. result = keyString;
  241. }
  242. return result;
  243. },
  244. /**
  245. * @return {string}
  246. */
  247. _stringifyKey: function(key)
  248. {
  249. if (typeof(key) === "string")
  250. return key;
  251. return JSON.stringify(key);
  252. },
  253. /**
  254. * @param {boolean} force
  255. */
  256. _updateData: function(force)
  257. {
  258. var key = this._parseKey(this._keyInputElement.value);
  259. var pageSize = this._pageSize;
  260. var skipCount = this._skipCount;
  261. this._refreshButton.setEnabled(false);
  262. this._clearButton.setEnabled(!this._isIndex);
  263. if (!force && this._lastKey === key && this._lastPageSize === pageSize && this._lastSkipCount === skipCount)
  264. return;
  265. if (this._lastKey !== key || this._lastPageSize !== pageSize) {
  266. skipCount = 0;
  267. this._skipCount = 0;
  268. }
  269. this._lastKey = key;
  270. this._lastPageSize = pageSize;
  271. this._lastSkipCount = skipCount;
  272. /**
  273. * @param {Array.<WebInspector.IndexedDBModel.Entry>} entries
  274. * @param {boolean} hasMore
  275. */
  276. function callback(entries, hasMore)
  277. {
  278. this._refreshButton.setEnabled(true);
  279. this.clear();
  280. this._entries = entries;
  281. for (var i = 0; i < entries.length; ++i) {
  282. var data = {};
  283. data["number"] = i + skipCount;
  284. data["key"] = entries[i].key;
  285. data["primaryKey"] = entries[i].primaryKey;
  286. data["value"] = entries[i].value;
  287. var primaryKey = JSON.stringify(this._isIndex ? entries[i].primaryKey : entries[i].key);
  288. var node = new WebInspector.IDBDataGridNode(data);
  289. this._dataGrid.rootNode().appendChild(node);
  290. }
  291. this._pageBackButton.disabled = skipCount === 0;
  292. this._pageForwardButton.disabled = !hasMore;
  293. }
  294. var idbKeyRange = key ? window.webkitIDBKeyRange.lowerBound(key) : null;
  295. if (this._isIndex)
  296. this._model.loadIndexData(this._databaseId, this._objectStore.name, this._index.name, idbKeyRange, skipCount, pageSize, callback.bind(this));
  297. else
  298. this._model.loadObjectStoreData(this._databaseId, this._objectStore.name, idbKeyRange, skipCount, pageSize, callback.bind(this));
  299. },
  300. _refreshButtonClicked: function(event)
  301. {
  302. this._updateData(true);
  303. },
  304. _clearButtonClicked: function(event)
  305. {
  306. function cleared() {
  307. this._clearButton.setEnabled(true);
  308. this._updateData(true);
  309. }
  310. this._clearButton.setEnabled(false);
  311. this._model.clearObjectStore(this._databaseId, this._objectStore.name, cleared.bind(this));
  312. },
  313. get statusBarItems()
  314. {
  315. return [this._refreshButton.element, this._clearButton.element];
  316. },
  317. clear: function()
  318. {
  319. this._dataGrid.rootNode().removeChildren();
  320. for (var i = 0; i < this._entries.length; ++i) {
  321. this._entries[i].key.release();
  322. this._entries[i].primaryKey.release();
  323. this._entries[i].value.release();
  324. }
  325. this._entries = [];
  326. },
  327. __proto__: WebInspector.View.prototype
  328. }
  329. /**
  330. * @constructor
  331. * @extends {WebInspector.DataGridNode}
  332. * @param {!Object.<string, *>} data
  333. */
  334. WebInspector.IDBDataGridNode = function(data)
  335. {
  336. WebInspector.DataGridNode.call(this, data, false);
  337. this.selectable = false;
  338. }
  339. WebInspector.IDBDataGridNode.prototype = {
  340. /**
  341. * @return {Element}
  342. */
  343. createCell: function(columnIdentifier)
  344. {
  345. var cell = WebInspector.DataGridNode.prototype.createCell.call(this, columnIdentifier);
  346. var value = this.data[columnIdentifier];
  347. switch (columnIdentifier) {
  348. case "value":
  349. case "key":
  350. case "primaryKey":
  351. cell.removeChildren();
  352. this._formatValue(cell, value);
  353. break;
  354. default:
  355. }
  356. return cell;
  357. },
  358. _formatValue: function(cell, value)
  359. {
  360. var type = value.subtype || value.type;
  361. var contents = cell.createChild("div", "source-code console-formatted-" + type);
  362. switch (type) {
  363. case "object":
  364. case "array":
  365. var section = new WebInspector.ObjectPropertiesSection(value, value.description)
  366. section.editable = false;
  367. section.skipProto = true;
  368. contents.appendChild(section.element);
  369. break;
  370. case "string":
  371. contents.addStyleClass("primitive-value");
  372. contents.appendChild(document.createTextNode("\"" + value.description + "\""));
  373. break;
  374. default:
  375. contents.addStyleClass("primitive-value");
  376. contents.appendChild(document.createTextNode(value.description));
  377. }
  378. },
  379. __proto__: WebInspector.DataGridNode.prototype
  380. }