From 5a2b5ca953a53f68eb451fab41a4450064f6e575 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 68867ecdf67..e0a761ce54c 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 05f258eaa75..9c19731f96a 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 a348bc16a7feae8a755bba3d4d32ebb824f0e452 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 e0a761ce54c..3be3f016721 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 cde702372fa..82790f73e17 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 e41e74bb9221d041c915cd0fe3b5e6c429cce22a 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 ba1ee7920ff..b3a148a304e 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 e4cd24dde9f85b8a2a30470da010e4934e573b8d 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 80828d8d4dd..b6d450da303 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 a93ba68fc54..506981a5a1e 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 00000000000..83274dba64b --- /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 9b9bbfaaf34f03236c2760a48f75b5c5e3fe01a1 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 9211919348a..b8131542f06 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 b5832b3a368..02143ef38de 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 69af2927ecf..08cb32f960d 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 00000000000..7735ef87bb7
--- /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 2ee772bbe98..6342fb0e929 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 8c7acbafd251b01c3481b5ee330aa4763bb5ff9e 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 02143ef38de..29ef0cc9fe5 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 08cb32f960d..a4f2b3cd26e 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 00000000000..f29947cf5a6 --- /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 0c5a99ce60c..7e11a9c3ed7 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 6342fb0e929..59a1ca30b71 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 6d2e39cd27776bd601a1829dae9cb2373158e3ca 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 d3979236de7..668407bfb13 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 2141c19b033..2478c74218f 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 defbb9fed82..1e8d6ec1281 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 48ce07a8fef995524c4d0ceeabff17f71f52e4ca 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 3c986d97354..cc2eddc0699 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 9e7523a80ae..b3fc7e5193b 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 cbb0d04a34eca3a5c814c34c296ba06d25549d71 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 53e6300042d..1631eb8c800 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 a43a4213abd7b04430838db0be56bd87a7e52dfa 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 8daef472d55..7f014e79d8e 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 ad2b0095273..a94cfc40ba6 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 f2ba3cec167..904677d4dab 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 d03d29f4872..1df1c02363c 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