From 7f1ad32221bd8bcfff768d33ecd336af8c599d84 Mon Sep 17 00:00:00 2001 From: Panos Astithas Date: Fri, 10 Jul 2015 20:08:35 +0300 Subject: [PATCH 01/16] Bug 1182541 - Resolve the 'm' accesskey conflict in the privacy panel. r=jaws --- browser/locales/en-US/chrome/browser/preferences/privacy.dtd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/browser/locales/en-US/chrome/browser/preferences/privacy.dtd b/browser/locales/en-US/chrome/browser/preferences/privacy.dtd index 5639bdb3c95e..d165969ce875 100644 --- a/browser/locales/en-US/chrome/browser/preferences/privacy.dtd +++ b/browser/locales/en-US/chrome/browser/preferences/privacy.dtd @@ -10,7 +10,7 @@ - + From ad3baf083b9be24c3aeacb197724545c324b57cd Mon Sep 17 00:00:00 2001 From: Paolo Amadini Date: Mon, 13 Jul 2015 18:38:13 +0100 Subject: [PATCH 02/16] Bug 1175689 - Group the existing site identity URL bar icon with the tracking protection shield when TP is enabled. r=ttaubert --HG-- extra : commitid : FQyIE2IzaKI extra : rebase_source : ccf7542df7b3b6542b89fb3c5566000dd14645ff --- .../content/browser-trackingprotection.js | 18 +++++----- browser/base/content/browser.xul | 1 + .../test/general/browser_trackingUI_1.js | 33 +++++++++++-------- browser/themes/linux/jar.mn | 2 ++ browser/themes/osx/jar.mn | 2 ++ .../themes/shared/controlcenter/panel.inc.css | 15 ++++----- .../identity-block/identity-block.inc.css | 18 +++++++++- .../identity-block/tracking-protection-16.svg | 21 ++++++++++++ .../tracking-protection-disabled-16.svg | 23 +++++++++++++ browser/themes/windows/jar.mn | 2 ++ 10 files changed, 103 insertions(+), 32 deletions(-) create mode 100755 browser/themes/shared/identity-block/tracking-protection-16.svg create mode 100755 browser/themes/shared/identity-block/tracking-protection-disabled-16.svg diff --git a/browser/base/content/browser-trackingprotection.js b/browser/base/content/browser-trackingprotection.js index 7c7ed7db5e51..aa070fa4e5eb 100644 --- a/browser/base/content/browser-trackingprotection.js +++ b/browser/base/content/browser-trackingprotection.js @@ -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. diff --git a/browser/base/content/browser.xul b/browser/base/content/browser.xul index de1a32947204..3968b8e2fea9 100644 --- a/browser/base/content/browser.xul +++ b/browser/base/content/browser.xul @@ -762,6 +762,7 @@ onclick="gIdentityHandler.handleIdentityButtonEvent(event);" onkeypress="gIdentityHandler.handleIdentityButtonEvent(event);" ondragstart="gIdentityHandler.onDragStart(event);"> + #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; } diff --git a/browser/themes/shared/identity-block/identity-block.inc.css b/browser/themes/shared/identity-block/identity-block.inc.css index b8486d1fdfd7..700ae89a44df 100644 --- a/browser/themes/shared/identity-block/identity-block.inc.css +++ b/browser/themes/shared/identity-block/identity-block.inc.css @@ -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; diff --git a/browser/themes/shared/identity-block/tracking-protection-16.svg b/browser/themes/shared/identity-block/tracking-protection-16.svg new file mode 100755 index 000000000000..ebe6b5f9b5c2 --- /dev/null +++ b/browser/themes/shared/identity-block/tracking-protection-16.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + diff --git a/browser/themes/shared/identity-block/tracking-protection-disabled-16.svg b/browser/themes/shared/identity-block/tracking-protection-disabled-16.svg new file mode 100755 index 000000000000..b0c68d28c5a6 --- /dev/null +++ b/browser/themes/shared/identity-block/tracking-protection-disabled-16.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/browser/themes/windows/jar.mn b/browser/themes/windows/jar.mn index b50112b70f3c..efc0cce43822 100644 --- a/browser/themes/windows/jar.mn +++ b/browser/themes/windows/jar.mn @@ -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 From 5473072c0aa86bab5a8e977b0c7b33c9f6ae927c Mon Sep 17 00:00:00 2001 From: Tim Taubert Date: Wed, 8 Jul 2015 12:57:57 +0200 Subject: [PATCH 03/16] Bug 1181523 - [Control Center] Hide tracking protection section for chromeUI pages r=Gijs * * * Bug 1181523 - [Control Center] Add comments to CSS selector hiding panel elements for various states r=Gijs --- browser/base/content/browser.js | 6 ++++++ .../themes/shared/controlcenter/panel.inc.css | 18 ++++++++++++++---- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js index 52edda1bc977..04dd78e73bff 100644 --- a/browser/base/content/browser.js +++ b/browser/base/content/browser.js @@ -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; diff --git a/browser/themes/shared/controlcenter/panel.inc.css b/browser/themes/shared/controlcenter/panel.inc.css index c1defaed2c4f..8d5a3700992c 100644 --- a/browser/themes/shared/controlcenter/panel.inc.css +++ b/browser/themes/shared/controlcenter/panel.inc.css @@ -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; } From 5d39dc93bcdb17a273c4df0dca799082498d269e Mon Sep 17 00:00:00 2001 From: Alessio Placitelli Date: Tue, 14 Jul 2015 12:51:20 +0200 Subject: [PATCH 04/16] Bug 1182424 - Part 2 - Update the docs. r=gfritzsche --- toolkit/components/telemetry/docs/preferences.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/toolkit/components/telemetry/docs/preferences.rst b/toolkit/components/telemetry/docs/preferences.rst index 50c0f74064bc..ed80f250ad29 100644 --- a/toolkit/components/telemetry/docs/preferences.rst +++ b/toolkit/components/telemetry/docs/preferences.rst @@ -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. From fdbecf8c58bf3634692e810bf08f06171a2d7106 Mon Sep 17 00:00:00 2001 From: Tim Taubert Date: Tue, 14 Jul 2015 12:58:16 +0200 Subject: [PATCH 05/16] Bug 1183580 - Don't let identity block background bleed over URL bar border when the window is inactive r=paolo --- browser/themes/osx/browser.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/browser/themes/osx/browser.css b/browser/themes/osx/browser.css index cb55ec730d08..c47ffbdb3164 100644 --- a/browser/themes/osx/browser.css +++ b/browser/themes/osx/browser.css @@ -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; From e8d20bd42cdee3d9e95d41e7836771f43f845f0b Mon Sep 17 00:00:00 2001 From: James Hugman Date: Tue, 30 Jun 2015 11:03:33 -0700 Subject: [PATCH 06/16] =?UTF-8?q?Bug=201132922=20=E2=80=93=20Disable=20voi?= =?UTF-8?q?ce=20search=20for=20pre=20ICS=20devices.=20r=3Dliuche?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --HG-- extra : transplant_source : %E8-%1A%03c%0C%7D%A349-f%7B%7D%F1%E5%12%FFs%9E --- mobile/android/base/util/InputOptionsUtils.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/mobile/android/base/util/InputOptionsUtils.java b/mobile/android/base/util/InputOptionsUtils.java index 684c42d8c42d..529ff5e35dc2 100644 --- a/mobile/android/base/util/InputOptionsUtils.java +++ b/mobile/android/base/util/InputOptionsUtils.java @@ -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; } From 3f8566884e47e90a9e8ab5350c2818ae92460287 Mon Sep 17 00:00:00 2001 From: Shu-yu Guo Date: Wed, 24 Jun 2015 17:06:00 -0400 Subject: [PATCH 07/16] Bug 1177293 - Don't overcount samples when flattening recursion. r=jsantell --- browser/devtools/performance/modules/logic/tree-model.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/browser/devtools/performance/modules/logic/tree-model.js b/browser/devtools/performance/modules/logic/tree-model.js index e3f6f9995865..1c3e2e326dc5 100644 --- a/browser/devtools/performance/modules/logic/tree-model.js +++ b/browser/devtools/performance/modules/logic/tree-model.js @@ -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; From e6516f7c8490eda93ec11d9db6f243ab95e6d4d1 Mon Sep 17 00:00:00 2001 From: Edouard Oger Date: Mon, 13 Jul 2015 15:31:00 -0400 Subject: [PATCH 08/16] Bug 1183352 - In the Sync Preferences, put the Terms of Service and Privacy Notice on the same line. r=markh --- browser/components/preferences/in-content/sync.xul | 4 ++-- browser/themes/shared/incontentprefs/preferences.inc.css | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/browser/components/preferences/in-content/sync.xul b/browser/components/preferences/in-content/sync.xul index 71eede8b5255..f2ec1330f835 100644 --- a/browser/components/preferences/in-content/sync.xul +++ b/browser/components/preferences/in-content/sync.xul @@ -343,13 +343,13 @@ - + - + diff --git a/browser/themes/shared/incontentprefs/preferences.inc.css b/browser/themes/shared/incontentprefs/preferences.inc.css index 307fe53083e4..495635170e02 100644 --- a/browser/themes/shared/incontentprefs/preferences.inc.css +++ b/browser/themes/shared/incontentprefs/preferences.inc.css @@ -400,3 +400,7 @@ description > html|a { background-color: transparent; opacity: 1; } + +#tosPP-small-ToS { + margin-right: 3em; +} From 5551466ab7ca5024fd68bd24ed2996e1779360f5 Mon Sep 17 00:00:00 2001 From: Jordan Santell Date: Mon, 13 Jul 2015 13:20:00 -0400 Subject: [PATCH 09/16] Bug 1159389 - Migrate profiler actor to use new form of protocol.js actors, and become a standalone module consumed by actors. r=vp, r=jryans --- .../performance/modules/logic/actors.js | 49 +- .../modules/logic/compatibility.js | 64 +-- .../test/browser_markers-cycle-collection.js | 4 + .../performance/test/browser_markers-gc.js | 3 + .../test/browser_markers-parse-html.js | 3 + .../test/browser_markers-styles.js | 3 + .../test/browser_markers-timestamp.js | 3 + .../test/browser_perf-compatibility-01.js | 3 + .../test/browser_perf-compatibility-03.js | 3 + .../test/browser_perf-compatibility-06.js | 3 + .../test/browser_perf-data-massaging-01.js | 2 - .../performance/test/browser_perf-front-01.js | 3 + .../performance/test/browser_perf-front-02.js | 3 + .../browser_perf-front-basic-profiler-01.js | 3 + .../browser_perf-front-basic-timeline-01.js | 3 + .../test/browser_perf-recording-model-02.js | 3 + toolkit/devtools/server/actors/profiler.js | 539 +++++++----------- toolkit/devtools/server/actors/root.js | 2 +- .../tests/unit/test_profiler_events-01.js | 132 ++--- .../tests/unit/test_profiler_events-02.js | 69 --- .../devtools/server/tests/unit/xpcshell.ini | 1 - toolkit/devtools/shared/moz.build | 1 + toolkit/devtools/shared/profiler.js | 438 ++++++++++++++ 23 files changed, 762 insertions(+), 575 deletions(-) delete mode 100644 toolkit/devtools/server/tests/unit/test_profiler_events-02.js create mode 100644 toolkit/devtools/shared/profiler.js diff --git a/browser/devtools/performance/modules/logic/actors.js b/browser/devtools/performance/modules/logic/actors.js index 7a8b21e59177..0cefcac59d68 100644 --- a/browser/devtools/performance/modules/logic/actors.js +++ b/browser/devtools/performance/modules/logic/actors.js @@ -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; diff --git a/browser/devtools/performance/modules/logic/compatibility.js b/browser/devtools/performance/modules/logic/compatibility.js index d9a2d102c175..3fbe5f3db634 100644 --- a/browser/devtools/performance/modules/logic/compatibility.js +++ b/browser/devtools/performance/modules/logic/compatibility.js @@ -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} + * 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; diff --git a/browser/devtools/performance/test/browser_markers-cycle-collection.js b/browser/devtools/performance/test/browser_markers-cycle-collection.js index eb75544f0a2e..a845019db0fa 100644 --- a/browser/devtools/performance/test/browser_markers-cycle-collection.js +++ b/browser/devtools/performance/test/browser_markers-cycle-collection.js @@ -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(); } diff --git a/browser/devtools/performance/test/browser_markers-gc.js b/browser/devtools/performance/test/browser_markers-gc.js index 8e46d952fd8e..0d87f5985c49 100644 --- a/browser/devtools/performance/test/browser_markers-gc.js +++ b/browser/devtools/performance/test/browser_markers-gc.js @@ -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(); diff --git a/browser/devtools/performance/test/browser_markers-parse-html.js b/browser/devtools/performance/test/browser_markers-parse-html.js index 10aba17c571d..9a537e2d8ee9 100644 --- a/browser/devtools/performance/test/browser_markers-parse-html.js +++ b/browser/devtools/performance/test/browser_markers-parse-html.js @@ -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(); diff --git a/browser/devtools/performance/test/browser_markers-styles.js b/browser/devtools/performance/test/browser_markers-styles.js index 917a4b3f1e71..3a935c91dbb7 100644 --- a/browser/devtools/performance/test/browser_markers-styles.js +++ b/browser/devtools/performance/test/browser_markers-styles.js @@ -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(); diff --git a/browser/devtools/performance/test/browser_markers-timestamp.js b/browser/devtools/performance/test/browser_markers-timestamp.js index 86cc6d545b16..de111a4075e1 100644 --- a/browser/devtools/performance/test/browser_markers-timestamp.js +++ b/browser/devtools/performance/test/browser_markers-timestamp.js @@ -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(); diff --git a/browser/devtools/performance/test/browser_perf-compatibility-01.js b/browser/devtools/performance/test/browser_perf-compatibility-01.js index dc45184d04cd..9adf9474f8f3 100644 --- a/browser/devtools/performance/test/browser_perf-compatibility-01.js +++ b/browser/devtools/performance/test/browser_perf-compatibility-01.js @@ -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(); } diff --git a/browser/devtools/performance/test/browser_perf-compatibility-03.js b/browser/devtools/performance/test/browser_perf-compatibility-03.js index e639232565f4..0b449bb91aa3 100644 --- a/browser/devtools/performance/test/browser_perf-compatibility-03.js +++ b/browser/devtools/performance/test/browser_perf-compatibility-03.js @@ -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(); } diff --git a/browser/devtools/performance/test/browser_perf-compatibility-06.js b/browser/devtools/performance/test/browser_perf-compatibility-06.js index 834ef76b7350..f1ebe60679c6 100644 --- a/browser/devtools/performance/test/browser_perf-compatibility-06.js +++ b/browser/devtools/performance/test/browser_perf-compatibility-06.js @@ -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(); } diff --git a/browser/devtools/performance/test/browser_perf-data-massaging-01.js b/browser/devtools/performance/test/browser_perf-data-massaging-01.js index 8b957a089d65..0d458738ffd8 100644 --- a/browser/devtools/performance/test/browser_perf-data-massaging-01.js +++ b/browser/devtools/performance/test/browser_perf-data-massaging-01.js @@ -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."); diff --git a/browser/devtools/performance/test/browser_perf-front-01.js b/browser/devtools/performance/test/browser_perf-front-01.js index dcc2ec7869d1..9c09b33deb9a 100644 --- a/browser/devtools/performance/test/browser_perf-front-01.js +++ b/browser/devtools/performance/test/browser_perf-front-01.js @@ -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(); } diff --git a/browser/devtools/performance/test/browser_perf-front-02.js b/browser/devtools/performance/test/browser_perf-front-02.js index 96d23fc61195..c0de1e11abda 100644 --- a/browser/devtools/performance/test/browser_perf-front-02.js +++ b/browser/devtools/performance/test/browser_perf-front-02.js @@ -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(); } diff --git a/browser/devtools/performance/test/browser_perf-front-basic-profiler-01.js b/browser/devtools/performance/test/browser_perf-front-basic-profiler-01.js index abd1994ed83f..a50b2397250a 100644 --- a/browser/devtools/performance/test/browser_perf-front-basic-profiler-01.js +++ b/browser/devtools/performance/test/browser_perf-front-basic-profiler-01.js @@ -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(); } diff --git a/browser/devtools/performance/test/browser_perf-front-basic-timeline-01.js b/browser/devtools/performance/test/browser_perf-front-basic-timeline-01.js index 0fc6d3b49976..b860acb19e4c 100644 --- a/browser/devtools/performance/test/browser_perf-front-basic-timeline-01.js +++ b/browser/devtools/performance/test/browser_perf-front-basic-timeline-01.js @@ -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(); diff --git a/browser/devtools/performance/test/browser_perf-recording-model-02.js b/browser/devtools/performance/test/browser_perf-recording-model-02.js index d6e2858ace0b..1f691fcd98fe 100644 --- a/browser/devtools/performance/test/browser_perf-recording-model-02.js +++ b/browser/devtools/performance/test/browser_perf-recording-model-02.js @@ -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(); } diff --git a/toolkit/devtools/server/actors/profiler.js b/toolkit/devtools/server/actors/profiler.js index 80a22931ae11..67957249a4e2 100644 --- a/toolkit/devtools/server/actors/profiler.js +++ b/toolkit/devtools/server/actors/profiler.js @@ -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 ( { - 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 -}; + }, +}); diff --git a/toolkit/devtools/server/actors/root.js b/toolkit/devtools/server/actors/root.js index e84822b5f1c6..3a42463cc4b1 100644 --- a/toolkit/devtools/server/actors/root.js +++ b/toolkit/devtools/server/actors/root.js @@ -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, }, /** diff --git a/toolkit/devtools/server/tests/unit/test_profiler_events-01.js b/toolkit/devtools/server/tests/unit/test_profiler_events-01.js index 7b11631cb455..ec05b79c678d 100644 --- a/toolkit/devtools/server/tests/unit/test_profiler_events-01.js +++ b/toolkit/devtools/server/tests/unit/test_profiler_events-01.js @@ -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; } diff --git a/toolkit/devtools/server/tests/unit/test_profiler_events-02.js b/toolkit/devtools/server/tests/unit/test_profiler_events-02.js deleted file mode 100644 index 11984f2571ab..000000000000 --- a/toolkit/devtools/server/tests/unit/test_profiler_events-02.js +++ /dev/null @@ -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); - }); -} diff --git a/toolkit/devtools/server/tests/unit/xpcshell.ini b/toolkit/devtools/server/tests/unit/xpcshell.ini index a0a2e526a62f..07690cdf4491 100644 --- a/toolkit/devtools/server/tests/unit/xpcshell.ini +++ b/toolkit/devtools/server/tests/unit/xpcshell.ini @@ -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] diff --git a/toolkit/devtools/shared/moz.build b/toolkit/devtools/shared/moz.build index 7fc0f8eccefb..86e4c1b69941 100644 --- a/toolkit/devtools/shared/moz.build +++ b/toolkit/devtools/shared/moz.build @@ -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', diff --git a/toolkit/devtools/shared/profiler.js b/toolkit/devtools/shared/profiler.js new file mode 100644 index 000000000000..6e5961ab0f42 --- /dev/null +++ b/toolkit/devtools/shared/profiler.js @@ -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} features [optional] + * @param {Array} 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} 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} 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); +} From cf2b90efc1117cb9c9b0277ac22d3b1acfcd8008 Mon Sep 17 00:00:00 2001 From: Hubert B Manilla Date: Mon, 13 Jul 2015 08:59:00 -0400 Subject: [PATCH 10/16] Bug 882790 - Add toolbar button for pause on exceptions. r=fitzgen --- browser/devtools/debugger/debugger.xul | 15 +- .../test/browser_dbg_pause-exceptions-01.js | 153 ++++++++++-------- .../test/browser_dbg_pause-exceptions-02.js | 111 +++++-------- .../test/browser_dbg_source-maps-04.js | 125 ++++++-------- .../devtools/debugger/views/options-view.js | 30 ---- .../devtools/debugger/views/sources-view.js | 44 +++++ .../debugger/views/stack-frames-view.js | 3 +- .../chrome/browser/devtools/debugger.dtd | 10 -- .../browser/devtools/debugger.properties | 15 ++ browser/themes/linux/jar.mn | 6 + browser/themes/osx/jar.mn | 6 + .../themes/shared/devtools/debugger.inc.css | 30 ++++ .../images/debugger-no-pause-exceptions.png | Bin 0 -> 1129 bytes .../debugger-no-pause-exceptions@2x.png | Bin 0 -> 4217 bytes .../images/debugger-pause-all-exceptions.png | Bin 0 -> 1129 bytes .../debugger-pause-all-exceptions@2x.png | Bin 0 -> 4217 bytes .../debugger-pause-uncaught-exceptions.png | Bin 0 -> 1129 bytes .../debugger-pause-uncaught-exceptions@2x.png | Bin 0 -> 4217 bytes browser/themes/windows/jar.mn | 6 + 19 files changed, 290 insertions(+), 264 deletions(-) create mode 100644 browser/themes/shared/devtools/images/debugger-no-pause-exceptions.png create mode 100644 browser/themes/shared/devtools/images/debugger-no-pause-exceptions@2x.png create mode 100644 browser/themes/shared/devtools/images/debugger-pause-all-exceptions.png create mode 100644 browser/themes/shared/devtools/images/debugger-pause-all-exceptions@2x.png create mode 100644 browser/themes/shared/devtools/images/debugger-pause-uncaught-exceptions.png create mode 100644 browser/themes/shared/devtools/images/debugger-pause-uncaught-exceptions@2x.png diff --git a/browser/devtools/debugger/debugger.xul b/browser/devtools/debugger/debugger.xul index 0f50a4c90aee..9d7f5aa2febc 100644 --- a/browser/devtools/debugger/debugger.xul +++ b/browser/devtools/debugger/debugger.xul @@ -40,7 +40,7 @@