From cbfcc1eb646eac871e2b2746526bf5a4de9e2cdd Mon Sep 17 00:00:00 2001 From: Margaret Leibovic Date: Thu, 12 Jun 2014 09:31:47 -0700 Subject: [PATCH 01/32] Bug 1019735 - Hide the button toast if the user touches outside of it. r=lucasr * * * Bug 1019735 - (Part 2) Don't do unnecssary work on every touch --- mobile/android/base/BrowserApp.java | 13 +++++++++++-- mobile/android/base/widget/ButtonToast.java | 6 ++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/mobile/android/base/BrowserApp.java b/mobile/android/base/BrowserApp.java index 68867ecdf675..e0a761ce54c8 100644 --- a/mobile/android/base/BrowserApp.java +++ b/mobile/android/base/BrowserApp.java @@ -487,7 +487,7 @@ abstract public class BrowserApp extends GeckoApp Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.INTENT); } - ((GeckoApp.MainLayout) mMainLayout).setTouchEventInterceptor(new HideTabsTouchListener()); + ((GeckoApp.MainLayout) mMainLayout).setTouchEventInterceptor(new HideOnTouchListener()); ((GeckoApp.MainLayout) mMainLayout).setMotionEventInterceptor(new MotionEventInterceptor() { @Override public boolean onInterceptMotionEvent(View view, MotionEvent event) { @@ -1985,11 +1985,20 @@ abstract public class BrowserApp extends GeckoApp mBrowserSearch.setUserVisibleHint(false); } - private class HideTabsTouchListener implements TouchEventInterceptor { + /** + * Hides certain UI elements (e.g. button toast, tabs tray) when the + * user touches the main layout. + */ + private class HideOnTouchListener implements TouchEventInterceptor { private boolean mIsHidingTabs = false; @Override public boolean onInterceptTouchEvent(View view, MotionEvent event) { + // Only try to hide the button toast if it's already inflated. + if (mToast != null) { + mToast.hide(false, ButtonToast.ReasonHidden.TOUCH_OUTSIDE); + } + // We need to account for scroll state for the touched view otherwise // tapping on an "empty" part of the view will still be considered a // valid touch event. diff --git a/mobile/android/base/widget/ButtonToast.java b/mobile/android/base/widget/ButtonToast.java index 05f258eaa750..9c19731f96a7 100644 --- a/mobile/android/base/widget/ButtonToast.java +++ b/mobile/android/base/widget/ButtonToast.java @@ -42,6 +42,7 @@ public class ButtonToast { public enum ReasonHidden { CLICKED, + TOUCH_OUTSIDE, TIMEOUT, REPLACED, STARTUP @@ -129,6 +130,11 @@ public class ButtonToast { } public void hide(boolean immediate, ReasonHidden reason) { + // There's nothing to do if the view is already hidden. + if (mView.getVisibility() == View.GONE) { + return; + } + if (mCurrentToast != null && mCurrentToast.listener != null) { mCurrentToast.listener.onToastHidden(reason); } From 03325abd9932e2e6a39e338b15530b8501d68840 Mon Sep 17 00:00:00 2001 From: Lucas Rocha Date: Mon, 16 Jun 2014 17:49:37 +0100 Subject: [PATCH 02/32] Bug 1023306 - Always dismiss edit mode when opening external URLs (r=margaret) --- mobile/android/base/BrowserApp.java | 27 ++++++++++++++-------- mobile/android/base/TelemetryContract.java | 3 +++ 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/mobile/android/base/BrowserApp.java b/mobile/android/base/BrowserApp.java index e0a761ce54c8..3be3f0167219 100644 --- a/mobile/android/base/BrowserApp.java +++ b/mobile/android/base/BrowserApp.java @@ -2636,10 +2636,25 @@ abstract public class BrowserApp extends GeckoApp */ @Override protected void onNewIntent(Intent intent) { - super.onNewIntent(intent); - String action = intent.getAction(); + final boolean isViewAction = Intent.ACTION_VIEW.equals(action); + final boolean isBookmarkAction = GeckoApp.ACTION_BOOKMARK.equals(action); + + if (mInitialized && (isViewAction || isBookmarkAction)) { + // Dismiss editing mode if the user is loading a URL from an external app. + mBrowserToolbar.cancelEdit(); + + // GeckoApp.ACTION_BOOKMARK means we're opening a bookmark that + // was added to Android's homescreen. + final TelemetryContract.Method method = + (isViewAction ? TelemetryContract.Method.INTENT : TelemetryContract.Method.HOMESCREEN); + + Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, method); + } + + super.onNewIntent(intent); + if (AppConstants.MOZ_ANDROID_BEAM && Build.VERSION.SDK_INT >= 10 && NfcAdapter.ACTION_NDEF_DISCOVERED.equals(action)) { String uri = intent.getDataString(); GeckoAppShell.sendEventToGecko(GeckoEvent.createURILoadEvent(uri)); @@ -2649,14 +2664,6 @@ abstract public class BrowserApp extends GeckoApp return; } - if (Intent.ACTION_VIEW.equals(action)) { - // Dismiss editing mode if the user is loading a URL from an external app. - mBrowserToolbar.cancelEdit(); - - Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.INTENT); - return; - } - // Only solicit feedback when the app has been launched from the icon shortcut. if (!Intent.ACTION_MAIN.equals(action)) { return; diff --git a/mobile/android/base/TelemetryContract.java b/mobile/android/base/TelemetryContract.java index cde702372fae..82790f73e175 100644 --- a/mobile/android/base/TelemetryContract.java +++ b/mobile/android/base/TelemetryContract.java @@ -126,6 +126,9 @@ public interface TelemetryContract { // Action occurred via an intent. INTENT("intent"), + // Action occurred via a homescreen launcher. + HOMESCREEN("homescreen"), + // Action triggered from a list. LIST("list"), From 7c70d0522cbdcaaa8c790624eebc0b577704f4a1 Mon Sep 17 00:00:00 2001 From: Mike Conley Date: Fri, 13 Jun 2014 14:22:06 -0400 Subject: [PATCH 03/32] Bug 988133 - Workaround crashes in nsDocShellEditorData::ReattachToWindow() when doing view source in a remote tab. r=felipe. This bug works around the crash by not retrieving the document from the cache, but by hitting the network again for the document source. That's clearly not ideal, but the crash is worse. This is a band-aid solution until we can get the cache retrieval working properly. This workaround should not ride the trains to release. --- browser/base/content/browser.js | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js index ba1ee7920ffd..b3a148a304e6 100644 --- a/browser/base/content/browser.js +++ b/browser/base/content/browser.js @@ -2090,10 +2090,23 @@ function BrowserViewSourceOfDocument(aDocument) // view-source to access the cached copy of the content rather than // refetching it from the network... // - try{ - var PageLoader = webNav.QueryInterface(Components.interfaces.nsIWebPageDescriptor); + try { - pageCookie = PageLoader.currentDescriptor; +#ifdef E10S_TESTING_ONLY + // Workaround for bug 988133, which causes a crash if we attempt to load + // the document from the cache when the document is a CPOW (which occurs + // if we're using remote tabs). This causes us to reload the document from + // the network in this case, so it's not a permanent solution, hence hiding + // it behind the E10S_TESTING_ONLY ifdef. This is just a band-aid fix until + // we can find something better - see bug 1025146. + if (!Cu.isCrossProcessWrapper(aDocument)) { +#endif + var PageLoader = webNav.QueryInterface(Components.interfaces.nsIWebPageDescriptor); + + pageCookie = PageLoader.currentDescriptor; +#ifdef E10S_TESTING_ONLY + } +#endif } catch(err) { // If no page descriptor is available, just use the view-source URL... } From 47e6eb1dac07b703d36c2a31a57c0e8e647008f4 Mon Sep 17 00:00:00 2001 From: Gijs Kruitbosch Date: Sun, 15 Jun 2014 14:17:00 +0100 Subject: [PATCH 04/32] Bug 1025483 - fire 'input' event for password autocompletes, r=MattN --HG-- extra : rebase_source : 2d972f2d8a6ccf78d5d43716850ae494cb73fe7d --- .../passwordmgr/LoginManagerContent.jsm | 4 +- .../components/passwordmgr/test/mochitest.ini | 1 + .../passwordmgr/test/test_input_events.html | 74 +++++++++++++++++++ 3 files changed, 77 insertions(+), 2 deletions(-) create mode 100644 toolkit/components/passwordmgr/test/test_input_events.html diff --git a/toolkit/components/passwordmgr/LoginManagerContent.jsm b/toolkit/components/passwordmgr/LoginManagerContent.jsm index 80828d8d4ddc..b6d450da3033 100644 --- a/toolkit/components/passwordmgr/LoginManagerContent.jsm +++ b/toolkit/components/passwordmgr/LoginManagerContent.jsm @@ -712,10 +712,10 @@ var LoginManagerContent = { usernameField.value.toLowerCase() == selectedLogin.username.toLowerCase()); if (!disabledOrReadOnly && !userEnteredDifferentCase) { - usernameField.value = selectedLogin.username; + usernameField.setUserInput(selectedLogin.username); } } - passwordField.value = selectedLogin.password; + passwordField.setUserInput(selectedLogin.password); didFillForm = true; } else if (selectedLogin && !autofillForm) { // For when autofillForm is false, but we still have the information diff --git a/toolkit/components/passwordmgr/test/mochitest.ini b/toolkit/components/passwordmgr/test/mochitest.ini index a93ba68fc54f..506981a5a1e1 100644 --- a/toolkit/components/passwordmgr/test/mochitest.ini +++ b/toolkit/components/passwordmgr/test/mochitest.ini @@ -60,6 +60,7 @@ skip-if = true skip-if = toolkit == 'android' #TIMED_OUT [test_bug_654348.html] [test_bug_776171.html] +[test_input_events.html] [test_master_password.html] skip-if = toolkit == 'android' #TIMED_OUT [test_master_password_cleanup.html] diff --git a/toolkit/components/passwordmgr/test/test_input_events.html b/toolkit/components/passwordmgr/test/test_input_events.html new file mode 100644 index 000000000000..83274dba64b9 --- /dev/null +++ b/toolkit/components/passwordmgr/test/test_input_events.html @@ -0,0 +1,74 @@ + + + + Test for input events in Login Manager + + + + + +Login Manager test: input events should fire. + + + +

