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