This commit is contained in:
Wes Kocher 2013-12-20 18:28:28 -08:00
Родитель 971cbb78c9 23bd5a736d
Коммит dd72dd2a82
92 изменённых файлов: 3122 добавлений и 642 удалений

Просмотреть файл

@ -22,4 +22,4 @@
# changes to stick? As of bug 928195, this shouldn't be necessary! Please # changes to stick? As of bug 928195, this shouldn't be necessary! Please
# don't change CLOBBER for WebIDL changes any more. # 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 // 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. // 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(); this.beginBatchUpdate();
try { try {
gSeenWidgets.add(widget.id); gSeenWidgets.add(widget.id);

Просмотреть файл

@ -48,4 +48,5 @@ skip-if = os == "mac"
[browser_944887_destroyWidget_should_destroy_in_palette.js] [browser_944887_destroyWidget_should_destroy_in_palette.js]
[browser_945739_showInPrivateBrowsing_customize_mode.js] [browser_945739_showInPrivateBrowsing_customize_mode.js]
[browser_947987_removable_default.js] [browser_947987_removable_default.js]
[browser_948985_non_removable_defaultArea.js]
[browser_panel_toggle.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); overridePage = overridePage.replace("%OLD_VERSION%", old_mstone);
break; 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) {} } catch (ex) {}

Просмотреть файл

@ -182,9 +182,6 @@ let TabStateInternal = {
tabData.index = history.index; 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 // 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 // the cache hasn't been filled by collect() in the meantime, let's
// fill the cache with the data we received. // fill the cache with the data we received.
@ -193,6 +190,16 @@ let TabStateInternal = {
this._pendingCollections.delete(browser); 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); throw new Task.Result(tabData);
}.bind(this)); }.bind(this));
@ -219,7 +226,16 @@ let TabStateInternal = {
throw new TypeError("Expecting a tab"); throw new TypeError("Expecting a tab");
} }
if (TabStateCache.has(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); let tabData = this._collectSyncUncached(tab);
@ -228,6 +244,16 @@ let TabStateInternal = {
TabStateCache.set(tab, tabData); 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. // Prevent all running asynchronous collections from filling the cache.
// Every asynchronous data collection started before a collectSync() call // Every asynchronous data collection started before a collectSync() call
// can't expect to retrieve different data than the sync call. That's why // can't expect to retrieve different data than the sync call. That's why
@ -262,7 +288,13 @@ let TabStateInternal = {
* up-to-date. * up-to-date.
*/ */
clone: function (tab) { 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; tabData.index = history.index;
} }
// Copy data from the persistent cache.
this._copyFromPersistentCache(tab, tabData, options);
return tabData; return tabData;
}, },

Просмотреть файл