+ + +

+
+

From b8bdba9c9c2ad4753169f8919e2df378727db940 Mon Sep 17 00:00:00 2001
From: Jordan Santell 
Date: Fri, 13 Jun 2014 16:48:30 -0700
Subject: [PATCH 05/32] Bug 1025311 - Add telemetry for canvas debugger.
 r=vp,miker

From eb3ed1cf3ff07a530297c74976a1d96ca8b5bd79 Mon Sep 17 00:00:00 2001
---
 browser/devtools/canvasdebugger/canvasdebugger.js  |   4 +
 browser/devtools/shared/telemetry.js               |   5 +
 browser/devtools/shared/test/browser.ini           |   1 +
 ...browser_telemetry_toolboxtabs_canvasdebugger.js | 117 +++++++++++++++++++++
 toolkit/components/telemetry/Histograms.json       |  17 +++
 5 files changed, 144 insertions(+)
 create mode 100644 browser/devtools/shared/test/browser_telemetry_toolboxtabs_canvasdebugger.js
---
 .../devtools/canvasdebugger/canvasdebugger.js |   4 +
 browser/devtools/shared/telemetry.js          |   5 +
 browser/devtools/shared/test/browser.ini      |   1 +
 ...er_telemetry_toolboxtabs_canvasdebugger.js | 117 ++++++++++++++++++
 toolkit/components/telemetry/Histograms.json  |  17 +++
 5 files changed, 144 insertions(+)
 create mode 100644 browser/devtools/shared/test/browser_telemetry_toolboxtabs_canvasdebugger.js

