Merge fx-team to central, a=merge
@ -12,6 +12,7 @@ let TrackingProtection = {
|
||||
let $ = selector => document.querySelector(selector);
|
||||
this.container = $("#tracking-protection-container");
|
||||
this.content = $("#tracking-protection-content");
|
||||
this.icon = $("#tracking-protection-icon");
|
||||
|
||||
this.updateEnabled();
|
||||
Services.prefs.addObserver(this.PREF_ENABLED_GLOBALLY, this, false);
|
||||
@ -60,15 +61,14 @@ let TrackingProtection = {
|
||||
STATE_BLOCKED_TRACKING_CONTENT, STATE_LOADED_TRACKING_CONTENT
|
||||
} = Ci.nsIWebProgressListener;
|
||||
|
||||
if (state & STATE_BLOCKED_TRACKING_CONTENT) {
|
||||
this.content.setAttribute("block-active", true);
|
||||
this.content.removeAttribute("block-disabled");
|
||||
} else if (state & STATE_LOADED_TRACKING_CONTENT) {
|
||||
this.content.setAttribute("block-disabled", true);
|
||||
this.content.removeAttribute("block-active");
|
||||
} else {
|
||||
this.content.removeAttribute("block-disabled");
|
||||
this.content.removeAttribute("block-active");
|
||||
for (let element of [this.icon, this.content]) {
|
||||
if (state & STATE_BLOCKED_TRACKING_CONTENT) {
|
||||
element.setAttribute("state", "blocked-tracking-content");
|
||||
} else if (state & STATE_LOADED_TRACKING_CONTENT) {
|
||||
element.setAttribute("state", "loaded-tracking-content");
|
||||
} else {
|
||||
element.removeAttribute("state");
|
||||
}
|
||||
}
|
||||
|
||||
// Telemetry for state change.
|
||||
|
@ -6642,6 +6642,11 @@ var gIdentityHandler = {
|
||||
return this._identityPopupSecurityView =
|
||||
document.getElementById("identity-popup-securityView");
|
||||
},
|
||||
get _identityPopupMainView () {
|
||||
delete this._identityPopupMainView;
|
||||
return this._identityPopupMainView =
|
||||
document.getElementById("identity-popup-mainView");
|
||||
},
|
||||
get _identityIconLabel () {
|
||||
delete this._identityIconLabel;
|
||||
return this._identityIconLabel = document.getElementById("identity-icon-label");
|
||||
@ -6987,6 +6992,7 @@ var gIdentityHandler = {
|
||||
setPopupMessages : function(newMode) {
|
||||
|
||||
this._identityPopup.className = newMode;
|
||||
this._identityPopupMainView.className = newMode;
|
||||
this._identityPopupSecurityView.className = newMode;
|
||||
this._identityPopupSecurityContent.className = newMode;
|
||||
|
||||
|
@ -762,6 +762,7 @@
|
||||
onclick="gIdentityHandler.handleIdentityButtonEvent(event);"
|
||||
onkeypress="gIdentityHandler.handleIdentityButtonEvent(event);"
|
||||
ondragstart="gIdentityHandler.onDragStart(event);">
|
||||
<image id="tracking-protection-icon"/>
|
||||
<image id="page-proxy-favicon"
|
||||
consumeanchor="identity-box"
|
||||
onclick="PageProxyClickHandler(event);"
|
||||
|
@ -45,9 +45,10 @@ function clickButton(sel) {
|
||||
function testBenignPage() {
|
||||
info("Non-tracking content must not be blocked");
|
||||
ok (!TrackingProtection.container.hidden, "The container is visible");
|
||||
ok (!TrackingProtection.content.hasAttribute("block-disabled"), "blocking not disabled");
|
||||
ok (!TrackingProtection.content.hasAttribute("block-active"), "blocking is not active");
|
||||
ok (!TrackingProtection.content.hasAttribute("state"), "content: no state");
|
||||
ok (!TrackingProtection.icon.hasAttribute("state"), "icon: no state");
|
||||
|
||||
ok (hidden("#tracking-protection-icon"), "icon is hidden");
|
||||
ok (hidden("#tracking-action-block"), "blockButton is hidden");
|
||||
ok (hidden("#tracking-action-unblock"), "unblockButton is hidden");
|
||||
|
||||
@ -60,9 +61,12 @@ function testBenignPage() {
|
||||
function testTrackingPage() {
|
||||
info("Tracking content must be blocked");
|
||||
ok (!TrackingProtection.container.hidden, "The container is visible");
|
||||
ok (!TrackingProtection.content.hasAttribute("block-disabled"), "blocking not disabled");
|
||||
ok (TrackingProtection.content.hasAttribute("block-active"), "blocking is active");
|
||||
is (TrackingProtection.content.getAttribute("state"), "blocked-tracking-content",
|
||||
'content: state="blocked-tracking-content"');
|
||||
is (TrackingProtection.icon.getAttribute("state"), "blocked-tracking-content",
|
||||
'icon: state="blocked-tracking-content"');
|
||||
|
||||
ok (!hidden("#tracking-protection-icon"), "icon is visible");
|
||||
ok (hidden("#tracking-action-block"), "blockButton is hidden");
|
||||
ok (!hidden("#tracking-action-unblock"), "unblockButton is visible");
|
||||
|
||||
@ -72,12 +76,15 @@ function testTrackingPage() {
|
||||
ok (!hidden("#tracking-blocked"), "labelTrackingBlocked is visible");
|
||||
}
|
||||
|
||||
function testTrackingPageWhitelisted() {
|
||||
function testTrackingPageUnblocked() {
|
||||
info("Tracking content must be white-listed and not blocked");
|
||||
ok (!TrackingProtection.container.hidden, "The container is visible");
|
||||
ok (TrackingProtection.content.hasAttribute("block-disabled"), "blocking is disabled");
|
||||
ok (!TrackingProtection.content.hasAttribute("block-active"), "blocking is not active");
|
||||
is (TrackingProtection.content.getAttribute("state"), "loaded-tracking-content",
|
||||
'content: state="loaded-tracking-content"');
|
||||
is (TrackingProtection.icon.getAttribute("state"), "loaded-tracking-content",
|
||||
'icon: state="loaded-tracking-content"');
|
||||
|
||||
ok (!hidden("#tracking-protection-icon"), "icon is visible");
|
||||
ok (!hidden("#tracking-action-block"), "blockButton is visible");
|
||||
ok (hidden("#tracking-action-unblock"), "unblockButton is hidden");
|
||||
|
||||
@ -97,17 +104,15 @@ function* testTrackingProtectionForTab(tab) {
|
||||
testTrackingPage();
|
||||
|
||||
info("Disable TP for the page (which reloads the page)");
|
||||
let tabReloadPromise = promiseTabLoadEvent(tab);
|
||||
clickButton("#tracking-action-unblock");
|
||||
|
||||
info("Wait for tab to reload following TP white-listing");
|
||||
yield promiseTabLoadEvent(tab);
|
||||
testTrackingPageWhitelisted();
|
||||
yield tabReloadPromise;
|
||||
testTrackingPageUnblocked();
|
||||
|
||||
info("Re-enable TP for the page (which reloads the page)");
|
||||
tabReloadPromise = promiseTabLoadEvent(tab);
|
||||
clickButton("#tracking-action-block");
|
||||
|
||||
info("Wait for tab to reload following TP black-listing");
|
||||
yield promiseTabLoadEvent(tab);
|
||||
yield tabReloadPromise;
|
||||
testTrackingPage();
|
||||
}
|
||||
|
||||
|
@ -1466,7 +1466,7 @@ html[dir="rtl"] .standalone .room-conversation-wrapper .room-inner-info-area {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin-bottom: .5em;
|
||||
text-align: end;
|
||||
text-align: start;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: flex-start;
|
||||
align-content: stretch;
|
||||
|
@ -343,13 +343,13 @@
|
||||
</hbox>
|
||||
</vbox>
|
||||
<spacer flex="1"/>
|
||||
<vbox id="tosPP-small">
|
||||
<hbox id="tosPP-small">
|
||||
<label id="tosPP-small-ToS" class="text-link">
|
||||
&prefs.tosLink.label;
|
||||
</label>
|
||||
<label id="tosPP-small-PP" class="text-link">
|
||||
&fxaPrivacyNotice.link.label;
|
||||
</label>
|
||||
</vbox>
|
||||
</hbox>
|
||||
</vbox>
|
||||
</deck>
|
||||
|
@ -40,7 +40,7 @@
|
||||
<script type="text/javascript" src="debugger/stack-frames-view.js"/>
|
||||
<script type="text/javascript" src="debugger/stack-frames-classic-view.js"/>
|
||||
<script type="text/javascript" src="debugger/filter-view.js"/>
|
||||
|
||||
|
||||
<commandset id="editMenuCommands"/>
|
||||
|
||||
<commandset id="debuggerCommands"></commandset>
|
||||
@ -128,16 +128,6 @@
|
||||
label="&debuggerUI.autoPrettyPrint;"
|
||||
accesskey="&debuggerUI.autoPrettyPrint.accesskey;"
|
||||
command="toggleAutoPrettyPrint"/>
|
||||
<menuitem id="pause-on-exceptions"
|
||||
type="checkbox"
|
||||
label="&debuggerUI.pauseExceptions;"
|
||||
accesskey="&debuggerUI.pauseExceptions.accesskey;"
|
||||
command="togglePauseOnExceptions"/>
|
||||
<menuitem id="ignore-caught-exceptions"
|
||||
type="checkbox"
|
||||
label="&debuggerUI.ignoreCaughtExceptions;"
|
||||
accesskey="&debuggerUI.ignoreCaughtExceptions.accesskey;"
|
||||
command="toggleIgnoreCaughtExceptions"/>
|
||||
<menuitem id="show-panes-on-startup"
|
||||
type="checkbox"
|
||||
label="&debuggerUI.showPanesOnInit;"
|
||||
@ -357,6 +347,9 @@
|
||||
class="devtools-toolbarbutton"
|
||||
tooltiptext="&debuggerUI.sources.toggleBreakpoints;"
|
||||
command="toggleBreakpointsCommand"/>
|
||||
<toolbarbutton id="toggle-pause-exceptions"
|
||||
class="devtools-toolbarbutton"
|
||||
command="togglePauseOnExceptionsCommand"/>
|
||||
</toolbar>
|
||||
</tabpanel>
|
||||
<tabpanel id="callstack-tabpanel">
|
||||
|
@ -8,7 +8,7 @@
|
||||
const TAB_URL = EXAMPLE_URL + "doc_pause-exceptions.html";
|
||||
|
||||
let gTab, gPanel, gDebugger;
|
||||
let gFrames, gVariables, gPrefs, gOptions;
|
||||
let gFrames, gVariables, gPrefs;
|
||||
|
||||
function test() {
|
||||
requestLongerTimeout(2);
|
||||
@ -19,19 +19,16 @@ function test() {
|
||||
gFrames = gDebugger.DebuggerView.StackFrames;
|
||||
gVariables = gDebugger.DebuggerView.Variables;
|
||||
gPrefs = gDebugger.Prefs;
|
||||
gOptions = gDebugger.DebuggerView.Options;
|
||||
|
||||
is(gPrefs.pauseOnExceptions, false,
|
||||
"The pause-on-exceptions pref should be disabled by default.");
|
||||
isnot(gOptions._pauseOnExceptionsItem.getAttribute("checked"), "true",
|
||||
"The pause-on-exceptions menu item should not be checked.");
|
||||
|
||||
testPauseOnExceptionsDisabled()
|
||||
.then(enablePauseOnExceptions)
|
||||
.then(disableIgnoreCaughtExceptions)
|
||||
.then(testPauseOnExceptionsEnabled)
|
||||
.then(disablePauseOnExceptions)
|
||||
.then(enableIgnoreCaughtExceptions)
|
||||
.then(clickToPauseOnAllExceptions)
|
||||
.then(testPauseOnAllExceptionsEnabled)
|
||||
.then(clickToPauseOnUncaughtExceptions)
|
||||
.then(testPauseOnUncaughtExceptionsEnabled)
|
||||
.then(clickToStopPauseOnExceptions)
|
||||
.then(() => closeDebuggerAndFinish(gPanel))
|
||||
.then(null, aError => {
|
||||
ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
|
||||
@ -82,9 +79,52 @@ function testPauseOnExceptionsDisabled() {
|
||||
return finished;
|
||||
}
|
||||
|
||||
function testPauseOnExceptionsEnabled() {
|
||||
function testPauseOnUncaughtExceptionsEnabled() {
|
||||
let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES).then(() => {
|
||||
info("Testing enabled pause-on-uncaught-exceptions only.");
|
||||
|
||||
is(gDebugger.gThreadClient.state, "paused",
|
||||
"Should only be getting stack frames while paused (1).");
|
||||
ok(isCaretPos(gPanel, 26),
|
||||
"Should be paused on the debugger statement (1).");
|
||||
|
||||
let innerScope = gVariables.getScopeAtIndex(0);
|
||||
let innerNodes = innerScope.target.querySelector(".variables-view-element-details").childNodes;
|
||||
|
||||
is(gFrames.itemCount, 1,
|
||||
"Should have one frame.");
|
||||
is(gVariables._store.length, 3,
|
||||
"Should have three scopes.");
|
||||
|
||||
is(innerNodes[0].querySelector(".name").getAttribute("value"), "this",
|
||||
"Should have the right property name for 'this'.");
|
||||
is(innerNodes[0].querySelector(".value").getAttribute("value"), "<button>",
|
||||
"Should have the right property value for 'this'.");
|
||||
|
||||
let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.AFTER_FRAMES_CLEARED).then(() => {
|
||||
isnot(gDebugger.gThreadClient.state, "paused",
|
||||
"Should not be paused after resuming.");
|
||||
ok(isCaretPos(gPanel, 26),
|
||||
"Should be idle on the debugger statement.");
|
||||
|
||||
ok(true, "Frames were cleared, debugger didn't pause again.");
|
||||
});
|
||||
|
||||
EventUtils.sendMouseEvent({ type: "mousedown" },
|
||||
gDebugger.document.getElementById("resume"),
|
||||
gDebugger);
|
||||
|
||||
return finished;
|
||||
});
|
||||
|
||||
generateMouseClickInTab(gTab, "content.document.querySelector('button')");
|
||||
|
||||
return finished;
|
||||
}
|
||||
|
||||
function testPauseOnAllExceptionsEnabled() {
|
||||
let finished = waitForCaretAndScopes(gPanel, 19).then(() => {
|
||||
info("Testing enabled pause-on-exceptions.");
|
||||
info("Testing enabled pause-on-all-exceptions.");
|
||||
|
||||
is(gDebugger.gThreadClient.state, "paused",
|
||||
"Should only be getting stack frames while paused.");
|
||||
@ -105,7 +145,7 @@ function testPauseOnExceptionsEnabled() {
|
||||
"Should have the right property value for <exception>.");
|
||||
|
||||
let finished = waitForCaretAndScopes(gPanel, 26).then(() => {
|
||||
info("Testing enabled pause-on-exceptions and resumed after pause.");
|
||||
info("Testing enabled pause-on-all-exceptions and resumed after pause.");
|
||||
|
||||
is(gDebugger.gThreadClient.state, "paused",
|
||||
"Should only be getting stack frames while paused.");
|
||||
@ -153,82 +193,64 @@ function testPauseOnExceptionsEnabled() {
|
||||
return finished;
|
||||
}
|
||||
|
||||
function enablePauseOnExceptions() {
|
||||
let deferred = promise.defer();
|
||||
function clickToPauseOnAllExceptions() {
|
||||
var deferred = promise.defer();
|
||||
var pauseOnExceptionsButton = getPauseOnExceptionsButton();
|
||||
|
||||
gDebugger.gThreadClient.addOneTimeListener("resumed", () => {
|
||||
is(gPrefs.pauseOnExceptions, true,
|
||||
"The pause-on-exceptions pref should now be enabled.");
|
||||
is(gOptions._pauseOnExceptionsItem.getAttribute("checked"), "true",
|
||||
"The pause-on-exceptions menu item should now be checked.");
|
||||
is(pauseOnExceptionsButton.getAttribute("tooltiptext"),
|
||||
"Pause on uncaught exceptions",
|
||||
"The button's tooltip text should be 'Pause on uncaught exceptions'.");
|
||||
is(pauseOnExceptionsButton.getAttribute("state"), 1,
|
||||
"The pause on exceptions button state variable should be 1");
|
||||
|
||||
ok(true, "Pausing on exceptions was enabled.");
|
||||
deferred.resolve();
|
||||
deferred.resolve();
|
||||
});
|
||||
|
||||
gOptions._pauseOnExceptionsItem.setAttribute("checked", "true");
|
||||
gOptions._togglePauseOnExceptions();
|
||||
pauseOnExceptionsButton.click();
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function disablePauseOnExceptions() {
|
||||
let deferred = promise.defer();
|
||||
function clickToPauseOnUncaughtExceptions() {
|
||||
var deferred = promise.defer();
|
||||
var pauseOnExceptionsButton = getPauseOnExceptionsButton();
|
||||
|
||||
gDebugger.gThreadClient.addOneTimeListener("resumed", () => {
|
||||
is(gPrefs.pauseOnExceptions, false,
|
||||
"The pause-on-exceptions pref should now be disabled.");
|
||||
isnot(gOptions._pauseOnExceptionsItem.getAttribute("checked"), "true",
|
||||
"The pause-on-exceptions menu item should now be unchecked.");
|
||||
gDebugger.gThreadClient.addOneTimeListener("resumed", () =>{
|
||||
is(pauseOnExceptionsButton.getAttribute("tooltiptext"),
|
||||
"Do not pause on exceptions",
|
||||
"The button's tooltip text should be 'Do not pause on exceptions'.");
|
||||
is(pauseOnExceptionsButton.getAttribute("state"), 2,
|
||||
"The pause on exceptions button state variable should be 2");
|
||||
|
||||
ok(true, "Pausing on exceptions was disabled.");
|
||||
deferred.resolve();
|
||||
deferred.resolve();
|
||||
});
|
||||
|
||||
gOptions._pauseOnExceptionsItem.setAttribute("checked", "false");
|
||||
gOptions._togglePauseOnExceptions();
|
||||
|
||||
pauseOnExceptionsButton.click();
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function enableIgnoreCaughtExceptions() {
|
||||
let deferred = promise.defer();
|
||||
function clickToStopPauseOnExceptions() {
|
||||
var deferred = promise.defer();
|
||||
var pauseOnExceptionsButton = getPauseOnExceptionsButton();
|
||||
|
||||
gDebugger.gThreadClient.addOneTimeListener("resumed", () => {
|
||||
is(gPrefs.ignoreCaughtExceptions, true,
|
||||
"The ignore-caught-exceptions pref should now be enabled.");
|
||||
is(gOptions._ignoreCaughtExceptionsItem.getAttribute("checked"), "true",
|
||||
"The ignore-caught-exceptions menu item should now be checked.");
|
||||
gDebugger.gThreadClient.addOneTimeListener("resumed", () =>{
|
||||
is(pauseOnExceptionsButton.getAttribute("tooltiptext"),
|
||||
"Pause on all exceptions",
|
||||
"The button's tooltip text should be 'Pause on all exceptions'.");
|
||||
is(pauseOnExceptionsButton.getAttribute("state"), 0,
|
||||
"The pause on exceptions button state variable should be 0");
|
||||
|
||||
ok(true, "Ignore caught exceptions was enabled.");
|
||||
deferred.resolve();
|
||||
deferred.resolve();
|
||||
});
|
||||
|
||||
gOptions._ignoreCaughtExceptionsItem.setAttribute("checked", "true");
|
||||
gOptions._toggleIgnoreCaughtExceptions();
|
||||
|
||||
pauseOnExceptionsButton.click();
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function disableIgnoreCaughtExceptions() {
|
||||
let deferred = promise.defer();
|
||||
|
||||
gDebugger.gThreadClient.addOneTimeListener("resumed", () => {
|
||||
is(gPrefs.ignoreCaughtExceptions, false,
|
||||
"The ignore-caught-exceptions pref should now be disabled.");
|
||||
isnot(gOptions._ignoreCaughtExceptionsItem.getAttribute("checked"), "true",
|
||||
"The ignore-caught-exceptions menu item should now be unchecked.");
|
||||
|
||||
ok(true, "Ignore caught exceptions was disabled.");
|
||||
deferred.resolve();
|
||||
});
|
||||
|
||||
gOptions._ignoreCaughtExceptionsItem.setAttribute("checked", "false");
|
||||
gOptions._toggleIgnoreCaughtExceptions();
|
||||
|
||||
return deferred.promise;
|
||||
function getPauseOnExceptionsButton() {
|
||||
return gDebugger.document.getElementById("toggle-pause-exceptions");
|
||||
}
|
||||
|
||||
|
||||
registerCleanupFunction(function() {
|
||||
gTab = null;
|
||||
gPanel = null;
|
||||
@ -236,5 +258,4 @@ registerCleanupFunction(function() {
|
||||
gFrames = null;
|
||||
gVariables = null;
|
||||
gPrefs = null;
|
||||
gOptions = null;
|
||||
});
|
||||
|
@ -2,15 +2,16 @@
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Make sure that pausing on exceptions works after reload.
|
||||
* Make sure that pausing on exceptions works after tab reload.
|
||||
*/
|
||||
|
||||
const TAB_URL = EXAMPLE_URL + "doc_pause-exceptions.html";
|
||||
|
||||
let gTab, gPanel, gDebugger;
|
||||
let gFrames, gVariables, gPrefs, gOptions;
|
||||
let gFrames, gVariables, gPrefs;
|
||||
|
||||
function test() {
|
||||
requestLongerTimeout(2);
|
||||
initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
|
||||
gTab = aTab;
|
||||
gPanel = aPanel;
|
||||
@ -18,19 +19,15 @@ function test() {
|
||||
gFrames = gDebugger.DebuggerView.StackFrames;
|
||||
gVariables = gDebugger.DebuggerView.Variables;
|
||||
gPrefs = gDebugger.Prefs;
|
||||
gOptions = gDebugger.DebuggerView.Options;
|
||||
|
||||
is(gPrefs.pauseOnExceptions, false,
|
||||
"The pause-on-exceptions pref should be disabled by default.");
|
||||
isnot(gOptions._pauseOnExceptionsItem.getAttribute("checked"), "true",
|
||||
"The pause-on-exceptions menu item should not be checked.");
|
||||
|
||||
enablePauseOnExceptions()
|
||||
.then(disableIgnoreCaughtExceptions)
|
||||
clickToPauseOnAllExceptions()
|
||||
.then(() => reloadActiveTab(gPanel, gDebugger.EVENTS.SOURCE_SHOWN))
|
||||
.then(testPauseOnExceptionsAfterReload)
|
||||
.then(disablePauseOnExceptions)
|
||||
.then(enableIgnoreCaughtExceptions)
|
||||
.then(testPauseOnAllExceptionsEnabledAfterReload)
|
||||
.then(clickToPauseOnUncaughtExceptions)
|
||||
.then(clickToStopPauseOnExceptions)
|
||||
.then(() => closeDebuggerAndFinish(gPanel))
|
||||
.then(null, aError => {
|
||||
ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
|
||||
@ -38,9 +35,9 @@ function test() {
|
||||
});
|
||||
}
|
||||
|
||||
function testPauseOnExceptionsAfterReload() {
|
||||
function testPauseOnAllExceptionsEnabledAfterReload() {
|
||||
let finished = waitForCaretAndScopes(gPanel, 19).then(() => {
|
||||
info("Testing enabled pause-on-exceptions.");
|
||||
info("Testing enabled pause-on-all-exceptions.");
|
||||
|
||||
is(gDebugger.gThreadClient.state, "paused",
|
||||
"Should only be getting stack frames while paused.");
|
||||
@ -61,7 +58,7 @@ function testPauseOnExceptionsAfterReload() {
|
||||
"Should have the right property value for <exception>.");
|
||||
|
||||
let finished = waitForCaretAndScopes(gPanel, 26).then(() => {
|
||||
info("Testing enabled pause-on-exceptions and resumed after pause.");
|
||||
info("Testing enabled pause-on-all-exceptions and resumed after pause.");
|
||||
|
||||
is(gDebugger.gThreadClient.state, "paused",
|
||||
"Should only be getting stack frames while paused.");
|
||||
@ -109,80 +106,61 @@ function testPauseOnExceptionsAfterReload() {
|
||||
return finished;
|
||||
}
|
||||
|
||||
function enablePauseOnExceptions() {
|
||||
let deferred = promise.defer();
|
||||
function clickToPauseOnAllExceptions() {
|
||||
var deferred = promise.defer();
|
||||
var pauseOnExceptionsButton = getPauseOnExceptionsButton();
|
||||
|
||||
gDebugger.gThreadClient.addOneTimeListener("resumed", () => {
|
||||
is(gPrefs.pauseOnExceptions, true,
|
||||
"The pause-on-exceptions pref should now be enabled.");
|
||||
is(gOptions._pauseOnExceptionsItem.getAttribute("checked"), "true",
|
||||
"The pause-on-exceptions menu item should now be checked.");
|
||||
is(pauseOnExceptionsButton.getAttribute("tooltiptext"),
|
||||
"Pause on uncaught exceptions",
|
||||
"The button's tooltip text should be 'Pause on uncaught exceptions'.");
|
||||
is(pauseOnExceptionsButton.getAttribute("state"), 1,
|
||||
"The pause on exceptions button state variable should be 1");
|
||||
|
||||
ok(true, "Pausing on exceptions was enabled.");
|
||||
deferred.resolve();
|
||||
deferred.resolve();
|
||||
});
|
||||
|
||||
gOptions._pauseOnExceptionsItem.setAttribute("checked", "true");
|
||||
gOptions._togglePauseOnExceptions();
|
||||
pauseOnExceptionsButton.click();
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function disablePauseOnExceptions() {
|
||||
let deferred = promise.defer();
|
||||
function clickToPauseOnUncaughtExceptions() {
|
||||
var deferred = promise.defer();
|
||||
var pauseOnExceptionsButton = getPauseOnExceptionsButton();
|
||||
|
||||
gDebugger.gThreadClient.addOneTimeListener("resumed", () => {
|
||||
is(gPrefs.pauseOnExceptions, false,
|
||||
"The pause-on-exceptions pref should now be disabled.");
|
||||
isnot(gOptions._pauseOnExceptionsItem.getAttribute("checked"), "true",
|
||||
"The pause-on-exceptions menu item should now be unchecked.");
|
||||
gDebugger.gThreadClient.addOneTimeListener("resumed", () =>{
|
||||
is(pauseOnExceptionsButton.getAttribute("tooltiptext"),
|
||||
"Do not pause on exceptions",
|
||||
"The button's tooltip text should be 'Do not pause on exceptions'.");
|
||||
is(pauseOnExceptionsButton.getAttribute("state"), 2,
|
||||
"The pause on exceptions button state variable should be 2");
|
||||
|
||||
ok(true, "Pausing on exceptions was disabled.");
|
||||
deferred.resolve();
|
||||
deferred.resolve();
|
||||
});
|
||||
|
||||
gOptions._pauseOnExceptionsItem.setAttribute("checked", "false");
|
||||
gOptions._togglePauseOnExceptions();
|
||||
|
||||
pauseOnExceptionsButton.click();
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function enableIgnoreCaughtExceptions() {
|
||||
let deferred = promise.defer();
|
||||
function clickToStopPauseOnExceptions() {
|
||||
var deferred = promise.defer();
|
||||
var pauseOnExceptionsButton = getPauseOnExceptionsButton();
|
||||
|
||||
gDebugger.gThreadClient.addOneTimeListener("resumed", () => {
|
||||
is(gPrefs.ignoreCaughtExceptions, true,
|
||||
"The ignore-caught-exceptions pref should now be enabled.");
|
||||
is(gOptions._ignoreCaughtExceptionsItem.getAttribute("checked"), "true",
|
||||
"The ignore-caught-exceptions menu item should now be checked.");
|
||||
gDebugger.gThreadClient.addOneTimeListener("resumed", () =>{
|
||||
is(pauseOnExceptionsButton.getAttribute("tooltiptext"),
|
||||
"Pause on all exceptions",
|
||||
"The button's tooltip text should be 'Pause on all exceptions'.");
|
||||
is(pauseOnExceptionsButton.getAttribute("state"), 0,
|
||||
"The pause on exceptions button state variable should be 0");
|
||||
|
||||
ok(true, "Ignore caught exceptions was enabled.");
|
||||
deferred.resolve();
|
||||
deferred.resolve();
|
||||
});
|
||||
|
||||
gOptions._ignoreCaughtExceptionsItem.setAttribute("checked", "true");
|
||||
gOptions._toggleIgnoreCaughtExceptions();
|
||||
|
||||
pauseOnExceptionsButton.click();
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function disableIgnoreCaughtExceptions() {
|
||||
let deferred = promise.defer();
|
||||
|
||||
gDebugger.gThreadClient.addOneTimeListener("resumed", () => {
|
||||
is(gPrefs.ignoreCaughtExceptions, false,
|
||||
"The ignore-caught-exceptions pref should now be disabled.");
|
||||
isnot(gOptions._ignoreCaughtExceptionsItem.getAttribute("checked"), "true",
|
||||
"The ignore-caught-exceptions menu item should now be unchecked.");
|
||||
|
||||
ok(true, "Ignore caught exceptions was disabled.");
|
||||
deferred.resolve();
|
||||
});
|
||||
|
||||
gOptions._ignoreCaughtExceptionsItem.setAttribute("checked", "false");
|
||||
gOptions._toggleIgnoreCaughtExceptions();
|
||||
|
||||
return deferred.promise;
|
||||
function getPauseOnExceptionsButton() {
|
||||
return gDebugger.document.getElementById("toggle-pause-exceptions");
|
||||
}
|
||||
|
||||
registerCleanupFunction(function() {
|
||||
@ -192,5 +170,4 @@ registerCleanupFunction(function() {
|
||||
gFrames = null;
|
||||
gVariables = null;
|
||||
gPrefs = null;
|
||||
gOptions = null;
|
||||
});
|
||||
|
@ -13,7 +13,7 @@ const JS_URL = EXAMPLE_URL + "code_math_bogus_map.js";
|
||||
let { DevToolsUtils } = Cu.import("resource://gre/modules/devtools/DevToolsUtils.jsm", {});
|
||||
DevToolsUtils.reportingDisabled = true;
|
||||
|
||||
let gPanel, gDebugger, gFrames, gSources, gPrefs, gOptions;
|
||||
let gPanel, gDebugger, gFrames, gSources, gPrefs;
|
||||
|
||||
function test() {
|
||||
initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
|
||||
@ -22,22 +22,18 @@ function test() {
|
||||
gFrames = gDebugger.DebuggerView.StackFrames;
|
||||
gSources = gDebugger.DebuggerView.Sources;
|
||||
gPrefs = gDebugger.Prefs;
|
||||
gOptions = gDebugger.DebuggerView.Options;
|
||||
|
||||
is(gPrefs.pauseOnExceptions, false,
|
||||
"The pause-on-exceptions pref should be disabled by default.");
|
||||
isnot(gOptions._pauseOnExceptionsItem.getAttribute("checked"), "true",
|
||||
"The pause-on-exceptions menu item should not be checked.");
|
||||
|
||||
waitForSourceShown(gPanel, JS_URL)
|
||||
.then(checkInitialSource)
|
||||
.then(enablePauseOnExceptions)
|
||||
.then(disableIgnoreCaughtExceptions)
|
||||
.then(clickToPauseOnAllExceptions)
|
||||
.then(clickToPauseOnUncaughtExceptions)
|
||||
.then(testSetBreakpoint)
|
||||
.then(reloadPage)
|
||||
.then(testHitBreakpoint)
|
||||
.then(enableIgnoreCaughtExceptions)
|
||||
.then(disablePauseOnExceptions)
|
||||
.then(clickToStopPauseOnExceptions)
|
||||
.then(() => closeDebuggerAndFinish(gPanel))
|
||||
.then(null, aError => {
|
||||
ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
|
||||
@ -50,40 +46,63 @@ function checkInitialSource() {
|
||||
"The debugger should show the minified js file.");
|
||||
}
|
||||
|
||||
function enablePauseOnExceptions() {
|
||||
let deferred = promise.defer();
|
||||
function clickToPauseOnAllExceptions() {
|
||||
var deferred = promise.defer();
|
||||
var pauseOnExceptionsButton = getPauseOnExceptionsButton();
|
||||
|
||||
gDebugger.gThreadClient.addOneTimeListener("resumed", () => {
|
||||
is(gPrefs.pauseOnExceptions, true,
|
||||
"The pause-on-exceptions pref should now be enabled.");
|
||||
is(pauseOnExceptionsButton.getAttribute("tooltiptext"),
|
||||
"Pause on uncaught exceptions",
|
||||
"The button's tooltip text should be 'Pause on uncaught exceptions'.");
|
||||
is(pauseOnExceptionsButton.getAttribute("state"), 1,
|
||||
"The pause on exceptions button state variable should be 1");
|
||||
|
||||
ok(true, "Pausing on exceptions was enabled.");
|
||||
deferred.resolve();
|
||||
deferred.resolve();
|
||||
});
|
||||
|
||||
gOptions._pauseOnExceptionsItem.setAttribute("checked", "true");
|
||||
gOptions._togglePauseOnExceptions();
|
||||
pauseOnExceptionsButton.click();
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function disableIgnoreCaughtExceptions() {
|
||||
let deferred = promise.defer();
|
||||
function clickToPauseOnUncaughtExceptions() {
|
||||
var deferred = promise.defer();
|
||||
var pauseOnExceptionsButton = getPauseOnExceptionsButton();
|
||||
|
||||
gDebugger.gThreadClient.addOneTimeListener("resumed", () =>{
|
||||
is(pauseOnExceptionsButton.getAttribute("tooltiptext"),
|
||||
"Do not pause on exceptions",
|
||||
"The button's tooltip text should be 'Do not pause on exceptions'.");
|
||||
is(pauseOnExceptionsButton.getAttribute("state"), 2,
|
||||
"The pause on exceptions button state variable should be 2");
|
||||
|
||||
deferred.resolve();
|
||||
});
|
||||
pauseOnExceptionsButton.click();
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function clickToStopPauseOnExceptions() {
|
||||
var deferred = promise.defer();
|
||||
var pauseOnExceptionsButton = getPauseOnExceptionsButton();
|
||||
|
||||
gDebugger.gThreadClient.addOneTimeListener("resumed", () => {
|
||||
is(gPrefs.ignoreCaughtExceptions, false,
|
||||
"The ignore-caught-exceptions pref should now be disabled.");
|
||||
is(pauseOnExceptionsButton.getAttribute("tooltiptext"),
|
||||
"Pause on all exceptions",
|
||||
"The button's tooltip text should be 'Pause on all exceptions'.");
|
||||
is(pauseOnExceptionsButton.getAttribute("state"), 0,
|
||||
"The pause on exceptions button state variable should be 0");
|
||||
|
||||
ok(true, "Ignore caught exceptions was disabled.");
|
||||
deferred.resolve();
|
||||
deferred.resolve();
|
||||
});
|
||||
|
||||
gOptions._ignoreCaughtExceptionsItem.setAttribute("checked", "false");
|
||||
gOptions._toggleIgnoreCaughtExceptions();
|
||||
|
||||
pauseOnExceptionsButton.click();
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function getPauseOnExceptionsButton() {
|
||||
return gDebugger.document.getElementById("toggle-pause-exceptions");
|
||||
}
|
||||
|
||||
function testSetBreakpoint() {
|
||||
let deferred = promise.defer();
|
||||
let sourceForm = getSourceForm(gSources, JS_URL);
|
||||
@ -113,62 +132,11 @@ function testHitBreakpoint() {
|
||||
gDebugger.gThreadClient.resume(aResponse => {
|
||||
ok(!aResponse.error, "Shouldn't get an error resuming.");
|
||||
is(aResponse.type, "resumed", "Type should be 'resumed'.");
|
||||
is(gFrames.itemCount, 2, "Should have two frames.");
|
||||
|
||||
waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES).then(() => {
|
||||
is(gFrames.itemCount, 2, "Should have two frames.");
|
||||
|
||||
// This is weird, but we need to let the debugger a chance to
|
||||
// update first
|
||||
executeSoon(() => {
|
||||
gDebugger.gThreadClient.resume(() => {
|
||||
gDebugger.gThreadClient.addOneTimeListener("paused", () => {
|
||||
gDebugger.gThreadClient.resume(() => {
|
||||
// We also need to make sure the next step doesn't add a
|
||||
// "resumed" handler until this is completely finished
|
||||
executeSoon(() => {
|
||||
deferred.resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function enableIgnoreCaughtExceptions() {
|
||||
let deferred = promise.defer();
|
||||
|
||||
gDebugger.gThreadClient.addOneTimeListener("resumed", () => {
|
||||
is(gPrefs.ignoreCaughtExceptions, true,
|
||||
"The ignore-caught-exceptions pref should now be enabled.");
|
||||
|
||||
ok(true, "Ignore caught exceptions was enabled.");
|
||||
deferred.resolve();
|
||||
});
|
||||
|
||||
gOptions._ignoreCaughtExceptionsItem.setAttribute("checked", "true");
|
||||
gOptions._toggleIgnoreCaughtExceptions();
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function disablePauseOnExceptions() {
|
||||
let deferred = promise.defer();
|
||||
|
||||
gDebugger.gThreadClient.addOneTimeListener("resumed", () => {
|
||||
is(gPrefs.pauseOnExceptions, false,
|
||||
"The pause-on-exceptions pref should now be disabled.");
|
||||
|
||||
ok(true, "Pausing on exceptions was disabled.");
|
||||
deferred.resolve();
|
||||
});
|
||||
|
||||
gOptions._pauseOnExceptionsItem.setAttribute("checked", "false");
|
||||
gOptions._togglePauseOnExceptions();
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
@ -178,6 +146,5 @@ registerCleanupFunction(function() {
|
||||
gFrames = null;
|
||||
gSources = null;
|
||||
gPrefs = null;
|
||||
gOptions = null;
|
||||
DevToolsUtils.reportingDisabled = false;
|
||||
});
|
||||
|
@ -17,8 +17,6 @@ function OptionsView(DebuggerController, DebuggerView) {
|
||||
this.DebuggerView = DebuggerView;
|
||||
|
||||
this._toggleAutoPrettyPrint = this._toggleAutoPrettyPrint.bind(this);
|
||||
this._togglePauseOnExceptions = this._togglePauseOnExceptions.bind(this);
|
||||
this._toggleIgnoreCaughtExceptions = this._toggleIgnoreCaughtExceptions.bind(this);
|
||||
this._toggleShowPanesOnStartup = this._toggleShowPanesOnStartup.bind(this);
|
||||
this._toggleShowVariablesOnlyEnum = this._toggleShowVariablesOnlyEnum.bind(this);
|
||||
this._toggleShowVariablesFilterBox = this._toggleShowVariablesFilterBox.bind(this);
|
||||
@ -35,8 +33,6 @@ OptionsView.prototype = {
|
||||
|
||||
this._button = document.getElementById("debugger-options");
|
||||
this._autoPrettyPrint = document.getElementById("auto-pretty-print");
|
||||
this._pauseOnExceptionsItem = document.getElementById("pause-on-exceptions");
|
||||
this._ignoreCaughtExceptionsItem = document.getElementById("ignore-caught-exceptions");
|
||||
this._showPanesOnStartupItem = document.getElementById("show-panes-on-startup");
|
||||
this._showVariablesOnlyEnumItem = document.getElementById("show-vars-only-enum");
|
||||
this._showVariablesFilterBoxItem = document.getElementById("show-vars-filter-box");
|
||||
@ -44,8 +40,6 @@ OptionsView.prototype = {
|
||||
this._autoBlackBoxItem = document.getElementById("auto-black-box");
|
||||
|
||||
this._autoPrettyPrint.setAttribute("checked", Prefs.autoPrettyPrint);
|
||||
this._pauseOnExceptionsItem.setAttribute("checked", Prefs.pauseOnExceptions);
|
||||
this._ignoreCaughtExceptionsItem.setAttribute("checked", Prefs.ignoreCaughtExceptions);
|
||||
this._showPanesOnStartupItem.setAttribute("checked", Prefs.panesVisibleOnStartup);
|
||||
this._showVariablesOnlyEnumItem.setAttribute("checked", Prefs.variablesOnlyEnumVisible);
|
||||
this._showVariablesFilterBoxItem.setAttribute("checked", Prefs.variablesSearchboxVisible);
|
||||
@ -69,8 +63,6 @@ OptionsView.prototype = {
|
||||
_addCommands: function() {
|
||||
XULUtils.addCommands(document.getElementById('debuggerCommands'), {
|
||||
toggleAutoPrettyPrint: () => this._toggleAutoPrettyPrint(),
|
||||
togglePauseOnExceptions: () => this._togglePauseOnExceptions(),
|
||||
toggleIgnoreCaughtExceptions: () => this._toggleIgnoreCaughtExceptions(),
|
||||
toggleShowPanesOnStartup: () => this._toggleShowPanesOnStartup(),
|
||||
toggleShowOnlyEnum: () => this._toggleShowVariablesOnlyEnum(),
|
||||
toggleShowVariablesFilterBox: () => this._toggleShowVariablesFilterBox(),
|
||||
@ -109,27 +101,6 @@ OptionsView.prototype = {
|
||||
this._autoPrettyPrint.getAttribute("checked") == "true";
|
||||
},
|
||||
|
||||
/**
|
||||
* Listener handling the 'pause on exceptions' menuitem command.
|
||||
*/
|
||||
_togglePauseOnExceptions: function() {
|
||||
Prefs.pauseOnExceptions =
|
||||
this._pauseOnExceptionsItem.getAttribute("checked") == "true";
|
||||
|
||||
this.DebuggerController.activeThread.pauseOnExceptions(
|
||||
Prefs.pauseOnExceptions,
|
||||
Prefs.ignoreCaughtExceptions);
|
||||
},
|
||||
|
||||
_toggleIgnoreCaughtExceptions: function() {
|
||||
Prefs.ignoreCaughtExceptions =
|
||||
this._ignoreCaughtExceptionsItem.getAttribute("checked") == "true";
|
||||
|
||||
this.DebuggerController.activeThread.pauseOnExceptions(
|
||||
Prefs.pauseOnExceptions,
|
||||
Prefs.ignoreCaughtExceptions);
|
||||
},
|
||||
|
||||
/**
|
||||
* Listener handling the 'show panes on startup' menuitem command.
|
||||
*/
|
||||
@ -198,7 +169,6 @@ OptionsView.prototype = {
|
||||
},
|
||||
|
||||
_button: null,
|
||||
_pauseOnExceptionsItem: null,
|
||||
_showPanesOnStartupItem: null,
|
||||
_showVariablesOnlyEnumItem: null,
|
||||
_showVariablesFilterBoxItem: null,
|
||||
|
@ -8,6 +8,9 @@
|
||||
const KNOWN_SOURCE_GROUPS = {
|
||||
"Add-on SDK": "resource://gre/modules/commonjs/",
|
||||
};
|
||||
const DO_NOT_PAUSE_ON_EXCEPTIONS = 0;
|
||||
const PAUSE_ON_ALL_EXCEPTIONS = 1;
|
||||
const PAUSE_ON_UNCAUGHT_EXCEPTIONS = 2;
|
||||
|
||||
KNOWN_SOURCE_GROUPS[L10N.getStr("anonymousSourcesLabel")] = "anonymous";
|
||||
|
||||
@ -20,10 +23,12 @@ function SourcesView(DebuggerController, DebuggerView) {
|
||||
this.Breakpoints = DebuggerController.Breakpoints;
|
||||
this.SourceScripts = DebuggerController.SourceScripts;
|
||||
this.DebuggerView = DebuggerView;
|
||||
this.DebuggerController = DebuggerController;
|
||||
|
||||
this.togglePrettyPrint = this.togglePrettyPrint.bind(this);
|
||||
this.toggleBlackBoxing = this.toggleBlackBoxing.bind(this);
|
||||
this.toggleBreakpoints = this.toggleBreakpoints.bind(this);
|
||||
this.togglePauseOnExceptions = this.togglePauseOnExceptions.bind(this);
|
||||
|
||||
this._onEditorLoad = this._onEditorLoad.bind(this);
|
||||
this._onEditorUnload = this._onEditorUnload.bind(this);
|
||||
@ -56,6 +61,9 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, {
|
||||
this._unnamedSourceIndex = 0;
|
||||
this.emptyText = L10N.getStr("noSourcesText");
|
||||
this._blackBoxCheckboxTooltip = L10N.getStr("blackBoxCheckboxTooltip");
|
||||
this._pauseAllExceptionsTooltip = L10N.getStr("pauseAllExceptionsTooltip");
|
||||
this._pauseUncaughtExceptionsTooltip = L10N.getStr("pauseUncaughtExceptionsTooltip");
|
||||
this._pauseNoExceptionsTooltip = L10N.getStr("pauseNoExceptionsTooltip");
|
||||
|
||||
this._commandset = document.getElementById("debuggerCommands");
|
||||
this._popupset = document.getElementById("debuggerPopupset");
|
||||
@ -66,6 +74,7 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, {
|
||||
this._stopBlackBoxButton = document.getElementById("black-boxed-message-button");
|
||||
this._prettyPrintButton = document.getElementById("pretty-print");
|
||||
this._toggleBreakpointsButton = document.getElementById("toggle-breakpoints");
|
||||
this._togglePauseOnExceptionsButton = document.getElementById("toggle-pause-exceptions");
|
||||
this._newTabMenuItem = document.getElementById("debugger-sources-context-newtab");
|
||||
this._copyUrlMenuItem = document.getElementById("debugger-sources-context-copyurl");
|
||||
|
||||
@ -73,6 +82,9 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, {
|
||||
this._prettyPrintButton.removeAttribute("hidden");
|
||||
}
|
||||
|
||||
this._togglePauseOnExceptionsButton.setAttribute("tooltiptext", this._pauseAllExceptionsTooltip);
|
||||
this._togglePauseOnExceptionsButton.setAttribute("state", DO_NOT_PAUSE_ON_EXCEPTIONS);
|
||||
|
||||
window.on(EVENTS.EDITOR_LOADED, this._onEditorLoad, false);
|
||||
window.on(EVENTS.EDITOR_UNLOADED, this._onEditorUnload, false);
|
||||
this.widget.addEventListener("select", this._onSourceSelect, false);
|
||||
@ -138,6 +150,7 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, {
|
||||
unBlackBoxButton: () => this._onStopBlackBoxing(),
|
||||
prettyPrintCommand: () => this.togglePrettyPrint(),
|
||||
toggleBreakpointsCommand: () =>this.toggleBreakpoints(),
|
||||
togglePauseOnExceptionsCommand: () => this.togglePauseOnExceptions(),
|
||||
nextSourceCommand: () => this.selectNextItem(),
|
||||
prevSourceCommand: () => this.selectPrevItem()
|
||||
});
|
||||
@ -615,6 +628,37 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Toggles the pause on exceptions functionality
|
||||
*/
|
||||
togglePauseOnExceptions: function() {
|
||||
let state = Number(this._togglePauseOnExceptionsButton.getAttribute("state"));
|
||||
let tooltip;
|
||||
|
||||
state = ++state % 3;
|
||||
|
||||
if (state === DO_NOT_PAUSE_ON_EXCEPTIONS) {
|
||||
tooltip = this._pauseAllExceptionsTooltip;
|
||||
Prefs.pauseOnExceptions = false;
|
||||
Prefs.ignoreCaughtExceptions = false;
|
||||
} else if ( state === PAUSE_ON_ALL_EXCEPTIONS) {
|
||||
tooltip = this._pauseUncaughtExceptionsTooltip;
|
||||
Prefs.pauseOnExceptions = true;
|
||||
Prefs.ignoreCaughtExceptions = false;
|
||||
} else {
|
||||
tooltip = this._pauseNoExceptionsTooltip;
|
||||
Prefs.pauseOnExceptions = true;
|
||||
Prefs.ignoreCaughtExceptions = true;
|
||||
}
|
||||
|
||||
this.DebuggerController.activeThread.pauseOnExceptions(
|
||||
Prefs.pauseOnExceptions,
|
||||
Prefs.ignoreCaughtExceptions);
|
||||
|
||||
this._togglePauseOnExceptionsButton.setAttribute("tooltiptext", tooltip);
|
||||
this._togglePauseOnExceptionsButton.setAttribute("state", state);
|
||||
},
|
||||
|
||||
hidePrettyPrinting: function() {
|
||||
this._prettyPrintButton.style.display = 'none';
|
||||
|
||||
|
@ -243,7 +243,8 @@ StackFramesView.prototype = Heritage.extend(WidgetMethods, {
|
||||
},
|
||||
|
||||
_mirror: null,
|
||||
_prevBlackBoxedUrl: null
|
||||
_prevBlackBoxedUrl: null,
|
||||
_popupset: null
|
||||
});
|
||||
|
||||
DebuggerView.StackFrames = new StackFramesView(DebuggerController, DebuggerView);
|
||||
|
@ -19,6 +19,8 @@ loader.lazyRequireGetter(this, "TimelineFront",
|
||||
"devtools/server/actors/timeline", true);
|
||||
loader.lazyRequireGetter(this, "MemoryFront",
|
||||
"devtools/server/actors/memory", true);
|
||||
loader.lazyRequireGetter(this, "ProfilerFront",
|
||||
"devtools/server/actors/profiler", true);
|
||||
|
||||
// how often do we pull allocation sites from the memory actor
|
||||
const ALLOCATION_SITE_POLL_TIMER = 200; // ms
|
||||
@ -58,7 +60,7 @@ ProfilerFrontFacade.prototype = {
|
||||
// Connects to the targets underlying real ProfilerFront.
|
||||
connect: Task.async(function*() {
|
||||
let target = this._target;
|
||||
this._actor = yield CompatUtils.getProfiler(target);
|
||||
this._front = new ProfilerFront(target.client, target.form);
|
||||
|
||||
// Fetch and store information about the SPS profiler and
|
||||
// server profiler.
|
||||
@ -68,8 +70,7 @@ ProfilerFrontFacade.prototype = {
|
||||
// Directly register to event notifications when connected
|
||||
// to hook into `console.profile|profileEnd` calls.
|
||||
yield this.registerEventNotifications({ events: this.EVENTS });
|
||||
// TODO bug 1159389, listen directly to actor if supporting new front
|
||||
target.client.addListener("eventNotification", this._onProfilerEvent);
|
||||
this.EVENTS.forEach(e => this._front.on(e, this._onProfilerEvent));
|
||||
}),
|
||||
|
||||
/**
|
||||
@ -79,9 +80,10 @@ ProfilerFrontFacade.prototype = {
|
||||
if (this._poller) {
|
||||
yield this._poller.destroy();
|
||||
}
|
||||
|
||||
this.EVENTS.forEach(e => this._front.off(e, this._onProfilerEvent));
|
||||
yield this.unregisterEventNotifications({ events: this.EVENTS });
|
||||
// TODO bug 1159389, listen directly to actor if supporting new front
|
||||
this._target.client.removeListener("eventNotification", this._onProfilerEvent);
|
||||
yield this._front.destroy();
|
||||
}),
|
||||
|
||||
/**
|
||||
@ -105,7 +107,14 @@ ProfilerFrontFacade.prototype = {
|
||||
// nsIPerformance module will be kept recording, because it's the same instance
|
||||
// for all targets and interacts with the whole platform, so we don't want
|
||||
// to affect other clients by stopping (or restarting) it.
|
||||
let { isActive, currentTime, position, generation, totalSize } = yield this.getStatus();
|
||||
let status = yield this.getStatus();
|
||||
|
||||
// This should only occur during teardown
|
||||
if (!status) {
|
||||
return;
|
||||
}
|
||||
|
||||
let { isActive, currentTime, position, generation, totalSize } = status;
|
||||
|
||||
if (isActive) {
|
||||
this.emit("profiler-already-active");
|
||||
@ -121,7 +130,7 @@ ProfilerFrontFacade.prototype = {
|
||||
|
||||
let startInfo = yield this.startProfiler(profilerOptions);
|
||||
let startTime = 0;
|
||||
if ('currentTime' in startInfo) {
|
||||
if ("currentTime" in startInfo) {
|
||||
startTime = startInfo.currentTime;
|
||||
}
|
||||
|
||||
@ -142,7 +151,7 @@ ProfilerFrontFacade.prototype = {
|
||||
* Wrapper around `profiler.isActive()` to take profiler status data and emit.
|
||||
*/
|
||||
getStatus: Task.async(function *() {
|
||||
let data = yield (CompatUtils.actorCompatibilityBridge("isActive").call(this));
|
||||
let data = yield CompatUtils.callFrontMethod("isActive").call(this);
|
||||
// If no data, the last poll for `isActive()` was wrapping up, and the target.client
|
||||
// is now null, so we no longer have data, so just abort here.
|
||||
if (!data) {
|
||||
@ -169,7 +178,7 @@ ProfilerFrontFacade.prototype = {
|
||||
* Returns profile data from now since `startTime`.
|
||||
*/
|
||||
getProfile: Task.async(function *(options) {
|
||||
let profilerData = yield (CompatUtils.actorCompatibilityBridge("getProfile").call(this, options));
|
||||
let profilerData = yield CompatUtils.callFrontMethod("getProfile").call(this, options);
|
||||
// If the backend is not deduped, dedupe it ourselves, as rest of the code
|
||||
// expects a deduped profile.
|
||||
if (profilerData.profile.meta.version === 2) {
|
||||
@ -191,7 +200,9 @@ ProfilerFrontFacade.prototype = {
|
||||
* @param object response
|
||||
* The data received from the backend.
|
||||
*/
|
||||
_onProfilerEvent: function (_, { topic, subject, details }) {
|
||||
_onProfilerEvent: function (data) {
|
||||
let { subject, topic, details } = data;
|
||||
|
||||
if (topic === "console-api-profiler") {
|
||||
if (subject.action === "profile") {
|
||||
this.emit("console-profile-start", details);
|
||||
@ -224,7 +235,7 @@ TimelineFrontFacade.prototype = {
|
||||
|
||||
connect: Task.async(function*() {
|
||||
let supported = yield CompatUtils.timelineActorSupported(this._target);
|
||||
this._actor = supported ?
|
||||
this._front = supported ?
|
||||
new TimelineFront(this._target.client, this._target.form) :
|
||||
new CompatUtils.MockTimelineFront();
|
||||
|
||||
@ -234,7 +245,7 @@ TimelineFrontFacade.prototype = {
|
||||
// exposed event.
|
||||
this.EVENTS.forEach(type => {
|
||||
let handler = this[`_on${type}`] = this._onTimelineData.bind(this, type);
|
||||
this._actor.on(type, handler);
|
||||
this._front.on(type, handler);
|
||||
});
|
||||
}),
|
||||
|
||||
@ -243,8 +254,8 @@ TimelineFrontFacade.prototype = {
|
||||
* destroying the underlying actor.
|
||||
*/
|
||||
destroy: Task.async(function *() {
|
||||
this.EVENTS.forEach(type => this._actor.off(type, this[`_on${type}`]));
|
||||
yield this._actor.destroy();
|
||||
this.EVENTS.forEach(type => this._front.off(type, this[`_on${type}`]));
|
||||
yield this._front.destroy();
|
||||
}),
|
||||
|
||||
/**
|
||||
@ -271,7 +282,7 @@ function MemoryFrontFacade (target) {
|
||||
MemoryFrontFacade.prototype = {
|
||||
connect: Task.async(function*() {
|
||||
let supported = yield CompatUtils.memoryActorSupported(this._target);
|
||||
this._actor = supported ?
|
||||
this._front = supported ?
|
||||
new MemoryFront(this._target.client, this._target.form) :
|
||||
new CompatUtils.MockMemoryFront();
|
||||
|
||||
@ -285,7 +296,7 @@ MemoryFrontFacade.prototype = {
|
||||
if (this._poller) {
|
||||
yield this._poller.destroy();
|
||||
}
|
||||
yield this._actor.destroy();
|
||||
yield this._front.destroy();
|
||||
}),
|
||||
|
||||
/**
|
||||
@ -374,9 +385,9 @@ MemoryFrontFacade.prototype = {
|
||||
};
|
||||
|
||||
// Bind all the methods that directly proxy to the actor
|
||||
PROFILER_ACTOR_METHODS.forEach(m => ProfilerFrontFacade.prototype[m] = CompatUtils.actorCompatibilityBridge(m));
|
||||
TIMELINE_ACTOR_METHODS.forEach(m => TimelineFrontFacade.prototype[m] = CompatUtils.actorCompatibilityBridge(m));
|
||||
MEMORY_ACTOR_METHODS.forEach(m => MemoryFrontFacade.prototype[m] = CompatUtils.actorCompatibilityBridge(m));
|
||||
PROFILER_ACTOR_METHODS.forEach(m => ProfilerFrontFacade.prototype[m] = CompatUtils.callFrontMethod(m));
|
||||
TIMELINE_ACTOR_METHODS.forEach(m => TimelineFrontFacade.prototype[m] = CompatUtils.callFrontMethod(m));
|
||||
MEMORY_ACTOR_METHODS.forEach(m => MemoryFrontFacade.prototype[m] = CompatUtils.callFrontMethod(m));
|
||||
|
||||
exports.ProfilerFront = ProfilerFrontFacade;
|
||||
exports.TimelineFront = TimelineFrontFacade;
|
||||
|
@ -101,51 +101,10 @@ function timelineActorSupported(target) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a promise resolving to the location of the profiler actor
|
||||
* within this context.
|
||||
*
|
||||
* @param {TabTarget} target
|
||||
* @return {Promise<ProfilerActor>}
|
||||
* Returns a function to be used as a method on an "Front" in ./actors.
|
||||
* Calls the underlying actor's method.
|
||||
*/
|
||||
function getProfiler (target) {
|
||||
let deferred = promise.defer();
|
||||
// Chrome and content process targets already have obtained a reference
|
||||
// to the profiler tab actor. Use it immediately.
|
||||
if (target.form && target.form.profilerActor) {
|
||||
deferred.resolve(target.form.profilerActor);
|
||||
}
|
||||
// Check if we already have a grip to the `listTabs` response object
|
||||
// and, if we do, use it to get to the profiler actor.
|
||||
else if (target.root && target.root.profilerActor) {
|
||||
deferred.resolve(target.root.profilerActor);
|
||||
}
|
||||
// Otherwise, call `listTabs`.
|
||||
else {
|
||||
target.client.listTabs(({ profilerActor }) => deferred.resolve(profilerActor));
|
||||
}
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a request to an actor that does not have the modern `Front`
|
||||
* interface.
|
||||
*/
|
||||
function legacyRequest (target, actor, method, args) {
|
||||
let deferred = promise.defer();
|
||||
let data = args[0] || {};
|
||||
data.to = actor;
|
||||
data.type = method;
|
||||
target.client.request(data, deferred.resolve);
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a function to be used as a method on an "Actor" in ./actors.
|
||||
* Calls the underlying actor's method, supporting the modern `Front`
|
||||
* interface if possible, otherwise, falling back to using
|
||||
* `legacyRequest`.
|
||||
*/
|
||||
function actorCompatibilityBridge (method) {
|
||||
function callFrontMethod (method) {
|
||||
return function () {
|
||||
// If there's no target or client on this actor facade,
|
||||
// abort silently -- this occurs in tests when polling occurs
|
||||
@ -154,19 +113,7 @@ function actorCompatibilityBridge (method) {
|
||||
if (!this._target || !this._target.client) {
|
||||
return;
|
||||
}
|
||||
// Check to see if this is a modern ActorFront, which has its
|
||||
// own `request` method. Also, check if its a mock actor, as it mimicks
|
||||
// the ActorFront interface.
|
||||
// The profiler actor does not currently support the modern `Front`
|
||||
// interface, so we have to manually push packets to it.
|
||||
// TODO bug 1159389, fix up profiler actor to not need this, however
|
||||
// we will need it for backwards compat
|
||||
if (this.IS_MOCK || this._actor.request) {
|
||||
return this._actor[method].apply(this._actor, arguments);
|
||||
}
|
||||
else {
|
||||
return legacyRequest(this._target, this._actor, method, arguments);
|
||||
}
|
||||
return this._front[method].apply(this._front, arguments);
|
||||
};
|
||||
}
|
||||
|
||||
@ -174,5 +121,4 @@ exports.MockMemoryFront = MockMemoryFront;
|
||||
exports.MockTimelineFront = MockTimelineFront;
|
||||
exports.memoryActorSupported = memoryActorSupported;
|
||||
exports.timelineActorSupported = timelineActorSupported;
|
||||
exports.getProfiler = getProfiler;
|
||||
exports.actorCompatibilityBridge = actorCompatibilityBridge;
|
||||
exports.callFrontMethod = callFrontMethod;
|
||||
|
@ -219,7 +219,8 @@ ThreadNode.prototype = {
|
||||
|
||||
// If we shouldn't flatten the current frame into the previous one, advance a
|
||||
// level in the call tree.
|
||||
if (!flattenRecursion || frameKey !== prevFrameKey) {
|
||||
let shouldFlatten = flattenRecursion && frameKey === prevFrameKey;
|
||||
if (!shouldFlatten) {
|
||||
calls = prevCalls;
|
||||
}
|
||||
|
||||
@ -233,7 +234,11 @@ ThreadNode.prototype = {
|
||||
sampleTime, stringTable);
|
||||
}
|
||||
}
|
||||
frameNode.samples++;
|
||||
|
||||
// Don't overcount flattened recursive frames.
|
||||
if (!shouldFlatten) {
|
||||
frameNode.samples++;
|
||||
}
|
||||
|
||||
prevFrameKey = frameKey;
|
||||
prevCalls = frameNode.calls;
|
||||
|
@ -45,6 +45,10 @@ function* spawnTest () {
|
||||
ok(true, "Got expected cycle collection events");
|
||||
|
||||
yield front.stopRecording();
|
||||
|
||||
// Destroy the front before removing tab to ensure no
|
||||
// lingering requests
|
||||
yield front.destroy();
|
||||
yield removeTab(target.tab);
|
||||
finish();
|
||||
}
|
||||
|
@ -35,6 +35,9 @@ function* spawnTest () {
|
||||
ok(markers.every(({causeName}) => typeof causeName === "string"),
|
||||
"All markers have a causeName.");
|
||||
|
||||
// Destroy the front before removing tab to ensure no
|
||||
// lingering requests
|
||||
yield front.destroy();
|
||||
yield removeTab(target.tab);
|
||||
finish();
|
||||
|
||||
|
@ -25,6 +25,9 @@ function* spawnTest () {
|
||||
|
||||
ok(markers.every(({name}) => name === "Parse HTML"), "All markers found are Parse HTML markers");
|
||||
|
||||
// Destroy the front before removing tab to ensure no
|
||||
// lingering requests
|
||||
yield front.destroy();
|
||||
yield removeTab(target.tab);
|
||||
finish();
|
||||
|
||||
|
@ -27,6 +27,9 @@ function* spawnTest () {
|
||||
|
||||
ok(markers.some(({restyleHint}) => restyleHint != void 0), "some markers have a restyleHint property");
|
||||
|
||||
// Destroy the front before removing tab to ensure no
|
||||
// lingering requests
|
||||
yield front.destroy();
|
||||
yield removeTab(target.tab);
|
||||
finish();
|
||||
|
||||
|
@ -36,6 +36,9 @@ function* spawnTest () {
|
||||
is(markers[0].causeName, void 0, "Unlabeled timestamps have an empty causeName");
|
||||
is(markers[1].causeName, "myLabel", "Labeled timestamps have correct causeName");
|
||||
|
||||
// Destroy the front before removing tab to ensure no
|
||||
// lingering requests
|
||||
yield front.destroy();
|
||||
yield removeTab(target.tab);
|
||||
finish();
|
||||
|
||||
|
@ -45,6 +45,9 @@ function* spawnTest() {
|
||||
ok(recording.getDuration() >= 0,
|
||||
"The profilerEndTime is after profilerStartTime.");
|
||||
|
||||
// Destroy the front before removing tab to ensure no
|
||||
// lingering requests
|
||||
yield front.destroy();
|
||||
yield removeTab(target.tab);
|
||||
finish();
|
||||
}
|
||||
|
@ -44,6 +44,9 @@ function* spawnTest() {
|
||||
ok(recording.getDuration() >= 0,
|
||||
"The profilerEndTime is after profilerStartTime.");
|
||||
|
||||
// Destroy the front before removing tab to ensure no
|
||||
// lingering requests
|
||||
yield front.destroy();
|
||||
yield removeTab(target.tab);
|
||||
finish();
|
||||
}
|
||||
|
@ -27,6 +27,9 @@ function* spawnTest() {
|
||||
yield front.stopRecording(model);
|
||||
is(model.getBufferUsage(), null, "after recording, model should still have `null` for its buffer usage");
|
||||
|
||||
// Destroy the front before removing tab to ensure no
|
||||
// lingering requests
|
||||
yield front.destroy();
|
||||
yield removeTab(target.tab);
|
||||
finish();
|
||||
}
|
||||
|
@ -37,8 +37,6 @@ function* spawnTest() {
|
||||
let secondRecordingProfile = secondRecording.getProfile();
|
||||
let secondRecordingSamples = secondRecordingProfile.threads[0].samples.data;
|
||||
|
||||
isnot(secondRecording._profilerStartTime, 0,
|
||||
"The profiling start time should not be 0 on the second recording.");
|
||||
ok(secondRecording.getDuration() >= WAIT_TIME,
|
||||
"The second recording duration is correct.");
|
||||
|
||||
|
@ -47,6 +47,9 @@ function* spawnTest() {
|
||||
is((yield front._request("memory", "getState")), "detached",
|
||||
"Memory actor is detached when stopping recording with allocations.");
|
||||
|
||||
// Destroy the front before removing tab to ensure no
|
||||
// lingering requests
|
||||
yield front.destroy();
|
||||
yield removeTab(target.tab);
|
||||
finish();
|
||||
}
|
||||
|
@ -30,6 +30,9 @@ function* spawnTest() {
|
||||
ok(timelineStart1 < timelineStart2, "can start timeline actor twice and get different start times");
|
||||
ok(memoryStart1 < memoryStart2, "can start memory actor twice and get different start times");
|
||||
|
||||
// Destroy the front before removing tab to ensure no
|
||||
// lingering requests
|
||||
yield front.destroy();
|
||||
yield removeTab(target.tab);
|
||||
finish();
|
||||
}
|
||||
|
@ -27,6 +27,9 @@ function* spawnTest() {
|
||||
ok(stopModel.getProfile(), "recording model has a profile after stopping.");
|
||||
ok(stopModel.getDuration(), "recording model has a duration after stopping.");
|
||||
|
||||
// Destroy the front before removing tab to ensure no
|
||||
// lingering requests
|
||||
yield front.destroy();
|
||||
yield removeTab(target.tab);
|
||||
finish();
|
||||
}
|
||||
|
@ -34,6 +34,9 @@ function* spawnTest() {
|
||||
is(counters.memory.length, 3, "three memory events fired.");
|
||||
is(counters.ticks.length, 3, "three ticks events fired.");
|
||||
|
||||
// Destroy the front before removing tab to ensure no
|
||||
// lingering requests
|
||||
yield front.destroy();
|
||||
yield removeTab(target.tab);
|
||||
finish();
|
||||
|
||||
|
@ -38,6 +38,9 @@ function* spawnTest() {
|
||||
|
||||
is(model.getBufferUsage(), null, "getBufferUsage() should be null when no longer recording.");
|
||||
|
||||
// Destroy the front before removing tab to ensure no
|
||||
// lingering requests
|
||||
yield front.destroy();
|
||||
yield removeTab(target.tab);
|
||||
finish();
|
||||
}
|
||||
|
@ -8,6 +8,10 @@
|
||||
|
||||
const {cssTokenizer} = require("devtools/sourceeditor/css-tokenizer");
|
||||
|
||||
const SELECTOR_ATTRIBUTE = exports.SELECTOR_ATTRIBUTE = 1;
|
||||
const SELECTOR_ELEMENT = exports.SELECTOR_ELEMENT = 2;
|
||||
const SELECTOR_PSEUDO_CLASS = exports.SELECTOR_PSEUDO_CLASS = 3;
|
||||
|
||||
/**
|
||||
* Returns an array of CSS declarations given an string.
|
||||
* For example, parseDeclarations("width: 1px; height: 1px") would return
|
||||
@ -87,8 +91,111 @@ function parseDeclarations(inputString) {
|
||||
declarations = declarations.filter(prop => prop.name || prop.value);
|
||||
|
||||
return declarations;
|
||||
};
|
||||
exports.parseDeclarations = parseDeclarations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of the parsed CSS selector value and type given a string.
|
||||
*
|
||||
* The components making up the CSS selector can be extracted into 3 different
|
||||
* types: element, attribute and pseudoclass. The object that is appended to
|
||||
* the returned array contains the value related to one of the 3 types described
|
||||
* along with the actual type.
|
||||
*
|
||||
* The following are the 3 types that can be returned in the object signature:
|
||||
* (1) SELECTOR_ATTRIBUTE
|
||||
* (2) SELECTOR_ELEMENT
|
||||
* (3) SELECTOR_PSEUDO_CLASS
|
||||
*
|
||||
* @param {string} value
|
||||
* The CSS selector text.
|
||||
* @return {Array} an array of objects with the following signature:
|
||||
* [{ "value": string, "type": integer }, ...]
|
||||
*/
|
||||
function parsePseudoClassesAndAttributes(value) {
|
||||
if (!value) {
|
||||
throw new Error("empty input string");
|
||||
}
|
||||
|
||||
let tokens = cssTokenizer(value);
|
||||
let result = [];
|
||||
let current = "";
|
||||
let functionCount = 0;
|
||||
let hasAttribute = false;
|
||||
let hasColon = false;
|
||||
|
||||
for (let token of tokens) {
|
||||
if (token.tokenType === "ident") {
|
||||
current += value.substring(token.startOffset, token.endOffset);
|
||||
|
||||
if (hasColon && !functionCount) {
|
||||
if (current) {
|
||||
result.push({ value: current, type: SELECTOR_PSEUDO_CLASS });
|
||||
}
|
||||
|
||||
current = "";
|
||||
hasColon = false;
|
||||
}
|
||||
} else if (token.tokenType === "symbol" && token.text === ":") {
|
||||
if (!hasColon) {
|
||||
if (current) {
|
||||
result.push({ value: current, type: SELECTOR_ELEMENT });
|
||||
}
|
||||
|
||||
current = "";
|
||||
hasColon = true;
|
||||
}
|
||||
|
||||
current += token.text;
|
||||
} else if (token.tokenType === "function") {
|
||||
current += value.substring(token.startOffset, token.endOffset);
|
||||
functionCount++;
|
||||
} else if (token.tokenType === "symbol" && token.text === ")") {
|
||||
current += token.text;
|
||||
|
||||
if (hasColon && functionCount == 1) {
|
||||
if (current) {
|
||||
result.push({ value: current, type: SELECTOR_PSEUDO_CLASS });
|
||||
}
|
||||
|
||||
current = "";
|
||||
functionCount--;
|
||||
hasColon = false;
|
||||
} else {
|
||||
functionCount--;
|
||||
}
|
||||
} else if (token.tokenType === "symbol" && token.text === "[") {
|
||||
if (!hasAttribute && !functionCount) {
|
||||
if (current) {
|
||||
result.push({ value: current, type: SELECTOR_ELEMENT });
|
||||
}
|
||||
|
||||
current = "";
|
||||
hasAttribute = true;
|
||||
}
|
||||
|
||||
current += token.text;
|
||||
} else if (token.tokenType === "symbol" && token.text === "]") {
|
||||
current += token.text;
|
||||
|
||||
if (hasAttribute && !functionCount) {
|
||||
if (current) {
|
||||
result.push({ value: current, type: SELECTOR_ATTRIBUTE });
|
||||
}
|
||||
|
||||
current = "";
|
||||
hasAttribute = false;
|
||||
}
|
||||
} else {
|
||||
current += value.substring(token.startOffset, token.endOffset);
|
||||
}
|
||||
}
|
||||
|
||||
if (current) {
|
||||
result.push({ value: current, type: SELECTOR_ELEMENT });
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Expects a single CSS value to be passed as the input and parses the value
|
||||
@ -103,5 +210,8 @@ function parseSingleValue(value) {
|
||||
value: declaration ? declaration.value : "",
|
||||
priority: declaration ? declaration.priority : ""
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
exports.parseDeclarations = parseDeclarations;
|
||||
exports.parsePseudoClassesAndAttributes = parsePseudoClassesAndAttributes;
|
||||
exports.parseSingleValue = parseSingleValue;
|
||||
|
@ -17,8 +17,14 @@ const {ELEMENT_STYLE, PSEUDO_ELEMENTS} =
|
||||
require("devtools/server/actors/styles");
|
||||
const {OutputParser} = require("devtools/output-parser");
|
||||
const {PrefObserver, PREF_ORIG_SOURCES} = require("devtools/styleeditor/utils");
|
||||
const {parseSingleValue, parseDeclarations} =
|
||||
require("devtools/styleinspector/css-parsing-utils");
|
||||
const {
|
||||
parseDeclarations,
|
||||
parseSingleValue,
|
||||
parsePseudoClassesAndAttributes,
|
||||
SELECTOR_ATTRIBUTE,
|
||||
SELECTOR_ELEMENT,
|
||||
SELECTOR_PSEUDO_CLASS
|
||||
} = require("devtools/styleinspector/css-parsing-utils");
|
||||
const overlays = require("devtools/styleinspector/style-inspector-overlays");
|
||||
const EventEmitter = require("devtools/toolkit/event-emitter");
|
||||
|
||||
@ -1563,7 +1569,11 @@ CssRuleView.prototype = {
|
||||
};
|
||||
} else if (classes.contains("ruleview-selector-unmatched") ||
|
||||
classes.contains("ruleview-selector-matched") ||
|
||||
classes.contains("ruleview-selector")) {
|
||||
classes.contains("ruleview-selectorcontainer") ||
|
||||
classes.contains("ruleview-selector") ||
|
||||
classes.contains("ruleview-selector-attribute") ||
|
||||
classes.contains("ruleview-selector-pseudo-class") ||
|
||||
classes.contains("ruleview-selector-pseudo-class-lock")) {
|
||||
type = overlays.VIEW_NODE_SELECTOR_TYPE;
|
||||
value = node.offsetParent._ruleEditor.selectorText.textContent;
|
||||
} else if (classes.contains("ruleview-rule-source")) {
|
||||
@ -2730,10 +2740,23 @@ RuleEditor.prototype = {
|
||||
|
||||
let header = createChild(code, "div", {});
|
||||
|
||||
this.selectorContainer = createChild(header, "span", {
|
||||
class: "ruleview-selectorcontainer"
|
||||
this.selectorText = createChild(header, "span", {
|
||||
class: "ruleview-selectorcontainer theme-fg-color3",
|
||||
tabindex: this.isSelectorEditable ? "0" : "-1",
|
||||
});
|
||||
|
||||
if (this.isSelectorEditable) {
|
||||
this.selectorText.addEventListener("click", aEvent => {
|
||||
// Clicks within the selector shouldn't propagate any further.
|
||||
aEvent.stopPropagation();
|
||||
}, false);
|
||||
|
||||
editableField({
|
||||
element: this.selectorText,
|
||||
done: this._onSelectorDone,
|
||||
});
|
||||
}
|
||||
|
||||
if (this.rule.domRule.type !== Ci.nsIDOMCSSRule.KEYFRAME_RULE &&
|
||||
this.rule.domRule.selectors) {
|
||||
let selector = this.rule.domRule.selectors.join(", ");
|
||||
@ -2749,23 +2772,6 @@ RuleEditor.prototype = {
|
||||
});
|
||||
}
|
||||
|
||||
this.selectorText = createChild(this.selectorContainer, "span", {
|
||||
class: "ruleview-selector theme-fg-color3",
|
||||
tabindex: this.isSelectorEditable ? "0" : "-1",
|
||||
});
|
||||
|
||||
if (this.isSelectorEditable) {
|
||||
this.selectorContainer.addEventListener("click", aEvent => {
|
||||
// Clicks within the selector shouldn't propagate any further.
|
||||
aEvent.stopPropagation();
|
||||
}, false);
|
||||
|
||||
editableField({
|
||||
element: this.selectorText,
|
||||
done: this._onSelectorDone,
|
||||
});
|
||||
}
|
||||
|
||||
this.openBrace = createChild(header, "span", {
|
||||
class: "ruleview-ruleopen",
|
||||
textContent: " {"
|
||||
@ -2863,16 +2869,41 @@ RuleEditor.prototype = {
|
||||
textContent: ", "
|
||||
});
|
||||
}
|
||||
let cls;
|
||||
if (this.rule.matchedSelectors.indexOf(selector) > -1) {
|
||||
cls = "ruleview-selector-matched";
|
||||
} else {
|
||||
cls = "ruleview-selector-unmatched";
|
||||
}
|
||||
createChild(this.selectorText, "span", {
|
||||
class: cls,
|
||||
textContent: selector
|
||||
|
||||
let containerClass =
|
||||
(this.rule.matchedSelectors.indexOf(selector) > -1) ?
|
||||
"ruleview-selector-matched" : "ruleview-selector-unmatched";
|
||||
let selectorContainer = createChild(this.selectorText, "span", {
|
||||
class: containerClass
|
||||
});
|
||||
|
||||
let parsedSelector = parsePseudoClassesAndAttributes(selector);
|
||||
|
||||
for (let selectorText of parsedSelector) {
|
||||
let selectorClass = "";
|
||||
|
||||
switch (selectorText.type) {
|
||||
case SELECTOR_ATTRIBUTE:
|
||||
selectorClass = "ruleview-selector-attribute";
|
||||
break;
|
||||
case SELECTOR_ELEMENT:
|
||||
selectorClass = "ruleview-selector";
|
||||
break;
|
||||
case SELECTOR_PSEUDO_CLASS:
|
||||
selectorClass =
|
||||
[":active", ":focus", ":hover"].includes(selectorText.value) ?
|
||||
"ruleview-selector-pseudo-class-lock" :
|
||||
"ruleview-selector-pseudo-class";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
createChild(selectorContainer, "span", {
|
||||
textContent: selectorText.value,
|
||||
class: selectorClass
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -64,8 +64,7 @@ body {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.ruleview-namecontainer,
|
||||
.ruleview-selectorcontainer {
|
||||
.ruleview-namecontainer {
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
|
@ -153,6 +153,7 @@ skip-if = e10s # Bug 1090340
|
||||
[browser_ruleview_selector-highlighter_01.js]
|
||||
[browser_ruleview_selector-highlighter_02.js]
|
||||
[browser_ruleview_selector-highlighter_03.js]
|
||||
[browser_ruleview_selector_highlight.js]
|
||||
[browser_ruleview_style-editor-link.js]
|
||||
skip-if = e10s # bug 1040670 Cannot open inline styles in viewSourceUtils
|
||||
[browser_ruleview_urls-clickable.js]
|
||||
|
@ -69,7 +69,7 @@ function checkRuleViewContent({doc}) {
|
||||
is(rules.length, 4, "There are 4 rules in the view");
|
||||
|
||||
for (let rule of rules) {
|
||||
let selector = rule.querySelector(".ruleview-selector");
|
||||
let selector = rule.querySelector(".ruleview-selectorcontainer");
|
||||
is(selector.textContent,
|
||||
STRINGS.GetStringFromName("rule.sourceElement"),
|
||||
"The rule's selector is correct");
|
||||
|
@ -47,7 +47,7 @@ add_task(function*() {
|
||||
});
|
||||
|
||||
function checkRuleViewContent(view, expectedSelectors) {
|
||||
let selectors = view.doc.querySelectorAll(".ruleview-selector");
|
||||
let selectors = view.doc.querySelectorAll(".ruleview-selectorcontainer");
|
||||
|
||||
is(selectors.length, expectedSelectors.length,
|
||||
expectedSelectors.length + " selectors are displayed");
|
||||
|
@ -0,0 +1,146 @@
|
||||
/* 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";
|
||||
|
||||
// Tests that the rule view selector text is highlighted correctly according
|
||||
// to the components of the selector.
|
||||
|
||||
const SEARCH = "00F";
|
||||
|
||||
const TEST_URI = [
|
||||
"<style type='text/css'>",
|
||||
" h1 {}",
|
||||
" h1#testid {}",
|
||||
" h1 + p {}",
|
||||
" div[hidden=\"true\"] {}",
|
||||
" div[title=\"test\"][checked=true] {}",
|
||||
" p:empty {}",
|
||||
" p:lang(en) {}",
|
||||
" .testclass:active {}",
|
||||
" .testclass:focus {}",
|
||||
" .testclass:hover {}",
|
||||
"</style>",
|
||||
"<h1>Styled Node</h1>",
|
||||
"<p>Paragraph</p>",
|
||||
"<h1 id=\"testid\">Styled Node</h1>",
|
||||
"<div hidden=\"true\"></div>",
|
||||
"<div title=\"test\" checked=\"true\"></div>",
|
||||
"<p></p>",
|
||||
"<p lang=\"en\">Paragraph<p>",
|
||||
"<div class=\"testclass\">Styled Node</div>"
|
||||
].join("\n");
|
||||
|
||||
const SELECTOR_ATTRIBUTE = "ruleview-selector-attribute";
|
||||
const SELECTOR_ELEMENT = "ruleview-selector";
|
||||
const SELECTOR_PSEUDO_CLASS = "ruleview-selector-pseudo-class";
|
||||
const SELECTOR_PSEUDO_CLASS_LOCK = "ruleview-selector-pseudo-class-lock";
|
||||
|
||||
const TEST_DATA = [
|
||||
{
|
||||
node: "h1",
|
||||
expected: [
|
||||
{ value: "h1", class: SELECTOR_ELEMENT }
|
||||
]
|
||||
},
|
||||
{
|
||||
node: "h1 + p",
|
||||
expected: [
|
||||
{ value: "h1 + p", class: SELECTOR_ELEMENT }
|
||||
]
|
||||
},
|
||||
{
|
||||
node: "h1#testid",
|
||||
expected: [
|
||||
{ value: "h1#testid", class: SELECTOR_ELEMENT }
|
||||
]
|
||||
},
|
||||
{
|
||||
node: "div[hidden='true']",
|
||||
expected: [
|
||||
{ value: "div", class: SELECTOR_ELEMENT },
|
||||
{ value: "[hidden=\"true\"]", class: SELECTOR_ATTRIBUTE }
|
||||
]
|
||||
},
|
||||
{
|
||||
node: "div[title=\"test\"][checked=\"true\"]",
|
||||
expected: [
|
||||
{ value: "div", class: SELECTOR_ELEMENT },
|
||||
{ value: "[title=\"test\"]", class: SELECTOR_ATTRIBUTE },
|
||||
{ value: "[checked=\"true\"]", class: SELECTOR_ATTRIBUTE }
|
||||
]
|
||||
},
|
||||
{
|
||||
node: "p:empty",
|
||||
expected: [
|
||||
{ value: "p", class: SELECTOR_ELEMENT },
|
||||
{ value: ":empty", class: SELECTOR_PSEUDO_CLASS }
|
||||
]
|
||||
},
|
||||
{
|
||||
node: "p:lang(en)",
|
||||
expected: [
|
||||
{ value: "p", class: SELECTOR_ELEMENT },
|
||||
{ value: ":lang(en)", class: SELECTOR_PSEUDO_CLASS }
|
||||
]
|
||||
},
|
||||
{
|
||||
node: ".testclass",
|
||||
pseudoClass: ":active",
|
||||
expected: [
|
||||
{ value: ".testclass", class: SELECTOR_ELEMENT },
|
||||
{ value: ":active", class: SELECTOR_PSEUDO_CLASS_LOCK }
|
||||
]
|
||||
},
|
||||
{
|
||||
node: ".testclass",
|
||||
pseudoClass: ":focus",
|
||||
expected: [
|
||||
{ value: ".testclass", class: SELECTOR_ELEMENT },
|
||||
{ value: ":focus", class: SELECTOR_PSEUDO_CLASS_LOCK }
|
||||
]
|
||||
},
|
||||
{
|
||||
node: ".testclass",
|
||||
pseudoClass: ":hover",
|
||||
expected: [
|
||||
{ value: ".testclass", class: SELECTOR_ELEMENT },
|
||||
{ value: ":hover", class: SELECTOR_PSEUDO_CLASS_LOCK }
|
||||
]
|
||||
},
|
||||
];
|
||||
|
||||
add_task(function*() {
|
||||
yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
|
||||
let {inspector, view} = yield openRuleView();
|
||||
|
||||
for (let {node, pseudoClass, expected} of TEST_DATA) {
|
||||
yield selectNode(node, inspector);
|
||||
|
||||
if (pseudoClass) {
|
||||
let onRefresh = inspector.once("rule-view-refreshed");
|
||||
inspector.togglePseudoClass(pseudoClass);
|
||||
yield onRefresh;
|
||||
}
|
||||
|
||||
let selectorContainer =
|
||||
getRuleViewRuleEditor(view, 1).selectorText.firstChild;
|
||||
|
||||
if (selectorContainer.children.length === expected.length) {
|
||||
for (let i = 0; i < expected.length; i++) {
|
||||
is(expected[i].value, selectorContainer.children[i].textContent,
|
||||
"Got expected selector value: " + expected[i].value + " == " +
|
||||
selectorContainer.children[i].textContent);
|
||||
is(expected[i].class, selectorContainer.children[i].className,
|
||||
"Got expected class name: " + expected[i].class + " == " +
|
||||
selectorContainer.children[i].className);
|
||||
}
|
||||
} else {
|
||||
for (let selector of selectorContainer.children) {
|
||||
info("Actual selector components: { value: " + selector.textContent +
|
||||
", class: " + selector.className + " }\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
@ -645,7 +645,8 @@ function synthesizeKeys(input, win) {
|
||||
function getRuleViewRule(view, selectorText) {
|
||||
let rule;
|
||||
for (let r of view.doc.querySelectorAll(".ruleview-rule")) {
|
||||
let selector = r.querySelector(".ruleview-selector, .ruleview-selector-matched");
|
||||
let selector = r.querySelector(".ruleview-selectorcontainer, " +
|
||||
".ruleview-selector-matched");
|
||||
if (selector && selector.textContent === selectorText) {
|
||||
rule = r;
|
||||
break;
|
||||
|
@ -0,0 +1,213 @@
|
||||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set 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 Cu = Components.utils;
|
||||
Cu.import("resource://gre/modules/devtools/Loader.jsm");
|
||||
const {
|
||||
parsePseudoClassesAndAttributes,
|
||||
SELECTOR_ATTRIBUTE,
|
||||
SELECTOR_ELEMENT,
|
||||
SELECTOR_PSEUDO_CLASS
|
||||
} = devtools.require("devtools/styleinspector/css-parsing-utils");
|
||||
|
||||
const TEST_DATA = [
|
||||
// Test that a null input throws an exception
|
||||
{
|
||||
input: null,
|
||||
throws: true
|
||||
},
|
||||
// Test that a undefined input throws an exception
|
||||
{
|
||||
input: undefined,
|
||||
throws: true
|
||||
},
|
||||
{
|
||||
input: ":root",
|
||||
expected: [
|
||||
{ value: ":root", type: SELECTOR_PSEUDO_CLASS }
|
||||
]
|
||||
},
|
||||
{
|
||||
input: ".testclass",
|
||||
expected: [
|
||||
{ value: ".testclass", type: SELECTOR_ELEMENT }
|
||||
]
|
||||
},
|
||||
{
|
||||
input: "div p",
|
||||
expected: [
|
||||
{ value: "div p", type: SELECTOR_ELEMENT }
|
||||
]
|
||||
},
|
||||
{
|
||||
input: "div > p",
|
||||
expected: [
|
||||
{ value: "div > p", type: SELECTOR_ELEMENT }
|
||||
]
|
||||
},
|
||||
{
|
||||
input: "a[hidden]",
|
||||
expected: [
|
||||
{ value: "a", type: SELECTOR_ELEMENT },
|
||||
{ value: "[hidden]", type: SELECTOR_ATTRIBUTE }
|
||||
]
|
||||
},
|
||||
{
|
||||
input: "a[hidden=true]",
|
||||
expected: [
|
||||
{ value: "a", type: SELECTOR_ELEMENT },
|
||||
{ value: "[hidden=true]", type: SELECTOR_ATTRIBUTE }
|
||||
]
|
||||
},
|
||||
{
|
||||
input: "a[hidden=true] p:hover",
|
||||
expected: [
|
||||
{ value: "a", type: SELECTOR_ELEMENT },
|
||||
{ value: "[hidden=true]", type: SELECTOR_ATTRIBUTE },
|
||||
{ value: " p", type: SELECTOR_ELEMENT },
|
||||
{ value: ":hover", type: SELECTOR_PSEUDO_CLASS }
|
||||
]
|
||||
},
|
||||
{
|
||||
input: "a[checked=\"true\"]",
|
||||
expected: [
|
||||
{ value: "a", type: SELECTOR_ELEMENT },
|
||||
{ value: "[checked=\"true\"]", type: SELECTOR_ATTRIBUTE }
|
||||
]
|
||||
},
|
||||
{
|
||||
input: "a[title~=test]",
|
||||
expected: [
|
||||
{ value: "a", type: SELECTOR_ELEMENT },
|
||||
{ value: "[title~=test]", type: SELECTOR_ATTRIBUTE }
|
||||
]
|
||||
},
|
||||
{
|
||||
input: "h1[hidden=\"true\"][title^=\"Important\"]",
|
||||
expected: [
|
||||
{ value: "h1", type: SELECTOR_ELEMENT },
|
||||
{ value: "[hidden=\"true\"]", type: SELECTOR_ATTRIBUTE },
|
||||
{ value: "[title^=\"Important\"]", type: SELECTOR_ATTRIBUTE}
|
||||
]
|
||||
},
|
||||
{
|
||||
input: "p:hover",
|
||||
expected: [
|
||||
{ value: "p", type: SELECTOR_ELEMENT },
|
||||
{ value: ":hover", type: SELECTOR_PSEUDO_CLASS }
|
||||
]
|
||||
},
|
||||
{
|
||||
input: "p + .testclass:hover",
|
||||
expected: [
|
||||
{ value: "p + .testclass", type: SELECTOR_ELEMENT },
|
||||
{ value: ":hover", type: SELECTOR_PSEUDO_CLASS }
|
||||
]
|
||||
},
|
||||
{
|
||||
input: "p::before",
|
||||
expected: [
|
||||
{ value: "p", type: SELECTOR_ELEMENT },
|
||||
{ value: "::before", type: SELECTOR_PSEUDO_CLASS }
|
||||
]
|
||||
},
|
||||
{
|
||||
input: "p:nth-child(2)",
|
||||
expected: [
|
||||
{ value: "p", type: SELECTOR_ELEMENT },
|
||||
{ value: ":nth-child(2)", type: SELECTOR_PSEUDO_CLASS }
|
||||
]
|
||||
},
|
||||
{
|
||||
input: "p:not([title=\"test\"]) .testclass",
|
||||
expected: [
|
||||
{ value: "p", type: SELECTOR_ELEMENT },
|
||||
{ value: ":not([title=\"test\"])", type: SELECTOR_PSEUDO_CLASS },
|
||||
{ value: " .testclass", type: SELECTOR_ELEMENT }
|
||||
]
|
||||
},
|
||||
{
|
||||
input: "a\\:hover",
|
||||
expected: [
|
||||
{ value: "a\\:hover", type: SELECTOR_ELEMENT }
|
||||
]
|
||||
},
|
||||
{
|
||||
input: ":not(:lang(it))",
|
||||
expected: [
|
||||
{ value: ":not(:lang(it))", type: SELECTOR_PSEUDO_CLASS }
|
||||
]
|
||||
},
|
||||
{
|
||||
input: "p:not(:lang(it))",
|
||||
expected: [
|
||||
{ value: "p", type: SELECTOR_ELEMENT },
|
||||
{ value: ":not(:lang(it))", type: SELECTOR_PSEUDO_CLASS }
|
||||
]
|
||||
},
|
||||
{
|
||||
input: "p:not(p:lang(it))",
|
||||
expected: [
|
||||
{ value: "p", type: SELECTOR_ELEMENT },
|
||||
{ value: ":not(p:lang(it))", type: SELECTOR_PSEUDO_CLASS }
|
||||
]
|
||||
},
|
||||
{
|
||||
input: ":not(:lang(it)",
|
||||
expected: [
|
||||
{ value: ":not(:lang(it)", type: SELECTOR_ELEMENT }
|
||||
]
|
||||
},
|
||||
{
|
||||
input: ":not(:lang(it)))",
|
||||
expected: [
|
||||
{ value: ":not(:lang(it))", type: SELECTOR_PSEUDO_CLASS },
|
||||
{ value: ")", type: SELECTOR_ELEMENT }
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
function run_test() {
|
||||
for (let test of TEST_DATA) {
|
||||
dump("Test input string " + test.input + "\n");
|
||||
let output;
|
||||
|
||||
try {
|
||||
output = parsePseudoClassesAndAttributes(test.input);
|
||||
} catch (e) {
|
||||
dump("parsePseudoClassesAndAttributes threw an exception with the " +
|
||||
"given input string\n");
|
||||
if (test.throws) {
|
||||
ok(true, "Exception expected");
|
||||
} else {
|
||||
dump();
|
||||
ok(false, "Exception unexpected\n" + e);
|
||||
}
|
||||
}
|
||||
|
||||
if (output) {
|
||||
assertOutput(output, test.expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function assertOutput(actual, expected) {
|
||||
if (actual.length === expected.length) {
|
||||
for (let i = 0; i < expected.length; i++) {
|
||||
dump("Check that the output item has the expected value and type\n");
|
||||
ok(!!actual[i]);
|
||||
equal(expected[i].value, actual[i].value);
|
||||
equal(expected[i].type, actual[i].type);
|
||||
}
|
||||
} else {
|
||||
for (let prop of actual) {
|
||||
dump("Actual output contained: {value: " + prop.value + ", type: " +
|
||||
prop.type + "}\n");
|
||||
}
|
||||
equal(actual.length, expected.length);
|
||||
}
|
||||
}
|
@ -6,4 +6,5 @@ firefox-appdir = browser
|
||||
skip-if = toolkit == 'android' || toolkit == 'gonk'
|
||||
|
||||
[test_parseDeclarations.js]
|
||||
[test_parsePseudoClassesAndAttributes.js]
|
||||
[test_parseSingleValue.js]
|
||||
|
@ -62,16 +62,6 @@
|
||||
- the button that clears the collected tracing data in the tracing tab. -->
|
||||
<!ENTITY debuggerUI.clearButton.tooltip "Clear the collected traces">
|
||||
|
||||
<!-- LOCALIZATION NOTE (debuggerUI.pauseExceptions): This is the label for the
|
||||
- checkbox that toggles pausing on exceptions. -->
|
||||
<!ENTITY debuggerUI.pauseExceptions "Pause on Exceptions">
|
||||
<!ENTITY debuggerUI.pauseExceptions.accesskey "E">
|
||||
|
||||
<!-- LOCALIZATION NOTE (debuggerUI.ignoreCaughtExceptions): This is the label for the
|
||||
- checkbox that toggles ignoring caught exceptions. -->
|
||||
<!ENTITY debuggerUI.ignoreCaughtExceptions "Ignore Caught Exceptions">
|
||||
<!ENTITY debuggerUI.ignoreCaughtExceptions.accesskey "C">
|
||||
|
||||
<!-- LOCALIZATION NOTE (debuggerUI.showPanesOnInit): This is the label for the
|
||||
- checkbox that toggles visibility of panes when opening the debugger. -->
|
||||
<!ENTITY debuggerUI.showPanesOnInit "Show Panes on Startup">
|
||||
|
@ -156,6 +156,21 @@ otherEvents=Other
|
||||
# source.
|
||||
blackBoxCheckboxTooltip=Toggle black boxing
|
||||
|
||||
# LOCALIZATION NOTE (pauseAllExceptionsTooltip): The tooltip text to display when
|
||||
# the user hovers over the pause on exceptions button in the "pause on all
|
||||
# exceptions" state.
|
||||
pauseAllExceptionsTooltip=Pause on all exceptions
|
||||
|
||||
# LOCALIZATION NOTE (pauseUncaughtExceptionsTooltip): The tooltip text to display when
|
||||
# the user hovers over the pause on exceptions button in the "pause on uncaught
|
||||
# exceptions" state.
|
||||
pauseUncaughtExceptionsTooltip=Pause on uncaught exceptions
|
||||
|
||||
# LOCALIZATION NOTE (pauseNoExceptionsTooltip): The tooltip text to display when
|
||||
# the user hovers over the pause on exceptions button in the "pause on no
|
||||
# exceptions" state.
|
||||
pauseNoExceptionsTooltip=Do not pause on exceptions
|
||||
|
||||
# LOCALIZATION NOTE (noMatchingStringsText): The text to display in the
|
||||
# global search results when there are no matching strings after filtering.
|
||||
noMatchingStringsText=No matches found
|
||||
|
@ -10,7 +10,7 @@
|
||||
<!ENTITY trackingProtection.accesskey "m">
|
||||
<!ENTITY trackingProtectionLearnMore.label "Learn more">
|
||||
<!ENTITY trackingProtectionPBM.label "Prevent sites from tracking my online activity in Private Windows">
|
||||
<!ENTITY trackingProtectionPBM.accesskey "m">
|
||||
<!ENTITY trackingProtectionPBM.accesskey "y">
|
||||
<!ENTITY trackingProtectionPBMLearnMore.label "Learn more">
|
||||
<!ENTITY doNotTrackInfo.label "Learn More">
|
||||
|
||||
|
@ -51,6 +51,8 @@ browser.jar:
|
||||
skin/classic/browser/identity-mixed-active-blocked.svg (../shared/identity-block/identity-mixed-active-blocked.svg)
|
||||
skin/classic/browser/identity-mixed-passive-loaded.svg (../shared/identity-block/identity-mixed-passive-loaded.svg)
|
||||
skin/classic/browser/identity-mixed-active-loaded.svg (../shared/identity-block/identity-mixed-active-loaded.svg)
|
||||
skin/classic/browser/tracking-protection-16.svg (../shared/identity-block/tracking-protection-16.svg)
|
||||
skin/classic/browser/tracking-protection-disabled-16.svg (../shared/identity-block/tracking-protection-disabled-16.svg)
|
||||
skin/classic/browser/Info.png
|
||||
skin/classic/browser/magnifier.png (../shared/magnifier.png)
|
||||
skin/classic/browser/magnifier@2x.png (../shared/magnifier@2x.png)
|
||||
@ -370,6 +372,12 @@ browser.jar:
|
||||
skin/classic/browser/devtools/debugger-blackbox@2x.png (../shared/devtools/images/debugger-blackbox@2x.png)
|
||||
skin/classic/browser/devtools/debugger-prettyprint.png (../shared/devtools/images/debugger-prettyprint.png)
|
||||
skin/classic/browser/devtools/debugger-prettyprint@2x.png (../shared/devtools/images/debugger-prettyprint@2x.png)
|
||||
skin/classic/browser/devtools/debugger-no-pause-exceptions.png (../shared/devtools/images/debugger-no-pause-exceptions.png)
|
||||
skin/classic/browser/devtools/debugger-no-pause-exceptions@2x.png (../shared/devtools/images/debugger-no-pause-exceptions@2x.png)
|
||||
skin/classic/browser/devtools/debugger-pause-all-exceptions.png (../shared/devtools/images/debugger-pause-all-exceptions.png)
|
||||
skin/classic/browser/devtools/debugger-pause-all-exceptions@2x.png (../shared/devtools/images/debugger-pause-all-exceptions@2x.png)
|
||||
skin/classic/browser/devtools/debugger-pause-uncaught-exceptions.png (../shared/devtools/images/debugger-pause-uncaught-exceptions.png)
|
||||
skin/classic/browser/devtools/debugger-pause-uncaught-exceptions@2x.png (../shared/devtools/images/debugger-pause-uncaught-exceptions@2x.png)
|
||||
skin/classic/browser/devtools/debugger-toggleBreakpoints.png (../shared/devtools/images/debugger-toggleBreakpoints.png)
|
||||
skin/classic/browser/devtools/debugger-toggleBreakpoints@2x.png (../shared/devtools/images/debugger-toggleBreakpoints@2x.png)
|
||||
skin/classic/browser/devtools/tracer-icon.png (../shared/devtools/images/tracer-icon.png)
|
||||
|
@ -1660,7 +1660,7 @@ toolbarbutton[constrain-size="true"][cui-areatype="toolbar"] > .toolbarbutton-ba
|
||||
}
|
||||
|
||||
@media (-moz-mac-yosemite-theme) {
|
||||
#urlbar:not([focused="true"]) > #identity-box {
|
||||
#urlbar:not([focused="true"]):not(:-moz-window-inactive) > #identity-box {
|
||||
margin: -2px 0;
|
||||
-moz-margin-end: 3px;
|
||||
padding: 3px 4px;
|
||||
|
@ -55,6 +55,8 @@ browser.jar:
|
||||
skin/classic/browser/identity-mixed-active-blocked.svg (../shared/identity-block/identity-mixed-active-blocked.svg)
|
||||
skin/classic/browser/identity-mixed-passive-loaded.svg (../shared/identity-block/identity-mixed-passive-loaded.svg)
|
||||
skin/classic/browser/identity-mixed-active-loaded.svg (../shared/identity-block/identity-mixed-active-loaded.svg)
|
||||
skin/classic/browser/tracking-protection-16.svg (../shared/identity-block/tracking-protection-16.svg)
|
||||
skin/classic/browser/tracking-protection-disabled-16.svg (../shared/identity-block/tracking-protection-disabled-16.svg)
|
||||
skin/classic/browser/Info.png
|
||||
skin/classic/browser/keyhole-circle.png
|
||||
skin/classic/browser/keyhole-circle@2x.png
|
||||
@ -472,6 +474,12 @@ browser.jar:
|
||||
skin/classic/browser/devtools/debugger-blackbox@2x.png (../shared/devtools/images/debugger-blackbox@2x.png)
|
||||
skin/classic/browser/devtools/debugger-prettyprint.png (../shared/devtools/images/debugger-prettyprint.png)
|
||||
skin/classic/browser/devtools/debugger-prettyprint@2x.png (../shared/devtools/images/debugger-prettyprint@2x.png)
|
||||
skin/classic/browser/devtools/debugger-no-pause-exceptions.png (../shared/devtools/images/debugger-no-pause-exceptions.png)
|
||||
skin/classic/browser/devtools/debugger-no-pause-exceptions@2x.png (../shared/devtools/images/debugger-no-pause-exceptions@2x.png)
|
||||
skin/classic/browser/devtools/debugger-pause-all-exceptions.png (../shared/devtools/images/debugger-pause-all-exceptions.png)
|
||||
skin/classic/browser/devtools/debugger-pause-all-exceptions@2x.png (../shared/devtools/images/debugger-pause-all-exceptions@2x.png)
|
||||
skin/classic/browser/devtools/debugger-pause-uncaught-exceptions.png (../shared/devtools/images/debugger-pause-uncaught-exceptions.png)
|
||||
skin/classic/browser/devtools/debugger-pause-uncaught-exceptions@2x.png (../shared/devtools/images/debugger-pause-uncaught-exceptions@2x.png)
|
||||
skin/classic/browser/devtools/debugger-toggleBreakpoints.png (../shared/devtools/images/debugger-toggleBreakpoints.png)
|
||||
skin/classic/browser/devtools/debugger-toggleBreakpoints@2x.png (../shared/devtools/images/debugger-toggleBreakpoints@2x.png)
|
||||
skin/classic/browser/devtools/tracer-icon.png (../shared/devtools/images/tracer-icon.png)
|
||||
|
@ -1,14 +1,24 @@
|
||||
/* Show the organization name only for EV certs. */
|
||||
#identity-popup-securityView:not(.verifiedIdentity) > #identity-popup-content-owner,
|
||||
#identity-popup-securityView:not(.verifiedIdentity) > #identity-popup-securityView-connection,
|
||||
/* Show the "Verified by" label only for DV and EV certs. */
|
||||
#identity-popup-securityView:not(.verifiedIdentity):not(.verifiedDomain) > #identity-popup-content-verifier,
|
||||
/* Show a longer explanation for non-secure sites, mixed content, and weak
|
||||
connection security. Show the organization address for EV certs. */
|
||||
#identity-popup-securityView:not(.unknownIdentity):not(.verifiedIdentity):not(.mixedContent):not(.weakCipher) > #identity-popup-content-supplemental,
|
||||
/* Show the "Connection is secure" labels only for EV and DV certs. */
|
||||
#identity-popup-security-content:not(.verifiedIdentity):not(.verifiedDomain) > .identity-popup-connection-secure,
|
||||
#identity-popup-securityView:not(.verifiedIdentity):not(.verifiedDomain) > #identity-popup-securityView-header > .identity-popup-connection-secure,
|
||||
#identity-popup-securityView:not(.unknownIdentity) > #identity-popup-securityView-header > .identity-popup-connection-not-secure,
|
||||
#identity-popup-securityView:not(.chromeUI) > #identity-popup-securityView-header > .identity-popup-connection-internal,
|
||||
/* Show the "Connection is not secure" labels only for non-secure sites. */
|
||||
#identity-popup-security-content:not(.unknownIdentity) > .identity-popup-connection-not-secure,
|
||||
#identity-popup-securityView:not(.verifiedIdentity) > #identity-popup-securityView-connection,
|
||||
#identity-popup-securityView:not(.unknownIdentity) > #identity-popup-securityView-header > .identity-popup-connection-not-secure,
|
||||
/* Show "This is a secure internal page" only for whitelisted pages. */
|
||||
#identity-popup-securityView:not(.chromeUI) > #identity-popup-securityView-header > .identity-popup-connection-internal,
|
||||
#identity-popup-security-content:not(.chromeUI) > .identity-popup-connection-internal,
|
||||
#identity-popup-security-content.chromeUI + .identity-popup-expander {
|
||||
/* Hide the subsection arrow for whitelisted chromeUI pages. */
|
||||
#identity-popup-security-content.chromeUI + .identity-popup-expander,
|
||||
/* Hide the tracking protection section for whitelisted chromeUI pages. */
|
||||
#identity-popup-mainView.chromeUI > #tracking-protection-container {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@ -243,7 +253,7 @@
|
||||
background-image: url("chrome://browser/skin/controlcenter/tracking-protection.svg");
|
||||
}
|
||||
|
||||
#tracking-protection-content[block-disabled] {
|
||||
#tracking-protection-content[state="loaded-tracking-content"] {
|
||||
background-image: url("chrome://browser/skin/controlcenter/tracking-protection-disabled.svg");
|
||||
}
|
||||
|
||||
@ -252,13 +262,12 @@
|
||||
margin: 1em 0 0;
|
||||
}
|
||||
|
||||
#tracking-protection-content[block-active] > #tracking-not-detected,
|
||||
#tracking-protection-content[block-disabled] > #tracking-not-detected,
|
||||
#tracking-protection-content:not([block-active]) > #tracking-blocked,
|
||||
#tracking-protection-content:not([block-active]) #tracking-action-unblock,
|
||||
#tracking-protection-content:not([block-disabled]) > #tracking-loaded,
|
||||
#tracking-protection-content:not([block-disabled]) #tracking-action-block,
|
||||
#tracking-protection-content:not([block-active]):not([block-disabled]) > #tracking-actions {
|
||||
#tracking-protection-content[state] > #tracking-not-detected,
|
||||
#tracking-protection-content:not([state="blocked-tracking-content"]) > #tracking-blocked,
|
||||
#tracking-protection-content:not([state="blocked-tracking-content"]) #tracking-action-unblock,
|
||||
#tracking-protection-content:not([state="loaded-tracking-content"]) > #tracking-loaded,
|
||||
#tracking-protection-content:not([state="loaded-tracking-content"]) #tracking-action-block,
|
||||
#tracking-protection-content:not([state]) > #tracking-actions {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
@ -92,6 +92,36 @@
|
||||
}
|
||||
}
|
||||
|
||||
#toggle-pause-exceptions[state="0"] {
|
||||
list-style-image: url(debugger-no-pause-exceptions.png);
|
||||
}
|
||||
|
||||
@media (min-resolution: 1.25dppx) {
|
||||
#toggle-pause-exceptions[state="0"] {
|
||||
list-style-image: url(debugger-no-pause-exceptions@2x.png);
|
||||
}
|
||||
}
|
||||
|
||||
#toggle-pause-exceptions[state="1"] {
|
||||
list-style-image: url(debugger-pause-all-exceptions.png);
|
||||
}
|
||||
|
||||
@media (min-resolution: 1.25dppx) {
|
||||
#toggle-pause-exceptions[state="1"] {
|
||||
list-style-image: url(debugger-pause-all-exceptions@2x.png);
|
||||
}
|
||||
}
|
||||
|
||||
#toggle-pause-exceptions[state="2"] {
|
||||
list-style-image: url(debugger-pause-uncaught-exceptions.png);
|
||||
}
|
||||
|
||||
@media (min-resolution: 1.25dppx) {
|
||||
#toggle-pause-exceptions[state="2"] {
|
||||
list-style-image: url(debugger-pause-uncaught-exceptions@2x.png);
|
||||
}
|
||||
}
|
||||
|
||||
#sources-toolbar .devtools-toolbarbutton:not([label]) {
|
||||
-moz-image-region: rect(0px,16px,16px,0px);
|
||||
}
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 4.1 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 4.1 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 4.1 KiB |
@ -245,14 +245,28 @@
|
||||
border-bottom-color: hsl(0,0%,50%);
|
||||
}
|
||||
|
||||
.ruleview-selector {
|
||||
.ruleview-selectorcontainer {
|
||||
word-wrap: break-word;
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
.ruleview-selector-separator, .ruleview-selector-unmatched {
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.ruleview-selector-matched > .ruleview-selector-attribute {
|
||||
/* TODO: Bug 1178535 Awaiting UX feedback on highlight colors */
|
||||
}
|
||||
|
||||
.ruleview-selector-matched > .ruleview-selector-pseudo-class {
|
||||
/* TODO: Bug 1178535 Awaiting UX feedback on highlight colors */
|
||||
}
|
||||
|
||||
.ruleview-selector-matched > .ruleview-selector-pseudo-class-lock {
|
||||
font-weight: bold;
|
||||
color: var(--theme-highlight-orange);
|
||||
}
|
||||
|
||||
.ruleview-selectorhighlighter {
|
||||
background: url("chrome://browser/skin/devtools/vview-open-inspector.png") no-repeat 0 0;
|
||||
padding-left: 16px;
|
||||
|
@ -63,7 +63,23 @@
|
||||
background-image: var(--identity-box-chrome-background-image);
|
||||
}
|
||||
|
||||
/* page proxy icon */
|
||||
/* TRACKING PROTECTION ICON */
|
||||
|
||||
#tracking-protection-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
list-style-image: url(chrome://browser/skin/tracking-protection-16.svg);
|
||||
}
|
||||
|
||||
#tracking-protection-icon[state="loaded-tracking-content"] {
|
||||
list-style-image: url(chrome://browser/skin/tracking-protection-disabled-16.svg);
|
||||
}
|
||||
|
||||
#tracking-protection-icon:not([state]) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* MAIN IDENTITY ICON */
|
||||
|
||||
#page-proxy-favicon {
|
||||
width: 16px;
|
||||
|
21
browser/themes/shared/identity-block/tracking-protection-16.svg
Executable file
@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
width="16" height="16" viewBox="0 0 16 16">
|
||||
<defs>
|
||||
<path id="shape-shield-outer" d="M8,1L2.8,1.9C2.4,1.9,2,2.4,2,2.8C2,4,2,6.1,2.1,7.1c0.3,2.7,0.8,4,1.9,5.6C5.6,14.7,8,15,8,15s2.4-0.3,4-2.4 c1.2-1.5,1.7-2.9,1.9-5.6C14,6.1,14,4,14,2.8c0-0.5-0.4-0.9-0.8-1L8,1L8,1z"/>
|
||||
<path id="shape-shield-inner" d="M8,2l5,0.8c0,2,0,3.5-0.1,4.1c-0.3,2.7-0.8,3.8-1.7,5.1c-1.1,1.5-2.7,1.9-3.2,2c-0.4-0.1-2.1-0.5-3.2-2 c-1-1.3-1.5-2.4-1.7-5.1C3,6.3,3,4.8,3,2.8L8,2"/>
|
||||
<path id="shape-shield-detail" d="M8,13c-0.5-0.1-1.6-0.5-2.4-1.5c-0.9-1.2-1.3-2.1-1.5-4.6C4,6.3,4,5.2,4,3.7L8,3 V13z"/>
|
||||
|
||||
<mask id="mask-shield-cutout">
|
||||
<rect width="16" height="16" fill="#000" />
|
||||
<use xlink:href="#shape-shield-outer" fill="#fff" />
|
||||
<use xlink:href="#shape-shield-inner" fill="#000" />
|
||||
<use xlink:href="#shape-shield-detail" fill="#fff" />
|
||||
</mask>
|
||||
</defs>
|
||||
|
||||
<use xlink:href="#shape-shield-outer" mask="url(#mask-shield-cutout)" fill="#808080" />
|
||||
</svg>
|
After Width: | Height: | Size: 1.3 KiB |
23
browser/themes/shared/identity-block/tracking-protection-disabled-16.svg
Executable file
@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
width="16" height="16" viewBox="0 0 16 16">
|
||||
<defs>
|
||||
<path id="shape-shield-outer" d="M8,1L2.8,1.9C2.4,1.9,2,2.4,2,2.8C2,4,2,6.1,2.1,7.1c0.3,2.7,0.8,4,1.9,5.6C5.6,14.7,8,15,8,15s2.4-0.3,4-2.4 c1.2-1.5,1.7-2.9,1.9-5.6C14,6.1,14,4,14,2.8c0-0.5-0.4-0.9-0.8-1L8,1L8,1z"/>
|
||||
<path id="shape-shield-inner" d="M8,2l5,0.8c0,2,0,3.5-0.1,4.1c-0.3,2.7-0.8,3.8-1.7,5.1c-1.1,1.5-2.7,1.9-3.2,2c-0.4-0.1-2.1-0.5-3.2-2 c-1-1.3-1.5-2.4-1.7-5.1C3,6.3,3,4.8,3,2.8L8,2"/>
|
||||
<path id="shape-shield-detail" d="M8,13c-0.5-0.1-1.6-0.5-2.4-1.5c-0.9-1.2-1.3-2.1-1.5-4.6C4,6.3,4,5.2,4,3.7L8,3 V13z"/>
|
||||
|
||||
<mask id="mask-shield-cutout">
|
||||
<rect width="16" height="16" fill="#000" />
|
||||
<use xlink:href="#shape-shield-outer" fill="#fff" />
|
||||
<use xlink:href="#shape-shield-inner" fill="#000" />
|
||||
<use xlink:href="#shape-shield-detail" fill="#fff" />
|
||||
<line x1="3" y1="15" x2="15" y2="3" stroke="#000" stroke-width="2" />
|
||||
</mask>
|
||||
</defs>
|
||||
|
||||
<use xlink:href="#shape-shield-outer" mask="url(#mask-shield-cutout)" fill="#808080" />
|
||||
<line x1="3" y1="14" x2="15" y2="2" stroke="#d92d21" stroke-width="2" />
|
||||
</svg>
|
After Width: | Height: | Size: 1.4 KiB |
@ -400,3 +400,7 @@ description > html|a {
|
||||
background-color: transparent;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
#tosPP-small-ToS {
|
||||
margin-right: 3em;
|
||||
}
|
||||
|
@ -52,6 +52,8 @@ browser.jar:
|
||||
skin/classic/browser/identity-mixed-active-blocked.svg (../shared/identity-block/identity-mixed-active-blocked.svg)
|
||||
skin/classic/browser/identity-mixed-passive-loaded.svg (../shared/identity-block/identity-mixed-passive-loaded.svg)
|
||||
skin/classic/browser/identity-mixed-active-loaded.svg (../shared/identity-block/identity-mixed-active-loaded.svg)
|
||||
skin/classic/browser/tracking-protection-16.svg (../shared/identity-block/tracking-protection-16.svg)
|
||||
skin/classic/browser/tracking-protection-disabled-16.svg (../shared/identity-block/tracking-protection-disabled-16.svg)
|
||||
skin/classic/browser/keyhole-forward-mask.svg
|
||||
skin/classic/browser/KUI-background.png
|
||||
skin/classic/browser/livemark-folder.png
|
||||
@ -466,6 +468,12 @@ browser.jar:
|
||||
skin/classic/browser/devtools/debugger-blackbox@2x.png (../shared/devtools/images/debugger-blackbox@2x.png)
|
||||
skin/classic/browser/devtools/debugger-prettyprint.png (../shared/devtools/images/debugger-prettyprint.png)
|
||||
skin/classic/browser/devtools/debugger-prettyprint@2x.png (../shared/devtools/images/debugger-prettyprint@2x.png)
|
||||
skin/classic/browser/devtools/debugger-no-pause-exceptions.png (../shared/devtools/images/debugger-no-pause-exceptions.png)
|
||||
skin/classic/browser/devtools/debugger-no-pause-exceptions@2x.png (../shared/devtools/images/debugger-no-pause-exceptions@2x.png)
|
||||
skin/classic/browser/devtools/debugger-pause-all-exceptions.png (../shared/devtools/images/debugger-pause-all-exceptions.png)
|
||||
skin/classic/browser/devtools/debugger-pause-all-exceptions@2x.png (../shared/devtools/images/debugger-pause-all-exceptions@2x.png)
|
||||
skin/classic/browser/devtools/debugger-pause-uncaught-exceptions.png (../shared/devtools/images/debugger-pause-uncaught-exceptions.png)
|
||||
skin/classic/browser/devtools/debugger-pause-uncaught-exceptions@2x.png (../shared/devtools/images/debugger-pause-uncaught-exceptions@2x.png)
|
||||
skin/classic/browser/devtools/debugger-toggleBreakpoints.png (../shared/devtools/images/debugger-toggleBreakpoints.png)
|
||||
skin/classic/browser/devtools/debugger-toggleBreakpoints@2x.png (../shared/devtools/images/debugger-toggleBreakpoints@2x.png)
|
||||
skin/classic/browser/devtools/tracer-icon.png (../shared/devtools/images/tracer-icon.png)
|
||||
|
@ -2646,6 +2646,7 @@ public class BrowserApp extends GeckoApp
|
||||
// FormAssistPopup.onMetricsChanged, which queues a runnable that undoes the effect of hide.
|
||||
// With hide first, onMetricsChanged will return early instead.
|
||||
mFormAssistPopup.hide();
|
||||
mFindInPageBar.hide();
|
||||
|
||||
// Refresh toolbar height to possibly restore the toolbar padding
|
||||
refreshToolbarHeight();
|
||||
@ -2800,9 +2801,6 @@ public class BrowserApp extends GeckoApp
|
||||
// We do this here because there are glitches when unlocking a device with
|
||||
// BrowserSearch in the foreground if we use BrowserSearch.onStart/Stop.
|
||||
getActivity().getWindow().setBackgroundDrawableResource(android.R.color.white);
|
||||
|
||||
// Hide potentially visible "find in page" bar (bug 1175434).
|
||||
mFindInPageBar.hide();
|
||||
}
|
||||
|
||||
private void hideBrowserSearch() {
|
||||
|
@ -225,13 +225,15 @@ public class ZoomedView extends FrameLayout implements LayerView.OnMetricsChange
|
||||
};
|
||||
touchListener = new ZoomedViewTouchListener();
|
||||
EventDispatcher.getInstance().registerGeckoThreadListener(this,
|
||||
"Gesture:clusteredLinksClicked", "Window:Resize", "Content:LocationChange");
|
||||
"Gesture:clusteredLinksClicked", "Window:Resize", "Content:LocationChange",
|
||||
"Gesture:CloseZoomedView");
|
||||
}
|
||||
|
||||
void destroy() {
|
||||
ThreadUtils.removeCallbacksFromUiThread(requestRenderRunnable);
|
||||
EventDispatcher.getInstance().unregisterGeckoThreadListener(this,
|
||||
"Gesture:clusteredLinksClicked", "Window:Resize", "Content:LocationChange");
|
||||
"Gesture:clusteredLinksClicked", "Window:Resize", "Content:LocationChange",
|
||||
"Gesture:CloseZoomedView");
|
||||
}
|
||||
|
||||
// This method (onFinishInflate) is called only when the zoomed view class is used inside
|
||||
@ -548,6 +550,8 @@ public class ZoomedView extends FrameLayout implements LayerView.OnMetricsChange
|
||||
refreshZoomedViewSize(metrics);
|
||||
} else if (event.equals("Content:LocationChange")) {
|
||||
stopZoomDisplay(false);
|
||||
} else if (event.equals("Gesture:CloseZoomedView")) {
|
||||
stopZoomDisplay(true);
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
Log.e(LOGTAG, "JSON exception", e);
|
||||
|
@ -8,9 +8,13 @@ package org.mozilla.gecko.util;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.speech.RecognizerIntent;
|
||||
import org.mozilla.gecko.AppConstants.Versions;
|
||||
|
||||
public class InputOptionsUtils {
|
||||
public static boolean supportsVoiceRecognizer(Context context, String prompt) {
|
||||
if (Versions.preICS) {
|
||||
return false;
|
||||
}
|
||||
final Intent intent = createVoiceRecognizerIntent(prompt);
|
||||
return intent.resolveActivity(context.getPackageManager()) != null;
|
||||
}
|
||||
|
@ -5314,6 +5314,9 @@ var BrowserEventHandler = {
|
||||
if (this._inCluster && this._clickInZoomedView != true) {
|
||||
this._clusterClicked(x, y);
|
||||
} else {
|
||||
if (this._clickInZoomedView != true) {
|
||||
this._closeZoomedView();
|
||||
}
|
||||
// The _highlightElement was chosen after fluffing the touch events
|
||||
// that led to this SingleTap, so by fluffing the mouse events, they
|
||||
// should find the same target since we fluff them again below.
|
||||
@ -5344,6 +5347,12 @@ var BrowserEventHandler = {
|
||||
}
|
||||
},
|
||||
|
||||
_closeZoomedView: function() {
|
||||
Messaging.sendRequest({
|
||||
type: "Gesture:CloseZoomedView"
|
||||
});
|
||||
},
|
||||
|
||||
_clusterClicked: function(aX, aY) {
|
||||
Messaging.sendRequest({
|
||||
type: "Gesture:clusteredLinksClicked",
|
||||
|
@ -10,7 +10,7 @@ import android.content.res.Resources;
|
||||
import org.mozilla.gecko.R;
|
||||
|
||||
public class StringHelper {
|
||||
private static StringHelper instance = null;
|
||||
private static StringHelper instance;
|
||||
|
||||
// This needs to be accessed statically, before an instance of StringHelper can be created.
|
||||
public static String STATIC_ABOUT_HOME_URL = "about:home";
|
||||
|
@ -13,6 +13,11 @@ Sending only happens on official builds with ``MOZ_TELEMETRY_REPORTING`` defined
|
||||
* Telemetry is always enabled and recording *base* data.
|
||||
* Telemetry will send additional ``main`` pings.
|
||||
|
||||
``toolkit.telemetry.unifiedIsOptIn``
|
||||
|
||||
When true, we enable the Telemetry system only for people that opted into Telemetry, even if unified Telemetry is enabled.
|
||||
Defaults to false & requires a restart.
|
||||
|
||||
``toolkit.telemetry.enabled``
|
||||
|
||||
If ``unified`` is off, this controls whether the Telemetry module is enabled.
|
||||
|
@ -1,355 +1,222 @@
|
||||
/* 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 {Cc, Ci, Cu, Cr} = require("chrome");
|
||||
const Services = require("Services");
|
||||
const DevToolsUtils = require("devtools/toolkit/DevToolsUtils.js");
|
||||
const { Cu } = require("chrome");
|
||||
const protocol = require("devtools/server/protocol");
|
||||
const { custom, method, RetVal, Arg, Option, types } = protocol;
|
||||
const { Profiler } = require("devtools/toolkit/shared/profiler");
|
||||
const { actorBridge } = require("devtools/server/actors/common");
|
||||
|
||||
let DEFAULT_PROFILER_OPTIONS = {
|
||||
// When using the DevTools Performance Tools, this will be overridden
|
||||
// by the pref `devtools.performance.profiler.buffer-size`.
|
||||
entries: Math.pow(10, 7),
|
||||
// When using the DevTools Performance Tools, this will be overridden
|
||||
// by the pref `devtools.performance.profiler.sample-rate-khz`.
|
||||
interval: 1,
|
||||
features: ["js"],
|
||||
threadFilters: ["GeckoMain"]
|
||||
};
|
||||
loader.lazyRequireGetter(this, "events", "sdk/event/core");
|
||||
loader.lazyRequireGetter(this, "extend", "sdk/util/object", true);
|
||||
|
||||
/**
|
||||
* The nsIProfiler is target agnostic and interacts with the whole platform.
|
||||
* Therefore, special care needs to be given to make sure different actor
|
||||
* consumers (i.e. "toolboxes") don't interfere with each other.
|
||||
*/
|
||||
let gProfilerConsumers = 0;
|
||||
|
||||
loader.lazyGetter(this, "nsIProfilerModule", () => {
|
||||
return Cc["@mozilla.org/tools/profiler;1"].getService(Ci.nsIProfiler);
|
||||
types.addType("profiler-data", {
|
||||
// On Fx42+, the profile is only deserialized on the front; older
|
||||
// servers will get the profiler data as an object from nsIProfiler,
|
||||
// causing one parse/stringify cycle, then again implicitly in a packet.
|
||||
read: (v) => {
|
||||
if (typeof v.profile === "string") {
|
||||
// Create a new response object since `profile` is read only.
|
||||
let newValue = Object.create(null);
|
||||
newValue.profile = JSON.parse(v.profile);
|
||||
newValue.currentTime = v.currentTime;
|
||||
return newValue;
|
||||
}
|
||||
return v;
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* The profiler actor provides remote access to the built-in nsIProfiler module.
|
||||
* This actor wraps the Profiler module at toolkit/devtools/shared/profiler.js
|
||||
* and provides RDP definitions.
|
||||
*
|
||||
* @see toolkit/devtools/shared/profiler.js for documentation.
|
||||
*/
|
||||
function ProfilerActor() {
|
||||
gProfilerConsumers++;
|
||||
this._observedEvents = new Set();
|
||||
}
|
||||
let ProfilerActor = exports.ProfilerActor = protocol.ActorClass({
|
||||
typeName: "profiler",
|
||||
|
||||
ProfilerActor.prototype = {
|
||||
actorPrefix: "profiler",
|
||||
/**
|
||||
* The set of events the ProfilerActor emits over RDP.
|
||||
*/
|
||||
events: {
|
||||
"console-api-profiler": {
|
||||
data: Arg(0, "json"),
|
||||
},
|
||||
"profiler-started": {
|
||||
data: Arg(0, "json"),
|
||||
},
|
||||
"profiler-stopped": {
|
||||
data: Arg(0, "json"),
|
||||
},
|
||||
|
||||
// Only for older geckos, pre-protocol.js ProfilerActor (<Fx42).
|
||||
// Emitted on other events as a transition from older profiler events
|
||||
// to newer ones.
|
||||
"eventNotification": {
|
||||
subject: Option(0, "json"),
|
||||
topic: Option(0, "string"),
|
||||
details: Option(0, "json")
|
||||
}
|
||||
},
|
||||
|
||||
initialize: function (conn) {
|
||||
protocol.Actor.prototype.initialize.call(this, conn);
|
||||
this._onProfilerEvent = this._onProfilerEvent.bind(this);
|
||||
|
||||
this.bridge = new Profiler();
|
||||
events.on(this.bridge, "*", this._onProfilerEvent);
|
||||
},
|
||||
|
||||
/**
|
||||
* `disconnect` method required to call destroy, since this
|
||||
* actor is not managed by a parent actor.
|
||||
*/
|
||||
disconnect: function() {
|
||||
for (let event of this._observedEvents) {
|
||||
Services.obs.removeObserver(this, event);
|
||||
this.destroy();
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
events.off(this.bridge, "*", this._onProfilerEvent);
|
||||
this.bridge.destroy();
|
||||
protocol.Actor.prototype.destroy.call(this);
|
||||
},
|
||||
|
||||
startProfiler: actorBridge("start", {
|
||||
// Write out every property in the request, since we want all these options to be
|
||||
// on the packet's top-level for backwards compatibility, when the profiler actor
|
||||
// was not using protocol.js (<Fx42)
|
||||
request: {
|
||||
entries: Option(0, "nullable:number"),
|
||||
interval: Option(0, "nullable:number"),
|
||||
features: Option(0, "nullable:array:string"),
|
||||
threadFilters: Option(0, "nullable:array:string"),
|
||||
},
|
||||
response: RetVal("json"),
|
||||
}),
|
||||
|
||||
stopProfiler: actorBridge("stop", {
|
||||
response: RetVal("json"),
|
||||
}),
|
||||
|
||||
getProfile: actorBridge("getProfile", {
|
||||
request: {
|
||||
startTime: Option(0, "nullable:number"),
|
||||
stringify: Option(0, "nullable:boolean")
|
||||
},
|
||||
response: RetVal("profiler-data")
|
||||
}),
|
||||
|
||||
getFeatures: actorBridge("getFeatures", {
|
||||
response: RetVal("json")
|
||||
}),
|
||||
|
||||
getBufferInfo: actorBridge("getBufferInfo", {
|
||||
response: RetVal("json")
|
||||
}),
|
||||
|
||||
getStartOptions: actorBridge("getStartOptions", {
|
||||
response: RetVal("json")
|
||||
}),
|
||||
|
||||
isActive: actorBridge("isActive", {
|
||||
response: RetVal("json")
|
||||
}),
|
||||
|
||||
getSharedLibraryInformation: actorBridge("getSharedLibraryInformation", {
|
||||
response: RetVal("json")
|
||||
}),
|
||||
|
||||
registerEventNotifications: actorBridge("registerEventNotifications", {
|
||||
// Explicitly enumerate the arguments
|
||||
// @see ProfilerActor#startProfiler
|
||||
request: {
|
||||
events: Option(0, "nullable:array:string"),
|
||||
},
|
||||
response: RetVal("json")
|
||||
}),
|
||||
|
||||
unregisterEventNotifications: actorBridge("unregisterEventNotifications", {
|
||||
// Explicitly enumerate the arguments
|
||||
// @see ProfilerActor#startProfiler
|
||||
request: {
|
||||
events: Option(0, "nullable:array:string"),
|
||||
},
|
||||
response: RetVal("json")
|
||||
}),
|
||||
|
||||
/**
|
||||
* Pipe events from Profiler module to this actor.
|
||||
*/
|
||||
_onProfilerEvent: function (eventName, ...data) {
|
||||
events.emit(this, eventName, ...data);
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* This can be used on older Profiler implementations, but the methods cannot
|
||||
* be changed -- you must introduce a new method, and detect the server.
|
||||
*/
|
||||
exports.ProfilerFront = protocol.FrontClass(ProfilerActor, {
|
||||
initialize: function(client, form) {
|
||||
protocol.Front.prototype.initialize.call(this, client, form);
|
||||
this.actorID = form.profilerActor;
|
||||
this.manage(this);
|
||||
|
||||
this._onProfilerEvent = this._onProfilerEvent.bind(this);
|
||||
events.on(this, "*", this._onProfilerEvent);
|
||||
},
|
||||
|
||||
destroy: function () {
|
||||
events.off(this, "*", this._onProfilerEvent);
|
||||
protocol.Front.prototype.destroy.call(this);
|
||||
},
|
||||
|
||||
/**
|
||||
* If using the protocol.js Fronts, then make stringify default,
|
||||
* since the read/write mechanisms will expose it as an object anyway, but
|
||||
* this lets other consumers who connect directly (xpcshell tests, Gecko Profiler) to
|
||||
* have unchanged behaviour.
|
||||
*/
|
||||
getProfile: custom(function (options) {
|
||||
return this._getProfile(extend({ stringify: true }, options));
|
||||
}, {
|
||||
impl: "_getProfile"
|
||||
}),
|
||||
|
||||
/**
|
||||
* Also emit an old `eventNotification` for older consumers of the profiler.
|
||||
*/
|
||||
_onProfilerEvent: function (eventName, data) {
|
||||
// If this event already passed through once, don't repropagate
|
||||
if (data.relayed) {
|
||||
return;
|
||||
}
|
||||
this._observedEvents = null;
|
||||
this.onStopProfiler();
|
||||
data.relayed = true;
|
||||
|
||||
gProfilerConsumers--;
|
||||
checkProfilerConsumers();
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns an array of feature strings, describing the profiler features
|
||||
* that are available on this platform. Can be called while the profiler
|
||||
* is stopped.
|
||||
*/
|
||||
onGetFeatures: function() {
|
||||
return { features: nsIProfilerModule.GetFeatures([]) };
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns an object with the values of the current status of the
|
||||
* circular buffer in the profiler, returning `position`, `totalSize`,
|
||||
* and the current `generation` of the buffer.
|
||||
*/
|
||||
onGetBufferInfo: function() {
|
||||
let position = {}, totalSize = {}, generation = {};
|
||||
nsIProfilerModule.GetBufferInfo(position, totalSize, generation);
|
||||
return {
|
||||
position: position.value,
|
||||
totalSize: totalSize.value,
|
||||
generation: generation.value
|
||||
// If this is `eventNotification`, this is coming from an older Gecko (<Fx42)
|
||||
// that doesn't use protocol.js style events. Massage it to emit a protocol.js
|
||||
// style event as well.
|
||||
if (eventName === "eventNotification") {
|
||||
events.emit(this, data.topic, data);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the configuration used that was originally passed in to start up the
|
||||
* profiler. Used for tests, and does not account for others using nsIProfiler.
|
||||
*/
|
||||
onGetStartOptions: function() {
|
||||
return this._profilerStartOptions || {};
|
||||
},
|
||||
|
||||
/**
|
||||
* Starts the nsIProfiler module. Doing so will discard any samples
|
||||
* that might have been accumulated so far.
|
||||
*
|
||||
* @param number entries [optional]
|
||||
* @param number interval [optional]
|
||||
* @param array:string features [optional]
|
||||
* @param array:string threadFilters [description]
|
||||
*/
|
||||
onStartProfiler: function(request = {}) {
|
||||
let options = this._profilerStartOptions = {
|
||||
entries: request.entries || DEFAULT_PROFILER_OPTIONS.entries,
|
||||
interval: request.interval || DEFAULT_PROFILER_OPTIONS.interval,
|
||||
features: request.features || DEFAULT_PROFILER_OPTIONS.features,
|
||||
threadFilters: request.threadFilters || DEFAULT_PROFILER_OPTIONS.threadFilters,
|
||||
};
|
||||
|
||||
// The start time should be before any samples we might be
|
||||
// interested in.
|
||||
let currentTime = nsIProfilerModule.getElapsedTime();
|
||||
|
||||
nsIProfilerModule.StartProfiler(
|
||||
options.entries,
|
||||
options.interval,
|
||||
options.features,
|
||||
options.features.length,
|
||||
options.threadFilters,
|
||||
options.threadFilters.length
|
||||
);
|
||||
let { position, totalSize, generation } = this.onGetBufferInfo();
|
||||
|
||||
return { started: true, position, totalSize, generation, currentTime };
|
||||
},
|
||||
|
||||
/**
|
||||
* Stops the nsIProfiler module, if no other client is using it.
|
||||
*/
|
||||
onStopProfiler: function() {
|
||||
// Actually stop the profiler only if the last client has stopped profiling.
|
||||
// Since this is a root actor, and the profiler module interacts with the
|
||||
// whole platform, we need to avoid a case in which the profiler is stopped
|
||||
// when there might be other clients still profiling.
|
||||
if (gProfilerConsumers == 1) {
|
||||
nsIProfilerModule.StopProfiler();
|
||||
}
|
||||
return { started: false };
|
||||
},
|
||||
|
||||
/**
|
||||
* Verifies whether or not the nsIProfiler module has started.
|
||||
* If already active, the current time is also returned.
|
||||
*/
|
||||
onIsActive: function() {
|
||||
let isActive = nsIProfilerModule.IsActive();
|
||||
let elapsedTime = isActive ? nsIProfilerModule.getElapsedTime() : undefined;
|
||||
let { position, totalSize, generation } = this.onGetBufferInfo();
|
||||
return { isActive: isActive, currentTime: elapsedTime, position, totalSize, generation };
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a stringified JSON object that describes the shared libraries
|
||||
* which are currently loaded into our process. Can be called while the
|
||||
* profiler is stopped.
|
||||
*/
|
||||
onGetSharedLibraryInformation: function() {
|
||||
return { sharedLibraryInformation: nsIProfilerModule.getSharedLibraryInformation() };
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns all the samples accumulated since the profiler was started,
|
||||
* along with the current time. The data has the following format:
|
||||
* {
|
||||
* libs: string,
|
||||
* meta: {
|
||||
* interval: number,
|
||||
* platform: string,
|
||||
* ...
|
||||
* },
|
||||
* threads: [{
|
||||
* samples: [{
|
||||
* frames: [{
|
||||
* line: number,
|
||||
* location: string,
|
||||
* category: number
|
||||
* } ... ],
|
||||
* name: string
|
||||
* responsiveness: number
|
||||
* time: number
|
||||
* } ... ]
|
||||
* } ... ]
|
||||
* }
|
||||
*
|
||||
*
|
||||
* @param number startTime
|
||||
* Since the circular buffer will only grow as long as the profiler lives,
|
||||
* the buffer can contain unwanted samples. Pass in a `startTime` to only retrieve
|
||||
* samples that took place after the `startTime`, with 0 being when the profiler
|
||||
* just started.
|
||||
*/
|
||||
onGetProfile: function(request) {
|
||||
let startTime = request.startTime || 0;
|
||||
let profile = nsIProfilerModule.getProfileData(startTime);
|
||||
return { profile: profile, currentTime: nsIProfilerModule.getElapsedTime() };
|
||||
},
|
||||
|
||||
/**
|
||||
* Registers for certain event notifications.
|
||||
* Currently supported events:
|
||||
* - "console-api-profiler"
|
||||
* - "profiler-started"
|
||||
* - "profiler-stopped"
|
||||
*/
|
||||
onRegisterEventNotifications: function(request) {
|
||||
let response = [];
|
||||
for (let event of request.events) {
|
||||
if (this._observedEvents.has(event)) {
|
||||
continue;
|
||||
}
|
||||
Services.obs.addObserver(this, event, false);
|
||||
this._observedEvents.add(event);
|
||||
response.push(event);
|
||||
}
|
||||
return { registered: response };
|
||||
},
|
||||
|
||||
/**
|
||||
* Unregisters from certain event notifications.
|
||||
* Currently supported events:
|
||||
* - "console-api-profiler"
|
||||
* - "profiler-started"
|
||||
* - "profiler-stopped"
|
||||
*/
|
||||
onUnregisterEventNotifications: function(request) {
|
||||
let response = [];
|
||||
for (let event of request.events) {
|
||||
if (!this._observedEvents.has(event)) {
|
||||
continue;
|
||||
}
|
||||
Services.obs.removeObserver(this, event);
|
||||
this._observedEvents.delete(event);
|
||||
response.push(event);
|
||||
}
|
||||
return { unregistered: response };
|
||||
},
|
||||
|
||||
/**
|
||||
* Callback for all observed notifications.
|
||||
* @param object subject
|
||||
* @param string topic
|
||||
* @param object data
|
||||
*/
|
||||
observe: DevToolsUtils.makeInfallible(function(subject, topic, data) {
|
||||
// Create JSON objects suitable for transportation across the RDP,
|
||||
// by breaking cycles and making a copy of the `subject` and `data` via
|
||||
// JSON.stringifying those values with a replacer that omits properties
|
||||
// known to introduce cycles, and then JSON.parsing the result.
|
||||
// This spends some CPU cycles, but it's simple.
|
||||
subject = (subject && !Cu.isXrayWrapper(subject) && subject.wrappedJSObject) || subject;
|
||||
subject = JSON.parse(JSON.stringify(subject, cycleBreaker));
|
||||
data = (data && !Cu.isXrayWrapper(data) && data.wrappedJSObject) || data;
|
||||
data = JSON.parse(JSON.stringify(data, cycleBreaker));
|
||||
|
||||
// Sends actor, type and other additional information over the remote
|
||||
// debugging protocol to any profiler clients.
|
||||
let reply = details => {
|
||||
this.conn.send({
|
||||
from: this.actorID,
|
||||
type: "eventNotification",
|
||||
subject: subject,
|
||||
topic: topic,
|
||||
data: data,
|
||||
details: details
|
||||
// Otherwise if a modern protocol.js event, emit it also as `eventNotification`
|
||||
// for compatibility reasons on the client (like for any add-ons/Gecko Profiler using this
|
||||
// event) and log a deprecation message if there is a listener.
|
||||
else {
|
||||
this.conn.emit("eventNotification", {
|
||||
subject: data.subject,
|
||||
topic: data.topic,
|
||||
data: data.data,
|
||||
details: data.details
|
||||
});
|
||||
};
|
||||
|
||||
switch (topic) {
|
||||
case "console-api-profiler":
|
||||
return void reply(this._handleConsoleEvent(subject, data));
|
||||
case "profiler-started":
|
||||
case "profiler-stopped":
|
||||
default:
|
||||
return void reply();
|
||||
}
|
||||
}, "ProfilerActor.prototype.observe"),
|
||||
|
||||
/**
|
||||
* Handles `console.profile` and `console.profileEnd` invocations and
|
||||
* creates an appropriate response sent over the protocol.
|
||||
* @param object subject
|
||||
* @param object data
|
||||
* @return object
|
||||
*/
|
||||
_handleConsoleEvent: function(subject, data) {
|
||||
// An optional label may be specified when calling `console.profile`.
|
||||
// If that's the case, stringify it and send it over with the response.
|
||||
let { action, arguments: args } = subject;
|
||||
let profileLabel = args.length > 0 ? args[0] + "" : undefined;
|
||||
|
||||
// If the event was generated from `console.profile` or `console.profileEnd`
|
||||
// we need to start the profiler right away and then just notify the client.
|
||||
// Otherwise, we'll lose precious samples.
|
||||
|
||||
if (action === "profile" || action === "profileEnd") {
|
||||
let { isActive, currentTime } = this.onIsActive();
|
||||
|
||||
// Start the profiler only if it wasn't already active. Otherwise, any
|
||||
// samples that might have been accumulated so far will be discarded.
|
||||
if (!isActive && action === "profile") {
|
||||
this.onStartProfiler();
|
||||
return {
|
||||
profileLabel: profileLabel,
|
||||
currentTime: 0
|
||||
};
|
||||
if (this.conn._getListeners("eventNotification").length) {
|
||||
Cu.reportError(`
|
||||
ProfilerActor's "eventNotification" on the DebuggerClient has been deprecated.
|
||||
Use the ProfilerFront found in "devtools/server/actors/profiler".`);
|
||||
}
|
||||
// Otherwise, if inactive and a call to profile end, send
|
||||
// an empty object because we can't do anything with this.
|
||||
else if (!isActive) {
|
||||
return {};
|
||||
}
|
||||
|
||||
// Otherwise, the profiler is already active, so just send
|
||||
// to the front the current time, label, and the notification
|
||||
// adds the action as well.
|
||||
return {
|
||||
profileLabel: profileLabel,
|
||||
currentTime: currentTime
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
exports.ProfilerActor = ProfilerActor;
|
||||
|
||||
/**
|
||||
* JSON.stringify callback used in ProfilerActor.prototype.observe.
|
||||
*/
|
||||
function cycleBreaker(key, value) {
|
||||
if (key == "wrappedJSObject") {
|
||||
return undefined;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts the value sanity of `gProfilerConsumers`.
|
||||
*/
|
||||
function checkProfilerConsumers() {
|
||||
if (gProfilerConsumers < 0) {
|
||||
let msg = "Somehow the number of started profilers is now negative.";
|
||||
DevToolsUtils.reportException("ProfilerActor", msg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The request types this actor can handle.
|
||||
* At the moment there are two known users of the Profiler actor:
|
||||
* the devtools and the Gecko Profiler addon, which uses the debugger
|
||||
* protocol to get profiles from Fennec.
|
||||
*/
|
||||
ProfilerActor.prototype.requestTypes = {
|
||||
"getBufferInfo": ProfilerActor.prototype.onGetBufferInfo,
|
||||
"getFeatures": ProfilerActor.prototype.onGetFeatures,
|
||||
"startProfiler": ProfilerActor.prototype.onStartProfiler,
|
||||
"stopProfiler": ProfilerActor.prototype.onStopProfiler,
|
||||
"isActive": ProfilerActor.prototype.onIsActive,
|
||||
"getSharedLibraryInformation": ProfilerActor.prototype.onGetSharedLibraryInformation,
|
||||
"getProfile": ProfilerActor.prototype.onGetProfile,
|
||||
"registerEventNotifications": ProfilerActor.prototype.onRegisterEventNotifications,
|
||||
"unregisterEventNotifications": ProfilerActor.prototype.onUnregisterEventNotifications,
|
||||
"getStartOptions": ProfilerActor.prototype.onGetStartOptions
|
||||
};
|
||||
},
|
||||
});
|
||||
|
@ -171,7 +171,7 @@ RootActor.prototype = {
|
||||
},
|
||||
// Whether or not `getProfile()` supports specifying a `startTime`
|
||||
// and `endTime` to filter out samples. Fx40+
|
||||
profilerDataFilterable: true
|
||||
profilerDataFilterable: true,
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -8,99 +8,55 @@
|
||||
*/
|
||||
|
||||
const Profiler = Cc["@mozilla.org/tools/profiler;1"].getService(Ci.nsIProfiler);
|
||||
const { ProfilerFront } = devtools.require("devtools/server/actors/profiler");
|
||||
|
||||
function run_test()
|
||||
{
|
||||
get_chrome_actors((client, form) => {
|
||||
let actor = form.profilerActor;
|
||||
activate_profiler(client, actor, () => {
|
||||
test_events(client, actor, () => {
|
||||
client.close(do_test_finished);
|
||||
});
|
||||
});
|
||||
})
|
||||
|
||||
do_test_pending();
|
||||
function run_test() {
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
function activate_profiler(client, actor, callback)
|
||||
{
|
||||
client.request({ to: actor, type: "startProfiler" }, response => {
|
||||
do_check_true(response.started);
|
||||
client.request({ to: actor, type: "isActive" }, response => {
|
||||
do_check_true(response.isActive);
|
||||
callback();
|
||||
});
|
||||
});
|
||||
}
|
||||
add_task(function *() {
|
||||
let [client, form] = yield getChromeActors();
|
||||
let front = new ProfilerFront(client, form);
|
||||
|
||||
function register_events(client, actor, events, callback)
|
||||
{
|
||||
client.request({
|
||||
to: actor,
|
||||
type: "registerEventNotifications",
|
||||
events: events
|
||||
}, callback);
|
||||
}
|
||||
|
||||
function unregister_events(client, actor, events, callback)
|
||||
{
|
||||
client.request({
|
||||
to: actor,
|
||||
type: "unregisterEventNotifications",
|
||||
events: events
|
||||
}, callback);
|
||||
}
|
||||
|
||||
function emit_and_wait_for_event(client, subject, topic, data, callback)
|
||||
{
|
||||
let events = [0, 0, 0, 0];
|
||||
front.on("console-api-profiler", () => events[0]++);
|
||||
front.on("profiler-started", () => events[1]++);
|
||||
front.on("profiler-stopped", () => events[2]++);
|
||||
client.addListener("eventNotification", (type, response) => {
|
||||
do_check_eq(type, "eventNotification");
|
||||
do_check_eq(response.topic, topic);
|
||||
do_check_eq(typeof response.subject, "object");
|
||||
|
||||
delete subject.wrappedJSObject;
|
||||
do_check_eq(JSON.stringify(response.subject), JSON.stringify(subject));
|
||||
|
||||
do_check_eq(response.data, data);
|
||||
callback();
|
||||
do_check_true(type === "eventNotification");
|
||||
events[3]++;
|
||||
});
|
||||
|
||||
// Make sure cyclic objects are handled before sending them over the protocol.
|
||||
// See ProfilerActor.prototype.observe for more information.
|
||||
subject.wrappedJSObject = subject;
|
||||
Services.obs.notifyObservers(subject, topic, data);
|
||||
}
|
||||
|
||||
function test_events(client, actor, callback)
|
||||
{
|
||||
register_events(client, actor, ["foo", "bar"], response => {
|
||||
do_check_eq(typeof response.registered, "object");
|
||||
do_check_eq(response.registered.length, 2);
|
||||
do_check_eq(response.registered[0], "foo");
|
||||
do_check_eq(response.registered[1], "bar");
|
||||
|
||||
register_events(client, actor, ["foo"], response => {
|
||||
do_check_eq(typeof response.registered, "object");
|
||||
do_check_eq(response.registered.length, 0);
|
||||
|
||||
emit_and_wait_for_event(client, { hello: "world" }, "foo", "bar", () => {
|
||||
|
||||
unregister_events(client, actor, ["foo", "bar", "baz"], response => {
|
||||
do_check_eq(typeof response.unregistered, "object");
|
||||
do_check_eq(response.unregistered.length, 2);
|
||||
do_check_eq(response.unregistered[0], "foo");
|
||||
do_check_eq(response.unregistered[1], "bar");
|
||||
|
||||
// All events being now unregistered, sending an event shouldn't
|
||||
// do anything. If it does, the eventNotification listeners added
|
||||
// above will catch the event and fail on the data.event test.
|
||||
Services.obs.notifyObservers(null, "foo", null);
|
||||
Services.obs.notifyObservers(null, "bar", null);
|
||||
|
||||
callback();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
yield front.startProfiler();
|
||||
yield front.stopProfiler();
|
||||
|
||||
// All should be empty without binding events
|
||||
do_check_true(events[0] === 0);
|
||||
do_check_true(events[1] === 0);
|
||||
do_check_true(events[2] === 0);
|
||||
do_check_true(events[3] === 0);
|
||||
|
||||
let ret = yield front.registerEventNotifications({ events: ["console-api-profiler", "profiler-started", "profiler-stopped"] });
|
||||
do_check_true(ret.registered.length === 3);
|
||||
|
||||
yield front.startProfiler();
|
||||
do_check_true(events[0] === 0);
|
||||
do_check_true(events[1] === 1);
|
||||
do_check_true(events[2] === 0);
|
||||
do_check_true(events[3] === 1, "compatibility events supported for eventNotifications");
|
||||
|
||||
yield front.stopProfiler();
|
||||
do_check_true(events[0] === 0);
|
||||
do_check_true(events[1] === 1);
|
||||
do_check_true(events[2] === 1);
|
||||
do_check_true(events[3] === 2, "compatibility events supported for eventNotifications");
|
||||
|
||||
ret = yield front.unregisterEventNotifications({ events: ["console-api-profiler", "profiler-started", "profiler-stopped"] });
|
||||
do_check_true(ret.registered.length === 3);
|
||||
});
|
||||
|
||||
function getChromeActors () {
|
||||
let deferred = promise.defer();
|
||||
get_chrome_actors((client, form) => deferred.resolve([client, form]));
|
||||
return deferred.promise;
|
||||
}
|
||||
|
@ -1,69 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests the event notification service for the profiler actor, specifically
|
||||
* for when the profiler is started and stopped.
|
||||
*/
|
||||
|
||||
const Profiler = Cc["@mozilla.org/tools/profiler;1"].getService(Ci.nsIProfiler);
|
||||
|
||||
function run_test()
|
||||
{
|
||||
get_chrome_actors((client, form) => {
|
||||
let actor = form.profilerActor;
|
||||
activate_profiler(client, actor, () => {
|
||||
test_events(client, actor, () => {
|
||||
client.close(do_test_finished);
|
||||
});
|
||||
});
|
||||
})
|
||||
|
||||
do_test_pending();
|
||||
}
|
||||
|
||||
function activate_profiler(client, actor, callback)
|
||||
{
|
||||
client.request({ to: actor, type: "startProfiler" }, response => {
|
||||
do_check_true(response.started);
|
||||
client.request({ to: actor, type: "isActive" }, response => {
|
||||
do_check_true(response.isActive);
|
||||
callback();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function register_events(client, actor, events, callback)
|
||||
{
|
||||
client.request({
|
||||
to: actor,
|
||||
type: "registerEventNotifications",
|
||||
events: events
|
||||
}, callback);
|
||||
}
|
||||
|
||||
function wait_for_event(client, topic, callback)
|
||||
{
|
||||
client.addListener("eventNotification", (type, response) => {
|
||||
do_check_eq(type, "eventNotification");
|
||||
|
||||
if (response.topic == topic) {
|
||||
callback();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function test_events(client, actor, callback)
|
||||
{
|
||||
register_events(client, actor, ["profiler-started", "profiler-stopped"], () => {
|
||||
wait_for_event(client, "profiler-started", () => {
|
||||
wait_for_event(client, "profiler-stopped", () => {
|
||||
callback();
|
||||
});
|
||||
Profiler.StopProfiler();
|
||||
});
|
||||
Profiler.StartProfiler(1000000, 1, ["js"], 1);
|
||||
});
|
||||
}
|
@ -232,7 +232,6 @@ reason = bug 820380
|
||||
[test_profiler_close.js]
|
||||
[test_profiler_data.js]
|
||||
[test_profiler_events-01.js]
|
||||
[test_profiler_events-02.js]
|
||||
[test_profiler_getbufferinfo.js]
|
||||
[test_profiler_getfeatures.js]
|
||||
[test_profiler_getsharedlibraryinformation.js]
|
||||
|
@ -10,6 +10,7 @@ EXTRA_JS_MODULES.devtools.shared += [
|
||||
'async-storage.js',
|
||||
'framerate.js',
|
||||
'memory.js',
|
||||
'profiler.js',
|
||||
'system.js',
|
||||
'timeline.js',
|
||||
'worker-helper.js',
|
||||
|
438
toolkit/devtools/shared/profiler.js
Normal file
@ -0,0 +1,438 @@
|
||||
/* 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 { Cc, Ci, Cu } = require("chrome");
|
||||
const Services = require("Services");
|
||||
const { Class } = require("sdk/core/heritage");
|
||||
loader.lazyRequireGetter(this, "events", "sdk/event/core");
|
||||
loader.lazyRequireGetter(this, "EventTarget", "sdk/event/target", true);
|
||||
loader.lazyRequireGetter(this, "DevToolsUtils", "devtools/toolkit/DevToolsUtils.js");
|
||||
|
||||
// Events piped from system observers to Profiler instances.
|
||||
const PROFILER_SYSTEM_EVENTS = [
|
||||
"console-api-profiler",
|
||||
"profiler-started",
|
||||
"profiler-stopped"
|
||||
];
|
||||
|
||||
loader.lazyGetter(this, "nsIProfilerModule", () => {
|
||||
return Cc["@mozilla.org/tools/profiler;1"].getService(Ci.nsIProfiler);
|
||||
});
|
||||
|
||||
let DEFAULT_PROFILER_OPTIONS = {
|
||||
// When using the DevTools Performance Tools, this will be overridden
|
||||
// by the pref `devtools.performance.profiler.buffer-size`.
|
||||
entries: Math.pow(10, 7),
|
||||
// When using the DevTools Performance Tools, this will be overridden
|
||||
// by the pref `devtools.performance.profiler.sample-rate-khz`.
|
||||
interval: 1,
|
||||
features: ["js"],
|
||||
threadFilters: ["GeckoMain"]
|
||||
};
|
||||
|
||||
/**
|
||||
* Main interface for interacting with nsIProfiler
|
||||
*/
|
||||
const ProfilerManager = (function () {
|
||||
let consumers = new Set();
|
||||
|
||||
return {
|
||||
/**
|
||||
* The nsIProfiler is target agnostic and interacts with the whole platform.
|
||||
* Therefore, special care needs to be given to make sure different profiler
|
||||
* consumers (i.e. "toolboxes") don't interfere with each other. Register
|
||||
* the instance here.
|
||||
*/
|
||||
addInstance: function (instance) {
|
||||
consumers.add(instance);
|
||||
|
||||
// Lazily register events
|
||||
this.registerEventListeners();
|
||||
},
|
||||
|
||||
removeInstance: function (instance) {
|
||||
consumers.delete(instance);
|
||||
|
||||
if (this.length < 0) {
|
||||
let msg = "Somehow the number of started profilers is now negative.";
|
||||
DevToolsUtils.reportException("Profiler", msg);
|
||||
}
|
||||
|
||||
if (this.length === 0) {
|
||||
this.unregisterEventListeners();
|
||||
this.stop();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Starts the nsIProfiler module. Doing so will discard any samples
|
||||
* that might have been accumulated so far.
|
||||
*
|
||||
* @param {number} entries [optional]
|
||||
* @param {number} interval [optional]
|
||||
* @param {Array<string>} features [optional]
|
||||
* @param {Array<string>} threadFilters [description]
|
||||
*
|
||||
* @return {object}
|
||||
*/
|
||||
start: function (options = {}) {
|
||||
let config = this._profilerStartOptions = {
|
||||
entries: options.entries || DEFAULT_PROFILER_OPTIONS.entries,
|
||||
interval: options.interval || DEFAULT_PROFILER_OPTIONS.interval,
|
||||
features: options.features || DEFAULT_PROFILER_OPTIONS.features,
|
||||
threadFilters: options.threadFilters || DEFAULT_PROFILER_OPTIONS.threadFilters,
|
||||
};
|
||||
|
||||
// The start time should be before any samples we might be
|
||||
// interested in.
|
||||
let currentTime = nsIProfilerModule.getElapsedTime();
|
||||
|
||||
nsIProfilerModule.StartProfiler(
|
||||
config.entries,
|
||||
config.interval,
|
||||
config.features,
|
||||
config.features.length,
|
||||
config.threadFilters,
|
||||
config.threadFilters.length
|
||||
);
|
||||
let { position, totalSize, generation } = this.getBufferInfo();
|
||||
|
||||
return { started: true, position, totalSize, generation, currentTime };
|
||||
},
|
||||
|
||||
stop: function () {
|
||||
// Actually stop the profiler only if the last client has stopped profiling.
|
||||
// Since this is used as a root actor, and the profiler module interacts with the
|
||||
// whole platform, we need to avoid a case in which the profiler is stopped
|
||||
// when there might be other clients still profiling.
|
||||
if (this.length <= 1) {
|
||||
nsIProfilerModule.StopProfiler();
|
||||
}
|
||||
return { started: false };
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns all the samples accumulated since the profiler was started,
|
||||
* along with the current time. The data has the following format:
|
||||
* {
|
||||
* libs: string,
|
||||
* meta: {
|
||||
* interval: number,
|
||||
* platform: string,
|
||||
* ...
|
||||
* },
|
||||
* threads: [{
|
||||
* samples: [{
|
||||
* frames: [{
|
||||
* line: number,
|
||||
* location: string,
|
||||
* category: number
|
||||
* } ... ],
|
||||
* name: string
|
||||
* responsiveness: number
|
||||
* time: number
|
||||
* } ... ]
|
||||
* } ... ]
|
||||
* }
|
||||
*
|
||||
*
|
||||
* @param number startTime
|
||||
* Since the circular buffer will only grow as long as the profiler lives,
|
||||
* the buffer can contain unwanted samples. Pass in a `startTime` to only retrieve
|
||||
* samples that took place after the `startTime`, with 0 being when the profiler
|
||||
* just started.
|
||||
* @param boolean stringify
|
||||
* Whether or not the returned profile object should be a string or not to save
|
||||
* JSON parse/stringify cycle if emitting over RDP.
|
||||
*/
|
||||
getProfile: function (options) {
|
||||
let startTime = options.startTime || 0;
|
||||
let profile = options.stringify ?
|
||||
nsIProfilerModule.GetProfile(startTime) :
|
||||
nsIProfilerModule.getProfileData(startTime);
|
||||
|
||||
return { profile: profile, currentTime: nsIProfilerModule.getElapsedTime() };
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns an array of feature strings, describing the profiler features
|
||||
* that are available on this platform. Can be called while the profiler
|
||||
* is stopped.
|
||||
*
|
||||
* @return {object}
|
||||
*/
|
||||
getFeatures: function () {
|
||||
return { features: nsIProfilerModule.GetFeatures([]) };
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns an object with the values of the current status of the
|
||||
* circular buffer in the profiler, returning `position`, `totalSize`,
|
||||
* and the current `generation` of the buffer.
|
||||
*
|
||||
* @return {object}
|
||||
*/
|
||||
getBufferInfo: function() {
|
||||
let position = {}, totalSize = {}, generation = {};
|
||||
nsIProfilerModule.GetBufferInfo(position, totalSize, generation);
|
||||
return {
|
||||
position: position.value,
|
||||
totalSize: totalSize.value,
|
||||
generation: generation.value
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the configuration used that was originally passed in to start up the
|
||||
* profiler. Used for tests, and does not account for others using nsIProfiler.
|
||||
*
|
||||
* @param {object}
|
||||
*/
|
||||
getStartOptions: function() {
|
||||
return this._profilerStartOptions || {};
|
||||
},
|
||||
|
||||
/**
|
||||
* Verifies whether or not the nsIProfiler module has started.
|
||||
* If already active, the current time is also returned.
|
||||
*
|
||||
* @return {object}
|
||||
*/
|
||||
isActive: function() {
|
||||
let isActive = nsIProfilerModule.IsActive();
|
||||
let elapsedTime = isActive ? nsIProfilerModule.getElapsedTime() : undefined;
|
||||
let { position, totalSize, generation } = this.getBufferInfo();
|
||||
return { isActive: isActive, currentTime: elapsedTime, position, totalSize, generation };
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a stringified JSON object that describes the shared libraries
|
||||
* which are currently loaded into our process. Can be called while the
|
||||
* profiler is stopped.
|
||||
*/
|
||||
getSharedLibraryInformation: function() {
|
||||
return { sharedLibraryInformation: nsIProfilerModule.getSharedLibraryInformation() };
|
||||
},
|
||||
|
||||
/**
|
||||
* Number of profiler instances.
|
||||
*
|
||||
* @return {number}
|
||||
*/
|
||||
get length() {
|
||||
return consumers.size;
|
||||
},
|
||||
|
||||
/**
|
||||
* Callback for all observed notifications.
|
||||
* @param object subject
|
||||
* @param string topic
|
||||
* @param object data
|
||||
*/
|
||||
observe: sanitizeHandler(function (subject, topic, data) {
|
||||
let details;
|
||||
|
||||
// An optional label may be specified when calling `console.profile`.
|
||||
// If that's the case, stringify it and send it over with the response.
|
||||
let { action, arguments: args } = subject || {};
|
||||
let profileLabel = args && args.length > 0 ? `${args[0]}` : void 0;
|
||||
|
||||
let subscribers = Array.from(consumers).filter(c => c.subscribedEvents.has(topic));
|
||||
|
||||
// If no consumers are listening, bail out
|
||||
if (subscribers.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the event was generated from `console.profile` or `console.profileEnd`
|
||||
// we need to start the profiler right away and then just notify the client.
|
||||
// Otherwise, we'll lose precious samples.
|
||||
if (topic === "console-api-profiler" && (action === "profile" || action === "profileEnd")) {
|
||||
let { isActive, currentTime } = this.isActive();
|
||||
|
||||
// Start the profiler only if it wasn't already active. Otherwise, any
|
||||
// samples that might have been accumulated so far will be discarded.
|
||||
if (!isActive && action === "profile") {
|
||||
this.start();
|
||||
details = { profileLabel, currentTime: 0 };
|
||||
}
|
||||
// Otherwise, if inactive and a call to profile end, do nothing
|
||||
// and don't emit event.
|
||||
else if (!isActive) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, the profiler is already active, so just send
|
||||
// to the front the current time, label, and the notification
|
||||
// adds the action as well.
|
||||
details = { profileLabel, currentTime };
|
||||
}
|
||||
|
||||
// Propagate the event to the profiler instances that
|
||||
// are subscribed to this event.
|
||||
for (let subscriber of subscribers) {
|
||||
events.emit(subscriber, topic, { subject, topic, data, details });
|
||||
}
|
||||
}, "ProfilerManager.observe"),
|
||||
|
||||
/**
|
||||
* Registers handlers for the following events to be emitted
|
||||
* on active Profiler instances:
|
||||
* - "console-api-profiler"
|
||||
* - "profiler-started"
|
||||
* - "profiler-stopped"
|
||||
*
|
||||
* The ProfilerManager listens to all events, and individual
|
||||
* consumers filter which events they are interested in.
|
||||
*/
|
||||
registerEventListeners: function () {
|
||||
if (!this._eventsRegistered) {
|
||||
PROFILER_SYSTEM_EVENTS.forEach(eventName =>
|
||||
Services.obs.addObserver(this, eventName, false));
|
||||
this._eventsRegistered = true;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Unregisters handlers for all system events.
|
||||
*/
|
||||
unregisterEventListeners: function () {
|
||||
if (this._eventsRegistered) {
|
||||
PROFILER_SYSTEM_EVENTS.forEach(eventName => Services.obs.removeObserver(this, eventName));
|
||||
this._eventsRegistered = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
/**
|
||||
* The profiler actor provides remote access to the built-in nsIProfiler module.
|
||||
*/
|
||||
let Profiler = exports.Profiler = Class({
|
||||
extends: EventTarget,
|
||||
|
||||
initialize: function () {
|
||||
this.subscribedEvents = new Set();
|
||||
ProfilerManager.addInstance(this);
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
this.subscribedEvents = null;
|
||||
ProfilerManager.removeInstance(this);
|
||||
},
|
||||
|
||||
/**
|
||||
* @see ProfilerManager.start
|
||||
*/
|
||||
start: function (options) { return ProfilerManager.start(options); },
|
||||
|
||||
/**
|
||||
* @see ProfilerManager.stop
|
||||
*/
|
||||
stop: function () { return ProfilerManager.stop(); },
|
||||
|
||||
/**
|
||||
* @see ProfilerManager.getProfile
|
||||
*/
|
||||
getProfile: function (request={}) { return ProfilerManager.getProfile(request); },
|
||||
|
||||
/**
|
||||
* @see ProfilerManager.getFeatures
|
||||
*/
|
||||
getFeatures: function() { return ProfilerManager.getFeatures(); },
|
||||
|
||||
/**
|
||||
* @see ProfilerManager.getBufferInfo
|
||||
*/
|
||||
getBufferInfo: function() { return ProfilerManager.getBufferInfo(); },
|
||||
|
||||
/**
|
||||
* @see ProfilerManager.getStartOptions
|
||||
*/
|
||||
getStartOptions: function() { return ProfilerManager.getStartOptions(); },
|
||||
|
||||
/**
|
||||
* @see ProfilerManager.isActive
|
||||
*/
|
||||
isActive: function() { return ProfilerManager.isActive(); },
|
||||
|
||||
/**
|
||||
* @see ProfilerManager.isActive
|
||||
*/
|
||||
getSharedLibraryInformation: function() { return ProfilerManager.getSharedLibraryInformation(); },
|
||||
|
||||
/**
|
||||
* Subscribes this instance to one of several events defined in
|
||||
* an events array.
|
||||
* - "console-api-profiler",
|
||||
* - "profiler-started",
|
||||
* - "profiler-stopped"
|
||||
*
|
||||
* @param {Array<string>} data.event
|
||||
* @return {object}
|
||||
*/
|
||||
registerEventNotifications: function(data={}) {
|
||||
let response = [];
|
||||
(data.events || []).forEach(e => {
|
||||
if (!this.subscribedEvents.has(e)) {
|
||||
this.subscribedEvents.add(e);
|
||||
response.push(e);
|
||||
}
|
||||
});
|
||||
return { registered: response };
|
||||
},
|
||||
|
||||
/**
|
||||
* Unsubscribes this instance to one of several events defined in
|
||||
* an events array.
|
||||
*
|
||||
* @param {Array<string>} data.event
|
||||
* @return {object}
|
||||
*/
|
||||
unregisterEventNotifications: function(data={}) {
|
||||
let response = [];
|
||||
(data.events || []).forEach(e => {
|
||||
if (this.subscribedEvents.has(e)) {
|
||||
this.subscribedEvents.delete(e);
|
||||
response.push(e);
|
||||
}
|
||||
});
|
||||
return { registered: response };
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* JSON.stringify callback used in Profiler.prototype.observe.
|
||||
*/
|
||||
function cycleBreaker(key, value) {
|
||||
if (key == "wrappedJSObject") {
|
||||
return undefined;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create JSON objects suitable for transportation across the RDP,
|
||||
* by breaking cycles and making a copy of the `subject` and `data` via
|
||||
* JSON.stringifying those values with a replacer that omits properties
|
||||
* known to introduce cycles, and then JSON.parsing the result.
|
||||
* This spends some CPU cycles, but it's simple.
|
||||
*
|
||||
* @TODO Also wraps it in a `makeInfallible` -- is this still necessary?
|
||||
*
|
||||
* @param {function} handler
|
||||
* @return {function}
|
||||
*/
|
||||
function sanitizeHandler (handler, identifier) {
|
||||
return DevToolsUtils.makeInfallible(function (subject, topic, data) {
|
||||
subject = (subject && !Cu.isXrayWrapper(subject) && subject.wrappedJSObject) || subject;
|
||||
subject = JSON.parse(JSON.stringify(subject, cycleBreaker));
|
||||
data = (data && !Cu.isXrayWrapper(data) && data.wrappedJSObject) || data;
|
||||
data = JSON.parse(JSON.stringify(data, cycleBreaker));
|
||||
|
||||
// Pass in clean data to the underlying handler
|
||||
return handler.call(this, subject, topic, data);
|
||||
}, identifier);
|
||||
}
|