зеркало из https://github.com/mozilla/gecko-dev.git
Merge fx-team to m-c
This commit is contained in:
Коммит
dd72dd2a82
2
CLOBBER
2
CLOBBER
|
@ -22,4 +22,4 @@
|
|||
# changes to stick? As of bug 928195, this shouldn't be necessary! Please
|
||||
# don't change CLOBBER for WebIDL changes any more.
|
||||
|
||||
Bug 878935 landed without a UUID change (and was since backed out)
|
||||
Bug 910189 requires a clobber due to Proguard inner class errors from bug 946083.
|
||||
|
|
|
@ -1695,7 +1695,10 @@ let CustomizableUIInternal = {
|
|||
|
||||
// If the widget doesn't have an existing placement, and it hasn't been
|
||||
// seen before, then add it to its default area so it can be used.
|
||||
if (autoAdd && !widget.currentArea && !gSeenWidgets.has(widget.id)) {
|
||||
// If the widget is not removable, we *have* to add it to its default
|
||||
// area here.
|
||||
let canBeAutoAdded = autoAdd && !gSeenWidgets.has(widget.id);
|
||||
if (!widget.currentArea && (!widget.removable || canBeAutoAdded)) {
|
||||
this.beginBatchUpdate();
|
||||
try {
|
||||
gSeenWidgets.add(widget.id);
|
||||
|
|
|
@ -48,4 +48,5 @@ skip-if = os == "mac"
|
|||
[browser_944887_destroyWidget_should_destroy_in_palette.js]
|
||||
[browser_945739_showInPrivateBrowsing_customize_mode.js]
|
||||
[browser_947987_removable_default.js]
|
||||
[browser_948985_non_removable_defaultArea.js]
|
||||
[browser_panel_toggle.js]
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
const kWidgetId = "test-destroy-non-removable-defaultArea";
|
||||
|
||||
add_task(function() {
|
||||
let spec = {id: kWidgetId, label: "Test non-removable defaultArea re-adding.",
|
||||
removable: false, defaultArea: CustomizableUI.AREA_NAVBAR};
|
||||
CustomizableUI.createWidget(spec);
|
||||
let placement = CustomizableUI.getPlacementOfWidget(kWidgetId);
|
||||
ok(placement, "Should have placed the widget.");
|
||||
is(placement && placement.area, CustomizableUI.AREA_NAVBAR, "Widget should be in navbar");
|
||||
CustomizableUI.destroyWidget(kWidgetId);
|
||||
CustomizableUI.removeWidgetFromArea(kWidgetId);
|
||||
|
||||
CustomizableUI.createWidget(spec);
|
||||
ok(placement, "Should have placed the widget.");
|
||||
is(placement && placement.area, CustomizableUI.AREA_NAVBAR, "Widget should be in navbar");
|
||||
CustomizableUI.destroyWidget(kWidgetId);
|
||||
CustomizableUI.removeWidgetFromArea(kWidgetId);
|
||||
|
||||
const kPrefCustomizationAutoAdd = "browser.uiCustomization.autoAdd";
|
||||
Services.prefs.setBoolPref(kPrefCustomizationAutoAdd, false);
|
||||
CustomizableUI.createWidget(spec);
|
||||
ok(placement, "Should have placed the widget.");
|
||||
is(placement && placement.area, CustomizableUI.AREA_NAVBAR, "Widget should be in navbar");
|
||||
CustomizableUI.destroyWidget(kWidgetId);
|
||||
CustomizableUI.removeWidgetFromArea(kWidgetId);
|
||||
Services.prefs.clearUserPref(kPrefCustomizationAutoAdd);
|
||||
});
|
||||
|
|
@ -583,6 +583,25 @@ nsBrowserContentHandler.prototype = {
|
|||
|
||||
overridePage = overridePage.replace("%OLD_VERSION%", old_mstone);
|
||||
break;
|
||||
|
||||
// Temporary case for Australis whatsnew
|
||||
case OVERRIDE_NEW_BUILD_ID:
|
||||
let locale = "en-US";
|
||||
try {
|
||||
locale = Services.prefs.getCharPref("general.useragent.locale");
|
||||
} catch (e) {}
|
||||
|
||||
let showedAustralisWhatsNew = false;
|
||||
try {
|
||||
showedAustralisWhatsNew = Services.prefs.getBoolPref("browser.showedAustralisWhatsNew");
|
||||
} catch(e) {}
|
||||
|
||||
// Show the Australis whatsnew page for en-US if we haven't yet shown it
|
||||
if (!showedAustralisWhatsNew && locale == "en-US") {
|
||||
Services.prefs.setBoolPref("browser.showedAustralisWhatsNew", true);
|
||||
overridePage = "https://www.mozilla.org/en-US/firefox/29.0a1/whatsnew/";
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (ex) {}
|
||||
|
|
|
@ -182,9 +182,6 @@ let TabStateInternal = {
|
|||
tabData.index = history.index;
|
||||
}
|
||||
|
||||
// Copy data from the persistent cache.
|
||||
this._copyFromPersistentCache(tab, tabData);
|
||||
|
||||
// If we're still the latest async collection for the given tab and
|
||||
// the cache hasn't been filled by collect() in the meantime, let's
|
||||
// fill the cache with the data we received.
|
||||
|
@ -193,6 +190,16 @@ let TabStateInternal = {
|
|||
this._pendingCollections.delete(browser);
|
||||
}
|
||||
|
||||
// Copy data from the persistent cache. We need to create an explicit
|
||||
// copy of the |tabData| object so that the properties injected by
|
||||
// |_copyFromPersistentCache| don't end up in the non-persistent cache.
|
||||
// The persistent cache does not store "null" values, so any values that
|
||||
// have been cleared by the frame script would not be overriden by
|
||||
// |_copyFromPersistentCache|. These two caches are only an interim
|
||||
// solution and the non-persistent one will go away soon.
|
||||
tabData = Utils.copy(tabData);
|
||||
this._copyFromPersistentCache(tab, tabData);
|
||||
|
||||
throw new Task.Result(tabData);
|
||||
}.bind(this));
|
||||
|
||||
|
@ -219,7 +226,16 @@ let TabStateInternal = {
|
|||
throw new TypeError("Expecting a tab");
|
||||
}
|
||||
if (TabStateCache.has(tab)) {
|
||||
return TabStateCache.get(tab);
|
||||
// Copy data from the persistent cache. We need to create an explicit
|
||||
// copy of the |tabData| object so that the properties injected by
|
||||
// |_copyFromPersistentCache| don't end up in the non-persistent cache.
|
||||
// The persistent cache does not store "null" values, so any values that
|
||||
// have been cleared by the frame script would not be overriden by
|
||||
// |_copyFromPersistentCache|. These two caches are only an interim
|
||||
// solution and the non-persistent one will go away soon.
|
||||
let tabData = Utils.copy(TabStateCache.get(tab));
|
||||
this._copyFromPersistentCache(tab, tabData);
|
||||
return tabData;
|
||||
}
|
||||
|
||||
let tabData = this._collectSyncUncached(tab);
|
||||
|
@ -228,6 +244,16 @@ let TabStateInternal = {
|
|||
TabStateCache.set(tab, tabData);
|
||||
}
|
||||
|
||||
// Copy data from the persistent cache. We need to create an explicit
|
||||
// copy of the |tabData| object so that the properties injected by
|
||||
// |_copyFromPersistentCache| don't end up in the non-persistent cache.
|
||||
// The persistent cache does not store "null" values, so any values that
|
||||
// have been cleared by the frame script would not be overriden by
|
||||
// |_copyFromPersistentCache|. These two caches are only an interim
|
||||
// solution and the non-persistent one will go away soon.
|
||||
tabData = Utils.copy(tabData);
|
||||
this._copyFromPersistentCache(tab, tabData);
|
||||
|
||||
// Prevent all running asynchronous collections from filling the cache.
|
||||
// Every asynchronous data collection started before a collectSync() call
|
||||
// can't expect to retrieve different data than the sync call. That's why
|
||||
|
@ -262,7 +288,13 @@ let TabStateInternal = {
|
|||
* up-to-date.
|
||||
*/
|
||||
clone: function (tab) {
|
||||
return this._collectSyncUncached(tab, {includePrivateData: true});
|
||||
let options = {includePrivateData: true};
|
||||
let tabData = this._collectSyncUncached(tab, options);
|
||||
|
||||
// Copy data from the persistent cache.
|
||||
this._copyFromPersistentCache(tab, tabData, options);
|
||||
|
||||
return tabData;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -305,9 +337,6 @@ let TabStateInternal = {
|
|||
tabData.index = history.index;
|
||||
}
|
||||
|
||||
// Copy data from the persistent cache.
|
||||
this._copyFromPersistentCache(tab, tabData, options);
|
||||
|
||||
return tabData;
|
||||
},
|
||||
|
||||
|
|
|
@ -64,5 +64,16 @@ this.Utils = Object.freeze({
|
|||
map.set(otherKey, value);
|
||||
map.delete(key);
|
||||
}
|
||||
},
|
||||
|
||||
// Copies all properties of a given object to a new one and returns it.
|
||||
copy: function (from) {
|
||||
let to = {};
|
||||
|
||||
for (let key of Object.keys(from)) {
|
||||
to[key] = from[key];
|
||||
}
|
||||
|
||||
return to;
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1928,7 +1928,10 @@ Breakpoints.prototype = {
|
|||
let disabledPromise = this._disabled.get(identifier);
|
||||
if (disabledPromise) {
|
||||
disabledPromise.then(({ conditionalExpression: previousValue }) => {
|
||||
aBreakpointClient.conditionalExpression = previousValue;
|
||||
// Setting a falsy conditional expression is redundant.
|
||||
if (previousValue) {
|
||||
aBreakpointClient.conditionalExpression = previousValue;
|
||||
}
|
||||
});
|
||||
this._disabled.delete(identifier);
|
||||
}
|
||||
|
|
|
@ -100,6 +100,7 @@ support-files =
|
|||
[browser_dbg_conditional-breakpoints-01.js]
|
||||
[browser_dbg_conditional-breakpoints-02.js]
|
||||
[browser_dbg_conditional-breakpoints-03.js]
|
||||
[browser_dbg_conditional-breakpoints-04.js]
|
||||
[browser_dbg_controller-evaluate-01.js]
|
||||
[browser_dbg_controller-evaluate-02.js]
|
||||
[browser_dbg_debugger-statement.js]
|
||||
|
|
|
@ -77,13 +77,13 @@ function test() {
|
|||
.getAttribute("value"), "getName",
|
||||
"Should have the right property name for 'getName' in person.");
|
||||
is(personNode.get("getName").target.querySelector(".value")
|
||||
.getAttribute("value"), "Function",
|
||||
.getAttribute("value"), "_pfactory/<.getName()",
|
||||
"'getName' in person should have the right value.");
|
||||
is(personNode.get("getFoo").target.querySelector(".name")
|
||||
.getAttribute("value"), "getFoo",
|
||||
"Should have the right property name for 'getFoo' in person.");
|
||||
is(personNode.get("getFoo").target.querySelector(".value")
|
||||
.getAttribute("value"), "Function",
|
||||
.getAttribute("value"), "_pfactory/<.getFoo()",
|
||||
"'getFoo' in person should have the right value.");
|
||||
|
||||
// Expand the function nodes. This causes their properties to be
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Make sure that conditional breakpoints with undefined expressions
|
||||
* are stored as plain breakpoints when re-enabling them.
|
||||
*/
|
||||
|
||||
const TAB_URL = EXAMPLE_URL + "doc_conditional-breakpoints.html";
|
||||
|
||||
function test() {
|
||||
let gTab, gDebuggee, gPanel, gDebugger;
|
||||
let gSources, gBreakpoints, gLocation;
|
||||
|
||||
initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
|
||||
gTab = aTab;
|
||||
gDebuggee = aDebuggee;
|
||||
gPanel = aPanel;
|
||||
gDebugger = gPanel.panelWin;
|
||||
gSources = gDebugger.DebuggerView.Sources;
|
||||
gBreakpoints = gDebugger.DebuggerController.Breakpoints;
|
||||
|
||||
gLocation = { url: gSources.selectedValue, line: 18 };
|
||||
|
||||
waitForSourceAndCaretAndScopes(gPanel, ".html", 17)
|
||||
.then(addBreakpoint)
|
||||
.then(setDummyConditional)
|
||||
.then(() => {
|
||||
let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.BREAKPOINT_REMOVED);
|
||||
toggleBreakpoint();
|
||||
return finished;
|
||||
})
|
||||
.then(() => {
|
||||
let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.BREAKPOINT_ADDED);
|
||||
toggleBreakpoint();
|
||||
return finished;
|
||||
})
|
||||
.then(testConditionalExpressionOnClient)
|
||||
.then(() => {
|
||||
let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.CONDITIONAL_BREAKPOINT_POPUP_SHOWING);
|
||||
openConditionalPopup();
|
||||
finished.then(() => ok(false, "The popup shouldn't have opened."));
|
||||
return waitForTime(1000);
|
||||
})
|
||||
.then(() => resumeDebuggerThenCloseAndFinish(gPanel))
|
||||
.then(null, aError => {
|
||||
ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
|
||||
});
|
||||
|
||||
gDebuggee.ermahgerd();
|
||||
});
|
||||
|
||||
function addBreakpoint() {
|
||||
return gPanel.addBreakpoint(gLocation);
|
||||
}
|
||||
|
||||
function setDummyConditional(aClient) {
|
||||
// This happens when a conditional expression input popup is shown
|
||||
// but the user doesn't type anything into it.
|
||||
aClient.conditionalExpression = "";
|
||||
}
|
||||
|
||||
function toggleBreakpoint() {
|
||||
EventUtils.sendMouseEvent({ type: "click" },
|
||||
gDebugger.document.querySelector(".dbg-breakpoint-checkbox"),
|
||||
gDebugger);
|
||||
}
|
||||
|
||||
function openConditionalPopup() {
|
||||
EventUtils.sendMouseEvent({ type: "click" },
|
||||
gDebugger.document.querySelector(".dbg-breakpoint"),
|
||||
gDebugger);
|
||||
}
|
||||
|
||||
function testConditionalExpressionOnClient() {
|
||||
return gBreakpoints._getAdded(gLocation).then(aClient => {
|
||||
if ("conditionalExpression" in aClient) {
|
||||
ok(false, "A conditional expression shouldn't have been set.");
|
||||
} else {
|
||||
ok(true, "The conditional expression wasn't set, as expected.");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -30,13 +30,15 @@ function testPause() {
|
|||
|
||||
gDebugger.gThreadClient.addOneTimeListener("paused", () => {
|
||||
gToolbox.selectTool("webconsole").then(() => {
|
||||
ok(gToolboxTab.classList.contains("highlighted"),
|
||||
ok(gToolboxTab.hasAttribute("highlighted") &&
|
||||
gToolboxTab.getAttribute("highlighted") == "true",
|
||||
"The highlighted class is present");
|
||||
ok(!gToolboxTab.hasAttribute("selected") ||
|
||||
gToolboxTab.getAttribute("selected") != "true",
|
||||
"The tab is not selected");
|
||||
}).then(() => gToolbox.selectTool("jsdebugger")).then(() => {
|
||||
ok(gToolboxTab.classList.contains("highlighted"),
|
||||
ok(gToolboxTab.hasAttribute("highlighted") &&
|
||||
gToolboxTab.getAttribute("highlighted") == "true",
|
||||
"The highlighted class is present");
|
||||
ok(gToolboxTab.hasAttribute("selected") &&
|
||||
gToolboxTab.getAttribute("selected") == "true",
|
||||
|
|
|
@ -78,13 +78,15 @@ function testPause() {
|
|||
"Debugger's tab got selected.");
|
||||
}
|
||||
gToolbox.selectTool("webconsole").then(() => {
|
||||
ok(gToolboxTab.classList.contains("highlighted"),
|
||||
ok(gToolboxTab.hasAttribute("highlighted") &&
|
||||
gToolboxTab.getAttribute("highlighted") == "true",
|
||||
"The highlighted class is present");
|
||||
ok(!gToolboxTab.hasAttribute("selected") ||
|
||||
gToolboxTab.getAttribute("selected") != "true",
|
||||
"The tab is not selected");
|
||||
}).then(() => gToolbox.selectTool("jsdebugger")).then(() => {
|
||||
ok(gToolboxTab.classList.contains("highlighted"),
|
||||
ok(gToolboxTab.hasAttribute("highlighted") &&
|
||||
gToolboxTab.getAttribute("highlighted") == "true",
|
||||
"The highlighted class is present");
|
||||
ok(gToolboxTab.hasAttribute("selected") &&
|
||||
gToolboxTab.getAttribute("selected") == "true",
|
||||
|
@ -100,7 +102,8 @@ function testPause() {
|
|||
function testResume() {
|
||||
gDebugger.gThreadClient.addOneTimeListener("resumed", () => {
|
||||
gToolbox.selectTool("webconsole").then(() => {
|
||||
ok(!gToolboxTab.classList.contains("highlighted"),
|
||||
ok(!gToolboxTab.hasAttribute("highlighted") ||
|
||||
gToolboxTab.getAttribute("highlighted") != "true",
|
||||
"The highlighted class is not present now after the resume");
|
||||
ok(!gToolboxTab.hasAttribute("selected") ||
|
||||
gToolboxTab.getAttribute("selected") != "true",
|
||||
|
|
|
@ -58,7 +58,7 @@ function testPauseOnExceptionsDisabled() {
|
|||
|
||||
is(innerNodes[0].querySelector(".name").getAttribute("value"), "this",
|
||||
"Should have the right property name for 'this'.");
|
||||
is(innerNodes[0].querySelector(".value").getAttribute("value"), "HTMLButtonElement",
|
||||
is(innerNodes[0].querySelector(".value").getAttribute("value"), "<button>",
|
||||
"Should have the right property value for 'this'.");
|
||||
|
||||
let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.AFTER_FRAMES_CLEARED).then(() => {
|
||||
|
@ -124,7 +124,7 @@ function testPauseOnExceptionsEnabled() {
|
|||
|
||||
is(innerNodes[0].querySelector(".name").getAttribute("value"), "this",
|
||||
"Should have the right property name for 'this'.");
|
||||
is(innerNodes[0].querySelector(".value").getAttribute("value"), "HTMLButtonElement",
|
||||
is(innerNodes[0].querySelector(".value").getAttribute("value"), "<button>",
|
||||
"Should have the right property value for 'this'.");
|
||||
|
||||
let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.AFTER_FRAMES_CLEARED).then(() => {
|
||||
|
|
|
@ -79,7 +79,7 @@ function testPauseOnExceptionsAfterReload() {
|
|||
|
||||
is(innerNodes[0].querySelector(".name").getAttribute("value"), "this",
|
||||
"Should have the right property name for 'this'.");
|
||||
is(innerNodes[0].querySelector(".value").getAttribute("value"), "HTMLButtonElement",
|
||||
is(innerNodes[0].querySelector(".value").getAttribute("value"), "<button>",
|
||||
"Should have the right property value for 'this'.");
|
||||
|
||||
let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.AFTER_FRAMES_CLEARED).then(() => {
|
||||
|
|
|
@ -22,16 +22,16 @@ function test() {
|
|||
.then(() => initialChecks())
|
||||
.then(() => testModification("a", "1"))
|
||||
.then(() => testModification("{ a: 1 }", "Object"))
|
||||
.then(() => testModification("[a]", "Array"))
|
||||
.then(() => testModification("[a]", "Array[1]"))
|
||||
.then(() => testModification("b", "Object"))
|
||||
.then(() => testModification("b.a", "1"))
|
||||
.then(() => testModification("c.a", "1"))
|
||||
.then(() => testModification("Infinity", "Infinity"))
|
||||
.then(() => testModification("NaN", "NaN"))
|
||||
.then(() => testModification("new Function", "Function"))
|
||||
.then(() => testModification("new Function", "anonymous()"))
|
||||
.then(() => testModification("+0", "0"))
|
||||
.then(() => testModification("-0", "-0"))
|
||||
.then(() => testModification("Object.keys({})", "Array"))
|
||||
.then(() => testModification("Object.keys({})", "Array[0]"))
|
||||
.then(() => testModification("document.title", '"Debugger test page"'))
|
||||
.then(() => resumeDebuggerThenCloseAndFinish(gPanel))
|
||||
.then(null, aError => {
|
||||
|
|
|
@ -89,14 +89,16 @@ function testExpandVariables() {
|
|||
waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_PROPERTIES, 3).then(() => {
|
||||
is(thisVar.get("window").target.querySelector(".name").getAttribute("value"), "window",
|
||||
"Should have the right property name for 'window'.");
|
||||
is(thisVar.get("window").target.querySelector(".value").getAttribute("value"), "Window",
|
||||
is(thisVar.get("window").target.querySelector(".value").getAttribute("value"),
|
||||
"Window \u2192 doc_frame-parameters.html",
|
||||
"Should have the right property value for 'window'.");
|
||||
ok(thisVar.get("window").target.querySelector(".value").className.contains("token-other"),
|
||||
"Should have the right token class for 'window'.");
|
||||
|
||||
is(thisVar.get("document").target.querySelector(".name").getAttribute("value"), "document",
|
||||
"Should have the right property name for 'document'.");
|
||||
is(thisVar.get("document").target.querySelector(".value").getAttribute("value"), "HTMLDocument",
|
||||
is(thisVar.get("document").target.querySelector(".value").getAttribute("value"),
|
||||
"HTMLDocument \u2192 doc_frame-parameters.html",
|
||||
"Should have the right property value for 'document'.");
|
||||
ok(thisVar.get("document").target.querySelector(".value").className.contains("token-other"),
|
||||
"Should have the right token class for 'document'.");
|
||||
|
|
|
@ -56,7 +56,8 @@ function testScopeVariables() {
|
|||
|
||||
is(localEnums[0].querySelector(".name").getAttribute("value"), "this",
|
||||
"Should have the right property name for 'this'.");
|
||||
is(localEnums[0].querySelector(".value").getAttribute("value"), "Window",
|
||||
is(localEnums[0].querySelector(".value").getAttribute("value"),
|
||||
"Window \u2192 doc_frame-parameters.html",
|
||||
"Should have the right property value for 'this'.");
|
||||
ok(localEnums[0].querySelector(".value").className.contains("token-other"),
|
||||
"Should have the right token class for 'this'.");
|
||||
|
@ -192,7 +193,8 @@ function testArgumentsProperties() {
|
|||
|
||||
is(argsNonEnums[0].querySelector(".name").getAttribute("value"), "callee",
|
||||
"Should have the right property name for 'callee'.");
|
||||
is(argsNonEnums[0].querySelector(".value").getAttribute("value"), "Function",
|
||||
is(argsNonEnums[0].querySelector(".value").getAttribute("value"),
|
||||
"test(aArg,bArg,cArg,dArg,eArg,fArg)",
|
||||
"Should have the right property name for 'callee'.");
|
||||
ok(argsNonEnums[0].querySelector(".value").className.contains("token-other"),
|
||||
"Should have the right token class for 'callee'.");
|
||||
|
@ -518,14 +520,16 @@ function testGetterSetterObject() {
|
|||
|
||||
is(propNonEnums[0].querySelector(".name").getAttribute("value"), "get",
|
||||
"Should have the right property name for 'get'.");
|
||||
is(propNonEnums[0].querySelector(".value").getAttribute("value"), "Function",
|
||||
is(propNonEnums[0].querySelector(".value").getAttribute("value"),
|
||||
"test/myVar.prop()",
|
||||
"Should have the right property value for 'get'.");
|
||||
ok(propNonEnums[0].querySelector(".value").className.contains("token-other"),
|
||||
"Should have the right token class for 'get'.");
|
||||
|
||||
is(propNonEnums[1].querySelector(".name").getAttribute("value"), "set",
|
||||
"Should have the right property name for 'set'.");
|
||||
is(propNonEnums[1].querySelector(".value").getAttribute("value"), "Function",
|
||||
is(propNonEnums[1].querySelector(".value").getAttribute("value"),
|
||||
"test/myVar.prop(val)",
|
||||
"Should have the right property value for 'set'.");
|
||||
ok(propNonEnums[1].querySelector(".value").className.contains("token-other"),
|
||||
"Should have the right token class for 'set'.");
|
||||
|
|
|
@ -71,12 +71,14 @@ function testGlobalScope() {
|
|||
|
||||
is(globalScope.get("window").target.querySelector(".name").getAttribute("value"), "window",
|
||||
"Should have the right property name for 'window'.");
|
||||
is(globalScope.get("window").target.querySelector(".value").getAttribute("value"), "Window",
|
||||
is(globalScope.get("window").target.querySelector(".value").getAttribute("value"),
|
||||
"Window \u2192 doc_frame-parameters.html",
|
||||
"Should have the right property value for 'window'.");
|
||||
|
||||
is(globalScope.get("document").target.querySelector(".name").getAttribute("value"), "document",
|
||||
"Should have the right property name for 'document'.");
|
||||
is(globalScope.get("document").target.querySelector(".value").getAttribute("value"), "HTMLDocument",
|
||||
is(globalScope.get("document").target.querySelector(".value").getAttribute("value"),
|
||||
"HTMLDocument \u2192 doc_frame-parameters.html",
|
||||
"Should have the right property value for 'document'.");
|
||||
|
||||
is(globalScope.get("undefined").target.querySelector(".name").getAttribute("value"), "undefined",
|
||||
|
@ -123,12 +125,14 @@ function testWindowVariable() {
|
|||
|
||||
is(windowVar.get("window").target.querySelector(".name").getAttribute("value"), "window",
|
||||
"Should have the right property name for 'window'.");
|
||||
is(windowVar.get("window").target.querySelector(".value").getAttribute("value"), "Window",
|
||||
is(windowVar.get("window").target.querySelector(".value").getAttribute("value"),
|
||||
"Window \u2192 doc_frame-parameters.html",
|
||||
"Should have the right property value for 'window'.");
|
||||
|
||||
is(windowVar.get("document").target.querySelector(".name").getAttribute("value"), "document",
|
||||
"Should have the right property name for 'document'.");
|
||||
is(windowVar.get("document").target.querySelector(".value").getAttribute("value"), "HTMLDocument",
|
||||
is(windowVar.get("document").target.querySelector(".value").getAttribute("value"),
|
||||
"HTMLDocument \u2192 doc_frame-parameters.html",
|
||||
"Should have the right property value for 'document'.");
|
||||
|
||||
is(windowVar.get("undefined").target.querySelector(".name").getAttribute("value"), "undefined",
|
||||
|
|
|
@ -57,7 +57,8 @@ function testFirstWithScope() {
|
|||
|
||||
is(withEnums[0].querySelector(".name").getAttribute("value"), "this",
|
||||
"Should have the right property name for 'this'.");
|
||||
is(withEnums[0].querySelector(".value").getAttribute("value"), "Window",
|
||||
is(withEnums[0].querySelector(".value").getAttribute("value"),
|
||||
"Window \u2192 doc_with-frame.html",
|
||||
"Should have the right property value for 'this'.");
|
||||
ok(withEnums[0].querySelector(".value").className.contains("token-other"),
|
||||
"Should have the right token class for 'this'.");
|
||||
|
@ -131,7 +132,7 @@ function testSecondWithScope() {
|
|||
|
||||
is(secondWithScope.get("random").target.querySelector(".name").getAttribute("value"), "random",
|
||||
"Should have the right property name for 'random'.");
|
||||
is(secondWithScope.get("random").target.querySelector(".value").getAttribute("value"), "Function",
|
||||
is(secondWithScope.get("random").target.querySelector(".value").getAttribute("value"), "random()",
|
||||
"Should have the right property value for 'random'.");
|
||||
ok(secondWithScope.get("random").target.querySelector(".value").className.contains("token-other"),
|
||||
"Should have the right token class for 'random'.");
|
||||
|
|
|
@ -54,7 +54,7 @@ function initialChecks() {
|
|||
|
||||
is(arrayVar.target.querySelector(".name").getAttribute("value"), "largeArray",
|
||||
"Should have the right property name for 'largeArray'.");
|
||||
is(arrayVar.target.querySelector(".value").getAttribute("value"), "Int8Array",
|
||||
is(arrayVar.target.querySelector(".value").getAttribute("value"), "Int8Array[10000]",
|
||||
"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'.");
|
||||
|
|
|
@ -62,7 +62,7 @@ function performTest() {
|
|||
|
||||
is(buttonVar.target.querySelector(".name").getAttribute("value"), "button",
|
||||
"Should have the right property name for 'button'.");
|
||||
is(buttonVar.target.querySelector(".value").getAttribute("value"), "HTMLButtonElement",
|
||||
is(buttonVar.target.querySelector(".value").getAttribute("value"), "<button>",
|
||||
"Should have the right property value for 'button'.");
|
||||
ok(buttonVar.target.querySelector(".value").className.contains("token-other"),
|
||||
"Should have the right token class for 'button'.");
|
||||
|
@ -76,7 +76,8 @@ function performTest() {
|
|||
|
||||
is(documentVar.target.querySelector(".name").getAttribute("value"), "document",
|
||||
"Should have the right property name for 'document'.");
|
||||
is(documentVar.target.querySelector(".value").getAttribute("value"), "HTMLDocument",
|
||||
is(documentVar.target.querySelector(".value").getAttribute("value"),
|
||||
"HTMLDocument \u2192 doc_frame-parameters.html",
|
||||
"Should have the right property value for 'document'.");
|
||||
ok(documentVar.target.querySelector(".value").className.contains("token-other"),
|
||||
"Should have the right token class for 'document'.");
|
||||
|
@ -98,14 +99,14 @@ function performTest() {
|
|||
|
||||
is(buttonVar.get("childNodes").target.querySelector(".name").getAttribute("value"), "childNodes",
|
||||
"Should have the right property name for 'childNodes'.");
|
||||
is(buttonVar.get("childNodes").target.querySelector(".value").getAttribute("value"), "NodeList",
|
||||
is(buttonVar.get("childNodes").target.querySelector(".value").getAttribute("value"), "NodeList[1]",
|
||||
"Should have the right property value for 'childNodes'.");
|
||||
ok(buttonVar.get("childNodes").target.querySelector(".value").className.contains("token-other"),
|
||||
"Should have the right token class for 'childNodes'.");
|
||||
|
||||
is(buttonVar.get("onclick").target.querySelector(".name").getAttribute("value"), "onclick",
|
||||
"Should have the right property name for 'onclick'.");
|
||||
is(buttonVar.get("onclick").target.querySelector(".value").getAttribute("value"), "Function",
|
||||
is(buttonVar.get("onclick").target.querySelector(".value").getAttribute("value"), "onclick(event)",
|
||||
"Should have the right property value for 'onclick'.");
|
||||
ok(buttonVar.get("onclick").target.querySelector(".value").className.contains("token-other"),
|
||||
"Should have the right token class for 'onclick'.");
|
||||
|
@ -119,7 +120,7 @@ function performTest() {
|
|||
|
||||
is(documentVar.get("childNodes").target.querySelector(".name").getAttribute("value"), "childNodes",
|
||||
"Should have the right property name for 'childNodes'.");
|
||||
is(documentVar.get("childNodes").target.querySelector(".value").getAttribute("value"), "NodeList",
|
||||
is(documentVar.get("childNodes").target.querySelector(".value").getAttribute("value"), "NodeList[3]",
|
||||
"Should have the right property value for 'childNodes'.");
|
||||
ok(documentVar.get("childNodes").target.querySelector(".value").className.contains("token-other"),
|
||||
"Should have the right token class for 'childNodes'.");
|
||||
|
@ -144,7 +145,7 @@ function performTest() {
|
|||
|
||||
is(buttonAsProtoProtoVar.target.querySelector(".name").getAttribute("value"), "__proto__",
|
||||
"Should have the right property name for '__proto__'.");
|
||||
is(buttonAsProtoProtoVar.target.querySelector(".value").getAttribute("value"), "HTMLButtonElement",
|
||||
is(buttonAsProtoProtoVar.target.querySelector(".value").getAttribute("value"), "<button>",
|
||||
"Should have the right property value for '__proto__'.");
|
||||
ok(buttonAsProtoProtoVar.target.querySelector(".value").className.contains("token-other"),
|
||||
"Should have the right token class for '__proto__'.");
|
||||
|
@ -173,14 +174,14 @@ function performTest() {
|
|||
|
||||
is(buttonAsProtoProtoVar.get("childNodes").target.querySelector(".name").getAttribute("value"), "childNodes",
|
||||
"Should have the right property name for 'childNodes'.");
|
||||
is(buttonAsProtoProtoVar.get("childNodes").target.querySelector(".value").getAttribute("value"), "NodeList",
|
||||
is(buttonAsProtoProtoVar.get("childNodes").target.querySelector(".value").getAttribute("value"), "NodeList[1]",
|
||||
"Should have the right property value for 'childNodes'.");
|
||||
ok(buttonAsProtoProtoVar.get("childNodes").target.querySelector(".value").className.contains("token-other"),
|
||||
"Should have the right token class for 'childNodes'.");
|
||||
|
||||
is(buttonAsProtoProtoVar.get("onclick").target.querySelector(".name").getAttribute("value"), "onclick",
|
||||
"Should have the right property name for 'onclick'.");
|
||||
is(buttonAsProtoProtoVar.get("onclick").target.querySelector(".value").getAttribute("value"), "Function",
|
||||
is(buttonAsProtoProtoVar.get("onclick").target.querySelector(".value").getAttribute("value"), "onclick(event)",
|
||||
"Should have the right property value for 'onclick'.");
|
||||
ok(buttonAsProtoProtoVar.get("onclick").target.querySelector(".value").className.contains("token-other"),
|
||||
"Should have the right token class for 'onclick'.");
|
||||
|
|
|
@ -65,20 +65,20 @@ function unhighlightTab(toolId) {
|
|||
|
||||
function checkHighlighted(toolId) {
|
||||
let tab = toolbox.doc.getElementById("toolbox-tab-" + toolId);
|
||||
ok(tab.classList.contains("highlighted"), "The highlighted class is present");
|
||||
ok(tab.hasAttribute("highlighted"), "The highlighted attribute is present");
|
||||
ok(!tab.hasAttribute("selected") || tab.getAttribute("selected") != "true",
|
||||
"The tab is not selected");
|
||||
}
|
||||
|
||||
function checkNoHighlightWhenSelected(toolId) {
|
||||
let tab = toolbox.doc.getElementById("toolbox-tab-" + toolId);
|
||||
ok(tab.classList.contains("highlighted"), "The highlighted class is present");
|
||||
ok(tab.hasAttribute("highlighted"), "The highlighted attribute is present");
|
||||
ok(tab.hasAttribute("selected") && tab.getAttribute("selected") == "true",
|
||||
"and the tab is selected, so the orange glow will not be present.");
|
||||
}
|
||||
|
||||
function checkNoHighlight(toolId) {
|
||||
let tab = toolbox.doc.getElementById("toolbox-tab-" + toolId);
|
||||
ok(!tab.classList.contains("highlighted"),
|
||||
"The highlighted class is not present");
|
||||
ok(!tab.hasAttribute("highlighted"),
|
||||
"The highlighted attribute is not present");
|
||||
}
|
||||
|
|
|
@ -796,7 +796,7 @@ Toolbox.prototype = {
|
|||
*/
|
||||
highlightTool: function(id) {
|
||||
let tab = this.doc.getElementById("toolbox-tab-" + id);
|
||||
tab && tab.classList.add("highlighted");
|
||||
tab && tab.setAttribute("highlighted", "true");
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -807,7 +807,7 @@ Toolbox.prototype = {
|
|||
*/
|
||||
unhighlightTool: function(id) {
|
||||
let tab = this.doc.getElementById("toolbox-tab-" + id);
|
||||
tab && tab.classList.remove("highlighted");
|
||||
tab && tab.removeAttribute("highlighted");
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -27,6 +27,9 @@ Cu.import("resource://gre/modules/devtools/DevToolsUtils.jsm");
|
|||
XPCOMUtils.defineLazyModuleGetter(this, "devtools",
|
||||
"resource://gre/modules/devtools/Loader.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
|
||||
"resource://gre/modules/PluralForm.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "clipboardHelper",
|
||||
"@mozilla.org/widget/clipboardhelper;1",
|
||||
"nsIClipboardHelper");
|
||||
|
@ -2346,7 +2349,10 @@ Variable.prototype = Heritage.extend(Scope.prototype, {
|
|||
this._valueLabel.classList.remove(VariablesView.getClass(prevGrip));
|
||||
}
|
||||
this._valueGrip = aGrip;
|
||||
this._valueString = VariablesView.getString(aGrip, true);
|
||||
this._valueString = VariablesView.getString(aGrip, {
|
||||
concise: true,
|
||||
noEllipsis: true,
|
||||
});
|
||||
this._valueClassName = VariablesView.getClass(aGrip);
|
||||
|
||||
this._valueLabel.classList.add(this._valueClassName);
|
||||
|
@ -3118,12 +3124,16 @@ VariablesView.getGrip = function(aValue) {
|
|||
*
|
||||
* @param any aGrip
|
||||
* @see Variable.setGrip
|
||||
* @param boolean aConciseFlag
|
||||
* Return a concisely formatted property string.
|
||||
* @param object aOptions
|
||||
* Options:
|
||||
* - concise: boolean that tells you want a concisely formatted string.
|
||||
* - noStringQuotes: boolean that tells to not quote strings.
|
||||
* - noEllipsis: boolean that tells to not add an ellipsis after the
|
||||
* initial text of a longString.
|
||||
* @return string
|
||||
* The formatted property string.
|
||||
*/
|
||||
VariablesView.getString = function(aGrip, aConciseFlag) {
|
||||
VariablesView.getString = function(aGrip, aOptions = {}) {
|
||||
if (aGrip && typeof aGrip == "object") {
|
||||
switch (aGrip.type) {
|
||||
case "undefined":
|
||||
|
@ -3133,18 +3143,30 @@ VariablesView.getString = function(aGrip, aConciseFlag) {
|
|||
case "-Infinity":
|
||||
case "-0":
|
||||
return aGrip.type;
|
||||
case "longString":
|
||||
return "\"" + aGrip.initial + "\"";
|
||||
default:
|
||||
if (!aConciseFlag) {
|
||||
return "[" + aGrip.type + " " + aGrip.class + "]";
|
||||
let stringifier = VariablesView.stringifiers.byType[aGrip.type];
|
||||
if (stringifier) {
|
||||
let result = stringifier(aGrip, aOptions);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return aGrip.class;
|
||||
|
||||
if (aGrip.displayString) {
|
||||
return VariablesView.getString(aGrip.displayString, aOptions);
|
||||
}
|
||||
|
||||
if (aGrip.type == "object" && aOptions.concise) {
|
||||
return aGrip.class;
|
||||
}
|
||||
|
||||
return "[" + aGrip.type + " " + aGrip.class + "]";
|
||||
}
|
||||
}
|
||||
|
||||
switch (typeof aGrip) {
|
||||
case "string":
|
||||
return "\"" + aGrip + "\"";
|
||||
return VariablesView.stringifiers.byType.string(aGrip, aOptions);
|
||||
case "boolean":
|
||||
return aGrip ? "true" : "false";
|
||||
case "number":
|
||||
|
@ -3156,6 +3178,367 @@ VariablesView.getString = function(aGrip, aConciseFlag) {
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The VariablesView stringifiers are used by VariablesView.getString(). These
|
||||
* are organized by object type, object class and by object actor preview kind.
|
||||
* Some objects share identical ways for previews, for example Arrays, Sets and
|
||||
* NodeLists.
|
||||
*
|
||||
* Any stringifier function must return a string. If null is returned, * then
|
||||
* the default stringifier will be used. When invoked, the stringifier is
|
||||
* given the same two arguments as those given to VariablesView.getString().
|
||||
*/
|
||||
VariablesView.stringifiers = {};
|
||||
|
||||
VariablesView.stringifiers.byType = {
|
||||
string: function(aGrip, {noStringQuotes}) {
|
||||
if (noStringQuotes) {
|
||||
return aGrip;
|
||||
}
|
||||
return uneval(aGrip);
|
||||
},
|
||||
|
||||
longString: function({initial}, {noStringQuotes, noEllipsis}) {
|
||||
let ellipsis = noEllipsis ? "" : Scope.ellipsis;
|
||||
if (noStringQuotes) {
|
||||
return initial + ellipsis;
|
||||
}
|
||||
let result = uneval(initial);
|
||||
if (!ellipsis) {
|
||||
return result;
|
||||
}
|
||||
return result.substr(0, result.length - 1) + ellipsis + '"';
|
||||
},
|
||||
|
||||
object: function(aGrip, aOptions) {
|
||||
let {preview} = aGrip;
|
||||
let stringifier;
|
||||
if (preview && preview.kind) {
|
||||
stringifier = VariablesView.stringifiers.byObjectKind[preview.kind];
|
||||
}
|
||||
if (!stringifier && aGrip.class) {
|
||||
stringifier = VariablesView.stringifiers.byObjectClass[aGrip.class];
|
||||
}
|
||||
if (stringifier) {
|
||||
return stringifier(aGrip, aOptions);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
}; // VariablesView.stringifiers.byType
|
||||
|
||||
VariablesView.stringifiers.byObjectClass = {
|
||||
Function: function(aGrip, {concise}) {
|
||||
// TODO: Bug 948484 - support arrow functions and ES6 generators
|
||||
|
||||
let name = aGrip.userDisplayName || aGrip.displayName || aGrip.name || "";
|
||||
name = VariablesView.getString(name, { noStringQuotes: true });
|
||||
|
||||
// TODO: Bug 948489 - Support functions with destructured parameters and
|
||||
// rest parameters
|
||||
let params = aGrip.parameterNames || "";
|
||||
if (!concise) {
|
||||
return "function " + name + "(" + params + ")";
|
||||
}
|
||||
return (name || "function ") + "(" + params + ")";
|
||||
},
|
||||
|
||||
RegExp: function({displayString}) {
|
||||
return VariablesView.getString(displayString, { noStringQuotes: true });
|
||||
},
|
||||
|
||||
Date: function({preview}) {
|
||||
if (!preview || !("timestamp" in preview)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (typeof preview.timestamp != "number") {
|
||||
return new Date(preview.timestamp).toString(); // invalid date
|
||||
}
|
||||
|
||||
return "Date " + new Date(preview.timestamp).toISOString();
|
||||
},
|
||||
}; // VariablesView.stringifiers.byObjectClass
|
||||
|
||||
VariablesView.stringifiers.byObjectKind = {
|
||||
ArrayLike: function(aGrip, {concise}) {
|
||||
let {preview} = aGrip;
|
||||
if (concise) {
|
||||
return aGrip.class + "[" + preview.length + "]";
|
||||
}
|
||||
|
||||
if (!preview.items) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let shown = 0, result = [], lastHole = null;
|
||||
for (let item of preview.items) {
|
||||
if (item === null) {
|
||||
if (lastHole !== null) {
|
||||
result[lastHole] += ",";
|
||||
} else {
|
||||
result.push("");
|
||||
}
|
||||
lastHole = result.length - 1;
|
||||
} else {
|
||||
lastHole = null;
|
||||
result.push(VariablesView.getString(item, { concise: true }));
|
||||
}
|
||||
shown++;
|
||||
}
|
||||
|
||||
if (shown < preview.length) {
|
||||
let n = preview.length - shown;
|
||||
result.push(VariablesView.stringifiers._getNMoreString(n));
|
||||
} else if (lastHole !== null) {
|
||||
// make sure we have the right number of commas...
|
||||
result[lastHole] += ",";
|
||||
}
|
||||
|
||||
let prefix = aGrip.class == "Array" ? "" : aGrip.class + " ";
|
||||
return prefix + "[" + result.join(", ") + "]";
|
||||
},
|
||||
|
||||
MapLike: function(aGrip, {concise}) {
|
||||
let {preview} = aGrip;
|
||||
if (concise || !preview.entries) {
|
||||
let size = typeof preview.size == "number" ?
|
||||
"[" + preview.size + "]" : "";
|
||||
return aGrip.class + size;
|
||||
}
|
||||
|
||||
let entries = [];
|
||||
for (let [key, value] of preview.entries) {
|
||||
let keyString = VariablesView.getString(key, {
|
||||
concise: true,
|
||||
noStringQuotes: true,
|
||||
});
|
||||
let valueString = VariablesView.getString(value, { concise: true });
|
||||
entries.push(keyString + ": " + valueString);
|
||||
}
|
||||
|
||||
if (typeof preview.size == "number" && preview.size > entries.length) {
|
||||
let n = preview.size - entries.length;
|
||||
entries.push(VariablesView.stringifiers._getNMoreString(n));
|
||||
}
|
||||
|
||||
return aGrip.class + " {" + entries.join(", ") + "}";
|
||||
},
|
||||
|
||||
ObjectWithText: function(aGrip, {concise}) {
|
||||
if (concise) {
|
||||
return aGrip.class;
|
||||
}
|
||||
|
||||
return aGrip.class + " " + VariablesView.getString(aGrip.preview.text);
|
||||
},
|
||||
|
||||
ObjectWithURL: function(aGrip, {concise}) {
|
||||
let result = aGrip.class;
|
||||
let url = aGrip.preview.url;
|
||||
if (!VariablesView.isFalsy({ value: url })) {
|
||||
result += " \u2192 " + WebConsoleUtils.abbreviateSourceURL(url,
|
||||
{ onlyCropQuery: !concise });
|
||||
}
|
||||
return result;
|
||||
},
|
||||
|
||||
// Stringifier for any kind of object.
|
||||
Object: function(aGrip, {concise}) {
|
||||
if (concise) {
|
||||
return aGrip.class;
|
||||
}
|
||||
|
||||
let {preview} = aGrip;
|
||||
let props = [];
|
||||
for (let key of Object.keys(preview.ownProperties || {})) {
|
||||
let value = preview.ownProperties[key];
|
||||
let valueString = "";
|
||||
if (value.get) {
|
||||
valueString = "Getter";
|
||||
} else if (value.set) {
|
||||
valueString = "Setter";
|
||||
} else {
|
||||
valueString = VariablesView.getString(value.value, { concise: true });
|
||||
}
|
||||
props.push(key + ": " + valueString);
|
||||
}
|
||||
|
||||
for (let key of Object.keys(preview.safeGetterValues || {})) {
|
||||
let value = preview.safeGetterValues[key];
|
||||
let valueString = VariablesView.getString(value.getterValue,
|
||||
{ concise: true });
|
||||
props.push(key + ": " + valueString);
|
||||
}
|
||||
|
||||
if (!props.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (preview.ownPropertiesLength) {
|
||||
let previewLength = Object.keys(preview.ownProperties).length;
|
||||
let diff = preview.ownPropertiesLength - previewLength;
|
||||
if (diff > 0) {
|
||||
props.push(VariablesView.stringifiers._getNMoreString(diff));
|
||||
}
|
||||
}
|
||||
|
||||
let prefix = aGrip.class != "Object" ? aGrip.class + " " : "";
|
||||
return prefix + "{" + props.join(", ") + "}";
|
||||
}, // Object
|
||||
|
||||
Error: function(aGrip, {concise}) {
|
||||
let {preview} = aGrip;
|
||||
let name = VariablesView.getString(preview.name, { noStringQuotes: true });
|
||||
if (concise) {
|
||||
return name || aGrip.class;
|
||||
}
|
||||
|
||||
let msg = name + ": " +
|
||||
VariablesView.getString(preview.message, { noStringQuotes: true });
|
||||
|
||||
if (!VariablesView.isFalsy({ value: preview.stack })) {
|
||||
msg += "\n" + STR.GetStringFromName("variablesViewErrorStacktrace") +
|
||||
"\n" + preview.stack;
|
||||
}
|
||||
|
||||
return msg;
|
||||
},
|
||||
|
||||
DOMException: function(aGrip, {concise}) {
|
||||
let {preview} = aGrip;
|
||||
if (concise) {
|
||||
return preview.name || aGrip.class;
|
||||
}
|
||||
|
||||
let msg = aGrip.class + " [" + preview.name + ": " +
|
||||
VariablesView.getString(preview.message) + "\n" +
|
||||
"code: " + preview.code + "\n" +
|
||||
"nsresult: 0x" + (+preview.result).toString(16);
|
||||
|
||||
if (preview.filename) {
|
||||
msg += "\nlocation: " + preview.filename;
|
||||
if (preview.lineNumber) {
|
||||
msg += ":" + preview.lineNumber;
|
||||
}
|
||||
}
|
||||
|
||||
return msg + "]";
|
||||
},
|
||||
|
||||
DOMEvent: function(aGrip, {concise}) {
|
||||
let {preview} = aGrip;
|
||||
if (!preview.type) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (concise) {
|
||||
return aGrip.class + " " + preview.type;
|
||||
}
|
||||
|
||||
let result = preview.type;
|
||||
|
||||
if (preview.eventKind == "key" && preview.modifiers &&
|
||||
preview.modifiers.length) {
|
||||
result += " " + preview.modifiers.join("-");
|
||||
}
|
||||
|
||||
let props = [];
|
||||
if (preview.target) {
|
||||
let target = VariablesView.getString(preview.target, { concise: true });
|
||||
props.push("target: " + target);
|
||||
}
|
||||
|
||||
for (let prop in preview.properties) {
|
||||
let value = preview.properties[prop];
|
||||
props.push(prop + ": " + VariablesView.getString(value, { concise: true }));
|
||||
}
|
||||
|
||||
return result + " {" + props.join(", ") + "}";
|
||||
}, // DOMEvent
|
||||
|
||||
DOMNode: function(aGrip, {concise}) {
|
||||
let {preview} = aGrip;
|
||||
|
||||
switch (preview.nodeType) {
|
||||
case Ci.nsIDOMNode.DOCUMENT_NODE: {
|
||||
let location = WebConsoleUtils.abbreviateSourceURL(preview.location,
|
||||
{ onlyCropQuery: !concise });
|
||||
return aGrip.class + " \u2192 " + location;
|
||||
}
|
||||
|
||||
case Ci.nsIDOMNode.ATTRIBUTE_NODE: {
|
||||
let value = VariablesView.getString(preview.value, { noStringQuotes: true });
|
||||
return preview.nodeName + '="' + escapeHTML(value) + '"';
|
||||
}
|
||||
|
||||
case Ci.nsIDOMNode.TEXT_NODE:
|
||||
return preview.nodeName + " " +
|
||||
VariablesView.getString(preview.textContent);
|
||||
|
||||
case Ci.nsIDOMNode.COMMENT_NODE: {
|
||||
let comment = VariablesView.getString(preview.textContent,
|
||||
{ noStringQuotes: true });
|
||||
return "<!--" + comment + "-->";
|
||||
}
|
||||
|
||||
case Ci.nsIDOMNode.DOCUMENT_FRAGMENT_NODE: {
|
||||
if (concise || !preview.childNodes) {
|
||||
return aGrip.class + "[" + preview.childNodesLength + "]";
|
||||
}
|
||||
let nodes = [];
|
||||
for (let node of preview.childNodes) {
|
||||
nodes.push(VariablesView.getString(node));
|
||||
}
|
||||
if (nodes.length < preview.childNodesLength) {
|
||||
let n = preview.childNodesLength - nodes.length;
|
||||
nodes.push(VariablesView.stringifiers._getNMoreString(n));
|
||||
}
|
||||
return aGrip.class + " [" + nodes.join(", ") + "]";
|
||||
}
|
||||
|
||||
case Ci.nsIDOMNode.ELEMENT_NODE: {
|
||||
let attrs = preview.attributes;
|
||||
if (!concise) {
|
||||
let n = 0, result = "<" + preview.nodeName;
|
||||
for (let name in attrs) {
|
||||
let value = VariablesView.getString(attrs[name],
|
||||
{ noStringQuotes: true });
|
||||
result += " " + name + '="' + escapeHTML(value) + '"';
|
||||
n++;
|
||||
}
|
||||
if (preview.attributesLength > n) {
|
||||
result += " " + Scope.ellipsis;
|
||||
}
|
||||
return result + ">";
|
||||
}
|
||||
|
||||
let result = "<" + preview.nodeName;
|
||||
if (attrs.id) {
|
||||
result += "#" + attrs.id;
|
||||
}
|
||||
return result + ">";
|
||||
}
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}, // DOMNode
|
||||
}; // VariablesView.stringifiers.byObjectKind
|
||||
|
||||
|
||||
/**
|
||||
* Get the "N more…" formatted string, given an N. This is used for displaying
|
||||
* how many elements are not displayed in an object preview (eg. an array).
|
||||
*
|
||||
* @private
|
||||
* @param number aNumber
|
||||
* @return string
|
||||
*/
|
||||
VariablesView.stringifiers._getNMoreString = function(aNumber) {
|
||||
let str = STR.GetStringFromName("variablesViewMoreObjects");
|
||||
return PluralForm.get(aNumber, str).replace("#1", aNumber);
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a custom class style for a grip.
|
||||
*
|
||||
|
@ -3208,6 +3591,22 @@ let generateId = (function() {
|
|||
};
|
||||
})();
|
||||
|
||||
/**
|
||||
* Escape some HTML special characters. We do not need full HTML serialization
|
||||
* here, we just want to make strings safe to display in HTML attributes, for
|
||||
* the stringifiers.
|
||||
*
|
||||
* @param string aString
|
||||
* @return string
|
||||
*/
|
||||
function escapeHTML(aString) {
|
||||
return aString.replace(/&/g, "&")
|
||||
.replace(/"/g, """)
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* An Editable encapsulates the UI of an edit box that overlays a label,
|
||||
* allowing the user to edit the value.
|
||||
|
|
|
@ -997,10 +997,12 @@ Messages.Extended.prototype = Heritage.extend(Messages.Simple.prototype,
|
|||
}
|
||||
|
||||
let result = this.document.createDocumentFragment();
|
||||
if (!isPrimitive || (!this._quoteStrings && typeof piece == "string")) {
|
||||
result.textContent = piece;
|
||||
if (isPrimitive) {
|
||||
result.textContent = VariablesView.getString(piece, {
|
||||
noStringQuotes: !this._quoteStrings,
|
||||
});
|
||||
} else {
|
||||
result.textContent = VariablesView.getString(piece);
|
||||
result.textContent = piece;
|
||||
}
|
||||
|
||||
return result;
|
||||
|
@ -1219,7 +1221,7 @@ Widgets.JSObject.prototype = Heritage.extend(Widgets.BaseWidget.prototype,
|
|||
_onClick: function()
|
||||
{
|
||||
this.output.openVariablesView({
|
||||
label: this.element.textContent,
|
||||
label: VariablesView.getString(this.objectActor, { concise: true }),
|
||||
objectActor: this.objectActor,
|
||||
autofocus: true,
|
||||
});
|
||||
|
@ -1273,11 +1275,10 @@ Widgets.LongString.prototype = Heritage.extend(Widgets.BaseWidget.prototype,
|
|||
*/
|
||||
_renderString: function(str)
|
||||
{
|
||||
if (this.message._quoteStrings) {
|
||||
this.element.textContent = VariablesView.getString(str);
|
||||
} else {
|
||||
this.element.textContent = str;
|
||||
}
|
||||
this.element.textContent = VariablesView.getString(str, {
|
||||
noStringQuotes: !this.message._quoteStrings,
|
||||
noEllipsis: true,
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -64,6 +64,10 @@ support-files =
|
|||
test-console-extras.html
|
||||
test-console-replaced-api.html
|
||||
test-console.html
|
||||
test-console-output-02.html
|
||||
test-console-output-03.html
|
||||
test-console-output-04.html
|
||||
test-console-output-events.html
|
||||
test-consoleiframes.html
|
||||
test-data.json
|
||||
test-data.json^headers^
|
||||
|
@ -167,7 +171,6 @@ support-files =
|
|||
[browser_webconsole_bug_597136_network_requests_from_chrome.js]
|
||||
[browser_webconsole_bug_597460_filter_scroll.js]
|
||||
[browser_webconsole_bug_597756_reopen_closed_tab.js]
|
||||
[browser_webconsole_bug_598357_jsterm_output.js]
|
||||
[browser_webconsole_bug_599725_response_headers.js]
|
||||
[browser_webconsole_bug_600183_charset.js]
|
||||
[browser_webconsole_bug_601177_log_levels.js]
|
||||
|
@ -248,3 +251,8 @@ run-if = os == "mac"
|
|||
[browser_webconsole_expandable_timestamps.js]
|
||||
[browser_webconsole_autocomplete_in_debugger_stackframe.js]
|
||||
[browser_webconsole_autocomplete_popup_close_on_tab_switch.js]
|
||||
[browser_webconsole_output_01.js]
|
||||
[browser_webconsole_output_02.js]
|
||||
[browser_webconsole_output_03.js]
|
||||
[browser_webconsole_output_04.js]
|
||||
[browser_webconsole_output_events.js]
|
||||
|
|
|
@ -36,7 +36,8 @@ function onExecuteFooObj(msg)
|
|||
|
||||
let anchor = msg.querySelector("a");
|
||||
ok(anchor, "object anchor");
|
||||
isnot(anchor.textContent.indexOf("[object Object]"), -1, "message text check");
|
||||
isnot(anchor.textContent.indexOf('testProp: "testValue"'), -1,
|
||||
"message text check");
|
||||
|
||||
gJSTerm.once("variablesview-fetched", onFooObjFetch);
|
||||
EventUtils.synthesizeMouse(anchor, 2, 2, {}, gWebConsole.iframeWindow)
|
||||
|
@ -76,7 +77,8 @@ function onExecuteWindow(msg)
|
|||
ok(msg, "output message found");
|
||||
let anchor = msg.querySelector("a");
|
||||
ok(anchor, "object anchor");
|
||||
isnot(anchor.textContent.indexOf("[object Window]"), -1, "message text check");
|
||||
isnot(anchor.textContent.indexOf("Window \u2192 http://example.com/browser/"), -1,
|
||||
"message text check");
|
||||
|
||||
gJSTerm.once("variablesview-fetched", onWindowFetch);
|
||||
EventUtils.synthesizeMouse(anchor, 2, 2, {}, gWebConsole.iframeWindow)
|
||||
|
|
|
@ -46,7 +46,7 @@ function onConsoleMessage(aResults)
|
|||
{
|
||||
let clickable = aResults[0].clickableElements[0];
|
||||
ok(clickable, "clickable object found");
|
||||
isnot(clickable.textContent.indexOf("[object Object]"), -1,
|
||||
isnot(clickable.textContent.indexOf('{hello: "world!",'), -1,
|
||||
"message text check");
|
||||
|
||||
gJSTerm.once("variablesview-fetched", onObjFetch);
|
||||
|
|
|
@ -71,7 +71,7 @@ function test()
|
|||
},
|
||||
{
|
||||
name: "console.error output",
|
||||
text: /\bbug851231-error\b.+\[object Object\]/,
|
||||
text: /\bbug851231-error\b.+\{bug851231prop:\s"bug851231value"\}/,
|
||||
category: CATEGORY_WEBDEV,
|
||||
severity: SEVERITY_ERROR,
|
||||
objects: true,
|
||||
|
@ -91,7 +91,7 @@ function test()
|
|||
},
|
||||
{
|
||||
name: "console.dir output",
|
||||
consoleDir: "[object XULDocument]",
|
||||
consoleDir: "XULDocument {",
|
||||
},
|
||||
{
|
||||
name: "console.time output",
|
||||
|
|
|
@ -34,7 +34,7 @@ function performTest(hud)
|
|||
}).then(([result]) => {
|
||||
let clickable = result.clickableElements[0];
|
||||
ok(clickable, "the console.log() object anchor was found");
|
||||
isnot(clickable.textContent.indexOf("Object"), -1,
|
||||
isnot(clickable.textContent.indexOf('{abba: "omgBug676722"}'), -1,
|
||||
"clickable node content is correct");
|
||||
|
||||
hud.jsterm.once("variablesview-fetched",
|
||||
|
|
|
@ -30,7 +30,7 @@ function consoleOpened(hud)
|
|||
waitForMessages({
|
||||
webconsole: hud,
|
||||
messages: [{
|
||||
text: "[object HTMLDocument]",
|
||||
text: "HTMLDocument \u2192 data:text/html;charset=utf8",
|
||||
category: CATEGORY_OUTPUT,
|
||||
objects: true,
|
||||
}],
|
||||
|
@ -90,7 +90,7 @@ function testParagraphs()
|
|||
waitForMessages({
|
||||
webconsole: gWebConsole,
|
||||
messages: [{
|
||||
text: "[object NodeList]",
|
||||
text: "NodeList [",
|
||||
category: CATEGORY_OUTPUT,
|
||||
objects: true,
|
||||
}],
|
||||
|
|
|
@ -28,7 +28,8 @@ function consoleOpened(hud)
|
|||
function onExecuteFooObj(msg)
|
||||
{
|
||||
ok(msg, "output message found");
|
||||
isnot(msg.textContent.indexOf("[object Object]"), -1, "message text check");
|
||||
isnot(msg.textContent.indexOf('{testProp: "testValue"}'), -1,
|
||||
"message text check");
|
||||
|
||||
let anchor = msg.querySelector("a");
|
||||
ok(anchor, "object link found");
|
||||
|
|
|
@ -62,7 +62,8 @@ function onFramesAdded()
|
|||
function onExecuteFooObj(msg)
|
||||
{
|
||||
ok(msg, "output message found");
|
||||
isnot(msg.textContent.indexOf("[object Object]"), -1, "message text check");
|
||||
isnot(msg.textContent.indexOf('{testProp2: "testValue2"}'), -1,
|
||||
"message text check");
|
||||
|
||||
let anchor = msg.querySelector("a");
|
||||
ok(anchor, "object link found");
|
||||
|
|
|
@ -57,7 +57,8 @@ function onFramesAdded()
|
|||
function onExecuteFooObj(msg)
|
||||
{
|
||||
ok(msg, "output message found");
|
||||
isnot(msg.textContent.indexOf("[object Object]"), -1, "message text check");
|
||||
isnot(msg.textContent.indexOf('{testProp2: "testValue2"}'), -1,
|
||||
"message text check");
|
||||
|
||||
let anchor = msg.querySelector("a");
|
||||
ok(anchor, "object link found");
|
||||
|
|
|
@ -29,7 +29,7 @@ function test()
|
|||
|
||||
findVariableViewProperties(aVar, [
|
||||
{ name: "testProp", value: "testValue" },
|
||||
{ name: "document", value: "HTMLDocument" },
|
||||
{ name: "document", value: /HTMLDocument \u2192 data:/ },
|
||||
], { webconsole: hud }).then(finishTest);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,10 +28,10 @@ function performTest(hud)
|
|||
ok(!hud.outputNode.querySelector("#foobar"), "no #foobar element found");
|
||||
|
||||
ok(msg, "eval output node found");
|
||||
is(msg.textContent.indexOf("HTMLDivElement"), -1,
|
||||
"HTMLDivElement string is not displayed");
|
||||
isnot(msg.textContent.indexOf("HTMLParagraphElement"), -1,
|
||||
"HTMLParagraphElement string is displayed");
|
||||
is(msg.textContent.indexOf("<div>"), -1,
|
||||
"<div> string is not displayed");
|
||||
isnot(msg.textContent.indexOf("<p>"), -1,
|
||||
"<p> string is displayed");
|
||||
|
||||
EventUtils.synthesizeMouseAtCenter(msg, {type: "mousemove"});
|
||||
ok(!gBrowser._bug772506, "no content variable");
|
||||
|
|
|
@ -80,9 +80,8 @@ function tab2Loaded(aEvent) {
|
|||
function testEnd() {
|
||||
ok(noErrors, "there were no errors");
|
||||
|
||||
Array.forEach(win1.gBrowser.tabs, function(aTab) {
|
||||
win1.gBrowser.removeTab(aTab);
|
||||
});
|
||||
win1.gBrowser.removeTab(tab1);
|
||||
|
||||
Array.forEach(win2.gBrowser.tabs, function(aTab) {
|
||||
win2.gBrowser.removeTab(aTab);
|
||||
});
|
||||
|
|
|
@ -1,267 +0,0 @@
|
|||
/* vim:set ts=2 sw=2 sts=2 et: */
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||
*
|
||||
* Contributor(s):
|
||||
* Mihai Șucan <mihai.sucan@gmail.com>
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console.html";
|
||||
|
||||
let testEnded = false;
|
||||
let pos = -1;
|
||||
|
||||
let dateNow = Date.now();
|
||||
|
||||
let tempScope = {};
|
||||
Cu.import("resource://gre/modules/devtools/dbg-server.jsm", tempScope);
|
||||
|
||||
let longString = (new Array(tempScope.DebuggerServer.LONG_STRING_LENGTH + 4)).join("a");
|
||||
let initialString = longString.substring(0,
|
||||
tempScope.DebuggerServer.LONG_STRING_INITIAL_LENGTH);
|
||||
|
||||
let inputValues = [
|
||||
// [showsVariablesView?, input value, expected output format,
|
||||
// print() output, console API output, optional console API test]
|
||||
|
||||
// 0
|
||||
[false, "'hello \\nfrom \\rthe \\\"string world!'",
|
||||
'"hello \nfrom \rthe "string world!"',
|
||||
"hello \nfrom \rthe \"string world!"],
|
||||
|
||||
// 1
|
||||
[false, "'\xFA\u1E47\u0129\xE7\xF6d\xEA \u021B\u0115\u0219\u0165'",
|
||||
"\"\xFA\u1E47\u0129\xE7\xF6d\xEA \u021B\u0115\u0219\u0165\"",
|
||||
"\xFA\u1E47\u0129\xE7\xF6d\xEA \u021B\u0115\u0219\u0165"],
|
||||
|
||||
// 2
|
||||
[false, "window.location.href", '"' + TEST_URI + '"', TEST_URI],
|
||||
|
||||
// 3
|
||||
[false, "0", "0"],
|
||||
|
||||
// 4
|
||||
[false, "'0'", '"0"', "0"],
|
||||
|
||||
// 5
|
||||
[false, "42", "42"],
|
||||
|
||||
// 6
|
||||
[false, "'42'", '"42"', "42"],
|
||||
|
||||
// 7
|
||||
[true, "/foobar/", "[object RegExp]", '"/foobar/"', "[object RegExp]"],
|
||||
|
||||
// 8
|
||||
[false, "null", "null"],
|
||||
|
||||
// 9
|
||||
[false, "undefined", "undefined"],
|
||||
|
||||
// 10
|
||||
[false, "true", "true"],
|
||||
|
||||
// 11
|
||||
[true, "document.getElementById", "[object Function]",
|
||||
"function getElementById() {\n [native code]\n}",
|
||||
"[object Function]"],
|
||||
|
||||
// 12
|
||||
[true, "(function() { return 42; })", "[object Function]",
|
||||
"function () { return 42; }", "[object Function]"],
|
||||
|
||||
// 13
|
||||
[true, "new Date(" + dateNow + ")", "[object Date]", (new Date(dateNow)).toString(), "[object Date]"],
|
||||
|
||||
// 14
|
||||
[true, "document.body", "[object HTMLBodyElement]"],
|
||||
|
||||
// 15
|
||||
[true, "window.location", "[object Location]", TEST_URI, "[object Location]"],
|
||||
|
||||
// 16
|
||||
[true, "[1,2,3,'a','b','c','4','5']", '[object Array]',
|
||||
'1,2,3,a,b,c,4,5',
|
||||
"[object Array]"],
|
||||
|
||||
// 17
|
||||
[true, "({a:'b', c:'d', e:1, f:'2'})", "[object Object]"],
|
||||
|
||||
// 18
|
||||
[false, "'" + longString + "'",
|
||||
'"' + initialString + "\"[\u2026]", initialString],
|
||||
];
|
||||
|
||||
longString = null;
|
||||
initialString = null;
|
||||
tempScope = null;
|
||||
|
||||
let eventHandlers = [];
|
||||
let popupShown = [];
|
||||
let HUD;
|
||||
let testDriver;
|
||||
|
||||
function tabLoad(aEvent) {
|
||||
browser.removeEventListener(aEvent.type, tabLoad, true);
|
||||
|
||||
waitForFocus(function () {
|
||||
openConsole(null, function(aHud) {
|
||||
HUD = aHud;
|
||||
testNext();
|
||||
});
|
||||
}, content);
|
||||
}
|
||||
|
||||
function subtestNext() {
|
||||
testDriver.next();
|
||||
}
|
||||
|
||||
function testNext() {
|
||||
pos++;
|
||||
if (pos == inputValues.length) {
|
||||
testEnd();
|
||||
return;
|
||||
}
|
||||
|
||||
testDriver = testGen();
|
||||
testDriver.next();
|
||||
}
|
||||
|
||||
function testGen() {
|
||||
let cpos = pos;
|
||||
|
||||
let showsVariablesView = inputValues[cpos][0];
|
||||
let inputValue = inputValues[cpos][1];
|
||||
let expectedOutput = inputValues[cpos][2];
|
||||
|
||||
let printOutput = inputValues[cpos].length >= 4 ?
|
||||
inputValues[cpos][3] : expectedOutput;
|
||||
|
||||
let consoleOutput = inputValues[cpos].length >= 5 ?
|
||||
inputValues[cpos][4] : printOutput;
|
||||
|
||||
let consoleTest = inputValues[cpos][5] || inputValue;
|
||||
|
||||
HUD.jsterm.clearOutput();
|
||||
|
||||
// Test the console.log() output.
|
||||
|
||||
let outputItem;
|
||||
function onExecute(msg) {
|
||||
outputItem = msg;
|
||||
subtestNext();
|
||||
}
|
||||
|
||||
HUD.jsterm.execute("console.log(" + consoleTest + ")");
|
||||
|
||||
waitForMessages({
|
||||
webconsole: HUD,
|
||||
messages: [{
|
||||
name: "console API output is correct for inputValues[" + cpos + "]",
|
||||
text: consoleOutput,
|
||||
category: CATEGORY_WEBDEV,
|
||||
severity: SEVERITY_LOG,
|
||||
}],
|
||||
}).then(subtestNext);
|
||||
|
||||
yield undefined;
|
||||
|
||||
HUD.jsterm.clearOutput();
|
||||
|
||||
// Test jsterm print() output.
|
||||
|
||||
HUD.jsterm.setInputValue("print(" + inputValue + ")");
|
||||
HUD.jsterm.execute(null, onExecute);
|
||||
|
||||
yield undefined;
|
||||
|
||||
ok(outputItem,
|
||||
"found the jsterm print() output line for inputValues[" + cpos + "]");
|
||||
ok(outputItem.textContent.indexOf(printOutput) > -1,
|
||||
"jsterm print() output is correct for inputValues[" + cpos + "]");
|
||||
|
||||
// Test jsterm execution output.
|
||||
|
||||
HUD.jsterm.clearOutput();
|
||||
HUD.jsterm.setInputValue(inputValue);
|
||||
HUD.jsterm.execute(null, onExecute);
|
||||
|
||||
yield undefined;
|
||||
|
||||
ok(outputItem, "found the jsterm output line for inputValues[" + cpos + "]");
|
||||
ok(outputItem.textContent.indexOf(expectedOutput) > -1,
|
||||
"jsterm output is correct for inputValues[" + cpos + "]");
|
||||
|
||||
let messageBody = outputItem.querySelector(".body a") ||
|
||||
outputItem.querySelector(".body");
|
||||
ok(messageBody, "we have the message body for inputValues[" + cpos + "]");
|
||||
|
||||
// Test click on output.
|
||||
let eventHandlerID = eventHandlers.length + 1;
|
||||
|
||||
let variablesViewShown = function(aEvent, aView, aOptions) {
|
||||
if (aOptions.label.indexOf(expectedOutput) == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
HUD.jsterm.off("variablesview-open", variablesViewShown);
|
||||
|
||||
eventHandlers[eventHandlerID] = null;
|
||||
|
||||
ok(showsVariablesView,
|
||||
"the variables view shown for inputValues[" + cpos + "]");
|
||||
|
||||
popupShown[cpos] = true;
|
||||
|
||||
if (showsVariablesView) {
|
||||
executeSoon(subtestNext);
|
||||
}
|
||||
};
|
||||
|
||||
HUD.jsterm.on("variablesview-open", variablesViewShown);
|
||||
|
||||
eventHandlers.push(variablesViewShown);
|
||||
|
||||
EventUtils.synthesizeMouse(messageBody, 2, 2, {}, HUD.iframeWindow);
|
||||
|
||||
if (showsVariablesView) {
|
||||
info("messageBody tagName '" + messageBody.tagName + "' className '" + messageBody.className + "'");
|
||||
yield undefined; // wait for the panel to open if we need to.
|
||||
}
|
||||
|
||||
testNext();
|
||||
|
||||
yield undefined;
|
||||
}
|
||||
|
||||
function testEnd() {
|
||||
if (testEnded) {
|
||||
return;
|
||||
}
|
||||
|
||||
testEnded = true;
|
||||
|
||||
for (let i = 0; i < eventHandlers.length; i++) {
|
||||
if (eventHandlers[i]) {
|
||||
HUD.jsterm.off("variablesview-open", eventHandlers[i]);
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < inputValues.length; i++) {
|
||||
if (inputValues[i][0] && !popupShown[i]) {
|
||||
ok(false, "the variables view failed to show for inputValues[" + i + "]");
|
||||
}
|
||||
}
|
||||
|
||||
HUD = inputValues = testDriver = null;
|
||||
executeSoon(finishTest);
|
||||
}
|
||||
|
||||
function test() {
|
||||
requestLongerTimeout(2);
|
||||
addTab(TEST_URI);
|
||||
browser.addEventListener("load", tabLoad, true);
|
||||
}
|
||||
|
|
@ -57,20 +57,16 @@ function autocompletePopupHidden()
|
|||
popup._panel.removeEventListener("popuphidden", autocompletePopupHidden, false);
|
||||
|
||||
ok(!popup.isOpen, "popup is not open");
|
||||
|
||||
jsterm.once("autocomplete-updated", function() {
|
||||
is(completeNode.value, testStr + "dy", "autocomplete shows document.body");
|
||||
testPropertyPanel();
|
||||
});
|
||||
|
||||
let inputStr = "document.b";
|
||||
jsterm.setInputValue(inputStr);
|
||||
EventUtils.synthesizeKey("o", {});
|
||||
let testStr = inputStr.replace(/./g, " ") + " ";
|
||||
|
||||
waitForSuccess({
|
||||
name: "autocomplete shows document.body",
|
||||
validatorFn: function()
|
||||
{
|
||||
return completeNode.value == testStr + "dy";
|
||||
},
|
||||
successFn: testPropertyPanel,
|
||||
failureFn: finishTest,
|
||||
});
|
||||
}
|
||||
|
||||
function testPropertyPanel()
|
||||
|
@ -87,7 +83,6 @@ function testPropertyPanel()
|
|||
function onVariablesViewReady(aEvent, aView)
|
||||
{
|
||||
findVariableViewProperties(aView, [
|
||||
{ name: "body", value: "HTMLBodyElement" },
|
||||
{ name: "body", value: "<body>" },
|
||||
], { webconsole: gHUD }).then(finishTest);
|
||||
}
|
||||
|
||||
|
|
|
@ -88,8 +88,7 @@ function performWebConsoleTests(hud)
|
|||
|
||||
function onNodeOutput(node)
|
||||
{
|
||||
isnot(node.textContent.indexOf("[object HTMLHeadingElement"), -1,
|
||||
"correct output for $0");
|
||||
isnot(node.textContent.indexOf("<h1>"), -1, "correct output for $0");
|
||||
|
||||
jsterm.clearOutput();
|
||||
jsterm.execute("$0.textContent = 'bug653531'", onNodeUpdate);
|
||||
|
|
|
@ -22,8 +22,8 @@ function consoleOpened(hud) {
|
|||
|
||||
function testConsoleDir(hud, ev, view) {
|
||||
findVariableViewProperties(view, [
|
||||
{ name: "__proto__.__proto__.querySelectorAll", value: "Function" },
|
||||
{ name: "location", value: "Location" },
|
||||
{ name: "__proto__.write", value: "Function" },
|
||||
{ name: "__proto__.__proto__.querySelectorAll", value: "querySelectorAll()" },
|
||||
{ name: "location", value: /Location \u2192 data:Web/ },
|
||||
{ name: "__proto__.write", value: "write()" },
|
||||
], { webconsole: hud }).then(finishTest);
|
||||
}
|
||||
|
|
|
@ -54,7 +54,7 @@ function consoleOpened(hud)
|
|||
waitForMessages({
|
||||
webconsole: gWebConsole,
|
||||
messages: [{
|
||||
text: "[object Function]",
|
||||
text: "function _pfactory/<.getName()",
|
||||
category: CATEGORY_OUTPUT,
|
||||
objects: true,
|
||||
}],
|
||||
|
|
|
@ -118,7 +118,7 @@ function testJSTerm(hud)
|
|||
|
||||
jsterm.clearOutput();
|
||||
jsterm.execute("pprint({b:2, a:1})");
|
||||
checkResult('" b: 2\n a: 1"', "pprint()");
|
||||
checkResult("\" b: 2\\n a: 1\"", "pprint()");
|
||||
yield undefined;
|
||||
|
||||
// check instanceof correctness, bug 599940
|
||||
|
@ -154,7 +154,7 @@ function testJSTerm(hud)
|
|||
// bug 614561
|
||||
jsterm.clearOutput();
|
||||
jsterm.execute("pprint('hi')");
|
||||
checkResult('" 0: "h"\n 1: "i""', "pprint('hi')");
|
||||
checkResult("\" 0: \\\"h\\\"\\n 1: \\\"i\\\"\"", "pprint('hi')");
|
||||
yield undefined;
|
||||
|
||||
// check that pprint(function) shows function source, bug 618344
|
||||
|
|
|
@ -0,0 +1,146 @@
|
|||
/*
|
||||
* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||
*/
|
||||
|
||||
// Test the webconsole output for various types of objects.
|
||||
|
||||
const TEST_URI = "data:text/html;charset=utf8,test for console output - 01";
|
||||
|
||||
let dateNow = Date.now();
|
||||
let {DebuggerServer} = Cu.import("resource://gre/modules/devtools/dbg-server.jsm", {});
|
||||
|
||||
let LONG_STRING_LENGTH = DebuggerServer.LONG_STRING_LENGTH;
|
||||
let LONG_STRING_INITIAL_LENGTH = DebuggerServer.LONG_STRING_INITIAL_LENGTH;
|
||||
DebuggerServer.LONG_STRING_LENGTH = 100;
|
||||
DebuggerServer.LONG_STRING_INITIAL_LENGTH = 50;
|
||||
|
||||
let longString = (new Array(DebuggerServer.LONG_STRING_LENGTH + 4)).join("a");
|
||||
let initialString = longString.substring(0, DebuggerServer.LONG_STRING_INITIAL_LENGTH);
|
||||
|
||||
let inputTests = [
|
||||
// 0
|
||||
{
|
||||
input: "'hello \\nfrom \\rthe \\\"string world!'",
|
||||
output: "\"hello \\nfrom \\rthe \\\"string world!\"",
|
||||
},
|
||||
|
||||
// 1
|
||||
{
|
||||
// unicode test
|
||||
input: "'\xFA\u1E47\u0129\xE7\xF6d\xEA \u021B\u0115\u0219\u0165'",
|
||||
output: "\"\\xFA\\u1E47\\u0129\\xE7\\xF6d\\xEA \\u021B\\u0115\\u0219\\u0165\"",
|
||||
},
|
||||
|
||||
// 2
|
||||
{
|
||||
input: "'" + longString + "'",
|
||||
output: '"' + initialString + "\"[\u2026]",
|
||||
printOutput: initialString,
|
||||
},
|
||||
|
||||
// 3
|
||||
{
|
||||
input: "''",
|
||||
output: '""',
|
||||
printOutput: '""',
|
||||
},
|
||||
|
||||
// 4
|
||||
{
|
||||
input: "0",
|
||||
output: "0",
|
||||
},
|
||||
|
||||
// 5
|
||||
{
|
||||
input: "'0'",
|
||||
output: '"0"',
|
||||
},
|
||||
|
||||
// 6
|
||||
{
|
||||
input: "42",
|
||||
output: "42",
|
||||
},
|
||||
|
||||
// 7
|
||||
{
|
||||
input: "'42'",
|
||||
output: '"42"',
|
||||
},
|
||||
|
||||
// 8
|
||||
{
|
||||
input: "/foobar/",
|
||||
output: "/foobar/",
|
||||
inspectable: true,
|
||||
},
|
||||
|
||||
// 9
|
||||
{
|
||||
input: "/foo?b*\\s\"ar/igym",
|
||||
output: "/foo?b*\\s\"ar/gimy",
|
||||
printOutput: "/foo?b*\\\\s\\\"ar/gimy",
|
||||
inspectable: true,
|
||||
},
|
||||
|
||||
// 10
|
||||
{
|
||||
input: "null",
|
||||
output: "null",
|
||||
},
|
||||
|
||||
// 11
|
||||
{
|
||||
input: "undefined",
|
||||
output: "undefined",
|
||||
},
|
||||
|
||||
// 12
|
||||
{
|
||||
input: "true",
|
||||
output: "true",
|
||||
},
|
||||
|
||||
// 13
|
||||
{
|
||||
input: "false",
|
||||
output: "false",
|
||||
},
|
||||
|
||||
|
||||
// 14
|
||||
{
|
||||
input: "new Date(" + dateNow + ")",
|
||||
output: "Date " + (new Date(dateNow)).toISOString(),
|
||||
printOutput: (new Date(dateNow)).toString(),
|
||||
inspectable: true,
|
||||
},
|
||||
|
||||
// 15
|
||||
{
|
||||
input: "new Date('test')",
|
||||
output: "Invalid Date",
|
||||
printOutput: "Invalid Date",
|
||||
inspectable: true,
|
||||
variablesViewLabel: "Invalid Date",
|
||||
},
|
||||
];
|
||||
|
||||
longString = initialString = null;
|
||||
|
||||
function test() {
|
||||
registerCleanupFunction(() => {
|
||||
DebuggerServer.LONG_STRING_LENGTH = LONG_STRING_LENGTH;
|
||||
DebuggerServer.LONG_STRING_INITIAL_LENGTH = LONG_STRING_INITIAL_LENGTH;
|
||||
});
|
||||
|
||||
addTab(TEST_URI);
|
||||
browser.addEventListener("load", function onLoad() {
|
||||
browser.removeEventListener("load", onLoad, true);
|
||||
openConsole().then((hud) => {
|
||||
return checkOutputForInputs(hud, inputTests);
|
||||
}).then(finishTest);
|
||||
}, true);
|
||||
}
|
|
@ -0,0 +1,161 @@
|
|||
/*
|
||||
* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||
*/
|
||||
|
||||
// Test the webconsole output for various types of objects.
|
||||
|
||||
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console-output-02.html";
|
||||
|
||||
let inputTests = [
|
||||
// 0 - native named function
|
||||
{
|
||||
input: "document.getElementById",
|
||||
output: "function getElementById()",
|
||||
printOutput: "function getElementById() {\\n [native code]\\n}",
|
||||
inspectable: true,
|
||||
variablesViewLabel: "getElementById()",
|
||||
},
|
||||
|
||||
// 1 - anonymous function
|
||||
{
|
||||
input: "(function() { return 42; })",
|
||||
output: "function ()",
|
||||
printOutput: "function () { return 42; }",
|
||||
inspectable: true,
|
||||
},
|
||||
|
||||
// 2 - named function
|
||||
{
|
||||
input: "window.testfn1",
|
||||
output: "function testfn1()",
|
||||
printOutput: "function testfn1() { return 42; }",
|
||||
inspectable: true,
|
||||
variablesViewLabel: "testfn1()",
|
||||
},
|
||||
|
||||
// 3 - anonymous function, but spidermonkey gives us an inferred name.
|
||||
{
|
||||
input: "testobj1.testfn2",
|
||||
output: "function testobj1.testfn2()",
|
||||
printOutput: "function () { return 42; }",
|
||||
inspectable: true,
|
||||
variablesViewLabel: "testobj1.testfn2()",
|
||||
},
|
||||
|
||||
// 4 - named function with custom display name
|
||||
{
|
||||
input: "window.testfn3",
|
||||
output: "function testfn3DisplayName()",
|
||||
printOutput: "function testfn3() { return 42; }",
|
||||
inspectable: true,
|
||||
variablesViewLabel: "testfn3DisplayName()",
|
||||
},
|
||||
|
||||
// 5 - basic array
|
||||
{
|
||||
input: "window.array1",
|
||||
output: '[1, 2, 3, "a", "b", "c", "4", "5"]',
|
||||
printOutput: "1,2,3,a,b,c,4,5",
|
||||
inspectable: true,
|
||||
variablesViewLabel: "Array[8]",
|
||||
},
|
||||
|
||||
// 6 - array with objects
|
||||
{
|
||||
input: "window.array2",
|
||||
output: '["a", HTMLDocument \u2192 test-console-output-02.html, <body>, ' +
|
||||
"DOMStringMap[0], DOMTokenList[0]]",
|
||||
printOutput: '"a,[object HTMLDocument],[object HTMLBodyElement],' +
|
||||
'[object DOMStringMap],"',
|
||||
inspectable: true,
|
||||
variablesViewLabel: "Array[5]",
|
||||
},
|
||||
|
||||
// 7 - array with more than 10 elements
|
||||
{
|
||||
input: "window.array3",
|
||||
output: '[1, Window \u2192 test-console-output-02.html, null, "a", "b", ' +
|
||||
'undefined, false, "", -Infinity, testfn3DisplayName(), 3 more\u2026]',
|
||||
printOutput: '"1,[object Window],,a,b,,false,,-Infinity,' +
|
||||
'function testfn3() { return 42; },[object Object],foo,bar"',
|
||||
inspectable: true,
|
||||
variablesViewLabel: "Array[13]",
|
||||
},
|
||||
|
||||
// 8 - array with holes and a cyclic reference
|
||||
{
|
||||
input: "window.array4",
|
||||
output: '[,,,,, "test", Array[7]]',
|
||||
printOutput: '",,,,,test,"',
|
||||
inspectable: true,
|
||||
variablesViewLabel: "Array[7]",
|
||||
},
|
||||
|
||||
// 9
|
||||
{
|
||||
input: "window.typedarray1",
|
||||
output: 'Int32Array [1, 287, 8651, 40983, 8754]',
|
||||
printOutput: "[object Int32Array]",
|
||||
inspectable: true,
|
||||
variablesViewLabel: "Int32Array[5]",
|
||||
},
|
||||
|
||||
// 10 - Set with cyclic reference
|
||||
{
|
||||
input: "window.set1",
|
||||
output: 'Set [1, 2, null, Array[13], "a", "b", undefined, <head>, Set[9]]',
|
||||
printOutput: "[object Set]",
|
||||
inspectable: true,
|
||||
variablesViewLabel: "Set[9]",
|
||||
},
|
||||
|
||||
// 11 - Object with cyclic reference and a getter
|
||||
{
|
||||
input: "window.testobj2",
|
||||
output: '{a: "b", c: "d", e: 1, f: "2", foo: Object, bar: Object, ' +
|
||||
"getterTest: Getter}",
|
||||
printOutput: "[object Object]",
|
||||
inspectable: true,
|
||||
variablesViewLabel: "Object",
|
||||
},
|
||||
|
||||
// 12 - Object with more than 10 properties
|
||||
{
|
||||
input: "window.testobj3",
|
||||
output: '{a: "b", c: "d", e: 1, f: "2", g: true, h: null, i: undefined, ' +
|
||||
'j: "", k: StyleSheetList[0], l: NodeList[5], 2 more\u2026}',
|
||||
printOutput: "[object Object]",
|
||||
inspectable: true,
|
||||
variablesViewLabel: "Object",
|
||||
},
|
||||
|
||||
// 13 - Object with a non-enumerable property that we do not show
|
||||
{
|
||||
input: "window.testobj4",
|
||||
output: '{a: "b", c: "d", 1 more\u2026}',
|
||||
printOutput: "[object Object]",
|
||||
inspectable: true,
|
||||
variablesViewLabel: "Object",
|
||||
},
|
||||
|
||||
// 14 - Map with cyclic references
|
||||
{
|
||||
input: "window.map1",
|
||||
output: 'Map {a: "b", HTMLCollection[2]: Object, Map[3]: Set[9]}',
|
||||
printOutput: "[object Map]",
|
||||
inspectable: true,
|
||||
variablesViewLabel: "Map[3]",
|
||||
},
|
||||
];
|
||||
|
||||
function test() {
|
||||
|
||||
addTab(TEST_URI);
|
||||
browser.addEventListener("load", function onLoad() {
|
||||
browser.removeEventListener("load", onLoad, true);
|
||||
openConsole().then((hud) => {
|
||||
return checkOutputForInputs(hud, inputTests);
|
||||
}).then(finishTest);
|
||||
}, true);
|
||||
}
|
|
@ -0,0 +1,164 @@
|
|||
/*
|
||||
* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||
*/
|
||||
|
||||
// Test the webconsole output for various types of objects.
|
||||
|
||||
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console-output-03.html";
|
||||
|
||||
let inputTests = [
|
||||
// 0
|
||||
{
|
||||
input: "document",
|
||||
output: "HTMLDocument \u2192 " + TEST_URI,
|
||||
printOutput: "[object HTMLDocument]",
|
||||
inspectable: true,
|
||||
noClick: true,
|
||||
},
|
||||
|
||||
// 1
|
||||
{
|
||||
input: "window",
|
||||
output: "Window \u2192 " + TEST_URI,
|
||||
printOutput: "[object Window",
|
||||
inspectable: true,
|
||||
noClick: true,
|
||||
},
|
||||
|
||||
// 2
|
||||
{
|
||||
input: "document.body",
|
||||
output: "<body>",
|
||||
printOutput: "[object HTMLBodyElement]",
|
||||
inspectable: true,
|
||||
noClick: true,
|
||||
},
|
||||
|
||||
// 3
|
||||
{
|
||||
input: "document.body.dataset",
|
||||
output: "DOMStringMap {}",
|
||||
printOutput: "[object DOMStringMap]",
|
||||
inspectable: true,
|
||||
variablesViewLabel: "DOMStringMap[0]",
|
||||
},
|
||||
|
||||
// 4
|
||||
{
|
||||
input: "document.body.classList",
|
||||
output: "DOMTokenList []",
|
||||
printOutput: '""',
|
||||
inspectable: true,
|
||||
variablesViewLabel: "DOMTokenList[0]",
|
||||
},
|
||||
|
||||
// 5
|
||||
{
|
||||
input: "window.location.href",
|
||||
output: '"' + TEST_URI + '"',
|
||||
},
|
||||
|
||||
// 6
|
||||
{
|
||||
input: "window.location",
|
||||
output: "Location \u2192 " + TEST_URI,
|
||||
printOutput: TEST_URI,
|
||||
inspectable: true,
|
||||
variablesViewLabel: "Location \u2192 test-console-output-03.html",
|
||||
},
|
||||
|
||||
// 7
|
||||
{
|
||||
input: "document.body.attributes",
|
||||
output: "MozNamedAttrMap []",
|
||||
printOutput: "[object MozNamedAttrMap]",
|
||||
inspectable: true,
|
||||
variablesViewLabel: "MozNamedAttrMap[0]",
|
||||
},
|
||||
|
||||
// 8
|
||||
{
|
||||
input: "document.styleSheets",
|
||||
output: "StyleSheetList []",
|
||||
printOutput: "[object StyleSheetList",
|
||||
inspectable: true,
|
||||
variablesViewLabel: "StyleSheetList[0]",
|
||||
},
|
||||
|
||||
// 9
|
||||
{
|
||||
input: "testBodyClassName()",
|
||||
output: '<body class="test1 tezt2">',
|
||||
printOutput: "[object HTMLBodyElement]",
|
||||
inspectable: true,
|
||||
noClick: true,
|
||||
},
|
||||
|
||||
// 10
|
||||
{
|
||||
input: "testBodyID()",
|
||||
output: '<body class="test1 tezt2" id="foobarid">',
|
||||
printOutput: "[object HTMLBodyElement]",
|
||||
inspectable: true,
|
||||
noClick: true,
|
||||
},
|
||||
|
||||
// 11
|
||||
{
|
||||
input: "document.body.classList",
|
||||
output: 'DOMTokenList ["test1", "tezt2"]',
|
||||
printOutput: '"test1 tezt2"',
|
||||
inspectable: true,
|
||||
variablesViewLabel: "DOMTokenList[2]",
|
||||
},
|
||||
|
||||
// 12
|
||||
{
|
||||
input: "testBodyDataset()",
|
||||
output: '<body class="test1 tezt2" id="foobarid"' +
|
||||
' data-preview="zuzu"<a>foo">',
|
||||
printOutput: "[object HTMLBodyElement]",
|
||||
inspectable: true,
|
||||
noClick: true,
|
||||
},
|
||||
|
||||
// 13
|
||||
{
|
||||
input: "document.body.dataset",
|
||||
output: 'DOMStringMap {preview: "zuzu\\"<a>foo"}',
|
||||
printOutput: "[object DOMStringMap]",
|
||||
inspectable: true,
|
||||
variablesViewLabel: "DOMStringMap[1]",
|
||||
},
|
||||
|
||||
// 14
|
||||
{
|
||||
input: "document.body.attributes",
|
||||
output: 'MozNamedAttrMap [class="test1 tezt2", id="foobarid", ' +
|
||||
'data-preview="zuzu"<a>foo"]',
|
||||
printOutput: "[object MozNamedAttrMap]",
|
||||
inspectable: true,
|
||||
variablesViewLabel: "MozNamedAttrMap[3]",
|
||||
},
|
||||
|
||||
// 15
|
||||
{
|
||||
input: "document.body.attributes[0]",
|
||||
output: 'class="test1 tezt2"',
|
||||
printOutput: "[object Attr]",
|
||||
inspectable: true,
|
||||
variablesViewLabel: 'class="test1 tezt2"',
|
||||
},
|
||||
];
|
||||
|
||||
function test() {
|
||||
|
||||
addTab(TEST_URI);
|
||||
browser.addEventListener("load", function onLoad() {
|
||||
browser.removeEventListener("load", onLoad, true);
|
||||
openConsole().then((hud) => {
|
||||
return checkOutputForInputs(hud, inputTests);
|
||||
}).then(finishTest);
|
||||
}, true);
|
||||
}
|
|
@ -0,0 +1,120 @@
|
|||
/*
|
||||
* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||
*/
|
||||
|
||||
// Test the webconsole output for various types of objects.
|
||||
|
||||
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console-output-04.html";
|
||||
|
||||
let inputTests = [
|
||||
// 0
|
||||
{
|
||||
input: "testTextNode()",
|
||||
output: '#text "hello world!"',
|
||||
printOutput: "[object Text]",
|
||||
inspectable: true,
|
||||
noClick: true,
|
||||
},
|
||||
|
||||
// 1
|
||||
{
|
||||
input: "testCommentNode()",
|
||||
output: "<!--\n - Any copyright ",
|
||||
printOutput: "[object Comment]",
|
||||
inspectable: true,
|
||||
noClick: true,
|
||||
},
|
||||
|
||||
// 2
|
||||
{
|
||||
input: "testDocumentFragment()",
|
||||
output: 'DocumentFragment [<div id="foo1" class="bar">, <div id="foo3">]',
|
||||
printOutput: "[object DocumentFragment]",
|
||||
inspectable: true,
|
||||
variablesViewLabel: "DocumentFragment[2]",
|
||||
},
|
||||
|
||||
// 3
|
||||
{
|
||||
input: "testError()",
|
||||
output: "TypeError: window.foobar is not a function\n" +
|
||||
"Stack trace:\n" +
|
||||
"testError@" + TEST_URI + ":44",
|
||||
printOutput: '"TypeError: window.foobar is not a function"',
|
||||
inspectable: true,
|
||||
variablesViewLabel: "TypeError",
|
||||
},
|
||||
|
||||
// 4
|
||||
{
|
||||
input: "testDOMException()",
|
||||
output: 'DOMException [SyntaxError: "An invalid or illegal string was specified"',
|
||||
printOutput: '[Exception... \\"An invalid or illegal string was specified\\"',
|
||||
inspectable: true,
|
||||
variablesViewLabel: "SyntaxError",
|
||||
},
|
||||
|
||||
// 5
|
||||
{
|
||||
input: "testCSSStyleDeclaration()",
|
||||
output: 'CSS2Properties {color: "green", font-size: "2em"}',
|
||||
printOutput: "[object CSS2Properties]",
|
||||
inspectable: true,
|
||||
noClick: true,
|
||||
},
|
||||
|
||||
// 6
|
||||
{
|
||||
input: "testStyleSheetList()",
|
||||
output: "StyleSheetList [CSSStyleSheet]",
|
||||
printOutput: "[object StyleSheetList",
|
||||
inspectable: true,
|
||||
variablesViewLabel: "StyleSheetList[1]",
|
||||
},
|
||||
|
||||
// 7
|
||||
{
|
||||
input: "document.styleSheets[0]",
|
||||
output: "CSSStyleSheet",
|
||||
printOutput: "[object CSSStyleSheet]",
|
||||
inspectable: true,
|
||||
},
|
||||
|
||||
// 8
|
||||
{
|
||||
input: "document.styleSheets[0].cssRules",
|
||||
output: "CSSRuleList [CSSStyleRule, CSSMediaRule]",
|
||||
printOutput: "[object CSSRuleList",
|
||||
inspectable: true,
|
||||
variablesViewLabel: "CSSRuleList[2]",
|
||||
},
|
||||
|
||||
// 9
|
||||
{
|
||||
input: "document.styleSheets[0].cssRules[0]",
|
||||
output: 'CSSStyleRule "p, div"',
|
||||
printOutput: "[object CSSStyleRule",
|
||||
inspectable: true,
|
||||
variablesViewLabel: "CSSStyleRule",
|
||||
},
|
||||
|
||||
// 10
|
||||
{
|
||||
input: "document.styleSheets[0].cssRules[1]",
|
||||
output: 'CSSMediaRule "print"',
|
||||
printOutput: "[object CSSMediaRule",
|
||||
inspectable: true,
|
||||
variablesViewLabel: "CSSMediaRule",
|
||||
},
|
||||
];
|
||||
|
||||
function test() {
|
||||
addTab(TEST_URI);
|
||||
browser.addEventListener("load", function onLoad() {
|
||||
browser.removeEventListener("load", onLoad, true);
|
||||
openConsole().then((hud) => {
|
||||
return checkOutputForInputs(hud, inputTests);
|
||||
}).then(finishTest);
|
||||
}, true);
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||
*/
|
||||
|
||||
// Test the webconsole output for DOM events.
|
||||
|
||||
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console-output-events.html";
|
||||
|
||||
function test() {
|
||||
addTab(TEST_URI);
|
||||
browser.addEventListener("load", function onLoad() {
|
||||
browser.removeEventListener("load", onLoad, true);
|
||||
Task.spawn(runner);
|
||||
}, true);
|
||||
|
||||
function* runner()
|
||||
{
|
||||
let hud = yield openConsole();
|
||||
|
||||
hud.jsterm.clearOutput();
|
||||
hud.jsterm.execute("testDOMEvents()");
|
||||
|
||||
yield waitForMessages({
|
||||
webconsole: hud,
|
||||
messages: [{
|
||||
name: "testDOMEvents() output",
|
||||
text: "undefined",
|
||||
category: CATEGORY_OUTPUT,
|
||||
}],
|
||||
});
|
||||
|
||||
EventUtils.synthesizeMouse(content.document.body, 2, 2, {type: "mousemove"}, content);
|
||||
|
||||
yield waitForMessages({
|
||||
webconsole: hud,
|
||||
messages: [{
|
||||
name: "console.log() output for mousemove",
|
||||
text: /"eventLogger" mousemove {target: .+, buttons: 1, clientX: \d+, clientY: \d+, layerX: \d+, layerY: \d+}/,
|
||||
category: CATEGORY_WEBDEV,
|
||||
severity: SEVERITY_LOG,
|
||||
}],
|
||||
});
|
||||
|
||||
content.focus();
|
||||
EventUtils.synthesizeKey("a", {shiftKey: true}, content);
|
||||
|
||||
yield waitForMessages({
|
||||
webconsole: hud,
|
||||
messages: [{
|
||||
name: "console.log() output for keypress",
|
||||
text: /"eventLogger" keypress Shift {target: .+, key: .+, charCode: \d+, keyCode: \d+}/,
|
||||
category: CATEGORY_WEBDEV,
|
||||
severity: SEVERITY_LOG,
|
||||
}],
|
||||
});
|
||||
|
||||
finishTest();
|
||||
}
|
||||
}
|
|
@ -3,18 +3,18 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
let WebConsoleUtils, gDevTools, TargetFactory, console, promise, require;
|
||||
let WebConsoleUtils, TargetFactory, require;
|
||||
let {gDevTools} = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});
|
||||
let {console} = Cu.import("resource://gre/modules/devtools/Console.jsm", {});
|
||||
let {Promise: promise} = Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js", {});
|
||||
let {Task} = Cu.import("resource://gre/modules/Task.jsm", {});
|
||||
|
||||
(() => {
|
||||
gDevTools = Cu.import("resource:///modules/devtools/gDevTools.jsm", {}).gDevTools;
|
||||
console = Cu.import("resource://gre/modules/devtools/Console.jsm", {}).console;
|
||||
promise = Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js", {}).Promise;
|
||||
|
||||
let tools = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools;
|
||||
let utils = tools.require("devtools/toolkit/webconsole/utils");
|
||||
TargetFactory = tools.TargetFactory;
|
||||
let {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
|
||||
let utils = devtools.require("devtools/toolkit/webconsole/utils");
|
||||
TargetFactory = devtools.TargetFactory;
|
||||
WebConsoleUtils = utils.Utils;
|
||||
require = tools.require;
|
||||
require = devtools.require;
|
||||
})();
|
||||
// promise._reportErrors = true; // please never leave me.
|
||||
|
||||
|
@ -152,15 +152,20 @@ function findLogEntry(aString)
|
|||
* @param function [aCallback]
|
||||
* Optional function to invoke after the Web Console completes
|
||||
* initialization (web-console-created).
|
||||
* @return object
|
||||
* A promise that is resolved once the web console is open.
|
||||
*/
|
||||
function openConsole(aTab, aCallback = function() { })
|
||||
{
|
||||
let deferred = promise.defer();
|
||||
let target = TargetFactory.forTab(aTab || tab);
|
||||
gDevTools.showToolbox(target, "webconsole").then(function(toolbox) {
|
||||
let hud = toolbox.getCurrentPanel().hud;
|
||||
hud.jsterm._lazyVariablesView = false;
|
||||
aCallback(hud);
|
||||
deferred.resolve(hud);
|
||||
});
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1259,3 +1264,153 @@ function whenDelayedStartupFinished(aWindow, aCallback)
|
|||
}
|
||||
}, "browser-delayed-startup-finished", false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the web console output for the given inputs. Each input is checked for
|
||||
* the expected JS eval result, the result of calling print(), the result of
|
||||
* console.log(). The JS eval result is also checked if it opens the variables
|
||||
* view on click.
|
||||
*
|
||||
* @param object hud
|
||||
* The web console instance to work with.
|
||||
* @param array inputTests
|
||||
* An array of input tests. An input test element is an object. Each
|
||||
* object has the following properties:
|
||||
* - input: string, JS input value to execute.
|
||||
*
|
||||
* - output: string|RegExp, expected JS eval result.
|
||||
*
|
||||
* - inspectable: boolean, when true, the test runner expects the JS eval
|
||||
* result is an object that can be clicked for inspection.
|
||||
*
|
||||
* - noClick: boolean, when true, the test runner does not click the JS
|
||||
* eval result. Some objects, like |window|, have a lot of properties and
|
||||
* opening vview for them is very slow (they can cause timeouts in debug
|
||||
* builds).
|
||||
*
|
||||
* - printOutput: string|RegExp, optional, expected output for
|
||||
* |print(input)|. If this is not provided, printOutput = output.
|
||||
*
|
||||
* - variablesViewLabel: string|RegExp, optional, the expected variables
|
||||
* view label when the object is inspected. If this is not provided, then
|
||||
* |output| is used.
|
||||
*/
|
||||
function checkOutputForInputs(hud, inputTests)
|
||||
{
|
||||
let eventHandlers = new Set();
|
||||
|
||||
function* runner()
|
||||
{
|
||||
for (let [i, entry] of inputTests.entries()) {
|
||||
info("checkInput(" + i + "): " + entry.input);
|
||||
yield checkInput(entry);
|
||||
}
|
||||
|
||||
for (let fn of eventHandlers) {
|
||||
hud.jsterm.off("variablesview-open", fn);
|
||||
}
|
||||
}
|
||||
|
||||
function* checkInput(entry)
|
||||
{
|
||||
yield checkConsoleLog(entry);
|
||||
yield checkPrintOutput(entry);
|
||||
yield checkJSEval(entry);
|
||||
}
|
||||
|
||||
function checkConsoleLog(entry)
|
||||
{
|
||||
hud.jsterm.clearOutput();
|
||||
hud.jsterm.execute("console.log(" + entry.input + ")");
|
||||
|
||||
return waitForMessages({
|
||||
webconsole: hud,
|
||||
messages: [{
|
||||
name: "console.log() output: " + entry.output,
|
||||
text: entry.output,
|
||||
category: CATEGORY_WEBDEV,
|
||||
severity: SEVERITY_LOG,
|
||||
}],
|
||||
});
|
||||
}
|
||||
|
||||
function checkPrintOutput(entry)
|
||||
{
|
||||
hud.jsterm.clearOutput();
|
||||
hud.jsterm.execute("print(" + entry.input + ")");
|
||||
|
||||
let printOutput = entry.printOutput || entry.output;
|
||||
|
||||
return waitForMessages({
|
||||
webconsole: hud,
|
||||
messages: [{
|
||||
name: "print() output: " + printOutput,
|
||||
text: printOutput,
|
||||
category: CATEGORY_OUTPUT,
|
||||
}],
|
||||
});
|
||||
}
|
||||
|
||||
function* checkJSEval(entry)
|
||||
{
|
||||
hud.jsterm.clearOutput();
|
||||
hud.jsterm.execute(entry.input);
|
||||
|
||||
let [result] = yield waitForMessages({
|
||||
webconsole: hud,
|
||||
messages: [{
|
||||
name: "JS eval output: " + entry.output,
|
||||
text: entry.output,
|
||||
category: CATEGORY_OUTPUT,
|
||||
}],
|
||||
});
|
||||
|
||||
if (!entry.noClick) {
|
||||
let msg = [...result.matched][0];
|
||||
yield checkObjectClick(entry, msg);
|
||||
}
|
||||
}
|
||||
|
||||
function checkObjectClick(entry, msg)
|
||||
{
|
||||
let body = msg.querySelector(".body a") || msg.querySelector(".body");
|
||||
ok(body, "the message body");
|
||||
|
||||
let deferred = promise.defer();
|
||||
|
||||
entry._onVariablesViewOpen = onVariablesViewOpen.bind(null, entry, deferred);
|
||||
hud.jsterm.on("variablesview-open", entry._onVariablesViewOpen);
|
||||
eventHandlers.add(entry._onVariablesViewOpen);
|
||||
|
||||
body.scrollIntoView();
|
||||
EventUtils.synthesizeMouse(body, 2, 2, {}, hud.iframeWindow);
|
||||
|
||||
if (entry.inspectable) {
|
||||
info("message body tagName '" + body.tagName + "' className '" + body.className + "'");
|
||||
return deferred.promise; // wait for the panel to open if we need to.
|
||||
}
|
||||
|
||||
return promise.resolve(null);
|
||||
}
|
||||
|
||||
function onVariablesViewOpen(entry, deferred, event, view, options)
|
||||
{
|
||||
let label = entry.variablesViewLabel || entry.output;
|
||||
if (typeof label == "string" && options.label != label) {
|
||||
return;
|
||||
}
|
||||
if (label instanceof RegExp && !label.test(options.label)) {
|
||||
return;
|
||||
}
|
||||
|
||||
hud.jsterm.off("variablesview-open", entry._onVariablesViewOpen);
|
||||
eventHandlers.delete(entry._onVariablesViewOpen);
|
||||
entry._onVariablesViewOpen = null;
|
||||
|
||||
ok(entry.inspectable, "variables view was shown");
|
||||
|
||||
deferred.resolve(null);
|
||||
}
|
||||
|
||||
return Task.spawn(runner);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html dir="ltr" lang="en-US">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Test the web console output - 02</title>
|
||||
<!--
|
||||
- Any copyright is dedicated to the Public Domain.
|
||||
- http://creativecommons.org/publicdomain/zero/1.0/
|
||||
-->
|
||||
</head>
|
||||
<body>
|
||||
<p>hello world!</p>
|
||||
<script type="text/javascript">
|
||||
function testfn1() { return 42; }
|
||||
|
||||
var testobj1 = {
|
||||
testfn2: function() { return 42; },
|
||||
};
|
||||
|
||||
function testfn3() { return 42; }
|
||||
testfn3.displayName = "testfn3DisplayName";
|
||||
|
||||
var array1 = [1, 2, 3, "a", "b", "c", "4", "5"];
|
||||
|
||||
var array2 = ["a", document, document.body, document.body.dataset,
|
||||
document.body.classList];
|
||||
|
||||
var array3 = [1, window, null, "a", "b", undefined, false, "", -Infinity, testfn3, testobj1, "foo", "bar"];
|
||||
|
||||
var array4 = new Array(5);
|
||||
array4.push("test");
|
||||
array4.push(array4);
|
||||
|
||||
var typedarray1 = new Int32Array([1, 287, 8651, 40983, 8754]);
|
||||
|
||||
var set1 = new Set([1, 2, null, array3, "a", "b", undefined, document.head]);
|
||||
set1.add(set1);
|
||||
|
||||
var testobj2 = {a: "b", c: "d", e: 1, f: "2"};
|
||||
testobj2.foo = testobj1;
|
||||
testobj2.bar = testobj2;
|
||||
Object.defineProperty(testobj2, "getterTest", {
|
||||
enumerable: true,
|
||||
get: function() {
|
||||
return 42;
|
||||
},
|
||||
});
|
||||
|
||||
var testobj3 = {a: "b", c: "d", e: 1, f: "2", g: true, h: null, i: undefined,
|
||||
j: "", k: document.styleSheets, l: document.body.childNodes,
|
||||
o: new Array(125), m: document.head};
|
||||
|
||||
var testobj4 = {a: "b", c: "d"};
|
||||
Object.defineProperty(testobj4, "nonEnumerable", { value: "hello world" });
|
||||
|
||||
var map1 = new Map([["a", "b"], [document.body.children, testobj2]]);
|
||||
map1.set(map1, set1);
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,30 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html dir="ltr" lang="en-US">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Test the web console output - 03</title>
|
||||
<!--
|
||||
- Any copyright is dedicated to the Public Domain.
|
||||
- http://creativecommons.org/publicdomain/zero/1.0/
|
||||
-->
|
||||
</head>
|
||||
<body>
|
||||
<p>hello world!</p>
|
||||
<script type="text/javascript">
|
||||
function testBodyClassName() {
|
||||
document.body.className = "test1 tezt2";
|
||||
return document.body;
|
||||
}
|
||||
|
||||
function testBodyID() {
|
||||
document.body.id = 'foobarid';
|
||||
return document.body;
|
||||
}
|
||||
|
||||
function testBodyDataset() {
|
||||
document.body.dataset.preview = 'zuzu"<a>foo';
|
||||
return document.body;
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,77 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html dir="ltr" lang="en-US">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Test the web console output - 04</title>
|
||||
<!--
|
||||
- Any copyright is dedicated to the Public Domain.
|
||||
- http://creativecommons.org/publicdomain/zero/1.0/
|
||||
-->
|
||||
</head>
|
||||
<body>
|
||||
<p>hello world!</p>
|
||||
<script type="text/javascript">
|
||||
function testTextNode() {
|
||||
return document.querySelector("p").childNodes[0];
|
||||
}
|
||||
|
||||
function testCommentNode() {
|
||||
return document.head.childNodes[5];
|
||||
}
|
||||
|
||||
function testDocumentFragment() {
|
||||
var frag = document.createDocumentFragment();
|
||||
|
||||
var div = document.createElement("div");
|
||||
div.id = "foo1";
|
||||
div.className = "bar";
|
||||
frag.appendChild(div);
|
||||
|
||||
var span = document.createElement("span");
|
||||
span.id = "foo2";
|
||||
span.textContent = "hello world";
|
||||
div.appendChild(span);
|
||||
|
||||
var div2 = document.createElement("div");
|
||||
div2.id = "foo3";
|
||||
frag.appendChild(div2);
|
||||
|
||||
return frag;
|
||||
}
|
||||
|
||||
function testError() {
|
||||
try {
|
||||
window.foobar("a");
|
||||
} catch (ex) {
|
||||
return ex;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function testDOMException() {
|
||||
try {
|
||||
var foo = document.querySelector("foo;()bar!");
|
||||
} catch (ex) {
|
||||
return ex;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function testCSSStyleDeclaration() {
|
||||
document.body.style = 'color: green; font-size: 2em';
|
||||
return document.body.style;
|
||||
}
|
||||
|
||||
function testStyleSheetList() {
|
||||
var style = document.querySelector("style");
|
||||
if (!style) {
|
||||
style = document.createElement("style");
|
||||
style.textContent = "p, div { color: blue; font-weight: bold }\n" +
|
||||
"@media print { p { background-color: yellow } }";
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
return document.styleSheets;
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,23 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html dir="ltr" lang="en-US">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Test the web console output for DOM events</title>
|
||||
<!--
|
||||
- Any copyright is dedicated to the Public Domain.
|
||||
- http://creativecommons.org/publicdomain/zero/1.0/
|
||||
-->
|
||||
</head>
|
||||
<body>
|
||||
<p>hello world!</p>
|
||||
<script type="text/javascript">
|
||||
function testDOMEvents() {
|
||||
function eventLogger(ev) {
|
||||
console.log("eventLogger", ev);
|
||||
}
|
||||
document.addEventListener("mousemove", eventLogger);
|
||||
document.addEventListener("keypress", eventLogger);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -1183,10 +1183,6 @@ WebConsoleFrame.prototype = {
|
|||
let clipboardArray = [];
|
||||
args.forEach((aValue) => {
|
||||
clipboardArray.push(VariablesView.getString(aValue));
|
||||
if (aValue && typeof aValue == "object" &&
|
||||
aValue.type == "longString") {
|
||||
clipboardArray.push(l10n.getStr("longStringEllipsis"));
|
||||
}
|
||||
});
|
||||
clipboardText = clipboardArray.join(" ");
|
||||
break;
|
||||
|
@ -3103,7 +3099,7 @@ JSTerm.prototype = {
|
|||
aAfterMessage._objectActors.add(helperResult.object.actor);
|
||||
}
|
||||
this.openVariablesView({
|
||||
label: VariablesView.getString(helperResult.object),
|
||||
label: VariablesView.getString(helperResult.object, { concise: true }),
|
||||
objectActor: helperResult.object,
|
||||
});
|
||||
break;
|
||||
|
@ -4265,6 +4261,7 @@ JSTerm.prototype = {
|
|||
popup.selectNextItem();
|
||||
}
|
||||
|
||||
this.emit("autocomplete-updated");
|
||||
aCallback && aCallback(this);
|
||||
},
|
||||
|
||||
|
|
|
@ -202,8 +202,8 @@ loadingText=Loading\u2026
|
|||
# viewer when there is an error loading a file
|
||||
errorLoadingText=Error loading source:\n
|
||||
|
||||
# LOCALIZATION NOTE (emptyStackText): The text that is displayed in the watch
|
||||
# expressions list to add a new item.
|
||||
# LOCALIZATION NOTE (addWatchExpressionText): The text that is displayed in the
|
||||
# watch expressions list to add a new item.
|
||||
addWatchExpressionText=Add watch expression
|
||||
|
||||
# LOCALIZATION NOTE (emptyVariablesText): The text that is displayed in the
|
||||
|
@ -225,6 +225,19 @@ watchExpressionsScopeLabel=Watch expressions
|
|||
# the global scope.
|
||||
globalScopeLabel=Global
|
||||
|
||||
# LOCALIZATION NOTE (variablesViewErrorStacktrace): This is the text that is
|
||||
# shown before the stack trace in an error.
|
||||
variablesViewErrorStacktrace=Stack trace:
|
||||
|
||||
# LOCALIZATION NOTE (variablesViewMoreObjects): the text that is displayed
|
||||
# when you have an object preview that does not show all of the elements. At the end of the list
|
||||
# you see "N more..." in the web console output.
|
||||
# This is a semi-colon list of plural forms.
|
||||
# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
|
||||
# #1 number of remaining items in the object
|
||||
# example: 3 more…
|
||||
variablesViewMoreObjects=#1 more…;#1 more…
|
||||
|
||||
# LOCALIZATION NOTE (variablesEditableNameTooltip): The text that is displayed
|
||||
# in the variables list on an item with an editable name.
|
||||
variablesEditableNameTooltip=Double click to edit
|
||||
|
|
|
@ -100,11 +100,26 @@ XPCOMUtils.defineLazyGetter(this, "DEFAULT_ITEMS", function() {
|
|||
return result;
|
||||
});
|
||||
|
||||
const ALL_BUILTIN_ITEMS = [
|
||||
"fullscreen-button",
|
||||
"switch-to-metro-button",
|
||||
"bookmarks-menu-button",
|
||||
];
|
||||
XPCOMUtils.defineLazyGetter(this, "ALL_BUILTIN_ITEMS", function() {
|
||||
// These special cases are for click events on built-in items that are
|
||||
// contained within customizable items (like the navigation widget).
|
||||
const SPECIAL_CASES = [
|
||||
"back-button",
|
||||
"forward-button",
|
||||
"urlbar-stop-button",
|
||||
"urlbar-go-button",
|
||||
"urlbar-reload-button",
|
||||
"searchbar",
|
||||
"cut-button",
|
||||
"copy-button",
|
||||
"paste-button",
|
||||
"zoom-out-button",
|
||||
"zoom-reset-button",
|
||||
"zoom-in-button",
|
||||
]
|
||||
return DEFAULT_ITEMS.concat(PALETTE_ITEMS)
|
||||
.concat(SPECIAL_CASES);
|
||||
});
|
||||
|
||||
const OTHER_MOUSEUP_MONITORED_ITEMS = [
|
||||
"PlacesChevron",
|
||||
|
@ -318,6 +333,14 @@ this.BrowserUITelemetry = {
|
|||
// Base case - we clicked directly on one of our built-in items,
|
||||
// and we can go ahead and register that click.
|
||||
this._countMouseUpEvent("click-builtin-item", item.id, aEvent.button);
|
||||
return;
|
||||
}
|
||||
|
||||
// If not, we need to check if one of the ancestors of the clicked
|
||||
// item is in our list of built-in items to check.
|
||||
let candidate = getIDBasedOnFirstIDedAncestor(item);
|
||||
if (ALL_BUILTIN_ITEMS.indexOf(candidate) != -1) {
|
||||
this._countMouseUpEvent("click-builtin-item", candidate, aEvent.button);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -396,4 +419,4 @@ function getIDBasedOnFirstIDedAncestor(aNode) {
|
|||
}
|
||||
|
||||
return aNode.id;
|
||||
}
|
||||
}
|
|
@ -51,7 +51,7 @@
|
|||
display: none;
|
||||
}
|
||||
|
||||
.devtools-toolbarbutton:not([checked=true]):hover:active {
|
||||
.devtools-toolbarbutton:not([checked]):hover:active {
|
||||
border-color: hsla(210,8%,5%,.6);
|
||||
background: linear-gradient(hsla(220,6%,10%,.3), hsla(212,7%,57%,.15) 65%, hsla(212,7%,57%,.3));
|
||||
box-shadow: 0 0 3px hsla(210,8%,5%,.25) inset, 0 1px 3px hsla(210,8%,5%,.25) inset, 0 1px 0 hsla(210,16%,76%,.15);
|
||||
|
@ -282,28 +282,28 @@
|
|||
background-image: linear-gradient(hsla(206,37%,4%,.4), hsla(206,37%,4%,.4)), @smallSeparator@;
|
||||
}
|
||||
|
||||
.devtools-sidebar-tabs > tabs > tab[selected=true] + tab {
|
||||
.devtools-sidebar-tabs > tabs > tab[selected] + tab {
|
||||
background-image: linear-gradient(transparent, transparent), @solidSeparator@;
|
||||
}
|
||||
|
||||
.devtools-sidebar-tabs > tabs > tab[selected=true] + tab:hover {
|
||||
.devtools-sidebar-tabs > tabs > tab[selected] + tab:hover {
|
||||
background-image: linear-gradient(hsla(206,37%,4%,.2), hsla(206,37%,4%,.2)), @solidSeparator@;
|
||||
}
|
||||
|
||||
.devtools-sidebar-tabs > tabs > tab[selected=true] + tab:hover:active {
|
||||
.devtools-sidebar-tabs > tabs > tab[selected] + tab:hover:active {
|
||||
background-image: linear-gradient(hsla(206,37%,4%,.4), hsla(206,37%,4%,.4)), @solidSeparator@;
|
||||
}
|
||||
|
||||
.devtools-sidebar-tabs > tabs > tab[selected=true] {
|
||||
.devtools-sidebar-tabs > tabs > tab[selected] {
|
||||
color: #f5f7fa;
|
||||
background-image: linear-gradient(#1d4f73, #1d4f73), @solidSeparator@;
|
||||
}
|
||||
|
||||
.devtools-sidebar-tabs > tabs > tab[selected=true]:hover {
|
||||
.devtools-sidebar-tabs > tabs > tab[selected]:hover {
|
||||
background-image: linear-gradient(#274f64, #274f64), @solidSeparator@;
|
||||
}
|
||||
|
||||
.devtools-sidebar-tabs > tabs > tab[selected=true]:hover:active {
|
||||
.devtools-sidebar-tabs > tabs > tab[selected]:hover:active {
|
||||
background-image: linear-gradient(#1f3e4f, #1f3e4f), @solidSeparator@;
|
||||
}
|
||||
|
||||
|
@ -520,7 +520,7 @@
|
|||
}
|
||||
|
||||
.devtools-tab:active > image,
|
||||
.devtools-tab[selected=true] > image {
|
||||
.devtools-tab[selected] > image {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
|
@ -534,7 +534,7 @@
|
|||
color: #f5f7fa;
|
||||
}
|
||||
|
||||
#toolbox-tabs .devtools-tab[selected=true] {
|
||||
#toolbox-tabs .devtools-tab[selected] {
|
||||
color: #f5f7fa;
|
||||
background-color: #1a4666;
|
||||
box-shadow: 0 2px 0 #d7f1ff inset,
|
||||
|
@ -542,32 +542,32 @@
|
|||
0 -2px 0 rgba(0,0,0,.2) inset;
|
||||
}
|
||||
|
||||
.devtools-tab[selected=true]:not(:first-child),
|
||||
.devtools-tab.highlighted:not(:first-child) {
|
||||
.devtools-tab[selected]:not(:first-child),
|
||||
.devtools-tab[highlighted]:not(:first-child) {
|
||||
border-width: 0;
|
||||
-moz-padding-start: 1px;
|
||||
}
|
||||
|
||||
.devtools-tab[selected=true]:last-child,
|
||||
.devtools-tab.highlighted:last-child {
|
||||
.devtools-tab[selected]:last-child,
|
||||
.devtools-tab[highlighted]:last-child {
|
||||
-moz-padding-end: 1px;
|
||||
}
|
||||
|
||||
.devtools-tab[selected=true] + .devtools-tab,
|
||||
.devtools-tab.highlighted + .devtools-tab {
|
||||
.devtools-tab[selected] + .devtools-tab,
|
||||
.devtools-tab[highlighted] + .devtools-tab {
|
||||
-moz-border-start-width: 0;
|
||||
-moz-padding-start: 1px;
|
||||
}
|
||||
|
||||
.devtools-tab:not([selected=true]).highlighted {
|
||||
.devtools-tab:not([selected])[highlighted] {
|
||||
color: #f5f7fa;
|
||||
background-color: hsla(99,100%,14%,.2);
|
||||
box-shadow: 0 2px 0 #7bc107 inset;
|
||||
}
|
||||
|
||||
.devtools-tab:not(.highlighted) > .highlighted-icon,
|
||||
.devtools-tab[selected=true] > .highlighted-icon,
|
||||
.devtools-tab:not([selected=true]).highlighted > .default-icon {
|
||||
.devtools-tab:not([highlighted]) > .highlighted-icon,
|
||||
.devtools-tab[selected] > .highlighted-icon,
|
||||
.devtools-tab:not([selected])[highlighted] > .default-icon {
|
||||
visibility: collapse;
|
||||
}
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ interface nsIMarkupDocumentViewer;
|
|||
|
||||
[ref] native nsIMarkupDocumentViewerTArray(nsTArray<nsCOMPtr<nsIMarkupDocumentViewer> >);
|
||||
|
||||
[scriptable, uuid(3528324f-f5d3-4724-bd8d-9233a7114112)]
|
||||
[scriptable, uuid(7aea9561-5346-401c-b40e-418688da2d0d)]
|
||||
interface nsIMarkupDocumentViewer : nsISupports
|
||||
{
|
||||
|
||||
|
@ -82,6 +82,18 @@ interface nsIMarkupDocumentViewer : nsISupports
|
|||
*/
|
||||
void changeMaxLineBoxWidth(in int32_t maxLineBoxWidth);
|
||||
|
||||
/**
|
||||
* Instruct the refresh driver to discontinue painting until further
|
||||
* notice.
|
||||
*/
|
||||
void pausePainting();
|
||||
|
||||
/**
|
||||
* Instruct the refresh driver to resume painting after a previous call to
|
||||
* pausePainting().
|
||||
*/
|
||||
void resumePainting();
|
||||
|
||||
/*
|
||||
* Render the document as if being viewed on a device with the specified
|
||||
* media type. This will cause a reflow.
|
||||
|
|
|
@ -2700,6 +2700,17 @@ struct LineBoxInfo
|
|||
nscoord mMaxLineBoxWidth;
|
||||
};
|
||||
|
||||
static void
|
||||
ChangeChildPaintingEnabled(nsIMarkupDocumentViewer* aChild, void* aClosure)
|
||||
{
|
||||
bool* enablePainting = (bool*) aClosure;
|
||||
if (*enablePainting) {
|
||||
aChild->ResumePainting();
|
||||
} else {
|
||||
aChild->PausePainting();
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
ChangeChildMaxLineBoxWidth(nsIMarkupDocumentViewer* aChild, void* aClosure)
|
||||
{
|
||||
|
@ -3126,7 +3137,36 @@ NS_IMETHODIMP nsDocumentViewer::AppendSubtree(nsTArray<nsCOMPtr<nsIMarkupDocumen
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP nsDocumentViewer::ChangeMaxLineBoxWidth(int32_t aMaxLineBoxWidth)
|
||||
NS_IMETHODIMP
|
||||
nsDocumentViewer::PausePainting()
|
||||
{
|
||||
bool enablePaint = false;
|
||||
CallChildren(ChangeChildPaintingEnabled, &enablePaint);
|
||||
|
||||
nsIPresShell* presShell = GetPresShell();
|
||||
if (presShell) {
|
||||
presShell->PausePainting();
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsDocumentViewer::ResumePainting()
|
||||
{
|
||||
bool enablePaint = true;
|
||||
CallChildren(ChangeChildPaintingEnabled, &enablePaint);
|
||||
|
||||
nsIPresShell* presShell = GetPresShell();
|
||||
if (presShell) {
|
||||
presShell->ResumePainting();
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsDocumentViewer::ChangeMaxLineBoxWidth(int32_t aMaxLineBoxWidth)
|
||||
{
|
||||
// Change the max line box width for all children.
|
||||
struct LineBoxInfo lbi = { aMaxLineBoxWidth };
|
||||
|
|
|
@ -129,10 +129,10 @@ typedef struct CapturingContentInfo {
|
|||
} CapturingContentInfo;
|
||||
|
||||
|
||||
// f5b542a9-eaf0-4560-a656-37a9d379864c
|
||||
// 0e4f2b36-7ab8-43c5-b912-5c311566297c
|
||||
#define NS_IPRESSHELL_IID \
|
||||
{ 0xf5b542a9, 0xeaf0, 0x4560, \
|
||||
{ 0x37, 0xa9, 0xd3, 0x79, 0x86, 0x4c } }
|
||||
{ 0xde498c49, 0xf83f, 0x47bf, \
|
||||
{0x8c, 0xc6, 0x8f, 0xf8, 0x74, 0x62, 0x22, 0x23 } }
|
||||
|
||||
// debug VerifyReflow flags
|
||||
#define VERIFY_REFLOW_ON 0x01
|
||||
|
@ -836,6 +836,20 @@ public:
|
|||
*/
|
||||
bool IsPaintingSuppressed() const { return mPaintingSuppressed; }
|
||||
|
||||
/**
|
||||
* Pause painting by freezing the refresh driver of this and all parent
|
||||
* presentations. This may not have the desired effect if this pres shell
|
||||
* has its own refresh driver.
|
||||
*/
|
||||
virtual void PausePainting() = 0;
|
||||
|
||||
/**
|
||||
* Resume painting by thawing the refresh driver of this and all parent
|
||||
* presentations. This may not have the desired effect if this pres shell
|
||||
* has its own refresh driver.
|
||||
*/
|
||||
virtual void ResumePainting() = 0;
|
||||
|
||||
/**
|
||||
* Unsuppress painting.
|
||||
*/
|
||||
|
@ -1601,6 +1615,7 @@ protected:
|
|||
bool mFontSizeInflationForceEnabled;
|
||||
bool mFontSizeInflationDisabledInMasterProcess;
|
||||
bool mFontSizeInflationEnabled;
|
||||
bool mPaintingIsFrozen;
|
||||
|
||||
// Dirty bit indicating that mFontSizeInflationEnabled needs to be recomputed.
|
||||
bool mFontSizeInflationEnabledIsDirty;
|
||||
|
|
|
@ -724,6 +724,8 @@ PresShell::PresShell()
|
|||
"layout.reflow.synthMouseMove", true);
|
||||
addedSynthMouseMove = true;
|
||||
}
|
||||
|
||||
mPaintingIsFrozen = false;
|
||||
}
|
||||
|
||||
NS_IMPL_ISUPPORTS7(PresShell, nsIPresShell, nsIDocumentObserver,
|
||||
|
@ -744,6 +746,13 @@ PresShell::~PresShell()
|
|||
mLastCallbackEventRequest == nullptr,
|
||||
"post-reflow queues not empty. This means we're leaking");
|
||||
|
||||
// Verify that if painting was frozen, but we're being removed from the tree,
|
||||
// that we now re-enable painting on our refresh driver, since it may need to
|
||||
// be re-used by another presentation.
|
||||
if (mPaintingIsFrozen) {
|
||||
mPresContext->RefreshDriver()->Thaw();
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
MOZ_ASSERT(mPresArenaAllocCount == 0,
|
||||
"Some pres arena objects were not freed");
|
||||
|
@ -9931,3 +9940,23 @@ nsIPresShell::SetMaxLineBoxWidth(nscoord aMaxLineBoxWidth)
|
|||
FrameNeedsReflow(GetRootFrame(), eResize, NS_FRAME_HAS_DIRTY_CHILDREN);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
PresShell::PausePainting()
|
||||
{
|
||||
if (GetPresContext()->RefreshDriver()->PresContext() != GetPresContext())
|
||||
return;
|
||||
|
||||
mPaintingIsFrozen = true;
|
||||
GetPresContext()->RefreshDriver()->Freeze();
|
||||
}
|
||||
|
||||
void
|
||||
PresShell::ResumePainting()
|
||||
{
|
||||
if (GetPresContext()->RefreshDriver()->PresContext() != GetPresContext())
|
||||
return;
|
||||
|
||||
mPaintingIsFrozen = false;
|
||||
GetPresContext()->RefreshDriver()->Thaw();
|
||||
}
|
||||
|
|
|
@ -689,6 +689,9 @@ protected:
|
|||
virtual void ThemeChanged() MOZ_OVERRIDE { mPresContext->ThemeChanged(); }
|
||||
virtual void BackingScaleFactorChanged() MOZ_OVERRIDE { mPresContext->UIResolutionChanged(); }
|
||||
|
||||
virtual void PausePainting() MOZ_OVERRIDE;
|
||||
virtual void ResumePainting() MOZ_OVERRIDE;
|
||||
|
||||
void UpdateImageVisibility();
|
||||
|
||||
nsRevocableEventPtr<nsRunnableMethod<PresShell> > mUpdateImageVisibilityEvent;
|
||||
|
|
|
@ -66,6 +66,7 @@ final public class InputMethods {
|
|||
@RobocopTarget
|
||||
public static boolean shouldDisableUrlBarUpdate(Context context) {
|
||||
String inputMethod = getCurrentInputMethod(context);
|
||||
// HTC Touch Input does not react well to restarting during input (bug 909940)
|
||||
return METHOD_HTC_TOUCH_INPUT.equals(inputMethod);
|
||||
}
|
||||
|
||||
|
|
|
@ -245,12 +245,16 @@ class JavaPanZoomController
|
|||
final RectF zoomRect = new RectF(x, y,
|
||||
x + (float)message.getDouble("w"),
|
||||
y + (float)message.getDouble("h"));
|
||||
mTarget.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
animatedZoomTo(zoomRect);
|
||||
}
|
||||
});
|
||||
if (message.optBoolean("animate", true)) {
|
||||
mTarget.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
animatedZoomTo(zoomRect);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
mTarget.setViewportMetrics(getMetricsToZoomTo(zoomRect));
|
||||
}
|
||||
} else if (MESSAGE_ZOOM_PAGE.equals(event)) {
|
||||
ImmutableViewportMetrics metrics = getMetrics();
|
||||
RectF cssPageRect = metrics.getCssPageRect();
|
||||
|
@ -264,12 +268,16 @@ class JavaPanZoomController
|
|||
y + dh/2,
|
||||
cssPageRect.width(),
|
||||
y + dh/2 + newHeight);
|
||||
mTarget.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
animatedZoomTo(r);
|
||||
}
|
||||
});
|
||||
if (message.optBoolean("animate", true)) {
|
||||
mTarget.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
animatedZoomTo(r);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
mTarget.setViewportMetrics(getMetricsToZoomTo(r));
|
||||
}
|
||||
} else if (MESSAGE_TOUCH_LISTENER.equals(event)) {
|
||||
int tabId = message.getInt("tabID");
|
||||
final Tab tab = Tabs.getInstance().getTab(tabId);
|
||||
|
@ -1399,7 +1407,7 @@ class JavaPanZoomController
|
|||
* While we usually use device pixels, @zoomToRect must be specified in CSS
|
||||
* pixels.
|
||||
*/
|
||||
private boolean animatedZoomTo(RectF zoomToRect) {
|
||||
private ImmutableViewportMetrics getMetricsToZoomTo(RectF zoomToRect) {
|
||||
final float startZoom = getMetrics().zoomFactor;
|
||||
|
||||
RectF viewport = getMetrics().getViewport();
|
||||
|
@ -1434,8 +1442,11 @@ class JavaPanZoomController
|
|||
// 2. now run getValidViewportMetrics on it, so that the target viewport is
|
||||
// clamped down to prevent overscroll, over-zoom, and other bad conditions.
|
||||
finalMetrics = getValidViewportMetrics(finalMetrics);
|
||||
return finalMetrics;
|
||||
}
|
||||
|
||||
bounce(finalMetrics, PanZoomState.ANIMATED_ZOOM);
|
||||
private boolean animatedZoomTo(RectF zoomToRect) {
|
||||
bounce(getMetricsToZoomTo(zoomToRect), PanZoomState.ANIMATED_ZOOM);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -73,6 +73,9 @@
|
|||
<!ENTITY pref_category_datareporting "Data choices">
|
||||
<!ENTITY pref_category_installed_search_engines "Installed search engines">
|
||||
<!ENTITY pref_category_add_search_providers "Add more search providers">
|
||||
<!ENTITY pref_category_search_restore_defaults "Restore search engines">
|
||||
<!ENTITY pref_search_restore_defaults "Restore defaults">
|
||||
<!ENTITY pref_search_restore_defaults_summary "Restore defaults">
|
||||
<!-- Localization note (pref_search_tip) : "TIP" as in "hint", "clue" etc. Displayed as an
|
||||
advisory message on the customise search providers settings page explaining how to add new
|
||||
search providers.-->
|
||||
|
|
|
@ -7,6 +7,13 @@ package org.mozilla.gecko.menu;
|
|||
import org.mozilla.gecko.R;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.PorterDuffColorFilter;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.ViewGroup;
|
||||
|
@ -16,6 +23,15 @@ public class MenuItemActionBar extends ImageButton
|
|||
implements GeckoMenuItem.Layout {
|
||||
private static final String LOGTAG = "GeckoMenuItemActionBar";
|
||||
|
||||
private static Bitmap sMoreIcon;
|
||||
private static float sHalfIconWidth;
|
||||
private static float sMoreWidth;
|
||||
private static int sMoreOffset;
|
||||
private static Paint sDisabledPaint;
|
||||
|
||||
private Drawable mIcon;
|
||||
private boolean mHasSubMenu = false;
|
||||
|
||||
public MenuItemActionBar(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
@ -26,6 +42,55 @@ public class MenuItemActionBar extends ImageButton
|
|||
|
||||
public MenuItemActionBar(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
|
||||
if (sMoreIcon == null) {
|
||||
final Resources res = getResources();
|
||||
|
||||
BitmapDrawable drawable = (BitmapDrawable) res.getDrawable(R.drawable.menu_item_more);
|
||||
sMoreIcon = drawable.getBitmap();
|
||||
|
||||
// The icon has some space on the right. Taking half the size feels better.
|
||||
sMoreWidth = getResources().getDimensionPixelSize(R.dimen.menu_item_state_icon) / 2.0f;
|
||||
sMoreOffset = res.getDimensionPixelSize(R.dimen.menu_item_more_offset);
|
||||
|
||||
final int rowHeight = res.getDimensionPixelSize(R.dimen.menu_item_row_height);
|
||||
final int padding = getPaddingTop() + getPaddingBottom();
|
||||
sHalfIconWidth = (rowHeight - padding) / 2.0f;
|
||||
|
||||
sDisabledPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
sDisabledPaint.setColorFilter(new PorterDuffColorFilter(0xFF999999, PorterDuff.Mode.SRC_ATOP));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
if (!mHasSubMenu) {
|
||||
super.onDraw(canvas);
|
||||
return;
|
||||
}
|
||||
|
||||
final int count = canvas.save();
|
||||
|
||||
final float halfWidth = getMeasuredWidth() / 2.0f;
|
||||
final float halfHeight = getMeasuredHeight() / 2.0f;
|
||||
|
||||
// If the width is small, the more icon might be pushed to the edges.
|
||||
// Instead translate the canvas, so that both the icon + more is centered as a whole.
|
||||
final boolean needsTranslation = (halfWidth < 1.5 * halfHeight);
|
||||
final float translateX = needsTranslation ? (sMoreOffset + sMoreWidth) / 2.0f : 0.0f;
|
||||
|
||||
canvas.translate(-translateX, 0);
|
||||
|
||||
super.onDraw(canvas);
|
||||
|
||||
final float left = halfWidth + sHalfIconWidth + sMoreOffset - translateX;
|
||||
final float top = halfHeight - sMoreWidth;
|
||||
|
||||
canvas.drawBitmap(sMoreIcon, left, top, isEnabled() ? null : sDisabledPaint);
|
||||
|
||||
canvas.translate(translateX, 0);
|
||||
|
||||
canvas.restoreToCount(count);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -37,24 +102,22 @@ public class MenuItemActionBar extends ImageButton
|
|||
setTitle(item.getTitle());
|
||||
setEnabled(item.isEnabled());
|
||||
setId(item.getItemId());
|
||||
setSubMenuIndicator(item.hasSubMenu());
|
||||
}
|
||||
|
||||
void setIcon(Drawable icon) {
|
||||
if (icon != null) {
|
||||
setImageDrawable(icon);
|
||||
setVisibility(VISIBLE);
|
||||
} else {
|
||||
mIcon = icon;
|
||||
|
||||
if (icon == null) {
|
||||
setVisibility(GONE);
|
||||
} else {
|
||||
setVisibility(VISIBLE);
|
||||
setImageDrawable(icon);
|
||||
}
|
||||
}
|
||||
|
||||
void setIcon(int icon) {
|
||||
if (icon != 0) {
|
||||
setImageResource(icon);
|
||||
setVisibility(VISIBLE);
|
||||
} else {
|
||||
setVisibility(GONE);
|
||||
}
|
||||
setIcon((icon == 0) ? null : getResources().getDrawable(icon));
|
||||
}
|
||||
|
||||
void setTitle(CharSequence title) {
|
||||
|
@ -72,4 +135,11 @@ public class MenuItemActionBar extends ImageButton
|
|||
public void setShowIcon(boolean show) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
private void setSubMenuIndicator(boolean hasSubMenu) {
|
||||
if (mHasSubMenu != hasSubMenu) {
|
||||
mHasSubMenu = hasSubMenu;
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -68,26 +68,11 @@ public class MenuItemActionView extends LinearLayout
|
|||
if (item == null)
|
||||
return;
|
||||
|
||||
setTitle(item.getTitle());
|
||||
setIcon(item.getIcon());
|
||||
mMenuItem.initialize(item);
|
||||
mMenuButton.initialize(item);
|
||||
setEnabled(item.isEnabled());
|
||||
}
|
||||
|
||||
private void setIcon(Drawable icon) {
|
||||
mMenuItem.setIcon(icon);
|
||||
mMenuButton.setIcon(icon);
|
||||
}
|
||||
|
||||
private void setIcon(int icon) {
|
||||
mMenuItem.setIcon(icon);
|
||||
mMenuButton.setIcon(icon);
|
||||
}
|
||||
|
||||
private void setTitle(CharSequence title) {
|
||||
mMenuItem.setTitle(title);
|
||||
mMenuButton.setTitle(title);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnabled(boolean enabled) {
|
||||
super.setEnabled(enabled);
|
||||
|
|
|
@ -102,12 +102,7 @@ public class MenuItemDefault extends TextView
|
|||
}
|
||||
|
||||
void setIcon(int icon) {
|
||||
Drawable drawable = null;
|
||||
|
||||
if (icon != 0)
|
||||
drawable = getResources().getDrawable(icon);
|
||||
|
||||
setIcon(drawable);
|
||||
setIcon((icon == 0) ? null : getResources().getDrawable(icon));
|
||||
}
|
||||
|
||||
void setTitle(CharSequence title) {
|
||||
|
|
|
@ -5,9 +5,12 @@
|
|||
|
||||
package org.mozilla.gecko.preferences;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.PrefsHelper;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.preference.Preference;
|
||||
import android.preference.PreferenceActivity;
|
||||
import android.preference.PreferenceCategory;
|
||||
|
@ -15,6 +18,10 @@ import android.preference.PreferenceFragment;
|
|||
import android.preference.PreferenceScreen;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.ViewConfiguration;
|
||||
|
||||
/* A simple implementation of PreferenceFragment for large screen devices
|
||||
* This will strip category headers (so that they aren't shown to the user twice)
|
||||
|
@ -28,23 +35,14 @@ public class GeckoPreferenceFragment extends PreferenceFragment {
|
|||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
String resourceName = getArguments().getString("resource");
|
||||
|
||||
int res = 0;
|
||||
if (resourceName != null) {
|
||||
// Fetch resource id by resource name.
|
||||
res = getActivity().getResources().getIdentifier(resourceName,
|
||||
"xml",
|
||||
getActivity().getPackageName());
|
||||
int res = getResource();
|
||||
|
||||
// Display a menu for Search preferences.
|
||||
if (res == R.xml.preferences_search) {
|
||||
setHasOptionsMenu(true);
|
||||
}
|
||||
|
||||
if (res == 0) {
|
||||
// The resource was invalid. Use the default resource.
|
||||
Log.e(LOGTAG, "Failed to find resource: " + resourceName + ". Displaying default settings.");
|
||||
|
||||
boolean isMultiPane = ((PreferenceActivity) getActivity()).onIsMultiPane();
|
||||
res = isMultiPane ? R.xml.preferences_customize_tablet : R.xml.preferences;
|
||||
}
|
||||
addPreferencesFromResource(res);
|
||||
|
||||
PreferenceScreen screen = getPreferenceScreen();
|
||||
|
@ -52,6 +50,39 @@ public class GeckoPreferenceFragment extends PreferenceFragment {
|
|||
mPrefsRequestId = ((GeckoPreferences)getActivity()).setupPreferences(screen);
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the resource from Fragment arguments and return it.
|
||||
*
|
||||
* If no resource can be found, return the resource id of the default preference screen.
|
||||
*/
|
||||
private int getResource() {
|
||||
int resid = 0;
|
||||
|
||||
String resourceName = getArguments().getString("resource");
|
||||
if (resourceName != null) {
|
||||
// Fetch resource id by resource name.
|
||||
resid = getActivity().getResources().getIdentifier(resourceName,
|
||||
"xml",
|
||||
getActivity().getPackageName());
|
||||
}
|
||||
|
||||
if (resid == 0) {
|
||||
// The resource was invalid. Use the default resource.
|
||||
Log.e(LOGTAG, "Failed to find resource: " + resourceName + ". Displaying default settings.");
|
||||
|
||||
boolean isMultiPane = ((PreferenceActivity) getActivity()).onIsMultiPane();
|
||||
resid = isMultiPane ? R.xml.preferences_customize_tablet : R.xml.preferences;
|
||||
}
|
||||
|
||||
return resid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
super.onCreateOptionsMenu(menu, inflater);
|
||||
inflater.inflate(R.menu.preferences_search_menu, menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
|
@ -59,4 +90,29 @@ public class GeckoPreferenceFragment extends PreferenceFragment {
|
|||
PrefsHelper.removeObserver(mPrefsRequestId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity) {
|
||||
super.onAttach(activity);
|
||||
showOverflowMenu(activity);
|
||||
}
|
||||
|
||||
/*
|
||||
* Force the overflow 3-dot menu to be displayed if it isn't already displayed.
|
||||
*
|
||||
* This is an ugly hack for 4.0+ Android devices that don't have a dedicated menu button
|
||||
* because Android does not provide a public API to display the ActionBar overflow menu.
|
||||
*/
|
||||
private void showOverflowMenu(Activity activity) {
|
||||
try {
|
||||
ViewConfiguration config = ViewConfiguration.get(activity);
|
||||
Field menuOverflow = ViewConfiguration.class.getDeclaredField("sHasPermanentMenuKey");
|
||||
if (menuOverflow != null) {
|
||||
menuOverflow.setAccessible(true);
|
||||
menuOverflow.setBoolean(config, false);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.d(LOGTAG, "Failed to force overflow menu, ignoring.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -77,7 +77,9 @@ public class GeckoPreferences
|
|||
private boolean mInitialized = false;
|
||||
private int mPrefsRequestId = 0;
|
||||
|
||||
// These match keys in resources/xml/preferences.xml.in.
|
||||
// These match keys in resources/xml*/preferences*.xml
|
||||
private static String PREFS_SEARCH_RESTORE_DEFAULTS = NON_PREF_PREFIX + "search.restore_defaults";
|
||||
|
||||
private static String PREFS_ANNOUNCEMENTS_ENABLED = NON_PREF_PREFIX + "privacy.announcements.enabled";
|
||||
private static String PREFS_DATA_REPORTING_PREFERENCES = NON_PREF_PREFIX + "datareporting.preferences";
|
||||
private static String PREFS_TELEMETRY_ENABLED = "datareporting.telemetry.enabled";
|
||||
|
@ -389,6 +391,14 @@ public class GeckoPreferences
|
|||
preferences.removePreference(pref);
|
||||
i--;
|
||||
continue;
|
||||
} else if (PREFS_SEARCH_RESTORE_DEFAULTS.equals(key)) {
|
||||
pref.setOnPreferenceClickListener(new OnPreferenceClickListener() {
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
GeckoPreferences.this.restoreDefaultSearchEngines();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Some Preference UI elements are not actually preferences,
|
||||
|
@ -404,14 +414,31 @@ public class GeckoPreferences
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore default search engines in Gecko and retrigger a search engine refresh.
|
||||
*/
|
||||
protected void restoreDefaultSearchEngines() {
|
||||
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("SearchEngines:RestoreDefaults", null));
|
||||
|
||||
// Send message to Gecko to get engines. SearchPreferenceCategory listens for the response.
|
||||
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("SearchEngines:GetVisible", null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
int itemId = item.getItemId();
|
||||
switch (itemId) {
|
||||
case android.R.id.home:
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Generated R.id.* apparently aren't constant expressions, so they can't be switched.
|
||||
if (itemId == R.id.restore_defaults) {
|
||||
restoreDefaultSearchEngines();
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
|
|
|
@ -40,8 +40,6 @@ public class SearchEnginePreference extends Preference implements View.OnLongCli
|
|||
|
||||
// Specifies if this engine is configured as the default search engine.
|
||||
private boolean mIsDefaultEngine;
|
||||
// Specifies if this engine is one of the ones bundled with the app, which cannot be deleted.
|
||||
private boolean mIsImmutableEngine;
|
||||
|
||||
// Dialog element labels.
|
||||
private String[] mDialogItems;
|
||||
|
@ -121,12 +119,7 @@ public class SearchEnginePreference extends Preference implements View.OnLongCli
|
|||
public void setSearchEngineFromJSON(JSONObject geckoEngineJSON) throws JSONException {
|
||||
final String engineName = geckoEngineJSON.getString("name");
|
||||
final SpannableString titleSpannable = new SpannableString(engineName);
|
||||
mIsImmutableEngine = geckoEngineJSON.getBoolean("immutable");
|
||||
|
||||
if (mIsImmutableEngine) {
|
||||
// Delete the "Remove" option from the menu.
|
||||
mDialogItems = new String[] { getContext().getResources().getString(R.string.pref_search_set_default) };
|
||||
}
|
||||
setTitle(titleSpannable);
|
||||
|
||||
final String iconURI = geckoEngineJSON.getString("iconURI");
|
||||
|
@ -176,11 +169,6 @@ public class SearchEnginePreference extends Preference implements View.OnLongCli
|
|||
return;
|
||||
}
|
||||
|
||||
// If we are both default and immutable, we have no enabled items to show on the menu - abort.
|
||||
if (mIsDefaultEngine && mIsImmutableEngine) {
|
||||
return;
|
||||
}
|
||||
|
||||
final AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
|
||||
builder.setTitle(getTitle().toString());
|
||||
builder.setItems(mDialogItems, new DialogInterface.OnClickListener() {
|
||||
|
|
|
@ -43,18 +43,19 @@ public class SearchPreferenceCategory extends PreferenceCategory implements Geck
|
|||
// Ensures default engine remains at top of list.
|
||||
setOrderingAsAdded(true);
|
||||
|
||||
// Request list of search engines from Gecko.
|
||||
// Register for SearchEngines messages and request list of search engines from Gecko.
|
||||
GeckoAppShell.registerEventListener("SearchEngines:Data", this);
|
||||
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("SearchEngines:Get", null));
|
||||
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("SearchEngines:GetVisible", null));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPrepareForRemoval() {
|
||||
GeckoAppShell.unregisterEventListener("SearchEngines:Data", this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(String event, final JSONObject data) {
|
||||
if (event.equals("SearchEngines:Data")) {
|
||||
// We are no longer interested in this event from Gecko, as we do not request it again with
|
||||
// this instance.
|
||||
GeckoAppShell.unregisterEventListener("SearchEngines:Data", this);
|
||||
|
||||
// Parse engines array from JSON.
|
||||
JSONArray engines;
|
||||
try {
|
||||
|
@ -64,6 +65,9 @@ public class SearchPreferenceCategory extends PreferenceCategory implements Geck
|
|||
return;
|
||||
}
|
||||
|
||||
// Clear the preferences category from this thread.
|
||||
this.removeAll();
|
||||
|
||||
// Create an element in this PreferenceCategory for each engine.
|
||||
for (int i = 0; i < engines.length(); i++) {
|
||||
try {
|
||||
|
|
Двоичные данные
mobile/android/base/resources/drawable-hdpi/menu_item_more.png
Двоичные данные
mobile/android/base/resources/drawable-hdpi/menu_item_more.png
Двоичный файл не отображается.
До Ширина: | Высота: | Размер: 1.1 KiB После Ширина: | Высота: | Размер: 1.1 KiB |
Двоичные данные
mobile/android/base/resources/drawable-mdpi/menu_item_more.png
Двоичные данные
mobile/android/base/resources/drawable-mdpi/menu_item_more.png
Двоичный файл не отображается.
До Ширина: | Высота: | Размер: 1.0 KiB После Ширина: | Высота: | Размер: 1.0 KiB |
Двоичные данные
mobile/android/base/resources/drawable-xhdpi/menu_item_more.png
Двоичные данные
mobile/android/base/resources/drawable-xhdpi/menu_item_more.png
Двоичный файл не отображается.
До Ширина: | Высота: | Размер: 1.1 KiB После Ширина: | Высота: | Размер: 1.1 KiB |
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<item android:id="@+id/restore_defaults"
|
||||
android:title="@string/pref_search_restore_defaults" />
|
||||
|
||||
</menu>
|
|
@ -49,6 +49,7 @@
|
|||
<dimen name="menu_item_state_icon">18dp</dimen>
|
||||
<dimen name="menu_item_row_height">44dp</dimen>
|
||||
<dimen name="menu_item_row_width">240dp</dimen>
|
||||
<dimen name="menu_item_more_offset">5dp</dimen>
|
||||
<dimen name="menu_popup_arrow_margin">5dip</dimen>
|
||||
<dimen name="menu_popup_arrow_width">40dip</dimen>
|
||||
<dimen name="menu_popup_offset">8dp</dimen>
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:gecko="http://schemas.android.com/apk/res-auto"
|
||||
android:title="@string/pref_category_search"
|
||||
android:enabled="false">
|
||||
|
||||
<CheckBoxPreference android:key="browser.search.suggest.enabled"
|
||||
android:title="@string/pref_search_suggestions"
|
||||
android:defaultValue="false"
|
||||
android:persistent="false" />
|
||||
|
||||
<org.mozilla.gecko.preferences.SearchPreferenceCategory
|
||||
android:title="@string/pref_category_installed_search_engines"/>
|
||||
|
||||
<PreferenceCategory android:title="@string/pref_category_add_search_providers">
|
||||
|
||||
<Preference android:layout="@layout/preference_search_tip"
|
||||
android:enabled="false"
|
||||
android:selectable="false"/>
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
</PreferenceScreen>
|
|
@ -16,6 +16,13 @@
|
|||
<org.mozilla.gecko.preferences.SearchPreferenceCategory
|
||||
android:title="@string/pref_category_installed_search_engines"/>
|
||||
|
||||
<PreferenceCategory android:title="@string/pref_category_search_restore_defaults">
|
||||
|
||||
<Preference android:key="android.not_a_preference.search.restore_defaults"
|
||||
android:title="@string/pref_search_restore_defaults_summary" />
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory android:title="@string/pref_category_add_search_providers">
|
||||
|
||||
<Preference android:layout="@layout/preference_search_tip"
|
||||
|
|
|
@ -93,7 +93,11 @@
|
|||
<string name="pref_category_datareporting">&pref_category_datareporting;</string>
|
||||
<string name="pref_category_installed_search_engines">&pref_category_installed_search_engines;</string>
|
||||
<string name="pref_category_add_search_providers">&pref_category_add_search_providers;</string>
|
||||
<string name="pref_category_search_restore_defaults">&pref_category_search_restore_defaults;</string>
|
||||
<string name="pref_search_restore_defaults">&pref_search_restore_defaults;</string>
|
||||
<string name="pref_search_restore_defaults_summary">&pref_search_restore_defaults_summary;</string>
|
||||
<string name="pref_search_tip">&pref_search_tip;</string>
|
||||
|
||||
<string name="pref_category_devtools">&pref_category_devtools;</string>
|
||||
<string name="pref_developer_remotedebugging">&pref_developer_remotedebugging;</string>
|
||||
<string name="pref_developer_remotedebugging_docs">&pref_developer_remotedebugging_docs;</string>
|
||||
|
|
|
@ -159,7 +159,7 @@ public class testDistribution extends ContentProviderTest {
|
|||
|
||||
private void checkSearchPlugin() {
|
||||
Actions.RepeatedEventExpecter eventExpecter = mActions.expectGeckoEvent("SearchEngines:Data");
|
||||
mActions.sendGeckoEvent("SearchEngines:Get", null);
|
||||
mActions.sendGeckoEvent("SearchEngines:GetVisible", null);
|
||||
|
||||
try {
|
||||
JSONObject data = new JSONObject(eventExpecter.blockForEventData());
|
||||
|
|
|
@ -47,7 +47,10 @@ public class ToolbarEditText extends CustomEditText
|
|||
|
||||
private final Context mContext;
|
||||
|
||||
private TextType mTextType;
|
||||
// Type of the URL bar go/search button
|
||||
private TextType mToolbarTextType;
|
||||
// Type of the keyboard go/search button (cannot be EMPTY)
|
||||
private TextType mKeyboardTextType;
|
||||
|
||||
private OnCommitListener mCommitListener;
|
||||
private OnDismissListener mDismissListener;
|
||||
|
@ -66,7 +69,8 @@ public class ToolbarEditText extends CustomEditText
|
|||
super(context, attrs);
|
||||
mContext = context;
|
||||
|
||||
mTextType = TextType.EMPTY;
|
||||
mToolbarTextType = TextType.EMPTY;
|
||||
mKeyboardTextType = TextType.URL;
|
||||
}
|
||||
|
||||
void setOnCommitListener(OnCommitListener listener) {
|
||||
|
@ -172,10 +176,13 @@ public class ToolbarEditText extends CustomEditText
|
|||
}
|
||||
|
||||
private void setTextType(TextType textType) {
|
||||
mTextType = textType;
|
||||
mToolbarTextType = textType;
|
||||
|
||||
if (textType != TextType.EMPTY) {
|
||||
mKeyboardTextType = textType;
|
||||
}
|
||||
if (mTextTypeListener != null) {
|
||||
mTextTypeListener.onTextTypeChange(this, mTextType);
|
||||
mTextTypeListener.onTextTypeChange(this, textType);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -186,6 +193,8 @@ public class ToolbarEditText extends CustomEditText
|
|||
}
|
||||
|
||||
if (InputMethods.shouldDisableUrlBarUpdate(mContext)) {
|
||||
// Set button type to match the previous keyboard type
|
||||
setTextType(mKeyboardTextType);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -222,10 +231,16 @@ public class ToolbarEditText extends CustomEditText
|
|||
restartInput = true;
|
||||
}
|
||||
|
||||
if (restartInput) {
|
||||
updateKeyboardInputType();
|
||||
imm.restartInput(ToolbarEditText.this);
|
||||
if (!restartInput) {
|
||||
// If the text content was previously empty, the toolbar text type
|
||||
// is empty as well. Since the keyboard text type cannot be empty,
|
||||
// the two text types are now inconsistent. Reset the toolbar text
|
||||
// type here to the keyboard text type to ensure consistency.
|
||||
setTextType(mKeyboardTextType);
|
||||
return;
|
||||
}
|
||||
updateKeyboardInputType();
|
||||
imm.restartInput(ToolbarEditText.this);
|
||||
|
||||
setTextType(imeAction == EditorInfo.IME_ACTION_GO ?
|
||||
TextType.URL : TextType.SEARCH_QUERY);
|
||||
|
|
|
@ -178,10 +178,20 @@ function doChangeMaxLineBoxWidth(aWidth) {
|
|||
range = BrowserApp.selectedTab._mReflozPoint.range;
|
||||
}
|
||||
|
||||
docViewer.changeMaxLineBoxWidth(aWidth);
|
||||
try {
|
||||
docViewer.pausePainting();
|
||||
docViewer.changeMaxLineBoxWidth(aWidth);
|
||||
|
||||
if (range) {
|
||||
BrowserEventHandler._zoomInAndSnapToRange(range);
|
||||
if (range) {
|
||||
BrowserEventHandler._zoomInAndSnapToRange(range);
|
||||
} else {
|
||||
// In this case, we actually didn't zoom into a specific range. It
|
||||
// probably happened from a page load reflow-on-zoom event, so we
|
||||
// need to make sure painting is re-enabled.
|
||||
BrowserApp.selectedTab.clearReflowOnZoomPendingActions();
|
||||
}
|
||||
} finally {
|
||||
docViewer.resumePainting();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2744,6 +2754,7 @@ Tab.prototype = {
|
|||
this.browser.addEventListener("MozApplicationManifest", this, true);
|
||||
|
||||
Services.obs.addObserver(this, "before-first-paint", false);
|
||||
Services.obs.addObserver(this, "after-viewport-change", false);
|
||||
Services.prefs.addObserver("browser.ui.zoom.force-user-scalable", this, false);
|
||||
|
||||
if (aParams.delayLoad) {
|
||||
|
@ -2803,21 +2814,58 @@ Tab.prototype = {
|
|||
return minFontSize / this.getInflatedFontSizeFor(aElement);
|
||||
},
|
||||
|
||||
clearReflowOnZoomPendingActions: function() {
|
||||
// Reflow was completed, so now re-enable painting.
|
||||
let webNav = BrowserApp.selectedTab.window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebNavigation);
|
||||
let docShell = webNav.QueryInterface(Ci.nsIDocShell);
|
||||
let docViewer = docShell.contentViewer.QueryInterface(Ci.nsIMarkupDocumentViewer);
|
||||
docViewer.resumePainting();
|
||||
|
||||
BrowserApp.selectedTab._mReflozPositioned = false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Reflow on zoom consists of a few different sub-operations:
|
||||
*
|
||||
* 1. When a double-tap event is seen, we verify that the correct preferences
|
||||
* are enabled and perform the pre-position handling calculation. We also
|
||||
* signal that reflow-on-zoom should be performed at this time, and pause
|
||||
* painting.
|
||||
* 2. During the next call to setViewport(), which is in the Tab prototype,
|
||||
* we detect that a call to changeMaxLineBoxWidth should be performed. If
|
||||
* we're zooming out, then the max line box width should be reset at this
|
||||
* time. Otherwise, we call performReflowOnZoom.
|
||||
* 2a. PerformReflowOnZoom() and resetMaxLineBoxWidth() schedule a call to
|
||||
* doChangeMaxLineBoxWidth, based on a timeout specified in preferences.
|
||||
* 3. doChangeMaxLineBoxWidth changes the line box width (which also
|
||||
* schedules a reflow event), and then calls _zoomInAndSnapToRange.
|
||||
* 4. _zoomInAndSnapToRange performs the positioning of reflow-on-zoom and
|
||||
* then re-enables painting.
|
||||
*
|
||||
* Some of the events happen synchronously, while others happen asynchronously.
|
||||
* The following is a rough sketch of the progression of events:
|
||||
*
|
||||
* double tap event seen -> onDoubleTap() -> ... asynchronous ...
|
||||
* -> setViewport() -> performReflowOnZoom() -> ... asynchronous ...
|
||||
* -> doChangeMaxLineBoxWidth() -> _zoomInAndSnapToRange()
|
||||
* -> ... asynchronous ... -> setViewport() -> Observe('after-viewport-change')
|
||||
* -> resumePainting()
|
||||
*/
|
||||
performReflowOnZoom: function(aViewport) {
|
||||
let zoom = this._drawZoom ? this._drawZoom : aViewport.zoom;
|
||||
let zoom = this._drawZoom ? this._drawZoom : aViewport.zoom;
|
||||
|
||||
let viewportWidth = gScreenWidth / zoom;
|
||||
let reflozTimeout = Services.prefs.getIntPref("browser.zoom.reflowZoom.reflowTimeout");
|
||||
let viewportWidth = gScreenWidth / zoom;
|
||||
let reflozTimeout = Services.prefs.getIntPref("browser.zoom.reflowZoom.reflowTimeout");
|
||||
|
||||
if (gReflowPending) {
|
||||
clearTimeout(gReflowPending);
|
||||
}
|
||||
if (gReflowPending) {
|
||||
clearTimeout(gReflowPending);
|
||||
}
|
||||
|
||||
// We add in a bit of fudge just so that the end characters
|
||||
// don't accidentally get clipped. 15px is an arbitrary choice.
|
||||
gReflowPending = setTimeout(doChangeMaxLineBoxWidth,
|
||||
reflozTimeout,
|
||||
viewportWidth - 15);
|
||||
// We add in a bit of fudge just so that the end characters
|
||||
// don't accidentally get clipped. 15px is an arbitrary choice.
|
||||
gReflowPending = setTimeout(doChangeMaxLineBoxWidth,
|
||||
reflozTimeout,
|
||||
viewportWidth - 15);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -2889,6 +2937,7 @@ Tab.prototype = {
|
|||
this.browser.removeEventListener("MozApplicationManifest", this, true);
|
||||
|
||||
Services.obs.removeObserver(this, "before-first-paint");
|
||||
Services.obs.removeObserver(this, "after-viewport-change");
|
||||
Services.prefs.removeObserver("browser.ui.zoom.force-user-scalable", this);
|
||||
|
||||
// Make sure the previously selected panel remains selected. The selected panel of a deck is
|
||||
|
@ -3175,13 +3224,21 @@ Tab.prototype = {
|
|||
// In this case, the user pinch-zoomed in, so we don't want to
|
||||
// preserve position as we would with reflow-on-zoom.
|
||||
BrowserApp.selectedTab.probablyNeedRefloz = false;
|
||||
BrowserApp.selectedTab.clearReflowOnZoomPendingActions();
|
||||
BrowserApp.selectedTab._mReflozPoint = null;
|
||||
}
|
||||
|
||||
let docViewer = null;
|
||||
|
||||
if (isZooming &&
|
||||
BrowserEventHandler.mReflozPref &&
|
||||
BrowserApp.selectedTab._mReflozPoint &&
|
||||
BrowserApp.selectedTab.probablyNeedRefloz) {
|
||||
let webNav = BrowserApp.selectedTab.window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebNavigation);
|
||||
let docShell = webNav.QueryInterface(Ci.nsIDocShell);
|
||||
docViewer = docShell.contentViewer.QueryInterface(Ci.nsIMarkupDocumentViewer);
|
||||
docViewer.pausePainting();
|
||||
|
||||
BrowserApp.selectedTab.performReflowOnZoom(aViewport);
|
||||
BrowserApp.selectedTab.probablyNeedRefloz = false;
|
||||
}
|
||||
|
@ -3210,6 +3267,9 @@ Tab.prototype = {
|
|||
aViewport.fixedMarginLeft / aViewport.zoom);
|
||||
|
||||
Services.obs.notifyObservers(null, "after-viewport-change", "");
|
||||
if (docViewer) {
|
||||
docViewer.resumePainting();
|
||||
}
|
||||
},
|
||||
|
||||
setResolution: function(aZoom, aForce) {
|
||||
|
@ -4166,6 +4226,11 @@ Tab.prototype = {
|
|||
BrowserApp.selectedTab.performReflowOnZoom(vp);
|
||||
}
|
||||
break;
|
||||
case "after-viewport-change":
|
||||
if (BrowserApp.selectedTab._mReflozPositioned) {
|
||||
BrowserApp.selectedTab.clearReflowOnZoomPendingActions();
|
||||
}
|
||||
break;
|
||||
case "nsPref:changed":
|
||||
if (aData == "browser.ui.zoom.force-user-scalable")
|
||||
ViewportHandler.updateMetadata(this, false);
|
||||
|
@ -4484,13 +4549,23 @@ var BrowserEventHandler = {
|
|||
if (BrowserEventHandler.mReflozPref &&
|
||||
!BrowserApp.selectedTab._mReflozPoint &&
|
||||
!this._shouldSuppressReflowOnZoom(element)) {
|
||||
let data = JSON.parse(aData);
|
||||
let zoomPointX = data.x;
|
||||
let zoomPointY = data.y;
|
||||
|
||||
BrowserApp.selectedTab._mReflozPoint = { x: zoomPointX, y: zoomPointY,
|
||||
range: BrowserApp.selectedBrowser.contentDocument.caretPositionFromPoint(zoomPointX, zoomPointY) };
|
||||
BrowserApp.selectedTab.probablyNeedRefloz = true;
|
||||
// See comment above performReflowOnZoom() for a detailed description of
|
||||
// the events happening in the reflow-on-zoom operation.
|
||||
let data = JSON.parse(aData);
|
||||
let zoomPointX = data.x;
|
||||
let zoomPointY = data.y;
|
||||
|
||||
BrowserApp.selectedTab._mReflozPoint = { x: zoomPointX, y: zoomPointY,
|
||||
range: BrowserApp.selectedBrowser.contentDocument.caretPositionFromPoint(zoomPointX, zoomPointY) };
|
||||
|
||||
// Before we perform a reflow on zoom, let's disable painting.
|
||||
let webNav = BrowserApp.selectedTab.window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebNavigation);
|
||||
let docShell = webNav.QueryInterface(Ci.nsIDocShell);
|
||||
let docViewer = docShell.contentViewer.QueryInterface(Ci.nsIMarkupDocumentViewer);
|
||||
docViewer.pausePainting();
|
||||
|
||||
BrowserApp.selectedTab.probablyNeedRefloz = true;
|
||||
}
|
||||
|
||||
if (!element) {
|
||||
|
@ -4590,11 +4665,7 @@ var BrowserEventHandler = {
|
|||
},
|
||||
|
||||
_zoomInAndSnapToRange: function(aRange) {
|
||||
if (!aRange) {
|
||||
Cu.reportError("aRange is null in zoomInAndSnapToRange. Unable to maintain position.");
|
||||
return;
|
||||
}
|
||||
|
||||
// aRange is always non-null here, since a check happened previously.
|
||||
let viewport = BrowserApp.selectedTab.getViewport();
|
||||
let fudge = 15; // Add a bit of fudge.
|
||||
let boundingElement = aRange.offsetNode;
|
||||
|
@ -4617,30 +4688,33 @@ var BrowserEventHandler = {
|
|||
let leftAdjustment = parseInt(boundingStyle.paddingLeft) +
|
||||
parseInt(boundingStyle.borderLeftWidth);
|
||||
|
||||
BrowserApp.selectedTab._mReflozPositioned = true;
|
||||
|
||||
rect.type = "Browser:ZoomToRect";
|
||||
rect.x = Math.max(viewport.cssPageLeft, rect.x - fudge + leftAdjustment);
|
||||
rect.y = Math.max(topPos, viewport.cssPageTop);
|
||||
rect.w = viewport.cssWidth;
|
||||
rect.h = viewport.cssHeight;
|
||||
rect.animate = false;
|
||||
|
||||
sendMessageToJava(rect);
|
||||
BrowserApp.selectedTab._mReflozPoint = null;
|
||||
},
|
||||
},
|
||||
|
||||
onPinchFinish: function(aData) {
|
||||
let data = {};
|
||||
try {
|
||||
data = JSON.parse(aData);
|
||||
} catch(ex) {
|
||||
console.log(ex);
|
||||
return;
|
||||
}
|
||||
onPinchFinish: function(aData) {
|
||||
let data = {};
|
||||
try {
|
||||
data = JSON.parse(aData);
|
||||
} catch(ex) {
|
||||
console.log(ex);
|
||||
return;
|
||||
}
|
||||
|
||||
if (BrowserEventHandler.mReflozPref &&
|
||||
data.zoomDelta < 0.0) {
|
||||
BrowserEventHandler.resetMaxLineBoxWidth();
|
||||
}
|
||||
},
|
||||
if (BrowserEventHandler.mReflozPref &&
|
||||
data.zoomDelta < 0.0) {
|
||||
BrowserEventHandler.resetMaxLineBoxWidth();
|
||||
}
|
||||
},
|
||||
|
||||
_shouldZoomToElement: function(aElement) {
|
||||
let win = aElement.ownerDocument.defaultView;
|
||||
|
@ -6608,10 +6682,10 @@ var SearchEngines = {
|
|||
|
||||
init: function init() {
|
||||
Services.obs.addObserver(this, "SearchEngines:Add", false);
|
||||
Services.obs.addObserver(this, "SearchEngines:Get", false);
|
||||
Services.obs.addObserver(this, "SearchEngines:GetVisible", false);
|
||||
Services.obs.addObserver(this, "SearchEngines:SetDefault", false);
|
||||
Services.obs.addObserver(this, "SearchEngines:Remove", false);
|
||||
Services.obs.addObserver(this, "SearchEngines:RestoreDefaults", false);
|
||||
Services.obs.addObserver(this, "SearchEngines:SetDefault", false);
|
||||
|
||||
let filter = {
|
||||
matches: function (aElement) {
|
||||
|
@ -6651,37 +6725,28 @@ var SearchEngines = {
|
|||
|
||||
uninit: function uninit() {
|
||||
Services.obs.removeObserver(this, "SearchEngines:Add");
|
||||
Services.obs.removeObserver(this, "SearchEngines:Get");
|
||||
Services.obs.removeObserver(this, "SearchEngines:GetVisible");
|
||||
Services.obs.removeObserver(this, "SearchEngines:SetDefault");
|
||||
Services.obs.removeObserver(this, "SearchEngines:Remove");
|
||||
Services.obs.removeObserver(this, "SearchEngines:RestoreDefaults");
|
||||
Services.obs.removeObserver(this, "SearchEngines:SetDefault");
|
||||
if (this._contextMenuId != null)
|
||||
NativeWindow.contextmenus.remove(this._contextMenuId);
|
||||
},
|
||||
|
||||
// Fetch list of search engines. all ? All engines : Visible engines only.
|
||||
_handleSearchEnginesGet: function _handleSearchEnginesGet(rv, all) {
|
||||
_handleSearchEnginesGetVisible: function _handleSearchEnginesGetVisible(rv, all) {
|
||||
if (!Components.isSuccessCode(rv)) {
|
||||
Cu.reportError("Could not initialize search service, bailing out.");
|
||||
return;
|
||||
}
|
||||
let engineData;
|
||||
if (all) {
|
||||
engineData = Services.search.getEngines({});
|
||||
} else {
|
||||
engineData = Services.search.getVisibleEngines({});
|
||||
}
|
||||
|
||||
// These engines are the bundled ones - they may not be uninstalled.
|
||||
let immutableEngines = Services.search.getDefaultEngines();
|
||||
|
||||
let engineData = Services.search.getVisibleEngines({});
|
||||
let searchEngines = engineData.map(function (engine) {
|
||||
return {
|
||||
name: engine.name,
|
||||
identifier: engine.identifier,
|
||||
iconURI: (engine.iconURI ? engine.iconURI.spec : null),
|
||||
hidden: engine.hidden,
|
||||
immutable: immutableEngines.indexOf(engine) != -1
|
||||
hidden: engine.hidden
|
||||
};
|
||||
});
|
||||
|
||||
|
@ -6718,13 +6783,6 @@ var SearchEngines = {
|
|||
} catch (e) {}
|
||||
},
|
||||
|
||||
_handleSearchEnginesGetAll: function _handleSearchEnginesGetAll(rv) {
|
||||
this._handleSearchEnginesGet(rv, true);
|
||||
},
|
||||
_handleSearchEnginesGetVisible: function _handleSearchEnginesGetVisible(rv) {
|
||||
this._handleSearchEnginesGet(rv, false)
|
||||
},
|
||||
|
||||
// Helper method to extract the engine name from a JSON. Simplifies the observe function.
|
||||
_extractEngineFromJSON: function _extractEngineFromJSON(aData) {
|
||||
let data = JSON.parse(aData);
|
||||
|
@ -6740,16 +6798,6 @@ var SearchEngines = {
|
|||
case "SearchEngines:GetVisible":
|
||||
Services.search.init(this._handleSearchEnginesGetVisible.bind(this));
|
||||
break;
|
||||
case "SearchEngines:Get":
|
||||
// Return a list of all engines, including "Hidden" ones.
|
||||
Services.search.init(this._handleSearchEnginesGetAll.bind(this));
|
||||
break;
|
||||
case "SearchEngines:SetDefault":
|
||||
engine = this._extractEngineFromJSON(aData);
|
||||
// Move the new default search engine to the top of the search engine list.
|
||||
Services.search.moveEngine(engine, 0);
|
||||
Services.search.defaultEngine = engine;
|
||||
break;
|
||||
case "SearchEngines:Remove":
|
||||
// Make sure the engine isn't hidden before removing it, to make sure it's
|
||||
// visible if the user later re-adds it (works around bug 341833)
|
||||
|
@ -6757,6 +6805,17 @@ var SearchEngines = {
|
|||
engine.hidden = false;
|
||||
Services.search.removeEngine(engine);
|
||||
break;
|
||||
case "SearchEngines:RestoreDefaults":
|
||||
// Un-hides all default engines.
|
||||
Services.search.restoreDefaultEngines();
|
||||
break;
|
||||
case "SearchEngines:SetDefault":
|
||||
engine = this._extractEngineFromJSON(aData);
|
||||
// Move the new default search engine to the top of the search engine list.
|
||||
Services.search.moveEngine(engine, 0);
|
||||
Services.search.defaultEngine = engine;
|
||||
break;
|
||||
|
||||
default:
|
||||
dump("Unexpected message type observed: " + aTopic);
|
||||
break;
|
||||
|
|
|
@ -223,7 +223,9 @@ SessionStore.prototype = {
|
|||
}
|
||||
case "pageshow": {
|
||||
let browser = aEvent.currentTarget;
|
||||
this.onTabLoad(window, browser, aEvent.persisted);
|
||||
// Top-level changes only
|
||||
if (aEvent.originalTarget == browser.contentDocument)
|
||||
this.onTabLoad(window, browser, aEvent.persisted);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
/* General utilities used throughout devtools. */
|
||||
|
||||
let Cu = Components.utils;
|
||||
let { Promise: promise } = Components.utils.import("resource://gre/modules/commonjs/sdk/core/promise.js", {});
|
||||
let { Services } = Components.utils.import("resource://gre/modules/Services.jsm", {});
|
||||
|
||||
|
@ -237,3 +238,24 @@ this.hasSafeGetter = function hasSafeGetter(aDesc) {
|
|||
return fn && fn.callable && fn.class == "Function" && fn.script === undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if it is safe to read properties and execute methods from the given JS
|
||||
* object. Safety is defined as being protected from unintended code execution
|
||||
* from content scripts (or cross-compartment code).
|
||||
*
|
||||
* See bugs 945920 and 946752 for discussion.
|
||||
*
|
||||
* @type Object aObj
|
||||
* The object to check.
|
||||
* @return Boolean
|
||||
* True if it is safe to read properties from aObj, or false otherwise.
|
||||
*/
|
||||
this.isSafeJSObject = function isSafeJSObject(aObj) {
|
||||
if (Cu.getGlobalForObject(aObj) ==
|
||||
Cu.getGlobalForObject(isSafeJSObject)) {
|
||||
return true; // aObj is not a cross-compartment wrapper.
|
||||
}
|
||||
|
||||
return Cu.isXrayWrapper(aObj);
|
||||
};
|
||||
|
||||
|
|
|
@ -28,4 +28,5 @@ this.DevToolsUtils = {
|
|||
defineLazyPrototypeGetter: defineLazyPrototypeGetter,
|
||||
getProperty: getProperty,
|
||||
hasSafeGetter: hasSafeGetter,
|
||||
isSafeJSObject: isSafeJSObject,
|
||||
};
|
||||
|
|
|
@ -6,6 +6,14 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
let TYPED_ARRAY_CLASSES = ["Uint8Array", "Uint8ClampedArray", "Uint16Array",
|
||||
"Uint32Array", "Int8Array", "Int16Array", "Int32Array", "Float32Array",
|
||||
"Float64Array"];
|
||||
|
||||
// Number of items to preview in objects, arrays, maps, sets, lists,
|
||||
// collections, etc.
|
||||
let OBJECT_PREVIEW_MAX_ITEMS = 10;
|
||||
|
||||
/**
|
||||
* BreakpointStore objects keep track of all breakpoints that get set so that we
|
||||
* can reset them when the same script is introduced to the thread again (such
|
||||
|
@ -440,6 +448,8 @@ function ThreadActor(aHooks, aGlobal)
|
|||
this._options = {
|
||||
useSourceMaps: false
|
||||
};
|
||||
|
||||
this._gripDepth = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -449,6 +459,9 @@ function ThreadActor(aHooks, aGlobal)
|
|||
ThreadActor.breakpointStore = new BreakpointStore();
|
||||
|
||||
ThreadActor.prototype = {
|
||||
// Used by the ObjectActor to keep track of the depth of grip() calls.
|
||||
_gripDepth: null,
|
||||
|
||||
actorPrefix: "context",
|
||||
|
||||
get state() { return this._state; },
|
||||
|
@ -2730,7 +2743,7 @@ function errorStringify(aObj) {
|
|||
* The stringification for the object.
|
||||
*/
|
||||
function stringify(aObj) {
|
||||
if (Cu.isDeadWrapper(aObj)) {
|
||||
if (aObj.class == "DeadObject") {
|
||||
const error = new Error("Dead object encountered.");
|
||||
DevToolsUtils.reportException("stringify", error);
|
||||
return "<dead object>";
|
||||
|
@ -2829,6 +2842,8 @@ ObjectActor.prototype = {
|
|||
* Returns a grip for this actor for returning in a protocol message.
|
||||
*/
|
||||
grip: function () {
|
||||
this.threadActor._gripDepth++;
|
||||
|
||||
let g = {
|
||||
"type": "object",
|
||||
"class": this.obj.class,
|
||||
|
@ -2838,29 +2853,22 @@ ObjectActor.prototype = {
|
|||
"sealed": this.obj.isSealed()
|
||||
};
|
||||
|
||||
// Add additional properties for functions.
|
||||
if (this.obj.class === "Function") {
|
||||
if (this.obj.name) {
|
||||
g.name = this.obj.name;
|
||||
}
|
||||
if (this.obj.displayName) {
|
||||
g.displayName = this.obj.displayName;
|
||||
if (this.obj.class != "DeadObject") {
|
||||
let raw = Cu.unwaiveXrays(this.obj.unsafeDereference());
|
||||
if (!DevToolsUtils.isSafeJSObject(raw)) {
|
||||
raw = null;
|
||||
}
|
||||
|
||||
// Check if the developer has added a de-facto standard displayName
|
||||
// property for us to use.
|
||||
try {
|
||||
let desc = this.obj.getOwnPropertyDescriptor("displayName");
|
||||
if (desc && desc.value && typeof desc.value == "string") {
|
||||
g.userDisplayName = this.threadActor.createValueGrip(desc.value);
|
||||
let previewers = DebuggerServer.ObjectActorPreviewers[this.obj.class] ||
|
||||
DebuggerServer.ObjectActorPreviewers.Object;
|
||||
for (let fn of previewers) {
|
||||
if (fn(this, g, raw)) {
|
||||
break;
|
||||
}
|
||||
} catch (e) {
|
||||
// Calling getOwnPropertyDescriptor with displayName might throw
|
||||
// with "permission denied" errors for some functions.
|
||||
dumpn(e);
|
||||
}
|
||||
}
|
||||
|
||||
this.threadActor._gripDepth--;
|
||||
return g;
|
||||
},
|
||||
|
||||
|
@ -2964,15 +2972,17 @@ ObjectActor.prototype = {
|
|||
* @param object aOwnProperties
|
||||
* The object that holds the list of known ownProperties for
|
||||
* |this.obj|.
|
||||
* @param number [aLimit=0]
|
||||
* Optional limit of getter values to find.
|
||||
* @return object
|
||||
* An object that maps property names to safe getter descriptors as
|
||||
* defined by the remote debugging protocol.
|
||||
*/
|
||||
_findSafeGetterValues: function (aOwnProperties)
|
||||
_findSafeGetterValues: function (aOwnProperties, aLimit = 0)
|
||||
{
|
||||
let safeGetterValues = Object.create(null);
|
||||
let obj = this.obj;
|
||||
let level = 0;
|
||||
let level = 0, i = 0;
|
||||
|
||||
while (obj) {
|
||||
let getters = this._findSafeGetters(obj);
|
||||
|
@ -3014,9 +3024,15 @@ ObjectActor.prototype = {
|
|||
enumerable: desc.enumerable,
|
||||
writable: level == 0 ? desc.writable : true,
|
||||
};
|
||||
if (aLimit && ++i == aLimit) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (aLimit && i == aLimit) {
|
||||
break;
|
||||
}
|
||||
|
||||
obj = obj.proto;
|
||||
level++;
|
||||
|
@ -3043,7 +3059,15 @@ ObjectActor.prototype = {
|
|||
}
|
||||
|
||||
let getters = new Set();
|
||||
for (let name of aObject.getOwnPropertyNames()) {
|
||||
let names = [];
|
||||
try {
|
||||
names = aObject.getOwnPropertyNames()
|
||||
} catch (ex) {
|
||||
// Calling getOwnPropertyNames() on some wrapped native prototypes is not
|
||||
// allowed: "cannot modify properties of a WrappedNative". See bug 952093.
|
||||
}
|
||||
|
||||
for (let name of names) {
|
||||
let desc = null;
|
||||
try {
|
||||
desc = aObject.getOwnPropertyDescriptor(name);
|
||||
|
@ -3108,10 +3132,17 @@ ObjectActor.prototype = {
|
|||
* A helper method that creates a property descriptor for the provided object,
|
||||
* properly formatted for sending in a protocol response.
|
||||
*
|
||||
* @private
|
||||
* @param string aName
|
||||
* The property that the descriptor is generated for.
|
||||
* @param boolean [aOnlyEnumerable]
|
||||
* Optional: true if you want a descriptor only for an enumerable
|
||||
* property, false otherwise.
|
||||
* @return object|undefined
|
||||
* The property descriptor, or undefined if this is not an enumerable
|
||||
* property and aOnlyEnumerable=true.
|
||||
*/
|
||||
_propertyDescriptor: function (aName) {
|
||||
_propertyDescriptor: function (aName, aOnlyEnumerable) {
|
||||
let desc;
|
||||
try {
|
||||
desc = this.obj.getOwnPropertyDescriptor(aName);
|
||||
|
@ -3127,7 +3158,7 @@ ObjectActor.prototype = {
|
|||
};
|
||||
}
|
||||
|
||||
if (!desc) {
|
||||
if (!desc || aOnlyEnumerable && !desc.enumerable) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
@ -3232,6 +3263,565 @@ ObjectActor.prototype.requestTypes = {
|
|||
};
|
||||
|
||||
|
||||
/**
|
||||
* Functions for adding information to ObjectActor grips for the purpose of
|
||||
* having customized output. This object holds arrays mapped by
|
||||
* Debugger.Object.prototype.class.
|
||||
*
|
||||
* In each array you can add functions that take two
|
||||
* arguments:
|
||||
* - the ObjectActor instance to make a preview for,
|
||||
* - the grip object being prepared for the client,
|
||||
* - the raw JS object after calling Debugger.Object.unsafeDereference(). This
|
||||
* argument is only provided if the object is safe for reading properties and
|
||||
* executing methods. See DevToolsUtils.isSafeJSObject().
|
||||
*
|
||||
* Functions must return false if they cannot provide preview
|
||||
* information for the debugger object, or true otherwise.
|
||||
*/
|
||||
DebuggerServer.ObjectActorPreviewers = {
|
||||
Function: [function({obj, threadActor}, aGrip) {
|
||||
if (obj.name) {
|
||||
aGrip.name = obj.name;
|
||||
}
|
||||
|
||||
if (obj.displayName) {
|
||||
aGrip.displayName = obj.displayName.substr(0, 500);
|
||||
}
|
||||
|
||||
if (obj.parameterNames) {
|
||||
aGrip.parameterNames = obj.parameterNames;
|
||||
}
|
||||
|
||||
// Check if the developer has added a de-facto standard displayName
|
||||
// property for us to use.
|
||||
let userDisplayName;
|
||||
try {
|
||||
userDisplayName = obj.getOwnPropertyDescriptor("displayName");
|
||||
} catch (e) {
|
||||
// Calling getOwnPropertyDescriptor with displayName might throw
|
||||
// with "permission denied" errors for some functions.
|
||||
dumpn(e);
|
||||
}
|
||||
|
||||
if (userDisplayName && typeof userDisplayName.value == "string" &&
|
||||
userDisplayName.value) {
|
||||
aGrip.userDisplayName = threadActor.createValueGrip(userDisplayName.value);
|
||||
}
|
||||
|
||||
return true;
|
||||
}],
|
||||
|
||||
RegExp: [function({obj, threadActor}, aGrip) {
|
||||
// Avoid having any special preview for the RegExp.prototype itself.
|
||||
if (!obj.proto || obj.proto.class != "RegExp") {
|
||||
return false;
|
||||
}
|
||||
|
||||
let str = RegExp.prototype.toString.call(obj.unsafeDereference());
|
||||
aGrip.displayString = threadActor.createValueGrip(str);
|
||||
return true;
|
||||
}],
|
||||
|
||||
Date: [function({obj, threadActor}, aGrip) {
|
||||
if (!obj.proto || obj.proto.class != "Date") {
|
||||
return false;
|
||||
}
|
||||
|
||||
let time = Date.prototype.getTime.call(obj.unsafeDereference());
|
||||
|
||||
aGrip.preview = {
|
||||
timestamp: threadActor.createValueGrip(time),
|
||||
};
|
||||
return true;
|
||||
}],
|
||||
|
||||
Array: [function({obj, threadActor}, aGrip) {
|
||||
let length = DevToolsUtils.getProperty(obj, "length");
|
||||
if (typeof length != "number") {
|
||||
return false;
|
||||
}
|
||||
|
||||
aGrip.preview = {
|
||||
kind: "ArrayLike",
|
||||
length: length,
|
||||
};
|
||||
|
||||
if (threadActor._gripDepth > 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
let raw = obj.unsafeDereference();
|
||||
let items = aGrip.preview.items = [];
|
||||
|
||||
for (let [i, value] of Array.prototype.entries.call(raw)) {
|
||||
if (Object.hasOwnProperty.call(raw, i)) {
|
||||
value = makeDebuggeeValueIfNeeded(obj, value);
|
||||
items.push(threadActor.createValueGrip(value));
|
||||
} else {
|
||||
items.push(null);
|
||||
}
|
||||
|
||||
if (items.length == OBJECT_PREVIEW_MAX_ITEMS) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}], // Array
|
||||
|
||||
Set: [function({obj, threadActor}, aGrip) {
|
||||
let size = DevToolsUtils.getProperty(obj, "size");
|
||||
if (typeof size != "number") {
|
||||
return false;
|
||||
}
|
||||
|
||||
aGrip.preview = {
|
||||
kind: "ArrayLike",
|
||||
length: size,
|
||||
};
|
||||
|
||||
// Avoid recursive object grips.
|
||||
if (threadActor._gripDepth > 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
let raw = obj.unsafeDereference();
|
||||
let items = aGrip.preview.items = [];
|
||||
for (let item of Set.prototype.values.call(raw)) {
|
||||
item = makeDebuggeeValueIfNeeded(obj, item);
|
||||
items.push(threadActor.createValueGrip(item));
|
||||
if (items.length == OBJECT_PREVIEW_MAX_ITEMS) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}], // Set
|
||||
|
||||
Map: [function({obj, threadActor}, aGrip) {
|
||||
let size = DevToolsUtils.getProperty(obj, "size");
|
||||
if (typeof size != "number") {
|
||||
return false;
|
||||
}
|
||||
|
||||
aGrip.preview = {
|
||||
kind: "MapLike",
|
||||
size: size,
|
||||
};
|
||||
|
||||
if (threadActor._gripDepth > 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
let raw = obj.unsafeDereference();
|
||||
let entries = aGrip.preview.entries = [];
|
||||
for (let [key, value] of Map.prototype.entries.call(raw)) {
|
||||
key = makeDebuggeeValueIfNeeded(obj, key);
|
||||
value = makeDebuggeeValueIfNeeded(obj, value);
|
||||
entries.push([threadActor.createValueGrip(key),
|
||||
threadActor.createValueGrip(value)]);
|
||||
if (entries.length == OBJECT_PREVIEW_MAX_ITEMS) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}], // Map
|
||||
|
||||
DOMStringMap: [function({obj, threadActor}, aGrip, aRawObj) {
|
||||
if (!aRawObj) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let keys = obj.getOwnPropertyNames();
|
||||
aGrip.preview = {
|
||||
kind: "MapLike",
|
||||
size: keys.length,
|
||||
};
|
||||
|
||||
if (threadActor._gripDepth > 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
let entries = aGrip.preview.entries = [];
|
||||
for (let key of keys) {
|
||||
let value = makeDebuggeeValueIfNeeded(obj, aRawObj[key]);
|
||||
entries.push([key, threadActor.createValueGrip(value)]);
|
||||
if (entries.length == OBJECT_PREVIEW_MAX_ITEMS) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}], // DOMStringMap
|
||||
}; // DebuggerServer.ObjectActorPreviewers
|
||||
|
||||
// Preview functions that do not rely on the object class.
|
||||
DebuggerServer.ObjectActorPreviewers.Object = [
|
||||
function TypedArray({obj, threadActor}, aGrip) {
|
||||
if (TYPED_ARRAY_CLASSES.indexOf(obj.class) == -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let length = DevToolsUtils.getProperty(obj, "length");
|
||||
if (typeof length != "number") {
|
||||
return false;
|
||||
}
|
||||
|
||||
aGrip.preview = {
|
||||
kind: "ArrayLike",
|
||||
length: length,
|
||||
};
|
||||
|
||||
if (threadActor._gripDepth > 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
let raw = obj.unsafeDereference();
|
||||
let global = Cu.getGlobalForObject(DebuggerServer);
|
||||
let classProto = global[obj.class].prototype;
|
||||
let safeView = classProto.subarray.call(raw, 0, OBJECT_PREVIEW_MAX_ITEMS);
|
||||
let items = aGrip.preview.items = [];
|
||||
for (let i = 0; i < safeView.length; i++) {
|
||||
items.push(safeView[i]);
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
function Error({obj, threadActor}, aGrip) {
|
||||
switch (obj.class) {
|
||||
case "Error":
|
||||
case "EvalError":
|
||||
case "RangeError":
|
||||
case "ReferenceError":
|
||||
case "SyntaxError":
|
||||
case "TypeError":
|
||||
case "URIError":
|
||||
let name = DevToolsUtils.getProperty(obj, "name");
|
||||
let msg = DevToolsUtils.getProperty(obj, "message");
|
||||
let stack = DevToolsUtils.getProperty(obj, "stack");
|
||||
let fileName = DevToolsUtils.getProperty(obj, "fileName");
|
||||
let lineNumber = DevToolsUtils.getProperty(obj, "lineNumber");
|
||||
let columnNumber = DevToolsUtils.getProperty(obj, "columnNumber");
|
||||
aGrip.preview = {
|
||||
kind: "Error",
|
||||
name: threadActor.createValueGrip(name),
|
||||
message: threadActor.createValueGrip(msg),
|
||||
stack: threadActor.createValueGrip(stack),
|
||||
fileName: threadActor.createValueGrip(fileName),
|
||||
lineNumber: threadActor.createValueGrip(lineNumber),
|
||||
columnNumber: threadActor.createValueGrip(columnNumber),
|
||||
};
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
function CSSMediaRule({obj, threadActor}, aGrip, aRawObj) {
|
||||
if (!aRawObj || !(aRawObj instanceof Ci.nsIDOMCSSMediaRule)) {
|
||||
return false;
|
||||
}
|
||||
aGrip.preview = {
|
||||
kind: "ObjectWithText",
|
||||
text: threadActor.createValueGrip(aRawObj.conditionText),
|
||||
};
|
||||
return true;
|
||||
},
|
||||
|
||||
function CSSStyleRule({obj, threadActor}, aGrip, aRawObj) {
|
||||
if (!aRawObj || !(aRawObj instanceof Ci.nsIDOMCSSStyleRule)) {
|
||||
return false;
|
||||
}
|
||||
aGrip.preview = {
|
||||
kind: "ObjectWithText",
|
||||
text: threadActor.createValueGrip(aRawObj.selectorText),
|
||||
};
|
||||
return true;
|
||||
},
|
||||
|
||||
function ObjectWithURL({obj, threadActor}, aGrip, aRawObj) {
|
||||
if (!aRawObj ||
|
||||
!(aRawObj instanceof Ci.nsIDOMCSSImportRule ||
|
||||
aRawObj instanceof Ci.nsIDOMCSSStyleSheet ||
|
||||
aRawObj instanceof Ci.nsIDOMLocation ||
|
||||
aRawObj instanceof Ci.nsIDOMWindow)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let url;
|
||||
if (aRawObj instanceof Ci.nsIDOMWindow) {
|
||||
url = aRawObj.location.href;
|
||||
} else {
|
||||
url = aRawObj.href;
|
||||
}
|
||||
|
||||
aGrip.preview = {
|
||||
kind: "ObjectWithURL",
|
||||
url: threadActor.createValueGrip(url),
|
||||
};
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
function ArrayLike({obj, threadActor}, aGrip, aRawObj) {
|
||||
if (!aRawObj ||
|
||||
obj.class != "DOMTokenList" &&
|
||||
!(aRawObj instanceof Ci.nsIDOMMozNamedAttrMap ||
|
||||
aRawObj instanceof Ci.nsIDOMCSSRuleList ||
|
||||
aRawObj instanceof Ci.nsIDOMCSSValueList ||
|
||||
aRawObj instanceof Ci.nsIDOMDOMStringList ||
|
||||
aRawObj instanceof Ci.nsIDOMFileList ||
|
||||
aRawObj instanceof Ci.nsIDOMFontFaceList ||
|
||||
aRawObj instanceof Ci.nsIDOMMediaList ||
|
||||
aRawObj instanceof Ci.nsIDOMNodeList ||
|
||||
aRawObj instanceof Ci.nsIDOMStyleSheetList)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (typeof aRawObj.length != "number") {
|
||||
return false;
|
||||
}
|
||||
|
||||
aGrip.preview = {
|
||||
kind: "ArrayLike",
|
||||
length: aRawObj.length,
|
||||
};
|
||||
|
||||
if (threadActor._gripDepth > 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
let items = aGrip.preview.items = [];
|
||||
|
||||
for (let i = 0; i < aRawObj.length &&
|
||||
items.length < OBJECT_PREVIEW_MAX_ITEMS; i++) {
|
||||
let value = makeDebuggeeValueIfNeeded(obj, aRawObj[i]);
|
||||
items.push(threadActor.createValueGrip(value));
|
||||
}
|
||||
|
||||
return true;
|
||||
}, // ArrayLike
|
||||
|
||||
function CSSStyleDeclaration({obj, threadActor}, aGrip, aRawObj) {
|
||||
if (!aRawObj || !(aRawObj instanceof Ci.nsIDOMCSSStyleDeclaration)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
aGrip.preview = {
|
||||
kind: "MapLike",
|
||||
size: aRawObj.length,
|
||||
};
|
||||
|
||||
let entries = aGrip.preview.entries = [];
|
||||
|
||||
for (let i = 0; i < OBJECT_PREVIEW_MAX_ITEMS &&
|
||||
i < aRawObj.length; i++) {
|
||||
let prop = aRawObj[i];
|
||||
let value = aRawObj.getPropertyValue(prop);
|
||||
entries.push([prop, threadActor.createValueGrip(value)]);
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
function DOMNode({obj, threadActor}, aGrip, aRawObj) {
|
||||
if (!aRawObj || !(aRawObj instanceof Ci.nsIDOMNode)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let preview = aGrip.preview = {
|
||||
kind: "DOMNode",
|
||||
nodeType: aRawObj.nodeType,
|
||||
nodeName: aRawObj.nodeName,
|
||||
};
|
||||
|
||||
if (aRawObj instanceof Ci.nsIDOMDocument) {
|
||||
preview.location = threadActor.createValueGrip(aRawObj.location.href);
|
||||
} else if (aRawObj instanceof Ci.nsIDOMDocumentFragment) {
|
||||
preview.childNodesLength = aRawObj.childNodes.length;
|
||||
|
||||
if (threadActor._gripDepth < 2) {
|
||||
preview.childNodes = [];
|
||||
for (let node of aRawObj.childNodes) {
|
||||
let actor = threadActor.createValueGrip(obj.makeDebuggeeValue(node));
|
||||
preview.childNodes.push(actor);
|
||||
if (preview.childNodes.length == OBJECT_PREVIEW_MAX_ITEMS) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (aRawObj instanceof Ci.nsIDOMElement) {
|
||||
// Add preview for DOM element attributes.
|
||||
if (aRawObj instanceof Ci.nsIDOMHTMLElement) {
|
||||
preview.nodeName = preview.nodeName.toLowerCase();
|
||||
}
|
||||
|
||||
let i = 0;
|
||||
preview.attributes = {};
|
||||
preview.attributesLength = aRawObj.attributes.length;
|
||||
for (let attr of aRawObj.attributes) {
|
||||
preview.attributes[attr.nodeName] = threadActor.createValueGrip(attr.value);
|
||||
if (++i == OBJECT_PREVIEW_MAX_ITEMS) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (aRawObj instanceof Ci.nsIDOMAttr) {
|
||||
preview.value = threadActor.createValueGrip(aRawObj.value);
|
||||
} else if (aRawObj instanceof Ci.nsIDOMText ||
|
||||
aRawObj instanceof Ci.nsIDOMComment) {
|
||||
preview.textContent = threadActor.createValueGrip(aRawObj.textContent);
|
||||
}
|
||||
|
||||
return true;
|
||||
}, // DOMNode
|
||||
|
||||
function DOMEvent({obj, threadActor}, aGrip, aRawObj) {
|
||||
if (!aRawObj || !(aRawObj instanceof Ci.nsIDOMEvent)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let preview = aGrip.preview = {
|
||||
kind: "DOMEvent",
|
||||
type: aRawObj.type,
|
||||
properties: Object.create(null),
|
||||
};
|
||||
|
||||
if (threadActor._gripDepth < 2) {
|
||||
let target = obj.makeDebuggeeValue(aRawObj.target);
|
||||
preview.target = threadActor.createValueGrip(target);
|
||||
}
|
||||
|
||||
let props = [];
|
||||
if (aRawObj instanceof Ci.nsIDOMMouseEvent) {
|
||||
props.push("buttons", "clientX", "clientY", "layerX", "layerY");
|
||||
} else if (aRawObj instanceof Ci.nsIDOMKeyEvent) {
|
||||
let modifiers = [];
|
||||
if (aRawObj.altKey) {
|
||||
modifiers.push("Alt");
|
||||
}
|
||||
if (aRawObj.ctrlKey) {
|
||||
modifiers.push("Control");
|
||||
}
|
||||
if (aRawObj.metaKey) {
|
||||
modifiers.push("Meta");
|
||||
}
|
||||
if (aRawObj.shiftKey) {
|
||||
modifiers.push("Shift");
|
||||
}
|
||||
preview.eventKind = "key";
|
||||
preview.modifiers = modifiers;
|
||||
|
||||
props.push("key", "charCode", "keyCode");
|
||||
} else if (aRawObj instanceof Ci.nsIDOMTransitionEvent ||
|
||||
aRawObj instanceof Ci.nsIDOMAnimationEvent) {
|
||||
props.push("animationName", "pseudoElement");
|
||||
} else if (aRawObj instanceof Ci.nsIDOMClipboardEvent) {
|
||||
props.push("clipboardData");
|
||||
}
|
||||
|
||||
// Add event-specific properties.
|
||||
for (let prop of props) {
|
||||
let value = aRawObj[prop];
|
||||
if (value && (typeof value == "object" || typeof value == "function")) {
|
||||
// Skip properties pointing to objects.
|
||||
if (threadActor._gripDepth > 1) {
|
||||
continue;
|
||||
}
|
||||
value = obj.makeDebuggeeValue(value);
|
||||
}
|
||||
preview.properties[prop] = threadActor.createValueGrip(value);
|
||||
}
|
||||
|
||||
// Add any properties we find on the event object.
|
||||
if (!props.length) {
|
||||
let i = 0;
|
||||
for (let prop in aRawObj) {
|
||||
let value = aRawObj[prop];
|
||||
if (prop == "target" || prop == "type" || value === null ||
|
||||
typeof value == "function") {
|
||||
continue;
|
||||
}
|
||||
if (value && typeof value == "object") {
|
||||
if (threadActor._gripDepth > 1) {
|
||||
continue;
|
||||
}
|
||||
value = obj.makeDebuggeeValue(value);
|
||||
}
|
||||
preview.properties[prop] = threadActor.createValueGrip(value);
|
||||
if (++i == OBJECT_PREVIEW_MAX_ITEMS) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}, // DOMEvent
|
||||
|
||||
function DOMException({obj, threadActor}, aGrip, aRawObj) {
|
||||
if (!aRawObj || !(aRawObj instanceof Ci.nsIDOMDOMException)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
aGrip.preview = {
|
||||
kind: "DOMException",
|
||||
name: threadActor.createValueGrip(aRawObj.name),
|
||||
message: threadActor.createValueGrip(aRawObj.message),
|
||||
code: threadActor.createValueGrip(aRawObj.code),
|
||||
result: threadActor.createValueGrip(aRawObj.result),
|
||||
filename: threadActor.createValueGrip(aRawObj.filename),
|
||||
lineNumber: threadActor.createValueGrip(aRawObj.lineNumber),
|
||||
columnNumber: threadActor.createValueGrip(aRawObj.columnNumber),
|
||||
};
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
function GenericObject(aObjectActor, aGrip) {
|
||||
let {obj, threadActor} = aObjectActor;
|
||||
if (aGrip.preview || aGrip.displayString || threadActor._gripDepth > 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let i = 0, names = [];
|
||||
let preview = aGrip.preview = {
|
||||
kind: "Object",
|
||||
ownProperties: Object.create(null),
|
||||
};
|
||||
|
||||
try {
|
||||
names = obj.getOwnPropertyNames();
|
||||
} catch (ex) {
|
||||
// Calling getOwnPropertyNames() on some wrapped native prototypes is not
|
||||
// allowed: "cannot modify properties of a WrappedNative". See bug 952093.
|
||||
}
|
||||
|
||||
preview.ownPropertiesLength = names.length;
|
||||
|
||||
for (let name of names) {
|
||||
let desc = aObjectActor._propertyDescriptor(name, true);
|
||||
if (!desc) {
|
||||
continue;
|
||||
}
|
||||
|
||||
preview.ownProperties[name] = desc;
|
||||
if (++i == OBJECT_PREVIEW_MAX_ITEMS) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (i < OBJECT_PREVIEW_MAX_ITEMS) {
|
||||
preview.safeGetterValues = aObjectActor.
|
||||
_findSafeGetterValues(preview.ownProperties,
|
||||
OBJECT_PREVIEW_MAX_ITEMS - i);
|
||||
}
|
||||
|
||||
return true;
|
||||
}, // GenericObject
|
||||
]; // DebuggerServer.ObjectActorPreviewers.Object
|
||||
|
||||
/**
|
||||
* Creates a pause-scoped actor for the specified object.
|
||||
* @see ObjectActor
|
||||
|
@ -4517,3 +5107,23 @@ function positionInNodeList(element, nodeList) {
|
|||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a debuggee value for the given object, if needed. Primitive values
|
||||
* are left the same.
|
||||
*
|
||||
* Use case: you have a raw JS object (after unsafe dereference) and you want to
|
||||
* send it to the client. In that case you need to use an ObjectActor which
|
||||
* requires a debuggee value. The Debugger.Object.prototype.makeDebuggeeValue()
|
||||
* method works only for JS objects and functions.
|
||||
*
|
||||
* @param Debugger.Object obj
|
||||
* @param any value
|
||||
* @return object
|
||||
*/
|
||||
function makeDebuggeeValueIfNeeded(obj, value) {
|
||||
if (value && (typeof value == "object" || typeof value == "function")) {
|
||||
return obj.makeDebuggeeValue(value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
|
|
@ -61,6 +61,7 @@ function WebConsoleActor(aConnection, aParentActor)
|
|||
|
||||
this._protoChains = new Map();
|
||||
this._netEvents = new Map();
|
||||
this._gripDepth = 0;
|
||||
|
||||
this._onObserverNotification = this._onObserverNotification.bind(this);
|
||||
if (this.parentActor.isRootActor) {
|
||||
|
@ -80,6 +81,13 @@ WebConsoleActor.prototype =
|
|||
*/
|
||||
dbg: null,
|
||||
|
||||
/**
|
||||
* This is used by the ObjectActor to keep track of the depth of grip() calls.
|
||||
* @private
|
||||
* @type number
|
||||
*/
|
||||
_gripDepth: null,
|
||||
|
||||
/**
|
||||
* Actor pool for all of the actors we send to the client.
|
||||
* @private
|
||||
|
|
|
@ -188,12 +188,18 @@ let WebConsoleUtils = {
|
|||
*
|
||||
* @param string aSourceURL
|
||||
* The source URL to shorten.
|
||||
* @param object [aOptions]
|
||||
* Options:
|
||||
* - onlyCropQuery: boolean that tells if the URL abbreviation function
|
||||
* should only remove the query parameters and the hash fragment from
|
||||
* the given URL.
|
||||
* @return string
|
||||
* The abbreviated form of the source URL.
|
||||
*/
|
||||
abbreviateSourceURL: function WCU_abbreviateSourceURL(aSourceURL)
|
||||
abbreviateSourceURL:
|
||||
function WCU_abbreviateSourceURL(aSourceURL, aOptions = {})
|
||||
{
|
||||
if (aSourceURL.substr(0, 5) == "data:") {
|
||||
if (!aOptions.onlyCropQuery && aSourceURL.substr(0, 5) == "data:") {
|
||||
let commaIndex = aSourceURL.indexOf(",");
|
||||
if (commaIndex > -1) {
|
||||
aSourceURL = "data:" + aSourceURL.substring(commaIndex + 1);
|
||||
|
@ -214,13 +220,15 @@ let WebConsoleUtils = {
|
|||
|
||||
// Remove a trailing "/".
|
||||
if (aSourceURL[aSourceURL.length - 1] == "/") {
|
||||
aSourceURL = aSourceURL.substring(0, aSourceURL.length - 1);
|
||||
aSourceURL = aSourceURL.replace(/\/+$/, "");
|
||||
}
|
||||
|
||||
// Remove all but the last path component.
|
||||
let slashIndex = aSourceURL.lastIndexOf("/");
|
||||
if (slashIndex > -1) {
|
||||
aSourceURL = aSourceURL.substring(slashIndex + 1);
|
||||
if (!aOptions.onlyCropQuery) {
|
||||
let slashIndex = aSourceURL.lastIndexOf("/");
|
||||
if (slashIndex > -1) {
|
||||
aSourceURL = aSourceURL.substring(slashIndex + 1);
|
||||
}
|
||||
}
|
||||
|
||||
return aSourceURL;
|
||||
|
|
|
@ -11,6 +11,9 @@ const Ci = Components.interfaces;
|
|||
Cu.import("resource://gre/modules/osfile.jsm");
|
||||
Cu.import("resource://gre/modules/Promise.jsm");
|
||||
|
||||
// Make it possible to mock out timers for testing
|
||||
let MakeTimer = () => Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["DeferredSave"];
|
||||
|
||||
// If delay parameter is not provided, default is 50 milliseconds.
|
||||
|
@ -104,7 +107,7 @@ this.DeferredSave.prototype = {
|
|||
|
||||
this.LOG("Starting timer");
|
||||
if (!this._timer)
|
||||
this._timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
|
||||
this._timer = MakeTimer();
|
||||
this._timer.initWithCallback(() => this._deferredSave(),
|
||||
this._delay, Ci.nsITimer.TYPE_ONE_SHOT);
|
||||
},
|
||||
|
|
|
@ -12,11 +12,11 @@ testFile.append("DeferredSaveTest");
|
|||
|
||||
Components.utils.import("resource://gre/modules/Promise.jsm");
|
||||
|
||||
let context = Components.utils.import("resource://gre/modules/DeferredSave.jsm", {});
|
||||
let DeferredSave = context.DeferredSave;
|
||||
let DSContext = Components.utils.import("resource://gre/modules/DeferredSave.jsm", {});
|
||||
let DeferredSave = DSContext.DeferredSave;
|
||||
|
||||
// Test wrapper to let us do promise/task based testing of DeferredSaveP
|
||||
function DeferredSaveTester(aDelay, aDataProvider) {
|
||||
// Test wrapper to let us do promise/task based testing of DeferredSave
|
||||
function DeferredSaveTester(aDataProvider) {
|
||||
let tester = {
|
||||
// Deferred for the promise returned by the mock writeAtomic
|
||||
waDeferred: null,
|
||||
|
@ -54,11 +54,11 @@ function DeferredSaveTester(aDelay, aDataProvider) {
|
|||
if (!aDataProvider)
|
||||
aDataProvider = () => tester.dataToSave;
|
||||
|
||||
tester.saver = new DeferredSave(testFile.path, aDataProvider, aDelay);
|
||||
tester.saver = new DeferredSave(testFile.path, aDataProvider);
|
||||
|
||||
// Install a mock for OS.File.writeAtomic to let us control the async
|
||||
// behaviour of the promise
|
||||
context.OS.File.writeAtomic = function mock_writeAtomic(aFile, aData, aOptions) {
|
||||
DSContext.OS.File.writeAtomic = function mock_writeAtomic(aFile, aData, aOptions) {
|
||||
do_print("writeAtomic: " + aFile + " data: '" + aData + "', " + aOptions.toSource());
|
||||
tester.writtenData = aData;
|
||||
tester.waDeferred = Promise.defer();
|
||||
|
@ -69,6 +69,70 @@ function DeferredSaveTester(aDelay, aDataProvider) {
|
|||
return tester;
|
||||
};
|
||||
|
||||
/**
|
||||
* Install a mock nsITimer factory that triggers on the next spin of
|
||||
* the event loop after it is scheduled
|
||||
*/
|
||||
function setQuickMockTimer() {
|
||||
let quickTimer = {
|
||||
initWithCallback: function(aFunction, aDelay, aType) {
|
||||
do_print("Starting quick timer, delay = " + aDelay);
|
||||
do_execute_soon(aFunction);
|
||||
},
|
||||
cancel: function() {
|
||||
do_throw("Attempted to cancel a quickMockTimer");
|
||||
}
|
||||
};
|
||||
DSContext.MakeTimer = () => {
|
||||
do_print("Creating quick timer");
|
||||
return quickTimer;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Install a mock nsITimer factory in DeferredSave.jsm, returning a promise that resolves
|
||||
* when the client code sets the timer. Test cases can use this to wait for client code to
|
||||
* be ready for a timer event, and then signal the event by calling mockTimer.callback().
|
||||
* This could use some enhancement; clients can re-use the returned timer,
|
||||
* but with this implementation it's not possible for the test to wait for
|
||||
* a second call to initWithCallback() on the re-used timer.
|
||||
* @return Promise{mockTimer} that resolves when initWithCallback()
|
||||
* is called
|
||||
*/
|
||||
function setPromiseMockTimer() {
|
||||
let waiter = Promise.defer();
|
||||
let mockTimer = {
|
||||
callback: null,
|
||||
delay: null,
|
||||
type: null,
|
||||
isCancelled: false,
|
||||
|
||||
initWithCallback: function(aFunction, aDelay, aType) {
|
||||
do_print("Starting timer, delay = " + aDelay);
|
||||
this.callback = aFunction;
|
||||
this.delay = aDelay;
|
||||
this.type = aType;
|
||||
// cancelled timers can be re-used
|
||||
this.isCancelled = false;
|
||||
waiter.resolve(this);
|
||||
},
|
||||
cancel: function() {
|
||||
do_print("Cancelled mock timer");
|
||||
this.callback = null;
|
||||
this.delay = null;
|
||||
this.type = null;
|
||||
this.isCancelled = true;
|
||||
// If initWithCallback was never called, resolve to let tests check for cancel
|
||||
waiter.resolve(this);
|
||||
}
|
||||
};
|
||||
DSContext.MakeTimer = () => {
|
||||
do_print("Creating mock timer");
|
||||
return mockTimer;
|
||||
};
|
||||
return waiter.promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a Promise<null> that resolves after the specified number of milliseconds
|
||||
*/
|
||||
|
@ -84,7 +148,8 @@ function run_test() {
|
|||
|
||||
// Modify set data once, ask for save, make sure it saves cleanly
|
||||
add_task(function test_basic_save_succeeds() {
|
||||
let tester = DeferredSaveTester(1);
|
||||
setQuickMockTimer();
|
||||
let tester = DeferredSaveTester();
|
||||
let data = "Test 1 Data";
|
||||
|
||||
yield tester.save(data);
|
||||
|
@ -95,7 +160,8 @@ add_task(function test_basic_save_succeeds() {
|
|||
// Two saves called during the same event loop, both with callbacks
|
||||
// Make sure we save only the second version of the data
|
||||
add_task(function test_two_saves() {
|
||||
let tester = DeferredSaveTester(1);
|
||||
setQuickMockTimer();
|
||||
let tester = DeferredSaveTester();
|
||||
let firstCallback_happened = false;
|
||||
let firstData = "Test first save";
|
||||
let secondData = "Test second save";
|
||||
|
@ -117,7 +183,8 @@ add_task(function test_two_saves() {
|
|||
// Two saves called with a delay in between, both with callbacks
|
||||
// Make sure we save the second version of the data
|
||||
add_task(function test_two_saves_delay() {
|
||||
let tester = DeferredSaveTester(50);
|
||||
let timerPromise = setPromiseMockTimer();
|
||||
let tester = DeferredSaveTester();
|
||||
let firstCallback_happened = false;
|
||||
let delayDone = false;
|
||||
|
||||
|
@ -131,9 +198,18 @@ add_task(function test_two_saves_delay() {
|
|||
firstCallback_happened = true;
|
||||
}, do_report_unexpected_exception);
|
||||
|
||||
yield delay(5);
|
||||
// Wait a short time to let async events possibly spawned by the
|
||||
// first tester.save() to run
|
||||
yield delay(2);
|
||||
delayDone = true;
|
||||
yield tester.save(secondData);
|
||||
// request to save modified data
|
||||
let saving = tester.save(secondData);
|
||||
// Yield to wait for client code to set the timer
|
||||
let activeTimer = yield timerPromise;
|
||||
// and then trigger it
|
||||
activeTimer.callback();
|
||||
// now wait for the DeferredSave to finish saving
|
||||
yield saving;
|
||||
do_check_true(firstCallback_happened);
|
||||
do_check_eq(secondData, tester.writtenData);
|
||||
do_check_eq(1, tester.saver.totalSaves);
|
||||
|
@ -144,12 +220,13 @@ add_task(function test_two_saves_delay() {
|
|||
// Also check that the "error" getter correctly returns the error
|
||||
// Then do a write that succeeds, and make sure the error is cleared
|
||||
add_task(function test_error_immediate() {
|
||||
let tester = DeferredSaveTester(1);
|
||||
let tester = DeferredSaveTester();
|
||||
let testError = new Error("Forced failure");
|
||||
function writeFail(aTester) {
|
||||
aTester.waDeferred.reject(testError);
|
||||
}
|
||||
|
||||
setQuickMockTimer();
|
||||
yield tester.save("test_error_immediate", writeFail).then(
|
||||
count => do_throw("Did not get expected error"),
|
||||
error => do_check_eq(testError.message, error.message)
|
||||
|
@ -167,7 +244,7 @@ add_task(function test_error_immediate() {
|
|||
// data two more times. Test that we re-write the dirty data exactly once
|
||||
// after the first write succeeds
|
||||
add_task(function dirty_while_writing() {
|
||||
let tester = DeferredSaveTester(1);
|
||||
let tester = DeferredSaveTester();
|
||||
let firstData = "First data";
|
||||
let secondData = "Second data";
|
||||
let thirdData = "Third data";
|
||||
|
@ -179,6 +256,7 @@ add_task(function dirty_while_writing() {
|
|||
writeStarted.resolve(aTester.waDeferred);
|
||||
}
|
||||
|
||||
setQuickMockTimer();
|
||||
do_print("First save");
|
||||
tester.save(firstData, writeCallback).then(
|
||||
count => {
|
||||
|
@ -240,7 +318,8 @@ function write_then_disable(aTester) {
|
|||
// Flush tests. First, do an ordinary clean save and then call flush;
|
||||
// there should not be another save
|
||||
add_task(function flush_after_save() {
|
||||
let tester = DeferredSaveTester(1);
|
||||
setQuickMockTimer();
|
||||
let tester = DeferredSaveTester();
|
||||
let dataToSave = "Flush after save";
|
||||
|
||||
yield tester.save(dataToSave);
|
||||
|
@ -250,7 +329,7 @@ add_task(function flush_after_save() {
|
|||
|
||||
// Flush while a write is in progress, but the in-memory data is clean
|
||||
add_task(function flush_during_write() {
|
||||
let tester = DeferredSaveTester(1);
|
||||
let tester = DeferredSaveTester();
|
||||
let dataToSave = "Flush during write";
|
||||
let firstCallback_happened = false;
|
||||
let writeStarted = Promise.defer();
|
||||
|
@ -259,6 +338,7 @@ add_task(function flush_during_write() {
|
|||
writeStarted.resolve(aTester.waDeferred);
|
||||
}
|
||||
|
||||
setQuickMockTimer();
|
||||
tester.save(dataToSave, writeCallback).then(
|
||||
count => {
|
||||
do_check_false(firstCallback_happened);
|
||||
|
@ -281,13 +361,9 @@ add_task(function flush_during_write() {
|
|||
// Flush while dirty but write not in progress
|
||||
// The data written should be the value at the time
|
||||
// flush() is called, even if it is changed later
|
||||
//
|
||||
// It would be nice to have a mock for Timer in here, to control
|
||||
// when the steps happen, but for now we'll call the flush without
|
||||
// going back around the event loop to make sure it happens before
|
||||
// the DeferredSave timer goes off
|
||||
add_task(function flush_while_dirty() {
|
||||
let tester = DeferredSaveTester(20);
|
||||
let timerPromise = setPromiseMockTimer();
|
||||
let tester = DeferredSaveTester();
|
||||
let firstData = "Flush while dirty, valid data";
|
||||
let firstCallback_happened = false;
|
||||
|
||||
|
@ -298,10 +374,18 @@ add_task(function flush_while_dirty() {
|
|||
do_check_eq(tester.writtenData, firstData);
|
||||
}, do_report_unexpected_exception);
|
||||
|
||||
// Wait for the timer to be set, but don't trigger it so the write won't start
|
||||
let activeTimer = yield timerPromise;
|
||||
|
||||
let flushing = tester.flush();
|
||||
|
||||
// Make sure the timer was cancelled
|
||||
do_check_true(activeTimer.isCancelled);
|
||||
|
||||
// Also make sure that data changed after the flush call
|
||||
// (even without a saveChanges() call) doesn't get written
|
||||
let flushing = tester.flush();
|
||||
tester.dataToSave = "Flush while dirty, invalid data";
|
||||
|
||||
yield flushing;
|
||||
do_check_true(firstCallback_happened);
|
||||
do_check_eq(tester.writtenData, firstData);
|
||||
|
@ -314,7 +398,8 @@ add_task(function flush_while_dirty() {
|
|||
// Data for the second write should be taken at the time
|
||||
// flush() is called, even if it is modified later
|
||||
add_task(function flush_writing_dirty() {
|
||||
let tester = DeferredSaveTester(5);
|
||||
let timerPromise = setPromiseMockTimer();
|
||||
let tester = DeferredSaveTester();
|
||||
let firstData = "Flush first pass data";
|
||||
let secondData = "Flush second pass data";
|
||||
let firstCallback_happened = false;
|
||||
|
@ -332,6 +417,9 @@ add_task(function flush_writing_dirty() {
|
|||
firstCallback_happened = true;
|
||||
}, do_report_unexpected_exception);
|
||||
|
||||
// Trigger the timer callback as soon as the DeferredSave sets it
|
||||
let activeTimer = yield timerPromise;
|
||||
activeTimer.callback();
|
||||
let writer = yield writeStarted.promise;
|
||||
// the first write has started
|
||||
|
||||
|
@ -346,8 +434,9 @@ add_task(function flush_writing_dirty() {
|
|||
}, do_report_unexpected_exception);
|
||||
|
||||
let flushing = tester.flush(write_then_disable);
|
||||
// Flush should have cancelled our timer
|
||||
do_check_true(activeTimer.isCancelled);
|
||||
tester.dataToSave = "Flush, invalid data: changed late";
|
||||
yield delay(1);
|
||||
// complete the first write
|
||||
writer.resolve(firstData.length);
|
||||
// now wait for the second write / flush to complete
|
||||
|
@ -375,8 +464,9 @@ function badDataProvider() {
|
|||
// Handle cases where data provider throws
|
||||
// First, throws during a normal save
|
||||
add_task(function data_throw() {
|
||||
setQuickMockTimer();
|
||||
badDataError = expectedDataError;
|
||||
let tester = DeferredSaveTester(1, badDataProvider);
|
||||
let tester = DeferredSaveTester(badDataProvider);
|
||||
yield tester.save("data_throw").then(
|
||||
count => do_throw("Expected serialization failure"),
|
||||
error => do_check_eq(error.message, expectedDataError));
|
||||
|
@ -385,9 +475,10 @@ add_task(function data_throw() {
|
|||
// Now, throws during flush
|
||||
add_task(function data_throw_during_flush() {
|
||||
badDataError = expectedDataError;
|
||||
let tester = DeferredSaveTester(1, badDataProvider);
|
||||
let tester = DeferredSaveTester(badDataProvider);
|
||||
let firstCallback_happened = false;
|
||||
|
||||
setPromiseMockTimer();
|
||||
// Write callback should never be called
|
||||
tester.save("data_throw_during_flush", disabled_write_callback).then(
|
||||
count => do_throw("Expected serialization failure"),
|
||||
|
@ -397,6 +488,7 @@ add_task(function data_throw_during_flush() {
|
|||
firstCallback_happened = true;
|
||||
});
|
||||
|
||||
// flush() will cancel the timer
|
||||
yield tester.flush(disabled_write_callback).then(
|
||||
count => do_throw("Expected serialization failure"),
|
||||
error => do_check_eq(error.message, expectedDataError)
|
||||
|
@ -417,7 +509,8 @@ add_task(function data_throw_during_flush() {
|
|||
// write completes
|
||||
// delayed timer goes off, throws error because DeferredSave has been torn down
|
||||
add_task(function delay_flush_race() {
|
||||
let tester = DeferredSaveTester(5);
|
||||
let timerPromise = setPromiseMockTimer();
|
||||
let tester = DeferredSaveTester();
|
||||
let firstData = "First save";
|
||||
let secondData = "Second save";
|
||||
let thirdData = "Third save";
|
||||
|
@ -429,6 +522,7 @@ add_task(function delay_flush_race() {
|
|||
|
||||
// This promise won't resolve until after writeStarted
|
||||
let firstSave = tester.save(firstData, writeCallback);
|
||||
(yield timerPromise).callback();
|
||||
|
||||
let writer = yield writeStarted.promise;
|
||||
// the first write has started
|
||||
|
|
Загрузка…
Ссылка в новой задаче