diff --git a/browser/devtools/canvasdebugger/canvasdebugger.js b/browser/devtools/canvasdebugger/canvasdebugger.js
index 9211919348a2..b8131542f06f 100644
--- a/browser/devtools/canvasdebugger/canvasdebugger.js
+++ b/browser/devtools/canvasdebugger/canvasdebugger.js
@@ -15,6 +15,8 @@ const promise = Cu.import("resource://gre/modules/Promise.jsm", {}).Promise;
 const EventEmitter = require("devtools/toolkit/event-emitter");
 const { CallWatcherFront } = require("devtools/server/actors/call-watcher");
 const { CanvasFront } = require("devtools/server/actors/canvas");
+const Telemetry = require("devtools/shared/telemetry");
+const telemetry = new Telemetry();
 
 XPCOMUtils.defineLazyModuleGetter(this, "Task",
   "resource://gre/modules/Task.jsm");
@@ -119,6 +121,7 @@ let EventsHandler = {
    * Listen for events emitted by the current tab target.
    */
   initialize: function() {
+    telemetry.toolOpened("canvasdebugger");
     this._onTabNavigated = this._onTabNavigated.bind(this);
     gTarget.on("will-navigate", this._onTabNavigated);
     gTarget.on("navigate", this._onTabNavigated);
@@ -128,6 +131,7 @@ let EventsHandler = {
    * Remove events emitted by the current tab target.
    */
   destroy: function() {
+    telemetry.toolClosed("canvasdebugger");
     gTarget.off("will-navigate", this._onTabNavigated);
     gTarget.off("navigate", this._onTabNavigated);
   },
diff --git a/browser/devtools/shared/telemetry.js b/browser/devtools/shared/telemetry.js
index b5832b3a368f..02143ef38de8 100644
--- a/browser/devtools/shared/telemetry.js
+++ b/browser/devtools/shared/telemetry.js
@@ -125,6 +125,11 @@ Telemetry.prototype = {
       userHistogram: "DEVTOOLS_SHADEREDITOR_OPENED_PER_USER_FLAG",
       timerHistogram: "DEVTOOLS_SHADEREDITOR_TIME_ACTIVE_SECONDS"
     },
+    canvasdebugger: {
+      histogram: "DEVTOOLS_CANVASDEBUGGER_OPENED_BOOLEAN",
+      userHistogram: "DEVTOOLS_CANVASDEBUGGER_OPENED_PER_USER_FLAG",
+      timerHistogram: "DEVTOOLS_CANVASDEBUGGER_TIME_ACTIVE_SECONDS"
+    },
     jsprofiler: {
       histogram: "DEVTOOLS_JSPROFILER_OPENED_BOOLEAN",
       userHistogram: "DEVTOOLS_JSPROFILER_OPENED_PER_USER_FLAG",
diff --git a/browser/devtools/shared/test/browser.ini b/browser/devtools/shared/test/browser.ini
index 69af2927ecf6..08cb32f960da 100644
--- a/browser/devtools/shared/test/browser.ini
+++ b/browser/devtools/shared/test/browser.ini
@@ -38,6 +38,7 @@ support-files =
 [browser_telemetry_sidebar.js]
 [browser_telemetry_toolbox.js]
 [browser_telemetry_toolboxtabs_inspector.js]
+[browser_telemetry_toolboxtabs_canvasdebugger.js]
 [browser_telemetry_toolboxtabs_jsdebugger.js]
 [browser_telemetry_toolboxtabs_jsprofiler.js]
 [browser_telemetry_toolboxtabs_netmonitor.js]
diff --git a/browser/devtools/shared/test/browser_telemetry_toolboxtabs_canvasdebugger.js b/browser/devtools/shared/test/browser_telemetry_toolboxtabs_canvasdebugger.js
new file mode 100644
index 000000000000..7735ef87bb79
--- /dev/null
+++ b/browser/devtools/shared/test/browser_telemetry_toolboxtabs_canvasdebugger.js
@@ -0,0 +1,117 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const TEST_URI = "data:text/html;charset=utf-8,

browser_telemetry_toolboxtabs_canvasdebugger.js

"; + +// Because we need to gather stats for the period of time that a tool has been +// opened we make use of setTimeout() to create tool active times. +const TOOL_DELAY = 200; + +let {Promise: promise} = Cu.import("resource://gre/modules/devtools/deprecated-sync-thenables.js", {}); +let {Services} = Cu.import("resource://gre/modules/Services.jsm", {}); + +let require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require; +let Telemetry = require("devtools/shared/telemetry"); +let originalPref = Services.prefs.getBoolPref("devtools.canvasdebugger.enabled"); +Services.prefs.setBoolPref("devtools.canvasdebugger.enabled", true); + +function init() { + Telemetry.prototype.telemetryInfo = {}; + Telemetry.prototype._oldlog = Telemetry.prototype.log; + Telemetry.prototype.log = function(histogramId, value) { + if (!this.telemetryInfo) { + // Can be removed when Bug 992911 lands (see Bug 1011652 Comment 10) + return; + } + if (histogramId) { + if (!this.telemetryInfo[histogramId]) { + this.telemetryInfo[histogramId] = []; + } + + this.telemetryInfo[histogramId].push(value); + } + } + + openToolboxTabTwice("canvasdebugger", false); +} + +function openToolboxTabTwice(id, secondPass) { + let target = TargetFactory.forTab(gBrowser.selectedTab); + + gDevTools.showToolbox(target, id).then(function(toolbox) { + info("Toolbox tab " + id + " opened"); + + toolbox.once("destroyed", function() { + if (secondPass) { + checkResults(); + } else { + openToolboxTabTwice(id, true); + } + }); + // We use a timeout to check the tools active time + setTimeout(function() { + gDevTools.closeToolbox(target); + }, TOOL_DELAY); + }).then(null, reportError); +} + +function checkResults() { + let result = Telemetry.prototype.telemetryInfo; + + for (let [histId, value] of Iterator(result)) { + if (histId.endsWith("OPENED_PER_USER_FLAG")) { + ok(value.length === 1 && value[0] === true, + "Per user value " + histId + " has a single value of true"); + } else if (histId.endsWith("OPENED_BOOLEAN")) { + ok(value.length > 1, histId + " has more than one entry"); + + let okay = value.every(function(element) { + return element === true; + }); + + ok(okay, "All " + histId + " entries are === true"); + } else if (histId.endsWith("TIME_ACTIVE_SECONDS")) { + ok(value.length > 1, histId + " has more than one entry"); + + let okay = value.every(function(element) { + return element > 0; + }); + + ok(okay, "All " + histId + " entries have time > 0"); + } + } + + finishUp(); +} + +function reportError(error) { + let stack = " " + error.stack.replace(/\n?.*?@/g, "\n JS frame :: "); + + ok(false, "ERROR: " + error + " at " + error.fileName + ":" + + error.lineNumber + "\n\nStack trace:" + stack); + finishUp(); +} + +function finishUp() { + gBrowser.removeCurrentTab(); + + Telemetry.prototype.log = Telemetry.prototype._oldlog; + delete Telemetry.prototype._oldlog; + delete Telemetry.prototype.telemetryInfo; + Services.prefs.setBoolPref("devtools.canvasdebugger.enabled", originalPref); + + TargetFactory = Services = promise = require = null; + + finish(); +} + +function test() { + waitForExplicitFinish(); + gBrowser.selectedTab = gBrowser.addTab(); + gBrowser.selectedBrowser.addEventListener("load", function() { + gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true); + waitForFocus(init, content); + }, true); + + content.location = TEST_URI; +} diff --git a/toolkit/components/telemetry/Histograms.json b/toolkit/components/telemetry/Histograms.json index 2ee772bbe98a..6342fb0e929b 100644 --- a/toolkit/components/telemetry/Histograms.json +++ b/toolkit/components/telemetry/Histograms.json @@ -5709,6 +5709,11 @@ "kind": "boolean", "description": "How many times has the devtool's Shader Editor been opened?" }, + "DEVTOOLS_CANVASDEBUGGER_OPENED_BOOLEAN": { + "expires_in_version": "never", + "kind": "boolean", + "description": "How many times has the devtool's Canvas Debugger been opened?" + }, "DEVTOOLS_JSPROFILER_OPENED_BOOLEAN": { "expires_in_version": "never", "kind": "boolean", @@ -5814,6 +5819,11 @@ "kind": "flag", "description": "How many users have opened the devtool's Shader Editor?" }, + "DEVTOOLS_CANVASDEBUGGER_OPENED_PER_USER_FLAG": { + "expires_in_version": "never", + "kind": "flag", + "description": "How many users have opened the devtool's Canvas Debugger?" + }, "DEVTOOLS_JSPROFILER_OPENED_PER_USER_FLAG": { "expires_in_version": "never", "kind": "flag", @@ -5945,6 +5955,13 @@ "n_buckets": 100, "description": "How long has the Shader Editor been active (seconds)" }, + "DEVTOOLS_CANVASDEBUGGER_TIME_ACTIVE_SECONDS": { + "expires_in_version": "never", + "kind": "exponential", + "high": "10000000", + "n_buckets": 100, + "description": "How long has the Canvas Debugger been active (seconds)" + }, "DEVTOOLS_JSPROFILER_TIME_ACTIVE_SECONDS": { "expires_in_version": "never", "kind": "exponential", From 566497d6849283d093e65a2076508e1dbe86a970 Mon Sep 17 00:00:00 2001 From: Jordan Santell Date: Fri, 13 Jun 2014 16:19:26 -0700 Subject: [PATCH 06/32] Bug 1025310 - Add telemetry to the web audio editor. r=vp,miker From 1e28c92f25088f7279686c1a2af68ea03a050d9c Mon Sep 17 00:00:00 2001 --- browser/devtools/shared/telemetry.js | 5 + browser/devtools/shared/test/browser.ini | 1 + ...browser_telemetry_toolboxtabs_webaudioeditor.js | 118 +++++++++++++++++++++ .../webaudioeditor/webaudioeditor-controller.js | 4 + toolkit/components/telemetry/Histograms.json | 17 +++ 5 files changed, 145 insertions(+) create mode 100644 browser/devtools/shared/test/browser_telemetry_toolboxtabs_webaudioeditor.js --- browser/devtools/shared/telemetry.js | 5 + browser/devtools/shared/test/browser.ini | 1 + ...er_telemetry_toolboxtabs_webaudioeditor.js | 118 ++++++++++++++++++ .../webaudioeditor-controller.js | 4 + toolkit/components/telemetry/Histograms.json | 17 +++ 5 files changed, 145 insertions(+) create mode 100644 browser/devtools/shared/test/browser_telemetry_toolboxtabs_webaudioeditor.js diff --git a/browser/devtools/shared/telemetry.js b/browser/devtools/shared/telemetry.js index 02143ef38de8..29ef0cc9fe52 100644 --- a/browser/devtools/shared/telemetry.js +++ b/browser/devtools/shared/telemetry.js @@ -125,6 +125,11 @@ Telemetry.prototype = { userHistogram: "DEVTOOLS_SHADEREDITOR_OPENED_PER_USER_FLAG", timerHistogram: "DEVTOOLS_SHADEREDITOR_TIME_ACTIVE_SECONDS" }, + webaudioeditor: { + histogram: "DEVTOOLS_WEBAUDIOEDITOR_OPENED_BOOLEAN", + userHistogram: "DEVTOOLS_WEBAUDIOEDITOR_OPENED_PER_USER_FLAG", + timerHistogram: "DEVTOOLS_WEBAUDIOEDITOR_TIME_ACTIVE_SECONDS" + }, canvasdebugger: { histogram: "DEVTOOLS_CANVASDEBUGGER_OPENED_BOOLEAN", userHistogram: "DEVTOOLS_CANVASDEBUGGER_OPENED_PER_USER_FLAG", diff --git a/browser/devtools/shared/test/browser.ini b/browser/devtools/shared/test/browser.ini index 08cb32f960da..a4f2b3cd26e0 100644 --- a/browser/devtools/shared/test/browser.ini +++ b/browser/devtools/shared/test/browser.ini @@ -38,6 +38,7 @@ support-files = [browser_telemetry_sidebar.js] [browser_telemetry_toolbox.js] [browser_telemetry_toolboxtabs_inspector.js] +[browser_telemetry_toolboxtabs_webaudioeditor.js] [browser_telemetry_toolboxtabs_canvasdebugger.js] [browser_telemetry_toolboxtabs_jsdebugger.js] [browser_telemetry_toolboxtabs_jsprofiler.js] diff --git a/browser/devtools/shared/test/browser_telemetry_toolboxtabs_webaudioeditor.js b/browser/devtools/shared/test/browser_telemetry_toolboxtabs_webaudioeditor.js new file mode 100644 index 000000000000..f29947cf5a61 --- /dev/null +++ b/browser/devtools/shared/test/browser_telemetry_toolboxtabs_webaudioeditor.js @@ -0,0 +1,118 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const TEST_URI = "data:text/html;charset=utf-8,

browser_telemetry_toolboxtabs_webaudioeditor.js

"; + +// Because we need to gather stats for the period of time that a tool has been +// opened we make use of setTimeout() to create tool active times. +const TOOL_DELAY = 200; + +let {Promise: promise} = Cu.import("resource://gre/modules/devtools/deprecated-sync-thenables.js", {}); +let {Services} = Cu.import("resource://gre/modules/Services.jsm", {}); + +let require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require; +let Telemetry = require("devtools/shared/telemetry"); + +let originalPref = Services.prefs.getBoolPref("devtools.webaudioeditor.enabled"); +Services.prefs.setBoolPref("devtools.webaudioeditor.enabled", true); + +function init() { + Telemetry.prototype.telemetryInfo = {}; + Telemetry.prototype._oldlog = Telemetry.prototype.log; + Telemetry.prototype.log = function(histogramId, value) { + if (!this.telemetryInfo) { + // Can be removed when Bug 992911 lands (see Bug 1011652 Comment 10) + return; + } + if (histogramId) { + if (!this.telemetryInfo[histogramId]) { + this.telemetryInfo[histogramId] = []; + } + + this.telemetryInfo[histogramId].push(value); + } + } + + openToolboxTabTwice("webaudioeditor", false); +} + +function openToolboxTabTwice(id, secondPass) { + let target = TargetFactory.forTab(gBrowser.selectedTab); + + gDevTools.showToolbox(target, id).then(function(toolbox) { + info("Toolbox tab " + id + " opened"); + + toolbox.once("destroyed", function() { + if (secondPass) { + checkResults(); + } else { + openToolboxTabTwice(id, true); + } + }); + // We use a timeout to check the tools active time + setTimeout(function() { + gDevTools.closeToolbox(target); + }, TOOL_DELAY); + }).then(null, reportError); +} + +function checkResults() { + let result = Telemetry.prototype.telemetryInfo; + + for (let [histId, value] of Iterator(result)) { + if (histId.endsWith("OPENED_PER_USER_FLAG")) { + ok(value.length === 1 && value[0] === true, + "Per user value " + histId + " has a single value of true"); + } else if (histId.endsWith("OPENED_BOOLEAN")) { + ok(value.length > 1, histId + " has more than one entry"); + + let okay = value.every(function(element) { + return element === true; + }); + + ok(okay, "All " + histId + " entries are === true"); + } else if (histId.endsWith("TIME_ACTIVE_SECONDS")) { + ok(value.length > 1, histId + " has more than one entry"); + + let okay = value.every(function(element) { + return element > 0; + }); + + ok(okay, "All " + histId + " entries have time > 0"); + } + } + + finishUp(); +} + +function reportError(error) { + let stack = " " + error.stack.replace(/\n?.*?@/g, "\n JS frame :: "); + + ok(false, "ERROR: " + error + " at " + error.fileName + ":" + + error.lineNumber + "\n\nStack trace:" + stack); + finishUp(); +} + +function finishUp() { + gBrowser.removeCurrentTab(); + + Telemetry.prototype.log = Telemetry.prototype._oldlog; + delete Telemetry.prototype._oldlog; + delete Telemetry.prototype.telemetryInfo; + + Services.prefs.setBoolPref("devtools.webaudioeditor.enabled", originalPref); + TargetFactory = Services = promise = require = null; + + finish(); +} + +function test() { + waitForExplicitFinish(); + gBrowser.selectedTab = gBrowser.addTab(); + gBrowser.selectedBrowser.addEventListener("load", function() { + gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true); + waitForFocus(init, content); + }, true); + + content.location = TEST_URI; +} diff --git a/browser/devtools/webaudioeditor/webaudioeditor-controller.js b/browser/devtools/webaudioeditor/webaudioeditor-controller.js index 0c5a99ce60cd..7e11a9c3ed72 100644 --- a/browser/devtools/webaudioeditor/webaudioeditor-controller.js +++ b/browser/devtools/webaudioeditor/webaudioeditor-controller.js @@ -18,6 +18,8 @@ const require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devt const EventEmitter = require("devtools/toolkit/event-emitter"); const STRINGS_URI = "chrome://browser/locale/devtools/webaudioeditor.properties" const L10N = new ViewHelpers.L10N(STRINGS_URI); +const Telemetry = require("devtools/shared/telemetry"); +const telemetry = new Telemetry(); let { console } = Cu.import("resource://gre/modules/devtools/Console.jsm", {}); @@ -144,6 +146,7 @@ let WebAudioEditorController = { * Listen for events emitted by the current tab target. */ initialize: function() { + telemetry.toolOpened("webaudioeditor"); this._onTabNavigated = this._onTabNavigated.bind(this); this._onThemeChange = this._onThemeChange.bind(this); gTarget.on("will-navigate", this._onTabNavigated); @@ -169,6 +172,7 @@ let WebAudioEditorController = { * Remove events emitted by the current tab target. */ destroy: function() { + telemetry.toolClosed("webaudioeditor"); gTarget.off("will-navigate", this._onTabNavigated); gTarget.off("navigate", this._onTabNavigated); gFront.off("start-context", this._onStartContext); diff --git a/toolkit/components/telemetry/Histograms.json b/toolkit/components/telemetry/Histograms.json index 6342fb0e929b..59a1ca30b711 100644 --- a/toolkit/components/telemetry/Histograms.json +++ b/toolkit/components/telemetry/Histograms.json @@ -5709,6 +5709,11 @@ "kind": "boolean", "description": "How many times has the devtool's Shader Editor been opened?" }, + "DEVTOOLS_WEBAUDIOEDITOR_OPENED_BOOLEAN": { + "expires_in_version": "never", + "kind": "boolean", + "description": "How many times has the devtool's Web Audio Editor been opened?" + }, "DEVTOOLS_CANVASDEBUGGER_OPENED_BOOLEAN": { "expires_in_version": "never", "kind": "boolean", @@ -5819,6 +5824,11 @@ "kind": "flag", "description": "How many users have opened the devtool's Shader Editor?" }, + "DEVTOOLS_WEBAUDIOEDITOR_OPENED_PER_USER_FLAG": { + "expires_in_version": "never", + "kind": "flag", + "description": "How many users have opened the devtool's Web Audio Editor?" + }, "DEVTOOLS_CANVASDEBUGGER_OPENED_PER_USER_FLAG": { "expires_in_version": "never", "kind": "flag", @@ -5955,6 +5965,13 @@ "n_buckets": 100, "description": "How long has the Shader Editor been active (seconds)" }, + "DEVTOOLS_WEBAUDIOEDITOR_TIME_ACTIVE_SECONDS": { + "expires_in_version": "never", + "kind": "exponential", + "high": "10000000", + "n_buckets": 100, + "description": "How long has the Web Audio Editor been active (seconds)" + }, "DEVTOOLS_CANVASDEBUGGER_TIME_ACTIVE_SECONDS": { "expires_in_version": "never", "kind": "exponential", From 42cfa0db730c51fdedf2d0f8ef65deaac78b9d58 Mon Sep 17 00:00:00 2001 From: Paul Rouget Date: Mon, 16 Jun 2014 12:12:30 -0700 Subject: [PATCH 07/32] Bug 1021475 - [appmgr v2] update app header in projectEditor. r=bgrins --- .../lib/plugins/app-manager/plugin.js | 50 ++++++++++++------- .../test/browser_projecteditor_app_options.js | 15 ------ browser/devtools/webide/content/webide.js | 28 ++++++++--- 3 files changed, 53 insertions(+), 40 deletions(-) diff --git a/browser/devtools/projecteditor/lib/plugins/app-manager/plugin.js b/browser/devtools/projecteditor/lib/plugins/app-manager/plugin.js index d3979236de74..668407bfb138 100644 --- a/browser/devtools/projecteditor/lib/plugins/app-manager/plugin.js +++ b/browser/devtools/projecteditor/lib/plugins/app-manager/plugin.js @@ -18,6 +18,35 @@ var AppManagerRenderer = Class({ return AppProjectEditor; } }, + getUI: function(parent) { + let doc = parent.ownerDocument; + if (parent.childElementCount == 0) { + let image = doc.createElement("image"); + let optionImage = doc.createElement("image"); + let flexElement = doc.createElement("div"); + let nameLabel = doc.createElement("span"); + let statusElement = doc.createElement("div"); + + image.className = "project-image"; + optionImage.className = "project-options"; + optionImage.setAttribute("src", OPTION_URL); + nameLabel.className = "project-name-label"; + statusElement.className = "project-status"; + flexElement.className = "project-flex"; + + parent.appendChild(image); + parent.appendChild(nameLabel); + parent.appendChild(flexElement); + parent.appendChild(statusElement); + parent.appendChild(optionImage); + } + + return { + image: parent.querySelector(".project-image"), + nameLabel: parent.querySelector(".project-name-label"), + statusElement: parent.querySelector(".project-status") + }; + }, onAnnotate: function(resource, editor, elt) { if (resource.parent || !this.isAppManagerProject()) { return; @@ -25,33 +54,16 @@ var AppManagerRenderer = Class({ let {appManagerOpts} = this.host.project; let doc = elt.ownerDocument; - let image = doc.createElement("image"); - let optionImage = doc.createElement("image"); - let flexElement = doc.createElement("div"); - let nameLabel = doc.createElement("span"); - let statusElement = doc.createElement("div"); - - image.className = "project-image"; - optionImage.className = "project-options"; - nameLabel.className = "project-name-label"; - statusElement.className = "project-status"; - flexElement.className = "project-flex"; + let {image,nameLabel,statusElement} = this.getUI(elt); let name = appManagerOpts.name || resource.basename; let url = appManagerOpts.iconUrl || "icon-sample.png"; let status = appManagerOpts.validationStatus || "unknown"; nameLabel.textContent = name; image.setAttribute("src", url); - optionImage.setAttribute("src", OPTION_URL); - statusElement.setAttribute("status", status) + statusElement.setAttribute("status", status); - elt.innerHTML = ""; - elt.appendChild(image); - elt.appendChild(nameLabel); - elt.appendChild(flexElement); - elt.appendChild(statusElement); - elt.appendChild(optionImage); return true; } }); diff --git a/browser/devtools/projecteditor/test/browser_projecteditor_app_options.js b/browser/devtools/projecteditor/test/browser_projecteditor_app_options.js index 2141c19b033b..2478c74218f0 100644 --- a/browser/devtools/projecteditor/test/browser_projecteditor_app_options.js +++ b/browser/devtools/projecteditor/test/browser_projecteditor_app_options.js @@ -47,12 +47,7 @@ let test = asyncTest(function*() { validationStatus: "error" }); - ok (!nameLabel.parentNode, "The old elements have been removed"); - info ("Getting ahold of and validating the project header DOM"); - let image = header.querySelector(".project-image"); - let nameLabel = header.querySelector(".project-name-label"); - let statusElement = header.querySelector(".project-status"); is (statusElement.getAttribute("status"), "error", "The status has been set correctly."); is (nameLabel.textContent, "Test2", "The name label has been set correctly"); is (image.getAttribute("src"), "chrome://browser/skin/devtools/tool-inspector.svg", "The icon has been set correctly"); @@ -65,12 +60,7 @@ let test = asyncTest(function*() { validationStatus: "warning" }); - ok (!nameLabel.parentNode, "The old elements have been removed"); - info ("Getting ahold of and validating the project header DOM"); - let image = header.querySelector(".project-image"); - let nameLabel = header.querySelector(".project-name-label"); - let statusElement = header.querySelector(".project-status"); is (statusElement.getAttribute("status"), "warning", "The status has been set correctly."); is (nameLabel.textContent, "Test3", "The name label has been set correctly"); is (image.getAttribute("src"), "chrome://browser/skin/devtools/tool-webconsole.svg", "The icon has been set correctly"); @@ -83,12 +73,7 @@ let test = asyncTest(function*() { validationStatus: "valid" }); - ok (!nameLabel.parentNode, "The old elements have been removed"); - info ("Getting ahold of and validating the project header DOM"); - let image = header.querySelector(".project-image"); - let nameLabel = header.querySelector(".project-name-label"); - let statusElement = header.querySelector(".project-status"); is (statusElement.getAttribute("status"), "valid", "The status has been set correctly."); is (nameLabel.textContent, "Test4", "The name label has been set correctly"); is (image.getAttribute("src"), "chrome://browser/skin/devtools/tool-debugger.svg", "The icon has been set correctly"); diff --git a/browser/devtools/webide/content/webide.js b/browser/devtools/webide/content/webide.js index defbb9fed82b..1e8d6ec12814 100644 --- a/browser/devtools/webide/content/webide.js +++ b/browser/devtools/webide/content/webide.js @@ -116,6 +116,7 @@ let UI = { this.updateTitle(); this.updateCommands(); this.updateProjectButton(); + this.updateProjectEditorHeader(); break; }; }, @@ -290,6 +291,25 @@ let UI = { return this.projecteditor.loaded; }, + updateProjectEditorHeader: function() { + let project = AppManager.selectedProject; + if (!project || !this.projecteditor) { + return; + } + let status = project.validationStatus || "unknown"; + if (status == "error warning") { + status = "error"; + } + this.getProjectEditor().then((projecteditor) => { + projecteditor.setProjectToAppPath(project.location, { + name: project.name, + iconUrl: project.icon, + projectOverviewURL: "chrome://webide/content/details.xhtml", + validationStatus: status + }); + }, console.error); + }, + isProjectEditorEnabled: function() { return Services.prefs.getBoolPref("devtools.webide.showProjectEditor"); }, @@ -333,12 +353,8 @@ let UI = { detailsIframe.setAttribute("hidden", "true"); projecteditorIframe.removeAttribute("hidden"); - this.getProjectEditor().then((projecteditor) => { - projecteditor.setProjectToAppPath(project.location, { - name: project.name, - iconUrl: project.icon, - projectOverviewURL: "chrome://webide/content/details.xhtml" - }); + this.getProjectEditor().then(() => { + this.updateProjectEditorHeader(); }, console.error); if (project.location) { From 4c906c25910db1344e44817e7017daaab799ca7b Mon Sep 17 00:00:00 2001 From: Jordan Santell Date: Mon, 16 Jun 2014 08:03:52 -0700 Subject: [PATCH 08/32] Bug 1019964 - Add an option to CallWatcher to only store weak references. r=vp From 7a169cbcf6bc4dd42dffb871aa8ec72885a307ed Mon Sep 17 00:00:00 2001 --- toolkit/devtools/server/actors/call-watcher.js | 36 +++++++++++++++++++++----- toolkit/devtools/server/actors/webaudio.js | 3 ++- 2 files changed, 32 insertions(+), 7 deletions(-) --- .../devtools/server/actors/call-watcher.js | 36 +++++++++++++++---- toolkit/devtools/server/actors/webaudio.js | 3 +- 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/toolkit/devtools/server/actors/call-watcher.js b/toolkit/devtools/server/actors/call-watcher.js index 3c986d97354c..cc2eddc0699f 100644 --- a/toolkit/devtools/server/actors/call-watcher.js +++ b/toolkit/devtools/server/actors/call-watcher.js @@ -73,15 +73,37 @@ let FunctionCallActor = protocol.ActorClass({ protocol.Actor.prototype.initialize.call(this, conn); this.details = { - window: window, - caller: caller, type: type, name: name, stack: stack, - args: args, - result: result }; + // Store a weak reference to all objects so we don't + // prevent natural GC if `holdWeak` was passed into + // setup as truthy. Used in the Web Audio Editor. + if (this._holdWeak) { + let weakRefs = { + window: Cu.getWeakReference(window), + caller: Cu.getWeakReference(caller), + result: Cu.getWeakReference(result), + args: Cu.getWeakReference(args) + }; + + Object.defineProperties(this.details, { + window: { get: () => weakRefs.window.get() }, + caller: { get: () => weakRefs.caller.get() }, + result: { get: () => weakRefs.result.get() }, + args: { get: () => weakRefs.args.get() } + }); + } + // Otherwise, hold strong references to the objects. + else { + this.details.window = window; + this.details.caller = caller; + this.details.result = result; + this.details.args = args; + } + this.meta = { global: -1, previews: { caller: "", args: "" } @@ -246,7 +268,7 @@ let CallWatcherActor = exports.CallWatcherActor = protocol.ActorClass({ * created, in order to instrument the specified objects and become * aware of everything the content does with them. */ - setup: method(function({ tracedGlobals, tracedFunctions, startRecording, performReload }) { + setup: method(function({ tracedGlobals, tracedFunctions, startRecording, performReload, holdWeak }) { if (this._initialized) { return; } @@ -255,6 +277,7 @@ let CallWatcherActor = exports.CallWatcherActor = protocol.ActorClass({ this._functionCalls = []; this._tracedGlobals = tracedGlobals || []; this._tracedFunctions = tracedFunctions || []; + this._holdWeak = !!holdWeak; this._contentObserver = new ContentObserver(this.tabActor); on(this._contentObserver, "global-created", this._onGlobalCreated); @@ -271,7 +294,8 @@ let CallWatcherActor = exports.CallWatcherActor = protocol.ActorClass({ tracedGlobals: Option(0, "nullable:array:string"), tracedFunctions: Option(0, "nullable:array:string"), startRecording: Option(0, "boolean"), - performReload: Option(0, "boolean") + performReload: Option(0, "boolean"), + holdWeak: Option(0, "boolean") }, oneway: true }), diff --git a/toolkit/devtools/server/actors/webaudio.js b/toolkit/devtools/server/actors/webaudio.js index 9e7523a80aea..b3fc7e5193b8 100644 --- a/toolkit/devtools/server/actors/webaudio.js +++ b/toolkit/devtools/server/actors/webaudio.js @@ -318,7 +318,8 @@ let WebAudioActor = exports.WebAudioActor = protocol.ActorClass({ this._callWatcher.setup({ tracedGlobals: AUDIO_GLOBALS, startRecording: true, - performReload: reload + performReload: reload, + holdWeak: true }); }, { request: { reload: Option(0, "boolean") }, From 125721fd9ce6d7cefe7680373b85744cb3d166c1 Mon Sep 17 00:00:00 2001 From: Gijs Kruitbosch Date: Mon, 16 Jun 2014 14:33:53 +0100 Subject: [PATCH 09/32] Bug 979207 - add some logging to see if the popup is getting closed while opening, r=mconley --- .../browser_968447_bookmarks_toolbar_items_in_panel.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/browser/components/customizableui/test/browser_968447_bookmarks_toolbar_items_in_panel.js b/browser/components/customizableui/test/browser_968447_bookmarks_toolbar_items_in_panel.js index 53e6300042d5..1631eb8c8009 100644 --- a/browser/components/customizableui/test/browser_968447_bookmarks_toolbar_items_in_panel.js +++ b/browser/components/customizableui/test/browser_968447_bookmarks_toolbar_items_in_panel.js @@ -29,6 +29,12 @@ add_task(function() { let newWin = yield openAndLoadWindow({}, true); info("Waiting for panel in new window to open"); + let hideTrace = function() { + info(new Error().stack); + info("Panel was hidden."); + }; + newWin.PanelUI.panel.addEventListener("popuphidden", hideTrace); + yield newWin.PanelUI.show(); let newWinBookmarksToolbarPlaceholder = newWin.document.getElementById(buttonId); ok(newWinBookmarksToolbarPlaceholder.classList.contains("toolbarbutton-1"), @@ -36,6 +42,7 @@ add_task(function() { is(newWinBookmarksToolbarPlaceholder.getAttribute("wrap"), "true", "Button in new window should have 'wrap' attribute"); + newWin.PanelUI.panel.removeEventListener("popuphidden", hideTrace); //XXXgijs on Linux, we're sometimes seeing the panel being hidden early // in the newly created window, probably because something else steals focus. if (newWin.PanelUI.panel.state != "closed") { From e07b6ed7dab5e22d807d647be8d7dc3d01455421 Mon Sep 17 00:00:00 2001 From: Gijs Kruitbosch Date: Sat, 14 Jun 2014 22:01:55 +0100 Subject: [PATCH 10/32] Bug 1022025, r=bholley,mfinkle --- .../html/content/public/HTMLMediaElement.h | 28 +++++++++++++++++++ content/html/content/src/HTMLMediaElement.cpp | 2 ++ dom/webidl/HTMLMediaElement.webidl | 2 ++ mobile/android/chrome/content/CastingApps.js | 13 ++++----- toolkit/content/widgets/videocontrols.xml | 19 ++++--------- 5 files changed, 42 insertions(+), 22 deletions(-) diff --git a/content/html/content/public/HTMLMediaElement.h b/content/html/content/public/HTMLMediaElement.h index 8daef472d557..7f014e79d8e5 100644 --- a/content/html/content/public/HTMLMediaElement.h +++ b/content/html/content/public/HTMLMediaElement.h @@ -488,6 +488,26 @@ public: mStatsShowing = aShow; } + bool MozAllowCasting() const + { + return mAllowCasting; + } + + void SetMozAllowCasting(bool aShow) + { + mAllowCasting = aShow; + } + + bool MozIsCasting() const + { + return mIsCasting; + } + + void SetMozIsCasting(bool aShow) + { + mIsCasting = aShow; + } + already_AddRefed GetMozSrcObject() const; void SetMozSrcObject(DOMMediaStream& aValue); @@ -1093,6 +1113,14 @@ protected: // video controls bool mStatsShowing; + // The following two fields are here for the private storage of the builtin + // video controls, and control 'casting' of the video to external devices + // (TVs, projectors etc.) + // True if casting is currently allowed + bool mAllowCasting; + // True if currently casting this video + bool mIsCasting; + // True if the sound is being captured. bool mAudioCaptured; diff --git a/content/html/content/src/HTMLMediaElement.cpp b/content/html/content/src/HTMLMediaElement.cpp index ad2b00952734..a94cfc40ba64 100644 --- a/content/html/content/src/HTMLMediaElement.cpp +++ b/content/html/content/src/HTMLMediaElement.cpp @@ -1992,6 +1992,8 @@ HTMLMediaElement::HTMLMediaElement(already_AddRefed& aNodeInfo) mPaused(true), mMuted(0), mStatsShowing(false), + mAllowCasting(false), + mIsCasting(false), mAudioCaptured(false), mPlayingBeforeSeek(false), mPausedForInactiveDocumentOrChannel(false), diff --git a/dom/webidl/HTMLMediaElement.webidl b/dom/webidl/HTMLMediaElement.webidl index f2ba3cec167d..904677d4dabb 100644 --- a/dom/webidl/HTMLMediaElement.webidl +++ b/dom/webidl/HTMLMediaElement.webidl @@ -104,6 +104,8 @@ partial interface HTMLMediaElement { // NB: for internal use with the video controls: [Func="IsChromeOrXBL"] attribute boolean mozMediaStatisticsShowing; + [Func="IsChromeOrXBL"] attribute boolean mozAllowCasting; + [Func="IsChromeOrXBL"] attribute boolean mozIsCasting; // Mozilla extension: stream capture [Throws] diff --git a/mobile/android/chrome/content/CastingApps.js b/mobile/android/chrome/content/CastingApps.js index d03d29f4872b..1df1c02363c3 100644 --- a/mobile/android/chrome/content/CastingApps.js +++ b/mobile/android/chrome/content/CastingApps.js @@ -250,8 +250,7 @@ var CastingApps = { // Look for a castable