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
# don't change CLOBBER for WebIDL changes any more.
Bug 878935 landed without a UUID change (and was since backed out)
Bug 910189 requires a clobber due to Proguard inner class errors from bug 946083.

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

@ -1695,7 +1695,10 @@ let CustomizableUIInternal = {
// If the widget doesn't have an existing placement, and it hasn't been
// seen before, then add it to its default area so it can be used.
if (autoAdd && !widget.currentArea && !gSeenWidgets.has(widget.id)) {
// If the widget is not removable, we *have* to add it to its default
// area here.
let canBeAutoAdded = autoAdd && !gSeenWidgets.has(widget.id);
if (!widget.currentArea && (!widget.removable || canBeAutoAdded)) {
this.beginBatchUpdate();
try {
gSeenWidgets.add(widget.id);

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

@ -48,4 +48,5 @@ skip-if = os == "mac"
[browser_944887_destroyWidget_should_destroy_in_palette.js]
[browser_945739_showInPrivateBrowsing_customize_mode.js]
[browser_947987_removable_default.js]
[browser_948985_non_removable_defaultArea.js]
[browser_panel_toggle.js]

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

@ -0,0 +1,32 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const kWidgetId = "test-destroy-non-removable-defaultArea";
add_task(function() {
let spec = {id: kWidgetId, label: "Test non-removable defaultArea re-adding.",
removable: false, defaultArea: CustomizableUI.AREA_NAVBAR};
CustomizableUI.createWidget(spec);
let placement = CustomizableUI.getPlacementOfWidget(kWidgetId);
ok(placement, "Should have placed the widget.");
is(placement && placement.area, CustomizableUI.AREA_NAVBAR, "Widget should be in navbar");
CustomizableUI.destroyWidget(kWidgetId);
CustomizableUI.removeWidgetFromArea(kWidgetId);
CustomizableUI.createWidget(spec);
ok(placement, "Should have placed the widget.");
is(placement && placement.area, CustomizableUI.AREA_NAVBAR, "Widget should be in navbar");
CustomizableUI.destroyWidget(kWidgetId);
CustomizableUI.removeWidgetFromArea(kWidgetId);
const kPrefCustomizationAutoAdd = "browser.uiCustomization.autoAdd";
Services.prefs.setBoolPref(kPrefCustomizationAutoAdd, false);
CustomizableUI.createWidget(spec);
ok(placement, "Should have placed the widget.");
is(placement && placement.area, CustomizableUI.AREA_NAVBAR, "Widget should be in navbar");
CustomizableUI.destroyWidget(kWidgetId);
CustomizableUI.removeWidgetFromArea(kWidgetId);
Services.prefs.clearUserPref(kPrefCustomizationAutoAdd);
});

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

@ -583,6 +583,25 @@ nsBrowserContentHandler.prototype = {
overridePage = overridePage.replace("%OLD_VERSION%", old_mstone);
break;
// Temporary case for Australis whatsnew
case OVERRIDE_NEW_BUILD_ID:
let locale = "en-US";
try {
locale = Services.prefs.getCharPref("general.useragent.locale");
} catch (e) {}
let showedAustralisWhatsNew = false;
try {
showedAustralisWhatsNew = Services.prefs.getBoolPref("browser.showedAustralisWhatsNew");
} catch(e) {}
// Show the Australis whatsnew page for en-US if we haven't yet shown it
if (!showedAustralisWhatsNew && locale == "en-US") {
Services.prefs.setBoolPref("browser.showedAustralisWhatsNew", true);
overridePage = "https://www.mozilla.org/en-US/firefox/29.0a1/whatsnew/";
}
break;
}
}
} catch (ex) {}

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

@ -182,9 +182,6 @@ let TabStateInternal = {
tabData.index = history.index;
}
// Copy data from the persistent cache.
this._copyFromPersistentCache(tab, tabData);
// If we're still the latest async collection for the given tab and
// the cache hasn't been filled by collect() in the meantime, let's
// fill the cache with the data we received.
@ -193,6 +190,16 @@ let TabStateInternal = {
this._pendingCollections.delete(browser);
}
// Copy data from the persistent cache. We need to create an explicit
// copy of the |tabData| object so that the properties injected by
// |_copyFromPersistentCache| don't end up in the non-persistent cache.
// The persistent cache does not store "null" values, so any values that
// have been cleared by the frame script would not be overriden by
// |_copyFromPersistentCache|. These two caches are only an interim
// solution and the non-persistent one will go away soon.
tabData = Utils.copy(tabData);
this._copyFromPersistentCache(tab, tabData);
throw new Task.Result(tabData);
}.bind(this));
@ -219,7 +226,16 @@ let TabStateInternal = {
throw new TypeError("Expecting a tab");
}
if (TabStateCache.has(tab)) {
return TabStateCache.get(tab);
// Copy data from the persistent cache. We need to create an explicit
// copy of the |tabData| object so that the properties injected by
// |_copyFromPersistentCache| don't end up in the non-persistent cache.
// The persistent cache does not store "null" values, so any values that
// have been cleared by the frame script would not be overriden by
// |_copyFromPersistentCache|. These two caches are only an interim
// solution and the non-persistent one will go away soon.
let tabData = Utils.copy(TabStateCache.get(tab));
this._copyFromPersistentCache(tab, tabData);
return tabData;
}
let tabData = this._collectSyncUncached(tab);
@ -228,6 +244,16 @@ let TabStateInternal = {
TabStateCache.set(tab, tabData);
}
// Copy data from the persistent cache. We need to create an explicit
// copy of the |tabData| object so that the properties injected by
// |_copyFromPersistentCache| don't end up in the non-persistent cache.
// The persistent cache does not store "null" values, so any values that
// have been cleared by the frame script would not be overriden by
// |_copyFromPersistentCache|. These two caches are only an interim
// solution and the non-persistent one will go away soon.
tabData = Utils.copy(tabData);
this._copyFromPersistentCache(tab, tabData);
// Prevent all running asynchronous collections from filling the cache.
// Every asynchronous data collection started before a collectSync() call
// can't expect to retrieve different data than the sync call. That's why
@ -262,7 +288,13 @@ let TabStateInternal = {
* up-to-date.
*/
clone: function (tab) {
return this._collectSyncUncached(tab, {includePrivateData: true});
let options = {includePrivateData: true};
let tabData = this._collectSyncUncached(tab, options);
// Copy data from the persistent cache.
this._copyFromPersistentCache(tab, tabData, options);
return tabData;
},
/**
@ -305,9 +337,6 @@ let TabStateInternal = {
tabData.index = history.index;
}
// Copy data from the persistent cache.
this._copyFromPersistentCache(tab, tabData, options);
return tabData;
},

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

@ -64,5 +64,16 @@ this.Utils = Object.freeze({
map.set(otherKey, value);
map.delete(key);
}
},
// Copies all properties of a given object to a new one and returns it.
copy: function (from) {
let to = {};
for (let key of Object.keys(from)) {
to[key] = from[key];
}
return to;
}
});

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

@ -1928,7 +1928,10 @@ Breakpoints.prototype = {
let disabledPromise = this._disabled.get(identifier);
if (disabledPromise) {
disabledPromise.then(({ conditionalExpression: previousValue }) => {
aBreakpointClient.conditionalExpression = previousValue;
// Setting a falsy conditional expression is redundant.
if (previousValue) {
aBreakpointClient.conditionalExpression = previousValue;
}
});
this._disabled.delete(identifier);
}

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

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

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

@ -77,13 +77,13 @@ function test() {
.getAttribute("value"), "getName",
"Should have the right property name for 'getName' in person.");
is(personNode.get("getName").target.querySelector(".value")
.getAttribute("value"), "Function",
.getAttribute("value"), "_pfactory/<.getName()",
"'getName' in person should have the right value.");
is(personNode.get("getFoo").target.querySelector(".name")
.getAttribute("value"), "getFoo",
"Should have the right property name for 'getFoo' in person.");
is(personNode.get("getFoo").target.querySelector(".value")
.getAttribute("value"), "Function",
.getAttribute("value"), "_pfactory/<.getFoo()",
"'getFoo' in person should have the right value.");
// Expand the function nodes. This causes their properties to be

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

@ -0,0 +1,84 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Make sure that conditional breakpoints with undefined expressions
* are stored as plain breakpoints when re-enabling them.
*/
const TAB_URL = EXAMPLE_URL + "doc_conditional-breakpoints.html";
function test() {
let gTab, gDebuggee, gPanel, gDebugger;
let gSources, gBreakpoints, gLocation;
initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
gTab = aTab;
gDebuggee = aDebuggee;
gPanel = aPanel;
gDebugger = gPanel.panelWin;
gSources = gDebugger.DebuggerView.Sources;
gBreakpoints = gDebugger.DebuggerController.Breakpoints;
gLocation = { url: gSources.selectedValue, line: 18 };
waitForSourceAndCaretAndScopes(gPanel, ".html", 17)
.then(addBreakpoint)
.then(setDummyConditional)
.then(() => {
let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.BREAKPOINT_REMOVED);
toggleBreakpoint();
return finished;
})
.then(() => {
let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.BREAKPOINT_ADDED);
toggleBreakpoint();
return finished;
})
.then(testConditionalExpressionOnClient)
.then(() => {
let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.CONDITIONAL_BREAKPOINT_POPUP_SHOWING);
openConditionalPopup();
finished.then(() => ok(false, "The popup shouldn't have opened."));
return waitForTime(1000);
})
.then(() => resumeDebuggerThenCloseAndFinish(gPanel))
.then(null, aError => {
ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
});
gDebuggee.ermahgerd();
});
function addBreakpoint() {
return gPanel.addBreakpoint(gLocation);
}
function setDummyConditional(aClient) {
// This happens when a conditional expression input popup is shown
// but the user doesn't type anything into it.
aClient.conditionalExpression = "";
}
function toggleBreakpoint() {
EventUtils.sendMouseEvent({ type: "click" },
gDebugger.document.querySelector(".dbg-breakpoint-checkbox"),
gDebugger);
}
function openConditionalPopup() {
EventUtils.sendMouseEvent({ type: "click" },
gDebugger.document.querySelector(".dbg-breakpoint"),
gDebugger);
}
function testConditionalExpressionOnClient() {
return gBreakpoints._getAdded(gLocation).then(aClient => {
if ("conditionalExpression" in aClient) {
ok(false, "A conditional expression shouldn't have been set.");
} else {
ok(true, "The conditional expression wasn't set, as expected.");
}
});
}
}

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