@ -64,5 +64,16 @@ this.Utils = Object.freeze({
map.set(otherKey, value); map.set(otherKey, value);
map.delete(key); 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); let disabledPromise = this._disabled.get(identifier);
if (disabledPromise) { if (disabledPromise) {
disabledPromise.then(({ conditionalExpression: previousValue }) => { disabledPromise.then(({ conditionalExpression: previousValue }) => {
aBreakpointClient.conditionalExpression = previousValue; // Setting a falsy conditional expression is redundant.
if (previousValue) {
aBreakpointClient.conditionalExpression = previousValue;
}
}); });
this._disabled.delete(identifier); this._disabled.delete(identifier);
} }

Просмотреть файл

@ -100,6 +100,7 @@ support-files =
[browser_dbg_conditional-breakpoints-01.js] [browser_dbg_conditional-breakpoints-01.js]
[browser_dbg_conditional-breakpoints-02.js] [browser_dbg_conditional-breakpoints-02.js]
[browser_dbg_conditional-breakpoints-03.js] [browser_dbg_conditional-breakpoints-03.js]
[browser_dbg_conditional-breakpoints-04.js]
[browser_dbg_controller-evaluate-01.js] [browser_dbg_controller-evaluate-01.js]
[browser_dbg_controller-evaluate-02.js] [browser_dbg_controller-evaluate-02.js]
[browser_dbg_debugger-statement.js] [browser_dbg_debugger-statement.js]

Просмотреть файл

@ -77,13 +77,13 @@ function test() {
.getAttribute("value"), "getName", .getAttribute("value"), "getName",
"Should have the right property name for 'getName' in person."); "Should have the right property name for 'getName' in person.");
is(personNode.get("getName").target.querySelector(".value") is(personNode.get("getName").target.querySelector(".value")
.getAttribute("value"), "Function", .getAttribute("value"), "_pfactory/<.getName()",
"'getName' in person should have the right value."); "'getName' in person should have the right value.");
is(personNode.get("getFoo").target.querySelector(".name") is(personNode.get("getFoo").target.querySelector(".name")
.getAttribute("value"), "getFoo", .getAttribute("value"), "getFoo",
"Should have the right property name for 'getFoo' in person."); "Should have the right property name for 'getFoo' in person.");
is(personNode.get("getFoo").target.querySelector(".value") is(personNode.get("getFoo").target.querySelector(".value")
.getAttribute("value"), "Function", .getAttribute("value"), "_pfactory/<.getFoo()",
"'getFoo' in person should have the right value."); "'getFoo' in person should have the right value.");
// Expand the function nodes. This causes their properties to be // 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", () => { gDebugger.gThreadClient.addOneTimeListener("paused", () => {
gToolbox.selectTool("webconsole").then(() => { gToolbox.selectTool("webconsole").then(() => {
ok(gToolboxTab.classList.contains("highlighted"), ok(gToolboxTab.hasAttribute("highlighted") &&
gToolboxTab.getAttribute("highlighted") == "true",
"The highlighted class is present"); "The highlighted class is present");
ok(!gToolboxTab.hasAttribute("selected") || ok(!gToolboxTab.hasAttribute("selected") ||
gToolboxTab.getAttribute("selected") != "true", gToolboxTab.getAttribute("selected") != "true",
"The tab is not selected"); "The tab is not selected");
}).then(() => gToolbox.selectTool("jsdebugger")).then(() => { }).then(() => gToolbox.selectTool("jsdebugger")).then(() => {
ok(gToolboxTab.classList.contains("highlighted"), ok(gToolboxTab.hasAttribute("highlighted") &&
gToolboxTab.getAttribute("highlighted") == "true",
"The highlighted class is present"); "The highlighted class is present");
ok(gToolboxTab.hasAttribute("selected") && ok(gToolboxTab.hasAttribute("selected") &&
gToolboxTab.getAttribute("selected") == "true", gToolboxTab.getAttribute("selected") == "true",

Просмотреть файл

@ -78,13 +78,15 @@ function testPause() {
"Debugger's tab got selected."); "Debugger's tab got selected.");
} }
gToolbox.selectTool("webconsole").then(() => { gToolbox.selectTool("webconsole").then(() => {
ok(gToolboxTab.classList.contains("highlighted"), ok(gToolboxTab.hasAttribute("highlighted") &&
gToolboxTab.getAttribute("highlighted") == "true",
"The highlighted class is present"); "The highlighted class is present");
ok(!gToolboxTab.hasAttribute("selected") || ok(!gToolboxTab.hasAttribute("selected") ||
gToolboxTab.getAttribute("selected") != "true", gToolboxTab.getAttribute("selected") != "true",
"The tab is not selected"); "The tab is not selected");
}).then(() => gToolbox.selectTool("jsdebugger")).then(() => { }).then(() => gToolbox.selectTool("jsdebugger")).then(() => {
ok(gToolboxTab.classList.contains("highlighted"), ok(gToolboxTab.hasAttribute("highlighted") &&
gToolboxTab.getAttribute("highlighted") == "true",
"The highlighted class is present"); "The highlighted class is present");
ok(gToolboxTab.hasAttribute("selected") && ok(gToolboxTab.hasAttribute("selected") &&
gToolboxTab.getAttribute("selected") == "true", gToolboxTab.getAttribute("selected") == "true",
@ -100,7 +102,8 @@ function testPause() {
function testResume() { function testResume() {
gDebugger.gThreadClient.addOneTimeListener("resumed", () => { gDebugger.gThreadClient.addOneTimeListener("resumed", () => {
gToolbox.selectTool("webconsole").then(() => { 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"); "The highlighted class is not present now after the resume");
ok(!gToolboxTab.hasAttribute("selected") || ok(!gToolboxTab.hasAttribute("selected") ||
gToolboxTab.getAttribute("selected") != "true", gToolboxTab.getAttribute("selected") != "true",

Просмотреть файл

@ -58,7 +58,7 @@ function testPauseOnExceptionsDisabled() {
is(innerNodes[0].querySelector(".name").getAttribute("value"), "this", is(innerNodes[0].querySelector(".name").getAttribute("value"), "this",
"Should have the right property name for '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'."); "Should have the right property value for 'this'.");
let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.AFTER_FRAMES_CLEARED).then(() => { let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.AFTER_FRAMES_CLEARED).then(() => {
@ -124,7 +124,7 @@ function testPauseOnExceptionsEnabled() {
is(innerNodes[0].querySelector(".name").getAttribute("value"), "this", is(innerNodes[0].querySelector(".name").getAttribute("value"), "this",
"Should have the right property name for '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'."); "Should have the right property value for 'this'.");
let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.AFTER_FRAMES_CLEARED).then(() => { let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.AFTER_FRAMES_CLEARED).then(() => {

Просмотреть файл

@ -79,7 +79,7 @@ function testPauseOnExceptionsAfterReload() {
is(innerNodes[0].querySelector(".name").getAttribute("value"), "this", is(innerNodes[0].querySelector(".name").getAttribute("value"), "this",
"Should have the right property name for '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'."); "Should have the right property value for 'this'.");
let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.AFTER_FRAMES_CLEARED).then(() => { let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.AFTER_FRAMES_CLEARED).then(() => {

Просмотреть файл

@ -22,16 +22,16 @@ function test() {
.then(() => initialChecks()) .then(() => initialChecks())
.then(() => testModification("a", "1")) .then(() => testModification("a", "1"))
.then(() => testModification("{ a: 1 }", "Object")) .then(() => testModification("{ a: 1 }", "Object"))
.then(() => testModification("[a]", "Array")) .then(() => testModification("[a]", "Array[1]"))
.then(() => testModification("b", "Object")) .then(() => testModification("b", "Object"))
.then(() => testModification("b.a", "1")) .then(() => testModification("b.a", "1"))
.then(() => testModification("c.a", "1")) .then(() => testModification("c.a", "1"))
.then(() => testModification("Infinity", "Infinity")) .then(() => testModification("Infinity", "Infinity"))
.then(() => testModification("NaN", "NaN")) .then(() => testModification("NaN", "NaN"))
.then(() => testModification("new Function", "Function")) .then(() => testModification("new Function", "anonymous()"))
.then(() => testModification("+0", "0")) .then(() => testModification("+0", "0"))
.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(() => testModification("document.title", '"Debugger test page"'))
.then(() => resumeDebuggerThenCloseAndFinish(gPanel)) .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
.then(null, aError => { .then(null, aError => {

Просмотреть файл

@ -89,14 +89,16 @@ function testExpandVariables() {
waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_PROPERTIES, 3).then(() => { waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_PROPERTIES, 3).then(() => {
is(thisVar.get("window").target.querySelector(".name").getAttribute("value"), "window", is(thisVar.get("window").target.querySelector(".name").getAttribute("value"), "window",
"Should have the right property name for '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'."); "Should have the right property value for 'window'.");
ok(thisVar.get("window").target.querySelector(".value").className.contains("token-other"), ok(thisVar.get("window").target.querySelector(".value").className.contains("token-other"),
"Should have the right token class for 'window'."); "Should have the right token class for 'window'.");
is(thisVar.get("document").target.querySelector(".name").getAttribute("value"), "document", is(thisVar.get("document").target.querySelector(".name").getAttribute("value"), "document",
"Should have the right property name for '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'."); "Should have the right property value for 'document'.");
ok(thisVar.get("document").target.querySelector(".value").className.contains("token-other"), ok(thisVar.get("document").target.querySelector(".value").className.contains("token-other"),
"Should have the right token class for 'document'."); "Should have the right token class for 'document'.");

Просмотреть файл

@ -56,7 +56,8 @@ function testScopeVariables() {
is(localEnums[0].querySelector(".name").getAttribute("value"), "this", is(localEnums[0].querySelector(".name").getAttribute("value"), "this",
"Should have the right property name for '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'."); "Should have the right property value for 'this'.");
ok(localEnums[0].querySelector(".value").className.contains("token-other"), ok(localEnums[0].querySelector(".value").className.contains("token-other"),
"Should have the right token class for 'this'."); "Should have the right token class for 'this'.");
@ -192,7 +193,8 @@ function testArgumentsProperties() {
is(argsNonEnums[0].querySelector(".name").getAttribute("value"), "callee", is(argsNonEnums[0].querySelector(".name").getAttribute("value"), "callee",
"Should have the right property name for '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'."); "Should have the right property name for 'callee'.");
ok(argsNonEnums[0].querySelector(".value").className.contains("token-other"), ok(argsNonEnums[0].querySelector(".value").className.contains("token-other"),
"Should have the right token class for 'callee'."); "Should have the right token class for 'callee'.");
@ -518,14 +520,16 @@ function testGetterSetterObject() {
is(propNonEnums[0].querySelector(".name").getAttribute("value"), "get", is(propNonEnums[0].querySelector(".name").getAttribute("value"), "get",
"Should have the right property name for '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'."); "Should have the right property value for 'get'.");
ok(propNonEnums[0].querySelector(".value").className.contains("token-other"), ok(propNonEnums[0].querySelector(".value").className.contains("token-other"),
"Should have the right token class for 'get'."); "Should have the right token class for 'get'.");
is(propNonEnums[1].querySelector(".name").getAttribute("value"), "set", is(propNonEnums[1].querySelector(".name").getAttribute("value"), "set",
"Should have the right property name for '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'."); "Should have the right property value for 'set'.");
ok(propNonEnums[1].querySelector(".value").className.contains("token-other"), ok(propNonEnums[1].querySelector(".value").className.contains("token-other"),
"Should have the right token class for 'set'."); "Should have the right token class for 'set'.");

Просмотреть файл

@ -71,12 +71,14 @@ function testGlobalScope() {
is(globalScope.get("window").target.querySelector(".name").getAttribute("value"), "window", is(globalScope.get("window").target.querySelector(".name").getAttribute("value"), "window",
"Should have the right property name for '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'."); "Should have the right property value for 'window'.");
is(globalScope.get("document").target.querySelector(".name").getAttribute("value"), "document", is(globalScope.get("document").target.querySelector(".name").getAttribute("value"), "document",
"Should have the right property name for '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'."); "Should have the right property value for 'document'.");
is(globalScope.get("undefined").target.querySelector(".name").getAttribute("value"), "undefined", 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", is(windowVar.get("window").target.querySelector(".name").getAttribute("value"), "window",
"Should have the right property name for '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'."); "Should have the right property value for 'window'.");
is(windowVar.get("document").target.querySelector(".name").getAttribute("value"), "document", is(windowVar.get("document").target.querySelector(".name").getAttribute("value"), "document",
"Should have the right property name for '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'."); "Should have the right property value for 'document'.");
is(windowVar.get("undefined").target.querySelector(".name").getAttribute("value"), "undefined", is(windowVar.get("undefined").target.querySelector(".name").getAttribute("value"), "undefined",

Просмотреть файл

@ -57,7 +57,8 @@ function testFirstWithScope() {
is(withEnums[0].querySelector(".name").getAttribute("value"), "this", is(withEnums[0].querySelector(".name").getAttribute("value"), "this",
"Should have the right property name for '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'."); "Should have the right property value for 'this'.");
ok(withEnums[0].querySelector(".value").className.contains("token-other"), ok(withEnums[0].querySelector(".value").className.contains("token-other"),
"Should have the right token class for 'this'."); "Should have the right token class for 'this'.");
@ -131,7 +132,7 @@ function testSecondWithScope() {
is(secondWithScope.get("random").target.querySelector(".name").getAttribute("value"), "random", is(secondWithScope.get("random").target.querySelector(".name").getAttribute("value"), "random",
"Should have the right property name for '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'."); "Should have the right property value for 'random'.");
ok(secondWithScope.get("random").target.querySelector(".value").className.contains("token-other"), ok(secondWithScope.get("random").target.querySelector(".value").className.contains("token-other"),
"Should have the right token class for 'random'."); "Should have the right token class for 'random'.");

Просмотреть файл

@ -54,7 +54,7 @@ function initialChecks() {
is(arrayVar.target.querySelector(".name").getAttribute("value"), "largeArray", is(arrayVar.target.querySelector(".name").getAttribute("value"), "largeArray",
"Should have the right property name for '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'."); "Should have the right property value for 'largeArray'.");
ok(arrayVar.target.querySelector(".value").className.contains("token-other"), ok(arrayVar.target.querySelector(".value").className.contains("token-other"),
"Should have the right token class for 'largeArray'."); "Should have the right token class for 'largeArray'.");

Просмотреть файл

@ -62,7 +62,7 @@ function performTest() {
is(buttonVar.target.querySelector(".name").getAttribute("value"), "button", is(buttonVar.target.querySelector(".name").getAttribute("value"), "button",
"Should have the right property name for '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'."); "Should have the right property value for 'button'.");
ok(buttonVar.target.querySelector(".value").className.contains("token-other"), ok(buttonVar.target.querySelector(".value").className.contains("token-other"),
"Should have the right token class for 'button'."); "Should have the right token class for 'button'.");
@ -76,7 +76,8 @@ function performTest() {
is(documentVar.target.querySelector(".name").getAttribute("value"), "document", is(documentVar.target.querySelector(".name").getAttribute("value"), "document",
"Should have the right property name for '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'."); "Should have the right property value for 'document'.");
ok(documentVar.target.querySelector(".value").className.contains("token-other"), ok(documentVar.target.querySelector(".value").className.contains("token-other"),
"Should have the right token class for 'document'."); "Should have the right token class for 'document'.");
@ -98,14 +99,14 @@ function performTest() {
is(buttonVar.get("childNodes").target.querySelector(".name").getAttribute("value"), "childNodes", is(buttonVar.get("childNodes").target.querySelector(".name").getAttribute("value"), "childNodes",
"Should have the right property name for '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'."); "Should have the right property value for 'childNodes'.");
ok(buttonVar.get("childNodes").target.querySelector(".value").className.contains("token-other"), ok(buttonVar.get("childNodes").target.querySelector(".value").className.contains("token-other"),
"Should have the right token class for 'childNodes'."); "Should have the right token class for 'childNodes'.");
is(buttonVar.get("onclick").target.querySelector(".name").getAttribute("value"), "onclick", is(buttonVar.get("onclick").target.querySelector(".name").getAttribute("value"), "onclick",
"Should have the right property name for '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'."); "Should have the right property value for 'onclick'.");
ok(buttonVar.get("onclick").target.querySelector(".value").className.contains("token-other"), ok(buttonVar.get("onclick").target.querySelector(".value").className.contains("token-other"),
"Should have the right token class for 'onclick'."); "Should have the right token class for 'onclick'.");
@ -119,7 +120,7 @@ function performTest() {
is(documentVar.get("childNodes").target.querySelector(".name").getAttribute("value"), "childNodes", is(documentVar.get("childNodes").target.querySelector(".name").getAttribute("value"), "childNodes",
"Should have the right property name for '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'."); "Should have the right property value for 'childNodes'.");
ok(documentVar.get("childNodes").target.querySelector(".value").className.contains("token-other"), ok(documentVar.get("childNodes").target.querySelector(".value").className.contains("token-other"),
"Should have the right token class for 'childNodes'."); "Should have the right token class for 'childNodes'.");
@ -144,7 +145,7 @@ function performTest() {
is(buttonAsProtoProtoVar.target.querySelector(".name").getAttribute("value"), "__proto__", is(buttonAsProtoProtoVar.target.querySelector(".name").getAttribute("value"), "__proto__",
"Should have the right property name for '__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__'."); "Should have the right property value for '__proto__'.");
ok(buttonAsProtoProtoVar.target.querySelector(".value").className.contains("token-other"), ok(buttonAsProtoProtoVar.target.querySelector(".value").className.contains("token-other"),
"Should have the right token class for '__proto__'."); "Should have the right token class for '__proto__'.");
@ -173,14 +174,14 @@ function performTest() {
is(buttonAsProtoProtoVar.get("childNodes").target.querySelector(".name").getAttribute("value"), "childNodes", is(buttonAsProtoProtoVar.get("childNodes").target.querySelector(".name").getAttribute("value"), "childNodes",
"Should have the right property name for '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'."); "Should have the right property value for 'childNodes'.");
ok(buttonAsProtoProtoVar.get("childNodes").target.querySelector(".value").className.contains("token-other"), ok(buttonAsProtoProtoVar.get("childNodes").target.querySelector(".value").className.contains("token-other"),
"Should have the right token class for 'childNodes'."); "Should have the right token class for 'childNodes'.");
is(buttonAsProtoProtoVar.get("onclick").target.querySelector(".name").getAttribute("value"), "onclick", is(buttonAsProtoProtoVar.get("onclick").target.querySelector(".name").getAttribute("value"), "onclick",
"Should have the right property name for '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'."); "Should have the right property value for 'onclick'.");
ok(buttonAsProtoProtoVar.get("onclick").target.querySelector(".value").className.contains("token-other"), ok(buttonAsProtoProtoVar.get("onclick").target.querySelector(".value").className.contains("token-other"),
"Should have the right token class for 'onclick'."); "Should have the right token class for 'onclick'.");

Просмотреть файл

@ -65,20 +65,20 @@ function unhighlightTab(toolId) {
function checkHighlighted(toolId) { function checkHighlighted(toolId) {
let tab = toolbox.doc.getElementById("toolbox-tab-" + 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", ok(!tab.hasAttribute("selected") || tab.getAttribute("selected") != "true",
"The tab is not selected"); "The tab is not selected");
} }
function checkNoHighlightWhenSelected(toolId) { function checkNoHighlightWhenSelected(toolId) {
let tab = toolbox.doc.getElementById("toolbox-tab-" + 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", ok(tab.hasAttribute("selected") && tab.getAttribute("selected") == "true",
"and the tab is selected, so the orange glow will not be present."); "and the tab is selected, so the orange glow will not be present.");
} }
function checkNoHighlight(toolId) { function checkNoHighlight(toolId) {
let tab = toolbox.doc.getElementById("toolbox-tab-" + toolId); let tab = toolbox.doc.getElementById("toolbox-tab-" + toolId);
ok(!tab.classList.contains("highlighted"), ok(!tab.hasAttribute("highlighted"),
"The highlighted class is not present"); "The highlighted attribute is not present");
} }

Просмотреть файл

@ -796,7 +796,7 @@ Toolbox.prototype = {
*/ */
highlightTool: function(id) { highlightTool: function(id) {
let tab = this.doc.getElementById("toolbox-tab-" + 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) { unhighlightTool: function(id) {
let tab = this.doc.getElementById("toolbox-tab-" + 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", XPCOMUtils.defineLazyModuleGetter(this, "devtools",
"resource://gre/modules/devtools/Loader.jsm"); "resource://gre/modules/devtools/Loader.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
"resource://gre/modules/PluralForm.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "clipboardHelper", XPCOMUtils.defineLazyServiceGetter(this, "clipboardHelper",
"@mozilla.org/widget/clipboardhelper;1", "@mozilla.org/widget/clipboardhelper;1",
"nsIClipboardHelper"); "nsIClipboardHelper");
@ -2346,7 +2349,10 @@ Variable.prototype = Heritage.extend(Scope.prototype, {
this._valueLabel.classList.remove(VariablesView.getClass(prevGrip)); this._valueLabel.classList.remove(VariablesView.getClass(prevGrip));
} }
this._valueGrip = aGrip; this._valueGrip = aGrip;
this._valueString = VariablesView.getString(aGrip, true); this._valueString = VariablesView.getString(aGrip, {
concise: true,
noEllipsis: true,
});
this._valueClassName = VariablesView.getClass(aGrip); this._valueClassName = VariablesView.getClass(aGrip);
this._valueLabel.classList.add(this._valueClassName); this._valueLabel.classList.add(this._valueClassName);
@ -3118,12 +3124,16 @@ VariablesView.getGrip = function(aValue) {
* *
* @param any aGrip * @param any aGrip
* @see Variable.setGrip * @see Variable.setGrip
* @param boolean aConciseFlag * @param object aOptions
* Return a concisely formatted property string. * 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 * @return string
* The formatted property string. * The formatted property string.
*/ */
VariablesView.getString = function(aGrip, aConciseFlag) { VariablesView.getString = function(aGrip, aOptions = {}) {
if (aGrip && typeof aGrip == "object") { if (aGrip && typeof aGrip == "object") {
switch (aGrip.type) { switch (aGrip.type) {
case "undefined": case "undefined":
@ -3133,18 +3143,30 @@ VariablesView.getString = function(aGrip, aConciseFlag) {
case "-Infinity": case "-Infinity":
case "-0": case "-0":
return aGrip.type; return aGrip.type;
case "longString":
return "\"" + aGrip.initial + "\"";
default: default:
if (!aConciseFlag) { let stringifier = VariablesView.stringifiers.byType[aGrip.type];
return "[" + aGrip.type + " " + aGrip.class + "]"; 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) { switch (typeof aGrip) {
case "string": case "string":
return "\"" + aGrip + "\""; return VariablesView.stringifiers.byType.string(aGrip, aOptions);
case "boolean": case "boolean":
return aGrip ? "true" : "false"; return aGrip ? "true" : "false";
case "number": 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. * 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, "&amp;")
.replace(/"/g, "&quot;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;");
}
/** /**
* An Editable encapsulates the UI of an edit box that overlays a label, * An Editable encapsulates the UI of an edit box that overlays a label,
* allowing the user to edit the value. * allowing the user to edit the value.

Просмотреть файл

@ -997,10 +997,12 @@ Messages.Extended.prototype = Heritage.extend(Messages.Simple.prototype,
} }
let result = this.document.createDocumentFragment(); let result = this.document.createDocumentFragment();
if (!isPrimitive || (!this._quoteStrings && typeof piece == "string")) { if (isPrimitive) {
result.textContent = piece; result.textContent = VariablesView.getString(piece, {
noStringQuotes: !this._quoteStrings,
});
} else { } else {
result.textContent = VariablesView.getString(piece); result.textContent = piece;
} }
return result; return result;
@ -1219,7 +1221,7 @@ Widgets.JSObject.prototype = Heritage.extend(Widgets.BaseWidget.prototype,
_onClick: function() _onClick: function()
{ {
this.output.openVariablesView({ this.output.openVariablesView({
label: this.element.textContent, label: VariablesView.getString(this.objectActor, { concise: true }),
objectActor: this.objectActor, objectActor: this.objectActor,
autofocus: true, autofocus: true,
}); });
@ -1273,11 +1275,10 @@ Widgets.LongString.prototype = Heritage.extend(Widgets.BaseWidget.prototype,
*/ */
_renderString: function(str) _renderString: function(str)
{ {
if (this.message._quoteStrings) { this.element.textContent = VariablesView.getString(str, {
this.element.textContent = VariablesView.getString(str); noStringQuotes: !this.message._quoteStrings,
} else { noEllipsis: true,
this.element.textContent = str; });
}
}, },
/** /**

Просмотреть файл

@ -64,6 +64,10 @@ support-files =
test-console-extras.html test-console-extras.html
test-console-replaced-api.html test-console-replaced-api.html
test-console.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-consoleiframes.html
test-data.json test-data.json
test-data.json^headers^ test-data.json^headers^
@ -167,7 +171,6 @@ support-files =
[browser_webconsole_bug_597136_network_requests_from_chrome.js] [browser_webconsole_bug_597136_network_requests_from_chrome.js]
[browser_webconsole_bug_597460_filter_scroll.js] [browser_webconsole_bug_597460_filter_scroll.js]
[browser_webconsole_bug_597756_reopen_closed_tab.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_599725_response_headers.js]
[browser_webconsole_bug_600183_charset.js] [browser_webconsole_bug_600183_charset.js]
[browser_webconsole_bug_601177_log_levels.js] [browser_webconsole_bug_601177_log_levels.js]
@ -248,3 +251,8 @@ run-if = os == "mac"
[browser_webconsole_expandable_timestamps.js] [browser_webconsole_expandable_timestamps.js]
[browser_webconsole_autocomplete_in_debugger_stackframe.js] [browser_webconsole_autocomplete_in_debugger_stackframe.js]
[browser_webconsole_autocomplete_popup_close_on_tab_switch.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"); let anchor = msg.querySelector("a");
ok(anchor, "object anchor"); 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); gJSTerm.once("variablesview-fetched", onFooObjFetch);
EventUtils.synthesizeMouse(anchor, 2, 2, {}, gWebConsole.iframeWindow) EventUtils.synthesizeMouse(anchor, 2, 2, {}, gWebConsole.iframeWindow)
@ -76,7 +77,8 @@ function onExecuteWindow(msg)
ok(msg, "output message found"); ok(msg, "output message found");
let anchor = msg.querySelector("a"); let anchor = msg.querySelector("a");
ok(anchor, "object anchor"); 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); gJSTerm.once("variablesview-fetched", onWindowFetch);
EventUtils.synthesizeMouse(anchor, 2, 2, {}, gWebConsole.iframeWindow) EventUtils.synthesizeMouse(anchor, 2, 2, {}, gWebConsole.iframeWindow)

Просмотреть файл

@ -46,7 +46,7 @@ function onConsoleMessage(aResults)
{ {
let clickable = aResults[0].clickableElements[0]; let clickable = aResults[0].clickableElements[0];
ok(clickable, "clickable object found"); ok(clickable, "clickable object found");
isnot(clickable.textContent.indexOf("[object Object]"), -1, isnot(clickable.textContent.indexOf('{hello: "world!",'), -1,
"message text check"); "message text check");
gJSTerm.once("variablesview-fetched", onObjFetch); gJSTerm.once("variablesview-fetched", onObjFetch);

Просмотреть файл

@ -71,7 +71,7 @@ function test()
}, },
{ {
name: "console.error output", name: "console.error output",
text: /\bbug851231-error\b.+\[object Object\]/, text: /\bbug851231-error\b.+\{bug851231prop:\s"bug851231value"\}/,
category: CATEGORY_WEBDEV, category: CATEGORY_WEBDEV,
severity: SEVERITY_ERROR, severity: SEVERITY_ERROR,
objects: true, objects: true,
@ -91,7 +91,7 @@ function test()
}, },
{ {
name: "console.dir output", name: "console.dir output",
consoleDir: "[object XULDocument]", consoleDir: "XULDocument {",
}, },
{ {
name: "console.time output", name: "console.time output",

Просмотреть файл

@ -34,7 +34,7 @@ function performTest(hud)
}).then(([result]) => { }).then(([result]) => {
let clickable = result.clickableElements[0]; let clickable = result.clickableElements[0];
ok(clickable, "the console.log() object anchor was found"); 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"); "clickable node content is correct");
hud.jsterm.once("variablesview-fetched", hud.jsterm.once("variablesview-fetched",

Просмотреть файл

@ -30,7 +30,7 @@ function consoleOpened(hud)
waitForMessages({ waitForMessages({
webconsole: hud, webconsole: hud,
messages: [{ messages: [{
text: "[object HTMLDocument]", text: "HTMLDocument \u2192 data:text/html;charset=utf8",
category: CATEGORY_OUTPUT, category: CATEGORY_OUTPUT,
objects: true, objects: true,
}], }],
@ -90,7 +90,7 @@ function testParagraphs()
waitForMessages({ waitForMessages({
webconsole: gWebConsole, webconsole: gWebConsole,
messages: [{ messages: [{
text: "[object NodeList]", text: "NodeList [",
category: CATEGORY_OUTPUT, category: CATEGORY_OUTPUT,
objects: true, objects: true,
}], }],

Просмотреть файл

@ -28,7 +28,8 @@ function consoleOpened(hud)
function onExecuteFooObj(msg) function onExecuteFooObj(msg)
{ {
ok(msg, "output message found"); 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"); let anchor = msg.querySelector("a");
ok(anchor, "object link found"); ok(anchor, "object link found");

Просмотреть файл

@ -62,7 +62,8 @@ function onFramesAdded()
function onExecuteFooObj(msg) function onExecuteFooObj(msg)
{ {
ok(msg, "output message found"); 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"); let anchor = msg.querySelector("a");
ok(anchor, "object link found"); ok(anchor, "object link found");

Просмотреть файл

@ -57,7 +57,8 @@ function onFramesAdded()
function onExecuteFooObj(msg) function onExecuteFooObj(msg)
{ {
ok(msg, "output message found"); 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"); let anchor = msg.querySelector("a");
ok(anchor, "object link found"); ok(anchor, "object link found");

Просмотреть файл

@ -29,7 +29,7 @@ function test()
findVariableViewProperties(aVar, [ findVariableViewProperties(aVar, [
{ name: "testProp", value: "testValue" }, { name: "testProp", value: "testValue" },
{ name: "document", value: "HTMLDocument" }, { name: "document", value: /HTMLDocument \u2192 data:/ },
], { webconsole: hud }).then(finishTest); ], { webconsole: hud }).then(finishTest);
} }
} }

Просмотреть файл

@ -28,10 +28,10 @@ function performTest(hud)
ok(!hud.outputNode.querySelector("#foobar"), "no #foobar element found"); ok(!hud.outputNode.querySelector("#foobar"), "no #foobar element found");
ok(msg, "eval output node found"); ok(msg, "eval output node found");
is(msg.textContent.indexOf("HTMLDivElement"), -1, is(msg.textContent.indexOf("<div>"), -1,
"HTMLDivElement string is not displayed"); "<div> string is not displayed");
isnot(msg.textContent.indexOf("HTMLParagraphElement"), -1, isnot(msg.textContent.indexOf("<p>"), -1,
"HTMLParagraphElement string is displayed"); "<p> string is displayed");
EventUtils.synthesizeMouseAtCenter(msg, {type: "mousemove"}); EventUtils.synthesizeMouseAtCenter(msg, {type: "mousemove"});
ok(!gBrowser._bug772506, "no content variable"); ok(!gBrowser._bug772506, "no content variable");

Просмотреть файл

@ -80,9 +80,8 @@ function tab2Loaded(aEvent) {
function testEnd() { function testEnd() {
ok(noErrors, "there were no errors"); ok(noErrors, "there were no errors");
Array.forEach(win1.gBrowser.tabs, function(aTab) { win1.gBrowser.removeTab(tab1);
win1.gBrowser.removeTab(aTab);
});
Array.forEach(win2.gBrowser.tabs, function(aTab) { Array.forEach(win2.gBrowser.tabs, function(aTab) {
win2.gBrowser.removeTab(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); popup._panel.removeEventListener("popuphidden", autocompletePopupHidden, false);
ok(!popup.isOpen, "popup is not open"); 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"; let inputStr = "document.b";
jsterm.setInputValue(inputStr); jsterm.setInputValue(inputStr);
EventUtils.synthesizeKey("o", {}); EventUtils.synthesizeKey("o", {});
let testStr = inputStr.replace(/./g, " ") + " "; let testStr = inputStr.replace(/./g, " ") + " ";
waitForSuccess({
name: "autocomplete shows document.body",
validatorFn: function()
{
return completeNode.value == testStr + "dy";
},
successFn: testPropertyPanel,
failureFn: finishTest,
});
} }
function testPropertyPanel() function testPropertyPanel()
@ -87,7 +83,6 @@ function testPropertyPanel()
function onVariablesViewReady(aEvent, aView) function onVariablesViewReady(aEvent, aView)
{ {
findVariableViewProperties(aView, [ findVariableViewProperties(aView, [
{ name: "body", value: "HTMLBodyElement" }, { name: "body", value: "<body>" },
], { webconsole: gHUD }).then(finishTest); ], { webconsole: gHUD }).then(finishTest);
} }

Просмотреть файл

@ -88,8 +88,7 @@ function performWebConsoleTests(hud)
function onNodeOutput(node) function onNodeOutput(node)
{ {
isnot(node.textContent.indexOf("[object HTMLHeadingElement"), -1, isnot(node.textContent.indexOf("<h1>"), -1, "correct output for $0");
"correct output for $0");
jsterm.clearOutput(); jsterm.clearOutput();
jsterm.execute("$0.textContent = 'bug653531'", onNodeUpdate); jsterm.execute("$0.textContent = 'bug653531'", onNodeUpdate);

Просмотреть файл

@ -22,8 +22,8 @@ function consoleOpened(hud) {
function testConsoleDir(hud, ev, view) { function testConsoleDir(hud, ev, view) {
findVariableViewProperties(view, [ findVariableViewProperties(view, [
{ name: "__proto__.__proto__.querySelectorAll", value: "Function" }, { name: "__proto__.__proto__.querySelectorAll", value: "querySelectorAll()" },
{ name: "location", value: "Location" }, { name: "location", value: /Location \u2192 data:Web/ },
{ name: "__proto__.write", value: "Function" }, { name: "__proto__.write", value: "write()" },
], { webconsole: hud }).then(finishTest); ], { webconsole: hud }).then(finishTest);
} }

Просмотреть файл

@ -54,7 +54,7 @@ function consoleOpened(hud)
waitForMessages({ waitForMessages({
webconsole: gWebConsole, webconsole: gWebConsole,
messages: [{ messages: [{
text: "[object Function]", text: "function _pfactory/<.getName()",
category: CATEGORY_OUTPUT, category: CATEGORY_OUTPUT,
objects: true, objects: true,
}], }],

Просмотреть файл

@ -118,7 +118,7 @@ function testJSTerm(hud)
jsterm.clearOutput(); jsterm.clearOutput();
jsterm.execute("pprint({b:2, a:1})"); jsterm.execute("pprint({b:2, a:1})");
checkResult('" b: 2\n a: 1"', "pprint()"); checkResult("\" b: 2\\n a: 1\"", "pprint()");
yield undefined; yield undefined;
// check instanceof correctness, bug 599940 // check instanceof correctness, bug 599940
@ -154,7 +154,7 @@ function testJSTerm(hud)
// bug 614561 // bug 614561
jsterm.clearOutput(); jsterm.clearOutput();
jsterm.execute("pprint('hi')"); jsterm.execute("pprint('hi')");
checkResult('" 0: "h"\n 1: "i""', "pprint('hi')"); checkResult("\" 0: \\\"h\\\"\\n 1: \\\"i\\\"\"", "pprint('hi')");
yield undefined; yield undefined;
// check that pprint(function) shows function source, bug 618344 // 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&quot;&lt;a&gt;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&quot;&lt;a&gt;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 * 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/. */ * 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; let {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
console = Cu.import("resource://gre/modules/devtools/Console.jsm", {}).console; let utils = devtools.require("devtools/toolkit/webconsole/utils");
promise = Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js", {}).Promise; TargetFactory = devtools.TargetFactory;
let tools = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools;
let utils = tools.require("devtools/toolkit/webconsole/utils");
TargetFactory = tools.TargetFactory;
WebConsoleUtils = utils.Utils; WebConsoleUtils = utils.Utils;
require = tools.require; require = devtools.require;
})(); })();
// promise._reportErrors = true; // please never leave me. // promise._reportErrors = true; // please never leave me.
@ -152,15 +152,20 @@ function findLogEntry(aString)
* @param function [aCallback] * @param function [aCallback]
* Optional function to invoke after the Web Console completes * Optional function to invoke after the Web Console completes
* initialization (web-console-created). * initialization (web-console-created).
* @return object
* A promise that is resolved once the web console is open.
*/ */
function openConsole(aTab, aCallback = function() { }) function openConsole(aTab, aCallback = function() { })
{ {
let deferred = promise.defer();
let target = TargetFactory.forTab(aTab || tab); let target = TargetFactory.forTab(aTab || tab);
gDevTools.showToolbox(target, "webconsole").then(function(toolbox) { gDevTools.showToolbox(target, "webconsole").then(function(toolbox) {
let hud = toolbox.getCurrentPanel().hud; let hud = toolbox.getCurrentPanel().hud;
hud.jsterm._lazyVariablesView = false; hud.jsterm._lazyVariablesView = false;
aCallback(hud); aCallback(hud);
deferred.resolve(hud);
}); });
return deferred.promise;
} }
/** /**
@ -1259,3 +1264,153 @@ function whenDelayedStartupFinished(aWindow, aCallback)
} }
}, "browser-delayed-startup-finished", false); }, "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 = []; let clipboardArray = [];
args.forEach((aValue) => { args.forEach((aValue) => {
clipboardArray.push(VariablesView.getString(aValue)); clipboardArray.push(VariablesView.getString(aValue));
if (aValue && typeof aValue == "object" &&
aValue.type == "longString") {
clipboardArray.push(l10n.getStr("longStringEllipsis"));
}
}); });
clipboardText = clipboardArray.join(" "); clipboardText = clipboardArray.join(" ");
break; break;
@ -3103,7 +3099,7 @@ JSTerm.prototype = {
aAfterMessage._objectActors.add(helperResult.object.actor); aAfterMessage._objectActors.add(helperResult.object.actor);
} }
this.openVariablesView({ this.openVariablesView({
label: VariablesView.getString(helperResult.object), label: VariablesView.getString(helperResult.object, { concise: true }),
objectActor: helperResult.object, objectActor: helperResult.object,
}); });
break; break;
@ -4265,6 +4261,7 @@ JSTerm.prototype = {
popup.selectNextItem(); popup.selectNextItem();
} }
this.emit("autocomplete-updated");
aCallback && aCallback(this); aCallback && aCallback(this);
}, },

Просмотреть файл

@ -202,8 +202,8 @@ loadingText=Loading\u2026
# viewer when there is an error loading a file # viewer when there is an error loading a file
errorLoadingText=Error loading source:\n errorLoadingText=Error loading source:\n
# LOCALIZATION NOTE (emptyStackText): The text that is displayed in the watch # LOCALIZATION NOTE (addWatchExpressionText): The text that is displayed in the
# expressions list to add a new item. # watch expressions list to add a new item.
addWatchExpressionText=Add watch expression addWatchExpressionText=Add watch expression
# LOCALIZATION NOTE (emptyVariablesText): The text that is displayed in the # LOCALIZATION NOTE (emptyVariablesText): The text that is displayed in the
@ -225,6 +225,19 @@ watchExpressionsScopeLabel=Watch expressions
# the global scope. # the global scope.
globalScopeLabel=Global 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 # LOCALIZATION NOTE (variablesEditableNameTooltip): The text that is displayed
# in the variables list on an item with an editable name. # in the variables list on an item with an editable name.
variablesEditableNameTooltip=Double click to edit variablesEditableNameTooltip=Double click to edit

Просмотреть файл

@ -100,11 +100,26 @@ XPCOMUtils.defineLazyGetter(this, "DEFAULT_ITEMS", function() {
return result; return result;
}); });
const ALL_BUILTIN_ITEMS = [ XPCOMUtils.defineLazyGetter(this, "ALL_BUILTIN_ITEMS", function() {
"fullscreen-button", // These special cases are for click events on built-in items that are
"switch-to-metro-button", // contained within customizable items (like the navigation widget).
"bookmarks-menu-button", 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 = [ const OTHER_MOUSEUP_MONITORED_ITEMS = [
"PlacesChevron", "PlacesChevron",
@ -318,6 +333,14 @@ this.BrowserUITelemetry = {
// Base case - we clicked directly on one of our built-in items, // Base case - we clicked directly on one of our built-in items,
// and we can go ahead and register that click. // and we can go ahead and register that click.
this._countMouseUpEvent("click-builtin-item", item.id, aEvent.button); 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; return aNode.id;
} }

Просмотреть файл

@ -51,7 +51,7 @@
display: none; display: none;
} }
.devtools-toolbarbutton:not([checked=true]):hover:active { .devtools-toolbarbutton:not([checked]):hover:active {
border-color: hsla(210,8%,5%,.6); 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)); 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); 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@; 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@; 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@; 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@; 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; color: #f5f7fa;
background-image: linear-gradient(#1d4f73, #1d4f73), @solidSeparator@; 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@; 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@; background-image: linear-gradient(#1f3e4f, #1f3e4f), @solidSeparator@;
} }
@ -520,7 +520,7 @@
} }
.devtools-tab:active > image, .devtools-tab:active > image,
.devtools-tab[selected=true] > image { .devtools-tab[selected] > image {
opacity: 1; opacity: 1;
} }
@ -534,7 +534,7 @@
color: #f5f7fa; color: #f5f7fa;
} }
#toolbox-tabs .devtools-tab[selected=true] { #toolbox-tabs .devtools-tab[selected] {
color: #f5f7fa; color: #f5f7fa;
background-color: #1a4666; background-color: #1a4666;
box-shadow: 0 2px 0 #d7f1ff inset, box-shadow: 0 2px 0 #d7f1ff inset,
@ -542,32 +542,32 @@
0 -2px 0 rgba(0,0,0,.2) inset; 0 -2px 0 rgba(0,0,0,.2) inset;
} }
.devtools-tab[selected=true]:not(:first-child), .devtools-tab[selected]:not(:first-child),
.devtools-tab.highlighted:not(:first-child) { .devtools-tab[highlighted]:not(:first-child) {
border-width: 0; border-width: 0;
-moz-padding-start: 1px; -moz-padding-start: 1px;
} }
.devtools-tab[selected=true]:last-child, .devtools-tab[selected]:last-child,
.devtools-tab.highlighted:last-child { .devtools-tab[highlighted]:last-child {
-moz-padding-end: 1px; -moz-padding-end: 1px;
} }
.devtools-tab[selected=true] + .devtools-tab, .devtools-tab[selected] + .devtools-tab,
.devtools-tab.highlighted + .devtools-tab { .devtools-tab[highlighted] + .devtools-tab {
-moz-border-start-width: 0; -moz-border-start-width: 0;
-moz-padding-start: 1px; -moz-padding-start: 1px;
} }
.devtools-tab:not([selected=true]).highlighted { .devtools-tab:not([selected])[highlighted] {
color: #f5f7fa; color: #f5f7fa;
background-color: hsla(99,100%,14%,.2); background-color: hsla(99,100%,14%,.2);
box-shadow: 0 2px 0 #7bc107 inset; box-shadow: 0 2px 0 #7bc107 inset;
} }
.devtools-tab:not(.highlighted) > .highlighted-icon, .devtools-tab:not([highlighted]) > .highlighted-icon,
.devtools-tab[selected=true] > .highlighted-icon, .devtools-tab[selected] > .highlighted-icon,
.devtools-tab:not([selected=true]).highlighted > .default-icon { .devtools-tab:not([selected])[highlighted] > .default-icon {
visibility: collapse; visibility: collapse;
} }

Просмотреть файл

@ -23,7 +23,7 @@ interface nsIMarkupDocumentViewer;
[ref] native nsIMarkupDocumentViewerTArray(nsTArray<nsCOMPtr<nsIMarkupDocumentViewer> >); [ref] native nsIMarkupDocumentViewerTArray(nsTArray<nsCOMPtr<nsIMarkupDocumentViewer> >);
[scriptable, uuid(3528324f-f5d3-4724-bd8d-9233a7114112)] [scriptable, uuid(7aea9561-5346-401c-b40e-418688da2d0d)]
interface nsIMarkupDocumentViewer : nsISupports interface nsIMarkupDocumentViewer : nsISupports
{ {
@ -82,6 +82,18 @@ interface nsIMarkupDocumentViewer : nsISupports
*/ */
void changeMaxLineBoxWidth(in int32_t maxLineBoxWidth); 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 * Render the document as if being viewed on a device with the specified
* media type. This will cause a reflow. * media type. This will cause a reflow.

Просмотреть файл

@ -2700,6 +2700,17 @@ struct LineBoxInfo
nscoord mMaxLineBoxWidth; nscoord mMaxLineBoxWidth;
}; };
static void
ChangeChildPaintingEnabled(nsIMarkupDocumentViewer* aChild, void* aClosure)
{
bool* enablePainting = (bool*) aClosure;
if (*enablePainting) {
aChild->ResumePainting();
} else {
aChild->PausePainting();
}
}
static void static void
ChangeChildMaxLineBoxWidth(nsIMarkupDocumentViewer* aChild, void* aClosure) ChangeChildMaxLineBoxWidth(nsIMarkupDocumentViewer* aChild, void* aClosure)
{ {
@ -3126,7 +3137,36 @@ NS_IMETHODIMP nsDocumentViewer::AppendSubtree(nsTArray<nsCOMPtr<nsIMarkupDocumen
return NS_OK; 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. // Change the max line box width for all children.
struct LineBoxInfo lbi = { aMaxLineBoxWidth }; struct LineBoxInfo lbi = { aMaxLineBoxWidth };

Просмотреть файл

@ -129,10 +129,10 @@ typedef struct CapturingContentInfo {
} CapturingContentInfo; } CapturingContentInfo;
// f5b542a9-eaf0-4560-a656-37a9d379864c // 0e4f2b36-7ab8-43c5-b912-5c311566297c
#define NS_IPRESSHELL_IID \ #define NS_IPRESSHELL_IID \
{ 0xf5b542a9, 0xeaf0, 0x4560, \ { 0xde498c49, 0xf83f, 0x47bf, \
{ 0x37, 0xa9, 0xd3, 0x79, 0x86, 0x4c } } {0x8c, 0xc6, 0x8f, 0xf8, 0x74, 0x62, 0x22, 0x23 } }
// debug VerifyReflow flags // debug VerifyReflow flags
#define VERIFY_REFLOW_ON 0x01 #define VERIFY_REFLOW_ON 0x01
@ -836,6 +836,20 @@ public:
*/ */
bool IsPaintingSuppressed() const { return mPaintingSuppressed; } 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. * Unsuppress painting.
*/ */
@ -1601,6 +1615,7 @@ protected:
bool mFontSizeInflationForceEnabled; bool mFontSizeInflationForceEnabled;
bool mFontSizeInflationDisabledInMasterProcess; bool mFontSizeInflationDisabledInMasterProcess;
bool mFontSizeInflationEnabled; bool mFontSizeInflationEnabled;
bool mPaintingIsFrozen;
// Dirty bit indicating that mFontSizeInflationEnabled needs to be recomputed. // Dirty bit indicating that mFontSizeInflationEnabled needs to be recomputed.
bool mFontSizeInflationEnabledIsDirty; bool mFontSizeInflationEnabledIsDirty;

Просмотреть файл

@ -724,6 +724,8 @@ PresShell::PresShell()
"layout.reflow.synthMouseMove", true); "layout.reflow.synthMouseMove", true);
addedSynthMouseMove = true; addedSynthMouseMove = true;
} }
mPaintingIsFrozen = false;
} }
NS_IMPL_ISUPPORTS7(PresShell, nsIPresShell, nsIDocumentObserver, NS_IMPL_ISUPPORTS7(PresShell, nsIPresShell, nsIDocumentObserver,
@ -744,6 +746,13 @@ PresShell::~PresShell()
mLastCallbackEventRequest == nullptr, mLastCallbackEventRequest == nullptr,
"post-reflow queues not empty. This means we're leaking"); "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 #ifdef DEBUG
MOZ_ASSERT(mPresArenaAllocCount == 0, MOZ_ASSERT(mPresArenaAllocCount == 0,
"Some pres arena objects were not freed"); "Some pres arena objects were not freed");
@ -9931,3 +9940,23 @@ nsIPresShell::SetMaxLineBoxWidth(nscoord aMaxLineBoxWidth)
FrameNeedsReflow(GetRootFrame(), eResize, NS_FRAME_HAS_DIRTY_CHILDREN); 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 ThemeChanged() MOZ_OVERRIDE { mPresContext->ThemeChanged(); }
virtual void BackingScaleFactorChanged() MOZ_OVERRIDE { mPresContext->UIResolutionChanged(); } virtual void BackingScaleFactorChanged() MOZ_OVERRIDE { mPresContext->UIResolutionChanged(); }
virtual void PausePainting() MOZ_OVERRIDE;
virtual void ResumePainting() MOZ_OVERRIDE;
void UpdateImageVisibility(); void UpdateImageVisibility();
nsRevocableEventPtr<nsRunnableMethod<PresShell> > mUpdateImageVisibilityEvent; nsRevocableEventPtr<nsRunnableMethod<PresShell> > mUpdateImageVisibilityEvent;

Просмотреть файл

@ -66,6 +66,7 @@ final public class InputMethods {
@RobocopTarget @RobocopTarget
public static boolean shouldDisableUrlBarUpdate(Context context) { public static boolean shouldDisableUrlBarUpdate(Context context) {
String inputMethod = getCurrentInputMethod(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); return METHOD_HTC_TOUCH_INPUT.equals(inputMethod);
} }

Просмотреть файл

@ -245,12 +245,16 @@ class JavaPanZoomController
final RectF zoomRect = new RectF(x, y, final RectF zoomRect = new RectF(x, y,
x + (float)message.getDouble("w"), x + (float)message.getDouble("w"),
y + (float)message.getDouble("h")); y + (float)message.getDouble("h"));
mTarget.post(new Runnable() { if (message.optBoolean("animate", true)) {
@Override mTarget.post(new Runnable() {
public void run() { @Override
animatedZoomTo(zoomRect); public void run() {
} animatedZoomTo(zoomRect);
}); }
});
} else {
mTarget.setViewportMetrics(getMetricsToZoomTo(zoomRect));
}
} else if (MESSAGE_ZOOM_PAGE.equals(event)) { } else if (MESSAGE_ZOOM_PAGE.equals(event)) {
ImmutableViewportMetrics metrics = getMetrics(); ImmutableViewportMetrics metrics = getMetrics();
RectF cssPageRect = metrics.getCssPageRect(); RectF cssPageRect = metrics.getCssPageRect();
@ -264,12 +268,16 @@ class JavaPanZoomController
y + dh/2, y + dh/2,
cssPageRect.width(), cssPageRect.width(),
y + dh/2 + newHeight); y + dh/2 + newHeight);
mTarget.post(new Runnable() { if (message.optBoolean("animate", true)) {
@Override mTarget.post(new Runnable() {
public void run() { @Override
animatedZoomTo(r); public void run() {
} animatedZoomTo(r);
}); }
});
} else {
mTarget.setViewportMetrics(getMetricsToZoomTo(r));
}
} else if (MESSAGE_TOUCH_LISTENER.equals(event)) { } else if (MESSAGE_TOUCH_LISTENER.equals(event)) {
int tabId = message.getInt("tabID"); int tabId = message.getInt("tabID");
final Tab tab = Tabs.getInstance().getTab(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 * While we usually use device pixels, @zoomToRect must be specified in CSS
* pixels. * pixels.
*/ */
private boolean animatedZoomTo(RectF zoomToRect) { private ImmutableViewportMetrics getMetricsToZoomTo(RectF zoomToRect) {
final float startZoom = getMetrics().zoomFactor; final float startZoom = getMetrics().zoomFactor;
RectF viewport = getMetrics().getViewport(); RectF viewport = getMetrics().getViewport();
@ -1434,8 +1442,11 @@ class JavaPanZoomController
// 2. now run getValidViewportMetrics on it, so that the target viewport is // 2. now run getValidViewportMetrics on it, so that the target viewport is
// clamped down to prevent overscroll, over-zoom, and other bad conditions. // clamped down to prevent overscroll, over-zoom, and other bad conditions.
finalMetrics = getValidViewportMetrics(finalMetrics); finalMetrics = getValidViewportMetrics(finalMetrics);
return finalMetrics;
}
bounce(finalMetrics, PanZoomState.ANIMATED_ZOOM); private boolean animatedZoomTo(RectF zoomToRect) {
bounce(getMetricsToZoomTo(zoomToRect), PanZoomState.ANIMATED_ZOOM);
return true; return true;
} }

Просмотреть файл

@ -73,6 +73,9 @@
<!ENTITY pref_category_datareporting "Data choices"> <!ENTITY pref_category_datareporting "Data choices">
<!ENTITY pref_category_installed_search_engines "Installed search engines"> <!ENTITY pref_category_installed_search_engines "Installed search engines">
<!ENTITY pref_category_add_search_providers "Add more search providers"> <!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 <!-- 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 advisory message on the customise search providers settings page explaining how to add new
search providers.--> search providers.-->

Просмотреть файл

@ -7,6 +7,13 @@ package org.mozilla.gecko.menu;
import org.mozilla.gecko.R; import org.mozilla.gecko.R;
import android.content.Context; 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.graphics.drawable.Drawable;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.ViewGroup; import android.view.ViewGroup;
@ -16,6 +23,15 @@ public class MenuItemActionBar extends ImageButton
implements GeckoMenuItem.Layout { implements GeckoMenuItem.Layout {
private static final String LOGTAG = "GeckoMenuItemActionBar"; 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) { public MenuItemActionBar(Context context) {
this(context, null); this(context, null);
} }
@ -26,6 +42,55 @@ public class MenuItemActionBar extends ImageButton
public MenuItemActionBar(Context context, AttributeSet attrs, int defStyle) { public MenuItemActionBar(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, 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 @Override
@ -37,24 +102,22 @@ public class MenuItemActionBar extends ImageButton
setTitle(item.getTitle()); setTitle(item.getTitle());
setEnabled(item.isEnabled()); setEnabled(item.isEnabled());
setId(item.getItemId()); setId(item.getItemId());
setSubMenuIndicator(item.hasSubMenu());
} }
void setIcon(Drawable icon) { void setIcon(Drawable icon) {
if (icon != null) { mIcon = icon;
setImageDrawable(icon);
setVisibility(VISIBLE); if (icon == null) {
} else {
setVisibility(GONE); setVisibility(GONE);
} else {
setVisibility(VISIBLE);
setImageDrawable(icon);
} }
} }
void setIcon(int icon) { void setIcon(int icon) {
if (icon != 0) { setIcon((icon == 0) ? null : getResources().getDrawable(icon));
setImageResource(icon);
setVisibility(VISIBLE);
} else {
setVisibility(GONE);
}
} }
void setTitle(CharSequence title) { void setTitle(CharSequence title) {
@ -72,4 +135,11 @@ public class MenuItemActionBar extends ImageButton
public void setShowIcon(boolean show) { public void setShowIcon(boolean show) {
// Do nothing. // 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) if (item == null)
return; return;
setTitle(item.getTitle()); mMenuItem.initialize(item);
setIcon(item.getIcon()); mMenuButton.initialize(item);
setEnabled(item.isEnabled()); 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 @Override
public void setEnabled(boolean enabled) { public void setEnabled(boolean enabled) {
super.setEnabled(enabled); super.setEnabled(enabled);

Просмотреть файл

@ -102,12 +102,7 @@ public class MenuItemDefault extends TextView
} }
void setIcon(int icon) { void setIcon(int icon) {
Drawable drawable = null; setIcon((icon == 0) ? null : getResources().getDrawable(icon));
if (icon != 0)
drawable = getResources().getDrawable(icon);
setIcon(drawable);
} }
void setTitle(CharSequence title) { void setTitle(CharSequence title) {

Просмотреть файл

@ -5,9 +5,12 @@
package org.mozilla.gecko.preferences; package org.mozilla.gecko.preferences;
import java.lang.reflect.Field;
import org.mozilla.gecko.R; import org.mozilla.gecko.R;
import org.mozilla.gecko.PrefsHelper; import org.mozilla.gecko.PrefsHelper;
import android.app.Activity;
import android.preference.Preference; import android.preference.Preference;
import android.preference.PreferenceActivity; import android.preference.PreferenceActivity;
import android.preference.PreferenceCategory; import android.preference.PreferenceCategory;
@ -15,6 +18,10 @@ import android.preference.PreferenceFragment;
import android.preference.PreferenceScreen; import android.preference.PreferenceScreen;
import android.os.Bundle; import android.os.Bundle;
import android.util.Log; 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 /* A simple implementation of PreferenceFragment for large screen devices
* This will strip category headers (so that they aren't shown to the user twice) * 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 @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
String resourceName = getArguments().getString("resource");
int res = 0; int res = getResource();
if (resourceName != null) {
// Fetch resource id by resource name. // Display a menu for Search preferences.
res = getActivity().getResources().getIdentifier(resourceName, if (res == R.xml.preferences_search) {
"xml", setHasOptionsMenu(true);
getActivity().getPackageName());
} }
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); addPreferencesFromResource(res);
PreferenceScreen screen = getPreferenceScreen(); PreferenceScreen screen = getPreferenceScreen();
@ -52,6 +50,39 @@ public class GeckoPreferenceFragment extends PreferenceFragment {
mPrefsRequestId = ((GeckoPreferences)getActivity()).setupPreferences(screen); 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 @Override
public void onDestroy() { public void onDestroy() {
super.onDestroy(); super.onDestroy();
@ -59,4 +90,29 @@ public class GeckoPreferenceFragment extends PreferenceFragment {
PrefsHelper.removeObserver(mPrefsRequestId); 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 boolean mInitialized = false;
private int mPrefsRequestId = 0; 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_ANNOUNCEMENTS_ENABLED = NON_PREF_PREFIX + "privacy.announcements.enabled";
private static String PREFS_DATA_REPORTING_PREFERENCES = NON_PREF_PREFIX + "datareporting.preferences"; private static String PREFS_DATA_REPORTING_PREFERENCES = NON_PREF_PREFIX + "datareporting.preferences";
private static String PREFS_TELEMETRY_ENABLED = "datareporting.telemetry.enabled"; private static String PREFS_TELEMETRY_ENABLED = "datareporting.telemetry.enabled";
@ -389,6 +391,14 @@ public class GeckoPreferences
preferences.removePreference(pref); preferences.removePreference(pref);
i--; i--;
continue; 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, // 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 @Override
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) { int itemId = item.getItemId();
switch (itemId) {
case android.R.id.home: case android.R.id.home:
finish(); finish();
return true; 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); 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. // Specifies if this engine is configured as the default search engine.
private boolean mIsDefaultEngine; 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. // Dialog element labels.
private String[] mDialogItems; private String[] mDialogItems;
@ -121,12 +119,7 @@ public class SearchEnginePreference extends Preference implements View.OnLongCli
public void setSearchEngineFromJSON(JSONObject geckoEngineJSON) throws JSONException { public void setSearchEngineFromJSON(JSONObject geckoEngineJSON) throws JSONException {
final String engineName = geckoEngineJSON.getString("name"); final String engineName = geckoEngineJSON.getString("name");
final SpannableString titleSpannable = new SpannableString(engineName); 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); setTitle(titleSpannable);
final String iconURI = geckoEngineJSON.getString("iconURI"); final String iconURI = geckoEngineJSON.getString("iconURI");
@ -176,11 +169,6 @@ public class SearchEnginePreference extends Preference implements View.OnLongCli
return; 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()); final AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
builder.setTitle(getTitle().toString()); builder.setTitle(getTitle().toString());
builder.setItems(mDialogItems, new DialogInterface.OnClickListener() { 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. // Ensures default engine remains at top of list.
setOrderingAsAdded(true); 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.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 @Override
public void handleMessage(String event, final JSONObject data) { public void handleMessage(String event, final JSONObject data) {
if (event.equals("SearchEngines: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. // Parse engines array from JSON.
JSONArray engines; JSONArray engines;
try { try {
@ -64,6 +65,9 @@ public class SearchPreferenceCategory extends PreferenceCategory implements Geck
return; return;
} }
// Clear the preferences category from this thread.
this.removeAll();
// Create an element in this PreferenceCategory for each engine. // Create an element in this PreferenceCategory for each engine.
for (int i = 0; i < engines.length(); i++) { for (int i = 0; i < engines.length(); i++) {
try { try {

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 1.1 KiB

После

Ширина:  |  Высота:  |  Размер: 1.1 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 1.0 KiB

После

Ширина:  |  Высота:  |  Размер: 1.0 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 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_state_icon">18dp</dimen>
<dimen name="menu_item_row_height">44dp</dimen> <dimen name="menu_item_row_height">44dp</dimen>
<dimen name="menu_item_row_width">240dp</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_margin">5dip</dimen>
<dimen name="menu_popup_arrow_width">40dip</dimen> <dimen name="menu_popup_arrow_width">40dip</dimen>
<dimen name="menu_popup_offset">8dp</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 <org.mozilla.gecko.preferences.SearchPreferenceCategory
android:title="@string/pref_category_installed_search_engines"/> 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"> <PreferenceCategory android:title="@string/pref_category_add_search_providers">
<Preference android:layout="@layout/preference_search_tip" <Preference android:layout="@layout/preference_search_tip"

Просмотреть файл

@ -93,7 +93,11 @@
<string name="pref_category_datareporting">&pref_category_datareporting;</string> <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_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_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_search_tip">&pref_search_tip;</string>
<string name="pref_category_devtools">&pref_category_devtools;</string> <string name="pref_category_devtools">&pref_category_devtools;</string>
<string name="pref_developer_remotedebugging">&pref_developer_remotedebugging;</string> <string name="pref_developer_remotedebugging">&pref_developer_remotedebugging;</string>
<string name="pref_developer_remotedebugging_docs">&pref_developer_remotedebugging_docs;</string> <string name="pref_developer_remotedebugging_docs">&pref_developer_remotedebugging_docs;</string>

Просмотреть файл

@ -159,7 +159,7 @@ public class testDistribution extends ContentProviderTest {
private void checkSearchPlugin() { private void checkSearchPlugin() {
Actions.RepeatedEventExpecter eventExpecter = mActions.expectGeckoEvent("SearchEngines:Data"); Actions.RepeatedEventExpecter eventExpecter = mActions.expectGeckoEvent("SearchEngines:Data");
mActions.sendGeckoEvent("SearchEngines:Get", null); mActions.sendGeckoEvent("SearchEngines:GetVisible", null);
try { try {
JSONObject data = new JSONObject(eventExpecter.blockForEventData()); JSONObject data = new JSONObject(eventExpecter.blockForEventData());

Просмотреть файл

@ -47,7 +47,10 @@ public class ToolbarEditText extends CustomEditText
private final Context mContext; 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 OnCommitListener mCommitListener;
private OnDismissListener mDismissListener; private OnDismissListener mDismissListener;
@ -66,7 +69,8 @@ public class ToolbarEditText extends CustomEditText
super(context, attrs); super(context, attrs);
mContext = context; mContext = context;
mTextType = TextType.EMPTY; mToolbarTextType = TextType.EMPTY;
mKeyboardTextType = TextType.URL;
} }
void setOnCommitListener(OnCommitListener listener) { void setOnCommitListener(OnCommitListener listener) {
@ -172,10 +176,13 @@ public class ToolbarEditText extends CustomEditText
} }
private void setTextType(TextType textType) { private void setTextType(TextType textType) {
mTextType = textType; mToolbarTextType = textType;
if (textType != TextType.EMPTY) {
mKeyboardTextType = textType;
}
if (mTextTypeListener != null) { if (mTextTypeListener != null) {
mTextTypeListener.onTextTypeChange(this, mTextType); mTextTypeListener.onTextTypeChange(this, textType);
} }
} }
@ -186,6 +193,8 @@ public class ToolbarEditText extends CustomEditText
} }
if (InputMethods.shouldDisableUrlBarUpdate(mContext)) { if (InputMethods.shouldDisableUrlBarUpdate(mContext)) {
// Set button type to match the previous keyboard type
setTextType(mKeyboardTextType);
return; return;
} }
@ -222,10 +231,16 @@ public class ToolbarEditText extends CustomEditText
restartInput = true; restartInput = true;
} }
if (restartInput) { if (!restartInput) {
updateKeyboardInputType(); // If the text content was previously empty, the toolbar text type
imm.restartInput(ToolbarEditText.this); // 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 ? setTextType(imeAction == EditorInfo.IME_ACTION_GO ?
TextType.URL : TextType.SEARCH_QUERY); TextType.URL : TextType.SEARCH_QUERY);

Просмотреть файл

@ -178,10 +178,20 @@ function doChangeMaxLineBoxWidth(aWidth) {
range = BrowserApp.selectedTab._mReflozPoint.range; range = BrowserApp.selectedTab._mReflozPoint.range;
} }
docViewer.changeMaxLineBoxWidth(aWidth); try {
docViewer.pausePainting();
docViewer.changeMaxLineBoxWidth(aWidth);
if (range) { if (range) {
BrowserEventHandler._zoomInAndSnapToRange(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); this.browser.addEventListener("MozApplicationManifest", this, true);
Services.obs.addObserver(this, "before-first-paint", false); 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); Services.prefs.addObserver("browser.ui.zoom.force-user-scalable", this, false);
if (aParams.delayLoad) { if (aParams.delayLoad) {
@ -2803,21 +2814,58 @@ Tab.prototype = {
return minFontSize / this.getInflatedFontSizeFor(aElement); 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) { performReflowOnZoom: function(aViewport) {
let zoom = this._drawZoom ? this._drawZoom : aViewport.zoom; let zoom = this._drawZoom ? this._drawZoom : aViewport.zoom;
let viewportWidth = gScreenWidth / zoom; let viewportWidth = gScreenWidth / zoom;
let reflozTimeout = Services.prefs.getIntPref("browser.zoom.reflowZoom.reflowTimeout"); let reflozTimeout = Services.prefs.getIntPref("browser.zoom.reflowZoom.reflowTimeout");
if (gReflowPending) { if (gReflowPending) {
clearTimeout(gReflowPending); clearTimeout(gReflowPending);
} }
// We add in a bit of fudge just so that the end characters // We add in a bit of fudge just so that the end characters
// don't accidentally get clipped. 15px is an arbitrary choice. // don't accidentally get clipped. 15px is an arbitrary choice.
gReflowPending = setTimeout(doChangeMaxLineBoxWidth, gReflowPending = setTimeout(doChangeMaxLineBoxWidth,
reflozTimeout, reflozTimeout,
viewportWidth - 15); viewportWidth - 15);
}, },
/** /**
@ -2889,6 +2937,7 @@ Tab.prototype = {
this.browser.removeEventListener("MozApplicationManifest", this, true); this.browser.removeEventListener("MozApplicationManifest", this, true);
Services.obs.removeObserver(this, "before-first-paint"); Services.obs.removeObserver(this, "before-first-paint");
Services.obs.removeObserver(this, "after-viewport-change");
Services.prefs.removeObserver("browser.ui.zoom.force-user-scalable", this); 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 // 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 // In this case, the user pinch-zoomed in, so we don't want to
// preserve position as we would with reflow-on-zoom. // preserve position as we would with reflow-on-zoom.
BrowserApp.selectedTab.probablyNeedRefloz = false; BrowserApp.selectedTab.probablyNeedRefloz = false;
BrowserApp.selectedTab.clearReflowOnZoomPendingActions();
BrowserApp.selectedTab._mReflozPoint = null; BrowserApp.selectedTab._mReflozPoint = null;
} }
let docViewer = null;
if (isZooming && if (isZooming &&
BrowserEventHandler.mReflozPref && BrowserEventHandler.mReflozPref &&
BrowserApp.selectedTab._mReflozPoint && BrowserApp.selectedTab._mReflozPoint &&
BrowserApp.selectedTab.probablyNeedRefloz) { 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.performReflowOnZoom(aViewport);
BrowserApp.selectedTab.probablyNeedRefloz = false; BrowserApp.selectedTab.probablyNeedRefloz = false;
} }
@ -3210,6 +3267,9 @@ Tab.prototype = {
aViewport.fixedMarginLeft / aViewport.zoom); aViewport.fixedMarginLeft / aViewport.zoom);
Services.obs.notifyObservers(null, "after-viewport-change", ""); Services.obs.notifyObservers(null, "after-viewport-change", "");
if (docViewer) {
docViewer.resumePainting();
}
}, },
setResolution: function(aZoom, aForce) { setResolution: function(aZoom, aForce) {
@ -4166,6 +4226,11 @@ Tab.prototype = {
BrowserApp.selectedTab.performReflowOnZoom(vp); BrowserApp.selectedTab.performReflowOnZoom(vp);
} }
break; break;
case "after-viewport-change":
if (BrowserApp.selectedTab._mReflozPositioned) {
BrowserApp.selectedTab.clearReflowOnZoomPendingActions();
}
break;
case "nsPref:changed": case "nsPref:changed":
if (aData == "browser.ui.zoom.force-user-scalable") if (aData == "browser.ui.zoom.force-user-scalable")
ViewportHandler.updateMetadata(this, false); ViewportHandler.updateMetadata(this, false);
@ -4484,13 +4549,23 @@ var BrowserEventHandler = {
if (BrowserEventHandler.mReflozPref && if (BrowserEventHandler.mReflozPref &&
!BrowserApp.selectedTab._mReflozPoint && !BrowserApp.selectedTab._mReflozPoint &&
!this._shouldSuppressReflowOnZoom(element)) { !this._shouldSuppressReflowOnZoom(element)) {
let data = JSON.parse(aData);
let zoomPointX = data.x;
let zoomPointY = data.y;
BrowserApp.selectedTab._mReflozPoint = { x: zoomPointX, y: zoomPointY, // See comment above performReflowOnZoom() for a detailed description of
range: BrowserApp.selectedBrowser.contentDocument.caretPositionFromPoint(zoomPointX, zoomPointY) }; // the events happening in the reflow-on-zoom operation.
BrowserApp.selectedTab.probablyNeedRefloz = true; 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) { if (!element) {
@ -4590,11 +4665,7 @@ var BrowserEventHandler = {
}, },
_zoomInAndSnapToRange: function(aRange) { _zoomInAndSnapToRange: function(aRange) {
if (!aRange) { // aRange is always non-null here, since a check happened previously.
Cu.reportError("aRange is null in zoomInAndSnapToRange. Unable to maintain position.");
return;
}
let viewport = BrowserApp.selectedTab.getViewport(); let viewport = BrowserApp.selectedTab.getViewport();
let fudge = 15; // Add a bit of fudge. let fudge = 15; // Add a bit of fudge.
let boundingElement = aRange.offsetNode; let boundingElement = aRange.offsetNode;
@ -4617,30 +4688,33 @@ var BrowserEventHandler = {
let leftAdjustment = parseInt(boundingStyle.paddingLeft) + let leftAdjustment = parseInt(boundingStyle.paddingLeft) +
parseInt(boundingStyle.borderLeftWidth); parseInt(boundingStyle.borderLeftWidth);
BrowserApp.selectedTab._mReflozPositioned = true;
rect.type = "Browser:ZoomToRect"; rect.type = "Browser:ZoomToRect";
rect.x = Math.max(viewport.cssPageLeft, rect.x - fudge + leftAdjustment); rect.x = Math.max(viewport.cssPageLeft, rect.x - fudge + leftAdjustment);
rect.y = Math.max(topPos, viewport.cssPageTop); rect.y = Math.max(topPos, viewport.cssPageTop);
rect.w = viewport.cssWidth; rect.w = viewport.cssWidth;
rect.h = viewport.cssHeight; rect.h = viewport.cssHeight;
rect.animate = false;
sendMessageToJava(rect); sendMessageToJava(rect);
BrowserApp.selectedTab._mReflozPoint = null; BrowserApp.selectedTab._mReflozPoint = null;
}, },
onPinchFinish: function(aData) { onPinchFinish: function(aData) {
let data = {}; let data = {};
try { try {
data = JSON.parse(aData); data = JSON.parse(aData);
} catch(ex) { } catch(ex) {
console.log(ex); console.log(ex);
return; return;
} }
if (BrowserEventHandler.mReflozPref && if (BrowserEventHandler.mReflozPref &&
data.zoomDelta < 0.0) { data.zoomDelta < 0.0) {
BrowserEventHandler.resetMaxLineBoxWidth(); BrowserEventHandler.resetMaxLineBoxWidth();
} }
}, },
_shouldZoomToElement: function(aElement) { _shouldZoomToElement: function(aElement) {
let win = aElement.ownerDocument.defaultView; let win = aElement.ownerDocument.defaultView;
@ -6608,10 +6682,10 @@ var SearchEngines = {
init: function init() { init: function init() {
Services.obs.addObserver(this, "SearchEngines:Add", false); 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:GetVisible", false);
Services.obs.addObserver(this, "SearchEngines:SetDefault", false);
Services.obs.addObserver(this, "SearchEngines:Remove", false); Services.obs.addObserver(this, "SearchEngines:Remove", false);
Services.obs.addObserver(this, "SearchEngines:RestoreDefaults", false);
Services.obs.addObserver(this, "SearchEngines:SetDefault", false);
let filter = { let filter = {
matches: function (aElement) { matches: function (aElement) {
@ -6651,37 +6725,28 @@ var SearchEngines = {
uninit: function uninit() { uninit: function uninit() {
Services.obs.removeObserver(this, "SearchEngines:Add"); Services.obs.removeObserver(this, "SearchEngines:Add");
Services.obs.removeObserver(this, "SearchEngines:Get");
Services.obs.removeObserver(this, "SearchEngines:GetVisible"); Services.obs.removeObserver(this, "SearchEngines:GetVisible");
Services.obs.removeObserver(this, "SearchEngines:SetDefault");
Services.obs.removeObserver(this, "SearchEngines:Remove"); Services.obs.removeObserver(this, "SearchEngines:Remove");
Services.obs.removeObserver(this, "SearchEngines:RestoreDefaults");
Services.obs.removeObserver(this, "SearchEngines:SetDefault");
if (this._contextMenuId != null) if (this._contextMenuId != null)
NativeWindow.contextmenus.remove(this._contextMenuId); NativeWindow.contextmenus.remove(this._contextMenuId);
}, },
// Fetch list of search engines. all ? All engines : Visible engines only. // 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)) { if (!Components.isSuccessCode(rv)) {
Cu.reportError("Could not initialize search service, bailing out."); Cu.reportError("Could not initialize search service, bailing out.");
return; 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) { let searchEngines = engineData.map(function (engine) {
return { return {
name: engine.name, name: engine.name,
identifier: engine.identifier, identifier: engine.identifier,
iconURI: (engine.iconURI ? engine.iconURI.spec : null), iconURI: (engine.iconURI ? engine.iconURI.spec : null),
hidden: engine.hidden, hidden: engine.hidden
immutable: immutableEngines.indexOf(engine) != -1
}; };
}); });
@ -6718,13 +6783,6 @@ var SearchEngines = {
} catch (e) {} } 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. // Helper method to extract the engine name from a JSON. Simplifies the observe function.
_extractEngineFromJSON: function _extractEngineFromJSON(aData) { _extractEngineFromJSON: function _extractEngineFromJSON(aData) {
let data = JSON.parse(aData); let data = JSON.parse(aData);
@ -6740,16 +6798,6 @@ var SearchEngines = {
case "SearchEngines:GetVisible": case "SearchEngines:GetVisible":
Services.search.init(this._handleSearchEnginesGetVisible.bind(this)); Services.search.init(this._handleSearchEnginesGetVisible.bind(this));
break; 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": case "SearchEngines:Remove":
// Make sure the engine isn't hidden before removing it, to make sure it's // 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) // visible if the user later re-adds it (works around bug 341833)
@ -6757,6 +6805,17 @@ var SearchEngines = {
engine.hidden = false; engine.hidden = false;
Services.search.removeEngine(engine); Services.search.removeEngine(engine);
break; 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: default:
dump("Unexpected message type observed: " + aTopic); dump("Unexpected message type observed: " + aTopic);
break; break;

Просмотреть файл

@ -223,7 +223,9 @@ SessionStore.prototype = {
} }
case "pageshow": { case "pageshow": {
let browser = aEvent.currentTarget; 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; break;
} }
} }

Просмотреть файл

@ -6,6 +6,7 @@
/* General utilities used throughout devtools. */ /* General utilities used throughout devtools. */
let Cu = Components.utils;
let { Promise: promise } = Components.utils.import("resource://gre/modules/commonjs/sdk/core/promise.js", {}); let { Promise: promise } = Components.utils.import("resource://gre/modules/commonjs/sdk/core/promise.js", {});
let { Services } = Components.utils.import("resource://gre/modules/Services.jsm", {}); 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; 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, defineLazyPrototypeGetter: defineLazyPrototypeGetter,
getProperty: getProperty, getProperty: getProperty,
hasSafeGetter: hasSafeGetter, hasSafeGetter: hasSafeGetter,
isSafeJSObject: isSafeJSObject,
}; };

Просмотреть файл

@ -6,6 +6,14 @@
"use strict"; "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 * 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 * can reset them when the same script is introduced to the thread again (such
@ -440,6 +448,8 @@ function ThreadActor(aHooks, aGlobal)
this._options = { this._options = {
useSourceMaps: false useSourceMaps: false
}; };
this._gripDepth = 0;
} }
/** /**
@ -449,6 +459,9 @@ function ThreadActor(aHooks, aGlobal)
ThreadActor.breakpointStore = new BreakpointStore(); ThreadActor.breakpointStore = new BreakpointStore();
ThreadActor.prototype = { ThreadActor.prototype = {
// Used by the ObjectActor to keep track of the depth of grip() calls.
_gripDepth: null,
actorPrefix: "context", actorPrefix: "context",
get state() { return this._state; }, get state() { return this._state; },
@ -2730,7 +2743,7 @@ function errorStringify(aObj) {
* The stringification for the object. * The stringification for the object.
*/ */
function stringify(aObj) { function stringify(aObj) {
if (Cu.isDeadWrapper(aObj)) { if (aObj.class == "DeadObject") {
const error = new Error("Dead object encountered."); const error = new Error("Dead object encountered.");
DevToolsUtils.reportException("stringify", error); DevToolsUtils.reportException("stringify", error);
return "<dead object>"; return "<dead object>";
@ -2829,6 +2842,8 @@ ObjectActor.prototype = {
* Returns a grip for this actor for returning in a protocol message. * Returns a grip for this actor for returning in a protocol message.
*/ */
grip: function () { grip: function () {
this.threadActor._gripDepth++;
let g = { let g = {
"type": "object", "type": "object",
"class": this.obj.class, "class": this.obj.class,
@ -2838,29 +2853,22 @@ ObjectActor.prototype = {
"sealed": this.obj.isSealed() "sealed": this.obj.isSealed()
}; };
// Add additional properties for functions. if (this.obj.class != "DeadObject") {
if (this.obj.class === "Function") { let raw = Cu.unwaiveXrays(this.obj.unsafeDereference());
if (this.obj.name) { if (!DevToolsUtils.isSafeJSObject(raw)) {
g.name = this.obj.name; raw = null;
}
if (this.obj.displayName) {
g.displayName = this.obj.displayName;
} }
// Check if the developer has added a de-facto standard displayName let previewers = DebuggerServer.ObjectActorPreviewers[this.obj.class] ||
// property for us to use. DebuggerServer.ObjectActorPreviewers.Object;
try { for (let fn of previewers) {
let desc = this.obj.getOwnPropertyDescriptor("displayName"); if (fn(this, g, raw)) {
if (desc && desc.value && typeof desc.value == "string") { break;
g.userDisplayName = this.threadActor.createValueGrip(desc.value);
} }
} catch (e) {
// Calling getOwnPropertyDescriptor with displayName might throw
// with "permission denied" errors for some functions.
dumpn(e);
} }
} }
this.threadActor._gripDepth--;
return g; return g;
}, },
@ -2964,15 +2972,17 @@ ObjectActor.prototype = {
* @param object aOwnProperties * @param object aOwnProperties
* The object that holds the list of known ownProperties for * The object that holds the list of known ownProperties for
* |this.obj|. * |this.obj|.
* @param number [aLimit=0]
* Optional limit of getter values to find.
* @return object * @return object
* An object that maps property names to safe getter descriptors as * An object that maps property names to safe getter descriptors as
* defined by the remote debugging protocol. * defined by the remote debugging protocol.
*/ */
_findSafeGetterValues: function (aOwnProperties) _findSafeGetterValues: function (aOwnProperties, aLimit = 0)
{ {
let safeGetterValues = Object.create(null); let safeGetterValues = Object.create(null);
let obj = this.obj; let obj = this.obj;
let level = 0; let level = 0, i = 0;
while (obj) { while (obj) {
let getters = this._findSafeGetters(obj); let getters = this._findSafeGetters(obj);
@ -3014,9 +3024,15 @@ ObjectActor.prototype = {
enumerable: desc.enumerable, enumerable: desc.enumerable,
writable: level == 0 ? desc.writable : true, writable: level == 0 ? desc.writable : true,
}; };
if (aLimit && ++i == aLimit) {
break;
}
} }
} }
} }
if (aLimit && i == aLimit) {
break;
}
obj = obj.proto; obj = obj.proto;
level++; level++;
@ -3043,7 +3059,15 @@ ObjectActor.prototype = {
} }
let getters = new Set(); 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; let desc = null;
try { try {
desc = aObject.getOwnPropertyDescriptor(name); desc = aObject.getOwnPropertyDescriptor(name);
@ -3108,10 +3132,17 @@ ObjectActor.prototype = {
* A helper method that creates a property descriptor for the provided object, * A helper method that creates a property descriptor for the provided object,
* properly formatted for sending in a protocol response. * properly formatted for sending in a protocol response.
* *
* @private
* @param string aName * @param string aName
* The property that the descriptor is generated for. * 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; let desc;
try { try {
desc = this.obj.getOwnPropertyDescriptor(aName); desc = this.obj.getOwnPropertyDescriptor(aName);
@ -3127,7 +3158,7 @@ ObjectActor.prototype = {
}; };
} }
if (!desc) { if (!desc || aOnlyEnumerable && !desc.enumerable) {
return undefined; 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. * Creates a pause-scoped actor for the specified object.
* @see ObjectActor * @see ObjectActor
@ -4517,3 +5107,23 @@ function positionInNodeList(element, nodeList) {
} }
return -1; 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._protoChains = new Map();
this._netEvents = new Map(); this._netEvents = new Map();
this._gripDepth = 0;
this._onObserverNotification = this._onObserverNotification.bind(this); this._onObserverNotification = this._onObserverNotification.bind(this);
if (this.parentActor.isRootActor) { if (this.parentActor.isRootActor) {
@ -80,6 +81,13 @@ WebConsoleActor.prototype =
*/ */
dbg: null, 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. * Actor pool for all of the actors we send to the client.
* @private * @private

Просмотреть файл

@ -188,12 +188,18 @@ let WebConsoleUtils = {
* *
* @param string aSourceURL * @param string aSourceURL
* The source URL to shorten. * 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 * @return string
* The abbreviated form of the source URL. * 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(","); let commaIndex = aSourceURL.indexOf(",");
if (commaIndex > -1) { if (commaIndex > -1) {
aSourceURL = "data:" + aSourceURL.substring(commaIndex + 1); aSourceURL = "data:" + aSourceURL.substring(commaIndex + 1);
@ -214,13 +220,15 @@ let WebConsoleUtils = {
// Remove a trailing "/". // Remove a trailing "/".
if (aSourceURL[aSourceURL.length - 1] == "/") { if (aSourceURL[aSourceURL.length - 1] == "/") {
aSourceURL = aSourceURL.substring(0, aSourceURL.length - 1); aSourceURL = aSourceURL.replace(/\/+$/, "");
} }
// Remove all but the last path component. // Remove all but the last path component.
let slashIndex = aSourceURL.lastIndexOf("/"); if (!aOptions.onlyCropQuery) {
if (slashIndex > -1) { let slashIndex = aSourceURL.lastIndexOf("/");
aSourceURL = aSourceURL.substring(slashIndex + 1); if (slashIndex > -1) {
aSourceURL = aSourceURL.substring(slashIndex + 1);
}
} }
return aSourceURL; return aSourceURL;

Просмотреть файл

@ -11,6 +11,9 @@ const Ci = Components.interfaces;
Cu.import("resource://gre/modules/osfile.jsm"); Cu.import("resource://gre/modules/osfile.jsm");
Cu.import("resource://gre/modules/Promise.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"]; this.EXPORTED_SYMBOLS = ["DeferredSave"];
// If delay parameter is not provided, default is 50 milliseconds. // If delay parameter is not provided, default is 50 milliseconds.
@ -104,7 +107,7 @@ this.DeferredSave.prototype = {
this.LOG("Starting timer"); this.LOG("Starting timer");
if (!this._timer) if (!this._timer)
this._timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); this._timer = MakeTimer();
this._timer.initWithCallback(() => this._deferredSave(), this._timer.initWithCallback(() => this._deferredSave(),
this._delay, Ci.nsITimer.TYPE_ONE_SHOT); this._delay, Ci.nsITimer.TYPE_ONE_SHOT);
}, },

Просмотреть файл

@ -12,11 +12,11 @@ testFile.append("DeferredSaveTest");
Components.utils.import("resource://gre/modules/Promise.jsm"); Components.utils.import("resource://gre/modules/Promise.jsm");
let context = Components.utils.import("resource://gre/modules/DeferredSave.jsm", {}); let DSContext = Components.utils.import("resource://gre/modules/DeferredSave.jsm", {});
let DeferredSave = context.DeferredSave; let DeferredSave = DSContext.DeferredSave;
// Test wrapper to let us do promise/task based testing of DeferredSaveP // Test wrapper to let us do promise/task based testing of DeferredSave
function DeferredSaveTester(aDelay, aDataProvider) { function DeferredSaveTester(aDataProvider) {
let tester = { let tester = {
// Deferred for the promise returned by the mock writeAtomic // Deferred for the promise returned by the mock writeAtomic
waDeferred: null, waDeferred: null,
@ -54,11 +54,11 @@ function DeferredSaveTester(aDelay, aDataProvider) {
if (!aDataProvider) if (!aDataProvider)
aDataProvider = () => tester.dataToSave; 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 // Install a mock for OS.File.writeAtomic to let us control the async
// behaviour of the promise // 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()); do_print("writeAtomic: " + aFile + " data: '" + aData + "', " + aOptions.toSource());
tester.writtenData = aData; tester.writtenData = aData;
tester.waDeferred = Promise.defer(); tester.waDeferred = Promise.defer();
@ -69,6 +69,70 @@ function DeferredSaveTester(aDelay, aDataProvider) {
return tester; 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 * 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 // Modify set data once, ask for save, make sure it saves cleanly
add_task(function test_basic_save_succeeds() { add_task(function test_basic_save_succeeds() {
let tester = DeferredSaveTester(1); setQuickMockTimer();
let tester = DeferredSaveTester();
let data = "Test 1 Data"; let data = "Test 1 Data";
yield tester.save(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 // Two saves called during the same event loop, both with callbacks
// Make sure we save only the second version of the data // Make sure we save only the second version of the data
add_task(function test_two_saves() { add_task(function test_two_saves() {
let tester = DeferredSaveTester(1); setQuickMockTimer();
let tester = DeferredSaveTester();
let firstCallback_happened = false; let firstCallback_happened = false;
let firstData = "Test first save"; let firstData = "Test first save";
let secondData = "Test second 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 // Two saves called with a delay in between, both with callbacks
// Make sure we save the second version of the data // Make sure we save the second version of the data
add_task(function test_two_saves_delay() { add_task(function test_two_saves_delay() {
let tester = DeferredSaveTester(50); let timerPromise = setPromiseMockTimer();
let tester = DeferredSaveTester();
let firstCallback_happened = false; let firstCallback_happened = false;
let delayDone = false; let delayDone = false;
@ -131,9 +198,18 @@ add_task(function test_two_saves_delay() {
firstCallback_happened = true; firstCallback_happened = true;
}, do_report_unexpected_exception); }, 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; 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_true(firstCallback_happened);
do_check_eq(secondData, tester.writtenData); do_check_eq(secondData, tester.writtenData);
do_check_eq(1, tester.saver.totalSaves); 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 // Also check that the "error" getter correctly returns the error
// Then do a write that succeeds, and make sure the error is cleared // Then do a write that succeeds, and make sure the error is cleared
add_task(function test_error_immediate() { add_task(function test_error_immediate() {
let tester = DeferredSaveTester(1); let tester = DeferredSaveTester();
let testError = new Error("Forced failure"); let testError = new Error("Forced failure");
function writeFail(aTester) { function writeFail(aTester) {
aTester.waDeferred.reject(testError); aTester.waDeferred.reject(testError);
} }
setQuickMockTimer();
yield tester.save("test_error_immediate", writeFail).then( yield tester.save("test_error_immediate", writeFail).then(
count => do_throw("Did not get expected error"), count => do_throw("Did not get expected error"),
error => do_check_eq(testError.message, error.message) 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 // data two more times. Test that we re-write the dirty data exactly once
// after the first write succeeds // after the first write succeeds
add_task(function dirty_while_writing() { add_task(function dirty_while_writing() {
let tester = DeferredSaveTester(1); let tester = DeferredSaveTester();
let firstData = "First data"; let firstData = "First data";
let secondData = "Second data"; let secondData = "Second data";
let thirdData = "Third data"; let thirdData = "Third data";
@ -179,6 +256,7 @@ add_task(function dirty_while_writing() {
writeStarted.resolve(aTester.waDeferred); writeStarted.resolve(aTester.waDeferred);
} }
setQuickMockTimer();
do_print("First save"); do_print("First save");
tester.save(firstData, writeCallback).then( tester.save(firstData, writeCallback).then(
count => { count => {
@ -240,7 +318,8 @@ function write_then_disable(aTester) {
// Flush tests. First, do an ordinary clean save and then call flush; // Flush tests. First, do an ordinary clean save and then call flush;
// there should not be another save // there should not be another save
add_task(function flush_after_save() { add_task(function flush_after_save() {
let tester = DeferredSaveTester(1); setQuickMockTimer();
let tester = DeferredSaveTester();
let dataToSave = "Flush after save"; let dataToSave = "Flush after save";
yield tester.save(dataToSave); 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 // Flush while a write is in progress, but the in-memory data is clean
add_task(function flush_during_write() { add_task(function flush_during_write() {
let tester = DeferredSaveTester(1); let tester = DeferredSaveTester();
let dataToSave = "Flush during write"; let dataToSave = "Flush during write";
let firstCallback_happened = false; let firstCallback_happened = false;
let writeStarted = Promise.defer(); let writeStarted = Promise.defer();
@ -259,6 +338,7 @@ add_task(function flush_during_write() {
writeStarted.resolve(aTester.waDeferred); writeStarted.resolve(aTester.waDeferred);
} }
setQuickMockTimer();
tester.save(dataToSave, writeCallback).then( tester.save(dataToSave, writeCallback).then(
count => { count => {
do_check_false(firstCallback_happened); do_check_false(firstCallback_happened);
@ -281,13 +361,9 @@ add_task(function flush_during_write() {
// Flush while dirty but write not in progress // Flush while dirty but write not in progress
// The data written should be the value at the time // The data written should be the value at the time
// flush() is called, even if it is changed later // 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() { add_task(function flush_while_dirty() {
let tester = DeferredSaveTester(20); let timerPromise = setPromiseMockTimer();
let tester = DeferredSaveTester();
let firstData = "Flush while dirty, valid data"; let firstData = "Flush while dirty, valid data";
let firstCallback_happened = false; let firstCallback_happened = false;
@ -298,10 +374,18 @@ add_task(function flush_while_dirty() {
do_check_eq(tester.writtenData, firstData); do_check_eq(tester.writtenData, firstData);
}, do_report_unexpected_exception); }, 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 // Also make sure that data changed after the flush call
// (even without a saveChanges() call) doesn't get written // (even without a saveChanges() call) doesn't get written
let flushing = tester.flush();
tester.dataToSave = "Flush while dirty, invalid data"; tester.dataToSave = "Flush while dirty, invalid data";
yield flushing; yield flushing;
do_check_true(firstCallback_happened); do_check_true(firstCallback_happened);
do_check_eq(tester.writtenData, firstData); 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 // Data for the second write should be taken at the time
// flush() is called, even if it is modified later // flush() is called, even if it is modified later
add_task(function flush_writing_dirty() { add_task(function flush_writing_dirty() {
let tester = DeferredSaveTester(5); let timerPromise = setPromiseMockTimer();
let tester = DeferredSaveTester();
let firstData = "Flush first pass data"; let firstData = "Flush first pass data";
let secondData = "Flush second pass data"; let secondData = "Flush second pass data";
let firstCallback_happened = false; let firstCallback_happened = false;
@ -332,6 +417,9 @@ add_task(function flush_writing_dirty() {
firstCallback_happened = true; firstCallback_happened = true;
}, do_report_unexpected_exception); }, 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; let writer = yield writeStarted.promise;
// the first write has started // the first write has started
@ -346,8 +434,9 @@ add_task(function flush_writing_dirty() {
}, do_report_unexpected_exception); }, do_report_unexpected_exception);
let flushing = tester.flush(write_then_disable); 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"; tester.dataToSave = "Flush, invalid data: changed late";
yield delay(1);
// complete the first write // complete the first write
writer.resolve(firstData.length); writer.resolve(firstData.length);
// now wait for the second write / flush to complete // now wait for the second write / flush to complete
@ -375,8 +464,9 @@ function badDataProvider() {
// Handle cases where data provider throws // Handle cases where data provider throws
// First, throws during a normal save // First, throws during a normal save
add_task(function data_throw() { add_task(function data_throw() {
setQuickMockTimer();
badDataError = expectedDataError; badDataError = expectedDataError;
let tester = DeferredSaveTester(1, badDataProvider); let tester = DeferredSaveTester(badDataProvider);
yield tester.save("data_throw").then( yield tester.save("data_throw").then(
count => do_throw("Expected serialization failure"), count => do_throw("Expected serialization failure"),
error => do_check_eq(error.message, expectedDataError)); error => do_check_eq(error.message, expectedDataError));
@ -385,9 +475,10 @@ add_task(function data_throw() {
// Now, throws during flush // Now, throws during flush
add_task(function data_throw_during_flush() { add_task(function data_throw_during_flush() {
badDataError = expectedDataError; badDataError = expectedDataError;
let tester = DeferredSaveTester(1, badDataProvider); let tester = DeferredSaveTester(badDataProvider);
let firstCallback_happened = false; let firstCallback_happened = false;
setPromiseMockTimer();
// Write callback should never be called // Write callback should never be called
tester.save("data_throw_during_flush", disabled_write_callback).then( tester.save("data_throw_during_flush", disabled_write_callback).then(
count => do_throw("Expected serialization failure"), count => do_throw("Expected serialization failure"),
@ -397,6 +488,7 @@ add_task(function data_throw_during_flush() {
firstCallback_happened = true; firstCallback_happened = true;
}); });
// flush() will cancel the timer
yield tester.flush(disabled_write_callback).then( yield tester.flush(disabled_write_callback).then(
count => do_throw("Expected serialization failure"), count => do_throw("Expected serialization failure"),
error => do_check_eq(error.message, expectedDataError) error => do_check_eq(error.message, expectedDataError)
@ -417,7 +509,8 @@ add_task(function data_throw_during_flush() {
// write completes // write completes
// delayed timer goes off, throws error because DeferredSave has been torn down // delayed timer goes off, throws error because DeferredSave has been torn down
add_task(function delay_flush_race() { add_task(function delay_flush_race() {
let tester = DeferredSaveTester(5); let timerPromise = setPromiseMockTimer();
let tester = DeferredSaveTester();
let firstData = "First save"; let firstData = "First save";
let secondData = "Second save"; let secondData = "Second save";
let thirdData = "Third save"; let thirdData = "Third save";
@ -429,6 +522,7 @@ add_task(function delay_flush_race() {
// This promise won't resolve until after writeStarted // This promise won't resolve until after writeStarted
let firstSave = tester.save(firstData, writeCallback); let firstSave = tester.save(firstData, writeCallback);
(yield timerPromise).callback();
let writer = yield writeStarted.promise; let writer = yield writeStarted.promise;
// the first write has started // the first write has started