Bug 768096 - Web Console remote debugging protocol support - Part 4: cleanups; r=robcee

This commit is contained in:
Mihai Sucan 2012-09-26 18:04:34 +01:00
parent bc8c482314
commit a50bab4256
12 changed files with 120 additions and 2962 deletions

View File

@ -7,7 +7,6 @@ browser.jar:
content/browser/devtools/markup-view.xhtml (markupview/markup-view.xhtml)
content/browser/devtools/markup-view.css (markupview/markup-view.css)
content/browser/NetworkPanel.xhtml (webconsole/NetworkPanel.xhtml)
content/browser/devtools/HUDService-content.js (webconsole/HUDService-content.js)
content/browser/devtools/webconsole.js (webconsole/webconsole.js)
* content/browser/devtools/webconsole.xul (webconsole/webconsole.xul)
* content/browser/scratchpad.xul (scratchpad/scratchpad.xul)

View File

@ -8,9 +8,6 @@ const EXPORTED_SYMBOLS = [ "DeveloperToolbar" ];
const NS_XHTML = "http://www.w3.org/1999/xhtml";
const WEBCONSOLE_CONTENT_SCRIPT_URL =
"chrome://browser/content/devtools/HUDService-content.js";
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
Components.utils.import("resource://gre/modules/Services.jsm");
Components.utils.import("resource:///modules/devtools/Commands.jsm");
@ -26,6 +23,9 @@ XPCOMUtils.defineLazyModuleGetter(this, "gcli",
XPCOMUtils.defineLazyModuleGetter(this, "CmdCommands",
"resource:///modules/devtools/CmdCmd.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PageErrorListener",
"resource://gre/modules/devtools/WebConsoleUtils.jsm");
/**
* Due to a number of panel bugs we need a way to check if we are running on
* Linux. See the comments for TooltipPanel and OutputPanel for further details.
@ -56,7 +56,8 @@ function DeveloperToolbar(aChromeWindow, aToolbarElement)
this._lastState = NOTIFICATIONS.HIDE;
this._pendingShowCallback = undefined;
this._pendingHide = false;
this._errorsCount = {};
this._errorsCount = Object.create(null);
this._errorListeners = Object.create(null);
this._webConsoleButton = this._doc
.getElementById("developer-toolbar-webconsole");
@ -88,9 +89,6 @@ const NOTIFICATIONS = {
*/
DeveloperToolbar.prototype.NOTIFICATIONS = NOTIFICATIONS;
DeveloperToolbar.prototype._contentMessageListeners =
["WebConsole:CachedMessages", "WebConsole:PageError"];
/**
* Is the toolbar open?
*/
@ -285,21 +283,18 @@ DeveloperToolbar.prototype._initErrorsCount = function DT__initErrorsCount(aTab)
return;
}
let messageManager = aTab.linkedBrowser.messageManager;
messageManager.loadFrameScript(WEBCONSOLE_CONTENT_SCRIPT_URL, true);
let window = aTab.linkedBrowser.contentWindow;
let listener = new PageErrorListener(window, {
onPageError: this._onPageError.bind(this, tabId),
});
listener.init();
this._errorListeners[tabId] = listener;
this._errorsCount[tabId] = 0;
this._contentMessageListeners.forEach(function(aName) {
messageManager.addMessageListener(aName, this);
}, this);
let messages = listener.getCachedMessages();
messages.forEach(this._onPageError.bind(this, tabId));
let message = {
features: ["PageError"],
cachedMessages: ["PageError"],
};
this.sendMessageToTab(aTab, "WebConsole:Init", message);
this._updateErrorsCount();
};
@ -319,14 +314,10 @@ DeveloperToolbar.prototype._stopErrorsCount = function DT__stopErrorsCount(aTab)
return;
}
this.sendMessageToTab(aTab, "WebConsole:Destroy", {});
let messageManager = aTab.linkedBrowser.messageManager;
this._contentMessageListeners.forEach(function(aName) {
messageManager.removeMessageListener(aName, this);
}, this);
this._errorListeners[tabId].destroy();
delete this._errorListeners[tabId];
delete this._errorsCount[tabId];
this._updateErrorsCount();
};
@ -434,61 +425,13 @@ DeveloperToolbar.prototype.handleEvent = function DT_handleEvent(aEvent)
};
/**
* The handler of messages received from the nsIMessageManager.
*
* @param object aMessage the message received from the content process.
*/
DeveloperToolbar.prototype.receiveMessage = function DT_receiveMessage(aMessage)
{
if (!aMessage.json || !(aMessage.json.hudId in this._errorsCount)) {
return;
}
let tabId = aMessage.json.hudId;
let errors = this._errorsCount[tabId];
switch (aMessage.name) {
case "WebConsole:PageError":
this._onPageError(tabId, aMessage.json.pageError);
break;
case "WebConsole:CachedMessages":
aMessage.json.messages.forEach(this._onPageError.bind(this, tabId));
break;
}
if (errors != this._errorsCount[tabId]) {
this._updateErrorsCount(tabId);
}
};
/**
* Send a message to the content process using the nsIMessageManager of the
* given tab.
*
* @param nsIDOMNode aTab the tab you want to send a message to.
* @param string aName the name of the message you want to send.
* @param object aMessage the message to send.
*/
DeveloperToolbar.prototype.sendMessageToTab =
function DT_sendMessageToTab(aTab, aName, aMessage)
{
let tabId = aTab.linkedPanel;
aMessage.hudId = tabId;
if (!("id" in aMessage)) {
aMessage.id = "DevToolbar-" + this.sequenceId;
}
aTab.linkedBrowser.messageManager.sendAsyncMessage(aName, aMessage);
};
/**
* Process a "WebConsole:PageError" message received from the given tab. This
* method counts the JavaScript exceptions received.
* Count a page error received for the currently selected tab. This
* method counts the JavaScript exceptions received and CSS errors/warnings.
*
* @private
* @param string aTabId the ID of the tab from where the page error comes.
* @param object aPageError the page error object received from the content
* process.
* @param object aPageError the page error object received from the
* PageErrorListener.
*/
DeveloperToolbar.prototype._onPageError =
function DT__onPageError(aTabId, aPageError)
@ -501,6 +444,7 @@ function DT__onPageError(aTabId, aPageError)
}
this._errorsCount[aTabId]++;
this._updateErrorsCount(aTabId);
};
/**

View File

@ -47,8 +47,11 @@ function test() {
function addErrors() {
expectUncaughtException();
let button = content.document.querySelector("button");
EventUtils.synthesizeMouse(button, 2, 2, {}, content);
waitForFocus(function() {
let button = content.document.querySelector("button");
EventUtils.synthesizeMouse(button, 2, 2, {}, content);
}, content);
waitForValue({
name: "button shows one more error after click in page",

File diff suppressed because it is too large Load Diff

View File

@ -57,9 +57,6 @@ const MINIMUM_PAGE_HEIGHT = 50;
// The default console height, as a ratio from the content window inner height.
const DEFAULT_CONSOLE_HEIGHT = 0.33;
// This script is inserted into the content process.
const CONTENT_SCRIPT_URL = "chrome://browser/content/devtools/HUDService-content.js";
// points to the file to load in the Web Console iframe.
const UI_IFRAME_URL = "chrome://browser/content/devtools/webconsole.xul";
@ -500,9 +497,11 @@ HUD_SERVICE.prototype =
function WebConsole(aTab)
{
this.tab = aTab;
this.chromeDocument = this.tab.ownerDocument;
this.chromeWindow = this.chromeDocument.defaultView;
this.hudId = "hud_" + this.tab.linkedPanel;
this._onIframeLoad = this._onIframeLoad.bind(this);
this._asyncRequests = {};
this._init();
this._initUI();
}
WebConsole.prototype = {
@ -512,6 +511,9 @@ WebConsole.prototype = {
*/
tab: null,
chromeWindow: null,
chromeDocument: null,
/**
* Getter for HUDService.lastFinishedRequestCallback.
*
@ -520,15 +522,6 @@ WebConsole.prototype = {
*/
get lastFinishedRequestCallback() HUDService.lastFinishedRequestCallback,
/**
* Track callback functions registered for specific async requests sent to
* the content process.
*
* @private
* @type object
*/
_asyncRequests: null,
/**
* The xul:panel that holds the Web Console when it is positioned as a window.
* @type nsIDOMElement
@ -555,22 +548,6 @@ WebConsole.prototype = {
get gViewSourceUtils() this.chromeWindow.gViewSourceUtils,
/**
* Initialize the Web Console instance.
* @private
*/
_init: function WC__init()
{
this.chromeDocument = this.tab.ownerDocument;
this.chromeWindow = this.chromeDocument.defaultView;
this.messageManager = this.tab.linkedBrowser.messageManager;
this.hudId = "hud_" + this.tab.linkedPanel;
this.notificationBox = this.chromeDocument
.getElementById(this.tab.linkedPanel);
this._initUI();
},
/**
* Initialize the Web Console UI. This method sets up the iframe.
* @private
@ -783,7 +760,7 @@ WebConsole.prototype = {
// get the node position index
let nodeIdx = this.positions[aPosition];
let nBox = this.notificationBox;
let nBox = this.chromeDocument.getElementById(this.tab.linkedPanel);
let node = nBox.childNodes[nodeIdx];
// check to see if console is already positioned in aPosition
@ -887,92 +864,6 @@ WebConsole.prototype = {
this.chromeWindow.DeveloperToolbar.resetErrorsCount(this.tab);
},
/**
* Setup the message manager used to communicate with the Web Console content
* script. This method loads the content script, adds the message listeners
* and initializes the connection to the content script.
*
* @private
*/
_setupMessageManager: function WC__setupMessageManager()
{
this.messageManager.loadFrameScript(CONTENT_SCRIPT_URL, true);
this._messageListeners.forEach(function(aName) {
this.messageManager.addMessageListener(aName, this.ui);
}, this);
let message = {
features: ["NetworkMonitor", "LocationChange"],
NetworkMonitor: { monitorFileActivity: true },
preferences: {
"NetworkMonitor.saveRequestAndResponseBodies":
this.ui.saveRequestAndResponseBodies,
},
};
this.sendMessageToContent("WebConsole:Init", message);
},
/**
* Handler for messages that have an associated callback function. The
* this.sendMessageToContent() allows one to provide a function to be invoked
* when the content script replies to the message previously sent. This is the
* method that invokes the callback.
*
* @see this.sendMessageToContent
* @private
* @param object aResponse
* Message object received from the content script.
*/
_receiveMessageWithCallback:
function WC__receiveMessageWithCallback(aResponse)
{
if (aResponse.id in this._asyncRequests) {
let request = this._asyncRequests[aResponse.id];
request.callback(aResponse, request.message);
delete this._asyncRequests[aResponse.id];
}
else {
Cu.reportError("receiveMessageWithCallback response for stale request " +
"ID " + aResponse.id);
}
},
/**
* Send a message to the content script.
*
* @param string aName
* The name of the message you want to send.
*
* @param object aMessage
* The message object you want to send. This object needs to have no
* cyclic references and it needs to be JSON-stringifiable.
*
* @param function [aCallback]
* Optional function you want to have called when the content script
* replies to your message. Your callback receives two arguments:
* (1) the response object from the content script and (2) the message
* you sent to the content script (which is aMessage here).
*/
sendMessageToContent:
function WC_sendMessageToContent(aName, aMessage, aCallback)
{
aMessage.hudId = this.hudId;
if (!("id" in aMessage)) {
aMessage.id = "HUDChrome-" + HUDService.sequenceId();
}
if (aCallback) {
this._asyncRequests[aMessage.id] = {
name: aName,
message: aMessage,
callback: aCallback,
};
}
this.messageManager.sendAsyncMessage(aName, aMessage);
},
/**
* Handler for page location changes. If the Web Console is
* opened in a panel the panel title is updated.

View File

@ -8,7 +8,7 @@ function test()
{
waitForExplicitFinish();
addTab("data:text/html,test for bug 676722 - inspectable objects for window.console");
addTab("data:text/html;charset=utf8,test for bug 676722 - inspectable objects for window.console");
gBrowser.selectedBrowser.addEventListener("load", function onLoad() {
gBrowser.selectedBrowser.removeEventListener("load", onLoad, true);

View File

@ -37,14 +37,14 @@ function test() {
let uri = Services.io.newFileURI(dir);
addTab(uri.spec);
addTab("data:text/html;charset=utf8,<p>test file URI");
browser.addEventListener("load", function tabLoad() {
browser.removeEventListener("load", tabLoad, true);
openConsole(null, function(aHud) {
hud = aHud;
hud.jsterm.clearOutput();
browser.addEventListener("load", tabReload, true);
content.location.reload();
content.location = uri.spec;
});
}, true);
}

View File

@ -38,7 +38,8 @@ function consoleOpened(aHud) {
// __defineGetter__ __defineSetter__ __lookupGetter__ __lookupSetter__
// constructor hasOwnProperty isPrototypeOf propertyIsEnumerable
// toLocaleString toSource toString unwatch valueOf watch.
let props = WCU.namesAndValuesOf(content.wrappedJSObject.document.body);
let props = WCU.inspectObject(content.wrappedJSObject.document.body,
function() { });
is(popup.itemCount, 14 + props.length, "popup.itemCount is correct");
popup._panel.addEventListener("popuphidden", autocompletePopupHidden, false);

View File

@ -13,7 +13,7 @@
const TEST_HTTPS_URI = "https://example.com/browser/browser/devtools/webconsole/test/test-bug-737873-mixedcontent.html";
function test() {
addTab("data:text/html,Web Console basic network logging test");
addTab("data:text/html;charset=utf8,Web Console mixed content test");
browser.addEventListener("load", onLoad, true);
}
@ -68,8 +68,9 @@ function testClickOpenNewTab(warningNode) {
let oldOpenUILinkIn = window.openUILinkIn;
window.openUILinkIn = function(aLink) {
if (aLink == "https://developer.mozilla.org/en/Security/MixedContent");
linkOpened = true;
if (aLink == "https://developer.mozilla.org/en/Security/MixedContent") {
linkOpened = true;
}
}
EventUtils.synthesizeMouse(warningNode, 2, 2, {},

View File

@ -1,5 +1,6 @@
<!DOCTYPE HTML>
<html dir="ltr" xml:lang="en-US" lang="en-US"><head>
<meta charset="utf8">
<title>Mixed Content test - http on https</title>
<script src="testscript.js"></script>
<!--

View File

@ -187,6 +187,10 @@ function WebConsoleFrame(aWebConsoleOwner, aPosition)
this._toggleFilter = this._toggleFilter.bind(this);
this._onPositionConsoleCommand = this._onPositionConsoleCommand.bind(this);
this._flushMessageQueue = this._flushMessageQueue.bind(this);
this._outputTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
this._outputTimerInitialized = false;
this._initDefaultFilterPrefs();
this._commandController = new CommandController(this);
@ -194,7 +198,6 @@ function WebConsoleFrame(aWebConsoleOwner, aPosition)
this.jsterm = new JSTerm(this);
this.jsterm.inputNode.focus();
this._initConnection();
}
@ -264,6 +267,15 @@ WebConsoleFrame.prototype = {
*/
_flushCallback: null,
/**
* Timer used for flushing the messages output queue.
*
* @private
* @type nsITimer
*/
_outputTimer: null,
_outputTimerInitialized: null,
/**
* Store for tracking repeated CSS nodes.
* @private
@ -1726,10 +1738,8 @@ WebConsoleFrame.prototype = {
this._outputQueue.push([aCategory, aMethodOrNode, aArguments]);
if (!this._outputTimeout) {
this._outputTimeout =
this.window.setTimeout(this._flushMessageQueue.bind(this),
OUTPUT_INTERVAL);
if (!this._outputTimerInitialized) {
this._initOutputTimer();
}
},
@ -1742,19 +1752,21 @@ WebConsoleFrame.prototype = {
*/
_flushMessageQueue: function WCF__flushMessageQueue()
{
if (!this._outputTimer) {
return;
}
let timeSinceFlush = Date.now() - this._lastOutputFlush;
if (this._outputQueue.length > MESSAGES_IN_INTERVAL &&
timeSinceFlush < THROTTLE_UPDATES) {
this._outputTimeout =
this.window.setTimeout(this._flushMessageQueue.bind(this),
OUTPUT_INTERVAL);
this._initOutputTimer();
return;
}
// Determine how many messages we can display now.
let toDisplay = Math.min(this._outputQueue.length, MESSAGES_IN_INTERVAL);
if (toDisplay < 1) {
this._outputTimeout = null;
this._outputTimerInitialized = false;
return;
}
@ -1767,7 +1779,7 @@ WebConsoleFrame.prototype = {
let batch = this._outputQueue.splice(0, toDisplay);
if (!batch.length) {
this._outputTimeout = null;
this._outputTimerInitialized = false;
return;
}
@ -1825,18 +1837,32 @@ WebConsoleFrame.prototype = {
// If the queue is not empty, schedule another flush.
if (this._outputQueue.length > 0) {
this._outputTimeout =
this.window.setTimeout(this._flushMessageQueue.bind(this),
OUTPUT_INTERVAL);
this._initOutputTimer();
}
else {
this._outputTimeout = null;
this._outputTimerInitialized = false;
this._flushCallback && this._flushCallback();
}
this._lastOutputFlush = Date.now();
},
/**
* Initialize the output timer.
* @private
*/
_initOutputTimer: function WCF__initOutputTimer()
{
if (!this._outputTimer) {
return;
}
this._outputTimerInitialized = true;
this._outputTimer.initWithCallback(this._flushMessageQueue,
OUTPUT_INTERVAL,
Ci.nsITimer.TYPE_ONE_SHOT);
},
/**
* Output a message from the queue.
*
@ -2545,7 +2571,9 @@ WebConsoleFrame.prototype = {
*/
_releaseObject: function WCF__releaseObject(aActor)
{
this.proxy.releaseActor(aActor);
if (this.proxy) {
this.proxy.releaseActor(aActor);
}
},
/**
@ -2572,13 +2600,28 @@ WebConsoleFrame.prototype = {
*/
destroy: function WCF_destroy(aOnDestroy)
{
this._cssNodes = {};
this._outputQueue = [];
this._pruneCategoriesQueue = {};
this._networkRequests = {};
if (this._outputTimerInitialized) {
this._outputTimerInitialized = false;
this._outputTimer.cancel();
}
this._outputTimer = null;
if (this.proxy) {
this.proxy.disconnect(aOnDestroy);
this.proxy = null;
}
if (this.jsterm) {
this.jsterm.destroy();
this.jsterm = null;
}
this._commandController = null;
},
};
@ -2675,6 +2718,10 @@ JSTerm.prototype = {
_executeResultCallback:
function JST__executeResultCallback(aAfterNode, aCallback, aResponse)
{
if (!this.hud) {
return;
}
let errorMessage = aResponse.errorMessage;
let result = aResponse.result;
let inspectable = result && typeof result == "object" && result.inspectable;
@ -3594,10 +3641,13 @@ JSTerm.prototype = {
this.clearOutput();
this.autocompletePopup.destroy();
this.autocompletePopup = null;
this.inputNode.removeEventListener("keypress", this._keyPress, false);
this.inputNode.removeEventListener("input", this._inputEventHandler, false);
this.inputNode.removeEventListener("keyup", this._inputEventHandler, false);
this.hud = null;
},
};
@ -3837,6 +3887,14 @@ WebConsoleConnectionProxy.prototype = {
*/
client: null,
/**
* The WebConsoleClient object.
*
* @see WebConsoleClient
* @type object
*/
webConsoleClient: null,
/**
* Tells if the connection is established.
* @type boolean
@ -4104,6 +4162,7 @@ WebConsoleConnectionProxy.prototype = {
this.client = null;
this.webConsoleClient = null;
this.connected = false;
this.owner = null;
},
};

View File

@ -44,21 +44,7 @@ const REGEX_MATCH_FUNCTION_NAME = /^\(?function\s+([^(\s]+)\s*\(/;
// Match the function arguments from the result of toString() or toSource().
const REGEX_MATCH_FUNCTION_ARGS = /^\(?function\s*[^\s(]*\s*\((.+?)\)/;
const TYPES = { OBJECT: 0,
FUNCTION: 1,
ARRAY: 2,
OTHER: 3,
ITERATOR: 4,
GETTER: 5,
GENERATOR: 6,
STRING: 7
};
var gObjectId = 0;
var WebConsoleUtils = {
TYPES: TYPES,
/**
* Convenience function to unwrap a wrapped object.
*
@ -354,103 +340,6 @@ var WebConsoleUtils = {
return type.toLowerCase();
},
/**
* Figures out the type of aObject and the string to display as the object
* value.
*
* @see TYPES
* @param object aObject
* The object to operate on.
* @return object
* An object of the form:
* {
* type: TYPES.OBJECT || TYPES.FUNCTION || ...
* display: string for displaying the object
* }
*/
presentableValueFor: function WCU_presentableValueFor(aObject)
{
let type = this.getResultType(aObject);
let presentable;
switch (type) {
case "undefined":
case "null":
return {
type: TYPES.OTHER,
display: type
};
case "array":
return {
type: TYPES.ARRAY,
display: "Array"
};
case "string":
return {
type: TYPES.STRING,
display: "\"" + aObject + "\""
};
case "date":
case "regexp":
case "number":
case "boolean":
return {
type: TYPES.OTHER,
display: aObject.toString()
};
case "iterator":
return {
type: TYPES.ITERATOR,
display: "Iterator"
};
case "function":
presentable = aObject.toString();
return {
type: TYPES.FUNCTION,
display: presentable.substring(0, presentable.indexOf(')') + 1)
};
default:
presentable = String(aObject);
let m = /^\[object (\S+)\]/.exec(presentable);
try {
if (typeof aObject == "object" && typeof aObject.next == "function" &&
m && m[1] == "Generator") {
return {
type: TYPES.GENERATOR,
display: m[1]
};
}
}
catch (ex) {
// window.history.next throws in the typeof check above.
return {
type: TYPES.OBJECT,
display: m ? m[1] : "Object"
};
}
if (typeof aObject == "object" &&
typeof aObject.__iterator__ == "function") {
return {
type: TYPES.ITERATOR,
display: "Iterator"
};
}
return {
type: TYPES.OBJECT,
display: m ? m[1] : "Object"
};
}
},
/**
* Tells if the given function is native or not.
*
@ -519,95 +408,6 @@ var WebConsoleUtils = {
return desc;
},
/**
* Get an array that describes the properties of the given object.
*
* @param object aObject
* The object to get the properties from.
* @param object aObjectCache
* Optional object cache where to store references to properties of
* aObject that are inspectable. See this.isObjectInspectable().
* @return array
* An array that describes each property from the given object. Each
* array element is an object (a property descriptor). Each property
* descriptor has the following properties:
* - name - property name
* - value - a presentable property value representation (see
* this.presentableValueFor())
* - type - value type (see this.presentableValueFor())
* - inspectable - tells if the property value holds further
* properties (see this.isObjectInspectable()).
* - objectId - optional, available only if aObjectCache is given and
* if |inspectable| is true. You can do
* aObjectCache[propertyDescriptor.objectId] to get the actual object
* referenced by the property of aObject.
*/
namesAndValuesOf: function WCU_namesAndValuesOf(aObject, aObjectCache)
{
let pairs = [];
let value, presentable;
let isDOMDocument = aObject instanceof Ci.nsIDOMDocument;
let deprecated = ["width", "height", "inputEncoding"];
for (let propName in aObject) {
// See bug 632275: skip deprecated properties.
if (isDOMDocument && deprecated.indexOf(propName) > -1) {
continue;
}
// Also skip non-native getters.
if (this.isNonNativeGetter(aObject, propName)) {
value = "";
presentable = {type: TYPES.GETTER, display: "Getter"};
}
else {
try {
value = aObject[propName];
presentable = this.presentableValueFor(value);
}
catch (ex) {
continue;
}
}
let pair = {};
pair.name = propName;
pair.value = presentable.display;
pair.inspectable = false;
pair.type = presentable.type;
switch (presentable.type) {
case TYPES.GETTER:
case TYPES.ITERATOR:
case TYPES.GENERATOR:
case TYPES.STRING:
break;
default:
try {
for (let p in value) {
pair.inspectable = true;
break;
}
}
catch (ex) { }
break;
}
// Store the inspectable object.
if (pair.inspectable && aObjectCache) {
pair.objectId = ++gObjectId;
aObjectCache[pair.objectId] = value;
}
pairs.push(pair);
}
pairs.sort(this.propertiesSort);
return pairs;
},
/**
* Sort function for object properties.
*
@ -1368,7 +1168,7 @@ function getMatchedProps(aObj, aOptions = {matchProp: ""})
aObj = aObj.constructor.prototype;
}
let c = MAX_COMPLETIONS;
let names = {}; // Using an Object to avoid duplicates.
let names = Object.create(null); // Using an Object to avoid duplicates.
// We need to go up the prototype chain.
let ownNames = null;
@ -1378,7 +1178,7 @@ function getMatchedProps(aObj, aOptions = {matchProp: ""})
// Filtering happens here.
// If we already have it in, no need to append it.
if (ownNames[i].indexOf(aOptions.matchProp) != 0 ||
names[ownNames[i]] == true) {
ownNames[i] in names) {
continue;
}
c--;