From 0edda3da59eb3cce34be9839c13f4a54b39d5524 Mon Sep 17 00:00:00 2001 From: Panos Astithas Date: Fri, 27 May 2011 21:58:05 +0300 Subject: [PATCH] Bug 651501 - document.body fails to autocomplete in Web Console. r=dtownsend --- .../console/hudservice/HUDService.jsm | 29 +++++-- .../console/hudservice/PropertyPanel.jsm | 76 ++++++++++++++++--- .../hudservice/tests/browser/Makefile.in | 1 + ...e_bug_651501_document_body_autocomplete.js | 72 ++++++++++++++++++ 4 files changed, 159 insertions(+), 19 deletions(-) create mode 100644 toolkit/components/console/hudservice/tests/browser/browser_webconsole_bug_651501_document_body_autocomplete.js diff --git a/toolkit/components/console/hudservice/HUDService.jsm b/toolkit/components/console/hudservice/HUDService.jsm index 55841a0296b7..c6076845e982 100644 --- a/toolkit/components/console/hudservice/HUDService.jsm +++ b/toolkit/components/console/hudservice/HUDService.jsm @@ -51,6 +51,7 @@ const CONSOLEAPI_CLASS_ID = "{b49c18f8-3379-4fc0-8c90-d7772c1a9ff3}"; Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource:///modules/NetworkHelper.jsm"); +Cu.import("resource:///modules/PropertyPanel.jsm"); var EXPORTED_SYMBOLS = ["HUDService", "ConsoleUtils"]; @@ -4157,6 +4158,8 @@ function findCompletionBeginning(aStr) function JSPropertyProvider(aScope, aInputValue) { let obj = unwrap(aScope); + // Store the scope object, since obj will be modified later on. + let win = obj; // Analyse the aInputValue and find the beginning of the last part that // should be completed. @@ -4195,10 +4198,15 @@ function JSPropertyProvider(aScope, aInputValue) // Check if prop is a getter function on obj. Functions can change other // stuff so we can't execute them to get the next object. Stop here. - if (obj.__lookupGetter__(prop)) { + if (isNonNativeGetter(win, obj, prop)) { + return null; + } + try { + obj = obj[prop]; + } + catch (ex) { return null; } - obj = obj[prop]; } } else { @@ -4241,10 +4249,16 @@ function isIteratorOrGenerator(aObject) return true; } - let str = aObject.toString(); - if (typeof aObject.next == "function" && - str.indexOf("[object Generator") == 0) { - return true; + try { + let str = aObject.toString(); + if (typeof aObject.next == "function" && + str.indexOf("[object Generator") == 0) { + return true; + } + } + catch (ex) { + // window.history.next throws in the typeof check above. + return false; } } @@ -4547,8 +4561,7 @@ JSTerm.prototype = { }, /** - * Evaluates a string in the sandbox. The string is currently wrapped by a - * with(window) { aString } construct, see bug 574033. + * Evaluates a string in the sandbox. * * @param string aString * String to evaluate in the sandbox. diff --git a/toolkit/components/console/hudservice/PropertyPanel.jsm b/toolkit/components/console/hudservice/PropertyPanel.jsm index b23ba617c8ee..282a117b14df 100644 --- a/toolkit/components/console/hudservice/PropertyPanel.jsm +++ b/toolkit/components/console/hudservice/PropertyPanel.jsm @@ -1,4 +1,5 @@ /* -*- Mode: js2; js2-basic-offset: 2; indent-tabs-mode: nil; -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * @@ -44,7 +45,8 @@ const Cu = Components.utils; Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/Services.jsm"); -var EXPORTED_SYMBOLS = ["PropertyPanel", "PropertyTreeView", "namesAndValuesOf"]; +var EXPORTED_SYMBOLS = ["PropertyPanel", "PropertyTreeView", + "namesAndValuesOf", "isNonNativeGetter"]; /////////////////////////////////////////////////////////////////////////// //// Helper for PropertyTreeView @@ -112,11 +114,20 @@ function presentableValueFor(aObject) presentable = aObject.toString(); let m = /^\[object (\S+)\]/.exec(presentable); - if (typeof aObject == "object" && typeof aObject.next == "function" && - m && m[1] == "Generator") { + try { + if (typeof aObject == "object" && typeof aObject.next == "function" && + m && m[1] == "Generator") { + return { + type: TYPE_OTHER, + display: m[1] + }; + } + } + catch (ex) { + // window.history.next throws in the typeof check above. return { - type: TYPE_OTHER, - display: m[1] + type: TYPE_OBJECT, + display: m ? m[1] : "Object" }; } @@ -148,6 +159,49 @@ function isNativeFunction(aFunction) return typeof aFunction == "function" && !("prototype" in aFunction); } +/** + * Tells if the given property of the provided object is a non-native getter or + * not. + * + * @param object aScope + * Scope to use for the check. + * + * @param object aObject + * The object that contains the property. + * + * @param string aProp + * The property you want to check if it is a getter or not. + * + * @return boolean + * True if the given property is a getter, false otherwise. + */ +function isNonNativeGetter(aScope, aObject, aProp) { + if (typeof aObject != "object") { + return false; + } + let desc; + while (aObject) { + try { + if (desc = aScope.Object.getOwnPropertyDescriptor(aObject, aProp)) { + break; + } + } + catch (ex) { + // Native getters throw here. See bug 520882. + if (ex.name == "NS_ERROR_XPC_BAD_CONVERT_JS" || + ex.name == "NS_ERROR_XPC_BAD_OP_ON_WN_PROTO") { + return false; + } + throw ex; + } + aObject = Object.getPrototypeOf(aObject); + } + if (desc && desc.get && !isNativeFunction(desc.get)) { + return true; + } + return false; +} + /** * Get an array of property name value pairs for the tree. * @@ -159,7 +213,7 @@ function isNativeFunction(aFunction) function namesAndValuesOf(aObject) { let pairs = []; - let value, presentable, getter; + let value, presentable; let isDOMDocument = aObject instanceof Ci.nsIDOMDocument; @@ -169,11 +223,11 @@ function namesAndValuesOf(aObject) continue; } - // Also skip non-native getters. - // TODO: implement a safer way to skip non-native getters. See bug 647235. - getter = aObject.__lookupGetter__ ? - aObject.__lookupGetter__(propName) : null; - if (getter && !isNativeFunction(getter)) { + // Also skip non-native getters. Pass the content window so that + // getOwnPropertyDescriptor can work later on. + let chromeWindow = Services.wm.getMostRecentWindow("navigator:browser"); + let contentWindow = chromeWindow.gBrowser.selectedBrowser.contentWindow; + if (isNonNativeGetter(contentWindow.wrappedJSObject, aObject, propName)) { value = ""; // Value is never displayed. presentable = {type: TYPE_OTHER, display: "Getter"}; } diff --git a/toolkit/components/console/hudservice/tests/browser/Makefile.in b/toolkit/components/console/hudservice/tests/browser/Makefile.in index 3e03cd95cb50..3e4adba9741c 100644 --- a/toolkit/components/console/hudservice/tests/browser/Makefile.in +++ b/toolkit/components/console/hudservice/tests/browser/Makefile.in @@ -141,6 +141,7 @@ _BROWSER_TEST_FILES = \ browser_webconsole_bug_642615_autocomplete.js \ browser_webconsole_bug_585991_autocomplete_popup.js \ browser_webconsole_bug_585991_autocomplete_keys.js \ + browser_webconsole_bug_651501_document_body_autocomplete.js \ head.js \ $(NULL) diff --git a/toolkit/components/console/hudservice/tests/browser/browser_webconsole_bug_651501_document_body_autocomplete.js b/toolkit/components/console/hudservice/tests/browser/browser_webconsole_bug_651501_document_body_autocomplete.js new file mode 100644 index 000000000000..6c9533e2bb96 --- /dev/null +++ b/toolkit/components/console/hudservice/tests/browser/browser_webconsole_bug_651501_document_body_autocomplete.js @@ -0,0 +1,72 @@ +/* vim:set ts=2 sw=2 sts=2 et: */ +/* + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// Tests that document.body autocompletes in the web console. + +Cu.import("resource://gre/modules/PropertyPanel.jsm"); + +function test() { + addTab("data:text/html,Web Console autocompletion bug in document.body"); + browser.addEventListener("load", onLoad, true); +} + +var gHUD; + +function onLoad(aEvent) { + browser.removeEventListener(aEvent.type, arguments.callee, true); + openConsole(); + let hudId = HUDService.getHudIdByWindow(content); + gHUD = HUDService.hudReferences[hudId]; + let jsterm = gHUD.jsterm; + let popup = jsterm.autocompletePopup; + let completeNode = jsterm.completeNode; + + ok(!popup.isOpen, "popup is not open"); + + popup._panel.addEventListener("popupshown", function() { + popup._panel.removeEventListener("popupshown", arguments.callee, false); + + ok(popup.isOpen, "popup is open"); + + let props = namesAndValuesOf(content.wrappedJSObject.document.body).length; + is(popup.itemCount, props, "popup.itemCount is correct"); + + popup._panel.addEventListener("popuphidden", autocompletePopupHidden, false); + + EventUtils.synthesizeKey("VK_ESCAPE", {}); + }, false); + + jsterm.setInputValue("document.body"); + EventUtils.synthesizeKey(".", {}); +} + +function autocompletePopupHidden() +{ + let jsterm = gHUD.jsterm; + let popup = jsterm.autocompletePopup; + let completeNode = jsterm.completeNode; + let inputNode = jsterm.inputNode; + + popup._panel.removeEventListener("popuphidden", arguments.callee, false); + + ok(!popup.isOpen, "popup is not open"); + let inputStr = "document.b"; + jsterm.setInputValue(inputStr); + EventUtils.synthesizeKey("o", {}); + let testStr = inputStr.replace(/./g, " ") + " "; + is(completeNode.value, testStr + "dy", "completeNode is empty"); + jsterm.setInputValue(""); + + // Check the property panel as well. + let propPanel = jsterm.openPropertyPanel("Test", content.document); + is (propPanel.treeView.rowCount, 153, "153 elements shown in propertyPanel"); + + let treeRows = propPanel.treeView._rows; + is (treeRows[30].display, "body: Object", "found document.body"); + propPanel.destroy(); + executeSoon(finishTest); +} +