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/eyedropper/**
|
||||||
devtools/client/framework/**
|
devtools/client/framework/**
|
||||||
!devtools/client/framework/selection.js
|
!devtools/client/framework/selection.js
|
||||||
|
!devtools/client/framework/toolbox.js
|
||||||
devtools/client/jsonview/lib/**
|
devtools/client/jsonview/lib/**
|
||||||
devtools/client/memory/**
|
devtools/client/memory/**
|
||||||
devtools/client/netmonitor/test/**
|
devtools/client/netmonitor/test/**
|
||||||
|
@ -84,3 +84,9 @@ addMessageListener("Test:WaitForObserverCall", ({data}) => {
|
|||||||
}
|
}
|
||||||
}, topic, false);
|
}, 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) {
|
function promiseMessage(aMessage, aAction) {
|
||||||
let deferred = Promise.defer();
|
let promise = new Promise((resolve, reject) => {
|
||||||
|
let mm = _mm();
|
||||||
content.addEventListener("message", function messageListener(event) {
|
mm.addMessageListener("Test:MessageReceived", function listener({data}) {
|
||||||
content.removeEventListener("message", messageListener);
|
is(data, aMessage, "received " + aMessage);
|
||||||
is(event.data, aMessage, "received " + aMessage);
|
if (data == aMessage)
|
||||||
if (event.data == aMessage)
|
resolve();
|
||||||
deferred.resolve();
|
else
|
||||||
else
|
reject();
|
||||||
deferred.reject();
|
mm.removeMessageListener("Test:MessageReceived", listener);
|
||||||
|
});
|
||||||
|
mm.sendAsyncMessage("Test:WaitForMessage");
|
||||||
});
|
});
|
||||||
|
|
||||||
if (aAction)
|
if (aAction)
|
||||||
aAction();
|
aAction();
|
||||||
|
|
||||||
return deferred.promise;
|
return promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
function promisePopupNotificationShown(aName, aAction) {
|
function promisePopupNotificationShown(aName, aAction) {
|
||||||
|
@ -292,7 +292,6 @@ function prompt(aBrowser, aRequest) {
|
|||||||
requestTypes: requestTypes} = aRequest;
|
requestTypes: requestTypes} = aRequest;
|
||||||
let uri = Services.io.newURI(aRequest.documentURI, null, null);
|
let uri = Services.io.newURI(aRequest.documentURI, null, null);
|
||||||
let host = getHost(uri);
|
let host = getHost(uri);
|
||||||
let principal = Services.scriptSecurityManager.createCodebasePrincipal(uri, {});
|
|
||||||
let chromeDoc = aBrowser.ownerDocument;
|
let chromeDoc = aBrowser.ownerDocument;
|
||||||
let chromeWin = chromeDoc.defaultView;
|
let chromeWin = chromeDoc.defaultView;
|
||||||
let stringBundle = chromeWin.gNavigatorBundle;
|
let stringBundle = chromeWin.gNavigatorBundle;
|
||||||
@ -388,14 +387,12 @@ function prompt(aBrowser, aRequest) {
|
|||||||
if (micPerm == perms.PROMPT_ACTION)
|
if (micPerm == perms.PROMPT_ACTION)
|
||||||
micPerm = perms.UNKNOWN_ACTION;
|
micPerm = perms.UNKNOWN_ACTION;
|
||||||
|
|
||||||
let camPermanentPerm = perms.testExactPermanentPermission(principal, "camera");
|
|
||||||
let camPerm = perms.testExactPermission(uri, "camera");
|
let camPerm = perms.testExactPermission(uri, "camera");
|
||||||
|
|
||||||
// Session approval given but never used to allocate a camera, remove
|
let mediaManagerPerm =
|
||||||
// and ask again
|
perms.testExactPermission(uri, "MediaManagerVideo");
|
||||||
if (camPerm && !camPermanentPerm) {
|
if (mediaManagerPerm) {
|
||||||
perms.remove(uri, "camera");
|
perms.remove(uri, "MediaManagerVideo");
|
||||||
camPerm = perms.UNKNOWN_ACTION;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (camPerm == perms.PROMPT_ACTION)
|
if (camPerm == perms.PROMPT_ACTION)
|
||||||
@ -534,10 +531,12 @@ function prompt(aBrowser, aRequest) {
|
|||||||
allowedDevices.push(videoDeviceIndex);
|
allowedDevices.push(videoDeviceIndex);
|
||||||
// Session permission will be removed after use
|
// Session permission will be removed after use
|
||||||
// (it's really one-shot, not for the entire session)
|
// (it's really one-shot, not for the entire session)
|
||||||
perms.add(uri, "camera", perms.ALLOW_ACTION,
|
perms.add(uri, "MediaManagerVideo", perms.ALLOW_ACTION,
|
||||||
aRemember ? perms.EXPIRE_NEVER : perms.EXPIRE_SESSION);
|
perms.EXPIRE_SESSION);
|
||||||
} else if (aRemember) {
|
}
|
||||||
perms.add(uri, "camera", perms.DENY_ACTION);
|
if (aRemember) {
|
||||||
|
perms.add(uri, "camera",
|
||||||
|
allowCamera ? perms.ALLOW_ACTION : perms.DENY_ACTION);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (audioDevices.length) {
|
if (audioDevices.length) {
|
||||||
|
@ -24,8 +24,7 @@ module.exports = createClass({
|
|||||||
targets = targets.sort(LocaleCompare);
|
targets = targets.sort(LocaleCompare);
|
||||||
}
|
}
|
||||||
targets = targets.map(target => {
|
targets = targets.map(target => {
|
||||||
let key = target.name || target.url || target.title;
|
return targetClass({ client, target, debugDisabled });
|
||||||
return targetClass({ client, key, target, debugDisabled });
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let content = "";
|
let content = "";
|
||||||
|
@ -11,6 +11,8 @@
|
|||||||
// is selected, animations will be displayed in the timeline, so the timeline
|
// is selected, animations will be displayed in the timeline, so the timeline
|
||||||
// play/resume button will be displayed
|
// play/resume button will be displayed
|
||||||
add_task(function* () {
|
add_task(function* () {
|
||||||
|
requestLongerTimeout(2);
|
||||||
|
|
||||||
yield addTab(URL_ROOT + "doc_simple_animation.html");
|
yield addTab(URL_ROOT + "doc_simple_animation.html");
|
||||||
let {panel, window} = yield openAnimationInspector();
|
let {panel, window} = yield openAnimationInspector();
|
||||||
let {playTimelineButtonEl} = panel;
|
let {playTimelineButtonEl} = panel;
|
||||||
|
@ -189,32 +189,16 @@
|
|||||||
<key id="resumeKey"
|
<key id="resumeKey"
|
||||||
keycode="&debuggerUI.stepping.resume1;"
|
keycode="&debuggerUI.stepping.resume1;"
|
||||||
command="resumeCommand"/>
|
command="resumeCommand"/>
|
||||||
<key id="resumeKey2"
|
|
||||||
keycode="&debuggerUI.stepping.resume2;"
|
|
||||||
modifiers="accel"
|
|
||||||
command="resumeCommand"/>
|
|
||||||
<key id="stepOverKey"
|
<key id="stepOverKey"
|
||||||
keycode="&debuggerUI.stepping.stepOver1;"
|
keycode="&debuggerUI.stepping.stepOver1;"
|
||||||
command="stepOverCommand"/>
|
command="stepOverCommand"/>
|
||||||
<key id="stepOverKey2"
|
|
||||||
keycode="&debuggerUI.stepping.stepOver2;"
|
|
||||||
modifiers="accel"
|
|
||||||
command="stepOverCommand"/>
|
|
||||||
<key id="stepInKey"
|
<key id="stepInKey"
|
||||||
keycode="&debuggerUI.stepping.stepIn1;"
|
keycode="&debuggerUI.stepping.stepIn1;"
|
||||||
command="stepInCommand"/>
|
command="stepInCommand"/>
|
||||||
<key id="stepInKey2"
|
|
||||||
keycode="&debuggerUI.stepping.stepIn2;"
|
|
||||||
modifiers="accel"
|
|
||||||
command="stepInCommand"/>
|
|
||||||
<key id="stepOutKey"
|
<key id="stepOutKey"
|
||||||
keycode="&debuggerUI.stepping.stepOut1;"
|
keycode="&debuggerUI.stepping.stepOut1;"
|
||||||
modifiers="shift"
|
modifiers="shift"
|
||||||
command="stepOutCommand"/>
|
command="stepOutCommand"/>
|
||||||
<key id="stepOutKey2"
|
|
||||||
keycode="&debuggerUI.stepping.stepOut2;"
|
|
||||||
modifiers="accel shift"
|
|
||||||
command="stepOutCommand"/>
|
|
||||||
<key id="fileSearchKey"
|
<key id="fileSearchKey"
|
||||||
key="&debuggerUI.searchFile.key;"
|
key="&debuggerUI.searchFile.key;"
|
||||||
modifiers="accel"
|
modifiers="accel"
|
||||||
|
@ -3,29 +3,27 @@
|
|||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
|
const { Task } = require("devtools/shared/task");
|
||||||
|
|
||||||
function DebuggerPanel(iframeWindow, toolbox) {
|
function DebuggerPanel(iframeWindow, toolbox) {
|
||||||
this.panelWin = iframeWindow;
|
this.panelWin = iframeWindow;
|
||||||
this.toolbox = toolbox;
|
this.toolbox = toolbox;
|
||||||
}
|
}
|
||||||
|
|
||||||
DebuggerPanel.prototype = {
|
DebuggerPanel.prototype = {
|
||||||
open: function() {
|
open: Task.async(function*() {
|
||||||
let targetPromise;
|
|
||||||
if (!this.toolbox.target.isRemote) {
|
if (!this.toolbox.target.isRemote) {
|
||||||
targetPromise = this.toolbox.target.makeRemote();
|
yield this.toolbox.target.makeRemote();
|
||||||
} else {
|
|
||||||
targetPromise = Promise.resolve(this.toolbox.target);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return targetPromise.then(() => {
|
yield this.panelWin.Debugger.bootstrap({
|
||||||
this.panelWin.Debugger.bootstrap({
|
threadClient: this.toolbox.threadClient,
|
||||||
threadClient: this.toolbox.threadClient,
|
tabTarget: this.toolbox.target
|
||||||
tabTarget: this.toolbox.target
|
|
||||||
});
|
|
||||||
this.isReady = true;
|
|
||||||
return this;
|
|
||||||
});
|
});
|
||||||
},
|
|
||||||
|
this.isReady = true;
|
||||||
|
return this;
|
||||||
|
}),
|
||||||
|
|
||||||
_store: function() {
|
_store: function() {
|
||||||
return this.panelWin.Debugger.store;
|
return this.panelWin.Debugger.store;
|
||||||
|
@ -54,12 +54,16 @@ DebuggerPanel.prototype = {
|
|||||||
this._toolbox.on("host-changed", this.handleHostChanged);
|
this._toolbox.on("host-changed", this.handleHostChanged);
|
||||||
// Add keys from this document's keyset to the toolbox, so they
|
// Add keys from this document's keyset to the toolbox, so they
|
||||||
// can work when the split console is focused.
|
// can work when the split console is focused.
|
||||||
let keysToClone = ["resumeKey", "resumeKey2", "stepOverKey",
|
let keysToClone = ["resumeKey", "stepOverKey", "stepInKey", "stepOutKey"];
|
||||||
"stepOverKey2", "stepInKey", "stepInKey2",
|
|
||||||
"stepOutKey", "stepOutKey2"];
|
|
||||||
for (let key of keysToClone) {
|
for (let key of keysToClone) {
|
||||||
let elm = this.panelWin.document.getElementById(key);
|
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.isReady = true;
|
||||||
this.emit("ready");
|
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
|
// DevToolPanel API
|
||||||
|
|
||||||
get target() {
|
get target() {
|
||||||
|
@ -207,7 +207,7 @@ skip-if = e10s && debug
|
|||||||
[browser_dbg_pretty-print-10.js]
|
[browser_dbg_pretty-print-10.js]
|
||||||
skip-if = e10s && debug
|
skip-if = e10s && debug
|
||||||
[browser_dbg_pretty-print-11.js]
|
[browser_dbg_pretty-print-11.js]
|
||||||
skip-if = e10s && debug || true
|
skip-if = e10s && debug
|
||||||
[browser_dbg_pretty-print-12.js]
|
[browser_dbg_pretty-print-12.js]
|
||||||
skip-if = e10s && debug
|
skip-if = e10s && debug
|
||||||
[browser_dbg_pretty-print-13.js]
|
[browser_dbg_pretty-print-13.js]
|
||||||
|
@ -101,13 +101,35 @@ ToolbarView.prototype = {
|
|||||||
*/
|
*/
|
||||||
_addCommands: function () {
|
_addCommands: function () {
|
||||||
XULUtils.addCommands(document.getElementById("debuggerCommands"), {
|
XULUtils.addCommands(document.getElementById("debuggerCommands"), {
|
||||||
resumeCommand: () => this._onResumePressed(),
|
resumeCommand: this.getCommandHandler("resumeCommand"),
|
||||||
stepOverCommand: () => this._onStepOverPressed(),
|
stepOverCommand: this.getCommandHandler("stepOverCommand"),
|
||||||
stepInCommand: () => this._onStepInPressed(),
|
stepInCommand: this.getCommandHandler("stepInCommand"),
|
||||||
stepOutCommand: () => this._onStepOutPressed()
|
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.
|
* Display a warning when trying to resume a debuggee while another is paused.
|
||||||
* Debuggees must be unpaused in a Last-In-First-Out order.
|
* Debuggees must be unpaused in a Last-In-First-Out order.
|
||||||
|
@ -60,7 +60,7 @@ Tools.inspector = {
|
|||||||
modifiers: osString == "Darwin" ? "accel,alt" : "accel,shift",
|
modifiers: osString == "Darwin" ? "accel,alt" : "accel,shift",
|
||||||
icon: "chrome://devtools/skin/images/tool-inspector.svg",
|
icon: "chrome://devtools/skin/images/tool-inspector.svg",
|
||||||
invertIconForDarkTheme: true,
|
invertIconForDarkTheme: true,
|
||||||
url: "chrome://devtools/content/inspector/inspector.xul",
|
url: "chrome://devtools/content/inspector/inspector.xhtml",
|
||||||
label: l10n("inspector.label"),
|
label: l10n("inspector.label"),
|
||||||
panelLabel: l10n("inspector.panelLabel"),
|
panelLabel: l10n("inspector.panelLabel"),
|
||||||
get tooltip() {
|
get tooltip() {
|
||||||
|
@ -3,6 +3,8 @@
|
|||||||
/* Any copyright is dedicated to the Public Domain.
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
// Tests that these toolbox split console APIs work:
|
// Tests that these toolbox split console APIs work:
|
||||||
// * toolbox.useKeyWithSplitConsole()
|
// * toolbox.useKeyWithSplitConsole()
|
||||||
// * toolbox.isSplitConsoleFocused
|
// * toolbox.isSplitConsoleFocused
|
||||||
@ -11,7 +13,6 @@ let gToolbox = null;
|
|||||||
let panelWin = null;
|
let panelWin = null;
|
||||||
|
|
||||||
const URL = "data:text/html;charset=utf8,test split console key delegation";
|
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)
|
// Force the old debugger UI since it's directly used (see Bug 1301705)
|
||||||
Services.prefs.setBoolPref("devtools.debugger.new-debugger-frontend", false);
|
Services.prefs.setBoolPref("devtools.debugger.new-debugger-frontend", false);
|
||||||
@ -45,18 +46,15 @@ function* testIsSplitConsoleFocused() {
|
|||||||
function* testUseKeyWithSplitConsole() {
|
function* testUseKeyWithSplitConsole() {
|
||||||
let commandCalled = false;
|
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");
|
info("useKeyWithSplitConsole on debugger while debugger is focused");
|
||||||
gToolbox.useKeyWithSplitConsole(keyElm, "jsdebugger");
|
gToolbox.useKeyWithSplitConsole("F3", () => {
|
||||||
|
commandCalled = true;
|
||||||
|
}, "jsdebugger");
|
||||||
|
|
||||||
info("synthesizeKey with the console focused");
|
info("synthesizeKey with the console focused");
|
||||||
let consoleInput = gToolbox.getPanel("webconsole").hud.jsterm.inputNode;
|
let consoleInput = gToolbox.getPanel("webconsole").hud.jsterm.inputNode;
|
||||||
consoleInput.focus();
|
consoleInput.focus();
|
||||||
synthesizeKeyElement(keyElm);
|
synthesizeKeyShortcut("F3", panelWin);
|
||||||
|
|
||||||
ok(commandCalled, "Shortcut key should trigger the command");
|
ok(commandCalled, "Shortcut key should trigger the command");
|
||||||
}
|
}
|
||||||
@ -65,18 +63,15 @@ function* testUseKeyWithSplitConsole() {
|
|||||||
function* testUseKeyWithSplitConsoleWrongTool() {
|
function* testUseKeyWithSplitConsoleWrongTool() {
|
||||||
let commandCalled = false;
|
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");
|
info("useKeyWithSplitConsole on inspector while debugger is focused");
|
||||||
gToolbox.useKeyWithSplitConsole(keyElm, "inspector");
|
gToolbox.useKeyWithSplitConsole("F4", () => {
|
||||||
|
commandCalled = true;
|
||||||
|
}, "inspector");
|
||||||
|
|
||||||
info("synthesizeKey with the console focused");
|
info("synthesizeKey with the console focused");
|
||||||
let consoleInput = gToolbox.getPanel("webconsole").hud.jsterm.inputNode;
|
let consoleInput = gToolbox.getPanel("webconsole").hud.jsterm.inputNode;
|
||||||
consoleInput.focus();
|
consoleInput.focus();
|
||||||
synthesizeKeyElement(keyElm);
|
synthesizeKeyShortcut("F4", panelWin);
|
||||||
|
|
||||||
ok(!commandCalled, "Shortcut key shouldn't trigger the command");
|
ok(!commandCalled, "Shortcut key shouldn't trigger the command");
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,14 @@ add_task(function* () {
|
|||||||
let toolbox = yield openNewTabAndToolbox(URL, "inspector");
|
let toolbox = yield openNewTabAndToolbox(URL, "inspector");
|
||||||
let textboxContextMenu = toolbox.textboxContextMenuPopup;
|
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");
|
ok(textboxContextMenu, "The textbox context menu is loaded in the toolbox");
|
||||||
|
|
||||||
let cmdUndo = textboxContextMenu.querySelector("[command=cmd_undo]");
|
let cmdUndo = textboxContextMenu.querySelector("[command=cmd_undo]");
|
||||||
@ -26,10 +34,17 @@ add_task(function* () {
|
|||||||
|
|
||||||
is(cmdUndo.getAttribute("disabled"), "true", "cmdUndo is disabled");
|
is(cmdUndo.getAttribute("disabled"), "true", "cmdUndo is disabled");
|
||||||
is(cmdDelete.getAttribute("disabled"), "true", "cmdDelete is disabled");
|
is(cmdDelete.getAttribute("disabled"), "true", "cmdDelete is disabled");
|
||||||
is(cmdSelectAll.getAttribute("disabled"), "", "cmdSelectAll is enabled");
|
is(cmdSelectAll.getAttribute("disabled"), "true", "cmdSelectAll is disabled");
|
||||||
is(cmdCut.getAttribute("disabled"), "true", "cmdCut is disabled");
|
|
||||||
is(cmdCopy.getAttribute("disabled"), "true", "cmdCopy is disabled");
|
// Cut/Copy items are enabled in context menu even if there
|
||||||
is(cmdPaste.getAttribute("disabled"), "true", "cmdPaste is disabled");
|
// 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);
|
yield cleanup(toolbox);
|
||||||
});
|
});
|
||||||
|
@ -84,6 +84,7 @@ function testReload(shortcut, docked, toolID, callback) {
|
|||||||
|
|
||||||
description = docked + " devtools with tool " + toolID + ", shortcut #" + shortcut;
|
description = docked + " devtools with tool " + toolID + ", shortcut #" + shortcut;
|
||||||
info("Testing reload in " + description);
|
info("Testing reload in " + description);
|
||||||
|
toolbox.win.focus();
|
||||||
synthesizeKeyShortcut(L10N.getStr(shortcut), toolbox.win);
|
synthesizeKeyShortcut(L10N.getStr(shortcut), toolbox.win);
|
||||||
reloadsSent++;
|
reloadsSent++;
|
||||||
}
|
}
|
||||||
|
@ -559,3 +559,20 @@ function stopRecordingTelemetryLogs(Telemetry) {
|
|||||||
delete Telemetry.prototype._oldlogKeyed;
|
delete Telemetry.prototype._oldlogKeyed;
|
||||||
delete Telemetry.prototype.telemetryInfo;
|
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 HTML_NS = "http://www.w3.org/1999/xhtml";
|
||||||
const { SourceMapService } = require("./source-map-service");
|
const { SourceMapService } = require("./source-map-service");
|
||||||
|
|
||||||
var {Cc, Ci, Cu} = require("chrome");
|
var {Ci, Cu} = require("chrome");
|
||||||
var promise = require("promise");
|
var promise = require("promise");
|
||||||
var defer = require("devtools/shared/defer");
|
var defer = require("devtools/shared/defer");
|
||||||
var Services = require("Services");
|
var Services = require("Services");
|
||||||
@ -235,8 +235,8 @@ Toolbox.prototype = {
|
|||||||
if (panel) {
|
if (panel) {
|
||||||
deferred.resolve(panel);
|
deferred.resolve(panel);
|
||||||
} else {
|
} else {
|
||||||
this.on(id + "-ready", (e, panel) => {
|
this.on(id + "-ready", (e, initializedPanel) => {
|
||||||
deferred.resolve(panel);
|
deferred.resolve(initializedPanel);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -406,17 +406,17 @@ Toolbox.prototype = {
|
|||||||
this.textboxContextMenuPopup.addEventListener("popupshowing",
|
this.textboxContextMenuPopup.addEventListener("popupshowing",
|
||||||
this._updateTextboxMenuItems, true);
|
this._updateTextboxMenuItems, true);
|
||||||
|
|
||||||
var shortcuts = new KeyShortcuts({
|
this.shortcuts = new KeyShortcuts({
|
||||||
window: this.doc.defaultView
|
window: this.doc.defaultView
|
||||||
});
|
});
|
||||||
this._buildDockButtons();
|
this._buildDockButtons();
|
||||||
this._buildOptions(shortcuts);
|
this._buildOptions();
|
||||||
this._buildTabs();
|
this._buildTabs();
|
||||||
this._applyCacheSettings();
|
this._applyCacheSettings();
|
||||||
this._applyServiceWorkersTestingSettings();
|
this._applyServiceWorkersTestingSettings();
|
||||||
this._addKeysToWindow();
|
this._addKeysToWindow();
|
||||||
this._addReloadKeys(shortcuts);
|
this._addReloadKeys();
|
||||||
this._addHostListeners(shortcuts);
|
this._addHostListeners();
|
||||||
this._registerOverlays();
|
this._registerOverlays();
|
||||||
if (!this._hostOptions || this._hostOptions.zoom === true) {
|
if (!this._hostOptions || this._hostOptions.zoom === true) {
|
||||||
ZoomKeys.register(this.win);
|
ZoomKeys.register(this.win);
|
||||||
@ -502,7 +502,8 @@ Toolbox.prototype = {
|
|||||||
this._telemetry.logOncePerBrowserVersion(OS_HISTOGRAM, system.getOSCPU());
|
this._telemetry.logOncePerBrowserVersion(OS_HISTOGRAM, system.getOSCPU());
|
||||||
this._telemetry.logOncePerBrowserVersion(OS_IS_64_BITS,
|
this._telemetry.logOncePerBrowserVersion(OS_IS_64_BITS,
|
||||||
Services.appinfo.is64Bit ? 1 : 0);
|
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());
|
this._telemetry.log(HOST_HISTOGRAM, this._getTelemetryHostId());
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -529,7 +530,7 @@ Toolbox.prototype = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_buildOptions: function (shortcuts) {
|
_buildOptions: function () {
|
||||||
let selectOptions = (name, event) => {
|
let selectOptions = (name, event) => {
|
||||||
// Flip back to the last used panel if we are already
|
// Flip back to the last used panel if we are already
|
||||||
// on the options panel.
|
// on the options panel.
|
||||||
@ -542,8 +543,8 @@ Toolbox.prototype = {
|
|||||||
// Prevent the opening of bookmarks window on toolbox.options.key
|
// Prevent the opening of bookmarks window on toolbox.options.key
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
};
|
};
|
||||||
shortcuts.on(L10N.getStr("toolbox.options.key"), selectOptions);
|
this.shortcuts.on(L10N.getStr("toolbox.options.key"), selectOptions);
|
||||||
shortcuts.on(L10N.getStr("toolbox.help.key"), selectOptions);
|
this.shortcuts.on(L10N.getStr("toolbox.help.key"), selectOptions);
|
||||||
},
|
},
|
||||||
|
|
||||||
_splitConsoleOnKeypress: function (e) {
|
_splitConsoleOnKeypress: function (e) {
|
||||||
@ -561,26 +562,24 @@ Toolbox.prototype = {
|
|||||||
* Add a shortcut key that should work when a split console
|
* Add a shortcut key that should work when a split console
|
||||||
* has focus to the toolbox.
|
* has focus to the toolbox.
|
||||||
*
|
*
|
||||||
* @param {element} keyElement
|
* @param {String} key
|
||||||
* They <key> XUL element describing the shortcut key
|
* The electron key shortcut.
|
||||||
* @param {string} whichTool
|
* @param {Function} handler
|
||||||
* The tool the key belongs to. The corresponding command
|
* The callback that should be called when the provided key shortcut is pressed.
|
||||||
* will only trigger if this tool is active.
|
* @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) {
|
useKeyWithSplitConsole: function (key, handler, whichTool) {
|
||||||
let cloned = keyElement.cloneNode();
|
this.shortcuts.on(key, (name, event) => {
|
||||||
cloned.setAttribute("oncommand", "void(0)");
|
|
||||||
cloned.removeAttribute("command");
|
|
||||||
cloned.addEventListener("command", (e) => {
|
|
||||||
// Only forward the command if the tool is active
|
|
||||||
if (this.currentToolId === whichTool && this.isSplitConsoleFocused()) {
|
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],
|
["reload", false],
|
||||||
["reload2", false],
|
["reload2", false],
|
||||||
@ -588,7 +587,7 @@ Toolbox.prototype = {
|
|||||||
["forceReload2", true]
|
["forceReload2", true]
|
||||||
].forEach(([id, force]) => {
|
].forEach(([id, force]) => {
|
||||||
let key = L10N.getStr("toolbox." + id + ".key");
|
let key = L10N.getStr("toolbox." + id + ".key");
|
||||||
shortcuts.on(key, (name, event) => {
|
this.shortcuts.on(key, (name, event) => {
|
||||||
this.reloadTarget(force);
|
this.reloadTarget(force);
|
||||||
|
|
||||||
// Prevent Firefox shortcuts from reloading the page
|
// Prevent Firefox shortcuts from reloading the page
|
||||||
@ -597,23 +596,23 @@ Toolbox.prototype = {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
_addHostListeners: function (shortcuts) {
|
_addHostListeners: function () {
|
||||||
shortcuts.on(L10N.getStr("toolbox.nextTool.key"),
|
this.shortcuts.on(L10N.getStr("toolbox.nextTool.key"),
|
||||||
(name, event) => {
|
(name, event) => {
|
||||||
this.selectNextTool();
|
this.selectNextTool();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
});
|
});
|
||||||
shortcuts.on(L10N.getStr("toolbox.previousTool.key"),
|
this.shortcuts.on(L10N.getStr("toolbox.previousTool.key"),
|
||||||
(name, event) => {
|
(name, event) => {
|
||||||
this.selectPreviousTool();
|
this.selectPreviousTool();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
});
|
});
|
||||||
shortcuts.on(L10N.getStr("toolbox.minimize.key"),
|
this.shortcuts.on(L10N.getStr("toolbox.minimize.key"),
|
||||||
(name, event) => {
|
(name, event) => {
|
||||||
this._toggleMinimizeMode();
|
this._toggleMinimizeMode();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
});
|
});
|
||||||
shortcuts.on(L10N.getStr("toolbox.toggleHost.key"),
|
this.shortcuts.on(L10N.getStr("toolbox.toggleHost.key"),
|
||||||
(name, event) => {
|
(name, event) => {
|
||||||
this.switchToPreviousHost();
|
this.switchToPreviousHost();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
@ -976,16 +975,16 @@ Toolbox.prototype = {
|
|||||||
this._requisition = requisition;
|
this._requisition = requisition;
|
||||||
|
|
||||||
const spec = CommandUtils.getCommandbarSpec("devtools.toolbox.toolbarSpec");
|
const spec = CommandUtils.getCommandbarSpec("devtools.toolbox.toolbarSpec");
|
||||||
return CommandUtils.createButtons(spec, this.target, this.doc,
|
return CommandUtils.createButtons(spec, this.target, this.doc, requisition)
|
||||||
requisition).then(buttons => {
|
.then(buttons => {
|
||||||
let container = this.doc.getElementById("toolbox-buttons");
|
let container = this.doc.getElementById("toolbox-buttons");
|
||||||
buttons.forEach(button=> {
|
buttons.forEach(button => {
|
||||||
if (button) {
|
if (button) {
|
||||||
container.appendChild(button);
|
container.appendChild(button);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.setToolboxButtonsVisibility();
|
this.setToolboxButtonsVisibility();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -996,7 +995,8 @@ Toolbox.prototype = {
|
|||||||
_buildPickerButton: function () {
|
_buildPickerButton: function () {
|
||||||
this._pickerButton = this.doc.createElementNS(HTML_NS, "button");
|
this._pickerButton = this.doc.createElementNS(HTML_NS, "button");
|
||||||
this._pickerButton.id = "command-button-pick";
|
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("title", L10N.getStr("pickButton.tooltip"));
|
||||||
this._pickerButton.setAttribute("hidden", "true");
|
this._pickerButton.setAttribute("hidden", "true");
|
||||||
|
|
||||||
@ -1082,7 +1082,9 @@ Toolbox.prototype = {
|
|||||||
let on = true;
|
let on = true;
|
||||||
try {
|
try {
|
||||||
on = Services.prefs.getBoolPref(visibilityswitch);
|
on = Services.prefs.getBoolPref(visibilityswitch);
|
||||||
} catch (ex) { }
|
} catch (ex) {
|
||||||
|
// Do nothing.
|
||||||
|
}
|
||||||
|
|
||||||
on = on && isTargetSupported(this.target);
|
on = on && isTargetSupported(this.target);
|
||||||
|
|
||||||
@ -1226,8 +1228,8 @@ Toolbox.prototype = {
|
|||||||
if (panel) {
|
if (panel) {
|
||||||
deferred.resolve(panel);
|
deferred.resolve(panel);
|
||||||
} else {
|
} else {
|
||||||
this.once(id + "-ready", panel => {
|
this.once(id + "-ready", initializedPanel => {
|
||||||
deferred.resolve(panel);
|
deferred.resolve(initializedPanel);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return deferred.promise;
|
return deferred.promise;
|
||||||
@ -1598,7 +1600,7 @@ Toolbox.prototype = {
|
|||||||
// Returns an instance of the preference actor
|
// Returns an instance of the preference actor
|
||||||
get _preferenceFront() {
|
get _preferenceFront() {
|
||||||
return this.target.root.then(rootForm => {
|
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) {
|
if (!this._initInspector) {
|
||||||
this._initInspector = Task.spawn(function* () {
|
this._initInspector = Task.spawn(function* () {
|
||||||
this._inspector = InspectorFront(this._target.client, this._target.form);
|
this._inspector = InspectorFront(this._target.client, this._target.form);
|
||||||
this._walker = yield this._inspector.getWalker(
|
let pref = "devtools.inspector.showAllAnonymousContent";
|
||||||
{showAllAnonymousContent: Services.prefs.getBoolPref("devtools.inspector.showAllAnonymousContent")}
|
let showAllAnonymousContent = Services.prefs.getBoolPref(pref);
|
||||||
);
|
this._walker = yield this._inspector.getWalker({ showAllAnonymousContent });
|
||||||
this._selection = new Selection(this._walker);
|
this._selection = new Selection(this._walker);
|
||||||
|
|
||||||
if (this.highlighterUtils.isRemoteHighlightable()) {
|
if (this.highlighterUtils.isRemoteHighlightable()) {
|
||||||
@ -1976,7 +1978,7 @@ Toolbox.prototype = {
|
|||||||
return this._destroyingInspector;
|
return this._destroyingInspector;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this._destroyingInspector = Task.spawn(function* () {
|
this._destroyingInspector = Task.spawn(function* () {
|
||||||
if (!this._inspector) {
|
if (!this._inspector) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -1988,7 +1990,9 @@ Toolbox.prototype = {
|
|||||||
if (this._walker && !this.walker.traits.autoReleased) {
|
if (this._walker && !this.walker.traits.autoReleased) {
|
||||||
try {
|
try {
|
||||||
yield this._walker.release();
|
yield this._walker.release();
|
||||||
} catch (e) {}
|
} catch (e) {
|
||||||
|
// Do nothing;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
yield this.highlighterUtils.stopPicker();
|
yield this.highlighterUtils.stopPicker();
|
||||||
@ -2018,6 +2022,7 @@ Toolbox.prototype = {
|
|||||||
this._selection = null;
|
this._selection = null;
|
||||||
this._walker = null;
|
this._walker = null;
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
|
return this._destroyingInspector;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -2233,7 +2238,7 @@ Toolbox.prototype = {
|
|||||||
// If target does not have profiler actor (addons), do not
|
// If target does not have profiler actor (addons), do not
|
||||||
// even register the shared performance connection.
|
// even register the shared performance connection.
|
||||||
if (!this.target.hasActor("profiler")) {
|
if (!this.target.hasActor("profiler")) {
|
||||||
return;
|
return promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._performanceFrontConnection) {
|
if (this._performanceFrontConnection) {
|
||||||
@ -2272,10 +2277,11 @@ Toolbox.prototype = {
|
|||||||
}),
|
}),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when any event comes from the PerformanceFront. If the performance tool is already
|
* Called when any event comes from the PerformanceFront. If the performance tool is
|
||||||
* loaded when the first event comes in, immediately unbind this handler, as this is
|
* already loaded when the first event comes in, immediately unbind this handler, as
|
||||||
* only used to queue up observed recordings before the performance tool can handle them,
|
* this is only used to queue up observed recordings before the performance tool can
|
||||||
* which will only occur when `console.profile()` recordings are started before the tool loads.
|
* handle them, which will only occur when `console.profile()` recordings are started
|
||||||
|
* before the tool loads.
|
||||||
*/
|
*/
|
||||||
_onPerformanceFrontEvent: Task.async(function* (eventName, recording) {
|
_onPerformanceFrontEvent: Task.async(function* (eventName, recording) {
|
||||||
if (this.getPanel("performance")) {
|
if (this.getPanel("performance")) {
|
||||||
@ -2283,7 +2289,8 @@ Toolbox.prototype = {
|
|||||||
return;
|
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
|
// Before any console recordings, we'll get a `console-profile-start` event
|
||||||
// warning us that a recording will come later (via `recording-started`), so
|
// warning us that a recording will come later (via `recording-started`), so
|
||||||
|
@ -31,7 +31,6 @@
|
|||||||
|
|
||||||
<commandset id="editMenuCommands"/>
|
<commandset id="editMenuCommands"/>
|
||||||
<keyset id="editMenuKeys"/>
|
<keyset id="editMenuKeys"/>
|
||||||
<keyset id="toolbox-keyset"/>
|
|
||||||
|
|
||||||
<popupset>
|
<popupset>
|
||||||
<menupopup id="toolbox-textbox-context-popup">
|
<menupopup id="toolbox-textbox-context-popup">
|
||||||
|
@ -12,19 +12,31 @@ const { DOM, createClass, PropTypes } = require("devtools/client/shared/vendor/r
|
|||||||
const { div } = DOM;
|
const { div } = DOM;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Side panel for the Inspector panel.
|
* Helper panel component that is using an existing DOM node
|
||||||
* This side panel is using an existing DOM node as a content.
|
* as the content. It's used by Sidebar as well as SplitBox
|
||||||
|
* components.
|
||||||
*/
|
*/
|
||||||
var InspectorTabPanel = createClass({
|
var InspectorTabPanel = createClass({
|
||||||
displayName: "InspectorTabPanel",
|
displayName: "InspectorTabPanel",
|
||||||
|
|
||||||
propTypes: {
|
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,
|
onMount: PropTypes.func,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getDefaultProps: function () {
|
||||||
|
return {
|
||||||
|
idPrefix: "",
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
componentDidMount: function () {
|
componentDidMount: function () {
|
||||||
let doc = this.refs.content.ownerDocument;
|
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.
|
// Append existing DOM node into panel's content.
|
||||||
this.refs.content.appendChild(panel);
|
this.refs.content.appendChild(panel);
|
||||||
|
@ -29,6 +29,13 @@ add_task(function* () {
|
|||||||
let cmdPaste = searchContextMenu.querySelector("[command=cmd_paste]");
|
let cmdPaste = searchContextMenu.querySelector("[command=cmd_paste]");
|
||||||
|
|
||||||
info("Opening context menu");
|
info("Opening context menu");
|
||||||
|
|
||||||
|
emptyClipboard();
|
||||||
|
|
||||||
|
let onFocus = once(searchField, "focus");
|
||||||
|
searchField.focus();
|
||||||
|
yield onFocus;
|
||||||
|
|
||||||
let onContextMenuPopup = once(searchContextMenu, "popupshowing");
|
let onContextMenuPopup = once(searchContextMenu, "popupshowing");
|
||||||
EventUtils.synthesizeMouse(searchField, 2, 2,
|
EventUtils.synthesizeMouse(searchField, 2, 2,
|
||||||
{type: "contextmenu", button: 2}, win);
|
{type: "contextmenu", button: 2}, win);
|
||||||
@ -36,10 +43,17 @@ add_task(function* () {
|
|||||||
|
|
||||||
is(cmdUndo.getAttribute("disabled"), "true", "cmdUndo is disabled");
|
is(cmdUndo.getAttribute("disabled"), "true", "cmdUndo is disabled");
|
||||||
is(cmdDelete.getAttribute("disabled"), "true", "cmdDelete is disabled");
|
is(cmdDelete.getAttribute("disabled"), "true", "cmdDelete is disabled");
|
||||||
is(cmdSelectAll.getAttribute("disabled"), "", "cmdSelectAll is enabled");
|
is(cmdSelectAll.getAttribute("disabled"), "true", "cmdSelectAll is disabled");
|
||||||
is(cmdCut.getAttribute("disabled"), "true", "cmdCut is disabled");
|
|
||||||
is(cmdCopy.getAttribute("disabled"), "true", "cmdCopy is disabled");
|
// Cut/Copy items are enabled in context menu even if there
|
||||||
is(cmdPaste.getAttribute("disabled"), "true", "cmdPaste is disabled");
|
// 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");
|
info("Closing context menu");
|
||||||
let onContextMenuHidden = once(searchContextMenu, "popuphidden");
|
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 {ViewHelpers} = require("devtools/client/shared/widgets/view-helpers");
|
||||||
const clipboardHelper = require("devtools/shared/platform/clipboard");
|
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 INSPECTOR_L10N = new LocalizationHelper("devtools/locale/inspector.properties");
|
||||||
const TOOLBOX_L10N = new LocalizationHelper("devtools/locale/toolbox.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.
|
* Represents an open instance of the Inspector for a tab.
|
||||||
* The inspector controls the breadcrumbs, the markup view, and the sidebar
|
* 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.onDetached = this.onDetached.bind(this);
|
||||||
this.onPaneToggleButtonClicked = this.onPaneToggleButtonClicked.bind(this);
|
this.onPaneToggleButtonClicked = this.onPaneToggleButtonClicked.bind(this);
|
||||||
this._onMarkupFrameLoad = this._onMarkupFrameLoad.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._target.on("will-navigate", this._onBeforeNavigate);
|
||||||
this._detectingActorFeatures = this._detectActorFeatures();
|
this._detectingActorFeatures = this._detectActorFeatures();
|
||||||
@ -108,6 +119,9 @@ InspectorPanel.prototype = {
|
|||||||
* open is effectively an asynchronous constructor
|
* open is effectively an asynchronous constructor
|
||||||
*/
|
*/
|
||||||
open: Task.async(function* () {
|
open: Task.async(function* () {
|
||||||
|
// Localize all the nodes containing a data-localization attribute.
|
||||||
|
localizeMarkup(this.panelDoc);
|
||||||
|
|
||||||
this._cssPropertiesLoaded = initCssProperties(this.toolbox);
|
this._cssPropertiesLoaded = initCssProperties(this.toolbox);
|
||||||
yield this._cssPropertiesLoaded;
|
yield this._cssPropertiesLoaded;
|
||||||
yield this.target.makeRemote();
|
yield this.target.makeRemote();
|
||||||
@ -400,6 +414,98 @@ InspectorPanel.prototype = {
|
|||||||
return this._toolbox.browserRequire;
|
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.
|
* Build the sidebar.
|
||||||
*/
|
*/
|
||||||
@ -455,56 +561,13 @@ InspectorPanel.prototype = {
|
|||||||
this.sidebar.toggleTab(true, "fontinspector");
|
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);
|
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 () {
|
setupToolbar: function () {
|
||||||
this.teardownToolbar();
|
this.teardownToolbar();
|
||||||
|
|
||||||
@ -798,6 +861,9 @@ InspectorPanel.prototype = {
|
|||||||
|
|
||||||
this.sidebar.off("select", this._setDefaultSidebar);
|
this.sidebar.off("select", this._setDefaultSidebar);
|
||||||
let sidebarDestroyer = this.sidebar.destroy();
|
let sidebarDestroyer = this.sidebar.destroy();
|
||||||
|
|
||||||
|
this.teardownSplitter();
|
||||||
|
|
||||||
this.sidebar = null;
|
this.sidebar = null;
|
||||||
|
|
||||||
this.teardownToolbar();
|
this.teardownToolbar();
|
||||||
@ -1251,7 +1317,8 @@ InspectorPanel.prototype = {
|
|||||||
* state and tooltip.
|
* state and tooltip.
|
||||||
*/
|
*/
|
||||||
onPaneToggleButtonClicked: function (e) {
|
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;
|
let isVisible = !this._sidebarToggle.state.collapsed;
|
||||||
|
|
||||||
// Make sure the sidebar has width and height attributes before collapsing
|
// 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/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/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/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 [
|
<!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;
|
|
||||||
]>
|
|
||||||
|
|
||||||
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
<html xmlns="http://www.w3.org/1999/xhtml"
|
||||||
xmlns:html="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"
|
<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">
|
<!-- Main Panel Content -->
|
||||||
<vbox flex="1" class="devtools-main-content">
|
<html:div id="inspector-main-content" class="devtools-main-content">
|
||||||
<html:div id="inspector-toolbar"
|
<html:div id="inspector-toolbar" class="devtools-toolbar" nowindowdrag="true"
|
||||||
class="devtools-toolbar"
|
data-localization-bundle="devtools/locale/inspector.properties">
|
||||||
nowindowdrag="true">
|
<html:button id="inspector-element-add-button" class="devtools-button"
|
||||||
<html:button id="inspector-element-add-button"
|
data-localization="title=inspectorAddNode.label"/>
|
||||||
title="&inspectorAddNode.label;"
|
|
||||||
class="devtools-button" />
|
|
||||||
<html:div class="devtools-toolbar-spacer" />
|
<html:div class="devtools-toolbar-spacer" />
|
||||||
<html:span id="inspector-searchlabel" />
|
<html:span id="inspector-searchlabel" />
|
||||||
<html:div id="inspector-search" class="devtools-searchbox has-clear-btn">
|
<html:div id="inspector-search" class="devtools-searchbox has-clear-btn">
|
||||||
<html:input id="inspector-searchbox" class="devtools-searchinput"
|
<html:input id="inspector-searchbox" class="devtools-searchinput"
|
||||||
type="search"
|
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:button id="inspector-searchinput-clear" class="devtools-searchinput-clear" tabindex="-1"></html:button>
|
||||||
</html:div>
|
</html:div>
|
||||||
<html:button id="inspector-eyedropper-toggle"
|
<html:button id="inspector-eyedropper-toggle"
|
||||||
title="&inspectorEyeDropper.label;"
|
data-localization="title=inspector.eyedropper.label"
|
||||||
class="devtools-button command-button-invertable" />
|
class="devtools-button command-button-invertable" />
|
||||||
<div xmlns="http://www.w3.org/1999/xhtml"
|
<html:div id="inspector-sidebar-toggle-box" />
|
||||||
id="inspector-sidebar-toggle-box" />
|
|
||||||
</html:div>
|
</html:div>
|
||||||
<vbox flex="1" id="markup-box">
|
<html:div id="markup-box" />
|
||||||
</vbox>
|
|
||||||
<html:div id="inspector-breadcrumbs-toolbar" class="devtools-toolbar">
|
<html:div id="inspector-breadcrumbs-toolbar" class="devtools-toolbar">
|
||||||
<html:div id="inspector-breadcrumbs" class="breadcrumbs-widget-container"
|
<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>
|
</html:div>
|
||||||
</vbox>
|
</html:div>
|
||||||
<splitter class="devtools-side-splitter"/>
|
|
||||||
<vbox id="inspector-sidebar-container">
|
<!-- Splitter -->
|
||||||
<!-- Specify the XHTML namespace explicitly
|
<html:div
|
||||||
otherwise the layout is broken. -->
|
xmlns="http://www.w3.org/1999/xhtml"
|
||||||
<div xmlns="http://www.w3.org/1999/xhtml"
|
id="inspector-splitter-box">
|
||||||
id="inspector-sidebar"
|
</html:div>
|
||||||
hidden="true" />
|
|
||||||
</vbox>
|
<!-- 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 -->
|
<!-- Sidebar panel definitions -->
|
||||||
<html:div xmlns="http://www.w3.org/1999/xhtml" id="tabpanels" style="visibility:collapse">
|
<html:div id="tabpanels" style="visibility:collapse">
|
||||||
<html:div id="sidebar-panel-ruleview" class="devtools-monospace theme-sidebar inspector-tabpanel">
|
<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-container" class="devtools-toolbar">
|
||||||
<html:div id="ruleview-toolbar">
|
<html:div id="ruleview-toolbar">
|
||||||
<html:div class="devtools-searchbox has-clear-btn">
|
<html:div class="devtools-searchbox has-clear-btn">
|
||||||
<html:input id="ruleview-searchbox"
|
<html:input id="ruleview-searchbox"
|
||||||
class="devtools-filterinput devtools-rule-searchbox"
|
class="devtools-filterinput devtools-rule-searchbox"
|
||||||
type="search"
|
type="search"
|
||||||
placeholder="&filterStylesPlaceholder;"/>
|
data-localization="placeholder=inspector.filterStyles.placeholder"/>
|
||||||
<html:button id="ruleview-searchinput-clear" class="devtools-searchinput-clear"></html:button>
|
<html:button id="ruleview-searchinput-clear" class="devtools-searchinput-clear"></html:button>
|
||||||
</html:div>
|
</html:div>
|
||||||
<html:div id="ruleview-command-toolbar">
|
<html:div id="ruleview-command-toolbar">
|
||||||
<html:button id="ruleview-add-rule-button" title="&addRuleButtonTooltip;" 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" title="&togglePseudoClassPanel;" 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>
|
</html:div>
|
||||||
<html:div id="pseudo-class-panel" hidden="true">
|
<html:div id="pseudo-class-panel" hidden="true">
|
||||||
@ -98,39 +102,41 @@
|
|||||||
</html:div>
|
</html:div>
|
||||||
</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 id="computedview-toolbar" class="devtools-toolbar">
|
||||||
<html:div class="devtools-searchbox has-clear-btn">
|
<html:div class="devtools-searchbox has-clear-btn">
|
||||||
<html:input id="computedview-searchbox"
|
<html:input id="computedview-searchbox"
|
||||||
class="devtools-filterinput devtools-rule-searchbox"
|
class="devtools-filterinput devtools-rule-searchbox"
|
||||||
type="search"
|
type="search"
|
||||||
placeholder="&filterStylesPlaceholder;"/>
|
data-localization="placeholder=inspector.filterStyles.placeholder"/>
|
||||||
<html:button id="computedview-searchinput-clear" class="devtools-searchinput-clear"></html:button>
|
<html:button id="computedview-searchinput-clear" class="devtools-searchinput-clear"></html:button>
|
||||||
</html:div>
|
</html:div>
|
||||||
<html:label id="browser-style-checkbox-label" for="browser-style-checkbox">
|
<html:input id="browser-style-checkbox"
|
||||||
<html:input id="browser-style-checkbox"
|
type="checkbox"
|
||||||
type="checkbox"
|
class="includebrowserstyles"/>
|
||||||
class="includebrowserstyles"
|
<html:label id="browser-style-checkbox-label" for="browser-style-checkbox"
|
||||||
label="&browserStylesLabel;"/>&browserStylesLabel;</html:label>
|
data-localization="content=inspector.browserStyles.label"/>
|
||||||
</html:div>
|
</html:div>
|
||||||
|
|
||||||
<html:div id="computedview-container">
|
<html:div id="computedview-container">
|
||||||
<html:div id="computedview-container-focusable" tabindex="-1">
|
<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-header">
|
||||||
<html:div id="boxmodel-expander" class="expander theme-twisty expandable" open=""></html:div>
|
<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>
|
||||||
|
|
||||||
<html:div id="boxmodel-container">
|
<html:div id="boxmodel-container">
|
||||||
<html:div id="boxmodel-main">
|
<html:div id="boxmodel-main">
|
||||||
<html:span class="boxmodel-legend" data-box="margin" title="&margin.tooltip;">&margin.tooltip;</html:span>
|
<html:span class="boxmodel-legend" data-box="margin" data-localization="content=boxmodel.margin;title=boxmodel.margin"/>
|
||||||
<html:div id="boxmodel-margins" data-box="margin" title="&margin.tooltip;">
|
<html:div id="boxmodel-margins" data-box="margin" data-localization="title=boxmodel.margin">
|
||||||
<html:span class="boxmodel-legend" data-box="border" title="&border.tooltip;">&border.tooltip;</html:span>
|
<html:span class="boxmodel-legend" data-box="border" data-localization="content=boxmodel.border;title=boxmodel.border"/>
|
||||||
<html:div id="boxmodel-borders" data-box="border" title="&border.tooltip;">
|
<html:div id="boxmodel-borders" data-box="border" data-localization="title=boxmodel.border">
|
||||||
<html:span class="boxmodel-legend" data-box="padding" title="&padding.tooltip;">&padding.tooltip;</html:span>
|
<html:span class="boxmodel-legend" data-box="padding" data-localization="content=boxmodel.padding;title=boxmodel.padding"/>
|
||||||
<html:div id="boxmodel-padding" data-box="padding" title="&padding.tooltip;">
|
<html:div id="boxmodel-padding" data-box="padding" data-localization="title=boxmodel.padding">
|
||||||
<html:div id="boxmodel-content" data-box="content" title="&content.tooltip;">
|
<html:div id="boxmodel-content" data-box="content" data-localization="title=boxmodel.content">
|
||||||
</html:div>
|
</html:div>
|
||||||
</html:div>
|
</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-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-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>
|
||||||
|
|
||||||
<html:div id="boxmodel-info">
|
<html:div id="boxmodel-info">
|
||||||
<html:span id="boxmodel-element-size"></html:span>
|
<html:span id="boxmodel-element-size"></html:span>
|
||||||
<html:section id="boxmodel-position-group">
|
<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:span id="boxmodel-element-position"></html:span>
|
||||||
</html:section>
|
</html:section>
|
||||||
</html:div>
|
</html:div>
|
||||||
@ -171,22 +180,21 @@
|
|||||||
<html:div id="propertyContainer" class="theme-separator" tabindex="0">
|
<html:div id="propertyContainer" class="theme-separator" tabindex="0">
|
||||||
</html:div>
|
</html:div>
|
||||||
|
|
||||||
<html:div id="computedview-no-results" hidden="">
|
<html:div id="computedview-no-results" hidden="" data-localization="content=inspector.noProperties"/>
|
||||||
&noPropertiesFound;
|
|
||||||
</html:div>
|
|
||||||
</html:div>
|
</html:div>
|
||||||
</html:div>
|
</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-toolbar">
|
||||||
<html:div class="devtools-searchbox">
|
<html:div class="devtools-searchbox">
|
||||||
<html:input id="font-preview-text-input"
|
<html:input id="font-preview-text-input" class="devtools-textinput" type="search"
|
||||||
class="devtools-textinput"
|
data-localization="placeholder=fontinspector.previewText"/>
|
||||||
type="search"
|
|
||||||
placeholder="&previewHint;"/>
|
|
||||||
</html:div>
|
</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>
|
||||||
|
|
||||||
<html:div id="font-container">
|
<html:div id="font-container">
|
||||||
@ -200,13 +208,15 @@
|
|||||||
</html:div>
|
</html:div>
|
||||||
<html:div class="font-info">
|
<html:div class="font-info">
|
||||||
<html:h1 class="font-name"></html:h1>
|
<html:h1 class="font-name"></html:h1>
|
||||||
<html:span class="font-is-local">&system;</html:span>
|
<html:span class="font-is-local" data-localization="content=fontinspector.system"/>
|
||||||
<html:span class="font-is-remote">&remote;</html:span>
|
<html:span class="font-is-remote" data-localization="content=fontinspector.remote"/>
|
||||||
<html:p class="font-format-url">
|
<html:p class="font-format-url">
|
||||||
<html:input readonly="readonly" class="font-url"></html:input>
|
<html:input readonly="readonly" class="font-url"></html:input>
|
||||||
<html:span class="font-format"></html:span>
|
<html:span class="font-format"></html:span>
|
||||||
</html:p>
|
</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:pre class="font-css-code"></html:pre>
|
||||||
</html:div>
|
</html:div>
|
||||||
</html:section>
|
</html:section>
|
||||||
@ -218,5 +228,6 @@
|
|||||||
</html:div>
|
</html:div>
|
||||||
</html:div>
|
</html:div>
|
||||||
|
|
||||||
</box>
|
</html:div>
|
||||||
</window>
|
</body>
|
||||||
|
</html>
|
@ -16,6 +16,7 @@ DevToolsModules(
|
|||||||
'inspector-commands.js',
|
'inspector-commands.js',
|
||||||
'inspector-panel.js',
|
'inspector-panel.js',
|
||||||
'inspector-search.js',
|
'inspector-search.js',
|
||||||
|
'inspector.xhtml',
|
||||||
'toolsidebar.js',
|
'toolsidebar.js',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -38,6 +38,7 @@ add_task(function* () {
|
|||||||
"Should have cancelled creating a new text property.");
|
"Should have cancelled creating a new text property.");
|
||||||
ok(!elementRuleEditor.propertyList.hasChildNodes(),
|
ok(!elementRuleEditor.propertyList.hasChildNodes(),
|
||||||
"Should not have any properties.");
|
"Should not have any properties.");
|
||||||
is(view.styleDocument.activeElement, view.styleDocument.documentElement,
|
|
||||||
|
is(view.styleDocument.activeElement, view.styleDocument.body,
|
||||||
"Correct element has focus");
|
"Correct element has focus");
|
||||||
});
|
});
|
||||||
|
@ -23,7 +23,7 @@ add_task(function* () {
|
|||||||
info("Test creating a new property and escaping");
|
info("Test creating a new property and escaping");
|
||||||
yield addProperty(view, 1, "color", "red", "VK_ESCAPE", false);
|
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");
|
"Correct element has focus");
|
||||||
|
|
||||||
let elementRuleEditor = getRuleViewRuleEditor(view, 1);
|
let elementRuleEditor = getRuleViewRuleEditor(view, 1);
|
||||||
|
@ -38,6 +38,6 @@ add_task(function* () {
|
|||||||
|
|
||||||
is(elementRuleEditor.rule.textProps.length, 1,
|
is(elementRuleEditor.rule.textProps.length, 1,
|
||||||
"Should have canceled creating a new text property.");
|
"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");
|
"Correct element has focus");
|
||||||
});
|
});
|
||||||
|
@ -51,7 +51,7 @@ add_task(function* () {
|
|||||||
let onHidden = cPicker.tooltip.once("hidden");
|
let onHidden = cPicker.tooltip.once("hidden");
|
||||||
// Validating the color change ends up updating the rule view twice
|
// Validating the color change ends up updating the rule view twice
|
||||||
let onRuleViewChanged = waitForNEvents(view, "ruleview-changed", 2);
|
let onRuleViewChanged = waitForNEvents(view, "ruleview-changed", 2);
|
||||||
EventUtils.sendKey("RETURN", spectrum.element.ownerDocument.defaultView);
|
focusAndSendKey(spectrum.element.ownerDocument.defaultView, "RETURN");
|
||||||
yield onHidden;
|
yield onHidden;
|
||||||
yield onRuleViewChanged;
|
yield onRuleViewChanged;
|
||||||
|
|
||||||
|
@ -56,7 +56,7 @@ function* basicTest(view, name, result) {
|
|||||||
let onHidden = cPicker.tooltip.once("hidden");
|
let onHidden = cPicker.tooltip.once("hidden");
|
||||||
// Validating the color change ends up updating the rule view twice
|
// Validating the color change ends up updating the rule view twice
|
||||||
let onRuleViewChanged = waitForNEvents(view, "ruleview-changed", 2);
|
let onRuleViewChanged = waitForNEvents(view, "ruleview-changed", 2);
|
||||||
EventUtils.sendKey("RETURN", spectrum.element.ownerDocument.defaultView);
|
focusAndSendKey(spectrum.element.ownerDocument.defaultView, "RETURN");
|
||||||
yield onHidden;
|
yield onHidden;
|
||||||
yield onRuleViewChanged;
|
yield onRuleViewChanged;
|
||||||
|
|
||||||
|
@ -48,7 +48,7 @@ function* testImageTooltipAfterColorChange(swatch, url, ruleView) {
|
|||||||
let spectrum = picker.spectrum;
|
let spectrum = picker.spectrum;
|
||||||
let onHidden = picker.tooltip.once("hidden");
|
let onHidden = picker.tooltip.once("hidden");
|
||||||
let onModifications = ruleView.once("ruleview-changed");
|
let onModifications = ruleView.once("ruleview-changed");
|
||||||
EventUtils.sendKey("RETURN", spectrum.element.ownerDocument.defaultView);
|
focusAndSendKey(spectrum.element.ownerDocument.defaultView, "RETURN");
|
||||||
yield onHidden;
|
yield onHidden;
|
||||||
yield onModifications;
|
yield onModifications;
|
||||||
|
|
||||||
|
@ -46,7 +46,7 @@ function* testColorChangeIsntRevertedWhenOtherTooltipIsShown(ruleView) {
|
|||||||
|
|
||||||
let onModifications = waitForNEvents(ruleView, "ruleview-changed", 2);
|
let onModifications = waitForNEvents(ruleView, "ruleview-changed", 2);
|
||||||
let onHidden = picker.tooltip.once("hidden");
|
let onHidden = picker.tooltip.once("hidden");
|
||||||
EventUtils.sendKey("RETURN", spectrum.element.ownerDocument.defaultView);
|
focusAndSendKey(spectrum.element.ownerDocument.defaultView, "RETURN");
|
||||||
yield onHidden;
|
yield onHidden;
|
||||||
yield onModifications;
|
yield onModifications;
|
||||||
|
|
||||||
|
@ -47,7 +47,7 @@ function* testPressingEnterCommitsChanges(swatch, ruleView) {
|
|||||||
let onModified = ruleView.once("ruleview-changed");
|
let onModified = ruleView.once("ruleview-changed");
|
||||||
let spectrum = cPicker.spectrum;
|
let spectrum = cPicker.spectrum;
|
||||||
let onHidden = cPicker.tooltip.once("hidden");
|
let onHidden = cPicker.tooltip.once("hidden");
|
||||||
EventUtils.sendKey("RETURN", spectrum.element.ownerDocument.defaultView);
|
focusAndSendKey(spectrum.element.ownerDocument.defaultView, "RETURN");
|
||||||
yield onHidden;
|
yield onHidden;
|
||||||
yield onModified;
|
yield onModified;
|
||||||
|
|
||||||
|
@ -53,7 +53,7 @@ function* testPressingEnterCommitsChanges(swatch, ruleView) {
|
|||||||
// Pressing RETURN ends up doing 2 rule-view updates, one for the preview and
|
// Pressing RETURN ends up doing 2 rule-view updates, one for the preview and
|
||||||
// one for the commit when the tooltip closes.
|
// one for the commit when the tooltip closes.
|
||||||
let onRuleViewChanged = waitForNEvents(ruleView, "ruleview-changed", 2);
|
let onRuleViewChanged = waitForNEvents(ruleView, "ruleview-changed", 2);
|
||||||
EventUtils.sendKey("RETURN", widget.parent.ownerDocument.defaultView);
|
focusAndSendKey(widget.parent.ownerDocument.defaultView, "RETURN");
|
||||||
yield onRuleViewChanged;
|
yield onRuleViewChanged;
|
||||||
|
|
||||||
let style = yield getComputedStyleProperty("body", null,
|
let style = yield getComputedStyleProperty("body", null,
|
||||||
|
@ -94,7 +94,7 @@ function* escapeTooltip(view) {
|
|||||||
let widget = yield bezierTooltip.widget;
|
let widget = yield bezierTooltip.widget;
|
||||||
let onHidden = bezierTooltip.tooltip.once("hidden");
|
let onHidden = bezierTooltip.tooltip.once("hidden");
|
||||||
let onModifications = view.once("ruleview-changed");
|
let onModifications = view.once("ruleview-changed");
|
||||||
EventUtils.sendKey("ESCAPE", widget.parent.ownerDocument.defaultView);
|
focusAndSendKey(widget.parent.ownerDocument.defaultView, "ESCAPE");
|
||||||
yield onHidden;
|
yield onHidden;
|
||||||
yield onModifications;
|
yield onModifications;
|
||||||
}
|
}
|
||||||
|
@ -64,6 +64,8 @@ add_task(function* () {
|
|||||||
tooltip.hide();
|
tooltip.hide();
|
||||||
yield onHidden;
|
yield onHidden;
|
||||||
ok(!tooltip.isVisible(), "color picker tooltip is closed");
|
ok(!tooltip.isVisible(), "color picker tooltip is closed");
|
||||||
|
|
||||||
|
yield waitForTick();
|
||||||
});
|
});
|
||||||
|
|
||||||
function* testESC(swatch, inspector, testActor) {
|
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");
|
"The inplace editor wasn't shown as a result of the filter swatch click");
|
||||||
|
|
||||||
yield hideTooltipAndWaitForRuleViewChanged(filterTooltip, view);
|
yield hideTooltipAndWaitForRuleViewChanged(filterTooltip, view);
|
||||||
|
|
||||||
|
yield waitForTick();
|
||||||
});
|
});
|
||||||
|
@ -28,6 +28,13 @@ add_task(function* () {
|
|||||||
let cmdPaste = searchContextMenu.querySelector("[command=cmd_paste]");
|
let cmdPaste = searchContextMenu.querySelector("[command=cmd_paste]");
|
||||||
|
|
||||||
info("Opening context menu");
|
info("Opening context menu");
|
||||||
|
|
||||||
|
emptyClipboard();
|
||||||
|
|
||||||
|
let onFocus = once(searchField, "focus");
|
||||||
|
searchField.focus();
|
||||||
|
yield onFocus;
|
||||||
|
|
||||||
let onContextMenuPopup = once(searchContextMenu, "popupshowing");
|
let onContextMenuPopup = once(searchContextMenu, "popupshowing");
|
||||||
EventUtils.synthesizeMouse(searchField, 2, 2,
|
EventUtils.synthesizeMouse(searchField, 2, 2,
|
||||||
{type: "contextmenu", button: 2}, win);
|
{type: "contextmenu", button: 2}, win);
|
||||||
@ -35,10 +42,17 @@ add_task(function* () {
|
|||||||
|
|
||||||
is(cmdUndo.getAttribute("disabled"), "true", "cmdUndo is disabled");
|
is(cmdUndo.getAttribute("disabled"), "true", "cmdUndo is disabled");
|
||||||
is(cmdDelete.getAttribute("disabled"), "true", "cmdDelete is disabled");
|
is(cmdDelete.getAttribute("disabled"), "true", "cmdDelete is disabled");
|
||||||
is(cmdSelectAll.getAttribute("disabled"), "", "cmdSelectAll is enabled");
|
is(cmdSelectAll.getAttribute("disabled"), "true", "cmdSelectAll is disabled");
|
||||||
is(cmdCut.getAttribute("disabled"), "true", "cmdCut is disabled");
|
|
||||||
is(cmdCopy.getAttribute("disabled"), "true", "cmdCopy is disabled");
|
// Cut/Copy items are enabled in context menu even if there
|
||||||
is(cmdPaste.getAttribute("disabled"), "true", "cmdPaste is disabled");
|
// 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");
|
info("Closing context menu");
|
||||||
let onContextMenuHidden = once(searchContextMenu, "popuphidden");
|
let onContextMenuHidden = once(searchContextMenu, "popuphidden");
|
||||||
|
@ -58,7 +58,7 @@ const TEST_DATA = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
add_task(function* () {
|
add_task(function* () {
|
||||||
requestLongerTimeout(2);
|
requestLongerTimeout(4);
|
||||||
|
|
||||||
info("Starting the test with the pref set to true before toolbox is opened");
|
info("Starting the test with the pref set to true before toolbox is opened");
|
||||||
yield setUserAgentStylesPref(true);
|
yield setUserAgentStylesPref(true);
|
||||||
|
@ -634,7 +634,9 @@ var togglePropStatus = Task.async(function* (view, textProp) {
|
|||||||
*/
|
*/
|
||||||
var focusNewRuleViewProperty = Task.async(function* (ruleEditor) {
|
var focusNewRuleViewProperty = Task.async(function* (ruleEditor) {
|
||||||
info("Clicking on a close ruleEditor brace to start editing a new property");
|
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,
|
let editor = yield focusEditableField(ruleEditor.ruleView,
|
||||||
ruleEditor.closeBrace);
|
ruleEditor.closeBrace);
|
||||||
|
|
||||||
@ -814,3 +816,13 @@ function waitForStyleModification(inspector) {
|
|||||||
inspector.on("markupmutation", checkForStyleModification);
|
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-01.js]
|
||||||
[browser_inspector_pane-toggle-02.js]
|
[browser_inspector_pane-toggle-02.js]
|
||||||
[browser_inspector_pane-toggle-03.js]
|
[browser_inspector_pane-toggle-03.js]
|
||||||
[browser_inspector_pane-toggle-04.js]
|
|
||||||
[browser_inspector_pane-toggle-05.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
|
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]
|
[browser_inspector_picker-stop-on-destroy.js]
|
||||||
|
@ -32,6 +32,10 @@ const NODES = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
add_task(function* () {
|
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);
|
let { inspector, toolbox } = yield openInspectorForURL(TEST_URI);
|
||||||
|
|
||||||
// No way to wait for scrolling to end (Bug 1172171)
|
// 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");
|
info("Open the inspector in a side toolbox host");
|
||||||
let {toolbox, inspector} = yield openInspectorForURL("about:blank", "side");
|
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");
|
let button = inspector.panelDoc.querySelector(".sidebar-toggle");
|
||||||
ok(!panel.classList.contains("pane-collapsed"), "The panel is in expanded state");
|
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 {inspector} = yield openInspectorForURL("about:blank");
|
||||||
|
|
||||||
let button = inspector.panelDoc.querySelector(".sidebar-toggle");
|
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");
|
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* () {
|
add_task(function* () {
|
||||||
let {inspector} = yield openInspectorForURL("about:blank", "side");
|
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");
|
let button = inspector.panelDoc.querySelector(".sidebar-toggle");
|
||||||
|
|
||||||
ok(!panel.classList.contains("pane-collapsed"), "The panel is in expanded state");
|
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 cmdCopy = searchContextMenu.querySelector("[command=cmd_copy]");
|
||||||
let cmdPaste = searchContextMenu.querySelector("[command=cmd_paste]");
|
let cmdPaste = searchContextMenu.querySelector("[command=cmd_paste]");
|
||||||
|
|
||||||
|
emptyClipboard();
|
||||||
|
|
||||||
info("Opening context menu");
|
info("Opening context menu");
|
||||||
|
let onFocus = once(searchBox, "focus");
|
||||||
|
searchBox.focus();
|
||||||
|
yield onFocus;
|
||||||
|
|
||||||
let onContextMenuPopup = once(searchContextMenu, "popupshowing");
|
let onContextMenuPopup = once(searchContextMenu, "popupshowing");
|
||||||
EventUtils.synthesizeMouse(searchBox, 2, 2,
|
EventUtils.synthesizeMouse(searchBox, 2, 2,
|
||||||
{type: "contextmenu", button: 2}, win);
|
{type: "contextmenu", button: 2}, win);
|
||||||
@ -34,10 +40,17 @@ add_task(function* () {
|
|||||||
|
|
||||||
is(cmdUndo.getAttribute("disabled"), "true", "cmdUndo is disabled");
|
is(cmdUndo.getAttribute("disabled"), "true", "cmdUndo is disabled");
|
||||||
is(cmdDelete.getAttribute("disabled"), "true", "cmdDelete is disabled");
|
is(cmdDelete.getAttribute("disabled"), "true", "cmdDelete is disabled");
|
||||||
is(cmdSelectAll.getAttribute("disabled"), "", "cmdSelectAll is enabled");
|
is(cmdSelectAll.getAttribute("disabled"), "true", "cmdSelectAll is disabled");
|
||||||
is(cmdCut.getAttribute("disabled"), "true", "cmdCut is disabled");
|
|
||||||
is(cmdCopy.getAttribute("disabled"), "true", "cmdCopy is disabled");
|
// Cut/Copy items are enabled in context menu even if there
|
||||||
is(cmdPaste.getAttribute("disabled"), "true", "cmdPaste is disabled");
|
// 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");
|
info("Closing context menu");
|
||||||
let onContextMenuHidden = once(searchContextMenu, "popuphidden");
|
let onContextMenuHidden = once(searchContextMenu, "popuphidden");
|
||||||
@ -47,6 +60,7 @@ add_task(function* () {
|
|||||||
info("Copy text in search field using the context menu");
|
info("Copy text in search field using the context menu");
|
||||||
searchBox.value = TEST_INPUT;
|
searchBox.value = TEST_INPUT;
|
||||||
searchBox.select();
|
searchBox.select();
|
||||||
|
searchBox.focus();
|
||||||
EventUtils.synthesizeMouse(searchBox, 2, 2,
|
EventUtils.synthesizeMouse(searchBox, 2, 2,
|
||||||
{type: "contextmenu", button: 2}, win);
|
{type: "contextmenu", button: 2}, win);
|
||||||
yield onContextMenuPopup;
|
yield onContextMenuPopup;
|
||||||
|
@ -66,12 +66,7 @@ ToolSidebar.prototype = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
get InspectorTabPanel() {
|
get InspectorTabPanel() {
|
||||||
if (!this._InspectorTabPanel) {
|
return this._toolPanel.InspectorTabPanel;
|
||||||
this._InspectorTabPanel =
|
|
||||||
this.React.createFactory(this.browserRequire(
|
|
||||||
"devtools/client/inspector/components/inspector-tab-panel"));
|
|
||||||
}
|
|
||||||
return this._InspectorTabPanel;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// Rendering
|
// Rendering
|
||||||
@ -90,7 +85,14 @@ ToolSidebar.prototype = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
addExistingTab: function (id, title, selected) {
|
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);
|
this.emit("new-tab-registered", id);
|
||||||
},
|
},
|
||||||
@ -105,6 +107,7 @@ ToolSidebar.prototype = {
|
|||||||
addFrameTab: function (id, title, url, selected) {
|
addFrameTab: function (id, title, url, selected) {
|
||||||
let panel = this.InspectorTabPanel({
|
let panel = this.InspectorTabPanel({
|
||||||
id: id,
|
id: id,
|
||||||
|
idPrefix: this.TABPANEL_ID_PREFIX,
|
||||||
key: id,
|
key: id,
|
||||||
title: title,
|
title: title,
|
||||||
url: url,
|
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.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-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/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.xhtml (framework/connect/connect.xhtml)
|
||||||
content/framework/connect/connect.css (framework/connect/connect.css)
|
content/framework/connect/connect.css (framework/connect/connect.css)
|
||||||
content/framework/connect/connect.js (framework/connect/connect.js)
|
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,
|
- control the stepping commands in the debugger (continue, step over,
|
||||||
- step in and step out). -->
|
- step in and step out). -->
|
||||||
<!ENTITY debuggerUI.stepping.resume1 "VK_F8">
|
<!ENTITY debuggerUI.stepping.resume1 "VK_F8">
|
||||||
<!ENTITY debuggerUI.stepping.resume2 "VK_SLASH">
|
|
||||||
<!ENTITY debuggerUI.stepping.stepOver1 "VK_F10">
|
<!ENTITY debuggerUI.stepping.stepOver1 "VK_F10">
|
||||||
<!ENTITY debuggerUI.stepping.stepOver2 "VK_QUOTE">
|
|
||||||
<!ENTITY debuggerUI.stepping.stepIn1 "VK_F11">
|
<!ENTITY debuggerUI.stepping.stepIn1 "VK_F11">
|
||||||
<!ENTITY debuggerUI.stepping.stepIn2 "VK_SEMICOLON">
|
|
||||||
<!ENTITY debuggerUI.stepping.stepOut1 "VK_F11">
|
<!ENTITY debuggerUI.stepping.stepOut1 "VK_F11">
|
||||||
<!ENTITY debuggerUI.stepping.stepOut2 "VK_SEMICOLON">
|
|
||||||
|
|
||||||
<!-- LOCALIZATION NOTE (debuggerUI.context.newTab): This is the label
|
<!-- LOCALIZATION NOTE (debuggerUI.context.newTab): This is the label
|
||||||
- for the Open in New Tab menu item displayed in the context menu of the
|
- 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.
|
# used in the page.
|
||||||
inspector.sidebar.computedViewTitle=Computed
|
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):
|
# LOCALIZATION NOTE (inspector.sidebar.animationInspectorTitle):
|
||||||
# This is the title shown in a tab in the side panel of the Inspector panel
|
# 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.
|
# that corresponds to the tool displaying animations defined in the page.
|
||||||
inspector.sidebar.animationInspectorTitle=Animations
|
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 += [
|
DIRS += [
|
||||||
'reps',
|
'reps',
|
||||||
|
'splitter',
|
||||||
'tabs',
|
'tabs',
|
||||||
'tree'
|
'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);
|
document.body.appendChild(panel);
|
||||||
|
|
||||||
return setState(tabbarReact, Object.assign({}, tabbarReact.state, {
|
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) {
|
} catch(e) {
|
||||||
|
@ -37,10 +37,6 @@ Object.defineProperty(this, "EVENTS", {
|
|||||||
writable: false
|
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,
|
* A table widget with various features like resizble/toggleable columns,
|
||||||
* sorting, keyboard navigation etc.
|
* sorting, keyboard navigation etc.
|
||||||
@ -1510,11 +1506,6 @@ Cell.prototype = {
|
|||||||
value = span;
|
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) {
|
if (value instanceof Node) {
|
||||||
this.label.removeAttribute("value");
|
this.label.removeAttribute("value");
|
||||||
|
|
||||||
|
@ -28,6 +28,7 @@
|
|||||||
|
|
||||||
#computedview-toolbar {
|
#computedview-toolbar {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
#browser-style-checkbox {
|
#browser-style-checkbox {
|
||||||
@ -36,15 +37,11 @@
|
|||||||
an extra space after. */
|
an extra space after. */
|
||||||
margin-inline-start: 5px;
|
margin-inline-start: 5px;
|
||||||
margin-inline-end: 5px;
|
margin-inline-end: 5px;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#browser-style-checkbox-label {
|
#browser-style-checkbox-label {
|
||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
|
|
||||||
/* Vertically center the 'Browser styles' checkbox in the
|
|
||||||
Computed panel with its label. */
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#propertyContainer {
|
#propertyContainer {
|
||||||
|
@ -11,9 +11,47 @@
|
|||||||
--eyedropper-image: url(images/firebug/command-eyedropper.svg);
|
--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
|
/* Use flex layout for the Inspector toolbar. For now, it's done
|
||||||
specifically for the Inspector toolbar since general rule applied
|
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
|
panels (e.g. webconsole, debugger), these are not ready for HTML
|
||||||
layout yet. */
|
layout yet. */
|
||||||
#inspector-toolbar.devtools-toolbar {
|
#inspector-toolbar.devtools-toolbar {
|
||||||
@ -124,8 +162,9 @@
|
|||||||
is fixed and the all-tabs-menu is available again. */
|
is fixed and the all-tabs-menu is available again. */
|
||||||
#inspector-sidebar-container {
|
#inspector-sidebar-container {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
min-width: 300px;
|
min-width: 50px;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
#inspector-sidebar {
|
#inspector-sidebar {
|
||||||
@ -149,3 +188,20 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
margin: 5px;
|
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) {
|
if (this.hud.NEW_CONSOLE_OUTPUT_ENABLED) {
|
||||||
this.hud.newConsoleOutput.dispatchMessageAdd(response);
|
this.hud.newConsoleOutput.dispatchMessageAdd(response);
|
||||||
// @TODO figure out what to do about the callback.
|
// @TODO figure out what to do about the callback.
|
||||||
|
callback && callback();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let msg = new Messages.JavaScriptEvalOutput(response,
|
let msg = new Messages.JavaScriptEvalOutput(response,
|
||||||
@ -423,12 +424,17 @@ JSTerm.prototype = {
|
|||||||
*/
|
*/
|
||||||
execute: function (executeString, callback) {
|
execute: function (executeString, callback) {
|
||||||
let deferred = promise.defer();
|
let deferred = promise.defer();
|
||||||
let resultCallback = function (msg) {
|
let resultCallback;
|
||||||
deferred.resolve(msg);
|
if (this.hud.NEW_CONSOLE_OUTPUT_ENABLED) {
|
||||||
if (callback) {
|
resultCallback = () => deferred.resolve();
|
||||||
callback(msg);
|
} else {
|
||||||
}
|
resultCallback = (msg) => {
|
||||||
};
|
deferred.resolve(msg);
|
||||||
|
if (callback) {
|
||||||
|
callback(msg);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// attempt to execute the content of the inputNode
|
// attempt to execute the content of the inputNode
|
||||||
executeString = executeString || this.getInputValue();
|
executeString = executeString || this.getInputValue();
|
||||||
|
@ -12,13 +12,12 @@ const {
|
|||||||
DOM: dom,
|
DOM: dom,
|
||||||
PropTypes
|
PropTypes
|
||||||
} = require("devtools/client/shared/vendor/react");
|
} = 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);
|
const MessageIcon = createFactory(require("devtools/client/webconsole/new-console-output/components/message-icon").MessageIcon);
|
||||||
|
|
||||||
ConsoleCommand.displayName = "ConsoleCommand";
|
ConsoleCommand.displayName = "ConsoleCommand";
|
||||||
|
|
||||||
ConsoleCommand.propTypes = {
|
ConsoleCommand.propTypes = {
|
||||||
message: PropTypes.instanceOf(ConsoleCommandType).isRequired,
|
message: PropTypes.object.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -26,6 +26,14 @@ function EvaluationResult(props) {
|
|||||||
const {source, type, level} = message;
|
const {source, type, level} = message;
|
||||||
const icon = MessageIcon({level});
|
const icon = MessageIcon({level});
|
||||||
|
|
||||||
|
let messageBody;
|
||||||
|
if (message.messageText) {
|
||||||
|
messageBody = message.messageText;
|
||||||
|
} else {
|
||||||
|
messageBody = GripMessageBody({grip: message.parameters});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
const classes = ["message", "cm-s-mozilla"];
|
const classes = ["message", "cm-s-mozilla"];
|
||||||
|
|
||||||
classes.push(source);
|
classes.push(source);
|
||||||
@ -41,7 +49,7 @@ function EvaluationResult(props) {
|
|||||||
dom.span({ className: "message-body-wrapper" },
|
dom.span({ className: "message-body-wrapper" },
|
||||||
dom.span({ className: "message-flex-body" },
|
dom.span({ className: "message-flex-body" },
|
||||||
dom.span({ className: "message-body devtools-monospace" },
|
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.displayName = "VariablesViewLink";
|
||||||
|
|
||||||
VariablesViewLink.propTypes = {
|
VariablesViewLink.propTypes = {
|
||||||
object: PropTypes.object.required
|
object: PropTypes.object.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
function VariablesViewLink(props) {
|
function VariablesViewLink(props) {
|
||||||
|
@ -2,29 +2,36 @@
|
|||||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const { stubPreparedMessages } = require("devtools/client/webconsole/new-console-output/test/fixtures/stubs/index");
|
// Test utils.
|
||||||
const { EvaluationResult } = require("devtools/client/webconsole/new-console-output/components/message-types/evaluation-result");
|
|
||||||
|
|
||||||
const expect = require("expect");
|
const expect = require("expect");
|
||||||
|
const { render } = require("enzyme");
|
||||||
|
|
||||||
const {
|
// React
|
||||||
renderComponent
|
const { createFactory } = require("devtools/client/shared/vendor/react");
|
||||||
} = require("devtools/client/webconsole/new-console-output/test/helpers");
|
|
||||||
|
// 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:", () => {
|
describe("EvaluationResult component:", () => {
|
||||||
it("renders a grip result", () => {
|
it("renders a grip result", () => {
|
||||||
const message = stubPreparedMessages.get("new Date(0)");
|
const message = stubPreparedMessages.get("new Date(0)");
|
||||||
const props = {
|
const wrapper = render(EvaluationResult({ message }));
|
||||||
message
|
|
||||||
};
|
|
||||||
const rendered = renderComponent(EvaluationResult, props);
|
|
||||||
|
|
||||||
const messageBody = getMessageBody(rendered);
|
expect(wrapper.find(".message-body").text()).toBe("Date 1970-01-01T00:00:00.000Z");
|
||||||
expect(messageBody.textContent).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
|
// Evaluation Result
|
||||||
|
|
||||||
const evaluationResultCommands = [
|
const evaluationResultCommands = [
|
||||||
"new Date(0)"
|
"new Date(0)",
|
||||||
|
"asdf()"
|
||||||
];
|
];
|
||||||
|
|
||||||
let evaluationResult = new Map(evaluationResultCommands.map(cmd => [cmd, cmd]));
|
let evaluationResult = new Map(evaluationResultCommands.map(cmd => [cmd, cmd]));
|
||||||
|
@ -19,7 +19,6 @@ stubPreparedMessages.set("new Date(0)", new ConsoleMessage({
|
|||||||
"source": "javascript",
|
"source": "javascript",
|
||||||
"type": "result",
|
"type": "result",
|
||||||
"level": "log",
|
"level": "log",
|
||||||
"messageText": null,
|
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"actor": "server1.conn0.child1/obj30",
|
"actor": "server1.conn0.child1/obj30",
|
||||||
@ -33,7 +32,23 @@ stubPreparedMessages.set("new Date(0)", new ConsoleMessage({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"repeat": 1,
|
"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,
|
"stacktrace": null,
|
||||||
"frame": null
|
"frame": null
|
||||||
}));
|
}));
|
||||||
@ -54,11 +69,41 @@ stubPackets.set("new Date(0)", {
|
|||||||
"timestamp": 0
|
"timestamp": 0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"timestamp": 1471886229652,
|
"timestamp": 1474405330863,
|
||||||
"exception": null,
|
"exception": null,
|
||||||
"helperResult": 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 = {
|
module.exports = {
|
||||||
stubPreparedMessages,
|
stubPreparedMessages,
|
||||||
|
@ -7,3 +7,7 @@ support-files =
|
|||||||
test-console.html
|
test-console.html
|
||||||
|
|
||||||
[browser_webconsole_init.js]
|
[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;
|
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);
|
this);
|
||||||
|
|
||||||
Services.prefs.setBoolPref("devtools.webconsole.new-frontend-enabled", true);
|
Services.prefs.setBoolPref("devtools.webconsole.new-frontend-enabled", true);
|
||||||
registerCleanupFunction(() => {
|
registerCleanupFunction(function* () {
|
||||||
Services.prefs.clearUserPref("devtools.webconsole.new-frontend-enabled");
|
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;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
const frame = {
|
const frame = message.filename ? {
|
||||||
source: message.filename || null,
|
source: message.filename,
|
||||||
line: message.lineNumber || null,
|
line: message.lineNumber,
|
||||||
column: message.columnNumber || null
|
column: message.columnNumber,
|
||||||
};
|
} : null;
|
||||||
|
|
||||||
return new ConsoleMessage({
|
return new ConsoleMessage({
|
||||||
source: MESSAGE_SOURCE.CONSOLE_API,
|
source: MESSAGE_SOURCE.CONSOLE_API,
|
||||||
@ -119,11 +119,11 @@ function transformPacket(packet) {
|
|||||||
level = MESSAGE_LEVEL.INFO;
|
level = MESSAGE_LEVEL.INFO;
|
||||||
}
|
}
|
||||||
|
|
||||||
const frame = {
|
const frame = pageError.sourceName ? {
|
||||||
source: pageError.sourceName,
|
source: pageError.sourceName,
|
||||||
line: pageError.lineNumber,
|
line: pageError.lineNumber,
|
||||||
column: pageError.columnNumber
|
column: pageError.columnNumber
|
||||||
};
|
} : null;
|
||||||
|
|
||||||
return new ConsoleMessage({
|
return new ConsoleMessage({
|
||||||
source: MESSAGE_SOURCE.JAVASCRIPT,
|
source: MESSAGE_SOURCE.JAVASCRIPT,
|
||||||
@ -148,13 +148,18 @@ function transformPacket(packet) {
|
|||||||
|
|
||||||
case "evaluationResult":
|
case "evaluationResult":
|
||||||
default: {
|
default: {
|
||||||
let { result } = packet;
|
let {
|
||||||
|
exceptionMessage: messageText,
|
||||||
|
result: parameters
|
||||||
|
} = packet;
|
||||||
|
|
||||||
|
const level = messageText ? MESSAGE_LEVEL.ERROR : MESSAGE_LEVEL.LOG;
|
||||||
return new ConsoleMessage({
|
return new ConsoleMessage({
|
||||||
source: MESSAGE_SOURCE.JAVASCRIPT,
|
source: MESSAGE_SOURCE.JAVASCRIPT,
|
||||||
type: MESSAGE_TYPE.RESULT,
|
type: MESSAGE_TYPE.RESULT,
|
||||||
level: MESSAGE_LEVEL.LOG,
|
level,
|
||||||
parameters: result,
|
messageText,
|
||||||
|
parameters,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,9 @@
|
|||||||
*
|
*
|
||||||
* Once JSTerm is also written in React/Redux, these will be actions.
|
* Once JSTerm is also written in React/Redux, these will be actions.
|
||||||
*/
|
*/
|
||||||
exports.openVariablesView = (object) => {
|
exports.openVariablesView = (objectActor) => {
|
||||||
window.jsterm.openVariablesView({objectActor: object});
|
window.jsterm.openVariablesView({
|
||||||
|
objectActor,
|
||||||
|
autofocus: true,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
@ -582,6 +582,7 @@ WebConsoleFrame.prototype = {
|
|||||||
// XXX: We should actually stop output from happening on old output
|
// XXX: We should actually stop output from happening on old output
|
||||||
// panel, but for now let's just hide it.
|
// panel, but for now let's just hide it.
|
||||||
this.experimentalOutputNode = this.outputNode.cloneNode();
|
this.experimentalOutputNode = this.outputNode.cloneNode();
|
||||||
|
this.experimentalOutputNode.removeAttribute("tabindex");
|
||||||
this.outputNode.hidden = true;
|
this.outputNode.hidden = true;
|
||||||
this.outputNode.parentNode.appendChild(this.experimentalOutputNode);
|
this.outputNode.parentNode.appendChild(this.experimentalOutputNode);
|
||||||
// @TODO Once the toolbox has been converted to React, see if passing
|
// @TODO Once the toolbox has been converted to React, see if passing
|
||||||
|
@ -406,7 +406,10 @@ TabSources.prototype = {
|
|||||||
* @return Promise of a SourceMapConsumer
|
* @return Promise of a SourceMapConsumer
|
||||||
*/
|
*/
|
||||||
fetchSourceMap: function (aSource) {
|
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);
|
return this._sourceMaps.get(aSource);
|
||||||
}
|
}
|
||||||
else if (!aSource || !aSource.sourceMapURL) {
|
else if (!aSource || !aSource.sourceMapURL) {
|
||||||
@ -457,10 +460,10 @@ TabSources.prototype = {
|
|||||||
* them from aScriptURL.
|
* them from aScriptURL.
|
||||||
*/
|
*/
|
||||||
_fetchSourceMap: function (aAbsSourceMapURL, aSourceURL) {
|
_fetchSourceMap: function (aAbsSourceMapURL, aSourceURL) {
|
||||||
if (!this._useSourceMaps) {
|
assert(this._useSourceMaps,
|
||||||
return resolve(null);
|
"Cannot fetch sourcemaps if they are disabled");
|
||||||
}
|
|
||||||
else if (this._sourceMapCache[aAbsSourceMapURL]) {
|
if (this._sourceMapCache[aAbsSourceMapURL]) {
|
||||||
return 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-01.js]
|
||||||
[test_listsources-02.js]
|
[test_listsources-02.js]
|
||||||
[test_listsources-03.js]
|
[test_listsources-03.js]
|
||||||
|
[test_listsources-04.js]
|
||||||
[test_new_source-01.js]
|
[test_new_source-01.js]
|
||||||
[test_sourcemaps-01.js]
|
[test_sourcemaps-01.js]
|
||||||
[test_sourcemaps-02.js]
|
[test_sourcemaps-02.js]
|
||||||
|
@ -53,12 +53,6 @@ rule.pseudoElement=Pseudo-elements
|
|||||||
# pseudo element header
|
# pseudo element header
|
||||||
rule.selectedElement=This Element
|
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
|
# 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
|
# entered into the rule view a warning icon is displayed. This text is used for
|
||||||
# the title attribute of the warning icon.
|
# 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
|
# LOCALIZATION NOTE (styleinspector.contextmenu.addNewRule): Text displayed in the
|
||||||
# rule view context menu for adding a new rule to the element.
|
# 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
|
styleinspector.contextmenu.addNewRule=Add New Rule
|
||||||
|
|
||||||
# LOCALIZATION NOTE (styleinspector.contextmenu.addRule.accessKey): Access key for
|
# LOCALIZATION NOTE (styleinspector.contextmenu.addRule.accessKey): Access key for
|
||||||
|
@ -707,9 +707,8 @@ static bool
|
|||||||
HasCameraPermission(const nsCString& aOrigin)
|
HasCameraPermission(const nsCString& aOrigin)
|
||||||
{
|
{
|
||||||
// Name used with nsIPermissionManager
|
// Name used with nsIPermissionManager
|
||||||
static const char* cameraPermission = "camera";
|
static const char* cameraPermission = "MediaManagerVideo";
|
||||||
bool allowed = false;
|
bool allowed = false;
|
||||||
bool permanent = false;
|
|
||||||
nsresult rv;
|
nsresult rv;
|
||||||
nsCOMPtr<nsIPermissionManager> mgr =
|
nsCOMPtr<nsIPermissionManager> mgr =
|
||||||
do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv);
|
do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv);
|
||||||
@ -728,19 +727,9 @@ HasCameraPermission(const nsCString& aOrigin)
|
|||||||
&video);
|
&video);
|
||||||
if (NS_SUCCEEDED(rv)) {
|
if (NS_SUCCEEDED(rv)) {
|
||||||
allowed = (video == nsIPermissionManager::ALLOW_ACTION);
|
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.
|
// Session permissions are removed after one use.
|
||||||
if (allowed && !permanent) {
|
if (allowed) {
|
||||||
mgr->RemoveFromPrincipal(principal, cameraPermission);
|
mgr->RemoveFromPrincipal(principal, cameraPermission);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
package org.mozilla.gecko.media;
|
package org.mozilla.gecko.media;
|
||||||
|
|
||||||
import org.mozilla.gecko.GeckoAppShell;
|
import org.mozilla.gecko.GeckoAppShell;
|
||||||
|
import org.mozilla.gecko.Telemetry;
|
||||||
|
|
||||||
import android.content.ComponentName;
|
import android.content.ComponentName;
|
||||||
import android.content.Context;
|
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
|
@Override
|
||||||
public void binderDied() {
|
public void binderDied() {
|
||||||
Log.e(LOGTAG, "remote codec is dead");
|
Log.e(LOGTAG, "remote codec is dead");
|
||||||
|
reportDecodingProcessCrash();
|
||||||
handleRemoteDeath();
|
handleRemoteDeath();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ function PageAction(options, extension) {
|
|||||||
|
|
||||||
this.options = {
|
this.options = {
|
||||||
title: options.default_title || extension.name,
|
title: options.default_title || extension.name,
|
||||||
id: extension.id,
|
id: `{${extension.uuid}}`,
|
||||||
clickCallback: () => {
|
clickCallback: () => {
|
||||||
if (this.popupUrl) {
|
if (this.popupUrl) {
|
||||||
let win = Services.wm.getMostRecentWindow("navigator:browser");
|
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");
|
Cu.import("resource://gre/modules/PageActions.jsm");
|
||||||
|
|
||||||
function isPageActionShown(extensionId) {
|
function isPageActionShown(uuid) {
|
||||||
return PageActions.isShown(extensionId);
|
return PageActions.isShown(uuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
function clickPageAction(extensionId) {
|
function clickPageAction(uuid) {
|
||||||
PageActions.synthesizeClick(extensionId);
|
PageActions.synthesizeClick(uuid);
|
||||||
}
|
}
|
||||||
|
@ -41,10 +41,15 @@ function background() {
|
|||||||
browser.test.sendMessage("page-action-clicked");
|
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({
|
let extension = ExtensionTestUtils.loadExtension({
|
||||||
background,
|
background,
|
||||||
manifest: {
|
manifest: {
|
||||||
@ -55,6 +60,11 @@ add_task(function* test_contentscript() {
|
|||||||
"18": "extension.png",
|
"18": "extension.png",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"applications": {
|
||||||
|
"gecko": {
|
||||||
|
"id": "foo@bar.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
files: {
|
files: {
|
||||||
"extension.png": IMAGE_ARRAYBUFFER,
|
"extension.png": IMAGE_ARRAYBUFFER,
|
||||||
@ -62,26 +72,26 @@ add_task(function* test_contentscript() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
yield extension.startup();
|
yield extension.startup();
|
||||||
yield extension.awaitMessage("ready");
|
let {uuid} = yield extension.awaitMessage("ready");
|
||||||
|
|
||||||
extension.sendMessage("pageAction-show");
|
extension.sendMessage("pageAction-show");
|
||||||
yield extension.awaitMessage("page-action-shown");
|
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");
|
extension.sendMessage("pageAction-hide");
|
||||||
yield extension.awaitMessage("page-action-hidden");
|
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");
|
extension.sendMessage("pageAction-show");
|
||||||
yield extension.awaitMessage("page-action-shown");
|
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");
|
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();
|
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>
|
</script>
|
||||||
|
|
||||||
|
@ -54,7 +54,12 @@ add_task(function* test_contentscript() {
|
|||||||
browser.test.sendMessage("page-action-onClicked-fired");
|
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() {
|
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.
|
// We don't need to set the popup when testing default_popup.
|
||||||
if (name != "default.html") {
|
if (name != "default.html") {
|
||||||
extension.sendMessage("page-action-set-popup", {name});
|
extension.sendMessage("page-action-set-popup", {name});
|
||||||
@ -124,7 +129,7 @@ add_task(function* test_contentscript() {
|
|||||||
extension.sendMessage("page-action-enable-onClicked-listener");
|
extension.sendMessage("page-action-enable-onClicked-listener");
|
||||||
yield extension.awaitMessage("page-action-onClicked-listener-enabled");
|
yield extension.awaitMessage("page-action-onClicked-listener-enabled");
|
||||||
|
|
||||||
clickPageAction(extension.id);
|
clickPageAction(uuid);
|
||||||
yield extension.awaitMessage("page-action-onClicked-fired");
|
yield extension.awaitMessage("page-action-onClicked-fired");
|
||||||
|
|
||||||
extension.sendMessage("page-action-disable-onClicked-listener");
|
extension.sendMessage("page-action-disable-onClicked-listener");
|
||||||
@ -132,7 +137,7 @@ add_task(function* test_contentscript() {
|
|||||||
} else {
|
} else {
|
||||||
ok(url.includes(name), "Calling pageAction.getPopup should return the correct popup URL when the popup is set.");
|
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");
|
let location = yield extension.awaitMessage("page-action-from-popup");
|
||||||
ok(location.includes(name), "The popup with the correct URL should be shown.");
|
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.startup();
|
||||||
yield extension.awaitMessage("ready");
|
let {uuid} = yield extension.awaitMessage("ready");
|
||||||
|
|
||||||
extension.sendMessage("page-action-show");
|
extension.sendMessage("page-action-show");
|
||||||
yield extension.awaitMessage("page-action-shown");
|
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("default.html", uuid);
|
||||||
yield testPopup("a.html");
|
yield testPopup("a.html", uuid);
|
||||||
yield testPopup("");
|
yield testPopup("", uuid);
|
||||||
yield testPopup("b.html");
|
yield testPopup("b.html", uuid);
|
||||||
|
|
||||||
yield extension.unload();
|
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>
|
</script>
|
||||||
|
|
||||||
|
@ -6330,7 +6330,14 @@
|
|||||||
"kind": "boolean",
|
"kind": "boolean",
|
||||||
"description": "Reports whether a decoder for an HTTP Live Streaming media type was created when requested.",
|
"description": "Reports whether a decoder for an HTTP Live Streaming media type was created when requested.",
|
||||||
"bug_numbers": [1262659]
|
"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": {
|
"VIDEO_MFT_OUTPUT_NULL_SAMPLES": {
|
||||||
"alert_emails": ["cpearce@mozilla.com"],
|
"alert_emails": ["cpearce@mozilla.com"],
|
||||||
|
@ -17,6 +17,7 @@ struct ScalarInfo {
|
|||||||
uint32_t name_offset;
|
uint32_t name_offset;
|
||||||
uint32_t expiration_offset;
|
uint32_t expiration_offset;
|
||||||
uint32_t dataset;
|
uint32_t dataset;
|
||||||
|
bool keyed;
|
||||||
|
|
||||||
const char *name() const;
|
const char *name() const;
|
||||||
const char *expiration() const;
|
const char *expiration() const;
|
||||||
|
@ -71,6 +71,58 @@ telemetry.test:
|
|||||||
- telemetry-client-dev@mozilla.com
|
- telemetry-client-dev@mozilla.com
|
||||||
release_channel_collection: opt-out
|
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.
|
# The following section contains the browser engagement scalars.
|
||||||
browser.engagement:
|
browser.engagement:
|
||||||
max_concurrent_tab_count:
|
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);
|
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
|
NS_IMETHODIMP
|
||||||
TelemetryImpl::ClearScalars()
|
TelemetryImpl::ClearScalars()
|
||||||
{
|
{
|
||||||
@ -2967,53 +2996,59 @@ void DestroyStatisticsRecorder()
|
|||||||
|
|
||||||
// Scalar API C++ Endpoints
|
// 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
|
void
|
||||||
ScalarAdd(mozilla::Telemetry::ScalarID aId, uint32_t aVal)
|
ScalarAdd(mozilla::Telemetry::ScalarID aId, uint32_t aVal)
|
||||||
{
|
{
|
||||||
TelemetryScalar::Add(aId, 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
|
void
|
||||||
ScalarSet(mozilla::Telemetry::ScalarID aId, uint32_t aVal)
|
ScalarSet(mozilla::Telemetry::ScalarID aId, uint32_t aVal)
|
||||||
{
|
{
|
||||||
TelemetryScalar::Set(aId, aVal);
|
TelemetryScalar::Set(aId, aVal);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
void
|
||||||
* Sets the scalar to the given value.
|
ScalarSet(mozilla::Telemetry::ScalarID aId, bool aVal)
|
||||||
*
|
{
|
||||||
* @param aId The scalar enum id.
|
TelemetryScalar::Set(aId, aVal);
|
||||||
* @param aValue The string value to set the scalar to.
|
}
|
||||||
*/
|
|
||||||
void
|
void
|
||||||
ScalarSet(mozilla::Telemetry::ScalarID aId, const nsAString& aVal)
|
ScalarSet(mozilla::Telemetry::ScalarID aId, const nsAString& aVal)
|
||||||
{
|
{
|
||||||
TelemetryScalar::Set(aId, 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
|
void
|
||||||
ScalarSetMaximum(mozilla::Telemetry::ScalarID aId, uint32_t aVal)
|
ScalarSetMaximum(mozilla::Telemetry::ScalarID aId, uint32_t aVal)
|
||||||
{
|
{
|
||||||
TelemetryScalar::SetMaximum(aId, 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 Telemetry
|
||||||
} // namespace mozilla
|
} // namespace mozilla
|
||||||
|
@ -363,7 +363,7 @@ void WriteFailedProfileLock(nsIFile* aProfileDir);
|
|||||||
* Adds the value to the given scalar.
|
* Adds the value to the given scalar.
|
||||||
*
|
*
|
||||||
* @param aId The scalar enum id.
|
* @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);
|
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.
|
* Sets the scalar to the given value.
|
||||||
*
|
*
|
||||||
* @param aId The scalar enum id.
|
* @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);
|
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.
|
* Sets the scalar to the given value.
|
||||||
*
|
*
|
||||||
* @param aId The scalar enum id.
|
* @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.
|
* 50 characters if exceeding that length.
|
||||||
*/
|
*/
|
||||||
void ScalarSet(mozilla::Telemetry::ScalarID aId, const nsAString& aValue);
|
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.
|
* Sets the scalar to the maximum of the current and the passed value.
|
||||||
*
|
*
|
||||||
* @param aId The scalar enum id.
|
* @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.
|
* than the current value.
|
||||||
*/
|
*/
|
||||||
void ScalarSetMaximum(mozilla::Telemetry::ScalarID aId, uint32_t aValue);
|
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 Telemetry
|
||||||
} // namespace mozilla
|
} // namespace mozilla
|
||||||
|
|
||||||
|
@ -70,6 +70,8 @@ using mozilla::Telemetry::Common::IsInDataset;
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
const uint32_t kMaximumNumberOfKeys = 100;
|
||||||
|
const uint32_t kMaximumKeyStringLength = 70;
|
||||||
const uint32_t kMaximumStringValueLength = 50;
|
const uint32_t kMaximumStringValueLength = 50;
|
||||||
const uint32_t kScalarCount =
|
const uint32_t kScalarCount =
|
||||||
static_cast<uint32_t>(mozilla::Telemetry::ScalarID::ScalarCount);
|
static_cast<uint32_t>(mozilla::Telemetry::ScalarID::ScalarCount);
|
||||||
@ -81,6 +83,9 @@ enum class ScalarResult : uint8_t {
|
|||||||
OperationNotSupported,
|
OperationNotSupported,
|
||||||
InvalidType,
|
InvalidType,
|
||||||
InvalidValue,
|
InvalidValue,
|
||||||
|
// Keyed Scalar Errors
|
||||||
|
KeyTooLong,
|
||||||
|
TooManyKeys,
|
||||||
// String Scalar Errors
|
// String Scalar Errors
|
||||||
StringTooLong,
|
StringTooLong,
|
||||||
// Unsigned Scalar Errors
|
// Unsigned Scalar Errors
|
||||||
@ -111,7 +116,10 @@ MapToNsResult(ScalarResult aSr)
|
|||||||
return NS_OK;
|
return NS_OK;
|
||||||
case ScalarResult::InvalidType:
|
case ScalarResult::InvalidType:
|
||||||
case ScalarResult::InvalidValue:
|
case ScalarResult::InvalidValue:
|
||||||
|
case ScalarResult::KeyTooLong:
|
||||||
return NS_ERROR_ILLEGAL_VALUE;
|
return NS_ERROR_ILLEGAL_VALUE;
|
||||||
|
case ScalarResult::TooManyKeys:
|
||||||
|
return NS_ERROR_FAILURE;
|
||||||
case ScalarResult::UnsignedNegativeValue:
|
case ScalarResult::UnsignedNegativeValue:
|
||||||
case ScalarResult::UnsignedTruncatedValue:
|
case ScalarResult::UnsignedTruncatedValue:
|
||||||
// We shouldn't throw if trying to set a negative number or are truncated,
|
// 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);
|
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 nsUint32HashKey ScalarIDHashKey;
|
||||||
typedef nsClassHashtable<ScalarIDHashKey, ScalarBase> ScalarStorageMapType;
|
typedef nsClassHashtable<ScalarIDHashKey, ScalarBase> ScalarStorageMapType;
|
||||||
|
typedef nsClassHashtable<ScalarIDHashKey, KeyedScalar> KeyedScalarStorageMapType;
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
@ -495,6 +731,9 @@ ScalarMapType gScalarNameIDMap(kScalarCount);
|
|||||||
// the scalar instance and takes care of deallocating them when they
|
// the scalar instance and takes care of deallocating them when they
|
||||||
// get removed from the map.
|
// get removed from the map.
|
||||||
ScalarStorageMapType gScalarStorageMap;
|
ScalarStorageMapType gScalarStorageMap;
|
||||||
|
// The ID -> Keyed Scalar Object map. As for plain scalars, this is
|
||||||
|
// nsClassHashtable. See above.
|
||||||
|
KeyedScalarStorageMapType gKeyedScalarStorageMap;
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
@ -551,12 +790,20 @@ internal_LogToBrowserConsole(uint32_t aLogLevel, const nsAString& aMsg)
|
|||||||
bool
|
bool
|
||||||
internal_ShouldLogError(ScalarResult aSr)
|
internal_ShouldLogError(ScalarResult aSr)
|
||||||
{
|
{
|
||||||
if (aSr == ScalarResult::StringTooLong ||
|
switch (aSr) {
|
||||||
aSr == ScalarResult::UnsignedNegativeValue ||
|
case ScalarResult::StringTooLong: MOZ_FALLTHROUGH;
|
||||||
aSr == ScalarResult::UnsignedTruncatedValue) {
|
case ScalarResult::KeyTooLong: MOZ_FALLTHROUGH;
|
||||||
|
case ScalarResult::TooManyKeys: MOZ_FALLTHROUGH;
|
||||||
|
case ScalarResult::UnsignedNegativeValue: MOZ_FALLTHROUGH;
|
||||||
|
case ScalarResult::UnsignedTruncatedValue:
|
||||||
|
// Intentional fall-through.
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// It should never reach this point.
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -577,6 +824,12 @@ internal_LogScalarError(const nsACString& aScalarName, ScalarResult aSr)
|
|||||||
case ScalarResult::StringTooLong:
|
case ScalarResult::StringTooLong:
|
||||||
errorMessage.Append(NS_LITERAL_STRING(" - Truncating scalar value to 50 characters."));
|
errorMessage.Append(NS_LITERAL_STRING(" - Truncating scalar value to 50 characters."));
|
||||||
break;
|
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:
|
case ScalarResult::UnsignedNegativeValue:
|
||||||
errorMessage.Append(NS_LITERAL_STRING(" - Trying to set an unsigned scalar to a negative number."));
|
errorMessage.Append(NS_LITERAL_STRING(" - Trying to set an unsigned scalar to a negative number."));
|
||||||
break;
|
break;
|
||||||
@ -618,6 +871,18 @@ internal_InfoForScalarID(mozilla::Telemetry::ScalarID aId)
|
|||||||
return gScalars[static_cast<uint32_t>(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
|
bool
|
||||||
internal_CanRecordForScalarID(mozilla::Telemetry::ScalarID aId)
|
internal_CanRecordForScalarID(mozilla::Telemetry::ScalarID aId)
|
||||||
{
|
{
|
||||||
@ -640,33 +905,6 @@ internal_CanRecordForScalarID(mozilla::Telemetry::ScalarID aId)
|
|||||||
return true;
|
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.
|
* Get the scalar enum id from the scalar name.
|
||||||
*
|
*
|
||||||
@ -708,6 +946,7 @@ nsresult
|
|||||||
internal_GetScalarByEnum(mozilla::Telemetry::ScalarID aId, ScalarBase** aRet)
|
internal_GetScalarByEnum(mozilla::Telemetry::ScalarID aId, ScalarBase** aRet)
|
||||||
{
|
{
|
||||||
if (!IsValidEnumId(aId)) {
|
if (!IsValidEnumId(aId)) {
|
||||||
|
MOZ_ASSERT(false, "Requested a scalar with an invalid id.");
|
||||||
return NS_ERROR_INVALID_ARG;
|
return NS_ERROR_INVALID_ARG;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -725,7 +964,7 @@ internal_GetScalarByEnum(mozilla::Telemetry::ScalarID aId, ScalarBase** aRet)
|
|||||||
return NS_ERROR_NOT_AVAILABLE;
|
return NS_ERROR_NOT_AVAILABLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
scalar = internal_ScalarAllocate(info);
|
scalar = internal_ScalarAllocate(info.kind);
|
||||||
if (!scalar) {
|
if (!scalar) {
|
||||||
return NS_ERROR_INVALID_ARG;
|
return NS_ERROR_INVALID_ARG;
|
||||||
}
|
}
|
||||||
@ -753,6 +992,102 @@ internal_GetRecordableScalar(mozilla::Telemetry::ScalarID aId)
|
|||||||
return nullptr;
|
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?
|
// Are we allowed to record this scalar?
|
||||||
if (!internal_CanRecordForScalarID(aId)) {
|
if (!internal_CanRecordForScalarID(aId)) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
@ -812,6 +1147,7 @@ TelemetryScalar::DeInitializeGlobalState()
|
|||||||
gCanRecordExtended = false;
|
gCanRecordExtended = false;
|
||||||
gScalarNameIDMap.Clear();
|
gScalarNameIDMap.Clear();
|
||||||
gScalarStorageMap.Clear();
|
gScalarStorageMap.Clear();
|
||||||
|
gKeyedScalarStorageMap.Clear();
|
||||||
gInitDone = false;
|
gInitDone = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -834,7 +1170,7 @@ TelemetryScalar::SetCanRecordExtended(bool b) {
|
|||||||
* @param aName The scalar name.
|
* @param aName The scalar name.
|
||||||
* @param aVal The numeric value to add to the scalar.
|
* @param aVal The numeric value to add to the scalar.
|
||||||
* @param aCx The JS context.
|
* @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.
|
* dataset. Otherwise, return an error.
|
||||||
*/
|
*/
|
||||||
nsresult
|
nsresult
|
||||||
@ -858,6 +1194,11 @@ TelemetryScalar::Add(const nsACString& aName, JS::HandleValue aVal, JSContext* a
|
|||||||
return rv;
|
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?
|
// Are we allowed to record this scalar?
|
||||||
if (!internal_CanRecordForScalarID(id)) {
|
if (!internal_CanRecordForScalarID(id)) {
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
@ -885,6 +1226,70 @@ TelemetryScalar::Add(const nsACString& aName, JS::HandleValue aVal, JSContext* a
|
|||||||
return MapToNsResult(sr);
|
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.
|
* Adds the value to the given scalar.
|
||||||
*
|
*
|
||||||
@ -904,6 +1309,27 @@ TelemetryScalar::Add(mozilla::Telemetry::ScalarID aId, uint32_t aValue)
|
|||||||
scalar->AddValue(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.
|
* Sets the scalar to the given value.
|
||||||
*
|
*
|
||||||
@ -934,6 +1360,11 @@ TelemetryScalar::Set(const nsACString& aName, JS::HandleValue aVal, JSContext* a
|
|||||||
return rv;
|
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?
|
// Are we allowed to record this scalar?
|
||||||
if (!internal_CanRecordForScalarID(id)) {
|
if (!internal_CanRecordForScalarID(id)) {
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
@ -961,6 +1392,70 @@ TelemetryScalar::Set(const nsACString& aName, JS::HandleValue aVal, JSContext* a
|
|||||||
return MapToNsResult(sr);
|
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.
|
* Sets the scalar to the given numeric value.
|
||||||
*
|
*
|
||||||
@ -1018,6 +1513,48 @@ TelemetryScalar::Set(mozilla::Telemetry::ScalarID aId, bool aValue)
|
|||||||
scalar->SetValue(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.
|
* 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;
|
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?
|
// Are we allowed to record this scalar?
|
||||||
if (!internal_CanRecordForScalarID(id)) {
|
if (!internal_CanRecordForScalarID(id)) {
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
@ -1075,6 +1617,70 @@ TelemetryScalar::SetMaximum(const nsACString& aName, JS::HandleValue aVal, JSCon
|
|||||||
return MapToNsResult(sr);
|
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.
|
* 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);
|
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.
|
* 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,...}.
|
* 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;
|
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.
|
* Resets all the stored scalars. This is intended to be only used in tests.
|
||||||
*/
|
*/
|
||||||
@ -1177,6 +1897,7 @@ TelemetryScalar::ClearScalars()
|
|||||||
{
|
{
|
||||||
StaticMutexAutoLock locker(gTelemetryScalarsMutex);
|
StaticMutexAutoLock locker(gTelemetryScalarsMutex);
|
||||||
gScalarStorageMap.Clear();
|
gScalarStorageMap.Clear();
|
||||||
|
gKeyedScalarStorageMap.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t
|
size_t
|
||||||
@ -1191,9 +1912,15 @@ TelemetryScalar::GetScalarSizesOfIncludingThis(mozilla::MallocSizeOf aMallocSize
|
|||||||
{
|
{
|
||||||
StaticMutexAutoLock locker(gTelemetryScalarsMutex);
|
StaticMutexAutoLock locker(gTelemetryScalarsMutex);
|
||||||
size_t n = 0;
|
size_t n = 0;
|
||||||
|
// For the plain scalars...
|
||||||
for (auto iter = gScalarStorageMap.Iter(); !iter.Done(); iter.Next()) {
|
for (auto iter = gScalarStorageMap.Iter(); !iter.Done(); iter.Next()) {
|
||||||
ScalarBase* scalar = static_cast<ScalarBase*>(iter.Data());
|
ScalarBase* scalar = static_cast<ScalarBase*>(iter.Data());
|
||||||
n += scalar->SizeOfIncludingThis(aMallocSizeOf);
|
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;
|
return n;
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,17 @@ nsresult CreateSnapshots(unsigned int aDataset, bool aClearScalars,
|
|||||||
JSContext* aCx, uint8_t optional_argc,
|
JSContext* aCx, uint8_t optional_argc,
|
||||||
JS::MutableHandle<JS::Value> aResult);
|
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.
|
// C++ API Endpoints.
|
||||||
void Add(mozilla::Telemetry::ScalarID aId, uint32_t aValue);
|
void Add(mozilla::Telemetry::ScalarID aId, uint32_t aValue);
|
||||||
void Set(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 Set(mozilla::Telemetry::ScalarID aId, bool aValue);
|
||||||
void SetMaximum(mozilla::Telemetry::ScalarID aId, uint32_t 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.
|
// Only to be used for testing.
|
||||||
void ClearScalars();
|
void ClearScalars();
|
||||||
|
|
||||||
|
@ -971,8 +971,16 @@ var Impl = {
|
|||||||
return ret;
|
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) {
|
if (!subsession) {
|
||||||
// We only support scalars for subsessions.
|
// We only support scalars for subsessions.
|
||||||
@ -980,7 +988,8 @@ var Impl = {
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
let scalarsSnapshot =
|
let scalarsSnapshot = keyed ?
|
||||||
|
Telemetry.snapshotKeyedScalars(this.getDatasetType(), clearSubsession) :
|
||||||
Telemetry.snapshotScalars(this.getDatasetType(), clearSubsession);
|
Telemetry.snapshotScalars(this.getDatasetType(), clearSubsession);
|
||||||
|
|
||||||
// Don't return the test scalars.
|
// Don't return the test scalars.
|
||||||
@ -1285,6 +1294,7 @@ var Impl = {
|
|||||||
payloadObj.processes = {
|
payloadObj.processes = {
|
||||||
parent: {
|
parent: {
|
||||||
scalars: protect(() => this.getScalars(isSubsession, clearSubsession)),
|
scalars: protect(() => this.getScalars(isSubsession, clearSubsession)),
|
||||||
|
keyedScalars: protect(() => this.getScalars(isSubsession, clearSubsession, true)),
|
||||||
},
|
},
|
||||||
content: {
|
content: {
|
||||||
histograms: histograms[HISTOGRAM_SUFFIXES.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.scalarSet(aName, aValue);
|
||||||
Services.telemetry.scalarSetMaximum(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
|
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
|
(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 informations.
|
additional information.
|
||||||
|
|
||||||
C++ API
|
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 ScalarSet(mozilla::Telemetry::ScalarID aId, bool aValue);
|
||||||
void ScalarSetMaximum(mozilla::Telemetry::ScalarID aId, uint32_t 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
|
The YAML definition file
|
||||||
========================
|
========================
|
||||||
Scalar probes are required to be registered, both for validation and transparency reasons,
|
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``.
|
- ``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.
|
- ``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
|
String type restrictions
|
||||||
------------------------
|
------------------------
|
||||||
To prevent abuses, the content of a string scalar is limited to 50 characters in length. Trying
|
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.
|
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 processor scripts
|
||||||
=====================
|
=====================
|
||||||
The scalar definition file is processed and checked for correctness at compile time. If it
|
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:
|
if cpp_guard:
|
||||||
print("#if defined(%s)" % cpp_guard, file=output)
|
print("#if defined(%s)" % cpp_guard, file=output)
|
||||||
|
|
||||||
print(" {{ {}, {}, {}, {} }},"\
|
print(" {{ {}, {}, {}, {}, {} }},"\
|
||||||
.format(scalar.nsITelemetry_kind,
|
.format(scalar.nsITelemetry_kind,
|
||||||
name_index,
|
name_index,
|
||||||
expiration_index,
|
expiration_index,
|
||||||
scalar.dataset),
|
scalar.dataset,
|
||||||
|
"true" if scalar.keyed else "false"),
|
||||||
file=output)
|
file=output)
|
||||||
|
|
||||||
if cpp_guard:
|
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