@ -30,13 +30,15 @@ function testPause() {
gDebugger.gThreadClient.addOneTimeListener("paused", () => {
gToolbox.selectTool("webconsole").then(() => {
ok(gToolboxTab.classList.contains("highlighted"),
ok(gToolboxTab.hasAttribute("highlighted") &&
gToolboxTab.getAttribute("highlighted") == "true",
"The highlighted class is present");
ok(!gToolboxTab.hasAttribute("selected") ||
gToolboxTab.getAttribute("selected") != "true",
"The tab is not selected");
}).then(() => gToolbox.selectTool("jsdebugger")).then(() => {
ok(gToolboxTab.classList.contains("highlighted"),
ok(gToolboxTab.hasAttribute("highlighted") &&
gToolboxTab.getAttribute("highlighted") == "true",
"The highlighted class is present");
ok(gToolboxTab.hasAttribute("selected") &&
gToolboxTab.getAttribute("selected") == "true",

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

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

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

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

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

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

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

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

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

@ -89,14 +89,16 @@ function testExpandVariables() {
waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_PROPERTIES, 3).then(() => {
is(thisVar.get("window").target.querySelector(".name").getAttribute("value"), "window",
"Should have the right property name for 'window'.");
is(thisVar.get("window").target.querySelector(".value").getAttribute("value"), "Window",
is(thisVar.get("window").target.querySelector(".value").getAttribute("value"),
"Window \u2192 doc_frame-parameters.html",
"Should have the right property value for 'window'.");
ok(thisVar.get("window").target.querySelector(".value").className.contains("token-other"),
"Should have the right token class for 'window'.");
is(thisVar.get("document").target.querySelector(".name").getAttribute("value"), "document",
"Should have the right property name for 'document'.");
is(thisVar.get("document").target.querySelector(".value").getAttribute("value"), "HTMLDocument",
is(thisVar.get("document").target.querySelector(".value").getAttribute("value"),
"HTMLDocument \u2192 doc_frame-parameters.html",
"Should have the right property value for 'document'.");
ok(thisVar.get("document").target.querySelector(".value").className.contains("token-other"),
"Should have the right token class for 'document'.");

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

@ -56,7 +56,8 @@ function testScopeVariables() {
is(localEnums[0].querySelector(".name").getAttribute("value"), "this",
"Should have the right property name for 'this'.");
is(localEnums[0].querySelector(".value").getAttribute("value"), "Window",
is(localEnums[0].querySelector(".value").getAttribute("value"),
"Window \u2192 doc_frame-parameters.html",
"Should have the right property value for 'this'.");
ok(localEnums[0].querySelector(".value").className.contains("token-other"),
"Should have the right token class for 'this'.");
@ -192,7 +193,8 @@ function testArgumentsProperties() {
is(argsNonEnums[0].querySelector(".name").getAttribute("value"), "callee",
"Should have the right property name for 'callee'.");
is(argsNonEnums[0].querySelector(".value").getAttribute("value"), "Function",
is(argsNonEnums[0].querySelector(".value").getAttribute("value"),
"test(aArg,bArg,cArg,dArg,eArg,fArg)",
"Should have the right property name for 'callee'.");
ok(argsNonEnums[0].querySelector(".value").className.contains("token-other"),
"Should have the right token class for 'callee'.");
@ -518,14 +520,16 @@ function testGetterSetterObject() {
is(propNonEnums[0].querySelector(".name").getAttribute("value"), "get",
"Should have the right property name for 'get'.");
is(propNonEnums[0].querySelector(".value").getAttribute("value"), "Function",
is(propNonEnums[0].querySelector(".value").getAttribute("value"),
"test/myVar.prop()",
"Should have the right property value for 'get'.");
ok(propNonEnums[0].querySelector(".value").className.contains("token-other"),
"Should have the right token class for 'get'.");
is(propNonEnums[1].querySelector(".name").getAttribute("value"), "set",
"Should have the right property name for 'set'.");
is(propNonEnums[1].querySelector(".value").getAttribute("value"), "Function",
is(propNonEnums[1].querySelector(".value").getAttribute("value"),
"test/myVar.prop(val)",
"Should have the right property value for 'set'.");
ok(propNonEnums[1].querySelector(".value").className.contains("token-other"),
"Should have the right token class for 'set'.");

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

@ -71,12 +71,14 @@ function testGlobalScope() {
is(globalScope.get("window").target.querySelector(".name").getAttribute("value"), "window",
"Should have the right property name for 'window'.");
is(globalScope.get("window").target.querySelector(".value").getAttribute("value"), "Window",
is(globalScope.get("window").target.querySelector(".value").getAttribute("value"),
"Window \u2192 doc_frame-parameters.html",
"Should have the right property value for 'window'.");
is(globalScope.get("document").target.querySelector(".name").getAttribute("value"), "document",
"Should have the right property name for 'document'.");
is(globalScope.get("document").target.querySelector(".value").getAttribute("value"), "HTMLDocument",
is(globalScope.get("document").target.querySelector(".value").getAttribute("value"),
"HTMLDocument \u2192 doc_frame-parameters.html",
"Should have the right property value for 'document'.");
is(globalScope.get("undefined").target.querySelector(".name").getAttribute("value"), "undefined",
@ -123,12 +125,14 @@ function testWindowVariable() {
is(windowVar.get("window").target.querySelector(".name").getAttribute("value"), "window",
"Should have the right property name for 'window'.");
is(windowVar.get("window").target.querySelector(".value").getAttribute("value"), "Window",
is(windowVar.get("window").target.querySelector(".value").getAttribute("value"),
"Window \u2192 doc_frame-parameters.html",
"Should have the right property value for 'window'.");
is(windowVar.get("document").target.querySelector(".name").getAttribute("value"), "document",
"Should have the right property name for 'document'.");
is(windowVar.get("document").target.querySelector(".value").getAttribute("value"), "HTMLDocument",
is(windowVar.get("document").target.querySelector(".value").getAttribute("value"),
"HTMLDocument \u2192 doc_frame-parameters.html",
"Should have the right property value for 'document'.");
is(windowVar.get("undefined").target.querySelector(".name").getAttribute("value"), "undefined",

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

@ -57,7 +57,8 @@ function testFirstWithScope() {
is(withEnums[0].querySelector(".name").getAttribute("value"), "this",
"Should have the right property name for 'this'.");
is(withEnums[0].querySelector(".value").getAttribute("value"), "Window",
is(withEnums[0].querySelector(".value").getAttribute("value"),
"Window \u2192 doc_with-frame.html",
"Should have the right property value for 'this'.");
ok(withEnums[0].querySelector(".value").className.contains("token-other"),
"Should have the right token class for 'this'.");
@ -131,7 +132,7 @@ function testSecondWithScope() {
is(secondWithScope.get("random").target.querySelector(".name").getAttribute("value"), "random",
"Should have the right property name for 'random'.");
is(secondWithScope.get("random").target.querySelector(".value").getAttribute("value"), "Function",
is(secondWithScope.get("random").target.querySelector(".value").getAttribute("value"), "random()",
"Should have the right property value for 'random'.");
ok(secondWithScope.get("random").target.querySelector(".value").className.contains("token-other"),
"Should have the right token class for 'random'.");

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

@ -54,7 +54,7 @@ function initialChecks() {
is(arrayVar.target.querySelector(".name").getAttribute("value"), "largeArray",
"Should have the right property name for 'largeArray'.");
is(arrayVar.target.querySelector(".value").getAttribute("value"), "Int8Array",
is(arrayVar.target.querySelector(".value").getAttribute("value"), "Int8Array[10000]",
"Should have the right property value for 'largeArray'.");
ok(arrayVar.target.querySelector(".value").className.contains("token-other"),
"Should have the right token class for 'largeArray'.");

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

@ -62,7 +62,7 @@ function performTest() {
is(buttonVar.target.querySelector(".name").getAttribute("value"), "button",
"Should have the right property name for 'button'.");
is(buttonVar.target.querySelector(".value").getAttribute("value"), "HTMLButtonElement",
is(buttonVar.target.querySelector(".value").getAttribute("value"), "<button>",
"Should have the right property value for 'button'.");
ok(buttonVar.target.querySelector(".value").className.contains("token-other"),
"Should have the right token class for 'button'.");
@ -76,7 +76,8 @@ function performTest() {
is(documentVar.target.querySelector(".name").getAttribute("value"), "document",
"Should have the right property name for 'document'.");
is(documentVar.target.querySelector(".value").getAttribute("value"), "HTMLDocument",
is(documentVar.target.querySelector(".value").getAttribute("value"),
"HTMLDocument \u2192 doc_frame-parameters.html",
"Should have the right property value for 'document'.");
ok(documentVar.target.querySelector(".value").className.contains("token-other"),
"Should have the right token class for 'document'.");
@ -98,14 +99,14 @@ function performTest() {
is(buttonVar.get("childNodes").target.querySelector(".name").getAttribute("value"), "childNodes",
"Should have the right property name for 'childNodes'.");
is(buttonVar.get("childNodes").target.querySelector(".value").getAttribute("value"), "NodeList",
is(buttonVar.get("childNodes").target.querySelector(".value").getAttribute("value"), "NodeList[1]",
"Should have the right property value for 'childNodes'.");
ok(buttonVar.get("childNodes").target.querySelector(".value").className.contains("token-other"),
"Should have the right token class for 'childNodes'.");
is(buttonVar.get("onclick").target.querySelector(".name").getAttribute("value"), "onclick",
"Should have the right property name for 'onclick'.");
is(buttonVar.get("onclick").target.querySelector(".value").getAttribute("value"), "Function",
is(buttonVar.get("onclick").target.querySelector(".value").getAttribute("value"), "onclick(event)",
"Should have the right property value for 'onclick'.");
ok(buttonVar.get("onclick").target.querySelector(".value").className.contains("token-other"),
"Should have the right token class for 'onclick'.");
@ -119,7 +120,7 @@ function performTest() {
is(documentVar.get("childNodes").target.querySelector(".name").getAttribute("value"), "childNodes",
"Should have the right property name for 'childNodes'.");
is(documentVar.get("childNodes").target.querySelector(".value").getAttribute("value"), "NodeList",
is(documentVar.get("childNodes").target.querySelector(".value").getAttribute("value"), "NodeList[3]",
"Should have the right property value for 'childNodes'.");
ok(documentVar.get("childNodes").target.querySelector(".value").className.contains("token-other"),
"Should have the right token class for 'childNodes'.");
@ -144,7 +145,7 @@ function performTest() {
is(buttonAsProtoProtoVar.target.querySelector(".name").getAttribute("value"), "__proto__",
"Should have the right property name for '__proto__'.");
is(buttonAsProtoProtoVar.target.querySelector(".value").getAttribute("value"), "HTMLButtonElement",
is(buttonAsProtoProtoVar.target.querySelector(".value").getAttribute("value"), "<button>",
"Should have the right property value for '__proto__'.");
ok(buttonAsProtoProtoVar.target.querySelector(".value").className.contains("token-other"),
"Should have the right token class for '__proto__'.");
@ -173,14 +174,14 @@ function performTest() {
is(buttonAsProtoProtoVar.get("childNodes").target.querySelector(".name").getAttribute("value"), "childNodes",
"Should have the right property name for 'childNodes'.");
is(buttonAsProtoProtoVar.get("childNodes").target.querySelector(".value").getAttribute("value"), "NodeList",
is(buttonAsProtoProtoVar.get("childNodes").target.querySelector(".value").getAttribute("value"), "NodeList[1]",
"Should have the right property value for 'childNodes'.");
ok(buttonAsProtoProtoVar.get("childNodes").target.querySelector(".value").className.contains("token-other"),
"Should have the right token class for 'childNodes'.");
is(buttonAsProtoProtoVar.get("onclick").target.querySelector(".name").getAttribute("value"), "onclick",
"Should have the right property name for 'onclick'.");
is(buttonAsProtoProtoVar.get("onclick").target.querySelector(".value").getAttribute("value"), "Function",
is(buttonAsProtoProtoVar.get("onclick").target.querySelector(".value").getAttribute("value"), "onclick(event)",
"Should have the right property value for 'onclick'.");
ok(buttonAsProtoProtoVar.get("onclick").target.querySelector(".value").className.contains("token-other"),
"Should have the right token class for 'onclick'.");

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

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

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

@ -796,7 +796,7 @@ Toolbox.prototype = {
*/
highlightTool: function(id) {
let tab = this.doc.getElementById("toolbox-tab-" + id);
tab && tab.classList.add("highlighted");
tab && tab.setAttribute("highlighted", "true");
},
/**
@ -807,7 +807,7 @@ Toolbox.prototype = {
*/
unhighlightTool: function(id) {
let tab = this.doc.getElementById("toolbox-tab-" + id);
tab && tab.classList.remove("highlighted");
tab && tab.removeAttribute("highlighted");
},
/**

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

@ -27,6 +27,9 @@ Cu.import("resource://gre/modules/devtools/DevToolsUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "devtools",
"resource://gre/modules/devtools/Loader.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
"resource://gre/modules/PluralForm.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "clipboardHelper",
"@mozilla.org/widget/clipboardhelper;1",
"nsIClipboardHelper");
@ -2346,7 +2349,10 @@ Variable.prototype = Heritage.extend(Scope.prototype, {
this._valueLabel.classList.remove(VariablesView.getClass(prevGrip));
}
this._valueGrip = aGrip;
this._valueString = VariablesView.getString(aGrip, true);
this._valueString = VariablesView.getString(aGrip, {
concise: true,
noEllipsis: true,
});
this._valueClassName = VariablesView.getClass(aGrip);
this._valueLabel.classList.add(this._valueClassName);
@ -3118,12 +3124,16 @@ VariablesView.getGrip = function(aValue) {
*
* @param any aGrip
* @see Variable.setGrip
* @param boolean aConciseFlag
* Return a concisely formatted property string.
* @param object aOptions
* Options:
* - concise: boolean that tells you want a concisely formatted string.
* - noStringQuotes: boolean that tells to not quote strings.
* - noEllipsis: boolean that tells to not add an ellipsis after the
* initial text of a longString.
* @return string
* The formatted property string.
*/
VariablesView.getString = function(aGrip, aConciseFlag) {
VariablesView.getString = function(aGrip, aOptions = {}) {
if (aGrip && typeof aGrip == "object") {
switch (aGrip.type) {
case "undefined":
@ -3133,18 +3143,30 @@ VariablesView.getString = function(aGrip, aConciseFlag) {
case "-Infinity":
case "-0":
return aGrip.type;
case "longString":
return "\"" + aGrip.initial + "\"";
default:
if (!aConciseFlag) {
return "[" + aGrip.type + " " + aGrip.class + "]";
let stringifier = VariablesView.stringifiers.byType[aGrip.type];
if (stringifier) {
let result = stringifier(aGrip, aOptions);
if (result != null) {
return result;
}
}
return aGrip.class;
if (aGrip.displayString) {
return VariablesView.getString(aGrip.displayString, aOptions);
}
if (aGrip.type == "object" && aOptions.concise) {
return aGrip.class;
}
return "[" + aGrip.type + " " + aGrip.class + "]";
}
}
switch (typeof aGrip) {
case "string":
return "\"" + aGrip + "\"";
return VariablesView.stringifiers.byType.string(aGrip, aOptions);
case "boolean":
return aGrip ? "true" : "false";
case "number":
@ -3156,6 +3178,367 @@ VariablesView.getString = function(aGrip, aConciseFlag) {
}
};
/**
* The VariablesView stringifiers are used by VariablesView.getString(). These
* are organized by object type, object class and by object actor preview kind.
* Some objects share identical ways for previews, for example Arrays, Sets and
* NodeLists.
*
* Any stringifier function must return a string. If null is returned, * then
* the default stringifier will be used. When invoked, the stringifier is
* given the same two arguments as those given to VariablesView.getString().
*/
VariablesView.stringifiers = {};
VariablesView.stringifiers.byType = {
string: function(aGrip, {noStringQuotes}) {
if (noStringQuotes) {
return aGrip;
}
return uneval(aGrip);
},
longString: function({initial}, {noStringQuotes, noEllipsis}) {
let ellipsis = noEllipsis ? "" : Scope.ellipsis;
if (noStringQuotes) {
return initial + ellipsis;
}
let result = uneval(initial);
if (!ellipsis) {
return result;
}
return result.substr(0, result.length - 1) + ellipsis + '"';
},
object: function(aGrip, aOptions) {
let {preview} = aGrip;
let stringifier;
if (preview && preview.kind) {
stringifier = VariablesView.stringifiers.byObjectKind[preview.kind];
}
if (!stringifier && aGrip.class) {
stringifier = VariablesView.stringifiers.byObjectClass[aGrip.class];
}
if (stringifier) {
return stringifier(aGrip, aOptions);
}
return null;
},
}; // VariablesView.stringifiers.byType
VariablesView.stringifiers.byObjectClass = {
Function: function(aGrip, {concise}) {
// TODO: Bug 948484 - support arrow functions and ES6 generators
let name = aGrip.userDisplayName || aGrip.displayName || aGrip.name || "";
name = VariablesView.getString(name, { noStringQuotes: true });
// TODO: Bug 948489 - Support functions with destructured parameters and
// rest parameters
let params = aGrip.parameterNames || "";
if (!concise) {
return "function " + name + "(" + params + ")";
}
return (name || "function ") + "(" + params + ")";
},
RegExp: function({displayString}) {
return VariablesView.getString(displayString, { noStringQuotes: true });
},
Date: function({preview}) {
if (!preview || !("timestamp" in preview)) {
return null;
}
if (typeof preview.timestamp != "number") {
return new Date(preview.timestamp).toString(); // invalid date
}
return "Date " + new Date(preview.timestamp).toISOString();
},
}; // VariablesView.stringifiers.byObjectClass
VariablesView.stringifiers.byObjectKind = {
ArrayLike: function(aGrip, {concise}) {
let {preview} = aGrip;
if (concise) {
return aGrip.class + "[" + preview.length + "]";
}
if (!preview.items) {
return null;
}
let shown = 0, result = [], lastHole = null;
for (let item of preview.items) {
if (item === null) {
if (lastHole !== null) {
result[lastHole] += ",";
} else {
result.push("");
}
lastHole = result.length - 1;
} else {
lastHole = null;
result.push(VariablesView.getString(item, { concise: true }));
}
shown++;
}
if (shown < preview.length) {
let n = preview.length - shown;
result.push(VariablesView.stringifiers._getNMoreString(n));
} else if (lastHole !== null) {
// make sure we have the right number of commas...
result[lastHole] += ",";
}
let prefix = aGrip.class == "Array" ? "" : aGrip.class + " ";
return prefix + "[" + result.join(", ") + "]";
},
MapLike: function(aGrip, {concise}) {
let {preview} = aGrip;
if (concise || !preview.entries) {
let size = typeof preview.size == "number" ?
"[" + preview.size + "]" : "";
return aGrip.class + size;
}
let entries = [];
for (let [key, value] of preview.entries) {
let keyString = VariablesView.getString(key, {
concise: true,
noStringQuotes: true,
});
let valueString = VariablesView.getString(value, { concise: true });
entries.push(keyString + ": " + valueString);
}
if (typeof preview.size == "number" && preview.size > entries.length) {
let n = preview.size - entries.length;
entries.push(VariablesView.stringifiers._getNMoreString(n));
}
return aGrip.class + " {" + entries.join(", ") + "}";
},
ObjectWithText: function(aGrip, {concise}) {
if (concise) {
return aGrip.class;
}
return aGrip.class + " " + VariablesView.getString(aGrip.preview.text);
},
ObjectWithURL: function(aGrip, {concise}) {
let result = aGrip.class;
let url = aGrip.preview.url;
if (!VariablesView.isFalsy({ value: url })) {
result += " \u2192 " + WebConsoleUtils.abbreviateSourceURL(url,
{ onlyCropQuery: !concise });
}
return result;
},
// Stringifier for any kind of object.
Object: function(aGrip, {concise}) {
if (concise) {
return aGrip.class;
}
let {preview} = aGrip;
let props = [];
for (let key of Object.keys(preview.ownProperties || {})) {
let value = preview.ownProperties[key];
let valueString = "";
if (value.get) {
valueString = "Getter";
} else if (value.set) {
valueString = "Setter";
} else {
valueString = VariablesView.getString(value.value, { concise: true });
}
props.push(key + ": " + valueString);
}
for (let key of Object.keys(preview.safeGetterValues || {})) {
let value = preview.safeGetterValues[key];
let valueString = VariablesView.getString(value.getterValue,
{ concise: true });
props.push(key + ": " + valueString);
}
if (!props.length) {
return null;
}
if (preview.ownPropertiesLength) {
let previewLength = Object.keys(preview.ownProperties).length;
let diff = preview.ownPropertiesLength - previewLength;
if (diff > 0) {
props.push(VariablesView.stringifiers._getNMoreString(diff));
}
}
let prefix = aGrip.class != "Object" ? aGrip.class + " " : "";
return prefix + "{" + props.join(", ") + "}";
}, // Object
Error: function(aGrip, {concise}) {
let {preview} = aGrip;
let name = VariablesView.getString(preview.name, { noStringQuotes: true });
if (concise) {
return name || aGrip.class;
}
let msg = name + ": " +
VariablesView.getString(preview.message, { noStringQuotes: true });
if (!VariablesView.isFalsy({ value: preview.stack })) {
msg += "\n" + STR.GetStringFromName("variablesViewErrorStacktrace") +
"\n" + preview.stack;
}
return msg;
},
DOMException: function(aGrip, {concise}) {
let {preview} = aGrip;
if (concise) {
return preview.name || aGrip.class;
}
let msg = aGrip.class + " [" + preview.name + ": " +
VariablesView.getString(preview.message) + "\n" +
"code: " + preview.code + "\n" +
"nsresult: 0x" + (+preview.result).toString(16);
if (preview.filename) {
msg += "\nlocation: " + preview.filename;
if (preview.lineNumber) {
msg += ":" + preview.lineNumber;
}
}
return msg + "]";
},
DOMEvent: function(aGrip, {concise}) {
let {preview} = aGrip;
if (!preview.type) {
return null;
}
if (concise) {
return aGrip.class + " " + preview.type;
}
let result = preview.type;
if (preview.eventKind == "key" && preview.modifiers &&
preview.modifiers.length) {
result += " " + preview.modifiers.join("-");
}
let props = [];
if (preview.target) {
let target = VariablesView.getString(preview.target, { concise: true });
props.push("target: " + target);
}
for (let prop in preview.properties) {
let value = preview.properties[prop];
props.push(prop + ": " + VariablesView.getString(value, { concise: true }));
}
return result + " {" + props.join(", ") + "}";
}, // DOMEvent
DOMNode: function(aGrip, {concise}) {
let {preview} = aGrip;
switch (preview.nodeType) {
case Ci.nsIDOMNode.DOCUMENT_NODE: {
let location = WebConsoleUtils.abbreviateSourceURL(preview.location,
{ onlyCropQuery: !concise });
return aGrip.class + " \u2192 " + location;
}
case Ci.nsIDOMNode.ATTRIBUTE_NODE: {
let value = VariablesView.getString(preview.value, { noStringQuotes: true });
return preview.nodeName + '="' + escapeHTML(value) + '"';
}
case Ci.nsIDOMNode.TEXT_NODE:
return preview.nodeName + " " +
VariablesView.getString(preview.textContent);
case Ci.nsIDOMNode.COMMENT_NODE: {
let comment = VariablesView.getString(preview.textContent,
{ noStringQuotes: true });
return "<!--" + comment + "-->";
}
case Ci.nsIDOMNode.DOCUMENT_FRAGMENT_NODE: {
if (concise || !preview.childNodes) {
return aGrip.class + "[" + preview.childNodesLength + "]";
}
let nodes = [];
for (let node of preview.childNodes) {
nodes.push(VariablesView.getString(node));
}
if (nodes.length < preview.childNodesLength) {
let n = preview.childNodesLength - nodes.length;
nodes.push(VariablesView.stringifiers._getNMoreString(n));
}
return aGrip.class + " [" + nodes.join(", ") + "]";
}
case Ci.nsIDOMNode.ELEMENT_NODE: {
let attrs = preview.attributes;
if (!concise) {
let n = 0, result = "<" + preview.nodeName;
for (let name in attrs) {
let value = VariablesView.getString(attrs[name],
{ noStringQuotes: true });
result += " " + name + '="' + escapeHTML(value) + '"';
n++;
}
if (preview.attributesLength > n) {
result += " " + Scope.ellipsis;
}
return result + ">";
}
let result = "<" + preview.nodeName;
if (attrs.id) {
result += "#" + attrs.id;
}
return result + ">";
}
default:
return null;
}
}, // DOMNode
}; // VariablesView.stringifiers.byObjectKind
/**
* Get the "N more…" formatted string, given an N. This is used for displaying
* how many elements are not displayed in an object preview (eg. an array).
*
* @private
* @param number aNumber
* @return string
*/
VariablesView.stringifiers._getNMoreString = function(aNumber) {
let str = STR.GetStringFromName("variablesViewMoreObjects");
return PluralForm.get(aNumber, str).replace("#1", aNumber);
};
/**
* Returns a custom class style for a grip.
*
@ -3208,6 +3591,22 @@ let generateId = (function() {
};
})();
/**
* Escape some HTML special characters. We do not need full HTML serialization
* here, we just want to make strings safe to display in HTML attributes, for
* the stringifiers.
*
* @param string aString
* @return string
*/
function escapeHTML(aString) {
return aString.replace(/&/g, "&amp;")
.replace(/"/g, "&quot;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;");
}
/**
* An Editable encapsulates the UI of an edit box that overlays a label,
* allowing the user to edit the value.

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

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

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

@ -64,6 +64,10 @@ support-files =
test-console-extras.html
test-console-replaced-api.html
test-console.html
test-console-output-02.html
test-console-output-03.html
test-console-output-04.html
test-console-output-events.html
test-consoleiframes.html
test-data.json
test-data.json^headers^
@ -167,7 +171,6 @@ support-files =
[browser_webconsole_bug_597136_network_requests_from_chrome.js]
[browser_webconsole_bug_597460_filter_scroll.js]
[browser_webconsole_bug_597756_reopen_closed_tab.js]
[browser_webconsole_bug_598357_jsterm_output.js]
[browser_webconsole_bug_599725_response_headers.js]
[browser_webconsole_bug_600183_charset.js]
[browser_webconsole_bug_601177_log_levels.js]
@ -248,3 +251,8 @@ run-if = os == "mac"
[browser_webconsole_expandable_timestamps.js]
[browser_webconsole_autocomplete_in_debugger_stackframe.js]
[browser_webconsole_autocomplete_popup_close_on_tab_switch.js]
[browser_webconsole_output_01.js]
[browser_webconsole_output_02.js]
[browser_webconsole_output_03.js]
[browser_webconsole_output_04.js]
[browser_webconsole_output_events.js]

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

@ -36,7 +36,8 @@ function onExecuteFooObj(msg)
let anchor = msg.querySelector("a");
ok(anchor, "object anchor");
isnot(anchor.textContent.indexOf("[object Object]"), -1, "message text check");
isnot(anchor.textContent.indexOf('testProp: "testValue"'), -1,
"message text check");
gJSTerm.once("variablesview-fetched", onFooObjFetch);
EventUtils.synthesizeMouse(anchor, 2, 2, {}, gWebConsole.iframeWindow)
@ -76,7 +77,8 @@ function onExecuteWindow(msg)
ok(msg, "output message found");
let anchor = msg.querySelector("a");
ok(anchor, "object anchor");
isnot(anchor.textContent.indexOf("[object Window]"), -1, "message text check");
isnot(anchor.textContent.indexOf("Window \u2192 http://example.com/browser/"), -1,
"message text check");
gJSTerm.once("variablesview-fetched", onWindowFetch);
EventUtils.synthesizeMouse(anchor, 2, 2, {}, gWebConsole.iframeWindow)

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

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

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

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

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

@ -34,7 +34,7 @@ function performTest(hud)
}).then(([result]) => {
let clickable = result.clickableElements[0];
ok(clickable, "the console.log() object anchor was found");
isnot(clickable.textContent.indexOf("Object"), -1,
isnot(clickable.textContent.indexOf('{abba: "omgBug676722"}'), -1,
"clickable node content is correct");
hud.jsterm.once("variablesview-fetched",

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

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

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

@ -28,7 +28,8 @@ function consoleOpened(hud)
function onExecuteFooObj(msg)
{
ok(msg, "output message found");
isnot(msg.textContent.indexOf("[object Object]"), -1, "message text check");
isnot(msg.textContent.indexOf('{testProp: "testValue"}'), -1,
"message text check");
let anchor = msg.querySelector("a");
ok(anchor, "object link found");

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

@ -62,7 +62,8 @@ function onFramesAdded()
function onExecuteFooObj(msg)
{
ok(msg, "output message found");
isnot(msg.textContent.indexOf("[object Object]"), -1, "message text check");
isnot(msg.textContent.indexOf('{testProp2: "testValue2"}'), -1,
"message text check");
let anchor = msg.querySelector("a");
ok(anchor, "object link found");

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

@ -57,7 +57,8 @@ function onFramesAdded()
function onExecuteFooObj(msg)
{
ok(msg, "output message found");
isnot(msg.textContent.indexOf("[object Object]"), -1, "message text check");
isnot(msg.textContent.indexOf('{testProp2: "testValue2"}'), -1,
"message text check");
let anchor = msg.querySelector("a");
ok(anchor, "object link found");

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

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

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

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

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

@ -80,9 +80,8 @@ function tab2Loaded(aEvent) {
function testEnd() {
ok(noErrors, "there were no errors");
Array.forEach(win1.gBrowser.tabs, function(aTab) {
win1.gBrowser.removeTab(aTab);
});
win1.gBrowser.removeTab(tab1);
Array.forEach(win2.gBrowser.tabs, function(aTab) {
win2.gBrowser.removeTab(aTab);
});

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

@ -1,267 +0,0 @@
/* vim:set ts=2 sw=2 sts=2 et: */
/* ***** BEGIN LICENSE BLOCK *****
* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*
* Contributor(s):
* Mihai Șucan <mihai.sucan@gmail.com>
*
* ***** END LICENSE BLOCK ***** */
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console.html";
let testEnded = false;
let pos = -1;
let dateNow = Date.now();
let tempScope = {};
Cu.import("resource://gre/modules/devtools/dbg-server.jsm", tempScope);
let longString = (new Array(tempScope.DebuggerServer.LONG_STRING_LENGTH + 4)).join("a");
let initialString = longString.substring(0,
tempScope.DebuggerServer.LONG_STRING_INITIAL_LENGTH);
let inputValues = [
// [showsVariablesView?, input value, expected output format,
// print() output, console API output, optional console API test]
// 0
[false, "'hello \\nfrom \\rthe \\\"string world!'",
'"hello \nfrom \rthe "string world!"',
"hello \nfrom \rthe \"string world!"],
// 1
[false, "'\xFA\u1E47\u0129\xE7\xF6d\xEA \u021B\u0115\u0219\u0165'",
"\"\xFA\u1E47\u0129\xE7\xF6d\xEA \u021B\u0115\u0219\u0165\"",
"\xFA\u1E47\u0129\xE7\xF6d\xEA \u021B\u0115\u0219\u0165"],
// 2
[false, "window.location.href", '"' + TEST_URI + '"', TEST_URI],
// 3
[false, "0", "0"],
// 4
[false, "'0'", '"0"', "0"],
// 5
[false, "42", "42"],
// 6
[false, "'42'", '"42"', "42"],
// 7
[true, "/foobar/", "[object RegExp]", '"/foobar/"', "[object RegExp]"],
// 8
[false, "null", "null"],
// 9
[false, "undefined", "undefined"],
// 10
[false, "true", "true"],
// 11
[true, "document.getElementById", "[object Function]",
"function getElementById() {\n [native code]\n}",
"[object Function]"],
// 12
[true, "(function() { return 42; })", "[object Function]",
"function () { return 42; }", "[object Function]"],
// 13
[true, "new Date(" + dateNow + ")", "[object Date]", (new Date(dateNow)).toString(), "[object Date]"],
// 14
[true, "document.body", "[object HTMLBodyElement]"],
// 15
[true, "window.location", "[object Location]", TEST_URI, "[object Location]"],
// 16
[true, "[1,2,3,'a','b','c','4','5']", '[object Array]',
'1,2,3,a,b,c,4,5',
"[object Array]"],
// 17
[true, "({a:'b', c:'d', e:1, f:'2'})", "[object Object]"],
// 18
[false, "'" + longString + "'",
'"' + initialString + "\"[\u2026]", initialString],
];
longString = null;
initialString = null;
tempScope = null;
let eventHandlers = [];
let popupShown = [];
let HUD;
let testDriver;
function tabLoad(aEvent) {
browser.removeEventListener(aEvent.type, tabLoad, true);
waitForFocus(function () {
openConsole(null, function(aHud) {
HUD = aHud;
testNext();
});
}, content);
}
function subtestNext() {
testDriver.next();
}
function testNext() {
pos++;
if (pos == inputValues.length) {
testEnd();
return;
}
testDriver = testGen();
testDriver.next();
}
function testGen() {
let cpos = pos;
let showsVariablesView = inputValues[cpos][0];
let inputValue = inputValues[cpos][1];
let expectedOutput = inputValues[cpos][2];
let printOutput = inputValues[cpos].length >= 4 ?
inputValues[cpos][3] : expectedOutput;
let consoleOutput = inputValues[cpos].length >= 5 ?
inputValues[cpos][4] : printOutput;
let consoleTest = inputValues[cpos][5] || inputValue;
HUD.jsterm.clearOutput();
// Test the console.log() output.
let outputItem;
function onExecute(msg) {
outputItem = msg;
subtestNext();
}
HUD.jsterm.execute("console.log(" + consoleTest + ")");
waitForMessages({
webconsole: HUD,
messages: [{
name: "console API output is correct for inputValues[" + cpos + "]",
text: consoleOutput,
category: CATEGORY_WEBDEV,
severity: SEVERITY_LOG,
}],
}).then(subtestNext);
yield undefined;
HUD.jsterm.clearOutput();
// Test jsterm print() output.
HUD.jsterm.setInputValue("print(" + inputValue + ")");
HUD.jsterm.execute(null, onExecute);
yield undefined;
ok(outputItem,
"found the jsterm print() output line for inputValues[" + cpos + "]");
ok(outputItem.textContent.indexOf(printOutput) > -1,
"jsterm print() output is correct for inputValues[" + cpos + "]");
// Test jsterm execution output.
HUD.jsterm.clearOutput();
HUD.jsterm.setInputValue(inputValue);
HUD.jsterm.execute(null, onExecute);
yield undefined;
ok(outputItem, "found the jsterm output line for inputValues[" + cpos + "]");
ok(outputItem.textContent.indexOf(expectedOutput) > -1,
"jsterm output is correct for inputValues[" + cpos + "]");
let messageBody = outputItem.querySelector(".body a") ||
outputItem.querySelector(".body");
ok(messageBody, "we have the message body for inputValues[" + cpos + "]");
// Test click on output.
let eventHandlerID = eventHandlers.length + 1;
let variablesViewShown = function(aEvent, aView, aOptions) {
if (aOptions.label.indexOf(expectedOutput) == -1) {
return;
}
HUD.jsterm.off("variablesview-open", variablesViewShown);
eventHandlers[eventHandlerID] = null;
ok(showsVariablesView,
"the variables view shown for inputValues[" + cpos + "]");
popupShown[cpos] = true;
if (showsVariablesView) {
executeSoon(subtestNext);
}
};
HUD.jsterm.on("variablesview-open", variablesViewShown);
eventHandlers.push(variablesViewShown);
EventUtils.synthesizeMouse(messageBody, 2, 2, {}, HUD.iframeWindow);
if (showsVariablesView) {
info("messageBody tagName '" + messageBody.tagName + "' className '" + messageBody.className + "'");
yield undefined; // wait for the panel to open if we need to.
}
testNext();
yield undefined;
}
function testEnd() {
if (testEnded) {
return;
}
testEnded = true;
for (let i = 0; i < eventHandlers.length; i++) {
if (eventHandlers[i]) {
HUD.jsterm.off("variablesview-open", eventHandlers[i]);
}
}
for (let i = 0; i < inputValues.length; i++) {
if (inputValues[i][0] && !popupShown[i]) {
ok(false, "the variables view failed to show for inputValues[" + i + "]");
}
}
HUD = inputValues = testDriver = null;
executeSoon(finishTest);
}
function test() {
requestLongerTimeout(2);
addTab(TEST_URI);
browser.addEventListener("load", tabLoad, true);
}

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

@ -57,20 +57,16 @@ function autocompletePopupHidden()
popup._panel.removeEventListener("popuphidden", autocompletePopupHidden, false);
ok(!popup.isOpen, "popup is not open");
jsterm.once("autocomplete-updated", function() {
is(completeNode.value, testStr + "dy", "autocomplete shows document.body");
testPropertyPanel();
});
let inputStr = "document.b";
jsterm.setInputValue(inputStr);
EventUtils.synthesizeKey("o", {});
let testStr = inputStr.replace(/./g, " ") + " ";
waitForSuccess({
name: "autocomplete shows document.body",
validatorFn: function()
{
return completeNode.value == testStr + "dy";
},
successFn: testPropertyPanel,
failureFn: finishTest,
});
}
function testPropertyPanel()
@ -87,7 +83,6 @@ function testPropertyPanel()
function onVariablesViewReady(aEvent, aView)
{
findVariableViewProperties(aView, [
{ name: "body", value: "HTMLBodyElement" },
{ name: "body", value: "<body>" },
], { webconsole: gHUD }).then(finishTest);
}

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

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

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

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

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

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

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

@ -118,7 +118,7 @@ function testJSTerm(hud)
jsterm.clearOutput();
jsterm.execute("pprint({b:2, a:1})");
checkResult('" b: 2\n a: 1"', "pprint()");
checkResult("\" b: 2\\n a: 1\"", "pprint()");
yield undefined;
// check instanceof correctness, bug 599940
@ -154,7 +154,7 @@ function testJSTerm(hud)
// bug 614561
jsterm.clearOutput();
jsterm.execute("pprint('hi')");
checkResult('" 0: "h"\n 1: "i""', "pprint('hi')");
checkResult("\" 0: \\\"h\\\"\\n 1: \\\"i\\\"\"", "pprint('hi')");
yield undefined;
// check that pprint(function) shows function source, bug 618344

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

@ -0,0 +1,146 @@
/*
* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
// Test the webconsole output for various types of objects.
const TEST_URI = "data:text/html;charset=utf8,test for console output - 01";
let dateNow = Date.now();
let {DebuggerServer} = Cu.import("resource://gre/modules/devtools/dbg-server.jsm", {});
let LONG_STRING_LENGTH = DebuggerServer.LONG_STRING_LENGTH;
let LONG_STRING_INITIAL_LENGTH = DebuggerServer.LONG_STRING_INITIAL_LENGTH;
DebuggerServer.LONG_STRING_LENGTH = 100;
DebuggerServer.LONG_STRING_INITIAL_LENGTH = 50;
let longString = (new Array(DebuggerServer.LONG_STRING_LENGTH + 4)).join("a");
let initialString = longString.substring(0, DebuggerServer.LONG_STRING_INITIAL_LENGTH);
let inputTests = [
// 0
{
input: "'hello \\nfrom \\rthe \\\"string world!'",
output: "\"hello \\nfrom \\rthe \\\"string world!\"",
},
// 1
{
// unicode test
input: "'\xFA\u1E47\u0129\xE7\xF6d\xEA \u021B\u0115\u0219\u0165'",
output: "\"\\xFA\\u1E47\\u0129\\xE7\\xF6d\\xEA \\u021B\\u0115\\u0219\\u0165\"",
},
// 2
{
input: "'" + longString + "'",
output: '"' + initialString + "\"[\u2026]",
printOutput: initialString,
},
// 3
{
input: "''",
output: '""',
printOutput: '""',
},
// 4
{
input: "0",
output: "0",
},
// 5
{
input: "'0'",
output: '"0"',
},
// 6
{
input: "42",
output: "42",
},
// 7
{
input: "'42'",
output: '"42"',
},
// 8
{
input: "/foobar/",
output: "/foobar/",
inspectable: true,
},
// 9
{
input: "/foo?b*\\s\"ar/igym",
output: "/foo?b*\\s\"ar/gimy",
printOutput: "/foo?b*\\\\s\\\"ar/gimy",
inspectable: true,
},
// 10
{
input: "null",
output: "null",
},
// 11
{
input: "undefined",
output: "undefined",
},
// 12
{
input: "true",
output: "true",
},
// 13
{
input: "false",
output: "false",
},
// 14
{
input: "new Date(" + dateNow + ")",
output: "Date " + (new Date(dateNow)).toISOString(),
printOutput: (new Date(dateNow)).toString(),
inspectable: true,
},
// 15
{
input: "new Date('test')",
output: "Invalid Date",
printOutput: "Invalid Date",
inspectable: true,
variablesViewLabel: "Invalid Date",
},
];
longString = initialString = null;
function test() {
registerCleanupFunction(() => {
DebuggerServer.LONG_STRING_LENGTH = LONG_STRING_LENGTH;
DebuggerServer.LONG_STRING_INITIAL_LENGTH = LONG_STRING_INITIAL_LENGTH;
});
addTab(TEST_URI);
browser.addEventListener("load", function onLoad() {
browser.removeEventListener("load", onLoad, true);
openConsole().then((hud) => {
return checkOutputForInputs(hud, inputTests);
}).then(finishTest);
}, true);
}

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

@ -0,0 +1,161 @@
/*
* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
// Test the webconsole output for various types of objects.
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console-output-02.html";
let inputTests = [
// 0 - native named function
{
input: "document.getElementById",
output: "function getElementById()",
printOutput: "function getElementById() {\\n [native code]\\n}",
inspectable: true,
variablesViewLabel: "getElementById()",
},
// 1 - anonymous function
{
input: "(function() { return 42; })",
output: "function ()",
printOutput: "function () { return 42; }",
inspectable: true,
},
// 2 - named function
{
input: "window.testfn1",
output: "function testfn1()",
printOutput: "function testfn1() { return 42; }",
inspectable: true,
variablesViewLabel: "testfn1()",
},
// 3 - anonymous function, but spidermonkey gives us an inferred name.
{
input: "testobj1.testfn2",
output: "function testobj1.testfn2()",
printOutput: "function () { return 42; }",
inspectable: true,
variablesViewLabel: "testobj1.testfn2()",
},
// 4 - named function with custom display name
{
input: "window.testfn3",
output: "function testfn3DisplayName()",
printOutput: "function testfn3() { return 42; }",
inspectable: true,
variablesViewLabel: "testfn3DisplayName()",
},
// 5 - basic array
{
input: "window.array1",
output: '[1, 2, 3, "a", "b", "c", "4", "5"]',
printOutput: "1,2,3,a,b,c,4,5",
inspectable: true,
variablesViewLabel: "Array[8]",
},
// 6 - array with objects
{
input: "window.array2",
output: '["a", HTMLDocument \u2192 test-console-output-02.html, <body>, ' +
"DOMStringMap[0], DOMTokenList[0]]",
printOutput: '"a,[object HTMLDocument],[object HTMLBodyElement],' +
'[object DOMStringMap],"',
inspectable: true,
variablesViewLabel: "Array[5]",
},
// 7 - array with more than 10 elements
{
input: "window.array3",
output: '[1, Window \u2192 test-console-output-02.html, null, "a", "b", ' +
'undefined, false, "", -Infinity, testfn3DisplayName(), 3 more\u2026]',
printOutput: '"1,[object Window],,a,b,,false,,-Infinity,' +
'function testfn3() { return 42; },[object Object],foo,bar"',
inspectable: true,
variablesViewLabel: "Array[13]",
},
// 8 - array with holes and a cyclic reference
{
input: "window.array4",
output: '[,,,,, "test", Array[7]]',
printOutput: '",,,,,test,"',
inspectable: true,
variablesViewLabel: "Array[7]",
},
// 9
{
input: "window.typedarray1",
output: 'Int32Array [1, 287, 8651, 40983, 8754]',
printOutput: "[object Int32Array]",
inspectable: true,
variablesViewLabel: "Int32Array[5]",
},
// 10 - Set with cyclic reference
{
input: "window.set1",
output: 'Set [1, 2, null, Array[13], "a", "b", undefined, <head>, Set[9]]',
printOutput: "[object Set]",
inspectable: true,
variablesViewLabel: "Set[9]",
},
// 11 - Object with cyclic reference and a getter
{
input: "window.testobj2",
output: '{a: "b", c: "d", e: 1, f: "2", foo: Object, bar: Object, ' +
"getterTest: Getter}",
printOutput: "[object Object]",
inspectable: true,
variablesViewLabel: "Object",
},
// 12 - Object with more than 10 properties
{
input: "window.testobj3",
output: '{a: "b", c: "d", e: 1, f: "2", g: true, h: null, i: undefined, ' +
'j: "", k: StyleSheetList[0], l: NodeList[5], 2 more\u2026}',
printOutput: "[object Object]",
inspectable: true,
variablesViewLabel: "Object",
},
// 13 - Object with a non-enumerable property that we do not show
{
input: "window.testobj4",
output: '{a: "b", c: "d", 1 more\u2026}',
printOutput: "[object Object]",
inspectable: true,
variablesViewLabel: "Object",
},
// 14 - Map with cyclic references
{
input: "window.map1",
output: 'Map {a: "b", HTMLCollection[2]: Object, Map[3]: Set[9]}',
printOutput: "[object Map]",
inspectable: true,
variablesViewLabel: "Map[3]",
},
];
function test() {
addTab(TEST_URI);
browser.addEventListener("load", function onLoad() {
browser.removeEventListener("load", onLoad, true);
openConsole().then((hud) => {
return checkOutputForInputs(hud, inputTests);
}).then(finishTest);
}, true);
}

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

@ -0,0 +1,164 @@
/*
* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
// Test the webconsole output for various types of objects.
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console-output-03.html";
let inputTests = [
// 0
{
input: "document",
output: "HTMLDocument \u2192 " + TEST_URI,
printOutput: "[object HTMLDocument]",
inspectable: true,
noClick: true,
},
// 1
{
input: "window",
output: "Window \u2192 " + TEST_URI,
printOutput: "[object Window",
inspectable: true,
noClick: true,
},
// 2
{
input: "document.body",
output: "<body>",
printOutput: "[object HTMLBodyElement]",
inspectable: true,
noClick: true,
},
// 3
{
input: "document.body.dataset",
output: "DOMStringMap {}",
printOutput: "[object DOMStringMap]",
inspectable: true,
variablesViewLabel: "DOMStringMap[0]",
},
// 4
{
input: "document.body.classList",
output: "DOMTokenList []",
printOutput: '""',
inspectable: true,
variablesViewLabel: "DOMTokenList[0]",
},
// 5
{
input: "window.location.href",
output: '"' + TEST_URI + '"',
},
// 6
{
input: "window.location",
output: "Location \u2192 " + TEST_URI,
printOutput: TEST_URI,
inspectable: true,
variablesViewLabel: "Location \u2192 test-console-output-03.html",
},
// 7
{
input: "document.body.attributes",
output: "MozNamedAttrMap []",
printOutput: "[object MozNamedAttrMap]",
inspectable: true,
variablesViewLabel: "MozNamedAttrMap[0]",
},
// 8
{
input: "document.styleSheets",
output: "StyleSheetList []",
printOutput: "[object StyleSheetList",
inspectable: true,
variablesViewLabel: "StyleSheetList[0]",
},
// 9
{
input: "testBodyClassName()",
output: '<body class="test1 tezt2">',
printOutput: "[object HTMLBodyElement]",
inspectable: true,
noClick: true,
},
// 10
{
input: "testBodyID()",
output: '<body class="test1 tezt2" id="foobarid">',
printOutput: "[object HTMLBodyElement]",
inspectable: true,
noClick: true,
},
// 11
{
input: "document.body.classList",
output: 'DOMTokenList ["test1", "tezt2"]',
printOutput: '"test1 tezt2"',
inspectable: true,
variablesViewLabel: "DOMTokenList[2]",
},
// 12
{
input: "testBodyDataset()",
output: '<body class="test1 tezt2" id="foobarid"' +
' data-preview="zuzu&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
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
let WebConsoleUtils, gDevTools, TargetFactory, console, promise, require;
let WebConsoleUtils, TargetFactory, require;
let {gDevTools} = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});
let {console} = Cu.import("resource://gre/modules/devtools/Console.jsm", {});
let {Promise: promise} = Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js", {});
let {Task} = Cu.import("resource://gre/modules/Task.jsm", {});
(() => {
gDevTools = Cu.import("resource:///modules/devtools/gDevTools.jsm", {}).gDevTools;
console = Cu.import("resource://gre/modules/devtools/Console.jsm", {}).console;
promise = Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js", {}).Promise;
let tools = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools;
let utils = tools.require("devtools/toolkit/webconsole/utils");
TargetFactory = tools.TargetFactory;
let {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
let utils = devtools.require("devtools/toolkit/webconsole/utils");
TargetFactory = devtools.TargetFactory;
WebConsoleUtils = utils.Utils;
require = tools.require;
require = devtools.require;
})();
// promise._reportErrors = true; // please never leave me.
@ -152,15 +152,20 @@ function findLogEntry(aString)
* @param function [aCallback]
* Optional function to invoke after the Web Console completes
* initialization (web-console-created).
* @return object
* A promise that is resolved once the web console is open.
*/
function openConsole(aTab, aCallback = function() { })
{
let deferred = promise.defer();
let target = TargetFactory.forTab(aTab || tab);
gDevTools.showToolbox(target, "webconsole").then(function(toolbox) {
let hud = toolbox.getCurrentPanel().hud;
hud.jsterm._lazyVariablesView = false;
aCallback(hud);
deferred.resolve(hud);
});
return deferred.promise;
}
/**
@ -1259,3 +1264,153 @@ function whenDelayedStartupFinished(aWindow, aCallback)
}
}, "browser-delayed-startup-finished", false);
}
/**
* Check the web console output for the given inputs. Each input is checked for
* the expected JS eval result, the result of calling print(), the result of
* console.log(). The JS eval result is also checked if it opens the variables
* view on click.
*
* @param object hud
* The web console instance to work with.
* @param array inputTests
* An array of input tests. An input test element is an object. Each
* object has the following properties:
* - input: string, JS input value to execute.
*
* - output: string|RegExp, expected JS eval result.
*
* - inspectable: boolean, when true, the test runner expects the JS eval
* result is an object that can be clicked for inspection.
*
* - noClick: boolean, when true, the test runner does not click the JS
* eval result. Some objects, like |window|, have a lot of properties and
* opening vview for them is very slow (they can cause timeouts in debug
* builds).
*
* - printOutput: string|RegExp, optional, expected output for
* |print(input)|. If this is not provided, printOutput = output.
*
* - variablesViewLabel: string|RegExp, optional, the expected variables
* view label when the object is inspected. If this is not provided, then
* |output| is used.
*/
function checkOutputForInputs(hud, inputTests)
{
let eventHandlers = new Set();
function* runner()
{
for (let [i, entry] of inputTests.entries()) {
info("checkInput(" + i + "): " + entry.input);
yield checkInput(entry);
}
for (let fn of eventHandlers) {
hud.jsterm.off("variablesview-open", fn);
}
}
function* checkInput(entry)
{
yield checkConsoleLog(entry);
yield checkPrintOutput(entry);
yield checkJSEval(entry);
}
function checkConsoleLog(entry)
{
hud.jsterm.clearOutput();
hud.jsterm.execute("console.log(" + entry.input + ")");
return waitForMessages({
webconsole: hud,
messages: [{
name: "console.log() output: " + entry.output,
text: entry.output,
category: CATEGORY_WEBDEV,
severity: SEVERITY_LOG,
}],
});
}
function checkPrintOutput(entry)
{
hud.jsterm.clearOutput();
hud.jsterm.execute("print(" + entry.input + ")");
let printOutput = entry.printOutput || entry.output;
return waitForMessages({
webconsole: hud,
messages: [{
name: "print() output: " + printOutput,
text: printOutput,
category: CATEGORY_OUTPUT,
}],
});
}
function* checkJSEval(entry)
{
hud.jsterm.clearOutput();
hud.jsterm.execute(entry.input);
let [result] = yield waitForMessages({
webconsole: hud,
messages: [{
name: "JS eval output: " + entry.output,
text: entry.output,
category: CATEGORY_OUTPUT,
}],
});
if (!entry.noClick) {
let msg = [...result.matched][0];
yield checkObjectClick(entry, msg);
}
}
function checkObjectClick(entry, msg)
{
let body = msg.querySelector(".body a") || msg.querySelector(".body");
ok(body, "the message body");
let deferred = promise.defer();
entry._onVariablesViewOpen = onVariablesViewOpen.bind(null, entry, deferred);
hud.jsterm.on("variablesview-open", entry._onVariablesViewOpen);
eventHandlers.add(entry._onVariablesViewOpen);
body.scrollIntoView();
EventUtils.synthesizeMouse(body, 2, 2, {}, hud.iframeWindow);
if (entry.inspectable) {
info("message body tagName '" + body.tagName + "' className '" + body.className + "'");
return deferred.promise; // wait for the panel to open if we need to.
}
return promise.resolve(null);
}
function onVariablesViewOpen(entry, deferred, event, view, options)
{
let label = entry.variablesViewLabel || entry.output;
if (typeof label == "string" && options.label != label) {
return;
}
if (label instanceof RegExp && !label.test(options.label)) {
return;
}
hud.jsterm.off("variablesview-open", entry._onVariablesViewOpen);
eventHandlers.delete(entry._onVariablesViewOpen);
entry._onVariablesViewOpen = null;
ok(entry.inspectable, "variables view was shown");
deferred.resolve(null);
}
return Task.spawn(runner);
}

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

@ -0,0 +1,61 @@
<!DOCTYPE HTML>
<html dir="ltr" lang="en-US">
<head>
<meta charset="utf-8">
<title>Test the web console output - 02</title>
<!--
- Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/
-->
</head>
<body>
<p>hello world!</p>
<script type="text/javascript">
function testfn1() { return 42; }
var testobj1 = {
testfn2: function() { return 42; },
};
function testfn3() { return 42; }
testfn3.displayName = "testfn3DisplayName";
var array1 = [1, 2, 3, "a", "b", "c", "4", "5"];
var array2 = ["a", document, document.body, document.body.dataset,
document.body.classList];
var array3 = [1, window, null, "a", "b", undefined, false, "", -Infinity, testfn3, testobj1, "foo", "bar"];
var array4 = new Array(5);
array4.push("test");
array4.push(array4);
var typedarray1 = new Int32Array([1, 287, 8651, 40983, 8754]);
var set1 = new Set([1, 2, null, array3, "a", "b", undefined, document.head]);
set1.add(set1);
var testobj2 = {a: "b", c: "d", e: 1, f: "2"};
testobj2.foo = testobj1;
testobj2.bar = testobj2;
Object.defineProperty(testobj2, "getterTest", {
enumerable: true,
get: function() {
return 42;
},
});
var testobj3 = {a: "b", c: "d", e: 1, f: "2", g: true, h: null, i: undefined,
j: "", k: document.styleSheets, l: document.body.childNodes,
o: new Array(125), m: document.head};
var testobj4 = {a: "b", c: "d"};
Object.defineProperty(testobj4, "nonEnumerable", { value: "hello world" });
var map1 = new Map([["a", "b"], [document.body.children, testobj2]]);
map1.set(map1, set1);
</script>
</body>
</html>

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

@ -0,0 +1,30 @@
<!DOCTYPE HTML>
<html dir="ltr" lang="en-US">
<head>
<meta charset="utf-8">
<title>Test the web console output - 03</title>
<!--
- Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/
-->
</head>
<body>
<p>hello world!</p>
<script type="text/javascript">
function testBodyClassName() {
document.body.className = "test1 tezt2";
return document.body;
}
function testBodyID() {
document.body.id = 'foobarid';
return document.body;
}
function testBodyDataset() {
document.body.dataset.preview = 'zuzu"<a>foo';
return document.body;
}
</script>
</body>
</html>

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

@ -0,0 +1,77 @@
<!DOCTYPE HTML>
<html dir="ltr" lang="en-US">
<head>
<meta charset="utf-8">
<title>Test the web console output - 04</title>
<!--
- Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/
-->
</head>
<body>
<p>hello world!</p>
<script type="text/javascript">
function testTextNode() {
return document.querySelector("p").childNodes[0];
}
function testCommentNode() {
return document.head.childNodes[5];
}
function testDocumentFragment() {
var frag = document.createDocumentFragment();
var div = document.createElement("div");
div.id = "foo1";
div.className = "bar";
frag.appendChild(div);
var span = document.createElement("span");
span.id = "foo2";
span.textContent = "hello world";
div.appendChild(span);
var div2 = document.createElement("div");
div2.id = "foo3";
frag.appendChild(div2);
return frag;
}
function testError() {
try {
window.foobar("a");
} catch (ex) {
return ex;
}
return null;
}
function testDOMException() {
try {
var foo = document.querySelector("foo;()bar!");
} catch (ex) {
return ex;
}
return null;
}
function testCSSStyleDeclaration() {
document.body.style = 'color: green; font-size: 2em';
return document.body.style;
}
function testStyleSheetList() {
var style = document.querySelector("style");
if (!style) {
style = document.createElement("style");
style.textContent = "p, div { color: blue; font-weight: bold }\n" +
"@media print { p { background-color: yellow } }";
document.head.appendChild(style);
}
return document.styleSheets;
}
</script>
</body>
</html>

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

@ -0,0 +1,23 @@
<!DOCTYPE HTML>
<html dir="ltr" lang="en-US">
<head>
<meta charset="utf-8">
<title>Test the web console output for DOM events</title>
<!--
- Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/
-->
</head>
<body>
<p>hello world!</p>
<script type="text/javascript">
function testDOMEvents() {
function eventLogger(ev) {
console.log("eventLogger", ev);
}
document.addEventListener("mousemove", eventLogger);
document.addEventListener("keypress", eventLogger);
}
</script>
</body>
</html>

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

@ -1183,10 +1183,6 @@ WebConsoleFrame.prototype = {
let clipboardArray = [];
args.forEach((aValue) => {
clipboardArray.push(VariablesView.getString(aValue));
if (aValue && typeof aValue == "object" &&
aValue.type == "longString") {
clipboardArray.push(l10n.getStr("longStringEllipsis"));
}
});
clipboardText = clipboardArray.join(" ");
break;
@ -3103,7 +3099,7 @@ JSTerm.prototype = {
aAfterMessage._objectActors.add(helperResult.object.actor);
}
this.openVariablesView({
label: VariablesView.getString(helperResult.object),
label: VariablesView.getString(helperResult.object, { concise: true }),
objectActor: helperResult.object,
});
break;
@ -4265,6 +4261,7 @@ JSTerm.prototype = {
popup.selectNextItem();
}
this.emit("autocomplete-updated");
aCallback && aCallback(this);
},

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

@ -202,8 +202,8 @@ loadingText=Loading\u2026
# viewer when there is an error loading a file
errorLoadingText=Error loading source:\n
# LOCALIZATION NOTE (emptyStackText): The text that is displayed in the watch
# expressions list to add a new item.
# LOCALIZATION NOTE (addWatchExpressionText): The text that is displayed in the
# watch expressions list to add a new item.
addWatchExpressionText=Add watch expression
# LOCALIZATION NOTE (emptyVariablesText): The text that is displayed in the
@ -225,6 +225,19 @@ watchExpressionsScopeLabel=Watch expressions
# the global scope.
globalScopeLabel=Global
# LOCALIZATION NOTE (variablesViewErrorStacktrace): This is the text that is
# shown before the stack trace in an error.
variablesViewErrorStacktrace=Stack trace:
# LOCALIZATION NOTE (variablesViewMoreObjects): the text that is displayed
# when you have an object preview that does not show all of the elements. At the end of the list
# you see "N more..." in the web console output.
# This is a semi-colon list of plural forms.
# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
# #1 number of remaining items in the object
# example: 3 more…
variablesViewMoreObjects=#1 more…;#1 more…
# LOCALIZATION NOTE (variablesEditableNameTooltip): The text that is displayed
# in the variables list on an item with an editable name.
variablesEditableNameTooltip=Double click to edit

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

@ -100,11 +100,26 @@ XPCOMUtils.defineLazyGetter(this, "DEFAULT_ITEMS", function() {
return result;
});
const ALL_BUILTIN_ITEMS = [
"fullscreen-button",
"switch-to-metro-button",
"bookmarks-menu-button",
];
XPCOMUtils.defineLazyGetter(this, "ALL_BUILTIN_ITEMS", function() {
// These special cases are for click events on built-in items that are
// contained within customizable items (like the navigation widget).
const SPECIAL_CASES = [
"back-button",
"forward-button",
"urlbar-stop-button",
"urlbar-go-button",
"urlbar-reload-button",
"searchbar",
"cut-button",
"copy-button",
"paste-button",
"zoom-out-button",
"zoom-reset-button",
"zoom-in-button",
]
return DEFAULT_ITEMS.concat(PALETTE_ITEMS)
.concat(SPECIAL_CASES);
});
const OTHER_MOUSEUP_MONITORED_ITEMS = [
"PlacesChevron",
@ -318,6 +333,14 @@ this.BrowserUITelemetry = {
// Base case - we clicked directly on one of our built-in items,
// and we can go ahead and register that click.
this._countMouseUpEvent("click-builtin-item", item.id, aEvent.button);
return;
}
// If not, we need to check if one of the ancestors of the clicked
// item is in our list of built-in items to check.
let candidate = getIDBasedOnFirstIDedAncestor(item);
if (ALL_BUILTIN_ITEMS.indexOf(candidate) != -1) {
this._countMouseUpEvent("click-builtin-item", candidate, aEvent.button);
}
},

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

@ -51,7 +51,7 @@
display: none;
}
.devtools-toolbarbutton:not([checked=true]):hover:active {
.devtools-toolbarbutton:not([checked]):hover:active {
border-color: hsla(210,8%,5%,.6);
background: linear-gradient(hsla(220,6%,10%,.3), hsla(212,7%,57%,.15) 65%, hsla(212,7%,57%,.3));
box-shadow: 0 0 3px hsla(210,8%,5%,.25) inset, 0 1px 3px hsla(210,8%,5%,.25) inset, 0 1px 0 hsla(210,16%,76%,.15);
@ -282,28 +282,28 @@
background-image: linear-gradient(hsla(206,37%,4%,.4), hsla(206,37%,4%,.4)), @smallSeparator@;
}
.devtools-sidebar-tabs > tabs > tab[selected=true] + tab {
.devtools-sidebar-tabs > tabs > tab[selected] + tab {
background-image: linear-gradient(transparent, transparent), @solidSeparator@;
}
.devtools-sidebar-tabs > tabs > tab[selected=true] + tab:hover {
.devtools-sidebar-tabs > tabs > tab[selected] + tab:hover {
background-image: linear-gradient(hsla(206,37%,4%,.2), hsla(206,37%,4%,.2)), @solidSeparator@;
}
.devtools-sidebar-tabs > tabs > tab[selected=true] + tab:hover:active {
.devtools-sidebar-tabs > tabs > tab[selected] + tab:hover:active {
background-image: linear-gradient(hsla(206,37%,4%,.4), hsla(206,37%,4%,.4)), @solidSeparator@;
}
.devtools-sidebar-tabs > tabs > tab[selected=true] {
.devtools-sidebar-tabs > tabs > tab[selected] {
color: #f5f7fa;
background-image: linear-gradient(#1d4f73, #1d4f73), @solidSeparator@;
}
.devtools-sidebar-tabs > tabs > tab[selected=true]:hover {
.devtools-sidebar-tabs > tabs > tab[selected]:hover {
background-image: linear-gradient(#274f64, #274f64), @solidSeparator@;
}
.devtools-sidebar-tabs > tabs > tab[selected=true]:hover:active {
.devtools-sidebar-tabs > tabs > tab[selected]:hover:active {
background-image: linear-gradient(#1f3e4f, #1f3e4f), @solidSeparator@;
}
@ -520,7 +520,7 @@
}
.devtools-tab:active > image,
.devtools-tab[selected=true] > image {
.devtools-tab[selected] > image {
opacity: 1;
}
@ -534,7 +534,7 @@
color: #f5f7fa;
}
#toolbox-tabs .devtools-tab[selected=true] {
#toolbox-tabs .devtools-tab[selected] {
color: #f5f7fa;
background-color: #1a4666;
box-shadow: 0 2px 0 #d7f1ff inset,
@ -542,32 +542,32 @@
0 -2px 0 rgba(0,0,0,.2) inset;
}
.devtools-tab[selected=true]:not(:first-child),
.devtools-tab.highlighted:not(:first-child) {
.devtools-tab[selected]:not(:first-child),
.devtools-tab[highlighted]:not(:first-child) {
border-width: 0;
-moz-padding-start: 1px;
}
.devtools-tab[selected=true]:last-child,
.devtools-tab.highlighted:last-child {
.devtools-tab[selected]:last-child,
.devtools-tab[highlighted]:last-child {
-moz-padding-end: 1px;
}
.devtools-tab[selected=true] + .devtools-tab,
.devtools-tab.highlighted + .devtools-tab {
.devtools-tab[selected] + .devtools-tab,
.devtools-tab[highlighted] + .devtools-tab {
-moz-border-start-width: 0;
-moz-padding-start: 1px;
}
.devtools-tab:not([selected=true]).highlighted {
.devtools-tab:not([selected])[highlighted] {
color: #f5f7fa;
background-color: hsla(99,100%,14%,.2);
box-shadow: 0 2px 0 #7bc107 inset;
}
.devtools-tab:not(.highlighted) > .highlighted-icon,
.devtools-tab[selected=true] > .highlighted-icon,
.devtools-tab:not([selected=true]).highlighted > .default-icon {
.devtools-tab:not([highlighted]) > .highlighted-icon,
.devtools-tab[selected] > .highlighted-icon,
.devtools-tab:not([selected])[highlighted] > .default-icon {
visibility: collapse;
}

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

@ -23,7 +23,7 @@ interface nsIMarkupDocumentViewer;
[ref] native nsIMarkupDocumentViewerTArray(nsTArray<nsCOMPtr<nsIMarkupDocumentViewer> >);
[scriptable, uuid(3528324f-f5d3-4724-bd8d-9233a7114112)]
[scriptable, uuid(7aea9561-5346-401c-b40e-418688da2d0d)]
interface nsIMarkupDocumentViewer : nsISupports
{
@ -82,6 +82,18 @@ interface nsIMarkupDocumentViewer : nsISupports
*/
void changeMaxLineBoxWidth(in int32_t maxLineBoxWidth);
/**
* Instruct the refresh driver to discontinue painting until further
* notice.
*/
void pausePainting();
/**
* Instruct the refresh driver to resume painting after a previous call to
* pausePainting().
*/
void resumePainting();
/*
* Render the document as if being viewed on a device with the specified
* media type. This will cause a reflow.

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

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

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

@ -129,10 +129,10 @@ typedef struct CapturingContentInfo {
} CapturingContentInfo;
// f5b542a9-eaf0-4560-a656-37a9d379864c
// 0e4f2b36-7ab8-43c5-b912-5c311566297c
#define NS_IPRESSHELL_IID \
{ 0xf5b542a9, 0xeaf0, 0x4560, \
{ 0x37, 0xa9, 0xd3, 0x79, 0x86, 0x4c } }
{ 0xde498c49, 0xf83f, 0x47bf, \
{0x8c, 0xc6, 0x8f, 0xf8, 0x74, 0x62, 0x22, 0x23 } }
// debug VerifyReflow flags
#define VERIFY_REFLOW_ON 0x01
@ -836,6 +836,20 @@ public:
*/
bool IsPaintingSuppressed() const { return mPaintingSuppressed; }
/**
* Pause painting by freezing the refresh driver of this and all parent
* presentations. This may not have the desired effect if this pres shell
* has its own refresh driver.
*/
virtual void PausePainting() = 0;
/**
* Resume painting by thawing the refresh driver of this and all parent
* presentations. This may not have the desired effect if this pres shell
* has its own refresh driver.
*/
virtual void ResumePainting() = 0;
/**
* Unsuppress painting.
*/
@ -1601,6 +1615,7 @@ protected:
bool mFontSizeInflationForceEnabled;
bool mFontSizeInflationDisabledInMasterProcess;
bool mFontSizeInflationEnabled;
bool mPaintingIsFrozen;
// Dirty bit indicating that mFontSizeInflationEnabled needs to be recomputed.
bool mFontSizeInflationEnabledIsDirty;

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

@ -724,6 +724,8 @@ PresShell::PresShell()
"layout.reflow.synthMouseMove", true);
addedSynthMouseMove = true;
}
mPaintingIsFrozen = false;
}
NS_IMPL_ISUPPORTS7(PresShell, nsIPresShell, nsIDocumentObserver,
@ -744,6 +746,13 @@ PresShell::~PresShell()
mLastCallbackEventRequest == nullptr,
"post-reflow queues not empty. This means we're leaking");
// Verify that if painting was frozen, but we're being removed from the tree,
// that we now re-enable painting on our refresh driver, since it may need to
// be re-used by another presentation.
if (mPaintingIsFrozen) {
mPresContext->RefreshDriver()->Thaw();
}
#ifdef DEBUG
MOZ_ASSERT(mPresArenaAllocCount == 0,
"Some pres arena objects were not freed");
@ -9931,3 +9940,23 @@ nsIPresShell::SetMaxLineBoxWidth(nscoord aMaxLineBoxWidth)
FrameNeedsReflow(GetRootFrame(), eResize, NS_FRAME_HAS_DIRTY_CHILDREN);
}
}
void
PresShell::PausePainting()
{
if (GetPresContext()->RefreshDriver()->PresContext() != GetPresContext())
return;
mPaintingIsFrozen = true;
GetPresContext()->RefreshDriver()->Freeze();
}
void
PresShell::ResumePainting()
{
if (GetPresContext()->RefreshDriver()->PresContext() != GetPresContext())
return;
mPaintingIsFrozen = false;
GetPresContext()->RefreshDriver()->Thaw();
}

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

@ -689,6 +689,9 @@ protected:
virtual void ThemeChanged() MOZ_OVERRIDE { mPresContext->ThemeChanged(); }
virtual void BackingScaleFactorChanged() MOZ_OVERRIDE { mPresContext->UIResolutionChanged(); }
virtual void PausePainting() MOZ_OVERRIDE;
virtual void ResumePainting() MOZ_OVERRIDE;
void UpdateImageVisibility();
nsRevocableEventPtr<nsRunnableMethod<PresShell> > mUpdateImageVisibilityEvent;

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

@ -66,6 +66,7 @@ final public class InputMethods {
@RobocopTarget
public static boolean shouldDisableUrlBarUpdate(Context context) {
String inputMethod = getCurrentInputMethod(context);
// HTC Touch Input does not react well to restarting during input (bug 909940)
return METHOD_HTC_TOUCH_INPUT.equals(inputMethod);
}

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

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

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

@ -73,6 +73,9 @@
<!ENTITY pref_category_datareporting "Data choices">
<!ENTITY pref_category_installed_search_engines "Installed search engines">
<!ENTITY pref_category_add_search_providers "Add more search providers">
<!ENTITY pref_category_search_restore_defaults "Restore search engines">
<!ENTITY pref_search_restore_defaults "Restore defaults">
<!ENTITY pref_search_restore_defaults_summary "Restore defaults">
<!-- Localization note (pref_search_tip) : "TIP" as in "hint", "clue" etc. Displayed as an
advisory message on the customise search providers settings page explaining how to add new
search providers.-->

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

@ -7,6 +7,13 @@ package org.mozilla.gecko.menu;
import org.mozilla.gecko.R;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.ViewGroup;
@ -16,6 +23,15 @@ public class MenuItemActionBar extends ImageButton
implements GeckoMenuItem.Layout {
private static final String LOGTAG = "GeckoMenuItemActionBar";
private static Bitmap sMoreIcon;
private static float sHalfIconWidth;
private static float sMoreWidth;
private static int sMoreOffset;
private static Paint sDisabledPaint;
private Drawable mIcon;
private boolean mHasSubMenu = false;
public MenuItemActionBar(Context context) {
this(context, null);
}
@ -26,6 +42,55 @@ public class MenuItemActionBar extends ImageButton
public MenuItemActionBar(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
if (sMoreIcon == null) {
final Resources res = getResources();
BitmapDrawable drawable = (BitmapDrawable) res.getDrawable(R.drawable.menu_item_more);
sMoreIcon = drawable.getBitmap();
// The icon has some space on the right. Taking half the size feels better.
sMoreWidth = getResources().getDimensionPixelSize(R.dimen.menu_item_state_icon) / 2.0f;
sMoreOffset = res.getDimensionPixelSize(R.dimen.menu_item_more_offset);
final int rowHeight = res.getDimensionPixelSize(R.dimen.menu_item_row_height);
final int padding = getPaddingTop() + getPaddingBottom();
sHalfIconWidth = (rowHeight - padding) / 2.0f;
sDisabledPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
sDisabledPaint.setColorFilter(new PorterDuffColorFilter(0xFF999999, PorterDuff.Mode.SRC_ATOP));
}
}
@Override
protected void onDraw(Canvas canvas) {
if (!mHasSubMenu) {
super.onDraw(canvas);
return;
}
final int count = canvas.save();
final float halfWidth = getMeasuredWidth() / 2.0f;
final float halfHeight = getMeasuredHeight() / 2.0f;
// If the width is small, the more icon might be pushed to the edges.
// Instead translate the canvas, so that both the icon + more is centered as a whole.
final boolean needsTranslation = (halfWidth < 1.5 * halfHeight);
final float translateX = needsTranslation ? (sMoreOffset + sMoreWidth) / 2.0f : 0.0f;
canvas.translate(-translateX, 0);
super.onDraw(canvas);
final float left = halfWidth + sHalfIconWidth + sMoreOffset - translateX;
final float top = halfHeight - sMoreWidth;
canvas.drawBitmap(sMoreIcon, left, top, isEnabled() ? null : sDisabledPaint);
canvas.translate(translateX, 0);
canvas.restoreToCount(count);
}
@Override
@ -37,24 +102,22 @@ public class MenuItemActionBar extends ImageButton
setTitle(item.getTitle());
setEnabled(item.isEnabled());
setId(item.getItemId());
setSubMenuIndicator(item.hasSubMenu());
}
void setIcon(Drawable icon) {
if (icon != null) {
setImageDrawable(icon);
setVisibility(VISIBLE);
} else {
mIcon = icon;
if (icon == null) {
setVisibility(GONE);
} else {
setVisibility(VISIBLE);
setImageDrawable(icon);
}
}
void setIcon(int icon) {
if (icon != 0) {
setImageResource(icon);
setVisibility(VISIBLE);
} else {
setVisibility(GONE);
}
setIcon((icon == 0) ? null : getResources().getDrawable(icon));
}
void setTitle(CharSequence title) {
@ -72,4 +135,11 @@ public class MenuItemActionBar extends ImageButton
public void setShowIcon(boolean show) {
// Do nothing.
}
private void setSubMenuIndicator(boolean hasSubMenu) {
if (mHasSubMenu != hasSubMenu) {
mHasSubMenu = hasSubMenu;
invalidate();
}
}
}

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

@ -68,26 +68,11 @@ public class MenuItemActionView extends LinearLayout
if (item == null)
return;
setTitle(item.getTitle());
setIcon(item.getIcon());
mMenuItem.initialize(item);
mMenuButton.initialize(item);
setEnabled(item.isEnabled());
}
private void setIcon(Drawable icon) {
mMenuItem.setIcon(icon);
mMenuButton.setIcon(icon);
}
private void setIcon(int icon) {
mMenuItem.setIcon(icon);
mMenuButton.setIcon(icon);
}
private void setTitle(CharSequence title) {
mMenuItem.setTitle(title);
mMenuButton.setTitle(title);
}
@Override
public void setEnabled(boolean enabled) {
super.setEnabled(enabled);

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

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

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

@ -5,9 +5,12 @@
package org.mozilla.gecko.preferences;
import java.lang.reflect.Field;
import org.mozilla.gecko.R;
import org.mozilla.gecko.PrefsHelper;
import android.app.Activity;
import android.preference.Preference;
import android.preference.PreferenceActivity;
import android.preference.PreferenceCategory;
@ -15,6 +18,10 @@ import android.preference.PreferenceFragment;
import android.preference.PreferenceScreen;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.ViewConfiguration;
/* A simple implementation of PreferenceFragment for large screen devices
* This will strip category headers (so that they aren't shown to the user twice)
@ -28,23 +35,14 @@ public class GeckoPreferenceFragment extends PreferenceFragment {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String resourceName = getArguments().getString("resource");
int res = 0;
if (resourceName != null) {
// Fetch resource id by resource name.
res = getActivity().getResources().getIdentifier(resourceName,
"xml",
getActivity().getPackageName());
int res = getResource();
// Display a menu for Search preferences.
if (res == R.xml.preferences_search) {
setHasOptionsMenu(true);
}
if (res == 0) {
// The resource was invalid. Use the default resource.
Log.e(LOGTAG, "Failed to find resource: " + resourceName + ". Displaying default settings.");
boolean isMultiPane = ((PreferenceActivity) getActivity()).onIsMultiPane();
res = isMultiPane ? R.xml.preferences_customize_tablet : R.xml.preferences;
}
addPreferencesFromResource(res);
PreferenceScreen screen = getPreferenceScreen();
@ -52,6 +50,39 @@ public class GeckoPreferenceFragment extends PreferenceFragment {
mPrefsRequestId = ((GeckoPreferences)getActivity()).setupPreferences(screen);
}
/*
* Get the resource from Fragment arguments and return it.
*
* If no resource can be found, return the resource id of the default preference screen.
*/
private int getResource() {
int resid = 0;
String resourceName = getArguments().getString("resource");
if (resourceName != null) {
// Fetch resource id by resource name.
resid = getActivity().getResources().getIdentifier(resourceName,
"xml",
getActivity().getPackageName());
}
if (resid == 0) {
// The resource was invalid. Use the default resource.
Log.e(LOGTAG, "Failed to find resource: " + resourceName + ". Displaying default settings.");
boolean isMultiPane = ((PreferenceActivity) getActivity()).onIsMultiPane();
resid = isMultiPane ? R.xml.preferences_customize_tablet : R.xml.preferences;
}
return resid;
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.preferences_search_menu, menu);
}
@Override
public void onDestroy() {
super.onDestroy();
@ -59,4 +90,29 @@ public class GeckoPreferenceFragment extends PreferenceFragment {
PrefsHelper.removeObserver(mPrefsRequestId);
}
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
showOverflowMenu(activity);
}
/*
* Force the overflow 3-dot menu to be displayed if it isn't already displayed.
*
* This is an ugly hack for 4.0+ Android devices that don't have a dedicated menu button
* because Android does not provide a public API to display the ActionBar overflow menu.
*/
private void showOverflowMenu(Activity activity) {
try {
ViewConfiguration config = ViewConfiguration.get(activity);
Field menuOverflow = ViewConfiguration.class.getDeclaredField("sHasPermanentMenuKey");
if (menuOverflow != null) {
menuOverflow.setAccessible(true);
menuOverflow.setBoolean(config, false);
}
} catch (Exception e) {
Log.d(LOGTAG, "Failed to force overflow menu, ignoring.");
}
}
}

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

@ -77,7 +77,9 @@ public class GeckoPreferences
private boolean mInitialized = false;
private int mPrefsRequestId = 0;
// These match keys in resources/xml/preferences.xml.in.
// These match keys in resources/xml*/preferences*.xml
private static String PREFS_SEARCH_RESTORE_DEFAULTS = NON_PREF_PREFIX + "search.restore_defaults";
private static String PREFS_ANNOUNCEMENTS_ENABLED = NON_PREF_PREFIX + "privacy.announcements.enabled";
private static String PREFS_DATA_REPORTING_PREFERENCES = NON_PREF_PREFIX + "datareporting.preferences";
private static String PREFS_TELEMETRY_ENABLED = "datareporting.telemetry.enabled";
@ -389,6 +391,14 @@ public class GeckoPreferences
preferences.removePreference(pref);
i--;
continue;
} else if (PREFS_SEARCH_RESTORE_DEFAULTS.equals(key)) {
pref.setOnPreferenceClickListener(new OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
GeckoPreferences.this.restoreDefaultSearchEngines();
return true;
}
});
}
// Some Preference UI elements are not actually preferences,
@ -404,14 +414,31 @@ public class GeckoPreferences
}
}
/**
* Restore default search engines in Gecko and retrigger a search engine refresh.
*/
protected void restoreDefaultSearchEngines() {
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("SearchEngines:RestoreDefaults", null));
// Send message to Gecko to get engines. SearchPreferenceCategory listens for the response.
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("SearchEngines:GetVisible", null));
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
int itemId = item.getItemId();
switch (itemId) {
case android.R.id.home:
finish();
return true;
}
// Generated R.id.* apparently aren't constant expressions, so they can't be switched.
if (itemId == R.id.restore_defaults) {
restoreDefaultSearchEngines();
return true;
}
return super.onOptionsItemSelected(item);
}

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

@ -40,8 +40,6 @@ public class SearchEnginePreference extends Preference implements View.OnLongCli
// Specifies if this engine is configured as the default search engine.
private boolean mIsDefaultEngine;
// Specifies if this engine is one of the ones bundled with the app, which cannot be deleted.
private boolean mIsImmutableEngine;
// Dialog element labels.
private String[] mDialogItems;
@ -121,12 +119,7 @@ public class SearchEnginePreference extends Preference implements View.OnLongCli
public void setSearchEngineFromJSON(JSONObject geckoEngineJSON) throws JSONException {
final String engineName = geckoEngineJSON.getString("name");
final SpannableString titleSpannable = new SpannableString(engineName);
mIsImmutableEngine = geckoEngineJSON.getBoolean("immutable");
if (mIsImmutableEngine) {
// Delete the "Remove" option from the menu.
mDialogItems = new String[] { getContext().getResources().getString(R.string.pref_search_set_default) };
}
setTitle(titleSpannable);
final String iconURI = geckoEngineJSON.getString("iconURI");
@ -176,11 +169,6 @@ public class SearchEnginePreference extends Preference implements View.OnLongCli
return;
}
// If we are both default and immutable, we have no enabled items to show on the menu - abort.
if (mIsDefaultEngine && mIsImmutableEngine) {
return;
}
final AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
builder.setTitle(getTitle().toString());
builder.setItems(mDialogItems, new DialogInterface.OnClickListener() {

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

@ -43,18 +43,19 @@ public class SearchPreferenceCategory extends PreferenceCategory implements Geck
// Ensures default engine remains at top of list.
setOrderingAsAdded(true);
// Request list of search engines from Gecko.
// Register for SearchEngines messages and request list of search engines from Gecko.
GeckoAppShell.registerEventListener("SearchEngines:Data", this);
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("SearchEngines:Get", null));
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("SearchEngines:GetVisible", null));
}
@Override
protected void onPrepareForRemoval() {
GeckoAppShell.unregisterEventListener("SearchEngines:Data", this);
}
@Override
public void handleMessage(String event, final JSONObject data) {
if (event.equals("SearchEngines:Data")) {
// We are no longer interested in this event from Gecko, as we do not request it again with
// this instance.
GeckoAppShell.unregisterEventListener("SearchEngines:Data", this);
// Parse engines array from JSON.
JSONArray engines;
try {
@ -64,6 +65,9 @@ public class SearchPreferenceCategory extends PreferenceCategory implements Geck
return;
}
// Clear the preferences category from this thread.
this.removeAll();
// Create an element in this PreferenceCategory for each engine.
for (int i = 0; i < engines.length(); i++) {
try {

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

До

Ширина:  |  Высота:  |  Размер: 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_row_height">44dp</dimen>
<dimen name="menu_item_row_width">240dp</dimen>
<dimen name="menu_item_more_offset">5dp</dimen>
<dimen name="menu_popup_arrow_margin">5dip</dimen>
<dimen name="menu_popup_arrow_width">40dip</dimen>
<dimen name="menu_popup_offset">8dp</dimen>

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

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:gecko="http://schemas.android.com/apk/res-auto"
android:title="@string/pref_category_search"
android:enabled="false">
<CheckBoxPreference android:key="browser.search.suggest.enabled"
android:title="@string/pref_search_suggestions"
android:defaultValue="false"
android:persistent="false" />
<org.mozilla.gecko.preferences.SearchPreferenceCategory
android:title="@string/pref_category_installed_search_engines"/>
<PreferenceCategory android:title="@string/pref_category_add_search_providers">
<Preference android:layout="@layout/preference_search_tip"
android:enabled="false"
android:selectable="false"/>
</PreferenceCategory>
</PreferenceScreen>

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

@ -16,6 +16,13 @@
<org.mozilla.gecko.preferences.SearchPreferenceCategory
android:title="@string/pref_category_installed_search_engines"/>
<PreferenceCategory android:title="@string/pref_category_search_restore_defaults">
<Preference android:key="android.not_a_preference.search.restore_defaults"
android:title="@string/pref_search_restore_defaults_summary" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/pref_category_add_search_providers">
<Preference android:layout="@layout/preference_search_tip"

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

@ -93,7 +93,11 @@
<string name="pref_category_datareporting">&pref_category_datareporting;</string>
<string name="pref_category_installed_search_engines">&pref_category_installed_search_engines;</string>
<string name="pref_category_add_search_providers">&pref_category_add_search_providers;</string>
<string name="pref_category_search_restore_defaults">&pref_category_search_restore_defaults;</string>
<string name="pref_search_restore_defaults">&pref_search_restore_defaults;</string>
<string name="pref_search_restore_defaults_summary">&pref_search_restore_defaults_summary;</string>
<string name="pref_search_tip">&pref_search_tip;</string>
<string name="pref_category_devtools">&pref_category_devtools;</string>
<string name="pref_developer_remotedebugging">&pref_developer_remotedebugging;</string>
<string name="pref_developer_remotedebugging_docs">&pref_developer_remotedebugging_docs;</string>

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

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

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

@ -47,7 +47,10 @@ public class ToolbarEditText extends CustomEditText
private final Context mContext;
private TextType mTextType;
// Type of the URL bar go/search button
private TextType mToolbarTextType;
// Type of the keyboard go/search button (cannot be EMPTY)
private TextType mKeyboardTextType;
private OnCommitListener mCommitListener;
private OnDismissListener mDismissListener;
@ -66,7 +69,8 @@ public class ToolbarEditText extends CustomEditText
super(context, attrs);
mContext = context;
mTextType = TextType.EMPTY;
mToolbarTextType = TextType.EMPTY;
mKeyboardTextType = TextType.URL;
}
void setOnCommitListener(OnCommitListener listener) {
@ -172,10 +176,13 @@ public class ToolbarEditText extends CustomEditText
}
private void setTextType(TextType textType) {
mTextType = textType;
mToolbarTextType = textType;
if (textType != TextType.EMPTY) {
mKeyboardTextType = textType;
}
if (mTextTypeListener != null) {
mTextTypeListener.onTextTypeChange(this, mTextType);
mTextTypeListener.onTextTypeChange(this, textType);
}
}
@ -186,6 +193,8 @@ public class ToolbarEditText extends CustomEditText
}
if (InputMethods.shouldDisableUrlBarUpdate(mContext)) {
// Set button type to match the previous keyboard type
setTextType(mKeyboardTextType);
return;
}
@ -222,10 +231,16 @@ public class ToolbarEditText extends CustomEditText
restartInput = true;
}
if (restartInput) {
updateKeyboardInputType();
imm.restartInput(ToolbarEditText.this);
if (!restartInput) {
// If the text content was previously empty, the toolbar text type
// is empty as well. Since the keyboard text type cannot be empty,
// the two text types are now inconsistent. Reset the toolbar text
// type here to the keyboard text type to ensure consistency.
setTextType(mKeyboardTextType);
return;
}
updateKeyboardInputType();
imm.restartInput(ToolbarEditText.this);
setTextType(imeAction == EditorInfo.IME_ACTION_GO ?
TextType.URL : TextType.SEARCH_QUERY);

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

@ -178,10 +178,20 @@ function doChangeMaxLineBoxWidth(aWidth) {
range = BrowserApp.selectedTab._mReflozPoint.range;
}
docViewer.changeMaxLineBoxWidth(aWidth);
try {
docViewer.pausePainting();
docViewer.changeMaxLineBoxWidth(aWidth);
if (range) {
BrowserEventHandler._zoomInAndSnapToRange(range);
if (range) {
BrowserEventHandler._zoomInAndSnapToRange(range);
} else {
// In this case, we actually didn't zoom into a specific range. It
// probably happened from a page load reflow-on-zoom event, so we
// need to make sure painting is re-enabled.
BrowserApp.selectedTab.clearReflowOnZoomPendingActions();
}
} finally {
docViewer.resumePainting();
}
}
@ -2744,6 +2754,7 @@ Tab.prototype = {
this.browser.addEventListener("MozApplicationManifest", this, true);
Services.obs.addObserver(this, "before-first-paint", false);
Services.obs.addObserver(this, "after-viewport-change", false);
Services.prefs.addObserver("browser.ui.zoom.force-user-scalable", this, false);
if (aParams.delayLoad) {
@ -2803,21 +2814,58 @@ Tab.prototype = {
return minFontSize / this.getInflatedFontSizeFor(aElement);
},
clearReflowOnZoomPendingActions: function() {
// Reflow was completed, so now re-enable painting.
let webNav = BrowserApp.selectedTab.window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebNavigation);
let docShell = webNav.QueryInterface(Ci.nsIDocShell);
let docViewer = docShell.contentViewer.QueryInterface(Ci.nsIMarkupDocumentViewer);
docViewer.resumePainting();
BrowserApp.selectedTab._mReflozPositioned = false;
},
/**
* Reflow on zoom consists of a few different sub-operations:
*
* 1. When a double-tap event is seen, we verify that the correct preferences
* are enabled and perform the pre-position handling calculation. We also
* signal that reflow-on-zoom should be performed at this time, and pause
* painting.
* 2. During the next call to setViewport(), which is in the Tab prototype,
* we detect that a call to changeMaxLineBoxWidth should be performed. If
* we're zooming out, then the max line box width should be reset at this
* time. Otherwise, we call performReflowOnZoom.
* 2a. PerformReflowOnZoom() and resetMaxLineBoxWidth() schedule a call to
* doChangeMaxLineBoxWidth, based on a timeout specified in preferences.
* 3. doChangeMaxLineBoxWidth changes the line box width (which also
* schedules a reflow event), and then calls _zoomInAndSnapToRange.
* 4. _zoomInAndSnapToRange performs the positioning of reflow-on-zoom and
* then re-enables painting.
*
* Some of the events happen synchronously, while others happen asynchronously.
* The following is a rough sketch of the progression of events:
*
* double tap event seen -> onDoubleTap() -> ... asynchronous ...
* -> setViewport() -> performReflowOnZoom() -> ... asynchronous ...
* -> doChangeMaxLineBoxWidth() -> _zoomInAndSnapToRange()
* -> ... asynchronous ... -> setViewport() -> Observe('after-viewport-change')
* -> resumePainting()
*/
performReflowOnZoom: function(aViewport) {
let zoom = this._drawZoom ? this._drawZoom : aViewport.zoom;
let zoom = this._drawZoom ? this._drawZoom : aViewport.zoom;
let viewportWidth = gScreenWidth / zoom;
let reflozTimeout = Services.prefs.getIntPref("browser.zoom.reflowZoom.reflowTimeout");
let viewportWidth = gScreenWidth / zoom;
let reflozTimeout = Services.prefs.getIntPref("browser.zoom.reflowZoom.reflowTimeout");
if (gReflowPending) {
clearTimeout(gReflowPending);
}
if (gReflowPending) {
clearTimeout(gReflowPending);
}
// We add in a bit of fudge just so that the end characters
// don't accidentally get clipped. 15px is an arbitrary choice.
gReflowPending = setTimeout(doChangeMaxLineBoxWidth,
reflozTimeout,
viewportWidth - 15);
// We add in a bit of fudge just so that the end characters
// don't accidentally get clipped. 15px is an arbitrary choice.
gReflowPending = setTimeout(doChangeMaxLineBoxWidth,
reflozTimeout,
viewportWidth - 15);
},
/**
@ -2889,6 +2937,7 @@ Tab.prototype = {
this.browser.removeEventListener("MozApplicationManifest", this, true);
Services.obs.removeObserver(this, "before-first-paint");
Services.obs.removeObserver(this, "after-viewport-change");
Services.prefs.removeObserver("browser.ui.zoom.force-user-scalable", this);
// Make sure the previously selected panel remains selected. The selected panel of a deck is
@ -3175,13 +3224,21 @@ Tab.prototype = {
// In this case, the user pinch-zoomed in, so we don't want to
// preserve position as we would with reflow-on-zoom.
BrowserApp.selectedTab.probablyNeedRefloz = false;
BrowserApp.selectedTab.clearReflowOnZoomPendingActions();
BrowserApp.selectedTab._mReflozPoint = null;
}
let docViewer = null;
if (isZooming &&
BrowserEventHandler.mReflozPref &&
BrowserApp.selectedTab._mReflozPoint &&
BrowserApp.selectedTab.probablyNeedRefloz) {
let webNav = BrowserApp.selectedTab.window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebNavigation);
let docShell = webNav.QueryInterface(Ci.nsIDocShell);
docViewer = docShell.contentViewer.QueryInterface(Ci.nsIMarkupDocumentViewer);
docViewer.pausePainting();
BrowserApp.selectedTab.performReflowOnZoom(aViewport);
BrowserApp.selectedTab.probablyNeedRefloz = false;
}
@ -3210,6 +3267,9 @@ Tab.prototype = {
aViewport.fixedMarginLeft / aViewport.zoom);
Services.obs.notifyObservers(null, "after-viewport-change", "");
if (docViewer) {
docViewer.resumePainting();
}
},
setResolution: function(aZoom, aForce) {
@ -4166,6 +4226,11 @@ Tab.prototype = {
BrowserApp.selectedTab.performReflowOnZoom(vp);
}
break;
case "after-viewport-change":
if (BrowserApp.selectedTab._mReflozPositioned) {
BrowserApp.selectedTab.clearReflowOnZoomPendingActions();
}
break;
case "nsPref:changed":
if (aData == "browser.ui.zoom.force-user-scalable")
ViewportHandler.updateMetadata(this, false);
@ -4484,13 +4549,23 @@ var BrowserEventHandler = {
if (BrowserEventHandler.mReflozPref &&
!BrowserApp.selectedTab._mReflozPoint &&
!this._shouldSuppressReflowOnZoom(element)) {
let data = JSON.parse(aData);
let zoomPointX = data.x;
let zoomPointY = data.y;
BrowserApp.selectedTab._mReflozPoint = { x: zoomPointX, y: zoomPointY,
range: BrowserApp.selectedBrowser.contentDocument.caretPositionFromPoint(zoomPointX, zoomPointY) };
BrowserApp.selectedTab.probablyNeedRefloz = true;
// See comment above performReflowOnZoom() for a detailed description of
// the events happening in the reflow-on-zoom operation.
let data = JSON.parse(aData);
let zoomPointX = data.x;
let zoomPointY = data.y;
BrowserApp.selectedTab._mReflozPoint = { x: zoomPointX, y: zoomPointY,
range: BrowserApp.selectedBrowser.contentDocument.caretPositionFromPoint(zoomPointX, zoomPointY) };
// Before we perform a reflow on zoom, let's disable painting.
let webNav = BrowserApp.selectedTab.window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebNavigation);
let docShell = webNav.QueryInterface(Ci.nsIDocShell);
let docViewer = docShell.contentViewer.QueryInterface(Ci.nsIMarkupDocumentViewer);
docViewer.pausePainting();
BrowserApp.selectedTab.probablyNeedRefloz = true;
}
if (!element) {
@ -4590,11 +4665,7 @@ var BrowserEventHandler = {
},
_zoomInAndSnapToRange: function(aRange) {
if (!aRange) {
Cu.reportError("aRange is null in zoomInAndSnapToRange. Unable to maintain position.");
return;
}
// aRange is always non-null here, since a check happened previously.
let viewport = BrowserApp.selectedTab.getViewport();
let fudge = 15; // Add a bit of fudge.
let boundingElement = aRange.offsetNode;
@ -4617,30 +4688,33 @@ var BrowserEventHandler = {
let leftAdjustment = parseInt(boundingStyle.paddingLeft) +
parseInt(boundingStyle.borderLeftWidth);
BrowserApp.selectedTab._mReflozPositioned = true;
rect.type = "Browser:ZoomToRect";
rect.x = Math.max(viewport.cssPageLeft, rect.x - fudge + leftAdjustment);
rect.y = Math.max(topPos, viewport.cssPageTop);
rect.w = viewport.cssWidth;
rect.h = viewport.cssHeight;
rect.animate = false;
sendMessageToJava(rect);
BrowserApp.selectedTab._mReflozPoint = null;
},
},
onPinchFinish: function(aData) {
let data = {};
try {
data = JSON.parse(aData);
} catch(ex) {
console.log(ex);
return;
}
onPinchFinish: function(aData) {
let data = {};
try {
data = JSON.parse(aData);
} catch(ex) {
console.log(ex);
return;
}
if (BrowserEventHandler.mReflozPref &&
data.zoomDelta < 0.0) {
BrowserEventHandler.resetMaxLineBoxWidth();
}
},
if (BrowserEventHandler.mReflozPref &&
data.zoomDelta < 0.0) {
BrowserEventHandler.resetMaxLineBoxWidth();
}
},
_shouldZoomToElement: function(aElement) {
let win = aElement.ownerDocument.defaultView;
@ -6608,10 +6682,10 @@ var SearchEngines = {
init: function init() {
Services.obs.addObserver(this, "SearchEngines:Add", false);
Services.obs.addObserver(this, "SearchEngines:Get", false);
Services.obs.addObserver(this, "SearchEngines:GetVisible", false);
Services.obs.addObserver(this, "SearchEngines:SetDefault", false);
Services.obs.addObserver(this, "SearchEngines:Remove", false);
Services.obs.addObserver(this, "SearchEngines:RestoreDefaults", false);
Services.obs.addObserver(this, "SearchEngines:SetDefault", false);
let filter = {
matches: function (aElement) {
@ -6651,37 +6725,28 @@ var SearchEngines = {
uninit: function uninit() {
Services.obs.removeObserver(this, "SearchEngines:Add");
Services.obs.removeObserver(this, "SearchEngines:Get");
Services.obs.removeObserver(this, "SearchEngines:GetVisible");
Services.obs.removeObserver(this, "SearchEngines:SetDefault");
Services.obs.removeObserver(this, "SearchEngines:Remove");
Services.obs.removeObserver(this, "SearchEngines:RestoreDefaults");
Services.obs.removeObserver(this, "SearchEngines:SetDefault");
if (this._contextMenuId != null)
NativeWindow.contextmenus.remove(this._contextMenuId);
},
// Fetch list of search engines. all ? All engines : Visible engines only.
_handleSearchEnginesGet: function _handleSearchEnginesGet(rv, all) {
_handleSearchEnginesGetVisible: function _handleSearchEnginesGetVisible(rv, all) {
if (!Components.isSuccessCode(rv)) {
Cu.reportError("Could not initialize search service, bailing out.");
return;
}
let engineData;
if (all) {
engineData = Services.search.getEngines({});
} else {
engineData = Services.search.getVisibleEngines({});
}
// These engines are the bundled ones - they may not be uninstalled.
let immutableEngines = Services.search.getDefaultEngines();
let engineData = Services.search.getVisibleEngines({});
let searchEngines = engineData.map(function (engine) {
return {
name: engine.name,
identifier: engine.identifier,
iconURI: (engine.iconURI ? engine.iconURI.spec : null),
hidden: engine.hidden,
immutable: immutableEngines.indexOf(engine) != -1
hidden: engine.hidden
};
});
@ -6718,13 +6783,6 @@ var SearchEngines = {
} catch (e) {}
},
_handleSearchEnginesGetAll: function _handleSearchEnginesGetAll(rv) {
this._handleSearchEnginesGet(rv, true);
},
_handleSearchEnginesGetVisible: function _handleSearchEnginesGetVisible(rv) {
this._handleSearchEnginesGet(rv, false)
},
// Helper method to extract the engine name from a JSON. Simplifies the observe function.
_extractEngineFromJSON: function _extractEngineFromJSON(aData) {
let data = JSON.parse(aData);
@ -6740,16 +6798,6 @@ var SearchEngines = {
case "SearchEngines:GetVisible":
Services.search.init(this._handleSearchEnginesGetVisible.bind(this));
break;
case "SearchEngines:Get":
// Return a list of all engines, including "Hidden" ones.
Services.search.init(this._handleSearchEnginesGetAll.bind(this));
break;
case "SearchEngines:SetDefault":
engine = this._extractEngineFromJSON(aData);
// Move the new default search engine to the top of the search engine list.
Services.search.moveEngine(engine, 0);
Services.search.defaultEngine = engine;
break;
case "SearchEngines:Remove":
// Make sure the engine isn't hidden before removing it, to make sure it's
// visible if the user later re-adds it (works around bug 341833)
@ -6757,6 +6805,17 @@ var SearchEngines = {
engine.hidden = false;
Services.search.removeEngine(engine);
break;
case "SearchEngines:RestoreDefaults":
// Un-hides all default engines.
Services.search.restoreDefaultEngines();
break;
case "SearchEngines:SetDefault":
engine = this._extractEngineFromJSON(aData);
// Move the new default search engine to the top of the search engine list.
Services.search.moveEngine(engine, 0);
Services.search.defaultEngine = engine;
break;
default:
dump("Unexpected message type observed: " + aTopic);
break;

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

@ -223,7 +223,9 @@ SessionStore.prototype = {
}
case "pageshow": {
let browser = aEvent.currentTarget;
this.onTabLoad(window, browser, aEvent.persisted);
// Top-level changes only
if (aEvent.originalTarget == browser.contentDocument)
this.onTabLoad(window, browser, aEvent.persisted);
break;
}
}

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

@ -6,6 +6,7 @@
/* General utilities used throughout devtools. */
let Cu = Components.utils;
let { Promise: promise } = Components.utils.import("resource://gre/modules/commonjs/sdk/core/promise.js", {});
let { Services } = Components.utils.import("resource://gre/modules/Services.jsm", {});
@ -237,3 +238,24 @@ this.hasSafeGetter = function hasSafeGetter(aDesc) {
return fn && fn.callable && fn.class == "Function" && fn.script === undefined;
};
/**
* Check if it is safe to read properties and execute methods from the given JS
* object. Safety is defined as being protected from unintended code execution
* from content scripts (or cross-compartment code).
*
* See bugs 945920 and 946752 for discussion.
*
* @type Object aObj
* The object to check.
* @return Boolean
* True if it is safe to read properties from aObj, or false otherwise.
*/
this.isSafeJSObject = function isSafeJSObject(aObj) {
if (Cu.getGlobalForObject(aObj) ==
Cu.getGlobalForObject(isSafeJSObject)) {
return true; // aObj is not a cross-compartment wrapper.
}
return Cu.isXrayWrapper(aObj);
};

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

@ -28,4 +28,5 @@ this.DevToolsUtils = {
defineLazyPrototypeGetter: defineLazyPrototypeGetter,
getProperty: getProperty,
hasSafeGetter: hasSafeGetter,
isSafeJSObject: isSafeJSObject,
};

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

@ -6,6 +6,14 @@
"use strict";
let TYPED_ARRAY_CLASSES = ["Uint8Array", "Uint8ClampedArray", "Uint16Array",
"Uint32Array", "Int8Array", "Int16Array", "Int32Array", "Float32Array",
"Float64Array"];
// Number of items to preview in objects, arrays, maps, sets, lists,
// collections, etc.
let OBJECT_PREVIEW_MAX_ITEMS = 10;
/**
* BreakpointStore objects keep track of all breakpoints that get set so that we
* can reset them when the same script is introduced to the thread again (such
@ -440,6 +448,8 @@ function ThreadActor(aHooks, aGlobal)
this._options = {
useSourceMaps: false
};
this._gripDepth = 0;
}
/**
@ -449,6 +459,9 @@ function ThreadActor(aHooks, aGlobal)
ThreadActor.breakpointStore = new BreakpointStore();
ThreadActor.prototype = {
// Used by the ObjectActor to keep track of the depth of grip() calls.
_gripDepth: null,
actorPrefix: "context",
get state() { return this._state; },
@ -2730,7 +2743,7 @@ function errorStringify(aObj) {
* The stringification for the object.
*/
function stringify(aObj) {
if (Cu.isDeadWrapper(aObj)) {
if (aObj.class == "DeadObject") {
const error = new Error("Dead object encountered.");
DevToolsUtils.reportException("stringify", error);
return "<dead object>";
@ -2829,6 +2842,8 @@ ObjectActor.prototype = {
* Returns a grip for this actor for returning in a protocol message.
*/
grip: function () {
this.threadActor._gripDepth++;
let g = {
"type": "object",
"class": this.obj.class,
@ -2838,29 +2853,22 @@ ObjectActor.prototype = {
"sealed": this.obj.isSealed()
};
// Add additional properties for functions.
if (this.obj.class === "Function") {
if (this.obj.name) {
g.name = this.obj.name;
}
if (this.obj.displayName) {
g.displayName = this.obj.displayName;
if (this.obj.class != "DeadObject") {
let raw = Cu.unwaiveXrays(this.obj.unsafeDereference());
if (!DevToolsUtils.isSafeJSObject(raw)) {
raw = null;
}
// Check if the developer has added a de-facto standard displayName
// property for us to use.
try {
let desc = this.obj.getOwnPropertyDescriptor("displayName");
if (desc && desc.value && typeof desc.value == "string") {
g.userDisplayName = this.threadActor.createValueGrip(desc.value);
let previewers = DebuggerServer.ObjectActorPreviewers[this.obj.class] ||
DebuggerServer.ObjectActorPreviewers.Object;
for (let fn of previewers) {
if (fn(this, g, raw)) {
break;
}
} catch (e) {
// Calling getOwnPropertyDescriptor with displayName might throw
// with "permission denied" errors for some functions.
dumpn(e);
}
}
this.threadActor._gripDepth--;
return g;
},
@ -2964,15 +2972,17 @@ ObjectActor.prototype = {
* @param object aOwnProperties
* The object that holds the list of known ownProperties for
* |this.obj|.
* @param number [aLimit=0]
* Optional limit of getter values to find.
* @return object
* An object that maps property names to safe getter descriptors as
* defined by the remote debugging protocol.
*/
_findSafeGetterValues: function (aOwnProperties)
_findSafeGetterValues: function (aOwnProperties, aLimit = 0)
{
let safeGetterValues = Object.create(null);
let obj = this.obj;
let level = 0;
let level = 0, i = 0;
while (obj) {
let getters = this._findSafeGetters(obj);
@ -3014,9 +3024,15 @@ ObjectActor.prototype = {
enumerable: desc.enumerable,
writable: level == 0 ? desc.writable : true,
};
if (aLimit && ++i == aLimit) {
break;
}
}
}
}
if (aLimit && i == aLimit) {
break;
}
obj = obj.proto;
level++;
@ -3043,7 +3059,15 @@ ObjectActor.prototype = {
}
let getters = new Set();
for (let name of aObject.getOwnPropertyNames()) {
let names = [];
try {
names = aObject.getOwnPropertyNames()
} catch (ex) {
// Calling getOwnPropertyNames() on some wrapped native prototypes is not
// allowed: "cannot modify properties of a WrappedNative". See bug 952093.
}
for (let name of names) {
let desc = null;
try {
desc = aObject.getOwnPropertyDescriptor(name);
@ -3108,10 +3132,17 @@ ObjectActor.prototype = {
* A helper method that creates a property descriptor for the provided object,
* properly formatted for sending in a protocol response.
*
* @private
* @param string aName
* The property that the descriptor is generated for.
* @param boolean [aOnlyEnumerable]
* Optional: true if you want a descriptor only for an enumerable
* property, false otherwise.
* @return object|undefined
* The property descriptor, or undefined if this is not an enumerable
* property and aOnlyEnumerable=true.
*/
_propertyDescriptor: function (aName) {
_propertyDescriptor: function (aName, aOnlyEnumerable) {
let desc;
try {
desc = this.obj.getOwnPropertyDescriptor(aName);
@ -3127,7 +3158,7 @@ ObjectActor.prototype = {
};
}
if (!desc) {
if (!desc || aOnlyEnumerable && !desc.enumerable) {
return undefined;
}
@ -3232,6 +3263,565 @@ ObjectActor.prototype.requestTypes = {
};
/**
* Functions for adding information to ObjectActor grips for the purpose of
* having customized output. This object holds arrays mapped by
* Debugger.Object.prototype.class.
*
* In each array you can add functions that take two
* arguments:
* - the ObjectActor instance to make a preview for,
* - the grip object being prepared for the client,
* - the raw JS object after calling Debugger.Object.unsafeDereference(). This
* argument is only provided if the object is safe for reading properties and
* executing methods. See DevToolsUtils.isSafeJSObject().
*
* Functions must return false if they cannot provide preview
* information for the debugger object, or true otherwise.
*/
DebuggerServer.ObjectActorPreviewers = {
Function: [function({obj, threadActor}, aGrip) {
if (obj.name) {
aGrip.name = obj.name;
}
if (obj.displayName) {
aGrip.displayName = obj.displayName.substr(0, 500);
}
if (obj.parameterNames) {
aGrip.parameterNames = obj.parameterNames;
}
// Check if the developer has added a de-facto standard displayName
// property for us to use.
let userDisplayName;
try {
userDisplayName = obj.getOwnPropertyDescriptor("displayName");
} catch (e) {
// Calling getOwnPropertyDescriptor with displayName might throw
// with "permission denied" errors for some functions.
dumpn(e);
}
if (userDisplayName && typeof userDisplayName.value == "string" &&
userDisplayName.value) {
aGrip.userDisplayName = threadActor.createValueGrip(userDisplayName.value);
}
return true;
}],
RegExp: [function({obj, threadActor}, aGrip) {
// Avoid having any special preview for the RegExp.prototype itself.
if (!obj.proto || obj.proto.class != "RegExp") {
return false;
}
let str = RegExp.prototype.toString.call(obj.unsafeDereference());
aGrip.displayString = threadActor.createValueGrip(str);
return true;
}],
Date: [function({obj, threadActor}, aGrip) {
if (!obj.proto || obj.proto.class != "Date") {
return false;
}
let time = Date.prototype.getTime.call(obj.unsafeDereference());
aGrip.preview = {
timestamp: threadActor.createValueGrip(time),
};
return true;
}],
Array: [function({obj, threadActor}, aGrip) {
let length = DevToolsUtils.getProperty(obj, "length");
if (typeof length != "number") {
return false;
}
aGrip.preview = {
kind: "ArrayLike",
length: length,
};
if (threadActor._gripDepth > 1) {
return true;
}
let raw = obj.unsafeDereference();
let items = aGrip.preview.items = [];
for (let [i, value] of Array.prototype.entries.call(raw)) {
if (Object.hasOwnProperty.call(raw, i)) {
value = makeDebuggeeValueIfNeeded(obj, value);
items.push(threadActor.createValueGrip(value));
} else {
items.push(null);
}
if (items.length == OBJECT_PREVIEW_MAX_ITEMS) {
break;
}
}
return true;
}], // Array
Set: [function({obj, threadActor}, aGrip) {
let size = DevToolsUtils.getProperty(obj, "size");
if (typeof size != "number") {
return false;
}
aGrip.preview = {
kind: "ArrayLike",
length: size,
};
// Avoid recursive object grips.
if (threadActor._gripDepth > 1) {
return true;
}
let raw = obj.unsafeDereference();
let items = aGrip.preview.items = [];
for (let item of Set.prototype.values.call(raw)) {
item = makeDebuggeeValueIfNeeded(obj, item);
items.push(threadActor.createValueGrip(item));
if (items.length == OBJECT_PREVIEW_MAX_ITEMS) {
break;
}
}
return true;
}], // Set
Map: [function({obj, threadActor}, aGrip) {
let size = DevToolsUtils.getProperty(obj, "size");
if (typeof size != "number") {
return false;
}
aGrip.preview = {
kind: "MapLike",
size: size,
};
if (threadActor._gripDepth > 1) {
return true;
}
let raw = obj.unsafeDereference();
let entries = aGrip.preview.entries = [];
for (let [key, value] of Map.prototype.entries.call(raw)) {
key = makeDebuggeeValueIfNeeded(obj, key);
value = makeDebuggeeValueIfNeeded(obj, value);
entries.push([threadActor.createValueGrip(key),
threadActor.createValueGrip(value)]);
if (entries.length == OBJECT_PREVIEW_MAX_ITEMS) {
break;
}
}
return true;
}], // Map
DOMStringMap: [function({obj, threadActor}, aGrip, aRawObj) {
if (!aRawObj) {
return false;
}
let keys = obj.getOwnPropertyNames();
aGrip.preview = {
kind: "MapLike",
size: keys.length,
};
if (threadActor._gripDepth > 1) {
return true;
}
let entries = aGrip.preview.entries = [];
for (let key of keys) {
let value = makeDebuggeeValueIfNeeded(obj, aRawObj[key]);
entries.push([key, threadActor.createValueGrip(value)]);
if (entries.length == OBJECT_PREVIEW_MAX_ITEMS) {
break;
}
}
return true;
}], // DOMStringMap
}; // DebuggerServer.ObjectActorPreviewers
// Preview functions that do not rely on the object class.
DebuggerServer.ObjectActorPreviewers.Object = [
function TypedArray({obj, threadActor}, aGrip) {
if (TYPED_ARRAY_CLASSES.indexOf(obj.class) == -1) {
return false;
}
let length = DevToolsUtils.getProperty(obj, "length");
if (typeof length != "number") {
return false;
}
aGrip.preview = {
kind: "ArrayLike",
length: length,
};
if (threadActor._gripDepth > 1) {
return true;
}
let raw = obj.unsafeDereference();
let global = Cu.getGlobalForObject(DebuggerServer);
let classProto = global[obj.class].prototype;
let safeView = classProto.subarray.call(raw, 0, OBJECT_PREVIEW_MAX_ITEMS);
let items = aGrip.preview.items = [];
for (let i = 0; i < safeView.length; i++) {
items.push(safeView[i]);
}
return true;
},
function Error({obj, threadActor}, aGrip) {
switch (obj.class) {
case "Error":
case "EvalError":
case "RangeError":
case "ReferenceError":
case "SyntaxError":
case "TypeError":
case "URIError":
let name = DevToolsUtils.getProperty(obj, "name");
let msg = DevToolsUtils.getProperty(obj, "message");
let stack = DevToolsUtils.getProperty(obj, "stack");
let fileName = DevToolsUtils.getProperty(obj, "fileName");
let lineNumber = DevToolsUtils.getProperty(obj, "lineNumber");
let columnNumber = DevToolsUtils.getProperty(obj, "columnNumber");
aGrip.preview = {
kind: "Error",
name: threadActor.createValueGrip(name),
message: threadActor.createValueGrip(msg),
stack: threadActor.createValueGrip(stack),
fileName: threadActor.createValueGrip(fileName),
lineNumber: threadActor.createValueGrip(lineNumber),
columnNumber: threadActor.createValueGrip(columnNumber),
};
return true;
default:
return false;
}
},
function CSSMediaRule({obj, threadActor}, aGrip, aRawObj) {
if (!aRawObj || !(aRawObj instanceof Ci.nsIDOMCSSMediaRule)) {
return false;
}
aGrip.preview = {
kind: "ObjectWithText",
text: threadActor.createValueGrip(aRawObj.conditionText),
};
return true;
},
function CSSStyleRule({obj, threadActor}, aGrip, aRawObj) {
if (!aRawObj || !(aRawObj instanceof Ci.nsIDOMCSSStyleRule)) {
return false;
}
aGrip.preview = {
kind: "ObjectWithText",
text: threadActor.createValueGrip(aRawObj.selectorText),
};
return true;
},
function ObjectWithURL({obj, threadActor}, aGrip, aRawObj) {
if (!aRawObj ||
!(aRawObj instanceof Ci.nsIDOMCSSImportRule ||
aRawObj instanceof Ci.nsIDOMCSSStyleSheet ||
aRawObj instanceof Ci.nsIDOMLocation ||
aRawObj instanceof Ci.nsIDOMWindow)) {
return false;
}
let url;
if (aRawObj instanceof Ci.nsIDOMWindow) {
url = aRawObj.location.href;
} else {
url = aRawObj.href;
}
aGrip.preview = {
kind: "ObjectWithURL",
url: threadActor.createValueGrip(url),
};
return true;
},
function ArrayLike({obj, threadActor}, aGrip, aRawObj) {
if (!aRawObj ||
obj.class != "DOMTokenList" &&
!(aRawObj instanceof Ci.nsIDOMMozNamedAttrMap ||
aRawObj instanceof Ci.nsIDOMCSSRuleList ||
aRawObj instanceof Ci.nsIDOMCSSValueList ||
aRawObj instanceof Ci.nsIDOMDOMStringList ||
aRawObj instanceof Ci.nsIDOMFileList ||
aRawObj instanceof Ci.nsIDOMFontFaceList ||
aRawObj instanceof Ci.nsIDOMMediaList ||
aRawObj instanceof Ci.nsIDOMNodeList ||
aRawObj instanceof Ci.nsIDOMStyleSheetList)) {
return false;
}
if (typeof aRawObj.length != "number") {
return false;
}
aGrip.preview = {
kind: "ArrayLike",
length: aRawObj.length,
};
if (threadActor._gripDepth > 1) {
return true;
}
let items = aGrip.preview.items = [];
for (let i = 0; i < aRawObj.length &&
items.length < OBJECT_PREVIEW_MAX_ITEMS; i++) {
let value = makeDebuggeeValueIfNeeded(obj, aRawObj[i]);
items.push(threadActor.createValueGrip(value));
}
return true;
}, // ArrayLike
function CSSStyleDeclaration({obj, threadActor}, aGrip, aRawObj) {
if (!aRawObj || !(aRawObj instanceof Ci.nsIDOMCSSStyleDeclaration)) {
return false;
}
aGrip.preview = {
kind: "MapLike",
size: aRawObj.length,
};
let entries = aGrip.preview.entries = [];
for (let i = 0; i < OBJECT_PREVIEW_MAX_ITEMS &&
i < aRawObj.length; i++) {
let prop = aRawObj[i];
let value = aRawObj.getPropertyValue(prop);
entries.push([prop, threadActor.createValueGrip(value)]);
}
return true;
},
function DOMNode({obj, threadActor}, aGrip, aRawObj) {
if (!aRawObj || !(aRawObj instanceof Ci.nsIDOMNode)) {
return false;
}
let preview = aGrip.preview = {
kind: "DOMNode",
nodeType: aRawObj.nodeType,
nodeName: aRawObj.nodeName,
};
if (aRawObj instanceof Ci.nsIDOMDocument) {
preview.location = threadActor.createValueGrip(aRawObj.location.href);
} else if (aRawObj instanceof Ci.nsIDOMDocumentFragment) {
preview.childNodesLength = aRawObj.childNodes.length;
if (threadActor._gripDepth < 2) {
preview.childNodes = [];
for (let node of aRawObj.childNodes) {
let actor = threadActor.createValueGrip(obj.makeDebuggeeValue(node));
preview.childNodes.push(actor);
if (preview.childNodes.length == OBJECT_PREVIEW_MAX_ITEMS) {
break;
}
}
}
} else if (aRawObj instanceof Ci.nsIDOMElement) {
// Add preview for DOM element attributes.
if (aRawObj instanceof Ci.nsIDOMHTMLElement) {
preview.nodeName = preview.nodeName.toLowerCase();
}
let i = 0;
preview.attributes = {};
preview.attributesLength = aRawObj.attributes.length;
for (let attr of aRawObj.attributes) {
preview.attributes[attr.nodeName] = threadActor.createValueGrip(attr.value);
if (++i == OBJECT_PREVIEW_MAX_ITEMS) {
break;
}
}
} else if (aRawObj instanceof Ci.nsIDOMAttr) {
preview.value = threadActor.createValueGrip(aRawObj.value);
} else if (aRawObj instanceof Ci.nsIDOMText ||
aRawObj instanceof Ci.nsIDOMComment) {
preview.textContent = threadActor.createValueGrip(aRawObj.textContent);
}
return true;
}, // DOMNode
function DOMEvent({obj, threadActor}, aGrip, aRawObj) {
if (!aRawObj || !(aRawObj instanceof Ci.nsIDOMEvent)) {
return false;
}
let preview = aGrip.preview = {
kind: "DOMEvent",
type: aRawObj.type,
properties: Object.create(null),
};
if (threadActor._gripDepth < 2) {
let target = obj.makeDebuggeeValue(aRawObj.target);
preview.target = threadActor.createValueGrip(target);
}
let props = [];
if (aRawObj instanceof Ci.nsIDOMMouseEvent) {
props.push("buttons", "clientX", "clientY", "layerX", "layerY");
} else if (aRawObj instanceof Ci.nsIDOMKeyEvent) {
let modifiers = [];
if (aRawObj.altKey) {
modifiers.push("Alt");
}
if (aRawObj.ctrlKey) {
modifiers.push("Control");
}
if (aRawObj.metaKey) {
modifiers.push("Meta");
}
if (aRawObj.shiftKey) {
modifiers.push("Shift");
}
preview.eventKind = "key";
preview.modifiers = modifiers;
props.push("key", "charCode", "keyCode");
} else if (aRawObj instanceof Ci.nsIDOMTransitionEvent ||
aRawObj instanceof Ci.nsIDOMAnimationEvent) {
props.push("animationName", "pseudoElement");
} else if (aRawObj instanceof Ci.nsIDOMClipboardEvent) {
props.push("clipboardData");
}
// Add event-specific properties.
for (let prop of props) {
let value = aRawObj[prop];
if (value && (typeof value == "object" || typeof value == "function")) {
// Skip properties pointing to objects.
if (threadActor._gripDepth > 1) {
continue;
}
value = obj.makeDebuggeeValue(value);
}
preview.properties[prop] = threadActor.createValueGrip(value);
}
// Add any properties we find on the event object.
if (!props.length) {
let i = 0;
for (let prop in aRawObj) {
let value = aRawObj[prop];
if (prop == "target" || prop == "type" || value === null ||
typeof value == "function") {
continue;
}
if (value && typeof value == "object") {
if (threadActor._gripDepth > 1) {
continue;
}
value = obj.makeDebuggeeValue(value);
}
preview.properties[prop] = threadActor.createValueGrip(value);
if (++i == OBJECT_PREVIEW_MAX_ITEMS) {
break;
}
}
}
return true;
}, // DOMEvent
function DOMException({obj, threadActor}, aGrip, aRawObj) {
if (!aRawObj || !(aRawObj instanceof Ci.nsIDOMDOMException)) {
return false;
}
aGrip.preview = {
kind: "DOMException",
name: threadActor.createValueGrip(aRawObj.name),
message: threadActor.createValueGrip(aRawObj.message),
code: threadActor.createValueGrip(aRawObj.code),
result: threadActor.createValueGrip(aRawObj.result),
filename: threadActor.createValueGrip(aRawObj.filename),
lineNumber: threadActor.createValueGrip(aRawObj.lineNumber),
columnNumber: threadActor.createValueGrip(aRawObj.columnNumber),
};
return true;
},
function GenericObject(aObjectActor, aGrip) {
let {obj, threadActor} = aObjectActor;
if (aGrip.preview || aGrip.displayString || threadActor._gripDepth > 1) {
return false;
}
let i = 0, names = [];
let preview = aGrip.preview = {
kind: "Object",
ownProperties: Object.create(null),
};
try {
names = obj.getOwnPropertyNames();
} catch (ex) {
// Calling getOwnPropertyNames() on some wrapped native prototypes is not
// allowed: "cannot modify properties of a WrappedNative". See bug 952093.
}
preview.ownPropertiesLength = names.length;
for (let name of names) {
let desc = aObjectActor._propertyDescriptor(name, true);
if (!desc) {
continue;
}
preview.ownProperties[name] = desc;
if (++i == OBJECT_PREVIEW_MAX_ITEMS) {
break;
}
}
if (i < OBJECT_PREVIEW_MAX_ITEMS) {
preview.safeGetterValues = aObjectActor.
_findSafeGetterValues(preview.ownProperties,
OBJECT_PREVIEW_MAX_ITEMS - i);
}
return true;
}, // GenericObject
]; // DebuggerServer.ObjectActorPreviewers.Object
/**
* Creates a pause-scoped actor for the specified object.
* @see ObjectActor
@ -4517,3 +5107,23 @@ function positionInNodeList(element, nodeList) {
}
return -1;
}
/**
* Make a debuggee value for the given object, if needed. Primitive values
* are left the same.
*
* Use case: you have a raw JS object (after unsafe dereference) and you want to
* send it to the client. In that case you need to use an ObjectActor which
* requires a debuggee value. The Debugger.Object.prototype.makeDebuggeeValue()
* method works only for JS objects and functions.
*
* @param Debugger.Object obj
* @param any value
* @return object
*/
function makeDebuggeeValueIfNeeded(obj, value) {
if (value && (typeof value == "object" || typeof value == "function")) {
return obj.makeDebuggeeValue(value);
}
return value;
}

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

@ -61,6 +61,7 @@ function WebConsoleActor(aConnection, aParentActor)
this._protoChains = new Map();
this._netEvents = new Map();
this._gripDepth = 0;
this._onObserverNotification = this._onObserverNotification.bind(this);
if (this.parentActor.isRootActor) {
@ -80,6 +81,13 @@ WebConsoleActor.prototype =
*/
dbg: null,
/**
* This is used by the ObjectActor to keep track of the depth of grip() calls.
* @private
* @type number
*/
_gripDepth: null,
/**
* Actor pool for all of the actors we send to the client.
* @private

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

@ -188,12 +188,18 @@ let WebConsoleUtils = {
*
* @param string aSourceURL
* The source URL to shorten.
* @param object [aOptions]
* Options:
* - onlyCropQuery: boolean that tells if the URL abbreviation function
* should only remove the query parameters and the hash fragment from
* the given URL.
* @return string
* The abbreviated form of the source URL.
*/
abbreviateSourceURL: function WCU_abbreviateSourceURL(aSourceURL)
abbreviateSourceURL:
function WCU_abbreviateSourceURL(aSourceURL, aOptions = {})
{
if (aSourceURL.substr(0, 5) == "data:") {
if (!aOptions.onlyCropQuery && aSourceURL.substr(0, 5) == "data:") {
let commaIndex = aSourceURL.indexOf(",");
if (commaIndex > -1) {
aSourceURL = "data:" + aSourceURL.substring(commaIndex + 1);
@ -214,13 +220,15 @@ let WebConsoleUtils = {
// Remove a trailing "/".
if (aSourceURL[aSourceURL.length - 1] == "/") {
aSourceURL = aSourceURL.substring(0, aSourceURL.length - 1);
aSourceURL = aSourceURL.replace(/\/+$/, "");
}
// Remove all but the last path component.
let slashIndex = aSourceURL.lastIndexOf("/");
if (slashIndex > -1) {
aSourceURL = aSourceURL.substring(slashIndex + 1);
if (!aOptions.onlyCropQuery) {
let slashIndex = aSourceURL.lastIndexOf("/");
if (slashIndex > -1) {
aSourceURL = aSourceURL.substring(slashIndex + 1);
}
}
return aSourceURL;

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

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

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

@ -12,11 +12,11 @@ testFile.append("DeferredSaveTest");
Components.utils.import("resource://gre/modules/Promise.jsm");
let context = Components.utils.import("resource://gre/modules/DeferredSave.jsm", {});
let DeferredSave = context.DeferredSave;
let DSContext = Components.utils.import("resource://gre/modules/DeferredSave.jsm", {});
let DeferredSave = DSContext.DeferredSave;
// Test wrapper to let us do promise/task based testing of DeferredSaveP
function DeferredSaveTester(aDelay, aDataProvider) {
// Test wrapper to let us do promise/task based testing of DeferredSave
function DeferredSaveTester(aDataProvider) {
let tester = {
// Deferred for the promise returned by the mock writeAtomic
waDeferred: null,
@ -54,11 +54,11 @@ function DeferredSaveTester(aDelay, aDataProvider) {
if (!aDataProvider)
aDataProvider = () => tester.dataToSave;
tester.saver = new DeferredSave(testFile.path, aDataProvider, aDelay);
tester.saver = new DeferredSave(testFile.path, aDataProvider);
// Install a mock for OS.File.writeAtomic to let us control the async
// behaviour of the promise
context.OS.File.writeAtomic = function mock_writeAtomic(aFile, aData, aOptions) {
DSContext.OS.File.writeAtomic = function mock_writeAtomic(aFile, aData, aOptions) {
do_print("writeAtomic: " + aFile + " data: '" + aData + "', " + aOptions.toSource());
tester.writtenData = aData;
tester.waDeferred = Promise.defer();
@ -69,6 +69,70 @@ function DeferredSaveTester(aDelay, aDataProvider) {
return tester;
};
/**
* Install a mock nsITimer factory that triggers on the next spin of
* the event loop after it is scheduled
*/
function setQuickMockTimer() {
let quickTimer = {
initWithCallback: function(aFunction, aDelay, aType) {
do_print("Starting quick timer, delay = " + aDelay);
do_execute_soon(aFunction);
},
cancel: function() {
do_throw("Attempted to cancel a quickMockTimer");
}
};
DSContext.MakeTimer = () => {
do_print("Creating quick timer");
return quickTimer;
};
}
/**
* Install a mock nsITimer factory in DeferredSave.jsm, returning a promise that resolves
* when the client code sets the timer. Test cases can use this to wait for client code to
* be ready for a timer event, and then signal the event by calling mockTimer.callback().
* This could use some enhancement; clients can re-use the returned timer,
* but with this implementation it's not possible for the test to wait for
* a second call to initWithCallback() on the re-used timer.
* @return Promise{mockTimer} that resolves when initWithCallback()
* is called
*/
function setPromiseMockTimer() {
let waiter = Promise.defer();
let mockTimer = {
callback: null,
delay: null,
type: null,
isCancelled: false,
initWithCallback: function(aFunction, aDelay, aType) {
do_print("Starting timer, delay = " + aDelay);
this.callback = aFunction;
this.delay = aDelay;
this.type = aType;
// cancelled timers can be re-used
this.isCancelled = false;
waiter.resolve(this);
},
cancel: function() {
do_print("Cancelled mock timer");
this.callback = null;
this.delay = null;
this.type = null;
this.isCancelled = true;
// If initWithCallback was never called, resolve to let tests check for cancel
waiter.resolve(this);
}
};
DSContext.MakeTimer = () => {
do_print("Creating mock timer");
return mockTimer;
};
return waiter.promise;
}
/**
* Return a Promise<null> that resolves after the specified number of milliseconds
*/
@ -84,7 +148,8 @@ function run_test() {
// Modify set data once, ask for save, make sure it saves cleanly
add_task(function test_basic_save_succeeds() {
let tester = DeferredSaveTester(1);
setQuickMockTimer();
let tester = DeferredSaveTester();
let data = "Test 1 Data";
yield tester.save(data);
@ -95,7 +160,8 @@ add_task(function test_basic_save_succeeds() {
// Two saves called during the same event loop, both with callbacks
// Make sure we save only the second version of the data
add_task(function test_two_saves() {
let tester = DeferredSaveTester(1);
setQuickMockTimer();
let tester = DeferredSaveTester();
let firstCallback_happened = false;
let firstData = "Test first save";
let secondData = "Test second save";
@ -117,7 +183,8 @@ add_task(function test_two_saves() {
// Two saves called with a delay in between, both with callbacks
// Make sure we save the second version of the data
add_task(function test_two_saves_delay() {
let tester = DeferredSaveTester(50);
let timerPromise = setPromiseMockTimer();
let tester = DeferredSaveTester();
let firstCallback_happened = false;
let delayDone = false;
@ -131,9 +198,18 @@ add_task(function test_two_saves_delay() {
firstCallback_happened = true;
}, do_report_unexpected_exception);
yield delay(5);
// Wait a short time to let async events possibly spawned by the
// first tester.save() to run
yield delay(2);
delayDone = true;
yield tester.save(secondData);
// request to save modified data
let saving = tester.save(secondData);
// Yield to wait for client code to set the timer
let activeTimer = yield timerPromise;
// and then trigger it
activeTimer.callback();
// now wait for the DeferredSave to finish saving
yield saving;
do_check_true(firstCallback_happened);
do_check_eq(secondData, tester.writtenData);
do_check_eq(1, tester.saver.totalSaves);
@ -144,12 +220,13 @@ add_task(function test_two_saves_delay() {
// Also check that the "error" getter correctly returns the error
// Then do a write that succeeds, and make sure the error is cleared
add_task(function test_error_immediate() {
let tester = DeferredSaveTester(1);
let tester = DeferredSaveTester();
let testError = new Error("Forced failure");
function writeFail(aTester) {
aTester.waDeferred.reject(testError);
}
setQuickMockTimer();
yield tester.save("test_error_immediate", writeFail).then(
count => do_throw("Did not get expected error"),
error => do_check_eq(testError.message, error.message)
@ -167,7 +244,7 @@ add_task(function test_error_immediate() {
// data two more times. Test that we re-write the dirty data exactly once
// after the first write succeeds
add_task(function dirty_while_writing() {
let tester = DeferredSaveTester(1);
let tester = DeferredSaveTester();
let firstData = "First data";
let secondData = "Second data";
let thirdData = "Third data";
@ -179,6 +256,7 @@ add_task(function dirty_while_writing() {
writeStarted.resolve(aTester.waDeferred);
}
setQuickMockTimer();
do_print("First save");
tester.save(firstData, writeCallback).then(
count => {
@ -240,7 +318,8 @@ function write_then_disable(aTester) {
// Flush tests. First, do an ordinary clean save and then call flush;
// there should not be another save
add_task(function flush_after_save() {
let tester = DeferredSaveTester(1);
setQuickMockTimer();
let tester = DeferredSaveTester();
let dataToSave = "Flush after save";
yield tester.save(dataToSave);
@ -250,7 +329,7 @@ add_task(function flush_after_save() {
// Flush while a write is in progress, but the in-memory data is clean
add_task(function flush_during_write() {
let tester = DeferredSaveTester(1);
let tester = DeferredSaveTester();
let dataToSave = "Flush during write";
let firstCallback_happened = false;
let writeStarted = Promise.defer();
@ -259,6 +338,7 @@ add_task(function flush_during_write() {
writeStarted.resolve(aTester.waDeferred);
}
setQuickMockTimer();
tester.save(dataToSave, writeCallback).then(
count => {
do_check_false(firstCallback_happened);
@ -281,13 +361,9 @@ add_task(function flush_during_write() {
// Flush while dirty but write not in progress
// The data written should be the value at the time
// flush() is called, even if it is changed later
//
// It would be nice to have a mock for Timer in here, to control
// when the steps happen, but for now we'll call the flush without
// going back around the event loop to make sure it happens before
// the DeferredSave timer goes off
add_task(function flush_while_dirty() {
let tester = DeferredSaveTester(20);
let timerPromise = setPromiseMockTimer();
let tester = DeferredSaveTester();
let firstData = "Flush while dirty, valid data";
let firstCallback_happened = false;
@ -298,10 +374,18 @@ add_task(function flush_while_dirty() {
do_check_eq(tester.writtenData, firstData);
}, do_report_unexpected_exception);
// Wait for the timer to be set, but don't trigger it so the write won't start
let activeTimer = yield timerPromise;
let flushing = tester.flush();
// Make sure the timer was cancelled
do_check_true(activeTimer.isCancelled);
// Also make sure that data changed after the flush call
// (even without a saveChanges() call) doesn't get written
let flushing = tester.flush();
tester.dataToSave = "Flush while dirty, invalid data";
yield flushing;
do_check_true(firstCallback_happened);
do_check_eq(tester.writtenData, firstData);
@ -314,7 +398,8 @@ add_task(function flush_while_dirty() {
// Data for the second write should be taken at the time
// flush() is called, even if it is modified later
add_task(function flush_writing_dirty() {
let tester = DeferredSaveTester(5);
let timerPromise = setPromiseMockTimer();
let tester = DeferredSaveTester();
let firstData = "Flush first pass data";
let secondData = "Flush second pass data";
let firstCallback_happened = false;
@ -332,6 +417,9 @@ add_task(function flush_writing_dirty() {
firstCallback_happened = true;
}, do_report_unexpected_exception);
// Trigger the timer callback as soon as the DeferredSave sets it
let activeTimer = yield timerPromise;
activeTimer.callback();
let writer = yield writeStarted.promise;
// the first write has started
@ -346,8 +434,9 @@ add_task(function flush_writing_dirty() {
}, do_report_unexpected_exception);
let flushing = tester.flush(write_then_disable);
// Flush should have cancelled our timer
do_check_true(activeTimer.isCancelled);
tester.dataToSave = "Flush, invalid data: changed late";
yield delay(1);
// complete the first write
writer.resolve(firstData.length);
// now wait for the second write / flush to complete
@ -375,8 +464,9 @@ function badDataProvider() {
// Handle cases where data provider throws
// First, throws during a normal save
add_task(function data_throw() {
setQuickMockTimer();
badDataError = expectedDataError;
let tester = DeferredSaveTester(1, badDataProvider);
let tester = DeferredSaveTester(badDataProvider);
yield tester.save("data_throw").then(
count => do_throw("Expected serialization failure"),
error => do_check_eq(error.message, expectedDataError));
@ -385,9 +475,10 @@ add_task(function data_throw() {
// Now, throws during flush
add_task(function data_throw_during_flush() {
badDataError = expectedDataError;
let tester = DeferredSaveTester(1, badDataProvider);
let tester = DeferredSaveTester(badDataProvider);
let firstCallback_happened = false;
setPromiseMockTimer();
// Write callback should never be called
tester.save("data_throw_during_flush", disabled_write_callback).then(
count => do_throw("Expected serialization failure"),
@ -397,6 +488,7 @@ add_task(function data_throw_during_flush() {
firstCallback_happened = true;
});
// flush() will cancel the timer
yield tester.flush(disabled_write_callback).then(
count => do_throw("Expected serialization failure"),
error => do_check_eq(error.message, expectedDataError)
@ -417,7 +509,8 @@ add_task(function data_throw_during_flush() {
// write completes
// delayed timer goes off, throws error because DeferredSave has been torn down
add_task(function delay_flush_race() {
let tester = DeferredSaveTester(5);
let timerPromise = setPromiseMockTimer();
let tester = DeferredSaveTester();
let firstData = "First save";
let secondData = "Second save";
let thirdData = "Third save";
@ -429,6 +522,7 @@ add_task(function delay_flush_race() {
// This promise won't resolve until after writeStarted
let firstSave = tester.save(firstData, writeCallback);
(yield timerPromise).callback();
let writer = yield writeStarted.promise;
// the first write has started