Merge fx-team to central, a=merge

This commit is contained in:
Wes Kocher 2015-07-14 15:21:52 -07:00
commit 8a2d5819b2
73 changed files with 1773 additions and 928 deletions

View File

@ -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.

View File

@ -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;

View File

@ -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);"

View File

@ -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();
}

View File

@ -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;

View File

@ -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>

View File

@ -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">

View File

@ -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;
});

View File

@ -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;
});

View File

@ -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;
});

View File

@ -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,

View File

@ -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';

View File

@ -243,7 +243,8 @@ StackFramesView.prototype = Heritage.extend(WidgetMethods, {
},
_mirror: null,
_prevBlackBoxedUrl: null
_prevBlackBoxedUrl: null,
_popupset: null
});
DebuggerView.StackFrames = new StackFramesView(DebuggerController, DebuggerView);

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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();
}

View File

@ -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();

View File

@ -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();

View File

@ -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();

View File

@ -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();

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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.");

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();

View File

@ -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();
}

View File

@ -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;

View File

@ -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
});
}
});
}

View File

@ -64,8 +64,7 @@ body {
pointer-events: none;
}
.ruleview-namecontainer,
.ruleview-selectorcontainer {
.ruleview-namecontainer {
cursor: text;
}

View File

@ -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]

View File

@ -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");

View File

@ -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");

View File

@ -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");
}
}
}
});

View File

@ -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;

View File

@ -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);
}
}

View File

@ -6,4 +6,5 @@ firefox-appdir = browser
skip-if = toolkit == 'android' || toolkit == 'gonk'
[test_parseDeclarations.js]
[test_parsePseudoClassesAndAttributes.js]
[test_parseSingleValue.js]

View File

@ -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">

View File

@ -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

View File

@ -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">

View File

@ -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)

View File

@ -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;

View File

@ -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)

View File

@ -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;
}

View File

@ -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);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

@ -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;

View File

@ -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;

View 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

View 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

View File

@ -400,3 +400,7 @@ description > html|a {
background-color: transparent;
opacity: 1;
}
#tosPP-small-ToS {
margin-right: 3em;
}

View File

@ -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)

View File

@ -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() {

View File

@ -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);

View File

@ -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;
}

View File

@ -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",

View File

@ -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";

View File

@ -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.

View File

@ -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
};
},
});

View File

@ -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,
},
/**

View File

@ -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;
}

View File

@ -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);
});
}

View File

@ -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]

View File

@ -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',

View 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);
}