grips.js (3261B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 "use strict"; 5 6 const constants = require("resource://devtools/client/dom/content/constants.js"); 7 8 /** 9 * Initial state definition 10 */ 11 function getInitialState() { 12 return new Map(); 13 } 14 15 /** 16 * Maintain a cache of received grip responses from the backend. 17 */ 18 function grips(state = getInitialState(), action) { 19 // This reducer supports only one action, fetching actor properties 20 // from the backend so, bail out if we are dealing with any other 21 // action. 22 if (action.type != constants.FETCH_PROPERTIES) { 23 return state; 24 } 25 26 switch (action.status) { 27 case "start": 28 return onRequestProperties(state, action); 29 case "done": 30 return onReceiveProperties(state, action); 31 } 32 33 return state; 34 } 35 36 /** 37 * Handle requestProperties action 38 */ 39 function onRequestProperties(state) { 40 return state; 41 } 42 43 /** 44 * Handle receiveProperties action 45 */ 46 function onReceiveProperties(cache, action) { 47 const response = action.response; 48 const from = response.from; 49 const className = action.grip?.getGrip 50 ? action.grip.getGrip().class 51 : action.grip.class; 52 53 // Properly deal with getters. 54 mergeProperties(response); 55 56 // Compute list of requested children. 57 const previewProps = response.preview ? response.preview.ownProperties : null; 58 const ownProps = response.ownProperties || previewProps || []; 59 60 const props = Object.keys(ownProps).map(key => { 61 // Array indexes as a special case. We convert any keys that are string 62 // representations of integers to integers. 63 if (className === "Array" && isInteger(key)) { 64 key = parseInt(key, 10); 65 } 66 return new Property(key, ownProps[key], key); 67 }); 68 69 props.sort(sortName); 70 71 // Return new state/map. 72 const newCache = new Map(cache); 73 newCache.set(from, props); 74 75 return newCache; 76 } 77 78 // Helpers 79 80 function mergeProperties(response) { 81 const { ownProperties } = response; 82 83 // 'safeGetterValues' is new and isn't necessary defined on old grips. 84 const safeGetterValues = response.safeGetterValues || {}; 85 86 // Merge the safe getter values into one object such that we can use it 87 // in variablesView. 88 for (const name of Object.keys(safeGetterValues)) { 89 if (name in ownProperties) { 90 const { getterValue, getterPrototypeLevel } = safeGetterValues[name]; 91 ownProperties[name].getterValue = getterValue; 92 ownProperties[name].getterPrototypeLevel = getterPrototypeLevel; 93 } else { 94 ownProperties[name] = safeGetterValues[name]; 95 } 96 } 97 } 98 99 function sortName(a, b) { 100 // Display non-enumerable properties at the end. 101 if (!a.value.enumerable && b.value.enumerable) { 102 return 1; 103 } 104 if (a.value.enumerable && !b.value.enumerable) { 105 return -1; 106 } 107 return a.name > b.name ? 1 : -1; 108 } 109 110 function isInteger(n) { 111 // We use parseInt(n, 10) == n to disregard scientific notation e.g. "3e24" 112 return isFinite(n) && parseInt(n, 10) == n; 113 } 114 115 function Property(name, value, key) { 116 this.name = name; 117 this.value = value; 118 this.key = key; 119 } 120 121 // Exports from this module 122 exports.grips = grips; 123 exports.Property = Property;