mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-24 21:31:04 +00:00
merge fx-team to mozilla-central a=merge
This commit is contained in:
commit
cce863628d
@ -82,6 +82,7 @@ devtools/client/debugger/**
|
||||
devtools/client/eyedropper/**
|
||||
devtools/client/framework/**
|
||||
!devtools/client/framework/selection.js
|
||||
!devtools/client/framework/toolbox.js
|
||||
devtools/client/jsonview/lib/**
|
||||
devtools/client/memory/**
|
||||
devtools/client/netmonitor/test/**
|
||||
|
@ -84,3 +84,9 @@ addMessageListener("Test:WaitForObserverCall", ({data}) => {
|
||||
}
|
||||
}, topic, false);
|
||||
});
|
||||
|
||||
addMessageListener("Test:WaitForMessage", () => {
|
||||
content.addEventListener("message", ({data}) => {
|
||||
sendAsyncMessage("Test:MessageReceived", data);
|
||||
}, {once: true});
|
||||
});
|
||||
|
@ -246,21 +246,23 @@ function promiseTodoObserverNotCalled(aTopic) {
|
||||
}
|
||||
|
||||
function promiseMessage(aMessage, aAction) {
|
||||
let deferred = Promise.defer();
|
||||
|
||||
content.addEventListener("message", function messageListener(event) {
|
||||
content.removeEventListener("message", messageListener);
|
||||
is(event.data, aMessage, "received " + aMessage);
|
||||
if (event.data == aMessage)
|
||||
deferred.resolve();
|
||||
else
|
||||
deferred.reject();
|
||||
let promise = new Promise((resolve, reject) => {
|
||||
let mm = _mm();
|
||||
mm.addMessageListener("Test:MessageReceived", function listener({data}) {
|
||||
is(data, aMessage, "received " + aMessage);
|
||||
if (data == aMessage)
|
||||
resolve();
|
||||
else
|
||||
reject();
|
||||
mm.removeMessageListener("Test:MessageReceived", listener);
|
||||
});
|
||||
mm.sendAsyncMessage("Test:WaitForMessage");
|
||||
});
|
||||
|
||||
if (aAction)
|
||||
aAction();
|
||||
|
||||
return deferred.promise;
|
||||
return promise;
|
||||
}
|
||||
|
||||
function promisePopupNotificationShown(aName, aAction) {
|
||||
|
@ -292,7 +292,6 @@ function prompt(aBrowser, aRequest) {
|
||||
requestTypes: requestTypes} = aRequest;
|
||||
let uri = Services.io.newURI(aRequest.documentURI, null, null);
|
||||
let host = getHost(uri);
|
||||
let principal = Services.scriptSecurityManager.createCodebasePrincipal(uri, {});
|
||||
let chromeDoc = aBrowser.ownerDocument;
|
||||
let chromeWin = chromeDoc.defaultView;
|
||||
let stringBundle = chromeWin.gNavigatorBundle;
|
||||
@ -388,14 +387,12 @@ function prompt(aBrowser, aRequest) {
|
||||
if (micPerm == perms.PROMPT_ACTION)
|
||||
micPerm = perms.UNKNOWN_ACTION;
|
||||
|
||||
let camPermanentPerm = perms.testExactPermanentPermission(principal, "camera");
|
||||
let camPerm = perms.testExactPermission(uri, "camera");
|
||||
|
||||
// Session approval given but never used to allocate a camera, remove
|
||||
// and ask again
|
||||
if (camPerm && !camPermanentPerm) {
|
||||
perms.remove(uri, "camera");
|
||||
camPerm = perms.UNKNOWN_ACTION;
|
||||
let mediaManagerPerm =
|
||||
perms.testExactPermission(uri, "MediaManagerVideo");
|
||||
if (mediaManagerPerm) {
|
||||
perms.remove(uri, "MediaManagerVideo");
|
||||
}
|
||||
|
||||
if (camPerm == perms.PROMPT_ACTION)
|
||||
@ -534,10 +531,12 @@ function prompt(aBrowser, aRequest) {
|
||||
allowedDevices.push(videoDeviceIndex);
|
||||
// Session permission will be removed after use
|
||||
// (it's really one-shot, not for the entire session)
|
||||
perms.add(uri, "camera", perms.ALLOW_ACTION,
|
||||
aRemember ? perms.EXPIRE_NEVER : perms.EXPIRE_SESSION);
|
||||
} else if (aRemember) {
|
||||
perms.add(uri, "camera", perms.DENY_ACTION);
|
||||
perms.add(uri, "MediaManagerVideo", perms.ALLOW_ACTION,
|
||||
perms.EXPIRE_SESSION);
|
||||
}
|
||||
if (aRemember) {
|
||||
perms.add(uri, "camera",
|
||||
allowCamera ? perms.ALLOW_ACTION : perms.DENY_ACTION);
|
||||
}
|
||||
}
|
||||
if (audioDevices.length) {
|
||||
|
@ -24,8 +24,7 @@ module.exports = createClass({
|
||||
targets = targets.sort(LocaleCompare);
|
||||
}
|
||||
targets = targets.map(target => {
|
||||
let key = target.name || target.url || target.title;
|
||||
return targetClass({ client, key, target, debugDisabled });
|
||||
return targetClass({ client, target, debugDisabled });
|
||||
});
|
||||
|
||||
let content = "";
|
||||
|
@ -11,6 +11,8 @@
|
||||
// is selected, animations will be displayed in the timeline, so the timeline
|
||||
// play/resume button will be displayed
|
||||
add_task(function* () {
|
||||
requestLongerTimeout(2);
|
||||
|
||||
yield addTab(URL_ROOT + "doc_simple_animation.html");
|
||||
let {panel, window} = yield openAnimationInspector();
|
||||
let {playTimelineButtonEl} = panel;
|
||||
|
@ -189,32 +189,16 @@
|
||||
<key id="resumeKey"
|
||||
keycode="&debuggerUI.stepping.resume1;"
|
||||
command="resumeCommand"/>
|
||||
<key id="resumeKey2"
|
||||
keycode="&debuggerUI.stepping.resume2;"
|
||||
modifiers="accel"
|
||||
command="resumeCommand"/>
|
||||
<key id="stepOverKey"
|
||||
keycode="&debuggerUI.stepping.stepOver1;"
|
||||
command="stepOverCommand"/>
|
||||
<key id="stepOverKey2"
|
||||
keycode="&debuggerUI.stepping.stepOver2;"
|
||||
modifiers="accel"
|
||||
command="stepOverCommand"/>
|
||||
<key id="stepInKey"
|
||||
keycode="&debuggerUI.stepping.stepIn1;"
|
||||
command="stepInCommand"/>
|
||||
<key id="stepInKey2"
|
||||
keycode="&debuggerUI.stepping.stepIn2;"
|
||||
modifiers="accel"
|
||||
command="stepInCommand"/>
|
||||
<key id="stepOutKey"
|
||||
keycode="&debuggerUI.stepping.stepOut1;"
|
||||
modifiers="shift"
|
||||
command="stepOutCommand"/>
|
||||
<key id="stepOutKey2"
|
||||
keycode="&debuggerUI.stepping.stepOut2;"
|
||||
modifiers="accel shift"
|
||||
command="stepOutCommand"/>
|
||||
<key id="fileSearchKey"
|
||||
key="&debuggerUI.searchFile.key;"
|
||||
modifiers="accel"
|
||||
|
@ -3,29 +3,27 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const { Task } = require("devtools/shared/task");
|
||||
|
||||
function DebuggerPanel(iframeWindow, toolbox) {
|
||||
this.panelWin = iframeWindow;
|
||||
this.toolbox = toolbox;
|
||||
}
|
||||
|
||||
DebuggerPanel.prototype = {
|
||||
open: function() {
|
||||
let targetPromise;
|
||||
open: Task.async(function*() {
|
||||
if (!this.toolbox.target.isRemote) {
|
||||
targetPromise = this.toolbox.target.makeRemote();
|
||||
} else {
|
||||
targetPromise = Promise.resolve(this.toolbox.target);
|
||||
yield this.toolbox.target.makeRemote();
|
||||
}
|
||||
|
||||
return targetPromise.then(() => {
|
||||
this.panelWin.Debugger.bootstrap({
|
||||
threadClient: this.toolbox.threadClient,
|
||||
tabTarget: this.toolbox.target
|
||||
});
|
||||
this.isReady = true;
|
||||
return this;
|
||||
yield this.panelWin.Debugger.bootstrap({
|
||||
threadClient: this.toolbox.threadClient,
|
||||
tabTarget: this.toolbox.target
|
||||
});
|
||||
},
|
||||
|
||||
this.isReady = true;
|
||||
return this;
|
||||
}),
|
||||
|
||||
_store: function() {
|
||||
return this.panelWin.Debugger.store;
|
||||
|
@ -54,12 +54,16 @@ DebuggerPanel.prototype = {
|
||||
this._toolbox.on("host-changed", this.handleHostChanged);
|
||||
// Add keys from this document's keyset to the toolbox, so they
|
||||
// can work when the split console is focused.
|
||||
let keysToClone = ["resumeKey", "resumeKey2", "stepOverKey",
|
||||
"stepOverKey2", "stepInKey", "stepInKey2",
|
||||
"stepOutKey", "stepOutKey2"];
|
||||
let keysToClone = ["resumeKey", "stepOverKey", "stepInKey", "stepOutKey"];
|
||||
for (let key of keysToClone) {
|
||||
let elm = this.panelWin.document.getElementById(key);
|
||||
this._toolbox.useKeyWithSplitConsole(elm, "jsdebugger");
|
||||
let keycode = elm.getAttribute("keycode");
|
||||
let modifiers = elm.getAttribute("modifiers");
|
||||
let command = elm.getAttribute("command");
|
||||
let handler = this._view.Toolbar.getCommandHandler(command);
|
||||
|
||||
let keyShortcut = this.translateToKeyShortcut(keycode, modifiers);
|
||||
this._toolbox.useKeyWithSplitConsole(keyShortcut, handler, "jsdebugger");
|
||||
}
|
||||
this.isReady = true;
|
||||
this.emit("ready");
|
||||
@ -70,6 +74,40 @@ DebuggerPanel.prototype = {
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Translate a VK_ keycode, with modifiers, to a key shortcut that can be used with
|
||||
* shared/key-shortcut.
|
||||
*
|
||||
* @param {String} keycode
|
||||
* The VK_* keycode to translate
|
||||
* @param {String} modifiers
|
||||
* The list (blank-space separated) of modifiers applying to this keycode.
|
||||
* @return {String} a key shortcut ready to be used with shared/key-shortcut.js
|
||||
*/
|
||||
translateToKeyShortcut: function (keycode, modifiers) {
|
||||
// Remove the VK_ prefix.
|
||||
keycode = keycode.replace("VK_", "");
|
||||
|
||||
// Translate modifiers
|
||||
if (modifiers.includes("shift")) {
|
||||
keycode = "Shift+" + keycode;
|
||||
}
|
||||
if (modifiers.includes("alt")) {
|
||||
keycode = "Alt+" + keycode;
|
||||
}
|
||||
if (modifiers.includes("control")) {
|
||||
keycode = "Ctrl+" + keycode;
|
||||
}
|
||||
if (modifiers.includes("meta")) {
|
||||
keycode = "Cmd+" + keycode;
|
||||
}
|
||||
if (modifiers.includes("accel")) {
|
||||
keycode = "CmdOrCtrl+" + keycode;
|
||||
}
|
||||
|
||||
return keycode;
|
||||
},
|
||||
|
||||
// DevToolPanel API
|
||||
|
||||
get target() {
|
||||
|
@ -207,7 +207,7 @@ skip-if = e10s && debug
|
||||
[browser_dbg_pretty-print-10.js]
|
||||
skip-if = e10s && debug
|
||||
[browser_dbg_pretty-print-11.js]
|
||||
skip-if = e10s && debug || true
|
||||
skip-if = e10s && debug
|
||||
[browser_dbg_pretty-print-12.js]
|
||||
skip-if = e10s && debug
|
||||
[browser_dbg_pretty-print-13.js]
|
||||
|
@ -101,13 +101,35 @@ ToolbarView.prototype = {
|
||||
*/
|
||||
_addCommands: function () {
|
||||
XULUtils.addCommands(document.getElementById("debuggerCommands"), {
|
||||
resumeCommand: () => this._onResumePressed(),
|
||||
stepOverCommand: () => this._onStepOverPressed(),
|
||||
stepInCommand: () => this._onStepInPressed(),
|
||||
stepOutCommand: () => this._onStepOutPressed()
|
||||
resumeCommand: this.getCommandHandler("resumeCommand"),
|
||||
stepOverCommand: this.getCommandHandler("stepOverCommand"),
|
||||
stepInCommand: this.getCommandHandler("stepInCommand"),
|
||||
stepOutCommand: this.getCommandHandler("stepOutCommand")
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieve the callback associated with the provided debugger command.
|
||||
*
|
||||
* @param {String} command
|
||||
* The debugger command id.
|
||||
* @return {Function} the corresponding callback.
|
||||
*/
|
||||
getCommandHandler: function (command) {
|
||||
switch (command) {
|
||||
case "resumeCommand":
|
||||
return () => this._onResumePressed();
|
||||
case "stepOverCommand":
|
||||
return () => this._onStepOverPressed();
|
||||
case "stepInCommand":
|
||||
return () => this._onStepInPressed();
|
||||
case "stepOutCommand":
|
||||
return () => this._onStepOutPressed();
|
||||
default:
|
||||
return () => {};
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Display a warning when trying to resume a debuggee while another is paused.
|
||||
* Debuggees must be unpaused in a Last-In-First-Out order.
|
||||
|
@ -60,7 +60,7 @@ Tools.inspector = {
|
||||
modifiers: osString == "Darwin" ? "accel,alt" : "accel,shift",
|
||||
icon: "chrome://devtools/skin/images/tool-inspector.svg",
|
||||
invertIconForDarkTheme: true,
|
||||
url: "chrome://devtools/content/inspector/inspector.xul",
|
||||
url: "chrome://devtools/content/inspector/inspector.xhtml",
|
||||
label: l10n("inspector.label"),
|
||||
panelLabel: l10n("inspector.panelLabel"),
|
||||
get tooltip() {
|
||||
|
@ -3,6 +3,8 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Tests that these toolbox split console APIs work:
|
||||
// * toolbox.useKeyWithSplitConsole()
|
||||
// * toolbox.isSplitConsoleFocused
|
||||
@ -11,7 +13,6 @@ let gToolbox = null;
|
||||
let panelWin = null;
|
||||
|
||||
const URL = "data:text/html;charset=utf8,test split console key delegation";
|
||||
const XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
|
||||
|
||||
// Force the old debugger UI since it's directly used (see Bug 1301705)
|
||||
Services.prefs.setBoolPref("devtools.debugger.new-debugger-frontend", false);
|
||||
@ -45,18 +46,15 @@ function* testIsSplitConsoleFocused() {
|
||||
function* testUseKeyWithSplitConsole() {
|
||||
let commandCalled = false;
|
||||
|
||||
let keyElm = panelWin.document.createElementNS(XULNS, "key");
|
||||
keyElm.setAttribute("keycode", "VK_F3");
|
||||
keyElm.addEventListener("command", () => {commandCalled = true;}, false);
|
||||
panelWin.document.getElementsByTagName("keyset")[0].appendChild(keyElm);
|
||||
|
||||
info("useKeyWithSplitConsole on debugger while debugger is focused");
|
||||
gToolbox.useKeyWithSplitConsole(keyElm, "jsdebugger");
|
||||
gToolbox.useKeyWithSplitConsole("F3", () => {
|
||||
commandCalled = true;
|
||||
}, "jsdebugger");
|
||||
|
||||
info("synthesizeKey with the console focused");
|
||||
let consoleInput = gToolbox.getPanel("webconsole").hud.jsterm.inputNode;
|
||||
consoleInput.focus();
|
||||
synthesizeKeyElement(keyElm);
|
||||
synthesizeKeyShortcut("F3", panelWin);
|
||||
|
||||
ok(commandCalled, "Shortcut key should trigger the command");
|
||||
}
|
||||
@ -65,18 +63,15 @@ function* testUseKeyWithSplitConsole() {
|
||||
function* testUseKeyWithSplitConsoleWrongTool() {
|
||||
let commandCalled = false;
|
||||
|
||||
let keyElm = panelWin.document.createElementNS(XULNS, "key");
|
||||
keyElm.setAttribute("keycode", "VK_F4");
|
||||
keyElm.addEventListener("command", () => {commandCalled = true;}, false);
|
||||
panelWin.document.getElementsByTagName("keyset")[0].appendChild(keyElm);
|
||||
|
||||
info("useKeyWithSplitConsole on inspector while debugger is focused");
|
||||
gToolbox.useKeyWithSplitConsole(keyElm, "inspector");
|
||||
gToolbox.useKeyWithSplitConsole("F4", () => {
|
||||
commandCalled = true;
|
||||
}, "inspector");
|
||||
|
||||
info("synthesizeKey with the console focused");
|
||||
let consoleInput = gToolbox.getPanel("webconsole").hud.jsterm.inputNode;
|
||||
consoleInput.focus();
|
||||
synthesizeKeyElement(keyElm);
|
||||
synthesizeKeyShortcut("F4", panelWin);
|
||||
|
||||
ok(!commandCalled, "Shortcut key shouldn't trigger the command");
|
||||
}
|
||||
|
@ -9,6 +9,14 @@ add_task(function* () {
|
||||
let toolbox = yield openNewTabAndToolbox(URL, "inspector");
|
||||
let textboxContextMenu = toolbox.textboxContextMenuPopup;
|
||||
|
||||
emptyClipboard();
|
||||
|
||||
// Make sure the focus is predictable.
|
||||
let inspector = toolbox.getPanel("inspector");
|
||||
let onFocus = once(inspector.searchBox, "focus");
|
||||
inspector.searchBox.focus();
|
||||
yield onFocus;
|
||||
|
||||
ok(textboxContextMenu, "The textbox context menu is loaded in the toolbox");
|
||||
|
||||
let cmdUndo = textboxContextMenu.querySelector("[command=cmd_undo]");
|
||||
@ -26,10 +34,17 @@ add_task(function* () {
|
||||
|
||||
is(cmdUndo.getAttribute("disabled"), "true", "cmdUndo is disabled");
|
||||
is(cmdDelete.getAttribute("disabled"), "true", "cmdDelete is disabled");
|
||||
is(cmdSelectAll.getAttribute("disabled"), "", "cmdSelectAll is enabled");
|
||||
is(cmdCut.getAttribute("disabled"), "true", "cmdCut is disabled");
|
||||
is(cmdCopy.getAttribute("disabled"), "true", "cmdCopy is disabled");
|
||||
is(cmdPaste.getAttribute("disabled"), "true", "cmdPaste is disabled");
|
||||
is(cmdSelectAll.getAttribute("disabled"), "true", "cmdSelectAll is disabled");
|
||||
|
||||
// Cut/Copy items are enabled in context menu even if there
|
||||
// is no selection. See also Bug 1303033
|
||||
is(cmdCut.getAttribute("disabled"), "", "cmdCut is enabled");
|
||||
is(cmdCopy.getAttribute("disabled"), "", "cmdCopy is enabled");
|
||||
|
||||
if (isWindows()) {
|
||||
// emptyClipboard only works on Windows (666254), assert paste only for this OS.
|
||||
is(cmdPaste.getAttribute("disabled"), "true", "cmdPaste is disabled");
|
||||
}
|
||||
|
||||
yield cleanup(toolbox);
|
||||
});
|
||||
|
@ -84,6 +84,7 @@ function testReload(shortcut, docked, toolID, callback) {
|
||||
|
||||
description = docked + " devtools with tool " + toolID + ", shortcut #" + shortcut;
|
||||
info("Testing reload in " + description);
|
||||
toolbox.win.focus();
|
||||
synthesizeKeyShortcut(L10N.getStr(shortcut), toolbox.win);
|
||||
reloadsSent++;
|
||||
}
|
||||
|
@ -559,3 +559,20 @@ function stopRecordingTelemetryLogs(Telemetry) {
|
||||
delete Telemetry.prototype._oldlogKeyed;
|
||||
delete Telemetry.prototype.telemetryInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean the logical clipboard content. This method only clears the OS clipboard on
|
||||
* Windows (see Bug 666254).
|
||||
*/
|
||||
function emptyClipboard() {
|
||||
let clipboard = Cc["@mozilla.org/widget/clipboard;1"]
|
||||
.getService(SpecialPowers.Ci.nsIClipboard);
|
||||
clipboard.emptyClipboard(clipboard.kGlobalClipboard);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current operating system is Windows.
|
||||
*/
|
||||
function isWindows() {
|
||||
return Services.appinfo.OS === "WINNT";
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ const SCREENSIZE_HISTOGRAM = "DEVTOOLS_SCREEN_RESOLUTION_ENUMERATED_PER_USER";
|
||||
const HTML_NS = "http://www.w3.org/1999/xhtml";
|
||||
const { SourceMapService } = require("./source-map-service");
|
||||
|
||||
var {Cc, Ci, Cu} = require("chrome");
|
||||
var {Ci, Cu} = require("chrome");
|
||||
var promise = require("promise");
|
||||
var defer = require("devtools/shared/defer");
|
||||
var Services = require("Services");
|
||||
@ -235,8 +235,8 @@ Toolbox.prototype = {
|
||||
if (panel) {
|
||||
deferred.resolve(panel);
|
||||
} else {
|
||||
this.on(id + "-ready", (e, panel) => {
|
||||
deferred.resolve(panel);
|
||||
this.on(id + "-ready", (e, initializedPanel) => {
|
||||
deferred.resolve(initializedPanel);
|
||||
});
|
||||
}
|
||||
|
||||
@ -406,17 +406,17 @@ Toolbox.prototype = {
|
||||
this.textboxContextMenuPopup.addEventListener("popupshowing",
|
||||
this._updateTextboxMenuItems, true);
|
||||
|
||||
var shortcuts = new KeyShortcuts({
|
||||
this.shortcuts = new KeyShortcuts({
|
||||
window: this.doc.defaultView
|
||||
});
|
||||
this._buildDockButtons();
|
||||
this._buildOptions(shortcuts);
|
||||
this._buildOptions();
|
||||
this._buildTabs();
|
||||
this._applyCacheSettings();
|
||||
this._applyServiceWorkersTestingSettings();
|
||||
this._addKeysToWindow();
|
||||
this._addReloadKeys(shortcuts);
|
||||
this._addHostListeners(shortcuts);
|
||||
this._addReloadKeys();
|
||||
this._addHostListeners();
|
||||
this._registerOverlays();
|
||||
if (!this._hostOptions || this._hostOptions.zoom === true) {
|
||||
ZoomKeys.register(this.win);
|
||||
@ -502,7 +502,8 @@ Toolbox.prototype = {
|
||||
this._telemetry.logOncePerBrowserVersion(OS_HISTOGRAM, system.getOSCPU());
|
||||
this._telemetry.logOncePerBrowserVersion(OS_IS_64_BITS,
|
||||
Services.appinfo.is64Bit ? 1 : 0);
|
||||
this._telemetry.logOncePerBrowserVersion(SCREENSIZE_HISTOGRAM, system.getScreenDimensions());
|
||||
this._telemetry.logOncePerBrowserVersion(SCREENSIZE_HISTOGRAM,
|
||||
system.getScreenDimensions());
|
||||
this._telemetry.log(HOST_HISTOGRAM, this._getTelemetryHostId());
|
||||
},
|
||||
|
||||
@ -529,7 +530,7 @@ Toolbox.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
_buildOptions: function (shortcuts) {
|
||||
_buildOptions: function () {
|
||||
let selectOptions = (name, event) => {
|
||||
// Flip back to the last used panel if we are already
|
||||
// on the options panel.
|
||||
@ -542,8 +543,8 @@ Toolbox.prototype = {
|
||||
// Prevent the opening of bookmarks window on toolbox.options.key
|
||||
event.preventDefault();
|
||||
};
|
||||
shortcuts.on(L10N.getStr("toolbox.options.key"), selectOptions);
|
||||
shortcuts.on(L10N.getStr("toolbox.help.key"), selectOptions);
|
||||
this.shortcuts.on(L10N.getStr("toolbox.options.key"), selectOptions);
|
||||
this.shortcuts.on(L10N.getStr("toolbox.help.key"), selectOptions);
|
||||
},
|
||||
|
||||
_splitConsoleOnKeypress: function (e) {
|
||||
@ -561,26 +562,24 @@ Toolbox.prototype = {
|
||||
* Add a shortcut key that should work when a split console
|
||||
* has focus to the toolbox.
|
||||
*
|
||||
* @param {element} keyElement
|
||||
* They <key> XUL element describing the shortcut key
|
||||
* @param {string} whichTool
|
||||
* The tool the key belongs to. The corresponding command
|
||||
* will only trigger if this tool is active.
|
||||
* @param {String} key
|
||||
* The electron key shortcut.
|
||||
* @param {Function} handler
|
||||
* The callback that should be called when the provided key shortcut is pressed.
|
||||
* @param {String} whichTool
|
||||
* The tool the key belongs to. The corresponding handler will only be triggered
|
||||
* if this tool is active.
|
||||
*/
|
||||
useKeyWithSplitConsole: function (keyElement, whichTool) {
|
||||
let cloned = keyElement.cloneNode();
|
||||
cloned.setAttribute("oncommand", "void(0)");
|
||||
cloned.removeAttribute("command");
|
||||
cloned.addEventListener("command", (e) => {
|
||||
// Only forward the command if the tool is active
|
||||
useKeyWithSplitConsole: function (key, handler, whichTool) {
|
||||
this.shortcuts.on(key, (name, event) => {
|
||||
if (this.currentToolId === whichTool && this.isSplitConsoleFocused()) {
|
||||
keyElement.doCommand();
|
||||
handler();
|
||||
event.preventDefault();
|
||||
}
|
||||
}, true);
|
||||
this.doc.getElementById("toolbox-keyset").appendChild(cloned);
|
||||
});
|
||||
},
|
||||
|
||||
_addReloadKeys: function (shortcuts) {
|
||||
_addReloadKeys: function () {
|
||||
[
|
||||
["reload", false],
|
||||
["reload2", false],
|
||||
@ -588,7 +587,7 @@ Toolbox.prototype = {
|
||||
["forceReload2", true]
|
||||
].forEach(([id, force]) => {
|
||||
let key = L10N.getStr("toolbox." + id + ".key");
|
||||
shortcuts.on(key, (name, event) => {
|
||||
this.shortcuts.on(key, (name, event) => {
|
||||
this.reloadTarget(force);
|
||||
|
||||
// Prevent Firefox shortcuts from reloading the page
|
||||
@ -597,23 +596,23 @@ Toolbox.prototype = {
|
||||
});
|
||||
},
|
||||
|
||||
_addHostListeners: function (shortcuts) {
|
||||
shortcuts.on(L10N.getStr("toolbox.nextTool.key"),
|
||||
_addHostListeners: function () {
|
||||
this.shortcuts.on(L10N.getStr("toolbox.nextTool.key"),
|
||||
(name, event) => {
|
||||
this.selectNextTool();
|
||||
event.preventDefault();
|
||||
});
|
||||
shortcuts.on(L10N.getStr("toolbox.previousTool.key"),
|
||||
this.shortcuts.on(L10N.getStr("toolbox.previousTool.key"),
|
||||
(name, event) => {
|
||||
this.selectPreviousTool();
|
||||
event.preventDefault();
|
||||
});
|
||||
shortcuts.on(L10N.getStr("toolbox.minimize.key"),
|
||||
this.shortcuts.on(L10N.getStr("toolbox.minimize.key"),
|
||||
(name, event) => {
|
||||
this._toggleMinimizeMode();
|
||||
event.preventDefault();
|
||||
});
|
||||
shortcuts.on(L10N.getStr("toolbox.toggleHost.key"),
|
||||
this.shortcuts.on(L10N.getStr("toolbox.toggleHost.key"),
|
||||
(name, event) => {
|
||||
this.switchToPreviousHost();
|
||||
event.preventDefault();
|
||||
@ -976,16 +975,16 @@ Toolbox.prototype = {
|
||||
this._requisition = requisition;
|
||||
|
||||
const spec = CommandUtils.getCommandbarSpec("devtools.toolbox.toolbarSpec");
|
||||
return CommandUtils.createButtons(spec, this.target, this.doc,
|
||||
requisition).then(buttons => {
|
||||
let container = this.doc.getElementById("toolbox-buttons");
|
||||
buttons.forEach(button=> {
|
||||
if (button) {
|
||||
container.appendChild(button);
|
||||
}
|
||||
});
|
||||
this.setToolboxButtonsVisibility();
|
||||
});
|
||||
return CommandUtils.createButtons(spec, this.target, this.doc, requisition)
|
||||
.then(buttons => {
|
||||
let container = this.doc.getElementById("toolbox-buttons");
|
||||
buttons.forEach(button => {
|
||||
if (button) {
|
||||
container.appendChild(button);
|
||||
}
|
||||
});
|
||||
this.setToolboxButtonsVisibility();
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
@ -996,7 +995,8 @@ Toolbox.prototype = {
|
||||
_buildPickerButton: function () {
|
||||
this._pickerButton = this.doc.createElementNS(HTML_NS, "button");
|
||||
this._pickerButton.id = "command-button-pick";
|
||||
this._pickerButton.className = "command-button command-button-invertable devtools-button";
|
||||
this._pickerButton.className =
|
||||
"command-button command-button-invertable devtools-button";
|
||||
this._pickerButton.setAttribute("title", L10N.getStr("pickButton.tooltip"));
|
||||
this._pickerButton.setAttribute("hidden", "true");
|
||||
|
||||
@ -1082,7 +1082,9 @@ Toolbox.prototype = {
|
||||
let on = true;
|
||||
try {
|
||||
on = Services.prefs.getBoolPref(visibilityswitch);
|
||||
} catch (ex) { }
|
||||
} catch (ex) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
on = on && isTargetSupported(this.target);
|
||||
|
||||
@ -1226,8 +1228,8 @@ Toolbox.prototype = {
|
||||
if (panel) {
|
||||
deferred.resolve(panel);
|
||||
} else {
|
||||
this.once(id + "-ready", panel => {
|
||||
deferred.resolve(panel);
|
||||
this.once(id + "-ready", initializedPanel => {
|
||||
deferred.resolve(initializedPanel);
|
||||
});
|
||||
}
|
||||
return deferred.promise;
|
||||
@ -1598,7 +1600,7 @@ Toolbox.prototype = {
|
||||
// Returns an instance of the preference actor
|
||||
get _preferenceFront() {
|
||||
return this.target.root.then(rootForm => {
|
||||
return new getPreferenceFront(this.target.client, rootForm);
|
||||
return getPreferenceFront(this.target.client, rootForm);
|
||||
});
|
||||
},
|
||||
|
||||
@ -1950,9 +1952,9 @@ Toolbox.prototype = {
|
||||
if (!this._initInspector) {
|
||||
this._initInspector = Task.spawn(function* () {
|
||||
this._inspector = InspectorFront(this._target.client, this._target.form);
|
||||
this._walker = yield this._inspector.getWalker(
|
||||
{showAllAnonymousContent: Services.prefs.getBoolPref("devtools.inspector.showAllAnonymousContent")}
|
||||
);
|
||||
let pref = "devtools.inspector.showAllAnonymousContent";
|
||||
let showAllAnonymousContent = Services.prefs.getBoolPref(pref);
|
||||
this._walker = yield this._inspector.getWalker({ showAllAnonymousContent });
|
||||
this._selection = new Selection(this._walker);
|
||||
|
||||
if (this.highlighterUtils.isRemoteHighlightable()) {
|
||||
@ -1976,7 +1978,7 @@ Toolbox.prototype = {
|
||||
return this._destroyingInspector;
|
||||
}
|
||||
|
||||
return this._destroyingInspector = Task.spawn(function* () {
|
||||
this._destroyingInspector = Task.spawn(function* () {
|
||||
if (!this._inspector) {
|
||||
return;
|
||||
}
|
||||
@ -1988,7 +1990,9 @@ Toolbox.prototype = {
|
||||
if (this._walker && !this.walker.traits.autoReleased) {
|
||||
try {
|
||||
yield this._walker.release();
|
||||
} catch (e) {}
|
||||
} catch (e) {
|
||||
// Do nothing;
|
||||
}
|
||||
}
|
||||
|
||||
yield this.highlighterUtils.stopPicker();
|
||||
@ -2018,6 +2022,7 @@ Toolbox.prototype = {
|
||||
this._selection = null;
|
||||
this._walker = null;
|
||||
}.bind(this));
|
||||
return this._destroyingInspector;
|
||||
},
|
||||
|
||||
/**
|
||||
@ -2233,7 +2238,7 @@ Toolbox.prototype = {
|
||||
// If target does not have profiler actor (addons), do not
|
||||
// even register the shared performance connection.
|
||||
if (!this.target.hasActor("profiler")) {
|
||||
return;
|
||||
return promise.resolve();
|
||||
}
|
||||
|
||||
if (this._performanceFrontConnection) {
|
||||
@ -2272,10 +2277,11 @@ Toolbox.prototype = {
|
||||
}),
|
||||
|
||||
/**
|
||||
* Called when any event comes from the PerformanceFront. If the performance tool is already
|
||||
* loaded when the first event comes in, immediately unbind this handler, as this is
|
||||
* only used to queue up observed recordings before the performance tool can handle them,
|
||||
* which will only occur when `console.profile()` recordings are started before the tool loads.
|
||||
* Called when any event comes from the PerformanceFront. If the performance tool is
|
||||
* already loaded when the first event comes in, immediately unbind this handler, as
|
||||
* this is only used to queue up observed recordings before the performance tool can
|
||||
* handle them, which will only occur when `console.profile()` recordings are started
|
||||
* before the tool loads.
|
||||
*/
|
||||
_onPerformanceFrontEvent: Task.async(function* (eventName, recording) {
|
||||
if (this.getPanel("performance")) {
|
||||
@ -2283,7 +2289,8 @@ Toolbox.prototype = {
|
||||
return;
|
||||
}
|
||||
|
||||
let recordings = this._performanceQueuedRecordings = this._performanceQueuedRecordings || [];
|
||||
this._performanceQueuedRecordings = this._performanceQueuedRecordings || [];
|
||||
let recordings = this._performanceQueuedRecordings;
|
||||
|
||||
// Before any console recordings, we'll get a `console-profile-start` event
|
||||
// warning us that a recording will come later (via `recording-started`), so
|
||||
|
@ -31,7 +31,6 @@
|
||||
|
||||
<commandset id="editMenuCommands"/>
|
||||
<keyset id="editMenuKeys"/>
|
||||
<keyset id="toolbox-keyset"/>
|
||||
|
||||
<popupset>
|
||||
<menupopup id="toolbox-textbox-context-popup">
|
||||
|
@ -12,19 +12,31 @@ const { DOM, createClass, PropTypes } = require("devtools/client/shared/vendor/r
|
||||
const { div } = DOM;
|
||||
|
||||
/**
|
||||
* Side panel for the Inspector panel.
|
||||
* This side panel is using an existing DOM node as a content.
|
||||
* Helper panel component that is using an existing DOM node
|
||||
* as the content. It's used by Sidebar as well as SplitBox
|
||||
* components.
|
||||
*/
|
||||
var InspectorTabPanel = createClass({
|
||||
displayName: "InspectorTabPanel",
|
||||
|
||||
propTypes: {
|
||||
// ID of the node that should be rendered as the content.
|
||||
id: PropTypes.string.isRequired,
|
||||
// Optional prefix for panel IDs.
|
||||
idPrefix: PropTypes.string,
|
||||
// Optional mount callback
|
||||
onMount: PropTypes.func,
|
||||
},
|
||||
|
||||
getDefaultProps: function () {
|
||||
return {
|
||||
idPrefix: "",
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount: function () {
|
||||
let doc = this.refs.content.ownerDocument;
|
||||
let panel = doc.getElementById("sidebar-panel-" + this.props.id);
|
||||
let panel = doc.getElementById(this.props.idPrefix + this.props.id);
|
||||
|
||||
// Append existing DOM node into panel's content.
|
||||
this.refs.content.appendChild(panel);
|
||||
|
@ -29,6 +29,13 @@ add_task(function* () {
|
||||
let cmdPaste = searchContextMenu.querySelector("[command=cmd_paste]");
|
||||
|
||||
info("Opening context menu");
|
||||
|
||||
emptyClipboard();
|
||||
|
||||
let onFocus = once(searchField, "focus");
|
||||
searchField.focus();
|
||||
yield onFocus;
|
||||
|
||||
let onContextMenuPopup = once(searchContextMenu, "popupshowing");
|
||||
EventUtils.synthesizeMouse(searchField, 2, 2,
|
||||
{type: "contextmenu", button: 2}, win);
|
||||
@ -36,10 +43,17 @@ add_task(function* () {
|
||||
|
||||
is(cmdUndo.getAttribute("disabled"), "true", "cmdUndo is disabled");
|
||||
is(cmdDelete.getAttribute("disabled"), "true", "cmdDelete is disabled");
|
||||
is(cmdSelectAll.getAttribute("disabled"), "", "cmdSelectAll is enabled");
|
||||
is(cmdCut.getAttribute("disabled"), "true", "cmdCut is disabled");
|
||||
is(cmdCopy.getAttribute("disabled"), "true", "cmdCopy is disabled");
|
||||
is(cmdPaste.getAttribute("disabled"), "true", "cmdPaste is disabled");
|
||||
is(cmdSelectAll.getAttribute("disabled"), "true", "cmdSelectAll is disabled");
|
||||
|
||||
// Cut/Copy items are enabled in context menu even if there
|
||||
// is no selection. See also Bug 1303033
|
||||
is(cmdCut.getAttribute("disabled"), "", "cmdCut is enabled");
|
||||
is(cmdCopy.getAttribute("disabled"), "", "cmdCopy is enabled");
|
||||
|
||||
if (isWindows()) {
|
||||
// emptyClipboard only works on Windows (666254), assert paste only for this OS.
|
||||
is(cmdPaste.getAttribute("disabled"), "true", "cmdPaste is disabled");
|
||||
}
|
||||
|
||||
info("Closing context menu");
|
||||
let onContextMenuHidden = once(searchContextMenu, "popuphidden");
|
||||
|
@ -31,10 +31,18 @@ const {ToolSidebar} = require("devtools/client/inspector/toolsidebar");
|
||||
const {ViewHelpers} = require("devtools/client/shared/widgets/view-helpers");
|
||||
const clipboardHelper = require("devtools/shared/platform/clipboard");
|
||||
|
||||
const {LocalizationHelper} = require("devtools/shared/l10n");
|
||||
const {LocalizationHelper, localizeMarkup} = require("devtools/shared/l10n");
|
||||
const INSPECTOR_L10N = new LocalizationHelper("devtools/locale/inspector.properties");
|
||||
const TOOLBOX_L10N = new LocalizationHelper("devtools/locale/toolbox.properties");
|
||||
|
||||
// Sidebar dimensions
|
||||
const INITIAL_SIDEBAR_SIZE = 350;
|
||||
const MIN_SIDEBAR_SIZE = 50;
|
||||
|
||||
// If the toolbox width is smaller than given amount of pixels,
|
||||
// the sidebar automatically switches from 'landscape' to 'portrait' mode.
|
||||
const PORTRAIT_MODE_WIDTH = 700;
|
||||
|
||||
/**
|
||||
* Represents an open instance of the Inspector for a tab.
|
||||
* The inspector controls the breadcrumbs, the markup view, and the sidebar
|
||||
@ -94,6 +102,9 @@ function InspectorPanel(iframeWindow, toolbox) {
|
||||
this.onDetached = this.onDetached.bind(this);
|
||||
this.onPaneToggleButtonClicked = this.onPaneToggleButtonClicked.bind(this);
|
||||
this._onMarkupFrameLoad = this._onMarkupFrameLoad.bind(this);
|
||||
this.onPanelWindowResize = this.onPanelWindowResize.bind(this);
|
||||
this.onSidebarShown = this.onSidebarShown.bind(this);
|
||||
this.onSidebarHidden = this.onSidebarHidden.bind(this);
|
||||
|
||||
this._target.on("will-navigate", this._onBeforeNavigate);
|
||||
this._detectingActorFeatures = this._detectActorFeatures();
|
||||
@ -108,6 +119,9 @@ InspectorPanel.prototype = {
|
||||
* open is effectively an asynchronous constructor
|
||||
*/
|
||||
open: Task.async(function* () {
|
||||
// Localize all the nodes containing a data-localization attribute.
|
||||
localizeMarkup(this.panelDoc);
|
||||
|
||||
this._cssPropertiesLoaded = initCssProperties(this.toolbox);
|
||||
yield this._cssPropertiesLoaded;
|
||||
yield this.target.makeRemote();
|
||||
@ -400,6 +414,98 @@ InspectorPanel.prototype = {
|
||||
return this._toolbox.browserRequire;
|
||||
},
|
||||
|
||||
get InspectorTabPanel() {
|
||||
if (!this._InspectorTabPanel) {
|
||||
this._InspectorTabPanel =
|
||||
this.React.createFactory(this.browserRequire(
|
||||
"devtools/client/inspector/components/inspector-tab-panel"));
|
||||
}
|
||||
return this._InspectorTabPanel;
|
||||
},
|
||||
|
||||
/**
|
||||
* Build Splitter located between the main and side area of
|
||||
* the Inspector panel.
|
||||
*/
|
||||
setupSplitter: function () {
|
||||
let SplitBox = this.React.createFactory(this.browserRequire(
|
||||
"devtools/client/shared/components/splitter/split-box"));
|
||||
|
||||
this.panelWin.addEventListener("resize", this.onPanelWindowResize, true);
|
||||
|
||||
let splitter = SplitBox({
|
||||
className: "inspector-sidebar-splitter",
|
||||
initialWidth: INITIAL_SIDEBAR_SIZE,
|
||||
initialHeight: INITIAL_SIDEBAR_SIZE,
|
||||
minSize: MIN_SIDEBAR_SIZE,
|
||||
splitterSize: 1,
|
||||
endPanelControl: true,
|
||||
startPanel: this.InspectorTabPanel({
|
||||
id: "inspector-main-content"
|
||||
}),
|
||||
endPanel: this.InspectorTabPanel({
|
||||
id: "inspector-sidebar-container"
|
||||
})
|
||||
});
|
||||
|
||||
this._splitter = this.ReactDOM.render(splitter,
|
||||
this.panelDoc.getElementById("inspector-splitter-box"));
|
||||
|
||||
// Persist splitter state in preferences.
|
||||
this.sidebar.on("show", this.onSidebarShown);
|
||||
this.sidebar.on("hide", this.onSidebarHidden);
|
||||
this.sidebar.on("destroy", this.onSidebarHidden);
|
||||
},
|
||||
|
||||
/**
|
||||
* Splitter clean up.
|
||||
*/
|
||||
teardownSplitter: function () {
|
||||
this.panelWin.removeEventListener("resize", this.onPanelWindowResize, true);
|
||||
|
||||
this.sidebar.off("show", this.onSidebarShown);
|
||||
this.sidebar.off("hide", this.onSidebarHidden);
|
||||
this.sidebar.off("destroy", this.onSidebarHidden);
|
||||
},
|
||||
|
||||
/**
|
||||
* If Toolbox width is less than 600 px, the splitter changes its mode
|
||||
* to `horizontal` to support portrait view.
|
||||
*/
|
||||
onPanelWindowResize: function () {
|
||||
let box = this.panelDoc.getElementById("inspector-splitter-box");
|
||||
this._splitter.setState({
|
||||
vert: (box.clientWidth > PORTRAIT_MODE_WIDTH)
|
||||
});
|
||||
},
|
||||
|
||||
onSidebarShown: function () {
|
||||
let width;
|
||||
let height;
|
||||
|
||||
// Initialize splitter size from preferences.
|
||||
try {
|
||||
width = Services.prefs.getIntPref("devtools.toolsidebar-width.inspector");
|
||||
height = Services.prefs.getIntPref("devtools.toolsidebar-height.inspector");
|
||||
} catch (e) {
|
||||
// Set width and height of the splitter. Only one
|
||||
// value is really useful at a time depending on the current
|
||||
// orientation (vertical/horizontal).
|
||||
// Having both is supported by the splitter component.
|
||||
width = INITIAL_SIDEBAR_SIZE;
|
||||
height = INITIAL_SIDEBAR_SIZE;
|
||||
}
|
||||
|
||||
this._splitter.setState({width, height});
|
||||
},
|
||||
|
||||
onSidebarHidden: function () {
|
||||
// Store the current splitter size to preferences.
|
||||
let state = this._splitter.state;
|
||||
Services.prefs.setIntPref("devtools.toolsidebar-width.inspector", state.width);
|
||||
Services.prefs.setIntPref("devtools.toolsidebar-height.inspector", state.height);
|
||||
},
|
||||
|
||||
/**
|
||||
* Build the sidebar.
|
||||
*/
|
||||
@ -455,56 +561,13 @@ InspectorPanel.prototype = {
|
||||
this.sidebar.toggleTab(true, "fontinspector");
|
||||
}
|
||||
|
||||
this.setupSidebarSize();
|
||||
// Setup the splitter before the sidebar is displayed so,
|
||||
// we don't miss any events.
|
||||
this.setupSplitter();
|
||||
|
||||
this.sidebar.show(defaultTab);
|
||||
},
|
||||
|
||||
/**
|
||||
* Sidebar size is currently driven by vbox.inspector-sidebar-container
|
||||
* element, which is located at the left/bottom side of the side bar splitter.
|
||||
* Its size is changed by the splitter and stored into preferences.
|
||||
* As soon as bug 1260552 is fixed and new HTML based splitter in place
|
||||
* the size can be driven by div.inspector-sidebar element. This element
|
||||
* represents the ToolSidebar and so, the entire logic related to size
|
||||
* persistence can be done inside the ToolSidebar.
|
||||
*/
|
||||
setupSidebarSize: function () {
|
||||
let sidePaneContainer = this.panelDoc.querySelector(
|
||||
"#inspector-sidebar-container");
|
||||
|
||||
this.sidebar.on("show", () => {
|
||||
try {
|
||||
sidePaneContainer.width = Services.prefs.getIntPref(
|
||||
"devtools.toolsidebar-width.inspector");
|
||||
sidePaneContainer.height = Services.prefs.getIntPref(
|
||||
"devtools.toolsidebar-height.inspector");
|
||||
} catch (e) {
|
||||
// The default width is the min-width set in CSS
|
||||
// for #inspector-sidebar-container
|
||||
// Set width and height of the sidebar container. Only one
|
||||
// value is really useful at a time depending on the current
|
||||
// toolbox orientation and having both doesn't break anything.
|
||||
sidePaneContainer.width = 450;
|
||||
sidePaneContainer.height = 450;
|
||||
}
|
||||
});
|
||||
|
||||
this.sidebar.on("hide", () => {
|
||||
Services.prefs.setIntPref("devtools.toolsidebar-width.inspector",
|
||||
sidePaneContainer.width);
|
||||
Services.prefs.setIntPref("devtools.toolsidebar-height.inspector",
|
||||
sidePaneContainer.height);
|
||||
});
|
||||
|
||||
this.sidebar.on("destroy", () => {
|
||||
Services.prefs.setIntPref("devtools.toolsidebar-width.inspector",
|
||||
sidePaneContainer.width);
|
||||
Services.prefs.setIntPref("devtools.toolsidebar-height.inspector",
|
||||
sidePaneContainer.height);
|
||||
});
|
||||
},
|
||||
|
||||
setupToolbar: function () {
|
||||
this.teardownToolbar();
|
||||
|
||||
@ -798,6 +861,9 @@ InspectorPanel.prototype = {
|
||||
|
||||
this.sidebar.off("select", this._setDefaultSidebar);
|
||||
let sidebarDestroyer = this.sidebar.destroy();
|
||||
|
||||
this.teardownSplitter();
|
||||
|
||||
this.sidebar = null;
|
||||
|
||||
this.teardownToolbar();
|
||||
@ -1251,7 +1317,8 @@ InspectorPanel.prototype = {
|
||||
* state and tooltip.
|
||||
*/
|
||||
onPaneToggleButtonClicked: function (e) {
|
||||
let sidePaneContainer = this.panelDoc.querySelector("#inspector-sidebar-container");
|
||||
let sidePaneContainer = this.panelDoc.querySelector(
|
||||
"#inspector-splitter-box .controlled");
|
||||
let isVisible = !this._sidebarToggle.state.collapsed;
|
||||
|
||||
// Make sure the sidebar has width and height attributes before collapsing
|
||||
|
@ -16,73 +16,77 @@
|
||||
<?xml-stylesheet href="resource://devtools/client/shared/components/tabs/tabbar.css" type="text/css"?>
|
||||
<?xml-stylesheet href="resource://devtools/client/inspector/components/side-panel.css" type="text/css"?>
|
||||
<?xml-stylesheet href="resource://devtools/client/inspector/components/inspector-tab-panel.css" type="text/css"?>
|
||||
<?xml-stylesheet href="resource://devtools/client/shared/components/splitter/split-box.css" type="text/css"?>
|
||||
|
||||
<!DOCTYPE window [
|
||||
<!ENTITY % inspectorDTD SYSTEM "chrome://devtools/locale/inspector.dtd"> %inspectorDTD;
|
||||
<!ENTITY % styleinspectorDTD SYSTEM "chrome://devtools/locale/styleinspector.dtd"> %styleinspectorDTD;
|
||||
<!ENTITY % fontinspectorDTD SYSTEM "chrome://devtools/locale/font-inspector.dtd"> %fontinspectorDTD;
|
||||
<!ENTITY % layoutviewDTD SYSTEM "chrome://devtools/locale/layoutview.dtd"> %layoutviewDTD;
|
||||
]>
|
||||
<!DOCTYPE window>
|
||||
|
||||
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
xmlns:html="http://www.w3.org/1999/xhtml">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml"
|
||||
xmlns:html="http://www.w3.org/1999/xhtml">
|
||||
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
|
||||
<script type="application/javascript;version=1.8"
|
||||
src="chrome://devtools/content/shared/theme-switching.js"/>
|
||||
src="chrome://devtools/content/shared/theme-switching.js"></script>
|
||||
</head>
|
||||
<body class="theme-body devtools-monospace" role="application">
|
||||
<html:div class="inspector-responsive-container theme-body inspector">
|
||||
|
||||
<box flex="1" class="devtools-responsive-container theme-body">
|
||||
<vbox flex="1" class="devtools-main-content">
|
||||
<html:div id="inspector-toolbar"
|
||||
class="devtools-toolbar"
|
||||
nowindowdrag="true">
|
||||
<html:button id="inspector-element-add-button"
|
||||
title="&inspectorAddNode.label;"
|
||||
class="devtools-button" />
|
||||
<!-- Main Panel Content -->
|
||||
<html:div id="inspector-main-content" class="devtools-main-content">
|
||||
<html:div id="inspector-toolbar" class="devtools-toolbar" nowindowdrag="true"
|
||||
data-localization-bundle="devtools/locale/inspector.properties">
|
||||
<html:button id="inspector-element-add-button" class="devtools-button"
|
||||
data-localization="title=inspectorAddNode.label"/>
|
||||
<html:div class="devtools-toolbar-spacer" />
|
||||
<html:span id="inspector-searchlabel" />
|
||||
<html:div id="inspector-search" class="devtools-searchbox has-clear-btn">
|
||||
<html:input id="inspector-searchbox" class="devtools-searchinput"
|
||||
type="search"
|
||||
placeholder="&inspectorSearchHTML.label3;"/>
|
||||
data-localization="placeholder=inspectorSearchHTML.label3"/>
|
||||
<html:button id="inspector-searchinput-clear" class="devtools-searchinput-clear" tabindex="-1"></html:button>
|
||||
</html:div>
|
||||
<html:button id="inspector-eyedropper-toggle"
|
||||
title="&inspectorEyeDropper.label;"
|
||||
data-localization="title=inspector.eyedropper.label"
|
||||
class="devtools-button command-button-invertable" />
|
||||
<div xmlns="http://www.w3.org/1999/xhtml"
|
||||
id="inspector-sidebar-toggle-box" />
|
||||
<html:div id="inspector-sidebar-toggle-box" />
|
||||
</html:div>
|
||||
<vbox flex="1" id="markup-box">
|
||||
</vbox>
|
||||
<html:div id="markup-box" />
|
||||
<html:div id="inspector-breadcrumbs-toolbar" class="devtools-toolbar">
|
||||
<html:div id="inspector-breadcrumbs" class="breadcrumbs-widget-container"
|
||||
role="group" aria-label="&inspectorBreadcrumbsGroup;" tabindex="0" />
|
||||
role="group" data-localization="aria-label=inspector.breadcrumbs.label" tabindex="0" />
|
||||
</html:div>
|
||||
</vbox>
|
||||
<splitter class="devtools-side-splitter"/>
|
||||
<vbox id="inspector-sidebar-container">
|
||||
<!-- Specify the XHTML namespace explicitly
|
||||
otherwise the layout is broken. -->
|
||||
<div xmlns="http://www.w3.org/1999/xhtml"
|
||||
id="inspector-sidebar"
|
||||
hidden="true" />
|
||||
</vbox>
|
||||
</html:div>
|
||||
|
||||
<!-- Splitter -->
|
||||
<html:div
|
||||
xmlns="http://www.w3.org/1999/xhtml"
|
||||
id="inspector-splitter-box">
|
||||
</html:div>
|
||||
|
||||
<!-- Sidebar Container -->
|
||||
<html:div id="inspector-sidebar-container">
|
||||
<html:div
|
||||
xmlns="http://www.w3.org/1999/xhtml"
|
||||
id="inspector-sidebar"
|
||||
hidden="true" />
|
||||
</html:div>
|
||||
|
||||
<!-- Sidebar panel definitions -->
|
||||
<html:div xmlns="http://www.w3.org/1999/xhtml" id="tabpanels" style="visibility:collapse">
|
||||
<html:div id="sidebar-panel-ruleview" class="devtools-monospace theme-sidebar inspector-tabpanel">
|
||||
<html:div id="tabpanels" style="visibility:collapse">
|
||||
<html:div id="sidebar-panel-ruleview" class="devtools-monospace theme-sidebar inspector-tabpanel"
|
||||
data-localization-bundle="devtools/locale/inspector.properties">
|
||||
<html:div id="ruleview-toolbar-container" class="devtools-toolbar">
|
||||
<html:div id="ruleview-toolbar">
|
||||
<html:div class="devtools-searchbox has-clear-btn">
|
||||
<html:input id="ruleview-searchbox"
|
||||
class="devtools-filterinput devtools-rule-searchbox"
|
||||
type="search"
|
||||
placeholder="&filterStylesPlaceholder;"/>
|
||||
data-localization="placeholder=inspector.filterStyles.placeholder"/>
|
||||
<html:button id="ruleview-searchinput-clear" class="devtools-searchinput-clear"></html:button>
|
||||
</html:div>
|
||||
<html:div id="ruleview-command-toolbar">
|
||||
<html:button id="ruleview-add-rule-button" title="&addRuleButtonTooltip;" class="devtools-button"></html:button>
|
||||
<html:button id="pseudo-class-panel-toggle" title="&togglePseudoClassPanel;" class="devtools-button"></html:button>
|
||||
<html:button id="ruleview-add-rule-button" data-localization="title=inspector.addRule.tooltip" class="devtools-button"></html:button>
|
||||
<html:button id="pseudo-class-panel-toggle" data-localization="title=inspector.togglePseudo.tooltip" class="devtools-button"></html:button>
|
||||
</html:div>
|
||||
</html:div>
|
||||
<html:div id="pseudo-class-panel" hidden="true">
|
||||
@ -98,39 +102,41 @@
|
||||
</html:div>
|
||||
</html:div>
|
||||
|
||||
<html:div id="sidebar-panel-computedview" class="devtools-monospace theme-sidebar inspector-tabpanel">
|
||||
<html:div id="sidebar-panel-computedview" class="devtools-monospace theme-sidebar inspector-tabpanel"
|
||||
data-localization-bundle="devtools/locale/inspector.properties">
|
||||
<html:div id="computedview-toolbar" class="devtools-toolbar">
|
||||
<html:div class="devtools-searchbox has-clear-btn">
|
||||
<html:input id="computedview-searchbox"
|
||||
class="devtools-filterinput devtools-rule-searchbox"
|
||||
type="search"
|
||||
placeholder="&filterStylesPlaceholder;"/>
|
||||
data-localization="placeholder=inspector.filterStyles.placeholder"/>
|
||||
<html:button id="computedview-searchinput-clear" class="devtools-searchinput-clear"></html:button>
|
||||
</html:div>
|
||||
<html:label id="browser-style-checkbox-label" for="browser-style-checkbox">
|
||||
<html:input id="browser-style-checkbox"
|
||||
type="checkbox"
|
||||
class="includebrowserstyles"
|
||||
label="&browserStylesLabel;"/>&browserStylesLabel;</html:label>
|
||||
<html:input id="browser-style-checkbox"
|
||||
type="checkbox"
|
||||
class="includebrowserstyles"/>
|
||||
<html:label id="browser-style-checkbox-label" for="browser-style-checkbox"
|
||||
data-localization="content=inspector.browserStyles.label"/>
|
||||
</html:div>
|
||||
|
||||
<html:div id="computedview-container">
|
||||
<html:div id="computedview-container-focusable" tabindex="-1">
|
||||
<html:div id="boxmodel-wrapper" tabindex="0">
|
||||
<html:div id="boxmodel-wrapper" tabindex="0"
|
||||
data-localization-bundle="devtools/locale/boxmodel.properties">
|
||||
<html:div id="boxmodel-header">
|
||||
<html:div id="boxmodel-expander" class="expander theme-twisty expandable" open=""></html:div>
|
||||
<html:span>&layoutViewTitle;</html:span>
|
||||
<html:span data-localization="content=boxmodel.title"/>
|
||||
</html:div>
|
||||
|
||||
<html:div id="boxmodel-container">
|
||||
<html:div id="boxmodel-main">
|
||||
<html:span class="boxmodel-legend" data-box="margin" title="&margin.tooltip;">&margin.tooltip;</html:span>
|
||||
<html:div id="boxmodel-margins" data-box="margin" title="&margin.tooltip;">
|
||||
<html:span class="boxmodel-legend" data-box="border" title="&border.tooltip;">&border.tooltip;</html:span>
|
||||
<html:div id="boxmodel-borders" data-box="border" title="&border.tooltip;">
|
||||
<html:span class="boxmodel-legend" data-box="padding" title="&padding.tooltip;">&padding.tooltip;</html:span>
|
||||
<html:div id="boxmodel-padding" data-box="padding" title="&padding.tooltip;">
|
||||
<html:div id="boxmodel-content" data-box="content" title="&content.tooltip;">
|
||||
<html:span class="boxmodel-legend" data-box="margin" data-localization="content=boxmodel.margin;title=boxmodel.margin"/>
|
||||
<html:div id="boxmodel-margins" data-box="margin" data-localization="title=boxmodel.margin">
|
||||
<html:span class="boxmodel-legend" data-box="border" data-localization="content=boxmodel.border;title=boxmodel.border"/>
|
||||
<html:div id="boxmodel-borders" data-box="border" data-localization="title=boxmodel.border">
|
||||
<html:span class="boxmodel-legend" data-box="padding" data-localization="content=boxmodel.padding;title=boxmodel.padding"/>
|
||||
<html:div id="boxmodel-padding" data-box="padding" data-localization="title=boxmodel.padding">
|
||||
<html:div id="boxmodel-content" data-box="content" data-localization="title=boxmodel.content">
|
||||
</html:div>
|
||||
</html:div>
|
||||
</html:div>
|
||||
@ -151,13 +157,16 @@
|
||||
<html:p class="boxmodel-padding boxmodel-bottom"><html:span data-box="padding" class="boxmodel-editable" title="padding-bottom"></html:span></html:p>
|
||||
<html:p class="boxmodel-padding boxmodel-left"><html:span data-box="padding" class="boxmodel-editable" title="padding-left"></html:span></html:p>
|
||||
|
||||
<html:p class="boxmodel-size"><html:span data-box="content" title="&content.tooltip;"></html:span></html:p>
|
||||
<html:p class="boxmodel-size">
|
||||
<html:span data-box="content" data-localization="title=boxmodel.content"></html:span>
|
||||
</html:p>
|
||||
</html:div>
|
||||
|
||||
<html:div id="boxmodel-info">
|
||||
<html:span id="boxmodel-element-size"></html:span>
|
||||
<html:section id="boxmodel-position-group">
|
||||
<html:button class="devtools-button" id="layout-geometry-editor" title="&geometry.button.tooltip;"></html:button>
|
||||
<html:button class="devtools-button" id="layout-geometry-editor"
|
||||
data-localization="title=boxmodel.geometryButton.tooltip"></html:button>
|
||||
<html:span id="boxmodel-element-position"></html:span>
|
||||
</html:section>
|
||||
</html:div>
|
||||
@ -171,22 +180,21 @@
|
||||
<html:div id="propertyContainer" class="theme-separator" tabindex="0">
|
||||
</html:div>
|
||||
|
||||
<html:div id="computedview-no-results" hidden="">
|
||||
&noPropertiesFound;
|
||||
</html:div>
|
||||
<html:div id="computedview-no-results" hidden="" data-localization="content=inspector.noProperties"/>
|
||||
</html:div>
|
||||
</html:div>
|
||||
</html:div>
|
||||
|
||||
<html:div id="sidebar-panel-fontinspector" class="devtools-monospace theme-sidebar inspector-tabpanel">
|
||||
<html:div id="sidebar-panel-fontinspector" class="devtools-monospace theme-sidebar inspector-tabpanel"
|
||||
data-localization-bundle="devtools/locale/font-inspector.properties">
|
||||
<html:div class="devtools-toolbar">
|
||||
<html:div class="devtools-searchbox">
|
||||
<html:input id="font-preview-text-input"
|
||||
class="devtools-textinput"
|
||||
type="search"
|
||||
placeholder="&previewHint;"/>
|
||||
<html:input id="font-preview-text-input" class="devtools-textinput" type="search"
|
||||
data-localization="placeholder=fontinspector.previewText"/>
|
||||
</html:div>
|
||||
<html:label id="font-showall" class="theme-link" title="&showAllFonts;">&showAllFontsUsed;</html:label>
|
||||
<html:label id="font-showall" class="theme-link"
|
||||
data-localization="content=fontinspector.seeAll;
|
||||
title=fontinspector.seeAll.tooltip"/>
|
||||
</html:div>
|
||||
|
||||
<html:div id="font-container">
|
||||
@ -200,13 +208,15 @@
|
||||
</html:div>
|
||||
<html:div class="font-info">
|
||||
<html:h1 class="font-name"></html:h1>
|
||||
<html:span class="font-is-local">&system;</html:span>
|
||||
<html:span class="font-is-remote">&remote;</html:span>
|
||||
<html:span class="font-is-local" data-localization="content=fontinspector.system"/>
|
||||
<html:span class="font-is-remote" data-localization="content=fontinspector.remote"/>
|
||||
<html:p class="font-format-url">
|
||||
<html:input readonly="readonly" class="font-url"></html:input>
|
||||
<html:span class="font-format"></html:span>
|
||||
</html:p>
|
||||
<html:p class="font-css">&usedAs; "<html:span class="font-css-name"></html:span>"</html:p>
|
||||
<html:p class="font-css">
|
||||
<html:span data-localization="content=fontinspector.usedAs"/> "<html:span class="font-css-name"></html:span>"
|
||||
</html:p>
|
||||
<html:pre class="font-css-code"></html:pre>
|
||||
</html:div>
|
||||
</html:section>
|
||||
@ -218,5 +228,6 @@
|
||||
</html:div>
|
||||
</html:div>
|
||||
|
||||
</box>
|
||||
</window>
|
||||
</html:div>
|
||||
</body>
|
||||
</html>
|
@ -16,6 +16,7 @@ DevToolsModules(
|
||||
'inspector-commands.js',
|
||||
'inspector-panel.js',
|
||||
'inspector-search.js',
|
||||
'inspector.xhtml',
|
||||
'toolsidebar.js',
|
||||
)
|
||||
|
||||
|
@ -38,6 +38,7 @@ add_task(function* () {
|
||||
"Should have cancelled creating a new text property.");
|
||||
ok(!elementRuleEditor.propertyList.hasChildNodes(),
|
||||
"Should not have any properties.");
|
||||
is(view.styleDocument.activeElement, view.styleDocument.documentElement,
|
||||
|
||||
is(view.styleDocument.activeElement, view.styleDocument.body,
|
||||
"Correct element has focus");
|
||||
});
|
||||
|
@ -23,7 +23,7 @@ add_task(function* () {
|
||||
info("Test creating a new property and escaping");
|
||||
yield addProperty(view, 1, "color", "red", "VK_ESCAPE", false);
|
||||
|
||||
is(view.styleDocument.documentElement, view.styleDocument.activeElement,
|
||||
is(view.styleDocument.activeElement, view.styleDocument.body,
|
||||
"Correct element has focus");
|
||||
|
||||
let elementRuleEditor = getRuleViewRuleEditor(view, 1);
|
||||
|
@ -38,6 +38,6 @@ add_task(function* () {
|
||||
|
||||
is(elementRuleEditor.rule.textProps.length, 1,
|
||||
"Should have canceled creating a new text property.");
|
||||
is(view.styleDocument.documentElement, view.styleDocument.activeElement,
|
||||
is(view.styleDocument.activeElement, view.styleDocument.body,
|
||||
"Correct element has focus");
|
||||
});
|
||||
|
@ -51,7 +51,7 @@ add_task(function* () {
|
||||
let onHidden = cPicker.tooltip.once("hidden");
|
||||
// Validating the color change ends up updating the rule view twice
|
||||
let onRuleViewChanged = waitForNEvents(view, "ruleview-changed", 2);
|
||||
EventUtils.sendKey("RETURN", spectrum.element.ownerDocument.defaultView);
|
||||
focusAndSendKey(spectrum.element.ownerDocument.defaultView, "RETURN");
|
||||
yield onHidden;
|
||||
yield onRuleViewChanged;
|
||||
|
||||
|
@ -56,7 +56,7 @@ function* basicTest(view, name, result) {
|
||||
let onHidden = cPicker.tooltip.once("hidden");
|
||||
// Validating the color change ends up updating the rule view twice
|
||||
let onRuleViewChanged = waitForNEvents(view, "ruleview-changed", 2);
|
||||
EventUtils.sendKey("RETURN", spectrum.element.ownerDocument.defaultView);
|
||||
focusAndSendKey(spectrum.element.ownerDocument.defaultView, "RETURN");
|
||||
yield onHidden;
|
||||
yield onRuleViewChanged;
|
||||
|
||||
|
@ -48,7 +48,7 @@ function* testImageTooltipAfterColorChange(swatch, url, ruleView) {
|
||||
let spectrum = picker.spectrum;
|
||||
let onHidden = picker.tooltip.once("hidden");
|
||||
let onModifications = ruleView.once("ruleview-changed");
|
||||
EventUtils.sendKey("RETURN", spectrum.element.ownerDocument.defaultView);
|
||||
focusAndSendKey(spectrum.element.ownerDocument.defaultView, "RETURN");
|
||||
yield onHidden;
|
||||
yield onModifications;
|
||||
|
||||
|
@ -46,7 +46,7 @@ function* testColorChangeIsntRevertedWhenOtherTooltipIsShown(ruleView) {
|
||||
|
||||
let onModifications = waitForNEvents(ruleView, "ruleview-changed", 2);
|
||||
let onHidden = picker.tooltip.once("hidden");
|
||||
EventUtils.sendKey("RETURN", spectrum.element.ownerDocument.defaultView);
|
||||
focusAndSendKey(spectrum.element.ownerDocument.defaultView, "RETURN");
|
||||
yield onHidden;
|
||||
yield onModifications;
|
||||
|
||||
|
@ -47,7 +47,7 @@ function* testPressingEnterCommitsChanges(swatch, ruleView) {
|
||||
let onModified = ruleView.once("ruleview-changed");
|
||||
let spectrum = cPicker.spectrum;
|
||||
let onHidden = cPicker.tooltip.once("hidden");
|
||||
EventUtils.sendKey("RETURN", spectrum.element.ownerDocument.defaultView);
|
||||
focusAndSendKey(spectrum.element.ownerDocument.defaultView, "RETURN");
|
||||
yield onHidden;
|
||||
yield onModified;
|
||||
|
||||
|
@ -53,7 +53,7 @@ function* testPressingEnterCommitsChanges(swatch, ruleView) {
|
||||
// Pressing RETURN ends up doing 2 rule-view updates, one for the preview and
|
||||
// one for the commit when the tooltip closes.
|
||||
let onRuleViewChanged = waitForNEvents(ruleView, "ruleview-changed", 2);
|
||||
EventUtils.sendKey("RETURN", widget.parent.ownerDocument.defaultView);
|
||||
focusAndSendKey(widget.parent.ownerDocument.defaultView, "RETURN");
|
||||
yield onRuleViewChanged;
|
||||
|
||||
let style = yield getComputedStyleProperty("body", null,
|
||||
|
@ -94,7 +94,7 @@ function* escapeTooltip(view) {
|
||||
let widget = yield bezierTooltip.widget;
|
||||
let onHidden = bezierTooltip.tooltip.once("hidden");
|
||||
let onModifications = view.once("ruleview-changed");
|
||||
EventUtils.sendKey("ESCAPE", widget.parent.ownerDocument.defaultView);
|
||||
focusAndSendKey(widget.parent.ownerDocument.defaultView, "ESCAPE");
|
||||
yield onHidden;
|
||||
yield onModifications;
|
||||
}
|
||||
|
@ -64,6 +64,8 @@ add_task(function* () {
|
||||
tooltip.hide();
|
||||
yield onHidden;
|
||||
ok(!tooltip.isVisible(), "color picker tooltip is closed");
|
||||
|
||||
yield waitForTick();
|
||||
});
|
||||
|
||||
function* testESC(swatch, inspector, testActor) {
|
||||
|
@ -29,4 +29,6 @@ add_task(function* () {
|
||||
"The inplace editor wasn't shown as a result of the filter swatch click");
|
||||
|
||||
yield hideTooltipAndWaitForRuleViewChanged(filterTooltip, view);
|
||||
|
||||
yield waitForTick();
|
||||
});
|
||||
|
@ -28,6 +28,13 @@ add_task(function* () {
|
||||
let cmdPaste = searchContextMenu.querySelector("[command=cmd_paste]");
|
||||
|
||||
info("Opening context menu");
|
||||
|
||||
emptyClipboard();
|
||||
|
||||
let onFocus = once(searchField, "focus");
|
||||
searchField.focus();
|
||||
yield onFocus;
|
||||
|
||||
let onContextMenuPopup = once(searchContextMenu, "popupshowing");
|
||||
EventUtils.synthesizeMouse(searchField, 2, 2,
|
||||
{type: "contextmenu", button: 2}, win);
|
||||
@ -35,10 +42,17 @@ add_task(function* () {
|
||||
|
||||
is(cmdUndo.getAttribute("disabled"), "true", "cmdUndo is disabled");
|
||||
is(cmdDelete.getAttribute("disabled"), "true", "cmdDelete is disabled");
|
||||
is(cmdSelectAll.getAttribute("disabled"), "", "cmdSelectAll is enabled");
|
||||
is(cmdCut.getAttribute("disabled"), "true", "cmdCut is disabled");
|
||||
is(cmdCopy.getAttribute("disabled"), "true", "cmdCopy is disabled");
|
||||
is(cmdPaste.getAttribute("disabled"), "true", "cmdPaste is disabled");
|
||||
is(cmdSelectAll.getAttribute("disabled"), "true", "cmdSelectAll is disabled");
|
||||
|
||||
// Cut/Copy items are enabled in context menu even if there
|
||||
// is no selection. See also Bug 1303033
|
||||
is(cmdCut.getAttribute("disabled"), "", "cmdCut is enabled");
|
||||
is(cmdCopy.getAttribute("disabled"), "", "cmdCopy is enabled");
|
||||
|
||||
if (isWindows()) {
|
||||
// emptyClipboard only works on Windows (666254), assert paste only for this OS.
|
||||
is(cmdPaste.getAttribute("disabled"), "true", "cmdPaste is disabled");
|
||||
}
|
||||
|
||||
info("Closing context menu");
|
||||
let onContextMenuHidden = once(searchContextMenu, "popuphidden");
|
||||
|
@ -58,7 +58,7 @@ const TEST_DATA = [
|
||||
];
|
||||
|
||||
add_task(function* () {
|
||||
requestLongerTimeout(2);
|
||||
requestLongerTimeout(4);
|
||||
|
||||
info("Starting the test with the pref set to true before toolbox is opened");
|
||||
yield setUserAgentStylesPref(true);
|
||||
|
@ -634,7 +634,9 @@ var togglePropStatus = Task.async(function* (view, textProp) {
|
||||
*/
|
||||
var focusNewRuleViewProperty = Task.async(function* (ruleEditor) {
|
||||
info("Clicking on a close ruleEditor brace to start editing a new property");
|
||||
ruleEditor.closeBrace.scrollIntoView();
|
||||
|
||||
// Use bottom alignment to avoid scrolling out of the parent element area.
|
||||
ruleEditor.closeBrace.scrollIntoView(false);
|
||||
let editor = yield focusEditableField(ruleEditor.ruleView,
|
||||
ruleEditor.closeBrace);
|
||||
|
||||
@ -814,3 +816,13 @@ function waitForStyleModification(inspector) {
|
||||
inspector.on("markupmutation", checkForStyleModification);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure window is properly focused before sending a key event.
|
||||
* @param {Window} win
|
||||
* @param {Event} key
|
||||
*/
|
||||
function focusAndSendKey(win, key) {
|
||||
win.document.documentElement.focus();
|
||||
EventUtils.sendKey(key, win);
|
||||
}
|
||||
|
@ -130,7 +130,6 @@ subsuite = clipboard
|
||||
[browser_inspector_pane-toggle-01.js]
|
||||
[browser_inspector_pane-toggle-02.js]
|
||||
[browser_inspector_pane-toggle-03.js]
|
||||
[browser_inspector_pane-toggle-04.js]
|
||||
[browser_inspector_pane-toggle-05.js]
|
||||
skip-if = os == "mac" # Full keyboard navigation on OSX only works if Full Keyboard Access setting is set to All Control in System Keyboard
|
||||
[browser_inspector_picker-stop-on-destroy.js]
|
||||
|
@ -32,6 +32,10 @@ const NODES = [
|
||||
];
|
||||
|
||||
add_task(function* () {
|
||||
// This test needs specific initial size of the sidebar.
|
||||
yield pushPref("devtools.toolsidebar-width.inspector", 350);
|
||||
yield pushPref("devtools.toolsidebar-height.inspector", 150);
|
||||
|
||||
let { inspector, toolbox } = yield openInspectorForURL(TEST_URI);
|
||||
|
||||
// No way to wait for scrolling to end (Bug 1172171)
|
||||
|
@ -11,7 +11,8 @@ add_task(function* () {
|
||||
info("Open the inspector in a side toolbox host");
|
||||
let {toolbox, inspector} = yield openInspectorForURL("about:blank", "side");
|
||||
|
||||
let panel = inspector.panelDoc.querySelector("#inspector-sidebar-container");
|
||||
let panel = inspector.panelDoc.querySelector("#inspector-splitter-box .controlled");
|
||||
|
||||
let button = inspector.panelDoc.querySelector(".sidebar-toggle");
|
||||
ok(!panel.classList.contains("pane-collapsed"), "The panel is in expanded state");
|
||||
|
||||
|
@ -10,7 +10,7 @@ add_task(function* () {
|
||||
let {inspector} = yield openInspectorForURL("about:blank");
|
||||
|
||||
let button = inspector.panelDoc.querySelector(".sidebar-toggle");
|
||||
let panel = inspector.panelDoc.querySelector("#inspector-sidebar-container");
|
||||
let panel = inspector.panelDoc.querySelector("#inspector-splitter-box .controlled");
|
||||
|
||||
ok(!button.classList.contains("pane-collapsed"), "The button is in expanded state");
|
||||
|
||||
|
@ -1,65 +0,0 @@
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
let { Toolbox } = require("devtools/client/framework/toolbox");
|
||||
|
||||
// Test that the dimensions of the collapsed inspector panel are not modified
|
||||
// when switching from horizontal to vertical layout, which is mandatory to make
|
||||
// sure the panel remains visually hidden (using negative margins).
|
||||
|
||||
add_task(function* () {
|
||||
info("Set temporary preferences to ensure a small sidebar width.");
|
||||
yield new Promise(resolve => {
|
||||
let options = {"set": [
|
||||
["devtools.toolsidebar-width.inspector", 200]
|
||||
]};
|
||||
SpecialPowers.pushPrefEnv(options, resolve);
|
||||
});
|
||||
|
||||
let { inspector, toolbox } = yield openInspectorForURL("about:blank");
|
||||
let button = inspector.panelDoc.querySelector(".sidebar-toggle");
|
||||
let panel = inspector.panelDoc.querySelector("#inspector-sidebar-container");
|
||||
|
||||
info("Changing toolbox host to a window.");
|
||||
yield toolbox.switchHost(Toolbox.HostType.WINDOW);
|
||||
|
||||
let hostWindow = toolbox._host._window;
|
||||
let originalWidth = hostWindow.outerWidth;
|
||||
let originalHeight = hostWindow.outerHeight;
|
||||
|
||||
info("Resizing window to switch to the horizontal layout.");
|
||||
hostWindow.resizeTo(800, 300);
|
||||
|
||||
// Check the sidebar is expanded when the test starts.
|
||||
ok(!panel.classList.contains("pane-collapsed"), "The panel is in expanded state");
|
||||
|
||||
info("Collapse the inspector sidebar.");
|
||||
let onTransitionEnd = once(panel, "transitionend");
|
||||
EventUtils.synthesizeMouseAtCenter(button, {},
|
||||
inspector.panelDoc.defaultView);
|
||||
yield onTransitionEnd;
|
||||
|
||||
ok(panel.classList.contains("pane-collapsed"), "The panel is in collapsed state");
|
||||
let currentPanelHeight = panel.getBoundingClientRect().height;
|
||||
let currentPanelMarginBottom = panel.style.marginBottom;
|
||||
|
||||
info("Resizing window to switch to the vertical layout.");
|
||||
hostWindow.resizeTo(300, 800);
|
||||
|
||||
// Check the panel is collapsed, and still has the same dimensions.
|
||||
ok(panel.classList.contains("pane-collapsed"), "The panel is still collapsed");
|
||||
is(panel.getBoundingClientRect().height, currentPanelHeight,
|
||||
"The panel height has not been modified when changing the layout.");
|
||||
is(panel.style.marginBottom, currentPanelMarginBottom,
|
||||
"The panel margin-bottom has not been modified when changing the layout.");
|
||||
|
||||
info("Restoring window original size.");
|
||||
hostWindow.resizeTo(originalWidth, originalHeight);
|
||||
});
|
||||
|
||||
registerCleanupFunction(function () {
|
||||
// Restore the host type for other tests.
|
||||
Services.prefs.clearUserPref("devtools.toolbox.host");
|
||||
});
|
@ -10,7 +10,8 @@
|
||||
|
||||
add_task(function* () {
|
||||
let {inspector} = yield openInspectorForURL("about:blank", "side");
|
||||
let panel = inspector.panelDoc.querySelector("#inspector-sidebar-container");
|
||||
let panel = inspector.panelDoc.querySelector("#inspector-splitter-box .controlled");
|
||||
|
||||
let button = inspector.panelDoc.querySelector(".sidebar-toggle");
|
||||
|
||||
ok(!panel.classList.contains("pane-collapsed"), "The panel is in expanded state");
|
||||
|
@ -26,7 +26,13 @@ add_task(function* () {
|
||||
let cmdCopy = searchContextMenu.querySelector("[command=cmd_copy]");
|
||||
let cmdPaste = searchContextMenu.querySelector("[command=cmd_paste]");
|
||||
|
||||
emptyClipboard();
|
||||
|
||||
info("Opening context menu");
|
||||
let onFocus = once(searchBox, "focus");
|
||||
searchBox.focus();
|
||||
yield onFocus;
|
||||
|
||||
let onContextMenuPopup = once(searchContextMenu, "popupshowing");
|
||||
EventUtils.synthesizeMouse(searchBox, 2, 2,
|
||||
{type: "contextmenu", button: 2}, win);
|
||||
@ -34,10 +40,17 @@ add_task(function* () {
|
||||
|
||||
is(cmdUndo.getAttribute("disabled"), "true", "cmdUndo is disabled");
|
||||
is(cmdDelete.getAttribute("disabled"), "true", "cmdDelete is disabled");
|
||||
is(cmdSelectAll.getAttribute("disabled"), "", "cmdSelectAll is enabled");
|
||||
is(cmdCut.getAttribute("disabled"), "true", "cmdCut is disabled");
|
||||
is(cmdCopy.getAttribute("disabled"), "true", "cmdCopy is disabled");
|
||||
is(cmdPaste.getAttribute("disabled"), "true", "cmdPaste is disabled");
|
||||
is(cmdSelectAll.getAttribute("disabled"), "true", "cmdSelectAll is disabled");
|
||||
|
||||
// Cut/Copy items are enabled in context menu even if there
|
||||
// is no selection. See also Bug 1303033
|
||||
is(cmdCut.getAttribute("disabled"), "", "cmdCut is enabled");
|
||||
is(cmdCopy.getAttribute("disabled"), "", "cmdCopy is enabled");
|
||||
|
||||
if (isWindows()) {
|
||||
// emptyClipboard only works on Windows (666254), assert paste only for this OS.
|
||||
is(cmdPaste.getAttribute("disabled"), "true", "cmdPaste is disabled");
|
||||
}
|
||||
|
||||
info("Closing context menu");
|
||||
let onContextMenuHidden = once(searchContextMenu, "popuphidden");
|
||||
@ -47,6 +60,7 @@ add_task(function* () {
|
||||
info("Copy text in search field using the context menu");
|
||||
searchBox.value = TEST_INPUT;
|
||||
searchBox.select();
|
||||
searchBox.focus();
|
||||
EventUtils.synthesizeMouse(searchBox, 2, 2,
|
||||
{type: "contextmenu", button: 2}, win);
|
||||
yield onContextMenuPopup;
|
||||
|
@ -66,12 +66,7 @@ ToolSidebar.prototype = {
|
||||
},
|
||||
|
||||
get InspectorTabPanel() {
|
||||
if (!this._InspectorTabPanel) {
|
||||
this._InspectorTabPanel =
|
||||
this.React.createFactory(this.browserRequire(
|
||||
"devtools/client/inspector/components/inspector-tab-panel"));
|
||||
}
|
||||
return this._InspectorTabPanel;
|
||||
return this._toolPanel.InspectorTabPanel;
|
||||
},
|
||||
|
||||
// Rendering
|
||||
@ -90,7 +85,14 @@ ToolSidebar.prototype = {
|
||||
},
|
||||
|
||||
addExistingTab: function (id, title, selected) {
|
||||
this._tabbar.addTab(id, title, selected, this.InspectorTabPanel);
|
||||
let panel = this.InspectorTabPanel({
|
||||
id: id,
|
||||
idPrefix: this.TABPANEL_ID_PREFIX,
|
||||
key: id,
|
||||
title: title,
|
||||
});
|
||||
|
||||
this._tabbar.addTab(id, title, selected, panel);
|
||||
|
||||
this.emit("new-tab-registered", id);
|
||||
},
|
||||
@ -105,6 +107,7 @@ ToolSidebar.prototype = {
|
||||
addFrameTab: function (id, title, url, selected) {
|
||||
let panel = this.InspectorTabPanel({
|
||||
id: id,
|
||||
idPrefix: this.TABPANEL_ID_PREFIX,
|
||||
key: id,
|
||||
title: title,
|
||||
url: url,
|
||||
|
@ -118,7 +118,7 @@ devtools.jar:
|
||||
content/framework/dev-edition-promo/dev-edition-promo.xul (framework/dev-edition-promo/dev-edition-promo.xul)
|
||||
* content/framework/dev-edition-promo/dev-edition-promo.css (framework/dev-edition-promo/dev-edition-promo.css)
|
||||
content/framework/dev-edition-promo/dev-edition-logo.png (framework/dev-edition-promo/dev-edition-logo.png)
|
||||
content/inspector/inspector.xul (inspector/inspector.xul)
|
||||
content/inspector/inspector.xhtml (inspector/inspector.xhtml)
|
||||
content/framework/connect/connect.xhtml (framework/connect/connect.xhtml)
|
||||
content/framework/connect/connect.css (framework/connect/connect.css)
|
||||
content/framework/connect/connect.js (framework/connect/connect.js)
|
||||
|
37
devtools/client/locales/en-US/boxmodel.properties
Normal file
37
devtools/client/locales/en-US/boxmodel.properties
Normal file
@ -0,0 +1,37 @@
|
||||
# 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/.
|
||||
|
||||
# LOCALIZATION NOTE : FILE This file contains the Layout View strings.
|
||||
# The Layout View is a panel displayed in the computed view tab of the Inspector sidebar.
|
||||
|
||||
# LOCALIZATION NOTE : FILE The correct localization of this file might be to
|
||||
# keep it in English, or another language commonly spoken among web developers.
|
||||
# You want to make that choice consistent across the developer tools.
|
||||
# A good criteria is the language in which you'd find the best
|
||||
# documentation on web development on the web.
|
||||
|
||||
# LOCALIZATION NOTE (boxmodel.title) This is the title of the box model panel and is
|
||||
# displayed as a label.
|
||||
boxmodel.title=Box Model
|
||||
|
||||
# LOCALIZATION NOTE (boxmodel.margin) This refers to the margin in the box model and
|
||||
# might be displayed as a label or as a tooltip.
|
||||
boxmodel.margin=margin
|
||||
|
||||
# LOCALIZATION NOTE (boxmodel.border) This refers to the border in the box model and
|
||||
# might be displayed as a label or as a tooltip.
|
||||
boxmodel.border=border
|
||||
|
||||
# LOCALIZATION NOTE (boxmodel.padding) This refers to the padding in the box model and
|
||||
# might be displayed as a label or as a tooltip.
|
||||
boxmodel.padding=padding
|
||||
|
||||
# LOCALIZATION NOTE (boxmodel.content) This refers to the content in the box model and
|
||||
# might be displayed as a label or as a tooltip.
|
||||
boxmodel.content=content
|
||||
|
||||
# LOCALIZATION NOTE: (boxmodel.geometryButton.tooltip) This label is displayed as a
|
||||
# tooltip that appears when hovering over the button that allows users to edit the
|
||||
# position of an element in the page.
|
||||
boxmodel.geometryButton.tooltip=Edit position
|
@ -197,13 +197,9 @@
|
||||
- control the stepping commands in the debugger (continue, step over,
|
||||
- step in and step out). -->
|
||||
<!ENTITY debuggerUI.stepping.resume1 "VK_F8">
|
||||
<!ENTITY debuggerUI.stepping.resume2 "VK_SLASH">
|
||||
<!ENTITY debuggerUI.stepping.stepOver1 "VK_F10">
|
||||
<!ENTITY debuggerUI.stepping.stepOver2 "VK_QUOTE">
|
||||
<!ENTITY debuggerUI.stepping.stepIn1 "VK_F11">
|
||||
<!ENTITY debuggerUI.stepping.stepIn2 "VK_SEMICOLON">
|
||||
<!ENTITY debuggerUI.stepping.stepOut1 "VK_F11">
|
||||
<!ENTITY debuggerUI.stepping.stepOut2 "VK_SEMICOLON">
|
||||
|
||||
<!-- LOCALIZATION NOTE (debuggerUI.context.newTab): This is the label
|
||||
- for the Open in New Tab menu item displayed in the context menu of the
|
||||
|
@ -1,16 +0,0 @@
|
||||
<!-- 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/. -->
|
||||
|
||||
<!-- LOCALIZATION NOTE : FILE This file contains the Font Inspector strings.
|
||||
- The Font Inspector is the panel accessible in the Inspector sidebar. -->
|
||||
|
||||
<!ENTITY showAllFonts "See all the fonts used in the page">
|
||||
<!ENTITY showAllFontsUsed "Show all fonts used">
|
||||
<!ENTITY usedAs "Used as: ">
|
||||
<!ENTITY system "system">
|
||||
<!ENTITY remote "remote">
|
||||
|
||||
<!-- LOCALIZATION NOTE (previewHint): This is the label shown as the
|
||||
placeholder in font inspector preview text box. -->
|
||||
<!ENTITY previewHint "Preview Text">
|
29
devtools/client/locales/en-US/font-inspector.properties
Normal file
29
devtools/client/locales/en-US/font-inspector.properties
Normal file
@ -0,0 +1,29 @@
|
||||
# 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/.
|
||||
|
||||
# LOCALIZATION NOTE This file contains the Font Inspector strings.
|
||||
# The Font Inspector is a panel accessible in the Inspector sidebar.
|
||||
|
||||
# LOCALIZATION NOTE (fontinspector.seeAll) This is the label of a link that will show all
|
||||
# the fonts used in the page, instead of the ones related to the inspected element.
|
||||
fontinspector.seeAll=Show all fonts used
|
||||
|
||||
# LOCALIZATION NOTE (fontinspector.seeAll.tooltip) see fontinspector.seeAll.
|
||||
fontinspector.seeAll.tooltip=See all the fonts used in the page
|
||||
|
||||
# LOCALIZATION NOTE (fontinspector.usedAs) This label introduces the name used to refer to
|
||||
# the font in a stylesheet.
|
||||
fontinspector.usedAs=Used as:
|
||||
|
||||
# LOCALIZATION NOTE (fontinspector.system) This label indicates that the font is a local
|
||||
# system font.
|
||||
fontinspector.system=system
|
||||
|
||||
# LOCALIZATION NOTE (fontinspector.remote) This label indicates that the font is a remote
|
||||
# font.
|
||||
fontinspector.remote=remote
|
||||
|
||||
# LOCALIZATION NOTE (previewHint):
|
||||
# This is the label shown as the placeholder in font inspector preview text box.
|
||||
fontinspector.previewText=Preview Text
|
@ -1,27 +0,0 @@
|
||||
<!-- LOCALIZATION NOTE (inspectorRemoveAttribute.label): This is the label shown in
|
||||
the inspector contextual-menu for the item that lets users delete attribute
|
||||
from current node -->
|
||||
<!ENTITY inspectorRemoveAttribute.label "Remove Attribute">
|
||||
<!ENTITY inspectorRemoveAttribute.accesskey "R">
|
||||
|
||||
<!ENTITY inspector.selectButton.tooltip "Select element with mouse">
|
||||
|
||||
<!-- LOCALIZATION NOTE (inspectorSearchHTML.label3): This is the label that is
|
||||
shown as the placeholder for the markup view search in the inspector. -->
|
||||
<!ENTITY inspectorSearchHTML.label3 "Search HTML">
|
||||
|
||||
<!-- LOCALIZATION NOTE (inspectorAddNode.label): This is the label shown in
|
||||
the inspector toolbar for the button that lets users add elements to the
|
||||
DOM (as children of the currently selected element). -->
|
||||
<!ENTITY inspectorAddNode.label "Create New Node">
|
||||
<!ENTITY inspectorAddNode.accesskey "C">
|
||||
|
||||
|
||||
<!-- LOCALIZATION NOTE (inspectorEyeDropper.label): A string displayed as the tooltip of
|
||||
a button in the inspector which toggles the Eyedropper tool -->
|
||||
<!ENTITY inspectorEyeDropper.label "Grab a color from the page">
|
||||
|
||||
<!-- LOCALIZATION NOTE (inspectorBreadcrumbsGroup): A string visible only to a
|
||||
screen reader and is used to label (using aria-label attribute) a container
|
||||
for inspector breadcrumbs -->
|
||||
<!ENTITY inspectorBreadcrumbsGroup "Breadcrumbs">
|
@ -313,12 +313,39 @@ inspector.sidebar.ruleViewTitle=Rules
|
||||
# used in the page.
|
||||
inspector.sidebar.computedViewTitle=Computed
|
||||
|
||||
# LOCALIZATION NOTE (inspector.sidebar.layoutViewTitle):
|
||||
# This is the title shown in a tab in the side panel of the Inspector panel
|
||||
# that corresponds to the tool displaying box model of the selected element.
|
||||
inspector.sidebar.layoutViewTitle=Box Model
|
||||
|
||||
# LOCALIZATION NOTE (inspector.sidebar.animationInspectorTitle):
|
||||
# This is the title shown in a tab in the side panel of the Inspector panel
|
||||
# that corresponds to the tool displaying animations defined in the page.
|
||||
inspector.sidebar.animationInspectorTitle=Animations
|
||||
|
||||
# LOCALIZATION NOTE (inspector.eyedropper.label): A string displayed as the tooltip of
|
||||
# a button in the inspector which toggles the Eyedropper tool
|
||||
inspector.eyedropper.label=Grab a color from the page
|
||||
|
||||
# LOCALIZATION NOTE (inspector.breadcrumbs.label): A string visible only to a screen reader and
|
||||
# is used to label (using aria-label attribute) a container for inspector breadcrumbs
|
||||
inspector.breadcrumbs.label=Breadcrumbs
|
||||
|
||||
# LOCALIZATION NOTE (inspector.browserStyles.label): This is the label for the checkbox
|
||||
# that specifies whether the styles that are not from the user's stylesheet should be
|
||||
# displayed or not.
|
||||
inspector.browserStyles.label=Browser styles
|
||||
|
||||
# LOCALIZATION NOTE (inspector.filterStyles.placeholder): This is the placeholder that
|
||||
# goes in the search box when no search term has been entered.
|
||||
inspector.filterStyles.placeholder=Filter Styles
|
||||
|
||||
# LOCALIZATION NOTE (inspector.addRule.tooltip): This is the tooltip shown when
|
||||
# hovering the `Add new rule` button in the rules view toolbar. This should
|
||||
# match ruleView.contextmenu.addNewRule in styleinspector.properties
|
||||
inspector.addRule.tooltip=Add new rule
|
||||
|
||||
# LOCALIZATION NOTE (inspector.togglePseudo.tooltip): This is the tooltip
|
||||
# shown when hovering over the `Toggle Pseudo Class Panel` button in the
|
||||
# rule view toolbar.
|
||||
inspector.togglePseudo.tooltip=Toggle pseudo-classes
|
||||
|
||||
# LOCALIZATION NOTE (inspector.noProperties): In the case where there are no CSS
|
||||
# properties to display e.g. due to search criteria this message is
|
||||
# displayed.
|
||||
inspector.noProperties=No CSS properties found.
|
||||
|
@ -1,28 +0,0 @@
|
||||
<!-- 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/. -->
|
||||
|
||||
<!-- LOCALIZATION NOTE : FILE This file contains the Layout View strings.
|
||||
- The Layout View is the panel accessible at the bottom of the Inspector
|
||||
- sidebar. -->
|
||||
|
||||
<!-- LOCALIZATION NOTE : FILE The correct localization of this file might be to
|
||||
- keep it in English, or another language commonly spoken among web developers.
|
||||
- You want to make that choice consistent across the developer tools.
|
||||
- A good criteria is the language in which you'd find the best
|
||||
- documentation on web development on the web. -->
|
||||
|
||||
<!-- LOCALIZATION NOTE (*.tooltip): These tooltips are not regular tooltips.
|
||||
- The text appears on the bottom right corner of the layout view when
|
||||
- the corresponding box is hovered. -->
|
||||
|
||||
<!ENTITY layoutViewTitle "Box Model">
|
||||
<!ENTITY margin.tooltip "margin">
|
||||
<!ENTITY border.tooltip "border">
|
||||
<!ENTITY padding.tooltip "padding">
|
||||
<!ENTITY content.tooltip "content">
|
||||
|
||||
<!-- LOCALIZATION NOTE: This label is displayed as a tooltip that appears when
|
||||
- hovering over the button that allows users to edit the position of an
|
||||
- element in the page. -->
|
||||
<!ENTITY geometry.button.tooltip "Edit position">
|
@ -1,38 +0,0 @@
|
||||
<!-- 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/. -->
|
||||
|
||||
<!-- LOCALIZATION NOTE : FILE The correct localization of this file might be to
|
||||
- keep it in English, or another language commonly spoken among web developers.
|
||||
- You want to make that choice consistent across the developer tools.
|
||||
- A good criteria is the language in which you'd find the best
|
||||
- documentation on web development on the web. -->
|
||||
|
||||
<!-- LOCALIZATION NOTE (browserStylesLabel): This is the label for the checkbox
|
||||
- that specifies whether the styles that are not from the user's stylesheet
|
||||
- should be displayed or not. -->
|
||||
<!ENTITY browserStylesLabel "Browser styles">
|
||||
|
||||
<!-- LOCALIZATION NOTE (filterStylesPlaceholder): This is the placeholder that goes in
|
||||
- the search box when no search term has been entered. -->
|
||||
<!ENTITY filterStylesPlaceholder "Filter Styles">
|
||||
|
||||
<!-- LOCALIZATION NOTE (addRuleButtonTooltip): This is the tooltip shown when
|
||||
- hovering the `Add new rule` button in the rules view toolbar. This should
|
||||
- match ruleView.contextmenu.addNewRule in styleinspector.properties -->
|
||||
<!ENTITY addRuleButtonTooltip "Add new rule">
|
||||
|
||||
<!-- LOCALIZATION NOTE (selectedElementLabel): This is the label for the path of
|
||||
- the highlighted element in the web page. This path is based on the document
|
||||
- tree. -->
|
||||
<!ENTITY selectedElementLabel "Selected element:">
|
||||
|
||||
<!-- LOCALIZATION NOTE (togglePseudoClassPanel): This is the tooltip
|
||||
- shown when hovering over the `Toggle Pseudo Class Panel` button in the
|
||||
- rule view toolbar. -->
|
||||
<!ENTITY togglePseudoClassPanel "Toggle pseudo-classes">
|
||||
|
||||
<!-- LOCALIZATION NOTE (noPropertiesFound): In the case where there are no CSS
|
||||
- properties to display e.g. due to search criteria this message is
|
||||
- displayed. -->
|
||||
<!ENTITY noPropertiesFound "No CSS properties found.">
|
@ -6,6 +6,7 @@
|
||||
|
||||
DIRS += [
|
||||
'reps',
|
||||
'splitter',
|
||||
'tabs',
|
||||
'tree'
|
||||
]
|
||||
|
54
devtools/client/shared/components/splitter/draggable.js
Normal file
54
devtools/client/shared/components/splitter/draggable.js
Normal file
@ -0,0 +1,54 @@
|
||||
/* 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/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const React = require("devtools/client/shared/vendor/react");
|
||||
const ReactDOM = require("devtools/client/shared/vendor/react-dom");
|
||||
const { DOM: dom, PropTypes } = React;
|
||||
|
||||
const Draggable = React.createClass({
|
||||
displayName: "Draggable",
|
||||
|
||||
propTypes: {
|
||||
onMove: PropTypes.func.isRequired,
|
||||
onStart: PropTypes.func,
|
||||
onStop: PropTypes.func,
|
||||
style: PropTypes.object,
|
||||
className: PropTypes.string
|
||||
},
|
||||
|
||||
startDragging(ev) {
|
||||
ev.preventDefault();
|
||||
const doc = ReactDOM.findDOMNode(this).ownerDocument;
|
||||
doc.addEventListener("mousemove", this.onMove);
|
||||
doc.addEventListener("mouseup", this.onUp);
|
||||
this.props.onStart && this.props.onStart();
|
||||
},
|
||||
|
||||
onMove(ev) {
|
||||
ev.preventDefault();
|
||||
// Use screen coordinates so, moving mouse over iframes
|
||||
// doesn't mangle (relative) coordinates.
|
||||
this.props.onMove(ev.screenX, ev.screenY);
|
||||
},
|
||||
|
||||
onUp(ev) {
|
||||
ev.preventDefault();
|
||||
const doc = ReactDOM.findDOMNode(this).ownerDocument;
|
||||
doc.removeEventListener("mousemove", this.onMove);
|
||||
doc.removeEventListener("mouseup", this.onUp);
|
||||
this.props.onStop && this.props.onStop();
|
||||
},
|
||||
|
||||
render() {
|
||||
return dom.div({
|
||||
style: this.props.style,
|
||||
className: this.props.className,
|
||||
onMouseDown: this.startDragging
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = Draggable;
|
11
devtools/client/shared/components/splitter/moz.build
Normal file
11
devtools/client/shared/components/splitter/moz.build
Normal file
@ -0,0 +1,11 @@
|
||||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
DevToolsModules(
|
||||
'draggable.js',
|
||||
'split-box.css',
|
||||
'split-box.js',
|
||||
)
|
88
devtools/client/shared/components/splitter/split-box.css
Normal file
88
devtools/client/shared/components/splitter/split-box.css
Normal file
@ -0,0 +1,88 @@
|
||||
/* vim:set ts=2 sw=2 sts=2 et: */
|
||||
/* 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/. */
|
||||
|
||||
.split-box {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.split-box.vert {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.split-box.horz {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.split-box > .uncontrolled {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.split-box > .controlled {
|
||||
display: flex;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.split-box > .splitter {
|
||||
background-image: none;
|
||||
border: 0;
|
||||
border-style: solid;
|
||||
border-color: transparent;
|
||||
background-color: var(--theme-splitter-color);
|
||||
background-clip: content-box;
|
||||
position: relative;
|
||||
|
||||
box-sizing: border-box;
|
||||
|
||||
/* Positive z-index positions the splitter on top of its siblings and makes
|
||||
it clickable on both sides. */
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.split-box.vert > .splitter {
|
||||
min-width: calc(var(--devtools-splitter-inline-start-width) +
|
||||
var(--devtools-splitter-inline-end-width) + 1px);
|
||||
|
||||
border-inline-start-width: var(--devtools-splitter-inline-start-width);
|
||||
border-inline-end-width: var(--devtools-splitter-inline-end-width);
|
||||
|
||||
margin-inline-start: calc(-1 * var(--devtools-splitter-inline-start-width) - 1px);
|
||||
margin-inline-end: calc(-1 * var(--devtools-splitter-inline-end-width));
|
||||
|
||||
cursor: ew-resize;
|
||||
}
|
||||
|
||||
.split-box.horz > .splitter {
|
||||
min-height: calc(var(--devtools-splitter-top-width) +
|
||||
var(--devtools-splitter-bottom-width) + 1px);
|
||||
|
||||
border-top-width: var(--devtools-splitter-top-width);
|
||||
border-bottom-width: var(--devtools-splitter-bottom-width);
|
||||
|
||||
margin-top: calc(-1 * var(--devtools-splitter-top-width) - 1px);
|
||||
margin-bottom: calc(-1 * var(--devtools-splitter-bottom-width));
|
||||
|
||||
cursor: ns-resize;
|
||||
}
|
||||
|
||||
.split-box.disabled {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure splitter panels are not processing any mouse
|
||||
* events. This is good for performance during splitter
|
||||
* bar dragging.
|
||||
*/
|
||||
.split-box.dragging > .controlled,
|
||||
.split-box.dragging > .uncontrolled {
|
||||
pointer-events: none;
|
||||
}
|
207
devtools/client/shared/components/splitter/split-box.js
Normal file
207
devtools/client/shared/components/splitter/split-box.js
Normal file
@ -0,0 +1,207 @@
|
||||
/* 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/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const React = require("devtools/client/shared/vendor/react");
|
||||
const ReactDOM = require("devtools/client/shared/vendor/react-dom");
|
||||
const Draggable = React.createFactory(require("devtools/client/shared/components/splitter/draggable"));
|
||||
const { DOM: dom, PropTypes } = React;
|
||||
|
||||
/**
|
||||
* This component represents a Splitter. The splitter supports vertical
|
||||
* as well as horizontal mode.
|
||||
*/
|
||||
const SplitBox = React.createClass({
|
||||
displayName: "SplitBox",
|
||||
|
||||
propTypes: {
|
||||
// Custom class name. You can use more names separated by a space.
|
||||
className: PropTypes.string,
|
||||
// Initial size of controlled panel.
|
||||
initialSize: PropTypes.number,
|
||||
// Left/top panel
|
||||
startPanel: PropTypes.any,
|
||||
// Min panel size.
|
||||
minSize: PropTypes.number,
|
||||
// Max panel size.
|
||||
maxSize: PropTypes.number,
|
||||
// Right/bottom panel
|
||||
endPanel: PropTypes.any,
|
||||
// True if the right/bottom panel should be controlled.
|
||||
endPanelControl: PropTypes.bool,
|
||||
// Size of the splitter handle bar.
|
||||
splitterSize: PropTypes.number,
|
||||
// True if the splitter bar is vertical (default is vertical).
|
||||
vert: PropTypes.bool
|
||||
},
|
||||
|
||||
getDefaultProps() {
|
||||
return {
|
||||
splitterSize: 5,
|
||||
vert: true,
|
||||
endPanelControl: false
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* The state stores the current orientation (vertical or horizontal)
|
||||
* and the current size (width/height). All these values can change
|
||||
* during the component's life time.
|
||||
*/
|
||||
getInitialState() {
|
||||
return {
|
||||
vert: this.props.vert,
|
||||
width: this.props.initialWidth || this.props.initialSize,
|
||||
height: this.props.initialHeight || this.props.initialSize
|
||||
};
|
||||
},
|
||||
|
||||
// Dragging Events
|
||||
|
||||
/**
|
||||
* Set 'resizing' cursor on entire document during splitter dragging.
|
||||
* This avoids cursor-flickering that happens when the mouse leaves
|
||||
* the splitter bar area (happens frequently).
|
||||
*/
|
||||
onStartMove() {
|
||||
const splitBox = ReactDOM.findDOMNode(this);
|
||||
const doc = splitBox.ownerDocument;
|
||||
let defaultCursor = doc.documentElement.style.cursor;
|
||||
doc.documentElement.style.cursor = (this.state.vert ? "ew-resize" : "ns-resize");
|
||||
|
||||
splitBox.classList.add("dragging");
|
||||
|
||||
this.setState({
|
||||
defaultCursor: defaultCursor
|
||||
});
|
||||
},
|
||||
|
||||
onStopMove() {
|
||||
const splitBox = ReactDOM.findDOMNode(this);
|
||||
const doc = splitBox.ownerDocument;
|
||||
doc.documentElement.style.cursor = this.state.defaultCursor;
|
||||
|
||||
splitBox.classList.remove("dragging");
|
||||
},
|
||||
|
||||
/**
|
||||
* Adjust size of the controlled panel. Depending on the current
|
||||
* orientation we either remember the width or height of
|
||||
* the splitter box.
|
||||
*/
|
||||
onMove(x, y) {
|
||||
const node = ReactDOM.findDOMNode(this);
|
||||
const doc = node.ownerDocument;
|
||||
const win = doc.defaultView;
|
||||
|
||||
let size;
|
||||
let { endPanelControl } = this.props;
|
||||
|
||||
if (this.state.vert) {
|
||||
// Switch the control flag in case of RTL. Note that RTL
|
||||
// has impact on vertical splitter only.
|
||||
let dir = win.getComputedStyle(doc.documentElement).direction;
|
||||
if (dir == "rtl") {
|
||||
endPanelControl = !endPanelControl;
|
||||
}
|
||||
|
||||
let innerOffset = x - win.mozInnerScreenX;
|
||||
size = endPanelControl ?
|
||||
(node.offsetLeft + node.offsetWidth) - innerOffset :
|
||||
innerOffset - node.offsetLeft;
|
||||
|
||||
this.setState({
|
||||
width: size
|
||||
});
|
||||
} else {
|
||||
let innerOffset = y - win.mozInnerScreenY;
|
||||
size = endPanelControl ?
|
||||
(node.offsetTop + node.offsetHeight) - innerOffset :
|
||||
innerOffset - node.offsetTop;
|
||||
|
||||
this.setState({
|
||||
height: size
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// Rendering
|
||||
|
||||
render() {
|
||||
const vert = this.state.vert;
|
||||
const { startPanel, endPanel, endPanelControl, minSize,
|
||||
maxSize, splitterSize } = this.props;
|
||||
|
||||
let style = Object.assign({}, this.props.style);
|
||||
|
||||
// Calculate class names list.
|
||||
let classNames = ["split-box"];
|
||||
classNames.push(vert ? "vert" : "horz");
|
||||
if (this.props.className) {
|
||||
classNames = classNames.concat(this.props.className.split(" "));
|
||||
}
|
||||
|
||||
let leftPanelStyle;
|
||||
let rightPanelStyle;
|
||||
|
||||
// Set proper size for panels depending on the current state.
|
||||
if (vert) {
|
||||
leftPanelStyle = {
|
||||
maxWidth: endPanelControl ? null : maxSize,
|
||||
minWidth: endPanelControl ? null : minSize,
|
||||
width: endPanelControl ? null : this.state.width
|
||||
};
|
||||
rightPanelStyle = {
|
||||
maxWidth: endPanelControl ? maxSize : null,
|
||||
minWidth: endPanelControl ? minSize : null,
|
||||
width: endPanelControl ? this.state.width : null
|
||||
};
|
||||
} else {
|
||||
leftPanelStyle = {
|
||||
maxHeight: endPanelControl ? null : maxSize,
|
||||
minHeight: endPanelControl ? null : minSize,
|
||||
height: endPanelControl ? null : this.state.height
|
||||
};
|
||||
rightPanelStyle = {
|
||||
maxHeight: endPanelControl ? maxSize : null,
|
||||
minHeight: endPanelControl ? minSize : null,
|
||||
height: endPanelControl ? this.state.height : null
|
||||
};
|
||||
}
|
||||
|
||||
// Calculate splitter size
|
||||
let splitterStyle = {
|
||||
flex: "0 0 " + splitterSize + "px"
|
||||
};
|
||||
|
||||
return (
|
||||
dom.div({
|
||||
className: classNames.join(" "),
|
||||
style: style },
|
||||
startPanel ?
|
||||
dom.div({
|
||||
className: endPanelControl ? "uncontrolled" : "controlled",
|
||||
style: leftPanelStyle},
|
||||
startPanel
|
||||
) : null,
|
||||
Draggable({
|
||||
className: "splitter",
|
||||
style: splitterStyle,
|
||||
onStart: this.onStartMove,
|
||||
onStop: this.onStopMove,
|
||||
onMove: this.onMove
|
||||
}),
|
||||
endPanel ?
|
||||
dom.div({
|
||||
className: endPanelControl ? "controlled" : "uncontrolled",
|
||||
style: rightPanelStyle},
|
||||
endPanel
|
||||
) : null
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = SplitBox;
|
@ -57,7 +57,11 @@ window.onload = Task.async(function* () {
|
||||
document.body.appendChild(panel);
|
||||
|
||||
return setState(tabbarReact, Object.assign({}, tabbarReact.state, {
|
||||
tabs: tabbarReact.state.tabs.concat({id: `${tabId}`, title: `tab-${tabId}`, panel: InspectorTabPanel}),
|
||||
tabs: tabbarReact.state.tabs.concat({
|
||||
id: `sidebar-panel-${tabId}`,
|
||||
title: `tab-${tabId}`,
|
||||
panel: InspectorTabPanel
|
||||
}),
|
||||
}));
|
||||
}
|
||||
} catch(e) {
|
||||
|
@ -37,10 +37,6 @@ Object.defineProperty(this, "EVENTS", {
|
||||
writable: false
|
||||
});
|
||||
|
||||
// Maximum number of character visible in any cell in the table. This is to
|
||||
// avoid making the cell take up all the space in a row.
|
||||
const MAX_VISIBLE_STRING_SIZE = 100;
|
||||
|
||||
/**
|
||||
* A table widget with various features like resizble/toggleable columns,
|
||||
* sorting, keyboard navigation etc.
|
||||
@ -1510,11 +1506,6 @@ Cell.prototype = {
|
||||
value = span;
|
||||
}
|
||||
|
||||
if (!(value instanceof Node) &&
|
||||
value.length > MAX_VISIBLE_STRING_SIZE) {
|
||||
value = value .substr(0, MAX_VISIBLE_STRING_SIZE) + "\u2026";
|
||||
}
|
||||
|
||||
if (value instanceof Node) {
|
||||
this.label.removeAttribute("value");
|
||||
|
||||
|
@ -28,6 +28,7 @@
|
||||
|
||||
#computedview-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#browser-style-checkbox {
|
||||
@ -36,15 +37,11 @@
|
||||
an extra space after. */
|
||||
margin-inline-start: 5px;
|
||||
margin-inline-end: 5px;
|
||||
|
||||
}
|
||||
|
||||
#browser-style-checkbox-label {
|
||||
margin-right: 5px;
|
||||
|
||||
/* Vertically center the 'Browser styles' checkbox in the
|
||||
Computed panel with its label. */
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#propertyContainer {
|
||||
|
@ -11,9 +11,47 @@
|
||||
--eyedropper-image: url(images/firebug/command-eyedropper.svg);
|
||||
}
|
||||
|
||||
/* Make sure to hide scroll bars for the parent window */
|
||||
window {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* The main Inspector panel container. */
|
||||
.inspector-responsive-container {
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
/* The main panel layout. This area consists of a toolbar, markup view
|
||||
and breadcrumbs bar. */
|
||||
.devtools-main-content {
|
||||
/* Subtract 1 pixel from the panel height. It's puzzling why this
|
||||
is needed, but if not presented the entire Inspector panel
|
||||
content jumps 1 pixel up when the Toolbox is opened. */
|
||||
height: calc(100% - 1px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
/* Inspector Panel Splitter */
|
||||
|
||||
#inspector-splitter-box {
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
}
|
||||
|
||||
/* Minimum width for the Inspector main (uncontrolled) area. */
|
||||
#inspector-splitter-box .uncontrolled {
|
||||
min-width: 275px;
|
||||
}
|
||||
|
||||
#inspector-splitter-box .controlled.pane-collapsed {
|
||||
visibility: collapse;
|
||||
}
|
||||
|
||||
/* Use flex layout for the Inspector toolbar. For now, it's done
|
||||
specifically for the Inspector toolbar since general rule applied
|
||||
on .devtools-toolbar breaks breadcrubs and also toolbars in other
|
||||
on .devtools-toolbar breaks breadcrumbs and also toolbars in other
|
||||
panels (e.g. webconsole, debugger), these are not ready for HTML
|
||||
layout yet. */
|
||||
#inspector-toolbar.devtools-toolbar {
|
||||
@ -124,8 +162,9 @@
|
||||
is fixed and the all-tabs-menu is available again. */
|
||||
#inspector-sidebar-container {
|
||||
overflow: hidden;
|
||||
min-width: 300px;
|
||||
min-width: 50px;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#inspector-sidebar {
|
||||
@ -149,3 +188,20 @@
|
||||
text-align: center;
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
/* Markup Box */
|
||||
|
||||
iframe {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
#markup-box {
|
||||
width: 100%;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
#markup-box > iframe {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
@ -378,6 +378,7 @@ JSTerm.prototype = {
|
||||
if (this.hud.NEW_CONSOLE_OUTPUT_ENABLED) {
|
||||
this.hud.newConsoleOutput.dispatchMessageAdd(response);
|
||||
// @TODO figure out what to do about the callback.
|
||||
callback && callback();
|
||||
return;
|
||||
}
|
||||
let msg = new Messages.JavaScriptEvalOutput(response,
|
||||
@ -423,12 +424,17 @@ JSTerm.prototype = {
|
||||
*/
|
||||
execute: function (executeString, callback) {
|
||||
let deferred = promise.defer();
|
||||
let resultCallback = function (msg) {
|
||||
deferred.resolve(msg);
|
||||
if (callback) {
|
||||
callback(msg);
|
||||
}
|
||||
};
|
||||
let resultCallback;
|
||||
if (this.hud.NEW_CONSOLE_OUTPUT_ENABLED) {
|
||||
resultCallback = () => deferred.resolve();
|
||||
} else {
|
||||
resultCallback = (msg) => {
|
||||
deferred.resolve(msg);
|
||||
if (callback) {
|
||||
callback(msg);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// attempt to execute the content of the inputNode
|
||||
executeString = executeString || this.getInputValue();
|
||||
|
@ -12,13 +12,12 @@ const {
|
||||
DOM: dom,
|
||||
PropTypes
|
||||
} = require("devtools/client/shared/vendor/react");
|
||||
const { ConsoleCommand: ConsoleCommandType } = require("devtools/client/webconsole/new-console-output/types");
|
||||
const MessageIcon = createFactory(require("devtools/client/webconsole/new-console-output/components/message-icon").MessageIcon);
|
||||
|
||||
ConsoleCommand.displayName = "ConsoleCommand";
|
||||
|
||||
ConsoleCommand.propTypes = {
|
||||
message: PropTypes.instanceOf(ConsoleCommandType).isRequired,
|
||||
message: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -26,6 +26,14 @@ function EvaluationResult(props) {
|
||||
const {source, type, level} = message;
|
||||
const icon = MessageIcon({level});
|
||||
|
||||
let messageBody;
|
||||
if (message.messageText) {
|
||||
messageBody = message.messageText;
|
||||
} else {
|
||||
messageBody = GripMessageBody({grip: message.parameters});
|
||||
}
|
||||
|
||||
|
||||
const classes = ["message", "cm-s-mozilla"];
|
||||
|
||||
classes.push(source);
|
||||
@ -41,7 +49,7 @@ function EvaluationResult(props) {
|
||||
dom.span({ className: "message-body-wrapper" },
|
||||
dom.span({ className: "message-flex-body" },
|
||||
dom.span({ className: "message-body devtools-monospace" },
|
||||
GripMessageBody({grip: message.parameters})
|
||||
messageBody
|
||||
)
|
||||
)
|
||||
)
|
||||
|
@ -16,7 +16,7 @@ const {openVariablesView} = require("devtools/client/webconsole/new-console-outp
|
||||
VariablesViewLink.displayName = "VariablesViewLink";
|
||||
|
||||
VariablesViewLink.propTypes = {
|
||||
object: PropTypes.object.required
|
||||
object: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
function VariablesViewLink(props) {
|
||||
|
@ -2,29 +2,36 @@
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
const { stubPreparedMessages } = require("devtools/client/webconsole/new-console-output/test/fixtures/stubs/index");
|
||||
const { EvaluationResult } = require("devtools/client/webconsole/new-console-output/components/message-types/evaluation-result");
|
||||
|
||||
// Test utils.
|
||||
const expect = require("expect");
|
||||
const { render } = require("enzyme");
|
||||
|
||||
const {
|
||||
renderComponent
|
||||
} = require("devtools/client/webconsole/new-console-output/test/helpers");
|
||||
// React
|
||||
const { createFactory } = require("devtools/client/shared/vendor/react");
|
||||
|
||||
// Components under test.
|
||||
const EvaluationResult = createFactory(require("devtools/client/webconsole/new-console-output/components/message-types/evaluation-result").EvaluationResult);
|
||||
|
||||
// Test fakes.
|
||||
const { stubPreparedMessages } = require("devtools/client/webconsole/new-console-output/test/fixtures/stubs/index");
|
||||
|
||||
describe("EvaluationResult component:", () => {
|
||||
it("renders a grip result", () => {
|
||||
const message = stubPreparedMessages.get("new Date(0)");
|
||||
const props = {
|
||||
message
|
||||
};
|
||||
const rendered = renderComponent(EvaluationResult, props);
|
||||
const wrapper = render(EvaluationResult({ message }));
|
||||
|
||||
const messageBody = getMessageBody(rendered);
|
||||
expect(messageBody.textContent).toBe("Date 1970-01-01T00:00:00.000Z");
|
||||
expect(wrapper.find(".message-body").text()).toBe("Date 1970-01-01T00:00:00.000Z");
|
||||
|
||||
expect(wrapper.find(".message.log").length).toBe(1);
|
||||
});
|
||||
|
||||
it("renders an error", () => {
|
||||
const message = stubPreparedMessages.get("asdf()");
|
||||
const wrapper = render(EvaluationResult({ message }));
|
||||
|
||||
expect(wrapper.find(".message-body").text())
|
||||
.toBe("ReferenceError: asdf is not defined");
|
||||
|
||||
expect(wrapper.find(".message.error").length).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
function getMessageBody(rendered) {
|
||||
const queryPath = "div.message span.message-body-wrapper span.message-body";
|
||||
return rendered.querySelector(queryPath);
|
||||
}
|
||||
|
@ -49,7 +49,8 @@ console.timeEnd("bar");
|
||||
// Evaluation Result
|
||||
|
||||
const evaluationResultCommands = [
|
||||
"new Date(0)"
|
||||
"new Date(0)",
|
||||
"asdf()"
|
||||
];
|
||||
|
||||
let evaluationResult = new Map(evaluationResultCommands.map(cmd => [cmd, cmd]));
|
||||
|
@ -19,7 +19,6 @@ stubPreparedMessages.set("new Date(0)", new ConsoleMessage({
|
||||
"source": "javascript",
|
||||
"type": "result",
|
||||
"level": "log",
|
||||
"messageText": null,
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"actor": "server1.conn0.child1/obj30",
|
||||
@ -33,7 +32,23 @@ stubPreparedMessages.set("new Date(0)", new ConsoleMessage({
|
||||
}
|
||||
},
|
||||
"repeat": 1,
|
||||
"repeatId": "{\"id\":null,\"allowRepeating\":true,\"source\":\"javascript\",\"type\":\"result\",\"level\":\"log\",\"messageText\":null,\"parameters\":{\"type\":\"object\",\"actor\":\"server1.conn0.child1/obj30\",\"class\":\"Date\",\"extensible\":true,\"frozen\":false,\"sealed\":false,\"ownPropertyLength\":0,\"preview\":{\"timestamp\":0}},\"repeatId\":null,\"stacktrace\":null,\"frame\":null}",
|
||||
"repeatId": "{\"id\":null,\"allowRepeating\":true,\"source\":\"javascript\",\"type\":\"result\",\"level\":\"log\",\"parameters\":{\"type\":\"object\",\"actor\":\"server1.conn0.child1/obj30\",\"class\":\"Date\",\"extensible\":true,\"frozen\":false,\"sealed\":false,\"ownPropertyLength\":0,\"preview\":{\"timestamp\":0}},\"repeatId\":null,\"stacktrace\":null,\"frame\":null}",
|
||||
"stacktrace": null,
|
||||
"frame": null
|
||||
}));
|
||||
|
||||
stubPreparedMessages.set("asdf()", new ConsoleMessage({
|
||||
"id": "1",
|
||||
"allowRepeating": true,
|
||||
"source": "javascript",
|
||||
"type": "result",
|
||||
"level": "error",
|
||||
"messageText": "ReferenceError: asdf is not defined",
|
||||
"parameters": {
|
||||
"type": "undefined"
|
||||
},
|
||||
"repeat": 1,
|
||||
"repeatId": "{\"id\":null,\"allowRepeating\":true,\"source\":\"javascript\",\"type\":\"result\",\"level\":\"error\",\"messageText\":\"ReferenceError: asdf is not defined\",\"parameters\":{\"type\":\"undefined\"},\"repeatId\":null,\"stacktrace\":null,\"frame\":null}",
|
||||
"stacktrace": null,
|
||||
"frame": null
|
||||
}));
|
||||
@ -54,11 +69,41 @@ stubPackets.set("new Date(0)", {
|
||||
"timestamp": 0
|
||||
}
|
||||
},
|
||||
"timestamp": 1471886229652,
|
||||
"timestamp": 1474405330863,
|
||||
"exception": null,
|
||||
"helperResult": null
|
||||
});
|
||||
|
||||
stubPackets.set("asdf()", {
|
||||
"from": "server1.conn0.child1/consoleActor2",
|
||||
"input": "asdf()",
|
||||
"result": {
|
||||
"type": "undefined"
|
||||
},
|
||||
"timestamp": 1474405330881,
|
||||
"exception": {
|
||||
"type": "object",
|
||||
"actor": "server1.conn0.child1/obj32",
|
||||
"class": "Error",
|
||||
"extensible": true,
|
||||
"frozen": false,
|
||||
"sealed": false,
|
||||
"ownPropertyLength": 4,
|
||||
"preview": {
|
||||
"kind": "Error",
|
||||
"name": "ReferenceError",
|
||||
"message": "asdf is not defined",
|
||||
"stack": "@debugger eval code:1:1\n",
|
||||
"fileName": "debugger eval code",
|
||||
"lineNumber": 1,
|
||||
"columnNumber": 1
|
||||
}
|
||||
},
|
||||
"exceptionMessage": "ReferenceError: asdf is not defined",
|
||||
"exceptionDocURL": "https://developer.mozilla.org/docs/Web/JavaScript/Reference/Errors/Not_defined?utm_source=mozilla&utm_medium=firefox-console-errors&utm_campaign=default",
|
||||
"helperResult": null
|
||||
});
|
||||
|
||||
|
||||
module.exports = {
|
||||
stubPreparedMessages,
|
||||
|
@ -7,3 +7,7 @@ support-files =
|
||||
test-console.html
|
||||
|
||||
[browser_webconsole_init.js]
|
||||
[browser_webconsole_input_focus.js]
|
||||
[browser_webconsole_observer_notifications.js]
|
||||
[browser_webconsole_vview_close_on_esc_key.js]
|
||||
|
||||
|
@ -33,36 +33,3 @@ add_task(function* () {
|
||||
|
||||
yield receievedMessages;
|
||||
});
|
||||
|
||||
/**
|
||||
* Wait for messages in the web console output, resolving once they are receieved.
|
||||
*
|
||||
* @param object options
|
||||
* - hud: the webconsole
|
||||
* - messages: Array[Object]. An array of messages to match. Current supported options:
|
||||
* - text: Exact text match in .message-body
|
||||
*/
|
||||
function waitForMessages({ hud, messages }) {
|
||||
return new Promise(resolve => {
|
||||
let numMatched = 0;
|
||||
let receivedLog = hud.ui.on("new-messages", function messagesReceieved(e, newMessage) {
|
||||
for (let message of messages) {
|
||||
if (message.matched) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (newMessage.node.querySelector(".message-body").textContent == message.text) {
|
||||
numMatched++;
|
||||
message.matched = true;
|
||||
info("Matched a message with text: " + message.text + ", still waiting for " + (messages.length - numMatched) + " messages");
|
||||
}
|
||||
|
||||
if (numMatched === messages.length) {
|
||||
hud.ui.off("new-messages", messagesReceieved);
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
// Tests that the input field is focused when the console is opened.
|
||||
|
||||
"use strict";
|
||||
|
||||
const TEST_URI = "data:text/html;charset=utf-8,Test input focus";
|
||||
|
||||
add_task(function* () {
|
||||
let hud = yield openNewTabAndConsole(TEST_URI);
|
||||
hud.jsterm.clearOutput();
|
||||
|
||||
let inputNode = hud.jsterm.inputNode;
|
||||
ok(inputNode.getAttribute("focused"), "input node is focused");
|
||||
});
|
@ -0,0 +1,47 @@
|
||||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
const TEST_URI = "data:text/html;charset=utf-8,<p>Web Console test for " +
|
||||
"obeserver notifications";
|
||||
|
||||
let created = false;
|
||||
let destroyed = false;
|
||||
|
||||
add_task(function* () {
|
||||
setupObserver();
|
||||
yield openNewTabAndConsole(TEST_URI);
|
||||
yield waitFor(() => created);
|
||||
|
||||
yield closeTabAndToolbox(gBrowser.selectedTab);
|
||||
yield waitFor(() => destroyed);
|
||||
});
|
||||
|
||||
function setupObserver() {
|
||||
const observer = {
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
|
||||
|
||||
observe: function observe(subject, topic) {
|
||||
subject = subject.QueryInterface(Ci.nsISupportsString);
|
||||
|
||||
switch (topic) {
|
||||
case "web-console-created":
|
||||
ok(HUDService.getHudReferenceById(subject.data), "We have a hud reference");
|
||||
Services.obs.removeObserver(observer, "web-console-created");
|
||||
created = true;
|
||||
break;
|
||||
case "web-console-destroyed":
|
||||
ok(!HUDService.getHudReferenceById(subject.data), "We do not have a hud reference");
|
||||
Services.obs.removeObserver(observer, "web-console-destroyed");
|
||||
destroyed = true;
|
||||
break;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
Services.obs.addObserver(observer, "web-console-created", false);
|
||||
Services.obs.addObserver(observer, "web-console-destroyed", false);
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
// Check that the variables view sidebar can be closed by pressing Escape in the
|
||||
// web console.
|
||||
|
||||
"use strict";
|
||||
|
||||
const TEST_URI =
|
||||
"data:text/html;charset=utf8,<script>let fooObj = {testProp: 'testValue'}</script>";
|
||||
|
||||
add_task(function* () {
|
||||
let hud = yield openNewTabAndConsole(TEST_URI);
|
||||
let jsterm = hud.jsterm;
|
||||
let vview;
|
||||
|
||||
yield openSidebar("fooObj", 'testProp: "testValue"');
|
||||
vview.window.focus();
|
||||
|
||||
let sidebarClosed = jsterm.once("sidebar-closed");
|
||||
EventUtils.synthesizeKey("VK_ESCAPE", {});
|
||||
yield sidebarClosed;
|
||||
|
||||
function* openSidebar(objName, expectedText) {
|
||||
yield jsterm.execute(objName);
|
||||
info("JSTerm executed");
|
||||
|
||||
let msg = yield waitFor(() => findMessage(hud, "Object"));
|
||||
ok(msg, "Message found");
|
||||
|
||||
let anchor = msg.querySelector("a");
|
||||
let body = msg.querySelector(".message-body");
|
||||
ok(anchor, "object anchor");
|
||||
ok(body, "message body");
|
||||
ok(body.textContent.includes(expectedText), "message text check");
|
||||
|
||||
msg.scrollIntoView();
|
||||
yield EventUtils.synthesizeMouse(anchor, 2, 2, {}, hud.iframeWindow);
|
||||
|
||||
let vviewVar = yield jsterm.once("variablesview-fetched");
|
||||
vview = vviewVar._variablesView;
|
||||
ok(vview, "variables view object exists");
|
||||
}
|
||||
});
|
@ -13,6 +13,102 @@ Services.scriptloader.loadSubScript(
|
||||
this);
|
||||
|
||||
Services.prefs.setBoolPref("devtools.webconsole.new-frontend-enabled", true);
|
||||
registerCleanupFunction(() => {
|
||||
registerCleanupFunction(function* () {
|
||||
Services.prefs.clearUserPref("devtools.webconsole.new-frontend-enabled");
|
||||
|
||||
let browserConsole = HUDService.getBrowserConsole();
|
||||
if (browserConsole) {
|
||||
if (browserConsole.jsterm) {
|
||||
browserConsole.jsterm.clearOutput(true);
|
||||
}
|
||||
yield HUDService.toggleBrowserConsole();
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Add a new tab and open the toolbox in it, and select the webconsole.
|
||||
*
|
||||
* @param string url
|
||||
* The URL for the tab to be opened.
|
||||
* @return Promise
|
||||
* Resolves when the tab has been added, loaded and the toolbox has been opened.
|
||||
* Resolves to the toolbox.
|
||||
*/
|
||||
var openNewTabAndConsole = Task.async(function* (url) {
|
||||
let toolbox = yield openNewTabAndToolbox(TEST_URI, "webconsole");
|
||||
let hud = toolbox.getCurrentPanel().hud;
|
||||
hud.jsterm._lazyVariablesView = false;
|
||||
return hud;
|
||||
});
|
||||
|
||||
/**
|
||||
* Wait for messages in the web console output, resolving once they are receieved.
|
||||
*
|
||||
* @param object options
|
||||
* - hud: the webconsole
|
||||
* - messages: Array[Object]. An array of messages to match. Current supported options:
|
||||
* - text: Exact text match in .message-body
|
||||
*/
|
||||
function waitForMessages({ hud, messages }) {
|
||||
return new Promise(resolve => {
|
||||
let numMatched = 0;
|
||||
let receivedLog = hud.ui.on("new-messages", function messagesReceieved(e, newMessage) {
|
||||
for (let message of messages) {
|
||||
if (message.matched) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (newMessage.node.querySelector(".message-body").textContent == message.text) {
|
||||
numMatched++;
|
||||
message.matched = true;
|
||||
info("Matched a message with text: " + message.text + ", still waiting for " + (messages.length - numMatched) + " messages");
|
||||
}
|
||||
|
||||
if (numMatched === messages.length) {
|
||||
hud.ui.off("new-messages", messagesReceieved);
|
||||
resolve(receivedLog);
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for a predicate to return a result.
|
||||
*
|
||||
* @param function condition
|
||||
* Invoked once in a while until it returns a truthy value. This should be an
|
||||
* idempotent function, since we have to run it a second time after it returns
|
||||
* true in order to return the value.
|
||||
* @param string message [optional]
|
||||
* A message to output if the condition failes.
|
||||
* @param number interval [optional]
|
||||
* How often the predicate is invoked, in milliseconds.
|
||||
* @return object
|
||||
* A promise that is resolved with the result of the condition.
|
||||
*/
|
||||
function* waitFor(condition, message = "waitFor", interval = 100, maxTries = 50) {
|
||||
return new Promise(resolve => {
|
||||
BrowserTestUtils.waitForCondition(condition, message, interval, maxTries)
|
||||
.then(resolve(condition()));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a message in the output.
|
||||
*
|
||||
* @param object hud
|
||||
* The web console.
|
||||
* @param string text
|
||||
* A substring that can be found in the message.
|
||||
* @param selector [optional]
|
||||
* The selector to use in finding the message.
|
||||
*/
|
||||
function findMessage(hud, text, selector = ".message") {
|
||||
const elements = Array.prototype.filter.call(
|
||||
hud.ui.experimentalOutputNode.querySelectorAll(selector),
|
||||
(el) => el.textContent.includes(text)
|
||||
);
|
||||
return elements.pop();
|
||||
}
|
||||
|
@ -83,11 +83,11 @@ function transformPacket(packet) {
|
||||
break;
|
||||
}
|
||||
|
||||
const frame = {
|
||||
source: message.filename || null,
|
||||
line: message.lineNumber || null,
|
||||
column: message.columnNumber || null
|
||||
};
|
||||
const frame = message.filename ? {
|
||||
source: message.filename,
|
||||
line: message.lineNumber,
|
||||
column: message.columnNumber,
|
||||
} : null;
|
||||
|
||||
return new ConsoleMessage({
|
||||
source: MESSAGE_SOURCE.CONSOLE_API,
|
||||
@ -119,11 +119,11 @@ function transformPacket(packet) {
|
||||
level = MESSAGE_LEVEL.INFO;
|
||||
}
|
||||
|
||||
const frame = {
|
||||
const frame = pageError.sourceName ? {
|
||||
source: pageError.sourceName,
|
||||
line: pageError.lineNumber,
|
||||
column: pageError.columnNumber
|
||||
};
|
||||
} : null;
|
||||
|
||||
return new ConsoleMessage({
|
||||
source: MESSAGE_SOURCE.JAVASCRIPT,
|
||||
@ -148,13 +148,18 @@ function transformPacket(packet) {
|
||||
|
||||
case "evaluationResult":
|
||||
default: {
|
||||
let { result } = packet;
|
||||
let {
|
||||
exceptionMessage: messageText,
|
||||
result: parameters
|
||||
} = packet;
|
||||
|
||||
const level = messageText ? MESSAGE_LEVEL.ERROR : MESSAGE_LEVEL.LOG;
|
||||
return new ConsoleMessage({
|
||||
source: MESSAGE_SOURCE.JAVASCRIPT,
|
||||
type: MESSAGE_TYPE.RESULT,
|
||||
level: MESSAGE_LEVEL.LOG,
|
||||
parameters: result,
|
||||
level,
|
||||
messageText,
|
||||
parameters,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,9 @@
|
||||
*
|
||||
* Once JSTerm is also written in React/Redux, these will be actions.
|
||||
*/
|
||||
exports.openVariablesView = (object) => {
|
||||
window.jsterm.openVariablesView({objectActor: object});
|
||||
exports.openVariablesView = (objectActor) => {
|
||||
window.jsterm.openVariablesView({
|
||||
objectActor,
|
||||
autofocus: true,
|
||||
});
|
||||
};
|
||||
|
@ -582,6 +582,7 @@ WebConsoleFrame.prototype = {
|
||||
// XXX: We should actually stop output from happening on old output
|
||||
// panel, but for now let's just hide it.
|
||||
this.experimentalOutputNode = this.outputNode.cloneNode();
|
||||
this.experimentalOutputNode.removeAttribute("tabindex");
|
||||
this.outputNode.hidden = true;
|
||||
this.outputNode.parentNode.appendChild(this.experimentalOutputNode);
|
||||
// @TODO Once the toolbox has been converted to React, see if passing
|
||||
|
@ -406,7 +406,10 @@ TabSources.prototype = {
|
||||
* @return Promise of a SourceMapConsumer
|
||||
*/
|
||||
fetchSourceMap: function (aSource) {
|
||||
if (this._sourceMaps.has(aSource)) {
|
||||
if (!this._useSourceMaps) {
|
||||
return resolve(null);
|
||||
}
|
||||
else if (this._sourceMaps.has(aSource)) {
|
||||
return this._sourceMaps.get(aSource);
|
||||
}
|
||||
else if (!aSource || !aSource.sourceMapURL) {
|
||||
@ -457,10 +460,10 @@ TabSources.prototype = {
|
||||
* them from aScriptURL.
|
||||
*/
|
||||
_fetchSourceMap: function (aAbsSourceMapURL, aSourceURL) {
|
||||
if (!this._useSourceMaps) {
|
||||
return resolve(null);
|
||||
}
|
||||
else if (this._sourceMapCache[aAbsSourceMapURL]) {
|
||||
assert(this._useSourceMaps,
|
||||
"Cannot fetch sourcemaps if they are disabled");
|
||||
|
||||
if (this._sourceMapCache[aAbsSourceMapURL]) {
|
||||
return this._sourceMapCache[aAbsSourceMapURL];
|
||||
}
|
||||
|
||||
|
58
devtools/server/tests/unit/test_listsources-04.js
Normal file
58
devtools/server/tests/unit/test_listsources-04.js
Normal file
@ -0,0 +1,58 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Check getSources functionality with sourcemaps.
|
||||
*/
|
||||
|
||||
const {SourceNode} = require("source-map");
|
||||
|
||||
function run_test() {
|
||||
run_test_with_server(DebuggerServer, function () {
|
||||
// Bug 1304144 - This test does not run in a worker because the
|
||||
// `rpc` method which talks to the main thread does not work.
|
||||
// run_test_with_server(WorkerDebuggerServer, do_test_finished);
|
||||
do_test_finished();
|
||||
});
|
||||
do_test_pending();
|
||||
}
|
||||
|
||||
function run_test_with_server(server, cb) {
|
||||
Task.spawn(function*() {
|
||||
initTestDebuggerServer(server);
|
||||
const debuggee = addTestGlobal("test-sources", server);
|
||||
const client = new DebuggerClient(server.connectPipe());
|
||||
yield client.connect();
|
||||
const [,,threadClient] = yield attachTestTabAndResume(client, "test-sources");
|
||||
|
||||
yield threadClient.reconfigure({ useSourceMaps: true });
|
||||
addSources(debuggee);
|
||||
|
||||
threadClient.getSources(Task.async(function* (res) {
|
||||
do_check_true(res.sources.length === 3, "3 sources exist");
|
||||
|
||||
yield threadClient.reconfigure({ useSourceMaps: false });
|
||||
|
||||
threadClient.getSources(function(res) {
|
||||
do_check_true(res.sources.length === 1, "1 source exist");
|
||||
client.close().then(cb);
|
||||
});
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
function addSources(debuggee) {
|
||||
let { code, map } = (new SourceNode(null, null, null, [
|
||||
new SourceNode(1, 0, "a.js", "function a() { return 'a'; }\n"),
|
||||
new SourceNode(1, 0, "b.js", "function b() { return 'b'; }\n"),
|
||||
new SourceNode(1, 0, "c.js", "function c() { return 'c'; }\n"),
|
||||
])).toStringWithSourceMap({
|
||||
file: "abc.js",
|
||||
sourceRoot: "http://example.com/www/js/"
|
||||
});
|
||||
|
||||
code += "//# sourceMappingURL=data:text/json;base64," + btoa(map.toString());
|
||||
|
||||
Components.utils.evalInSandbox(code, debuggee, "1.8",
|
||||
"http://example.com/www/js/abc.js", 1);
|
||||
}
|
@ -165,6 +165,7 @@ skip-if = toolkit != "gonk"
|
||||
[test_listsources-01.js]
|
||||
[test_listsources-02.js]
|
||||
[test_listsources-03.js]
|
||||
[test_listsources-04.js]
|
||||
[test_new_source-01.js]
|
||||
[test_sourcemaps-01.js]
|
||||
[test_sourcemaps-02.js]
|
||||
|
@ -53,12 +53,6 @@ rule.pseudoElement=Pseudo-elements
|
||||
# pseudo element header
|
||||
rule.selectedElement=This Element
|
||||
|
||||
# LOCALIZATION NOTE (helpLinkTitle): For each style property
|
||||
# the user can hover it and get a help link button which allows one to
|
||||
# quickly jump to the documentation from the Mozilla Developer Network site.
|
||||
# This is the link title shown in the hover tooltip.
|
||||
helpLinkTitle=Read the documentation for this property
|
||||
|
||||
# LOCALIZATION NOTE (rule.warning.title): When an invalid property value is
|
||||
# entered into the rule view a warning icon is displayed. This text is used for
|
||||
# the title attribute of the warning icon.
|
||||
@ -142,7 +136,7 @@ styleinspector.contextmenu.showMdnDocs.accessKey=D
|
||||
|
||||
# LOCALIZATION NOTE (styleinspector.contextmenu.addNewRule): Text displayed in the
|
||||
# rule view context menu for adding a new rule to the element.
|
||||
# This should match addRuleButton.tooltip in styleinspector.dtd
|
||||
# This should match inspector.addRule.tooltip in inspector.properties
|
||||
styleinspector.contextmenu.addNewRule=Add New Rule
|
||||
|
||||
# LOCALIZATION NOTE (styleinspector.contextmenu.addRule.accessKey): Access key for
|
||||
|
@ -707,9 +707,8 @@ static bool
|
||||
HasCameraPermission(const nsCString& aOrigin)
|
||||
{
|
||||
// Name used with nsIPermissionManager
|
||||
static const char* cameraPermission = "camera";
|
||||
static const char* cameraPermission = "MediaManagerVideo";
|
||||
bool allowed = false;
|
||||
bool permanent = false;
|
||||
nsresult rv;
|
||||
nsCOMPtr<nsIPermissionManager> mgr =
|
||||
do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv);
|
||||
@ -728,19 +727,9 @@ HasCameraPermission(const nsCString& aOrigin)
|
||||
&video);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
allowed = (video == nsIPermissionManager::ALLOW_ACTION);
|
||||
// Was allowed, now see if this is a persistent permission
|
||||
// or a session one.
|
||||
if (allowed) {
|
||||
rv = mgr->TestExactPermanentPermission(principal,
|
||||
cameraPermission,
|
||||
&video);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
permanent = (video == nsIPermissionManager::ALLOW_ACTION);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Session permissions are removed after one use.
|
||||
if (allowed && !permanent) {
|
||||
if (allowed) {
|
||||
mgr->RemoveFromPrincipal(principal, cameraPermission);
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@
|
||||
package org.mozilla.gecko.media;
|
||||
|
||||
import org.mozilla.gecko.GeckoAppShell;
|
||||
import org.mozilla.gecko.Telemetry;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
@ -139,9 +140,14 @@ public final class RemoteManager implements IBinder.DeathRecipient {
|
||||
}
|
||||
}
|
||||
|
||||
private static final String MEDIA_DECODING_PROCESS_CRASH = "MEDIA_DECODING_PROCESS_CRASH";
|
||||
private void reportDecodingProcessCrash() {
|
||||
Telemetry.addToHistogram(MEDIA_DECODING_PROCESS_CRASH, 1);
|
||||
}
|
||||
@Override
|
||||
public void binderDied() {
|
||||
Log.e(LOGTAG, "remote codec is dead");
|
||||
reportDecodingProcessCrash();
|
||||
handleRemoteDeath();
|
||||
}
|
||||
|
||||
|
@ -32,7 +32,7 @@ function PageAction(options, extension) {
|
||||
|
||||
this.options = {
|
||||
title: options.default_title || extension.name,
|
||||
id: extension.id,
|
||||
id: `{${extension.uuid}}`,
|
||||
clickCallback: () => {
|
||||
if (this.popupUrl) {
|
||||
let win = Services.wm.getMostRecentWindow("navigator:browser");
|
||||
|
@ -6,10 +6,10 @@ const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/PageActions.jsm");
|
||||
|
||||
function isPageActionShown(extensionId) {
|
||||
return PageActions.isShown(extensionId);
|
||||
function isPageActionShown(uuid) {
|
||||
return PageActions.isShown(uuid);
|
||||
}
|
||||
|
||||
function clickPageAction(extensionId) {
|
||||
PageActions.synthesizeClick(extensionId);
|
||||
function clickPageAction(uuid) {
|
||||
PageActions.synthesizeClick(uuid);
|
||||
}
|
||||
|
@ -41,10 +41,15 @@ function background() {
|
||||
browser.test.sendMessage("page-action-clicked");
|
||||
});
|
||||
|
||||
browser.test.sendMessage("ready");
|
||||
let extensionInfo = {
|
||||
// Extract the assigned uuid from the background page url.
|
||||
uuid: `{${window.location.hostname}}`,
|
||||
};
|
||||
|
||||
browser.test.sendMessage("ready", extensionInfo);
|
||||
}
|
||||
|
||||
add_task(function* test_contentscript() {
|
||||
add_task(function* test_pageAction() {
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
background,
|
||||
manifest: {
|
||||
@ -55,6 +60,11 @@ add_task(function* test_contentscript() {
|
||||
"18": "extension.png",
|
||||
},
|
||||
},
|
||||
"applications": {
|
||||
"gecko": {
|
||||
"id": "foo@bar.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
files: {
|
||||
"extension.png": IMAGE_ARRAYBUFFER,
|
||||
@ -62,26 +72,26 @@ add_task(function* test_contentscript() {
|
||||
});
|
||||
|
||||
yield extension.startup();
|
||||
yield extension.awaitMessage("ready");
|
||||
let {uuid} = yield extension.awaitMessage("ready");
|
||||
|
||||
extension.sendMessage("pageAction-show");
|
||||
yield extension.awaitMessage("page-action-shown");
|
||||
ok(isPageActionShown(extension.id), "The PageAction should be shown");
|
||||
ok(isPageActionShown(uuid), "The PageAction should be shown");
|
||||
|
||||
extension.sendMessage("pageAction-hide");
|
||||
yield extension.awaitMessage("page-action-hidden");
|
||||
ok(!isPageActionShown(extension.id), "The PageAction should be hidden");
|
||||
ok(!isPageActionShown(uuid), "The PageAction should be hidden");
|
||||
|
||||
extension.sendMessage("pageAction-show");
|
||||
yield extension.awaitMessage("page-action-shown");
|
||||
ok(isPageActionShown(extension.id), "The PageAction should be shown");
|
||||
ok(isPageActionShown(uuid), "The PageAction should be shown");
|
||||
|
||||
clickPageAction(extension.id);
|
||||
clickPageAction(uuid);
|
||||
yield extension.awaitMessage("page-action-clicked");
|
||||
ok(isPageActionShown(extension.id), "The PageAction should still be shown after being clicked");
|
||||
ok(isPageActionShown(uuid), "The PageAction should still be shown after being clicked");
|
||||
|
||||
yield extension.unload();
|
||||
ok(!isPageActionShown(extension.id), "The PageAction should be removed after unload");
|
||||
ok(!isPageActionShown(uuid), "The PageAction should be removed after unload");
|
||||
});
|
||||
</script>
|
||||
|
||||
|
@ -54,7 +54,12 @@ add_task(function* test_contentscript() {
|
||||
browser.test.sendMessage("page-action-onClicked-fired");
|
||||
});
|
||||
|
||||
browser.test.sendMessage("ready");
|
||||
let extensionInfo = {
|
||||
// Extract the assigned uuid from the background page url.
|
||||
uuid: `{${window.location.hostname}}`,
|
||||
};
|
||||
|
||||
browser.test.sendMessage("ready", extensionInfo);
|
||||
}
|
||||
|
||||
function popupScript() {
|
||||
@ -107,7 +112,7 @@ add_task(function* test_contentscript() {
|
||||
});
|
||||
};
|
||||
|
||||
function* testPopup(name) {
|
||||
function* testPopup(name, uuid) {
|
||||
// We don't need to set the popup when testing default_popup.
|
||||
if (name != "default.html") {
|
||||
extension.sendMessage("page-action-set-popup", {name});
|
||||
@ -124,7 +129,7 @@ add_task(function* test_contentscript() {
|
||||
extension.sendMessage("page-action-enable-onClicked-listener");
|
||||
yield extension.awaitMessage("page-action-onClicked-listener-enabled");
|
||||
|
||||
clickPageAction(extension.id);
|
||||
clickPageAction(uuid);
|
||||
yield extension.awaitMessage("page-action-onClicked-fired");
|
||||
|
||||
extension.sendMessage("page-action-disable-onClicked-listener");
|
||||
@ -132,7 +137,7 @@ add_task(function* test_contentscript() {
|
||||
} else {
|
||||
ok(url.includes(name), "Calling pageAction.getPopup should return the correct popup URL when the popup is set.");
|
||||
|
||||
clickPageAction(extension.id);
|
||||
clickPageAction(uuid);
|
||||
let location = yield extension.awaitMessage("page-action-from-popup");
|
||||
ok(location.includes(name), "The popup with the correct URL should be shown.");
|
||||
|
||||
@ -144,19 +149,19 @@ add_task(function* test_contentscript() {
|
||||
}
|
||||
|
||||
yield extension.startup();
|
||||
yield extension.awaitMessage("ready");
|
||||
let {uuid} = yield extension.awaitMessage("ready");
|
||||
|
||||
extension.sendMessage("page-action-show");
|
||||
yield extension.awaitMessage("page-action-shown");
|
||||
ok(isPageActionShown(extension.id), "The PageAction should be shown.");
|
||||
ok(isPageActionShown(uuid), "The PageAction should be shown.");
|
||||
|
||||
yield testPopup("default.html");
|
||||
yield testPopup("a.html");
|
||||
yield testPopup("");
|
||||
yield testPopup("b.html");
|
||||
yield testPopup("default.html", uuid);
|
||||
yield testPopup("a.html", uuid);
|
||||
yield testPopup("", uuid);
|
||||
yield testPopup("b.html", uuid);
|
||||
|
||||
yield extension.unload();
|
||||
ok(!isPageActionShown(extension.id), "The PageAction should be removed after unload.");
|
||||
ok(!isPageActionShown(uuid), "The PageAction should be removed after unload.");
|
||||
});
|
||||
</script>
|
||||
|
||||
|
@ -6330,7 +6330,14 @@
|
||||
"kind": "boolean",
|
||||
"description": "Reports whether a decoder for an HTTP Live Streaming media type was created when requested.",
|
||||
"bug_numbers": [1262659]
|
||||
|
||||
},
|
||||
"MEDIA_DECODING_PROCESS_CRASH": {
|
||||
"alert_emails": ["bwu@mozilla.com", "jolin@mozilla.com", "jacheng@mozilla.com"],
|
||||
"expires_in_version": "57",
|
||||
"kind": "count",
|
||||
"bug_numbers": [1297556, 1257777],
|
||||
"description": "Records a value each time Fennec remote decoding process crashes unexpected while decoding media content.",
|
||||
"releaseChannelCollection": "opt-out"
|
||||
},
|
||||
"VIDEO_MFT_OUTPUT_NULL_SAMPLES": {
|
||||
"alert_emails": ["cpearce@mozilla.com"],
|
||||
|
@ -17,6 +17,7 @@ struct ScalarInfo {
|
||||
uint32_t name_offset;
|
||||
uint32_t expiration_offset;
|
||||
uint32_t dataset;
|
||||
bool keyed;
|
||||
|
||||
const char *name() const;
|
||||
const char *expiration() const;
|
||||
|
@ -71,6 +71,58 @@ telemetry.test:
|
||||
- telemetry-client-dev@mozilla.com
|
||||
release_channel_collection: opt-out
|
||||
|
||||
keyed_release_optin:
|
||||
bug_numbers:
|
||||
- 1277806
|
||||
description: A testing scalar; not meant to be touched.
|
||||
expires: never
|
||||
kind: uint
|
||||
keyed: true
|
||||
notification_emails:
|
||||
- telemetry-client-dev@mozilla.com
|
||||
release_channel_collection: opt-in
|
||||
|
||||
keyed_release_optout:
|
||||
bug_numbers:
|
||||
- 1277806
|
||||
description: A testing scalar; not meant to be touched.
|
||||
expires: never
|
||||
kind: uint
|
||||
keyed: true
|
||||
notification_emails:
|
||||
- telemetry-client-dev@mozilla.com
|
||||
release_channel_collection: opt-out
|
||||
|
||||
keyed_expired:
|
||||
bug_numbers:
|
||||
- 1277806
|
||||
description: This is an expired testing scalar; not meant to be touched.
|
||||
expires: 4.0a1
|
||||
kind: uint
|
||||
keyed: true
|
||||
notification_emails:
|
||||
- telemetry-client-dev@mozilla.com
|
||||
|
||||
keyed_unsigned_int:
|
||||
bug_numbers:
|
||||
- 1277806
|
||||
description: A testing keyed uint scalar; not meant to be touched.
|
||||
expires: never
|
||||
kind: uint
|
||||
keyed: true
|
||||
notification_emails:
|
||||
- telemetry-client-dev@mozilla.com
|
||||
|
||||
keyed_boolean_kind:
|
||||
bug_numbers:
|
||||
- 1277806
|
||||
description: A testing keyed boolean scalar; not meant to be touched.
|
||||
expires: never
|
||||
kind: boolean
|
||||
keyed: true
|
||||
notification_emails:
|
||||
- telemetry-client-dev@mozilla.com
|
||||
|
||||
# The following section contains the browser engagement scalars.
|
||||
browser.engagement:
|
||||
max_concurrent_tab_count:
|
||||
|
@ -2341,6 +2341,35 @@ TelemetryImpl::SnapshotScalars(unsigned int aDataset, bool aClearScalars, JSCont
|
||||
return TelemetryScalar::CreateSnapshots(aDataset, aClearScalars, aCx, optional_argc, aResult);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
TelemetryImpl::KeyedScalarAdd(const nsACString& aName, const nsAString& aKey,
|
||||
JS::HandleValue aVal, JSContext* aCx)
|
||||
{
|
||||
return TelemetryScalar::Add(aName, aKey, aVal, aCx);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
TelemetryImpl::KeyedScalarSet(const nsACString& aName, const nsAString& aKey,
|
||||
JS::HandleValue aVal, JSContext* aCx)
|
||||
{
|
||||
return TelemetryScalar::Set(aName, aKey, aVal, aCx);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
TelemetryImpl::KeyedScalarSetMaximum(const nsACString& aName, const nsAString& aKey,
|
||||
JS::HandleValue aVal, JSContext* aCx)
|
||||
{
|
||||
return TelemetryScalar::SetMaximum(aName, aKey, aVal, aCx);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
TelemetryImpl::SnapshotKeyedScalars(unsigned int aDataset, bool aClearScalars, JSContext* aCx,
|
||||
uint8_t optional_argc, JS::MutableHandleValue aResult)
|
||||
{
|
||||
return TelemetryScalar::CreateKeyedSnapshots(aDataset, aClearScalars, aCx, optional_argc,
|
||||
aResult);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
TelemetryImpl::ClearScalars()
|
||||
{
|
||||
@ -2967,53 +2996,59 @@ void DestroyStatisticsRecorder()
|
||||
|
||||
// Scalar API C++ Endpoints
|
||||
|
||||
/**
|
||||
* Adds the value to the given scalar.
|
||||
*
|
||||
* @param aId The scalar enum id.
|
||||
* @param aValue The unsigned value to add to the scalar.
|
||||
*/
|
||||
void
|
||||
ScalarAdd(mozilla::Telemetry::ScalarID aId, uint32_t aVal)
|
||||
{
|
||||
TelemetryScalar::Add(aId, aVal);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the scalar to the given value.
|
||||
*
|
||||
* @param aId The scalar enum id.
|
||||
* @param aValue The numeric, unsigned value to set the scalar to.
|
||||
*/
|
||||
void
|
||||
ScalarSet(mozilla::Telemetry::ScalarID aId, uint32_t aVal)
|
||||
{
|
||||
TelemetryScalar::Set(aId, aVal);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the scalar to the given value.
|
||||
*
|
||||
* @param aId The scalar enum id.
|
||||
* @param aValue The string value to set the scalar to.
|
||||
*/
|
||||
void
|
||||
ScalarSet(mozilla::Telemetry::ScalarID aId, bool aVal)
|
||||
{
|
||||
TelemetryScalar::Set(aId, aVal);
|
||||
}
|
||||
|
||||
void
|
||||
ScalarSet(mozilla::Telemetry::ScalarID aId, const nsAString& aVal)
|
||||
{
|
||||
TelemetryScalar::Set(aId, aVal);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the scalar to the maximum of the current and the passed value.
|
||||
*
|
||||
* @param aId The scalar enum id.
|
||||
* @param aValue The unsigned value to set the scalar to.
|
||||
*/
|
||||
void
|
||||
ScalarSetMaximum(mozilla::Telemetry::ScalarID aId, uint32_t aVal)
|
||||
{
|
||||
TelemetryScalar::SetMaximum(aId, aVal);
|
||||
}
|
||||
|
||||
void
|
||||
ScalarAdd(mozilla::Telemetry::ScalarID aId, const nsAString& aKey, uint32_t aVal)
|
||||
{
|
||||
TelemetryScalar::Add(aId, aKey, aVal);
|
||||
}
|
||||
|
||||
void
|
||||
ScalarSet(mozilla::Telemetry::ScalarID aId, const nsAString& aKey, uint32_t aVal)
|
||||
{
|
||||
TelemetryScalar::Set(aId, aKey, aVal);
|
||||
}
|
||||
|
||||
void
|
||||
ScalarSet(mozilla::Telemetry::ScalarID aId, const nsAString& aKey, bool aVal)
|
||||
{
|
||||
TelemetryScalar::Set(aId, aKey, aVal);
|
||||
}
|
||||
|
||||
void
|
||||
ScalarSetMaximum(mozilla::Telemetry::ScalarID aId, const nsAString& aKey, uint32_t aVal)
|
||||
{
|
||||
TelemetryScalar::SetMaximum(aId, aKey, aVal);
|
||||
}
|
||||
|
||||
} // namespace Telemetry
|
||||
} // namespace mozilla
|
||||
|
@ -363,7 +363,7 @@ void WriteFailedProfileLock(nsIFile* aProfileDir);
|
||||
* Adds the value to the given scalar.
|
||||
*
|
||||
* @param aId The scalar enum id.
|
||||
* @param aValue The unsigned value to add to the scalar.
|
||||
* @param aValue The value to add to the scalar.
|
||||
*/
|
||||
void ScalarAdd(mozilla::Telemetry::ScalarID aId, uint32_t aValue);
|
||||
|
||||
@ -371,7 +371,7 @@ void ScalarAdd(mozilla::Telemetry::ScalarID aId, uint32_t aValue);
|
||||
* Sets the scalar to the given value.
|
||||
*
|
||||
* @param aId The scalar enum id.
|
||||
* @param aValue The numeric, unsigned value to set the scalar to.
|
||||
* @param aValue The value to set the scalar to.
|
||||
*/
|
||||
void ScalarSet(mozilla::Telemetry::ScalarID aId, uint32_t aValue);
|
||||
|
||||
@ -379,7 +379,15 @@ void ScalarSet(mozilla::Telemetry::ScalarID aId, uint32_t aValue);
|
||||
* Sets the scalar to the given value.
|
||||
*
|
||||
* @param aId The scalar enum id.
|
||||
* @param aValue The string value to set the scalar to, truncated to
|
||||
* @param aValue The value to set the scalar to.
|
||||
*/
|
||||
void ScalarSet(mozilla::Telemetry::ScalarID aId, bool aValue);
|
||||
|
||||
/**
|
||||
* Sets the scalar to the given value.
|
||||
*
|
||||
* @param aId The scalar enum id.
|
||||
* @param aValue The value to set the scalar to, truncated to
|
||||
* 50 characters if exceeding that length.
|
||||
*/
|
||||
void ScalarSet(mozilla::Telemetry::ScalarID aId, const nsAString& aValue);
|
||||
@ -388,11 +396,48 @@ void ScalarSet(mozilla::Telemetry::ScalarID aId, const nsAString& aValue);
|
||||
* Sets the scalar to the maximum of the current and the passed value.
|
||||
*
|
||||
* @param aId The scalar enum id.
|
||||
* @param aValue The unsigned value the scalar is set to if its greater
|
||||
* @param aValue The value the scalar is set to if its greater
|
||||
* than the current value.
|
||||
*/
|
||||
void ScalarSetMaximum(mozilla::Telemetry::ScalarID aId, uint32_t aValue);
|
||||
|
||||
/**
|
||||
* Adds the value to the given scalar.
|
||||
*
|
||||
* @param aId The scalar enum id.
|
||||
* @param aKey The scalar key.
|
||||
* @param aValue The value to add to the scalar.
|
||||
*/
|
||||
void ScalarAdd(mozilla::Telemetry::ScalarID aId, const nsAString& aKey, uint32_t aValue);
|
||||
|
||||
/**
|
||||
* Sets the scalar to the given value.
|
||||
*
|
||||
* @param aId The scalar enum id.
|
||||
* @param aKey The scalar key.
|
||||
* @param aValue The value to set the scalar to.
|
||||
*/
|
||||
void ScalarSet(mozilla::Telemetry::ScalarID aId, const nsAString& aKey, uint32_t aValue);
|
||||
|
||||
/**
|
||||
* Sets the scalar to the given value.
|
||||
*
|
||||
* @param aId The scalar enum id.
|
||||
* @param aKey The scalar key.
|
||||
* @param aValue The value to set the scalar to.
|
||||
*/
|
||||
void ScalarSet(mozilla::Telemetry::ScalarID aId, const nsAString& aKey, bool aValue);
|
||||
|
||||
/**
|
||||
* Sets the scalar to the maximum of the current and the passed value.
|
||||
*
|
||||
* @param aId The scalar enum id.
|
||||
* @param aKey The scalar key.
|
||||
* @param aValue The value the scalar is set to if its greater
|
||||
* than the current value.
|
||||
*/
|
||||
void ScalarSetMaximum(mozilla::Telemetry::ScalarID aId, const nsAString& aKey, uint32_t aValue);
|
||||
|
||||
} // namespace Telemetry
|
||||
} // namespace mozilla
|
||||
|
||||
|
@ -70,6 +70,8 @@ using mozilla::Telemetry::Common::IsInDataset;
|
||||
|
||||
namespace {
|
||||
|
||||
const uint32_t kMaximumNumberOfKeys = 100;
|
||||
const uint32_t kMaximumKeyStringLength = 70;
|
||||
const uint32_t kMaximumStringValueLength = 50;
|
||||
const uint32_t kScalarCount =
|
||||
static_cast<uint32_t>(mozilla::Telemetry::ScalarID::ScalarCount);
|
||||
@ -81,6 +83,9 @@ enum class ScalarResult : uint8_t {
|
||||
OperationNotSupported,
|
||||
InvalidType,
|
||||
InvalidValue,
|
||||
// Keyed Scalar Errors
|
||||
KeyTooLong,
|
||||
TooManyKeys,
|
||||
// String Scalar Errors
|
||||
StringTooLong,
|
||||
// Unsigned Scalar Errors
|
||||
@ -111,7 +116,10 @@ MapToNsResult(ScalarResult aSr)
|
||||
return NS_OK;
|
||||
case ScalarResult::InvalidType:
|
||||
case ScalarResult::InvalidValue:
|
||||
case ScalarResult::KeyTooLong:
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
case ScalarResult::TooManyKeys:
|
||||
return NS_ERROR_FAILURE;
|
||||
case ScalarResult::UnsignedNegativeValue:
|
||||
case ScalarResult::UnsignedTruncatedValue:
|
||||
// We shouldn't throw if trying to set a negative number or are truncated,
|
||||
@ -471,8 +479,236 @@ ScalarBoolean::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
|
||||
return aMallocSizeOf(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Allocate a scalar class given the scalar info.
|
||||
*
|
||||
* @param aInfo The informations for the scalar coming from the definition file.
|
||||
* @return nullptr if the scalar type is unknown, otherwise a valid pointer to the
|
||||
* scalar type.
|
||||
*/
|
||||
ScalarBase*
|
||||
internal_ScalarAllocate(uint32_t aScalarKind)
|
||||
{
|
||||
ScalarBase* scalar = nullptr;
|
||||
switch (aScalarKind) {
|
||||
case nsITelemetry::SCALAR_COUNT:
|
||||
scalar = new ScalarUnsigned();
|
||||
break;
|
||||
case nsITelemetry::SCALAR_STRING:
|
||||
scalar = new ScalarString();
|
||||
break;
|
||||
case nsITelemetry::SCALAR_BOOLEAN:
|
||||
scalar = new ScalarBoolean();
|
||||
break;
|
||||
default:
|
||||
MOZ_ASSERT(false, "Invalid scalar type");
|
||||
}
|
||||
return scalar;
|
||||
}
|
||||
|
||||
/**
|
||||
* The implementation for the keyed scalar type.
|
||||
*/
|
||||
class KeyedScalar
|
||||
{
|
||||
public:
|
||||
typedef mozilla::Pair<nsCString, nsCOMPtr<nsIVariant>> KeyValuePair;
|
||||
|
||||
explicit KeyedScalar(uint32_t aScalarKind) : mScalarKind(aScalarKind) {};
|
||||
~KeyedScalar() {};
|
||||
|
||||
// Set, Add and SetMaximum functions as described in the Telemetry IDL.
|
||||
// These methods implicitly instantiate a Scalar[*] for each key.
|
||||
ScalarResult SetValue(const nsAString& aKey, nsIVariant* aValue);
|
||||
ScalarResult AddValue(const nsAString& aKey, nsIVariant* aValue);
|
||||
ScalarResult SetMaximum(const nsAString& aKey, nsIVariant* aValue);
|
||||
|
||||
// Convenience methods used by the C++ API.
|
||||
void SetValue(const nsAString& aKey, uint32_t aValue);
|
||||
void SetValue(const nsAString& aKey, bool aValue);
|
||||
void AddValue(const nsAString& aKey, uint32_t aValue);
|
||||
void SetMaximum(const nsAString& aKey, uint32_t aValue);
|
||||
|
||||
// GetValue is used to get the key-value pairs stored in the keyed scalar
|
||||
// when persisting it to JS.
|
||||
nsresult GetValue(nsTArray<KeyValuePair>& aValues) const;
|
||||
|
||||
// To measure the memory stats.
|
||||
size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf);
|
||||
|
||||
private:
|
||||
typedef nsClassHashtable<nsCStringHashKey, ScalarBase> ScalarKeysMapType;
|
||||
|
||||
ScalarKeysMapType mScalarKeys;
|
||||
const uint32_t mScalarKind;
|
||||
|
||||
ScalarResult GetScalarForKey(const nsAString& aKey, ScalarBase** aRet);
|
||||
};
|
||||
|
||||
ScalarResult
|
||||
KeyedScalar::SetValue(const nsAString& aKey, nsIVariant* aValue)
|
||||
{
|
||||
ScalarBase* scalar = nullptr;
|
||||
ScalarResult sr = GetScalarForKey(aKey, &scalar);
|
||||
if (sr != ScalarResult::Ok) {
|
||||
return sr;
|
||||
}
|
||||
|
||||
return scalar->SetValue(aValue);
|
||||
}
|
||||
|
||||
ScalarResult
|
||||
KeyedScalar::AddValue(const nsAString& aKey, nsIVariant* aValue)
|
||||
{
|
||||
ScalarBase* scalar = nullptr;
|
||||
ScalarResult sr = GetScalarForKey(aKey, &scalar);
|
||||
if (sr != ScalarResult::Ok) {
|
||||
return sr;
|
||||
}
|
||||
|
||||
return scalar->AddValue(aValue);
|
||||
}
|
||||
|
||||
ScalarResult
|
||||
KeyedScalar::SetMaximum(const nsAString& aKey, nsIVariant* aValue)
|
||||
{
|
||||
ScalarBase* scalar = nullptr;
|
||||
ScalarResult sr = GetScalarForKey(aKey, &scalar);
|
||||
if (sr != ScalarResult::Ok) {
|
||||
return sr;
|
||||
}
|
||||
|
||||
return scalar->SetMaximum(aValue);
|
||||
}
|
||||
|
||||
void
|
||||
KeyedScalar::SetValue(const nsAString& aKey, uint32_t aValue)
|
||||
{
|
||||
ScalarBase* scalar = nullptr;
|
||||
ScalarResult sr = GetScalarForKey(aKey, &scalar);
|
||||
if (sr != ScalarResult::Ok) {
|
||||
MOZ_ASSERT(false, "Key too long or too many keys are recorded in the scalar.");
|
||||
return;
|
||||
}
|
||||
|
||||
return scalar->SetValue(aValue);
|
||||
}
|
||||
|
||||
void
|
||||
KeyedScalar::SetValue(const nsAString& aKey, bool aValue)
|
||||
{
|
||||
ScalarBase* scalar = nullptr;
|
||||
ScalarResult sr = GetScalarForKey(aKey, &scalar);
|
||||
if (sr != ScalarResult::Ok) {
|
||||
MOZ_ASSERT(false, "Key too long or too many keys are recorded in the scalar.");
|
||||
return;
|
||||
}
|
||||
|
||||
return scalar->SetValue(aValue);
|
||||
}
|
||||
|
||||
void
|
||||
KeyedScalar::AddValue(const nsAString& aKey, uint32_t aValue)
|
||||
{
|
||||
ScalarBase* scalar = nullptr;
|
||||
ScalarResult sr = GetScalarForKey(aKey, &scalar);
|
||||
if (sr != ScalarResult::Ok) {
|
||||
MOZ_ASSERT(false, "Key too long or too many keys are recorded in the scalar.");
|
||||
return;
|
||||
}
|
||||
|
||||
return scalar->AddValue(aValue);
|
||||
}
|
||||
|
||||
void
|
||||
KeyedScalar::SetMaximum(const nsAString& aKey, uint32_t aValue)
|
||||
{
|
||||
ScalarBase* scalar = nullptr;
|
||||
ScalarResult sr = GetScalarForKey(aKey, &scalar);
|
||||
if (sr != ScalarResult::Ok) {
|
||||
MOZ_ASSERT(false, "Key too long or too many keys are recorded in the scalar.");
|
||||
return;
|
||||
}
|
||||
|
||||
return scalar->SetMaximum(aValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a key-value array with the values for the Keyed Scalar.
|
||||
* @param aValue The array that will hold the key-value pairs.
|
||||
* @return {nsresult} NS_OK or an error value as reported by the
|
||||
* the specific scalar objects implementations (e.g.
|
||||
* ScalarUnsigned).
|
||||
*/
|
||||
nsresult
|
||||
KeyedScalar::GetValue(nsTArray<KeyValuePair>& aValues) const
|
||||
{
|
||||
for (auto iter = mScalarKeys.ConstIter(); !iter.Done(); iter.Next()) {
|
||||
ScalarBase* scalar = static_cast<ScalarBase*>(iter.Data());
|
||||
|
||||
// Get the scalar value.
|
||||
nsCOMPtr<nsIVariant> scalarValue;
|
||||
nsresult rv = scalar->GetValue(scalarValue);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
// Append it to value list.
|
||||
aValues.AppendElement(mozilla::MakePair(nsCString(iter.Key()), scalarValue));
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the scalar for the referenced key.
|
||||
* If there's no such key, instantiate a new Scalar object with the
|
||||
* same type of the Keyed scalar and create the key.
|
||||
*/
|
||||
ScalarResult
|
||||
KeyedScalar::GetScalarForKey(const nsAString& aKey, ScalarBase** aRet)
|
||||
{
|
||||
if (aKey.Length() >= kMaximumKeyStringLength) {
|
||||
return ScalarResult::KeyTooLong;
|
||||
}
|
||||
|
||||
if (mScalarKeys.Count() >= kMaximumNumberOfKeys) {
|
||||
return ScalarResult::TooManyKeys;
|
||||
}
|
||||
|
||||
NS_ConvertUTF16toUTF8 utf8Key(aKey);
|
||||
|
||||
ScalarBase* scalar = nullptr;
|
||||
if (mScalarKeys.Get(utf8Key, &scalar)) {
|
||||
*aRet = scalar;
|
||||
return ScalarResult::Ok;
|
||||
}
|
||||
|
||||
scalar = internal_ScalarAllocate(mScalarKind);
|
||||
if (!scalar) {
|
||||
return ScalarResult::InvalidType;
|
||||
}
|
||||
|
||||
mScalarKeys.Put(utf8Key, scalar);
|
||||
|
||||
*aRet = scalar;
|
||||
return ScalarResult::Ok;
|
||||
}
|
||||
|
||||
size_t
|
||||
KeyedScalar::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf)
|
||||
{
|
||||
size_t n = aMallocSizeOf(this);
|
||||
for (auto iter = mScalarKeys.Iter(); !iter.Done(); iter.Next()) {
|
||||
ScalarBase* scalar = static_cast<ScalarBase*>(iter.Data());
|
||||
n += scalar->SizeOfIncludingThis(aMallocSizeOf);
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
typedef nsUint32HashKey ScalarIDHashKey;
|
||||
typedef nsClassHashtable<ScalarIDHashKey, ScalarBase> ScalarStorageMapType;
|
||||
typedef nsClassHashtable<ScalarIDHashKey, KeyedScalar> KeyedScalarStorageMapType;
|
||||
|
||||
} // namespace
|
||||
|
||||
@ -495,6 +731,9 @@ ScalarMapType gScalarNameIDMap(kScalarCount);
|
||||
// the scalar instance and takes care of deallocating them when they
|
||||
// get removed from the map.
|
||||
ScalarStorageMapType gScalarStorageMap;
|
||||
// The ID -> Keyed Scalar Object map. As for plain scalars, this is
|
||||
// nsClassHashtable. See above.
|
||||
KeyedScalarStorageMapType gKeyedScalarStorageMap;
|
||||
|
||||
} // namespace
|
||||
|
||||
@ -551,12 +790,20 @@ internal_LogToBrowserConsole(uint32_t aLogLevel, const nsAString& aMsg)
|
||||
bool
|
||||
internal_ShouldLogError(ScalarResult aSr)
|
||||
{
|
||||
if (aSr == ScalarResult::StringTooLong ||
|
||||
aSr == ScalarResult::UnsignedNegativeValue ||
|
||||
aSr == ScalarResult::UnsignedTruncatedValue) {
|
||||
switch (aSr) {
|
||||
case ScalarResult::StringTooLong: MOZ_FALLTHROUGH;
|
||||
case ScalarResult::KeyTooLong: MOZ_FALLTHROUGH;
|
||||
case ScalarResult::TooManyKeys: MOZ_FALLTHROUGH;
|
||||
case ScalarResult::UnsignedNegativeValue: MOZ_FALLTHROUGH;
|
||||
case ScalarResult::UnsignedTruncatedValue:
|
||||
// Intentional fall-through.
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
// It should never reach this point.
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -577,6 +824,12 @@ internal_LogScalarError(const nsACString& aScalarName, ScalarResult aSr)
|
||||
case ScalarResult::StringTooLong:
|
||||
errorMessage.Append(NS_LITERAL_STRING(" - Truncating scalar value to 50 characters."));
|
||||
break;
|
||||
case ScalarResult::KeyTooLong:
|
||||
errorMessage.Append(NS_LITERAL_STRING(" - The key length must be limited to 70 characters."));
|
||||
break;
|
||||
case ScalarResult::TooManyKeys:
|
||||
errorMessage.Append(NS_LITERAL_STRING(" - Keyed scalars cannot have more than 100 keys."));
|
||||
break;
|
||||
case ScalarResult::UnsignedNegativeValue:
|
||||
errorMessage.Append(NS_LITERAL_STRING(" - Trying to set an unsigned scalar to a negative number."));
|
||||
break;
|
||||
@ -618,6 +871,18 @@ internal_InfoForScalarID(mozilla::Telemetry::ScalarID aId)
|
||||
return gScalars[static_cast<uint32_t>(aId)];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given scalar is a keyed scalar.
|
||||
*
|
||||
* @param aId The scalar enum.
|
||||
* @return true if aId refers to a keyed scalar, false otherwise.
|
||||
*/
|
||||
bool
|
||||
internal_IsKeyedScalar(mozilla::Telemetry::ScalarID aId)
|
||||
{
|
||||
return internal_InfoForScalarID(aId).keyed;
|
||||
}
|
||||
|
||||
bool
|
||||
internal_CanRecordForScalarID(mozilla::Telemetry::ScalarID aId)
|
||||
{
|
||||
@ -640,33 +905,6 @@ internal_CanRecordForScalarID(mozilla::Telemetry::ScalarID aId)
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allocate a scalar class given the scalar info.
|
||||
*
|
||||
* @param aInfo The informations for the scalar coming from the definition file.
|
||||
* @return nullptr if the scalar type is unknown, otherwise a valid pointer to the
|
||||
* scalar type.
|
||||
*/
|
||||
ScalarBase*
|
||||
internal_ScalarAllocate(const ScalarInfo& aInfo)
|
||||
{
|
||||
ScalarBase* scalar = nullptr;
|
||||
switch (aInfo.kind) {
|
||||
case nsITelemetry::SCALAR_COUNT:
|
||||
scalar = new ScalarUnsigned();
|
||||
break;
|
||||
case nsITelemetry::SCALAR_STRING:
|
||||
scalar = new ScalarString();
|
||||
break;
|
||||
case nsITelemetry::SCALAR_BOOLEAN:
|
||||
scalar = new ScalarBoolean();
|
||||
break;
|
||||
default:
|
||||
MOZ_ASSERT(false, "Invalid scalar type");
|
||||
}
|
||||
return scalar;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the scalar enum id from the scalar name.
|
||||
*
|
||||
@ -708,6 +946,7 @@ nsresult
|
||||
internal_GetScalarByEnum(mozilla::Telemetry::ScalarID aId, ScalarBase** aRet)
|
||||
{
|
||||
if (!IsValidEnumId(aId)) {
|
||||
MOZ_ASSERT(false, "Requested a scalar with an invalid id.");
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
@ -725,7 +964,7 @@ internal_GetScalarByEnum(mozilla::Telemetry::ScalarID aId, ScalarBase** aRet)
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
scalar = internal_ScalarAllocate(info);
|
||||
scalar = internal_ScalarAllocate(info.kind);
|
||||
if (!scalar) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
@ -753,6 +992,102 @@ internal_GetRecordableScalar(mozilla::Telemetry::ScalarID aId)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (internal_IsKeyedScalar(aId)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Are we allowed to record this scalar?
|
||||
if (!internal_CanRecordForScalarID(aId)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return scalar;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// PRIVATE: thread-unsafe helpers for the keyed scalars
|
||||
|
||||
namespace {
|
||||
|
||||
/**
|
||||
* Get a keyed scalar object by its enum id. This implicitly allocates the keyed
|
||||
* scalar object in the storage if it wasn't previously allocated.
|
||||
*
|
||||
* @param aId The scalar id.
|
||||
* @param aRes The output variable that stores scalar object.
|
||||
* @return
|
||||
* NS_ERROR_INVALID_ARG if the scalar id is unknown or a this is a keyed string
|
||||
* scalar.
|
||||
* NS_ERROR_NOT_AVAILABLE if the scalar is expired.
|
||||
* NS_OK if the scalar was found. If that's the case, aResult contains a
|
||||
* valid pointer to a scalar type.
|
||||
*/
|
||||
nsresult
|
||||
internal_GetKeyedScalarByEnum(mozilla::Telemetry::ScalarID aId, KeyedScalar** aRet)
|
||||
{
|
||||
if (!IsValidEnumId(aId)) {
|
||||
MOZ_ASSERT(false, "Requested a keyed scalar with an invalid id.");
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
const uint32_t id = static_cast<uint32_t>(aId);
|
||||
|
||||
KeyedScalar* scalar = nullptr;
|
||||
if (gKeyedScalarStorageMap.Get(id, &scalar)) {
|
||||
*aRet = scalar;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
const ScalarInfo &info = gScalars[id];
|
||||
|
||||
if (IsExpiredVersion(info.expiration())) {
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
// We don't currently support keyed string scalars. Disable them.
|
||||
if (info.kind == nsITelemetry::SCALAR_STRING) {
|
||||
MOZ_ASSERT(false, "Keyed string scalars are not currently supported.");
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
scalar = new KeyedScalar(info.kind);
|
||||
if (!scalar) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
gKeyedScalarStorageMap.Put(id, scalar);
|
||||
|
||||
*aRet = scalar;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a keyed scalar object by its enum id, if we're allowed to record it.
|
||||
*
|
||||
* @param aId The scalar id.
|
||||
* @return The KeyedScalar instance or nullptr if we're not allowed to record
|
||||
* the scalar.
|
||||
*/
|
||||
KeyedScalar*
|
||||
internal_GetRecordableKeyedScalar(mozilla::Telemetry::ScalarID aId)
|
||||
{
|
||||
// Get the scalar by the enum (it also internally checks for aId validity).
|
||||
KeyedScalar* scalar = nullptr;
|
||||
nsresult rv = internal_GetKeyedScalarByEnum(aId, &scalar);
|
||||
if (NS_FAILED(rv)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!internal_IsKeyedScalar(aId)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Are we allowed to record this scalar?
|
||||
if (!internal_CanRecordForScalarID(aId)) {
|
||||
return nullptr;
|
||||
@ -812,6 +1147,7 @@ TelemetryScalar::DeInitializeGlobalState()
|
||||
gCanRecordExtended = false;
|
||||
gScalarNameIDMap.Clear();
|
||||
gScalarStorageMap.Clear();
|
||||
gKeyedScalarStorageMap.Clear();
|
||||
gInitDone = false;
|
||||
}
|
||||
|
||||
@ -834,7 +1170,7 @@ TelemetryScalar::SetCanRecordExtended(bool b) {
|
||||
* @param aName The scalar name.
|
||||
* @param aVal The numeric value to add to the scalar.
|
||||
* @param aCx The JS context.
|
||||
* @return NS_OK if the value was added or if we're not allow to record to this
|
||||
* @return NS_OK if the value was added or if we're not allowed to record to this
|
||||
* dataset. Otherwise, return an error.
|
||||
*/
|
||||
nsresult
|
||||
@ -858,6 +1194,11 @@ TelemetryScalar::Add(const nsACString& aName, JS::HandleValue aVal, JSContext* a
|
||||
return rv;
|
||||
}
|
||||
|
||||
// We're trying to set a plain scalar, so make sure this is one.
|
||||
if (internal_IsKeyedScalar(id)) {
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
|
||||
// Are we allowed to record this scalar?
|
||||
if (!internal_CanRecordForScalarID(id)) {
|
||||
return NS_OK;
|
||||
@ -885,6 +1226,70 @@ TelemetryScalar::Add(const nsACString& aName, JS::HandleValue aVal, JSContext* a
|
||||
return MapToNsResult(sr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the value to the given scalar.
|
||||
*
|
||||
* @param aName The scalar name.
|
||||
* @param aKey The key name.
|
||||
* @param aVal The numeric value to add to the scalar.
|
||||
* @param aCx The JS context.
|
||||
* @return NS_OK if the value was added or if we're not allow to record to this
|
||||
* dataset. Otherwise, return an error.
|
||||
*/
|
||||
nsresult
|
||||
TelemetryScalar::Add(const nsACString& aName, const nsAString& aKey, JS::HandleValue aVal,
|
||||
JSContext* aCx)
|
||||
{
|
||||
// Unpack the aVal to nsIVariant. This uses the JS context.
|
||||
nsCOMPtr<nsIVariant> unpackedVal;
|
||||
nsresult rv =
|
||||
nsContentUtils::XPConnect()->JSToVariant(aCx, aVal, getter_AddRefs(unpackedVal));
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
ScalarResult sr;
|
||||
{
|
||||
StaticMutexAutoLock locker(gTelemetryScalarsMutex);
|
||||
|
||||
mozilla::Telemetry::ScalarID id;
|
||||
rv = internal_GetEnumByScalarName(aName, &id);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
// Make sure this is a keyed scalar.
|
||||
if (!internal_IsKeyedScalar(id)) {
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
|
||||
// Are we allowed to record this scalar?
|
||||
if (!internal_CanRecordForScalarID(id)) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Finally get the scalar.
|
||||
KeyedScalar* scalar = nullptr;
|
||||
rv = internal_GetKeyedScalarByEnum(id, &scalar);
|
||||
if (NS_FAILED(rv)) {
|
||||
// Don't throw on expired scalars.
|
||||
if (rv == NS_ERROR_NOT_AVAILABLE) {
|
||||
return NS_OK;
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
sr = scalar->AddValue(aKey, unpackedVal);
|
||||
}
|
||||
|
||||
// Warn the user about the error if we need to.
|
||||
if (internal_ShouldLogError(sr)) {
|
||||
internal_LogScalarError(aName, sr);
|
||||
}
|
||||
|
||||
return MapToNsResult(sr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the value to the given scalar.
|
||||
*
|
||||
@ -904,6 +1309,27 @@ TelemetryScalar::Add(mozilla::Telemetry::ScalarID aId, uint32_t aValue)
|
||||
scalar->AddValue(aValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the value to the given keyed scalar.
|
||||
*
|
||||
* @param aId The scalar enum id.
|
||||
* @param aKey The key name.
|
||||
* @param aVal The numeric value to add to the scalar.
|
||||
*/
|
||||
void
|
||||
TelemetryScalar::Add(mozilla::Telemetry::ScalarID aId, const nsAString& aKey,
|
||||
uint32_t aValue)
|
||||
{
|
||||
StaticMutexAutoLock locker(gTelemetryScalarsMutex);
|
||||
|
||||
KeyedScalar* scalar = internal_GetRecordableKeyedScalar(aId);
|
||||
if (!scalar) {
|
||||
return;
|
||||
}
|
||||
|
||||
scalar->AddValue(aKey, aValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the scalar to the given value.
|
||||
*
|
||||
@ -934,6 +1360,11 @@ TelemetryScalar::Set(const nsACString& aName, JS::HandleValue aVal, JSContext* a
|
||||
return rv;
|
||||
}
|
||||
|
||||
// We're trying to set a plain scalar, so make sure this is one.
|
||||
if (internal_IsKeyedScalar(id)) {
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
|
||||
// Are we allowed to record this scalar?
|
||||
if (!internal_CanRecordForScalarID(id)) {
|
||||
return NS_OK;
|
||||
@ -961,6 +1392,70 @@ TelemetryScalar::Set(const nsACString& aName, JS::HandleValue aVal, JSContext* a
|
||||
return MapToNsResult(sr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the keyed scalar to the given value.
|
||||
*
|
||||
* @param aName The scalar name.
|
||||
* @param aKey The key name.
|
||||
* @param aVal The value to set the scalar to.
|
||||
* @param aCx The JS context.
|
||||
* @return NS_OK if the value was added or if we're not allow to record to this
|
||||
* dataset. Otherwise, return an error.
|
||||
*/
|
||||
nsresult
|
||||
TelemetryScalar::Set(const nsACString& aName, const nsAString& aKey, JS::HandleValue aVal,
|
||||
JSContext* aCx)
|
||||
{
|
||||
// Unpack the aVal to nsIVariant. This uses the JS context.
|
||||
nsCOMPtr<nsIVariant> unpackedVal;
|
||||
nsresult rv =
|
||||
nsContentUtils::XPConnect()->JSToVariant(aCx, aVal, getter_AddRefs(unpackedVal));
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
ScalarResult sr;
|
||||
{
|
||||
StaticMutexAutoLock locker(gTelemetryScalarsMutex);
|
||||
|
||||
mozilla::Telemetry::ScalarID id;
|
||||
rv = internal_GetEnumByScalarName(aName, &id);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
// We're trying to set a keyed scalar. Report an error if this isn't one.
|
||||
if (!internal_IsKeyedScalar(id)) {
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
|
||||
// Are we allowed to record this scalar?
|
||||
if (!internal_CanRecordForScalarID(id)) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Finally get the scalar.
|
||||
KeyedScalar* scalar = nullptr;
|
||||
rv = internal_GetKeyedScalarByEnum(id, &scalar);
|
||||
if (NS_FAILED(rv)) {
|
||||
// Don't throw on expired scalars.
|
||||
if (rv == NS_ERROR_NOT_AVAILABLE) {
|
||||
return NS_OK;
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
sr = scalar->SetValue(aKey, unpackedVal);
|
||||
}
|
||||
|
||||
// Warn the user about the error if we need to.
|
||||
if (internal_ShouldLogError(sr)) {
|
||||
internal_LogScalarError(aName, sr);
|
||||
}
|
||||
|
||||
return MapToNsResult(sr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the scalar to the given numeric value.
|
||||
*
|
||||
@ -1018,6 +1513,48 @@ TelemetryScalar::Set(mozilla::Telemetry::ScalarID aId, bool aValue)
|
||||
scalar->SetValue(aValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the keyed scalar to the given numeric value.
|
||||
*
|
||||
* @param aId The scalar enum id.
|
||||
* @param aKey The scalar key.
|
||||
* @param aValue The numeric, unsigned value to set the scalar to.
|
||||
*/
|
||||
void
|
||||
TelemetryScalar::Set(mozilla::Telemetry::ScalarID aId, const nsAString& aKey,
|
||||
uint32_t aValue)
|
||||
{
|
||||
StaticMutexAutoLock locker(gTelemetryScalarsMutex);
|
||||
|
||||
KeyedScalar* scalar = internal_GetRecordableKeyedScalar(aId);
|
||||
if (!scalar) {
|
||||
return;
|
||||
}
|
||||
|
||||
scalar->SetValue(aKey, aValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the scalar to the given boolean value.
|
||||
*
|
||||
* @param aId The scalar enum id.
|
||||
* @param aKey The scalar key.
|
||||
* @param aValue The boolean value to set the scalar to.
|
||||
*/
|
||||
void
|
||||
TelemetryScalar::Set(mozilla::Telemetry::ScalarID aId, const nsAString& aKey,
|
||||
bool aValue)
|
||||
{
|
||||
StaticMutexAutoLock locker(gTelemetryScalarsMutex);
|
||||
|
||||
KeyedScalar* scalar = internal_GetRecordableKeyedScalar(aId);
|
||||
if (!scalar) {
|
||||
return;
|
||||
}
|
||||
|
||||
scalar->SetValue(aKey, aValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the scalar to the maximum of the current and the passed value.
|
||||
*
|
||||
@ -1048,6 +1585,11 @@ TelemetryScalar::SetMaximum(const nsACString& aName, JS::HandleValue aVal, JSCon
|
||||
return rv;
|
||||
}
|
||||
|
||||
// Make sure this is not a keyed scalar.
|
||||
if (internal_IsKeyedScalar(id)) {
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
|
||||
// Are we allowed to record this scalar?
|
||||
if (!internal_CanRecordForScalarID(id)) {
|
||||
return NS_OK;
|
||||
@ -1075,6 +1617,70 @@ TelemetryScalar::SetMaximum(const nsACString& aName, JS::HandleValue aVal, JSCon
|
||||
return MapToNsResult(sr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the scalar to the maximum of the current and the passed value.
|
||||
*
|
||||
* @param aName The scalar name.
|
||||
* @param aKey The key name.
|
||||
* @param aVal The numeric value to set the scalar to.
|
||||
* @param aCx The JS context.
|
||||
* @return NS_OK if the value was added or if we're not allow to record to this
|
||||
* dataset. Otherwise, return an error.
|
||||
*/
|
||||
nsresult
|
||||
TelemetryScalar::SetMaximum(const nsACString& aName, const nsAString& aKey, JS::HandleValue aVal,
|
||||
JSContext* aCx)
|
||||
{
|
||||
// Unpack the aVal to nsIVariant. This uses the JS context.
|
||||
nsCOMPtr<nsIVariant> unpackedVal;
|
||||
nsresult rv =
|
||||
nsContentUtils::XPConnect()->JSToVariant(aCx, aVal, getter_AddRefs(unpackedVal));
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
ScalarResult sr;
|
||||
{
|
||||
StaticMutexAutoLock locker(gTelemetryScalarsMutex);
|
||||
|
||||
mozilla::Telemetry::ScalarID id;
|
||||
rv = internal_GetEnumByScalarName(aName, &id);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
// Make sure this is a keyed scalar.
|
||||
if (!internal_IsKeyedScalar(id)) {
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
|
||||
// Are we allowed to record this scalar?
|
||||
if (!internal_CanRecordForScalarID(id)) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Finally get the scalar.
|
||||
KeyedScalar* scalar = nullptr;
|
||||
rv = internal_GetKeyedScalarByEnum(id, &scalar);
|
||||
if (NS_FAILED(rv)) {
|
||||
// Don't throw on expired scalars.
|
||||
if (rv == NS_ERROR_NOT_AVAILABLE) {
|
||||
return NS_OK;
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
sr = scalar->SetMaximum(aKey, unpackedVal);
|
||||
}
|
||||
|
||||
// Warn the user about the error if we need to.
|
||||
if (internal_ShouldLogError(sr)) {
|
||||
internal_LogScalarError(aName, sr);
|
||||
}
|
||||
|
||||
return MapToNsResult(sr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the scalar to the maximum of the current and the passed value.
|
||||
*
|
||||
@ -1094,6 +1700,27 @@ TelemetryScalar::SetMaximum(mozilla::Telemetry::ScalarID aId, uint32_t aValue)
|
||||
scalar->SetValue(aValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the keyed scalar to the maximum of the current and the passed value.
|
||||
*
|
||||
* @param aId The scalar enum id.
|
||||
* @param aKey The key name.
|
||||
* @param aValue The numeric value to set the scalar to.
|
||||
*/
|
||||
void
|
||||
TelemetryScalar::SetMaximum(mozilla::Telemetry::ScalarID aId, const nsAString& aKey,
|
||||
uint32_t aValue)
|
||||
{
|
||||
StaticMutexAutoLock locker(gTelemetryScalarsMutex);
|
||||
|
||||
KeyedScalar* scalar = internal_GetRecordableKeyedScalar(aId);
|
||||
if (!scalar) {
|
||||
return;
|
||||
}
|
||||
|
||||
scalar->SetValue(aKey, aValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes the scalars from the given dataset to a json-style object and resets them.
|
||||
* The returned structure looks like {"group1.probe":1,"group1.other_probe":false,...}.
|
||||
@ -1169,6 +1796,99 @@ TelemetryScalar::CreateSnapshots(unsigned int aDataset, bool aClearScalars, JSCo
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes the scalars from the given dataset to a json-style object and resets them.
|
||||
* The returned structure looks like:
|
||||
* { "group1.probe": { "key_1": 2, "key_2": 1, ... }, ... }
|
||||
*
|
||||
* @param aDataset DATASET_RELEASE_CHANNEL_OPTOUT or DATASET_RELEASE_CHANNEL_OPTIN.
|
||||
* @param aClear Whether to clear out the keyed scalars after snapshotting.
|
||||
*/
|
||||
nsresult
|
||||
TelemetryScalar::CreateKeyedSnapshots(unsigned int aDataset, bool aClearScalars, JSContext* aCx,
|
||||
uint8_t optional_argc, JS::MutableHandle<JS::Value> aResult)
|
||||
{
|
||||
// If no arguments were passed in, apply the default value.
|
||||
if (!optional_argc) {
|
||||
aClearScalars = false;
|
||||
}
|
||||
|
||||
JS::Rooted<JSObject*> root_obj(aCx, JS_NewPlainObject(aCx));
|
||||
if (!root_obj) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
aResult.setObject(*root_obj);
|
||||
|
||||
// Only lock the mutex while accessing our data, without locking any JS related code.
|
||||
typedef mozilla::Pair<const char*, nsTArray<KeyedScalar::KeyValuePair>> DataPair;
|
||||
nsTArray<DataPair> scalarsToReflect;
|
||||
{
|
||||
StaticMutexAutoLock locker(gTelemetryScalarsMutex);
|
||||
// Iterate the scalars in gKeyedScalarStorageMap. The storage may contain empty or yet
|
||||
// to be initialized scalars.
|
||||
for (auto iter = gKeyedScalarStorageMap.Iter(); !iter.Done(); iter.Next()) {
|
||||
KeyedScalar* scalar = static_cast<KeyedScalar*>(iter.Data());
|
||||
|
||||
// Get the informations for this scalar.
|
||||
const ScalarInfo& info = gScalars[iter.Key()];
|
||||
|
||||
// Serialize the scalar if it's in the desired dataset.
|
||||
if (IsInDataset(info.dataset, aDataset)) {
|
||||
// Get the keys for this scalar.
|
||||
nsTArray<KeyedScalar::KeyValuePair> scalarKeyedData;
|
||||
nsresult rv = scalar->GetValue(scalarKeyedData);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
// Append it to our list.
|
||||
scalarsToReflect.AppendElement(mozilla::MakePair(info.name(), scalarKeyedData));
|
||||
}
|
||||
}
|
||||
|
||||
if (aClearScalars) {
|
||||
// The map already takes care of freeing the allocated memory.
|
||||
gKeyedScalarStorageMap.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
// Reflect it to JS.
|
||||
for (nsTArray<DataPair>::size_type i = 0; i < scalarsToReflect.Length(); i++) {
|
||||
const DataPair& keyedScalarData = scalarsToReflect[i];
|
||||
|
||||
// Go through each keyed scalar and create a keyed scalar object.
|
||||
// This object will hold the values for all the keyed scalar keys.
|
||||
JS::RootedObject keyedScalarObj(aCx, JS_NewPlainObject(aCx));
|
||||
|
||||
// Define a property for each scalar key, then add it to the keyed scalar
|
||||
// object.
|
||||
const nsTArray<KeyedScalar::KeyValuePair>& keyProps = keyedScalarData.second();
|
||||
for (uint32_t i = 0; i < keyProps.Length(); i++) {
|
||||
const KeyedScalar::KeyValuePair& keyData = keyProps[i];
|
||||
|
||||
// Convert the value for the key to a JSValue.
|
||||
JS::Rooted<JS::Value> keyJsValue(aCx);
|
||||
nsresult rv =
|
||||
nsContentUtils::XPConnect()->VariantToJS(aCx, keyedScalarObj, keyData.second(), &keyJsValue);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
// Add the key to the scalar representation.
|
||||
const NS_ConvertUTF8toUTF16 key(keyData.first());
|
||||
if (!JS_DefineUCProperty(aCx, keyedScalarObj, key.Data(), key.Length(), keyJsValue, JSPROP_ENUMERATE)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
// Add the scalar to the root object.
|
||||
if (!JS_DefineProperty(aCx, root_obj, keyedScalarData.first(), keyedScalarObj, JSPROP_ENUMERATE)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets all the stored scalars. This is intended to be only used in tests.
|
||||
*/
|
||||
@ -1177,6 +1897,7 @@ TelemetryScalar::ClearScalars()
|
||||
{
|
||||
StaticMutexAutoLock locker(gTelemetryScalarsMutex);
|
||||
gScalarStorageMap.Clear();
|
||||
gKeyedScalarStorageMap.Clear();
|
||||
}
|
||||
|
||||
size_t
|
||||
@ -1191,9 +1912,15 @@ TelemetryScalar::GetScalarSizesOfIncludingThis(mozilla::MallocSizeOf aMallocSize
|
||||
{
|
||||
StaticMutexAutoLock locker(gTelemetryScalarsMutex);
|
||||
size_t n = 0;
|
||||
// For the plain scalars...
|
||||
for (auto iter = gScalarStorageMap.Iter(); !iter.Done(); iter.Next()) {
|
||||
ScalarBase* scalar = static_cast<ScalarBase*>(iter.Data());
|
||||
n += scalar->SizeOfIncludingThis(aMallocSizeOf);
|
||||
}
|
||||
// ...and for the keyed scalars.
|
||||
for (auto iter = gKeyedScalarStorageMap.Iter(); !iter.Done(); iter.Next()) {
|
||||
KeyedScalar* scalar = static_cast<KeyedScalar*>(iter.Data());
|
||||
n += scalar->SizeOfIncludingThis(aMallocSizeOf);
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
@ -29,6 +29,17 @@ nsresult CreateSnapshots(unsigned int aDataset, bool aClearScalars,
|
||||
JSContext* aCx, uint8_t optional_argc,
|
||||
JS::MutableHandle<JS::Value> aResult);
|
||||
|
||||
// Keyed JS API Endpoints.
|
||||
nsresult Add(const nsACString& aName, const nsAString& aKey, JS::HandleValue aVal,
|
||||
JSContext* aCx);
|
||||
nsresult Set(const nsACString& aName, const nsAString& aKey, JS::HandleValue aVal,
|
||||
JSContext* aCx);
|
||||
nsresult SetMaximum(const nsACString& aName, const nsAString& aKey, JS::HandleValue aVal,
|
||||
JSContext* aCx);
|
||||
nsresult CreateKeyedSnapshots(unsigned int aDataset, bool aClearScalars,
|
||||
JSContext* aCx, uint8_t optional_argc,
|
||||
JS::MutableHandle<JS::Value> aResult);
|
||||
|
||||
// C++ API Endpoints.
|
||||
void Add(mozilla::Telemetry::ScalarID aId, uint32_t aValue);
|
||||
void Set(mozilla::Telemetry::ScalarID aId, uint32_t aValue);
|
||||
@ -36,6 +47,12 @@ void Set(mozilla::Telemetry::ScalarID aId, const nsAString& aValue);
|
||||
void Set(mozilla::Telemetry::ScalarID aId, bool aValue);
|
||||
void SetMaximum(mozilla::Telemetry::ScalarID aId, uint32_t aValue);
|
||||
|
||||
// Keyed C++ API Endpoints.
|
||||
void Add(mozilla::Telemetry::ScalarID aId, const nsAString& aKey, uint32_t aValue);
|
||||
void Set(mozilla::Telemetry::ScalarID aId, const nsAString& aKey, uint32_t aValue);
|
||||
void Set(mozilla::Telemetry::ScalarID aId, const nsAString& aKey, bool aValue);
|
||||
void SetMaximum(mozilla::Telemetry::ScalarID aId, const nsAString& aKey, uint32_t aValue);
|
||||
|
||||
// Only to be used for testing.
|
||||
void ClearScalars();
|
||||
|
||||
|
@ -971,8 +971,16 @@ var Impl = {
|
||||
return ret;
|
||||
},
|
||||
|
||||
getScalars: function (subsession, clearSubsession) {
|
||||
this._log.trace("getScalars - subsession: " + subsession + ", clearSubsession: " + clearSubsession);
|
||||
/**
|
||||
* Get a snapshot of the scalars and clear them.
|
||||
* @param {subsession} If true, then we collect the data for a subsession.
|
||||
* @param {clearSubsession} If true, we need to clear the subsession.
|
||||
* @param {keyed} Take a snapshot of keyed or non keyed scalars.
|
||||
* @return {Object} The scalar data as a Javascript object.
|
||||
*/
|
||||
getScalars: function (subsession, clearSubsession, keyed) {
|
||||
this._log.trace("getScalars - subsession: " + subsession + ", clearSubsession: " +
|
||||
clearSubsession + ", keyed: " + keyed);
|
||||
|
||||
if (!subsession) {
|
||||
// We only support scalars for subsessions.
|
||||
@ -980,7 +988,8 @@ var Impl = {
|
||||
return {};
|
||||
}
|
||||
|
||||
let scalarsSnapshot =
|
||||
let scalarsSnapshot = keyed ?
|
||||
Telemetry.snapshotKeyedScalars(this.getDatasetType(), clearSubsession) :
|
||||
Telemetry.snapshotScalars(this.getDatasetType(), clearSubsession);
|
||||
|
||||
// Don't return the test scalars.
|
||||
@ -1285,6 +1294,7 @@ var Impl = {
|
||||
payloadObj.processes = {
|
||||
parent: {
|
||||
scalars: protect(() => this.getScalars(isSubsession, clearSubsession)),
|
||||
keyedScalars: protect(() => this.getScalars(isSubsession, clearSubsession, true)),
|
||||
},
|
||||
content: {
|
||||
histograms: histograms[HISTOGRAM_SUFFIXES.CONTENT],
|
||||
|
@ -23,9 +23,13 @@ Probes in privileged JavaScript code can use the following functions to manipula
|
||||
Services.telemetry.scalarSet(aName, aValue);
|
||||
Services.telemetry.scalarSetMaximum(aName, aValue);
|
||||
|
||||
Services.telemetry.keyedScalarAdd(aName, aKey, aValue);
|
||||
Services.telemetry.keyedScalarSet(aName, aKey, aValue);
|
||||
Services.telemetry.keyedScalarSetMaximum(aName, aKey, aValue);
|
||||
|
||||
These functions can throw if, for example, an operation is performed on a scalar type that doesn't support it
|
||||
(e.g. calling scalarSetMaximum on a scalar of the string kind). Please look at the code documentation for
|
||||
additional informations.
|
||||
(e.g. calling scalarSetMaximum on a scalar of the string kind). Please look at the `code documentation <https://dxr.mozilla.org/mozilla-central/search?q=regexp%3ATelemetryScalar%3A%3A(Set%7CAdd)+file%3ATelemetryScalar.cpp&redirect=false>`_ for
|
||||
additional information.
|
||||
|
||||
C++ API
|
||||
-------
|
||||
@ -39,6 +43,11 @@ Probes in native code can use the more convenient helper functions declared in `
|
||||
void ScalarSet(mozilla::Telemetry::ScalarID aId, bool aValue);
|
||||
void ScalarSetMaximum(mozilla::Telemetry::ScalarID aId, uint32_t aValue);
|
||||
|
||||
void ScalarAdd(mozilla::Telemetry::ScalarID aId, const nsAString& aKey, uint32_t aValue);
|
||||
void ScalarSet(mozilla::Telemetry::ScalarID aId, const nsAString& aKey, uint32_t aValue);
|
||||
void ScalarSet(mozilla::Telemetry::ScalarID aId, const nsAString& aKey, bool aValue);
|
||||
void ScalarSetMaximum(mozilla::Telemetry::ScalarID aId, const nsAString& aKey, uint32_t aValue);
|
||||
|
||||
The YAML definition file
|
||||
========================
|
||||
Scalar probes are required to be registered, both for validation and transparency reasons,
|
||||
@ -97,12 +106,19 @@ Optional Fields
|
||||
|
||||
- ``cpp_guard``: A string that gets inserted as an ``#ifdef`` directive around the automatically generated C++ declaration. This is typically used for platform-specific scalars, e.g. ``ANDROID``.
|
||||
- ``release_channel_collection``: This can be either ``opt-in`` (default) or ``opt-out``. With the former the scalar is submitted by default on pre-release channels; on the release channel only if the user opted into additional data collection. With the latter the scalar is submitted by default on release and pre-release channels, unless the user opted out.
|
||||
- ``keyed``: A boolean that determines whether this is a keyed scalar. It defaults to ``False``.
|
||||
|
||||
String type restrictions
|
||||
------------------------
|
||||
To prevent abuses, the content of a string scalar is limited to 50 characters in length. Trying
|
||||
to set a longer string will result in an error and no string being set.
|
||||
|
||||
Keyed Scalars
|
||||
-------------
|
||||
Keyed scalars are collections of one of the available scalar types, indexed by a string key that can contain UTF8 characters and cannot be longer than 70 characters. Keyed scalars can contain up to 100 keys. This scalar type is for example useful when you want to break down certain counts by a name, like how often searches happen with which search engine.
|
||||
|
||||
Keyed scalars should only be used if the set of keys are not known beforehand. If the keys are from a known set of strings, other options are preferred if suitable, like categorical histograms or splitting measurements up into separate scalars.
|
||||
|
||||
The processor scripts
|
||||
=====================
|
||||
The scalar definition file is processed and checked for correctness at compile time. If it
|
||||
|
@ -40,11 +40,12 @@ def write_scalar_info(scalar, output, name_index, expiration_index):
|
||||
if cpp_guard:
|
||||
print("#if defined(%s)" % cpp_guard, file=output)
|
||||
|
||||
print(" {{ {}, {}, {}, {} }},"\
|
||||
print(" {{ {}, {}, {}, {}, {} }},"\
|
||||
.format(scalar.nsITelemetry_kind,
|
||||
name_index,
|
||||
expiration_index,
|
||||
scalar.dataset),
|
||||
scalar.dataset,
|
||||
"true" if scalar.keyed else "false"),
|
||||
file=output)
|
||||
|
||||
if cpp_guard:
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user