author | Victor Porof <vporof@mozilla.com> |
Wed, 18 Dec 2013 19:01:38 +0200 | |
changeset 161089 | b48f948547efc7b04bb6d111935a19c18d83a268 |
parent 161088 | e472c39cc2bbe9fa5391bf74b5905614228a613a |
child 161090 | eb4d2915ab262eab87ca762ced13629a89a4ca19 |
push id | 25867 |
push user | ryanvm@gmail.com |
push date | Thu, 19 Dec 2013 02:19:33 +0000 |
treeherder | mozilla-central@04a70c8908de [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | past |
bugs | 830344 |
milestone | 29.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/browser/devtools/debugger/test/browser_dbg_variables-view-accessibility.js +++ b/browser/devtools/debugger/test/browser_dbg_variables-view-accessibility.js @@ -56,17 +56,17 @@ function performTest() { get someProp7() { return arr; }, set someProp7(value) { arr[0] = value } }; gVariablesView.eval = function() {}; gVariablesView.switch = function() {}; gVariablesView.delete = function() {}; gVariablesView.rawObject = test; - gVariablesView.pageSize = 5; + gVariablesView.scrollPageSize = 5; return Task.spawn(function() { yield waitForTick(); // Part 0: Test generic focus methods on the variables view. gVariablesView.focusFirstVisibleItem(); is(gVariablesView.getFocusedItem().name, "someProp0",
--- a/browser/devtools/debugger/test/browser_dbg_variables-view-large-array-buffer.js +++ b/browser/devtools/debugger/test/browser_dbg_variables-view-large-array-buffer.js @@ -4,42 +4,240 @@ /** * Make sure that the variables view remains responsive when faced with * huge ammounts of data. */ const TAB_URL = EXAMPLE_URL + "doc_large-array-buffer.html"; let gTab, gDebuggee, gPanel, gDebugger; -let gVariables; +let gVariables, gEllipsis; function test() { initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => { gTab = aTab; gDebuggee = aDebuggee; gPanel = aPanel; gDebugger = gPanel.panelWin; gVariables = gDebugger.DebuggerView.Variables; + gEllipsis = Services.prefs.getComplexValue("intl.ellipsis", Ci.nsIPrefLocalizedString).data; - waitForSourceAndCaretAndScopes(gPanel, ".html", 18) - .then(() => performTest()) + waitForSourceAndCaretAndScopes(gPanel, ".html", 23) + .then(() => initialChecks()) + .then(() => verifyFirstLevel()) + .then(() => verifyNextLevels()) .then(() => resumeDebuggerThenCloseAndFinish(gPanel)) .then(null, aError => { ok(false, "Got an error: " + aError.message + "\n" + aError.stack); }); EventUtils.sendMouseEvent({ type: "click" }, gDebuggee.document.querySelector("button"), gDebuggee); }); } -function performTest() { +function initialChecks() { + let localScope = gVariables.getScopeAtIndex(0); + let bufferVar = localScope.get("buffer"); + let arrayVar = localScope.get("largeArray"); + let objectVar = localScope.get("largeObject"); + + ok(bufferVar, "There should be a 'buffer' variable present in the scope."); + ok(arrayVar, "There should be a 'largeArray' variable present in the scope."); + ok(objectVar, "There should be a 'largeObject' variable present in the scope."); + + is(bufferVar.target.querySelector(".name").getAttribute("value"), "buffer", + "Should have the right property name for 'buffer'."); + is(bufferVar.target.querySelector(".value").getAttribute("value"), "ArrayBuffer", + "Should have the right property value for 'buffer'."); + ok(bufferVar.target.querySelector(".value").className.contains("token-other"), + "Should have the right token class for 'buffer'."); + + is(arrayVar.target.querySelector(".name").getAttribute("value"), "largeArray", + "Should have the right property name for 'largeArray'."); + is(arrayVar.target.querySelector(".value").getAttribute("value"), "Int8Array", + "Should have the right property value for 'largeArray'."); + ok(arrayVar.target.querySelector(".value").className.contains("token-other"), + "Should have the right token class for 'largeArray'."); + + is(objectVar.target.querySelector(".name").getAttribute("value"), "largeObject", + "Should have the right property name for 'largeObject'."); + is(objectVar.target.querySelector(".value").getAttribute("value"), "Object", + "Should have the right property value for 'largeObject'."); + ok(objectVar.target.querySelector(".value").className.contains("token-other"), + "Should have the right token class for 'largeObject'."); + + is(bufferVar.expanded, false, + "The 'buffer' variable shouldn't be expanded."); + is(arrayVar.expanded, false, + "The 'largeArray' variable shouldn't be expanded."); + is(objectVar.expanded, false, + "The 'largeObject' variable shouldn't be expanded."); + + let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_PROPERTIES, 2); + arrayVar.expand(); + objectVar.expand(); + return finished; +} + +function verifyFirstLevel() { + let localScope = gVariables.getScopeAtIndex(0); + let arrayVar = localScope.get("largeArray"); + let objectVar = localScope.get("largeObject"); + + let arrayEnums = arrayVar.target.querySelector(".variables-view-element-details.enum").childNodes; + let arrayNonEnums = arrayVar.target.querySelector(".variables-view-element-details.nonenum").childNodes; + is(arrayEnums.length, 0, + "The 'largeArray' shouldn't contain any enumerable elements."); + is(arrayNonEnums.length, 9, + "The 'largeArray' should contain all the created non-enumerable elements."); + + let objectEnums = objectVar.target.querySelector(".variables-view-element-details.enum").childNodes; + let objectNonEnums = objectVar.target.querySelector(".variables-view-element-details.nonenum").childNodes; + is(objectEnums.length, 0, + "The 'largeObject' shouldn't contain any enumerable elements."); + is(objectNonEnums.length, 5, + "The 'largeObject' should contain all the created non-enumerable elements."); + + is(arrayVar.target.querySelectorAll(".variables-view-property .name")[0].getAttribute("value"), + 0 + gEllipsis + 1999, "The first page in the 'largeArray' is named correctly."); + is(arrayVar.target.querySelectorAll(".variables-view-property .value")[0].getAttribute("value"), + "", "The first page in the 'largeArray' should not have a corresponding value."); + is(arrayVar.target.querySelectorAll(".variables-view-property .name")[1].getAttribute("value"), + 2000 + gEllipsis + 3999, "The second page in the 'largeArray' is named correctly."); + is(arrayVar.target.querySelectorAll(".variables-view-property .value")[1].getAttribute("value"), + "", "The second page in the 'largeArray' should not have a corresponding value."); + is(arrayVar.target.querySelectorAll(".variables-view-property .name")[2].getAttribute("value"), + 4000 + gEllipsis + 5999, "The third page in the 'largeArray' is named correctly."); + is(arrayVar.target.querySelectorAll(".variables-view-property .value")[2].getAttribute("value"), + "", "The third page in the 'largeArray' should not have a corresponding value."); + is(arrayVar.target.querySelectorAll(".variables-view-property .name")[3].getAttribute("value"), + 6000 + gEllipsis + 9999, "The fourth page in the 'largeArray' is named correctly."); + is(arrayVar.target.querySelectorAll(".variables-view-property .value")[3].getAttribute("value"), + "", "The fourth page in the 'largeArray' should not have a corresponding value."); + + is(objectVar.target.querySelectorAll(".variables-view-property .name")[0].getAttribute("value"), + 0 + gEllipsis + 1999, "The first page in the 'largeObject' is named correctly."); + is(objectVar.target.querySelectorAll(".variables-view-property .value")[0].getAttribute("value"), + "", "The first page in the 'largeObject' should not have a corresponding value."); + is(objectVar.target.querySelectorAll(".variables-view-property .name")[1].getAttribute("value"), + 2000 + gEllipsis + 3999, "The second page in the 'largeObject' is named correctly."); + is(objectVar.target.querySelectorAll(".variables-view-property .value")[1].getAttribute("value"), + "", "The second page in the 'largeObject' should not have a corresponding value."); + is(objectVar.target.querySelectorAll(".variables-view-property .name")[2].getAttribute("value"), + 4000 + gEllipsis + 5999, "The thrid page in the 'largeObject' is named correctly."); + is(objectVar.target.querySelectorAll(".variables-view-property .value")[2].getAttribute("value"), + "", "The thrid page in the 'largeObject' should not have a corresponding value."); + is(objectVar.target.querySelectorAll(".variables-view-property .name")[3].getAttribute("value"), + 6000 + gEllipsis + 9999, "The fourth page in the 'largeObject' is named correctly."); + is(objectVar.target.querySelectorAll(".variables-view-property .value")[3].getAttribute("value"), + "", "The fourth page in the 'largeObject' should not have a corresponding value."); + is(arrayVar.target.querySelectorAll(".variables-view-property .name")[4].getAttribute("value"), + "length", "The other properties 'largeArray' are named correctly."); + is(arrayVar.target.querySelectorAll(".variables-view-property .value")[4].getAttribute("value"), + "10000", "The other properties 'largeArray' have the correct value."); + is(arrayVar.target.querySelectorAll(".variables-view-property .name")[5].getAttribute("value"), + "buffer", "The other properties 'largeArray' are named correctly."); + is(arrayVar.target.querySelectorAll(".variables-view-property .value")[5].getAttribute("value"), + "ArrayBuffer", "The other properties 'largeArray' have the correct value."); + is(arrayVar.target.querySelectorAll(".variables-view-property .name")[6].getAttribute("value"), + "byteLength", "The other properties 'largeArray' are named correctly."); + is(arrayVar.target.querySelectorAll(".variables-view-property .value")[6].getAttribute("value"), + "10000", "The other properties 'largeArray' have the correct value."); + is(arrayVar.target.querySelectorAll(".variables-view-property .name")[7].getAttribute("value"), + "byteOffset", "The other properties 'largeArray' are named correctly."); + is(arrayVar.target.querySelectorAll(".variables-view-property .value")[7].getAttribute("value"), + "0", "The other properties 'largeArray' have the correct value."); + is(arrayVar.target.querySelectorAll(".variables-view-property .name")[8].getAttribute("value"), + "__proto__", "The other properties 'largeArray' are named correctly."); + is(arrayVar.target.querySelectorAll(".variables-view-property .value")[8].getAttribute("value"), + "Int8ArrayPrototype", "The other properties 'largeArray' have the correct value."); + + is(objectVar.target.querySelectorAll(".variables-view-property .name")[4].getAttribute("value"), + "__proto__", "The other properties 'largeObject' are named correctly."); + is(objectVar.target.querySelectorAll(".variables-view-property .value")[4].getAttribute("value"), + "Object", "The other properties 'largeObject' have the correct value."); +} + +function verifyNextLevels() { + let localScope = gVariables.getScopeAtIndex(0); + let objectVar = localScope.get("largeObject"); + + let lastPage1 = objectVar.get(6000 + gEllipsis + 9999); + ok(lastPage1, "The last page in the first level was retrieved successfully."); + lastPage1.expand(); + + let pageEnums1 = lastPage1.target.querySelector(".variables-view-element-details.enum").childNodes; + let pageNonEnums1 = lastPage1.target.querySelector(".variables-view-element-details.nonenum").childNodes; + is(pageEnums1.length, 0, + "The last page in the first level shouldn't contain any enumerable elements."); + is(pageNonEnums1.length, 4, + "The last page in the first level should contain all the created non-enumerable elements."); + + is(lastPage1._nonenum.querySelectorAll(".variables-view-property .name")[0].getAttribute("value"), + 6000 + gEllipsis + 6999, "The first page in this level named correctly (1)."); + is(lastPage1._nonenum.querySelectorAll(".variables-view-property .name")[1].getAttribute("value"), + 7000 + gEllipsis + 7999, "The second page in this level named correctly (1)."); + is(lastPage1._nonenum.querySelectorAll(".variables-view-property .name")[2].getAttribute("value"), + 8000 + gEllipsis + 8999, "The third page in this level named correctly (1)."); + is(lastPage1._nonenum.querySelectorAll(".variables-view-property .name")[3].getAttribute("value"), + 9000 + gEllipsis + 9999, "The fourth page in this level named correctly (1)."); + + let lastPage2 = lastPage1.get(9000 + gEllipsis + 9999); + ok(lastPage2, "The last page in the second level was retrieved successfully."); + lastPage2.expand(); + + let pageEnums2 = lastPage2.target.querySelector(".variables-view-element-details.enum").childNodes; + let pageNonEnums2 = lastPage2.target.querySelector(".variables-view-element-details.nonenum").childNodes; + is(pageEnums2.length, 0, + "The last page in the second level shouldn't contain any enumerable elements."); + is(pageNonEnums2.length, 4, + "The last page in the second level should contain all the created non-enumerable elements."); + + is(lastPage2._nonenum.querySelectorAll(".variables-view-property .name")[0].getAttribute("value"), + 9000 + gEllipsis + 9199, "The first page in this level named correctly (2)."); + is(lastPage2._nonenum.querySelectorAll(".variables-view-property .name")[1].getAttribute("value"), + 9200 + gEllipsis + 9399, "The second page in this level named correctly (2)."); + is(lastPage2._nonenum.querySelectorAll(".variables-view-property .name")[2].getAttribute("value"), + 9400 + gEllipsis + 9599, "The third page in this level named correctly (2)."); + is(lastPage2._nonenum.querySelectorAll(".variables-view-property .name")[3].getAttribute("value"), + 9600 + gEllipsis + 9999, "The fourth page in this level named correctly (2)."); + + let lastPage3 = lastPage2.get(9600 + gEllipsis + 9999); + ok(lastPage3, "The last page in the third level was retrieved successfully."); + lastPage3.expand(); + + let pageEnums3 = lastPage3.target.querySelector(".variables-view-element-details.enum").childNodes; + let pageNonEnums3 = lastPage3.target.querySelector(".variables-view-element-details.nonenum").childNodes; + is(pageEnums3.length, 400, + "The last page in the third level should contain all the created enumerable elements."); + is(pageNonEnums3.length, 0, + "The last page in the third level shouldn't contain any non-enumerable elements."); + + is(lastPage3._enum.querySelectorAll(".variables-view-property .name")[0].getAttribute("value"), + 9600, "The properties in this level are named correctly (3)."); + is(lastPage3._enum.querySelectorAll(".variables-view-property .name")[1].getAttribute("value"), + 9601, "The properties in this level are named correctly (3)."); + is(lastPage3._enum.querySelectorAll(".variables-view-property .name")[398].getAttribute("value"), + 9998, "The properties in this level are named correctly (3)."); + is(lastPage3._enum.querySelectorAll(".variables-view-property .name")[399].getAttribute("value"), + 9999, "The properties in this level are named correctly (3)."); + + is(lastPage3._enum.querySelectorAll(".variables-view-property .value")[0].getAttribute("value"), + 399, "The properties in this level have the correct value (3)."); + is(lastPage3._enum.querySelectorAll(".variables-view-property .value")[1].getAttribute("value"), + 398, "The properties in this level have the correct value (3)."); + is(lastPage3._enum.querySelectorAll(".variables-view-property .value")[398].getAttribute("value"), + 1, "The properties in this level have the correct value (3)."); + is(lastPage3._enum.querySelectorAll(".variables-view-property .value")[399].getAttribute("value"), + 0, "The properties in this level have the correct value (3)."); } registerCleanupFunction(function() { gTab = null; gDebuggee = null; gPanel = null; gDebugger = null; gVariables = null; + gEllipsis = null; });
--- a/browser/devtools/debugger/test/doc_large-array-buffer.html +++ b/browser/devtools/debugger/test/doc_large-array-buffer.html @@ -9,14 +9,19 @@ </head> <body> <button onclick="test(10000)">Click me!</button> <script type="text/javascript"> function test(aNumber) { var buffer = new ArrayBuffer(aNumber); - var z = new Int8Array(buffer); + var largeArray = new Int8Array(buffer); + var largeObject = {}; + + for (var i = 0; i < aNumber; i++) { + largeObject[i] = aNumber - i - 1; + } debugger; } </script> </body> </html>
--- a/browser/devtools/netmonitor/test/browser_net_json-long.js +++ b/browser/devtools/netmonitor/test/browser_net_json-long.js @@ -56,41 +56,36 @@ function test() { .hasAttribute("hidden"), true, "The response content textarea box doesn't have the intended visibility."); is(tabpanel.querySelector("#response-content-image-box") .hasAttribute("hidden"), true, "The response content image box doesn't have the intended visibility."); is(tabpanel.querySelectorAll(".variables-view-scope").length, 1, "There should be 1 json scope displayed in this tabpanel."); - is(tabpanel.querySelectorAll(".variables-view-property").length, 6057, - "There should be 6057 json properties displayed in this tabpanel."); + is(tabpanel.querySelectorAll(".variables-view-property").length, 6143, + "There should be 6143 json properties displayed in this tabpanel."); is(tabpanel.querySelectorAll(".variables-view-empty-notice").length, 0, "The empty notice should not be displayed in this tabpanel."); let jsonScope = tabpanel.querySelectorAll(".variables-view-scope")[0]; - let names = ".variables-view-property .name"; - let values = ".variables-view-property .value"; + let names = ".variables-view-property > .title > .name"; + let values = ".variables-view-property > .title > .value"; is(jsonScope.querySelector(".name").getAttribute("value"), L10N.getStr("jsonScopeName"), "The json scope doesn't have the correct title."); is(jsonScope.querySelectorAll(names)[0].getAttribute("value"), "0", "The first json property name was incorrect."); is(jsonScope.querySelectorAll(values)[0].getAttribute("value"), "Object", "The first json property value was incorrect."); is(jsonScope.querySelectorAll(names)[1].getAttribute("value"), "greeting", "The second json property name was incorrect."); is(jsonScope.querySelectorAll(values)[1].getAttribute("value"), "\"Hello long string JSON!\"", "The second json property value was incorrect."); - - is(Array.slice(jsonScope.querySelectorAll(names), -1).shift().getAttribute("value"), - "__proto__", "The last json property name was incorrect."); - is(Array.slice(jsonScope.querySelectorAll(values), -1).shift().getAttribute("value"), - "Object", "The last json property value was incorrect."); } }); aDebuggee.performRequests(); }); }
--- a/browser/devtools/shared/widgets/SideMenuWidget.jsm +++ b/browser/devtools/shared/widgets/SideMenuWidget.jsm @@ -172,16 +172,17 @@ SideMenuWidget.prototype = { aChild.parentNode.remove(); } else { // Groups with no title don't have any special internal structure. aChild.remove(); } this._orderedMenuElementsArray.splice( this._orderedMenuElementsArray.indexOf(aChild), 1); + this._itemsByElement.delete(aChild); if (this._selectedItem == aChild) { this._selectedItem = null; } }, /**
--- a/browser/devtools/shared/widgets/VariablesView.jsm +++ b/browser/devtools/shared/widgets/VariablesView.jsm @@ -6,16 +6,18 @@ "use strict"; const Ci = Components.interfaces; const Cu = Components.utils; const DBG_STRINGS_URI = "chrome://browser/locale/devtools/debugger.properties"; const LAZY_EMPTY_DELAY = 150; // ms const LAZY_EXPAND_DELAY = 50; // ms +const SCROLL_PAGE_SIZE_DEFAULT = 0; +const APPEND_PAGE_SIZE_DEFAULT = 500; const PAGE_SIZE_SCROLL_HEIGHT_RATIO = 100; const PAGE_SIZE_MAX_JUMPS = 30; const SEARCH_ACTION_MAX_DELAY = 300; // ms const ITEM_FLASH_DURATION = 300 // ms Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource:///modules/devtools/ViewHelpers.jsm"); @@ -224,16 +226,29 @@ VariablesView.prototype = { lazyEmpty: false, /** * Specifies if nodes in this view may be searched lazily. */ lazySearch: true, /** + * The number of elements in this container to jump when Page Up or Page Down + * keys are pressed. If falsy, then the page size will be based on the + * container height. + */ + scrollPageSize: SCROLL_PAGE_SIZE_DEFAULT, + + /** + * The maximum number of elements allowed in a scope, variable or property + * that allows pagination when appending children. + */ + appendPageSize: APPEND_PAGE_SIZE_DEFAULT, + + /** * Function called each time a variable or property's value is changed via * user interaction. If null, then value changes are disabled. * * This property is applied recursively onto each scope in this view and * affects only the child nodes when they're created. */ eval: null, @@ -802,24 +817,24 @@ VariablesView.prototype = { item.expand(); } else { this.focusNextItem(true); } return; case e.DOM_VK_PAGE_UP: // Rewind a certain number of elements based on the container height. - this.focusItemAtDelta(-(this.pageSize || Math.min(Math.floor(this._list.scrollHeight / + this.focusItemAtDelta(-(this.scrollPageSize || Math.min(Math.floor(this._list.scrollHeight / PAGE_SIZE_SCROLL_HEIGHT_RATIO), PAGE_SIZE_MAX_JUMPS))); return; case e.DOM_VK_PAGE_DOWN: // Advance a certain number of elements based on the container height. - this.focusItemAtDelta(+(this.pageSize || Math.min(Math.floor(this._list.scrollHeight / + this.focusItemAtDelta(+(this.scrollPageSize || Math.min(Math.floor(this._list.scrollHeight / PAGE_SIZE_SCROLL_HEIGHT_RATIO), PAGE_SIZE_MAX_JUMPS))); return; case e.DOM_VK_HOME: this.focusFirstVisibleItem(); return; @@ -864,23 +879,16 @@ VariablesView.prototype = { clipboardHelper.copyString( item._nameString + item.separatorStr + item._valueString ); } } }, /** - * The number of elements in this container to jump when Page Up or Page Down - * keys are pressed. If falsy, then the page size will be based on the - * container height. - */ - pageSize: 0, - - /** * Sets the text displayed in this container when there are no available items. * @param string aValue */ set emptyText(aValue) { if (this._emptyTextNode) { this._emptyTextNode.setAttribute("value", aValue); } this._emptyTextValue = aValue; @@ -1185,16 +1193,18 @@ function Scope(aView, aName, aFlags = {} this.ownerView = aView; this._onClick = this._onClick.bind(this); this._openEnum = this._openEnum.bind(this); this._openNonEnum = this._openNonEnum.bind(this); // Inherit properties and flags from the parent view. You can override // each of these directly onto any scope, variable or property instance. + this.scrollPageSize = aView.scrollPageSize; + this.appendPageSize = aView.appendPageSize; this.eval = aView.eval; this.switch = aView.switch; this.delete = aView.delete; this.new = aView.new; this.preventDisableOnChange = aView.preventDisableOnChange; this.preventDescriptorModifiers = aView.preventDescriptorModifiers; this.editableNameTooltip = aView.editableNameTooltip; this.editableValueTooltip = aView.editableValueTooltip; @@ -1208,16 +1218,21 @@ function Scope(aView, aName, aFlags = {} Scope.prototype = { /** * Whether this Scope should be prefetched when it is remoted. */ shouldPrefetch: true, /** + * Whether this Scope should paginate its contents. + */ + allowPaginate: false, + + /** * The class name applied to this scope's target element. */ targetClassName: "variables-view-scope", /** * Create a new Variable that is a child of this Scope. * * @param string aName @@ -1284,24 +1299,94 @@ Scope.prototype = { * someProp4: { value: { type: "null" } }, * someProp5: { value: { type: "object", class: "Object" } }, * someProp6: { get: { type: "object", class: "Function" }, * set: { type: "undefined" } } } * @param object aOptions [optional] * Additional options for adding the properties. Supported options: * - sorted: true to sort all the properties before adding them * - callback: function invoked after each item is added + * @param string aKeysType [optional] + * Helper argument in the case of paginated items. Can be either + * "just-strings" or "just-numbers". Humans shouldn't use this argument. */ - addItems: function(aItems, aOptions = {}) { + addItems: function(aItems, aOptions = {}, aKeysType = "") { let names = Object.keys(aItems); + // Building the view when inspecting an object with a very large number of + // properties may take a long time. To avoid blocking the UI, group + // the items into several lazily populated pseudo-items. + let exceedsThreshold = names.length >= this.appendPageSize; + let shouldPaginate = exceedsThreshold && aKeysType != "just-strings"; + if (shouldPaginate && this.allowPaginate) { + // Group the items to append into two separate arrays, one containing + // number-like keys, the other one containing string keys. + if (aKeysType == "just-numbers") { + var numberKeys = names; + var stringKeys = []; + } else { + var numberKeys = []; + var stringKeys = []; + for (let name of names) { + // Be very careful. Avoid Infinity, NaN and non Natural number keys. + let coerced = +name; + if (Number.isInteger(coerced) && coerced > -1) { + numberKeys.push(name); + } else { + stringKeys.push(name); + } + } + } + + // This object contains a very large number of properties, but they're + // almost all strings that can't be coerced to numbers. Don't paginate. + if (numberKeys.length < this.appendPageSize) { + this.addItems(aItems, aOptions, "just-strings"); + return; + } + + // Slices a section of the { name: descriptor } data properties. + let paginate = (aArray, aBegin = 0, aEnd = aArray.length) => { + let store = {} + for (let i = aBegin; i < aEnd; i++) { + let name = aArray[i]; + store[name] = aItems[name]; + } + return store; + }; + + // Creates a pseudo-item that populates itself with the data properties + // from the corresponding page range. + let createRangeExpander = (aArray, aBegin, aEnd, aOptions, aKeyTypes) => { + let rangeVar = this.addItem(aArray[aBegin] + Scope.ellipsis + aArray[aEnd - 1]); + rangeVar.onexpand = () => { + let pageItems = paginate(aArray, aBegin, aEnd); + rangeVar.addItems(pageItems, aOptions, aKeyTypes); + } + rangeVar.showArrow(); + rangeVar.target.setAttribute("pseudo-item", ""); + }; + + // Divide the number keys into quarters. + let page = +Math.round(numberKeys.length / 4).toPrecision(1); + createRangeExpander(numberKeys, 0, page, aOptions, "just-numbers"); + createRangeExpander(numberKeys, page, page * 2, aOptions, "just-numbers"); + createRangeExpander(numberKeys, page * 2, page * 3, aOptions, "just-numbers"); + createRangeExpander(numberKeys, page * 3, numberKeys.length, aOptions, "just-numbers"); + + // Append all the string keys together. + this.addItems(paginate(stringKeys), aOptions, "just-strings"); + return; + } + // Sort all of the properties before adding them, if preferred. - if (aOptions.sorted) { + if (aOptions.sorted && aKeysType != "just-numbers") { names.sort(); } + // Add the properties to the current scope. for (let name of names) { let descriptor = aItems[name]; let item = this.addItem(name, descriptor); if (aOptions.callback) { aOptions.callback(item, descriptor.value); } @@ -2012,16 +2097,20 @@ Scope.prototype = { // Creating maps and arrays thousands of times for variables or properties // with a large number of children fills up a lot of memory. Make sure // these are instantiated only if needed. DevToolsUtils.defineLazyPrototypeGetter(Scope.prototype, "_store", Map); DevToolsUtils.defineLazyPrototypeGetter(Scope.prototype, "_enumItems", Array); DevToolsUtils.defineLazyPrototypeGetter(Scope.prototype, "_nonEnumItems", Array); +// An ellipsis symbol (usually "…") used for localization. +XPCOMUtils.defineLazyGetter(Scope, "ellipsis", () => + Services.prefs.getComplexValue("intl.ellipsis", Ci.nsIPrefLocalizedString).data); + /** * A Variable is a Scope holding Property instances. * Iterable via "for (let [name, property] of instance) { }". * * @param Scope aScope * The scope to contain this variable. * @param string aName * The variable's name. @@ -2043,23 +2132,30 @@ function Variable(aScope, aName, aDescri Scope.call(this, aScope, aName, this._initialDescriptor = aDescriptor); this.setGrip(aDescriptor.value); this._symbolicName = aName; this._absoluteName = aScope.name + "[\"" + aName + "\"]"; } Variable.prototype = Heritage.extend(Scope.prototype, { /** - * Whether this Scope should be prefetched when it is remoted. + * Whether this Variable should be prefetched when it is remoted. */ - get shouldPrefetch(){ + get shouldPrefetch() { return this.name == "window" || this.name == "this"; }, /** + * Whether this Variable should paginate its contents. + */ + get allowPaginate() { + return this.name != "window" && this.name != "this"; + }, + + /** * The class name applied to this variable's target element. */ targetClassName: "variables-view-variable variable-or-property", /** * Create a new Property that is a child of Variable. * * @param string aName @@ -3109,17 +3205,16 @@ VariablesView.getClass = function(aGrip) */ let generateId = (function() { let count = 0; return function(aName = "") { return aName.toLowerCase().trim().replace(/\s+/g, "-") + (++count); }; })(); - /** * An Editable encapsulates the UI of an edit box that overlays a label, * allowing the user to edit the value. * * @param Variable aVariable * The Variable or Property to make editable. * @param object aOptions * - onSave
--- a/browser/devtools/shared/widgets/widgets.css +++ b/browser/devtools/shared/widgets/widgets.css @@ -59,17 +59,24 @@ .variable-or-property:not([safe-getter]) > tooltip > label.WebIDL, .variable-or-property:not([overridden]) > tooltip > label.overridden, .variable-or-property:not([non-extensible]) > tooltip > label.extensible, .variable-or-property:not([frozen]) > tooltip > label.frozen, .variable-or-property:not([sealed]) > tooltip > label.sealed { display: none; } -.variable-or-property[pseudo-item] > tooltip { +.variable-or-property[pseudo-item] > tooltip, +.variable-or-property[pseudo-item] > .title > .variables-view-edit, +.variable-or-property[pseudo-item] > .title > .variables-view-delete, +.variable-or-property[pseudo-item] > .title > .variables-view-add-property, +.variable-or-property[pseudo-item] > .title > .variable-or-property-frozen-label, +.variable-or-property[pseudo-item] > .title > .variable-or-property-sealed-label, +.variable-or-property[pseudo-item] > .title > .variable-or-property-non-extensible-label, +.variable-or-property[pseudo-item] > .title > .variable-or-property-non-writable-icon { display: none; } *:not(:hover) .variables-view-delete, *:not(:hover) .variables-view-add-property { visibility: hidden; }
--- a/browser/themes/linux/devtools/widgets.css +++ b/browser/themes/linux/devtools/widgets.css @@ -446,28 +446,18 @@ color: GrayText; padding: 2px; } .variables-view-scope > .title { color: #fff; } -.variables-view-scope > .variables-view-element-details:not(:empty) { - -moz-margin-start: 2px; - -moz-margin-end: 1px; -} - /* Generic variables traits */ -.variables-view-variable { - -moz-margin-start: 1px; - -moz-margin-end: 1px; -} - .variables-view-variable:not(:last-child) { border-bottom: 1px solid rgba(128, 128, 128, .15); } .variables-view-variable > .title > .name { font-weight: 600; } @@ -476,18 +466,22 @@ .variable-or-property:focus > .title > label { color: inherit !important; } .variable-or-property > .title > .value { -moz-box-flex: 1; } +.variable-or-property > .title > .arrow { + -moz-margin-start: 3px; +} + .variable-or-property:not([untitled]) > .variables-view-element-details { - -moz-margin-start: 10px; + -moz-margin-start: 7px; } /* Traits applied when variables or properties are changed or overridden */ .variable-or-property:not([overridden]) { transition: background 1s ease-in-out; }
--- a/browser/themes/osx/devtools/widgets.css +++ b/browser/themes/osx/devtools/widgets.css @@ -440,28 +440,18 @@ color: GrayText; padding: 2px; } .variables-view-scope > .title { color: #fff; } -.variables-view-scope > .variables-view-element-details:not(:empty) { - -moz-margin-start: 2px; - -moz-margin-end: 1px; -} - /* Generic variables traits */ -.variables-view-variable { - -moz-margin-start: 1px; - -moz-margin-end: 1px; -} - .variables-view-variable:not(:last-child) { border-bottom: 1px solid rgba(128, 128, 128, .15); } .variables-view-variable > .title > .name { font-weight: 600; } @@ -470,18 +460,22 @@ .variable-or-property:focus > .title > label { color: inherit !important; } .variable-or-property > .title > .value { -moz-box-flex: 1; } +.variable-or-property > .title > .arrow { + -moz-margin-start: 3px; +} + .variable-or-property:not([untitled]) > .variables-view-element-details { - -moz-margin-start: 10px; + -moz-margin-start: 7px; } /* Traits applied when variables or properties are changed or overridden */ .variable-or-property:not([overridden]) { transition: background 1s ease-in-out; }
--- a/browser/themes/windows/devtools/widgets.css +++ b/browser/themes/windows/devtools/widgets.css @@ -443,28 +443,18 @@ color: GrayText; padding: 2px; } .variables-view-scope > .title { color: #fff; } -.variables-view-scope > .variables-view-element-details:not(:empty) { - -moz-margin-start: 2px; - -moz-margin-end: 1px; -} - /* Generic variables traits */ -.variables-view-variable { - -moz-margin-start: 1px; - -moz-margin-end: 1px; -} - .variables-view-variable:not(:last-child) { border-bottom: 1px solid rgba(128, 128, 128, .15); } .variables-view-variable > .title > .name { font-weight: 600; } @@ -473,18 +463,22 @@ .variable-or-property:focus > .title > label { color: inherit !important; } .variable-or-property > .title > .value { -moz-box-flex: 1; } +.variable-or-property > .title > .arrow { + -moz-margin-start: 3px; +} + .variable-or-property:not([untitled]) > .variables-view-element-details { - -moz-margin-start: 10px; + -moz-margin-start: 7px; } /* Traits applied when variables or properties are changed or overridden */ .variable-or-property:not([overridden]) { transition: background 1s ease-in-out; }