diff --git a/CLOBBER b/CLOBBER index a439da1d2d8a94c4b0f481272848525a0dd03c7a..808a6cb10569127d52fe19f72defceb02e3da27e 100644 --- a/CLOBBER +++ b/CLOBBER @@ -22,4 +22,4 @@ # changes to stick? As of bug 928195, this shouldn't be necessary! Please # don't change CLOBBER for WebIDL changes any more. -Bug 1288460 requires another clobber due to bug 1298779. +Bug 1302429 to fix also bustage. diff --git a/browser/app/profile/firefox.js b/browser/app/profile/firefox.js index 070ddd023d5de47cd2da58afabc7e1b048c9d5e1..777e049000cf7edfcaada8a44ef3f9aa39e30192 100644 --- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -1507,3 +1507,14 @@ pref("print.use_simplify_page", true); // Space separated list of URLS that are allowed to send objects (instead of // only strings) through webchannels. This list is duplicated in mobile/android/app/mobile.js pref("webchannel.allowObject.urlWhitelist", "https://accounts.firefox.com https://content.cdn.mozilla.net https://input.mozilla.org https://support.mozilla.org https://install.mozilla.org"); + +// Whether or not the browser should scan for unsubmitted +// crash reports, and then show a notification for submitting +// those reports. +#ifdef RELEASE_BUILD +pref("browser.crashReports.unsubmittedCheck.enabled", false); +#else +pref("browser.crashReports.unsubmittedCheck.enabled", true); +#endif + +pref("browser.crashReports.unsubmittedCheck.autoSubmit", false); \ No newline at end of file diff --git a/browser/base/content/browser.css b/browser/base/content/browser.css index 0eeece8c7cbb4ae5f4e8a4fbbf20cd0a43ac5c59..d1756f00cf5400371bdef6a50b664710c3b153b8 100644 --- a/browser/base/content/browser.css +++ b/browser/base/content/browser.css @@ -777,13 +777,6 @@ html|*#fullscreen-exit-button { -moz-binding: url("chrome://browser/content/urlbarBindings.xml#click-to-play-plugins-notification"); } -#login-fill-notification { - -moz-binding: url("chrome://browser/content/urlbarBindings.xml#login-fill-notification"); -} - -.login-fill-item { - -moz-binding: url("chrome://passwordmgr/content/login.xml#login"); -} .plugin-popupnotification-centeritem { -moz-binding: url("chrome://browser/content/urlbarBindings.xml#plugin-popupnotification-center-item"); @@ -1185,10 +1178,6 @@ toolbarpaletteitem[place="palette"][hidden] { display: none; } -#login-fill-doorhanger:not([inDetailView]) > #login-fill-clickcapturer { - pointer-events: none; -} - .popup-notification-invalid-input { box-shadow: 0 0 1.5px 1px red; } diff --git a/browser/base/content/browser.xul b/browser/base/content/browser.xul index f1eae4fc0b34c067543713ca0ed0d8707e8d9505..4d607740b317c2dfebac9a798132531150ca93d0 100644 --- a/browser/base/content/browser.xul +++ b/browser/base/content/browser.xul @@ -718,8 +718,6 @@ tooltiptext="&urlbar.addonsNotificationAnchor.tooltip;"/> <image id="indexedDB-notification-icon" class="notification-anchor-icon indexedDB-icon" role="button" tooltiptext="&urlbar.indexedDBNotificationAnchor.tooltip;"/> - <image id="login-fill-notification-icon" class="notification-anchor-icon login-icon" role="button" - tooltiptext="&urlbar.loginFillNotificationAnchor.tooltip;"/> <image id="password-notification-icon" class="notification-anchor-icon login-icon" role="button" tooltiptext="&urlbar.passwordNotificationAnchor.tooltip;"/> <image id="plugins-notification-icon" class="notification-anchor-icon plugin-icon" role="button" diff --git a/browser/base/content/popup-notifications.inc b/browser/base/content/popup-notifications.inc index 622488001543d9c09fb3526e5ec4829df02c8ff2..62d28f1b6876ecc47f651578760bd973932c704e 100644 --- a/browser/base/content/popup-notifications.inc +++ b/browser/base/content/popup-notifications.inc @@ -52,22 +52,6 @@ </popupnotificationcontent> </popupnotification> - <stack id="login-fill-doorhanger" hidden="true"> - <vbox id="login-fill-mainview"> - <description id="login-fill-testing" - value="Thanks for testing the login fill doorhanger!"/> - <textbox id="login-fill-filter"/> - <richlistbox id="login-fill-list"/> - </vbox> - <vbox id="login-fill-clickcapturer"/> - <vbox id="login-fill-details"> - <textbox id="login-fill-username" readonly="true"/> - <textbox id="login-fill-password" type="password" disabled="true"/> - <hbox> - <button id="login-fill-use" label="Use in form"/> - </hbox> - </vbox> - </stack> <popupnotification id="addon-progress-notification" hidden="true"> <popupnotificationcontent orient="vertical"> diff --git a/browser/base/content/test/webrtc/browser_devices_get_user_media.js b/browser/base/content/test/webrtc/browser_devices_get_user_media.js index 4ac278c557436f77447b0411d30e566355da691c..5e10b843f86a030c22816a9b81138dd54d24cce2 100644 --- a/browser/base/content/test/webrtc/browser_devices_get_user_media.js +++ b/browser/base/content/test/webrtc/browser_devices_get_user_media.js @@ -543,6 +543,7 @@ function test() { yield expectNoObserverCalled(); } }).then(finish, ex => { + Cu.reportError(ex); ok(false, "Unexpected Exception: " + ex); finish(); }); diff --git a/browser/base/content/test/webrtc/browser_devices_get_user_media_anim.js b/browser/base/content/test/webrtc/browser_devices_get_user_media_anim.js index 273453d44d8d15f2a6602a6bb68c9d162dddbfcb..b75629f6a979084cb7cd15c8a52e85632175d128 100644 --- a/browser/base/content/test/webrtc/browser_devices_get_user_media_anim.js +++ b/browser/base/content/test/webrtc/browser_devices_get_user_media_anim.js @@ -93,6 +93,7 @@ function test() { yield test.run(); } }).then(finish, ex => { + Cu.reportError(ex); ok(false, "Unexpected Exception: " + ex); finish(); }); diff --git a/browser/base/content/test/webrtc/browser_devices_get_user_media_in_frame.js b/browser/base/content/test/webrtc/browser_devices_get_user_media_in_frame.js index 9be3d4be4f7a2f406e0660917493893d92c4d576..e9dd87b49b2243d549cbf6dca80d5fee32c28b16 100644 --- a/browser/base/content/test/webrtc/browser_devices_get_user_media_in_frame.js +++ b/browser/base/content/test/webrtc/browser_devices_get_user_media_in_frame.js @@ -262,6 +262,7 @@ function test() { yield expectNoObserverCalled(); } }).then(finish, ex => { + Cu.reportError(ex); ok(false, "Unexpected Exception: " + ex); finish(); }); diff --git a/browser/base/content/test/webrtc/browser_devices_get_user_media_tear_off_tab.js b/browser/base/content/test/webrtc/browser_devices_get_user_media_tear_off_tab.js index d930ee1a69590ee914c5f459d1e97ea28c0aa1a8..9061c7a00fd4d0400105835e16af800beead00a9 100644 --- a/browser/base/content/test/webrtc/browser_devices_get_user_media_tear_off_tab.js +++ b/browser/base/content/test/webrtc/browser_devices_get_user_media_tear_off_tab.js @@ -95,6 +95,7 @@ function test() { yield expectNoObserverCalled(); } }).then(finish, ex => { + Cu.reportError(ex); ok(false, "Unexpected Exception: " + ex); finish(); }); diff --git a/browser/base/content/urlbarBindings.xml b/browser/base/content/urlbarBindings.xml index c7f8694938e8df424afafb697f4458d92ef003ec..b9b300774081e994e93034cf3f3bc3420375a02a 100644 --- a/browser/base/content/urlbarBindings.xml +++ b/browser/base/content/urlbarBindings.xml @@ -2478,16 +2478,6 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/. </handlers> </binding> - <!-- This is the XBL notification definition for the login fill doorhanger, - which is empty because the actual panel is not implemented inside an XBL - binding, but made of elements added to the notification panel. This - allows accessing the full structure while the panel is hidden. --> - <binding id="login-fill-notification" extends="chrome://global/content/bindings/notification.xml#popup-notification"> - <content> - <children/> - </content> - </binding> - <binding id="splitmenu"> <content> <xul:hbox anonid="menuitem" flex="1" diff --git a/browser/components/nsBrowserGlue.js b/browser/components/nsBrowserGlue.js index a27decbce95c67acf1080035da927fd080d463c2..eebbef271ea0f00526708519ead6c663ce6d0cd4 100644 --- a/browser/components/nsBrowserGlue.js +++ b/browser/components/nsBrowserGlue.js @@ -71,6 +71,8 @@ XPCOMUtils.defineLazyServiceGetter(this, "AlertsService", "@mozilla.org/alerts-s if (AppConstants.MOZ_CRASHREPORTER) { XPCOMUtils.defineLazyModuleGetter(this, "PluginCrashReporter", "resource:///modules/ContentCrashHandlers.jsm"); + XPCOMUtils.defineLazyModuleGetter(this, "UnsubmittedCrashHandler", + "resource:///modules/ContentCrashHandlers.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "CrashSubmit", "resource://gre/modules/CrashSubmit.jsm"); } @@ -714,6 +716,7 @@ BrowserGlue.prototype = { TabCrashHandler.init(); if (AppConstants.MOZ_CRASHREPORTER) { PluginCrashReporter.init(); + UnsubmittedCrashHandler.init(); } Services.obs.notifyObservers(null, "browser-ui-startup-complete", ""); @@ -744,64 +747,6 @@ BrowserGlue.prototype = { } }, - checkForPendingCrashReports: function() { - // We don't process crash reports older than 28 days, so don't bother submitting them - const PENDING_CRASH_REPORT_DAYS = 28; - if (AppConstants.MOZ_CRASHREPORTER) { - let dateLimit = new Date(); - dateLimit.setDate(dateLimit.getDate() - PENDING_CRASH_REPORT_DAYS); - CrashSubmit.pendingIDsAsync(dateLimit).then( - function onSuccess(ids) { - let count = ids.length; - if (count) { - let win = RecentWindow.getMostRecentBrowserWindow(); - if (!win) { - return; - } - let nb = win.document.getElementById("global-notificationbox"); - let notification = nb.getNotificationWithValue("pending-crash-reports"); - if (notification) { - return; - } - let buttons = [ - { - label: win.gNavigatorBundle.getString("pendingCrashReports.submitAll"), - callback: function() { - ids.forEach(function(id) { - CrashSubmit.submit(id, {extraExtraKeyVals: {"SubmittedFromInfobar": true}}); - }); - } - }, - { - label: win.gNavigatorBundle.getString("pendingCrashReports.ignoreAll"), - callback: function() { - ids.forEach(function(id) { - CrashSubmit.ignore(id); - }); - } - }, - { - label: win.gNavigatorBundle.getString("pendingCrashReports.viewAll"), - callback: function() { - win.openUILinkIn("about:crashes", "tab"); - return true; - } - } - ]; - nb.appendNotification(PluralForm.get(count, - win.gNavigatorBundle.getString("pendingCrashReports.label")).replace("#1", count), - "pending-crash-reports", - "chrome://browser/skin/tab-crashed.svg", - nb.PRIORITY_INFO_HIGH, buttons); - } - }, - function onError(err) { - Cu.reportError(err); - } - ); - } - }, - _onSafeModeRestart: function BG_onSafeModeRestart() { // prompt the user to confirm let strings = gBrowserBundle; @@ -1070,10 +1015,6 @@ BrowserGlue.prototype = { this._checkForOldBuildUpdates(); - if (!AppConstants.RELEASE_BUILD) { - this.checkForPendingCrashReports(); - } - CaptivePortalWatcher.init(); AutoCompletePopup.init(); diff --git a/browser/components/preferences/in-content/advanced.js b/browser/components/preferences/in-content/advanced.js index f03e1250d733133612f15d5e08381a7dce68a07d..c54f52f426e92ff8af199f27a6a90e23da1c4164 100644 --- a/browser/components/preferences/in-content/advanced.js +++ b/browser/components/preferences/in-content/advanced.js @@ -60,10 +60,7 @@ var gAdvancedPane = { setEventListener("submitHealthReportBox", "command", gAdvancedPane.updateSubmitHealthReport); } - if (AppConstants.MOZ_CRASHREPORTER) { - setEventListener("submitCrashesBox", "command", - gAdvancedPane.updateSubmitCrashes); - } + setEventListener("connectionSettings", "command", gAdvancedPane.showConnections); setEventListener("clearCacheButton", "command", @@ -243,28 +240,6 @@ var gAdvancedPane = { { this._setupLearnMoreLink("toolkit.crashreporter.infoURL", "crashReporterLearnMore"); - - var checkbox = document.getElementById("submitCrashesBox"); - try { - var cr = Components.classes["@mozilla.org/toolkit/crash-reporter;1"]. - getService(Components.interfaces.nsICrashReporter); - checkbox.checked = cr.submitReports; - } catch (e) { - checkbox.style.display = "none"; - } - }, - - /** - * - */ - updateSubmitCrashes: function () - { - var checkbox = document.getElementById("submitCrashesBox"); - try { - var cr = Components.classes["@mozilla.org/toolkit/crash-reporter;1"]. - getService(Components.interfaces.nsICrashReporter); - cr.submitReports = checkbox.checked; - } catch (e) { } }, /** @@ -762,9 +737,7 @@ var gAdvancedPane = { */ showCertificates: function () { - openDialog("chrome://pippki/content/certManager.xul", - "mozilla:certmanager", - "modal=yes", null); + gSubDialog.open("chrome://pippki/content/certManager.xul"); }, /** diff --git a/browser/components/preferences/in-content/advanced.xul b/browser/components/preferences/in-content/advanced.xul index a125e7ce651702dfdd2fed9af46352ef63d9dbb8..8a8536c107c0dffad9631b3b6eb3ba3846c8caab 100644 --- a/browser/components/preferences/in-content/advanced.xul +++ b/browser/components/preferences/in-content/advanced.xul @@ -54,6 +54,13 @@ type="bool"/> #endif + <!-- Data Choices tab --> +#ifdef MOZ_CRASHREPORTER + <preference id="browser.crashReports.unsubmittedCheck.autoSubmit" + name="browser.crashReports.unsubmittedCheck.autoSubmit" + type="bool"/> +#endif + <!-- Network tab --> <preference id="browser.cache.disk.capacity" name="browser.cache.disk.capacity" @@ -229,11 +236,13 @@ #ifdef MOZ_CRASHREPORTER <groupbox> <caption> - <checkbox id="submitCrashesBox" label="&enableCrashReporter.label;" - accesskey="&enableCrashReporter.accesskey;"/> + <checkbox id="automaticallySubmitCrashesBox" + preference="browser.crashReports.unsubmittedCheck.autoSubmit" + label="&alwaysSubmitCrashReports.label;" + accesskey="&alwaysSubmitCrashReports.accesskey;"/> </caption> <hbox class="indent"> - <label flex="1">&crashReporterDesc.label;</label> + <label flex="1">&crashReporterDesc2.label;</label> <spacer flex="10"/> <label id="crashReporterLearnMore" class="text-link">&crashReporterLearnMore.label;</label> diff --git a/browser/config/tooltool-manifests/win32/releng.manifest b/browser/config/tooltool-manifests/win32/releng.manifest index 43af53c507efaee41b2b68bf0bdc3fb5e1980fe6..7b6d77771331c03a97462fbf4e21a9508fdfdb5a 100644 --- a/browser/config/tooltool-manifests/win32/releng.manifest +++ b/browser/config/tooltool-manifests/win32/releng.manifest @@ -29,11 +29,11 @@ "unpack": true }, { -"version": "Visual Studio 2015 Update 2 / SDK 10.0.10586.0/212", -"size": 332442800, -"digest": "995394a4a515c7cb0f8595f26f5395361a638870dd0bbfcc22193fe1d98a0c47126057d5999cc494f3f3eac5cb49160e79757c468f83ee5797298e286ef6252c", +"version": "Visual Studio 2015 Update 3 14.0.25425.01 / SDK 10.0.14393.0", +"size": 326656969, +"digest": "babc414ffc0457d27f5a1ed24a8e4873afbe2f1c1a4075469a27c005e1babc3b2a788f643f825efedff95b79686664c67ec4340ed535487168a3482e68559bc7", "algorithm": "sha512", -"filename": "vs2015u2.zip", +"filename": "vs2015u3.zip", "unpack": true } ] diff --git a/browser/config/tooltool-manifests/win64/releng.manifest b/browser/config/tooltool-manifests/win64/releng.manifest index b6d3a6197bf5e3447b9beb6f6a1bb2a233e0d436..3bba2d91a96a8bf8942a5d9e6fad2d4d27d2e978 100644 --- a/browser/config/tooltool-manifests/win64/releng.manifest +++ b/browser/config/tooltool-manifests/win64/releng.manifest @@ -30,11 +30,11 @@ "unpack": true }, { -"version": "Visual Studio 2015 Update 2 / SDK 10.0.10586.0/212", -"size": 332442800, -"digest": "995394a4a515c7cb0f8595f26f5395361a638870dd0bbfcc22193fe1d98a0c47126057d5999cc494f3f3eac5cb49160e79757c468f83ee5797298e286ef6252c", +"version": "Visual Studio 2015 Update 3 14.0.25425.01 / SDK 10.0.14393.0", +"size": 326656969, +"digest": "babc414ffc0457d27f5a1ed24a8e4873afbe2f1c1a4075469a27c005e1babc3b2a788f643f825efedff95b79686664c67ec4340ed535487168a3482e68559bc7", "algorithm": "sha512", -"filename": "vs2015u2.zip", +"filename": "vs2015u3.zip", "unpack": true } ] diff --git a/browser/locales/en-US/chrome/browser/browser.dtd b/browser/locales/en-US/chrome/browser/browser.dtd index 8042f717b0a554fbe597598ebee4aadc7aa9a8bc..ba169e9a3542fb2785e12cf288f3eb9e694a717b 100644 --- a/browser/locales/en-US/chrome/browser/browser.dtd +++ b/browser/locales/en-US/chrome/browser/browser.dtd @@ -211,7 +211,6 @@ These should match what Safari and other Apple applications use on OS X Lion. -- <!ENTITY urlbar.geolocationNotificationAnchor.tooltip "Open location request panel"> <!ENTITY urlbar.addonsNotificationAnchor.tooltip "Open add-on installation message panel"> <!ENTITY urlbar.indexedDBNotificationAnchor.tooltip "Open offline storage message panel"> -<!ENTITY urlbar.loginFillNotificationAnchor.tooltip "Manage your login information"> <!ENTITY urlbar.passwordNotificationAnchor.tooltip "Open save password message panel"> <!ENTITY urlbar.pluginsNotificationAnchor.tooltip "Manage plug-in use"> <!ENTITY urlbar.webNotificationAnchor.tooltip "Change whether you can receive notifications from the site"> diff --git a/browser/locales/en-US/chrome/browser/browser.properties b/browser/locales/en-US/chrome/browser/browser.properties index 04ee8665ee773f8c897d3dc79d9add982e21c6b2..59173e12f937e0386d7b319af399596378fc68d4 100644 --- a/browser/locales/en-US/chrome/browser/browser.properties +++ b/browser/locales/en-US/chrome/browser/browser.properties @@ -730,10 +730,10 @@ tabgroups.migration.tabGroupBookmarkFolderName = Bookmarked Tab Groups # LOCALIZATION NOTE (pendingCrashReports.label): Semi-colon list of plural forms # See: http://developer.mozilla.org/en/docs/Localization_and_Plurals # #1 is the number of pending crash reports -pendingCrashReports.label = You have an unsubmitted crash report;You have #1 unsubmitted crash reports +pendingCrashReports2.label = You have an unsent crash report;You have #1 unsent crash reports pendingCrashReports.viewAll = View -pendingCrashReports.submitAll = Submit -pendingCrashReports.ignoreAll = Ignore +pendingCrashReports.send = Send +pendingCrashReports.alwaysSend = Always Send decoder.noCodecs.button = Learn how decoder.noCodecs.accesskey = L diff --git a/browser/locales/en-US/chrome/browser/preferences/advanced.dtd b/browser/locales/en-US/chrome/browser/preferences/advanced.dtd index c65132d53c169256565c41e637d28372276da57e..124c00d845f42d4b6208a956c99e77bd0e529398 100644 --- a/browser/locales/en-US/chrome/browser/preferences/advanced.dtd +++ b/browser/locales/en-US/chrome/browser/preferences/advanced.dtd @@ -40,10 +40,10 @@ <!ENTITY enableTelemetryData.accesskey "T"> <!ENTITY telemetryLearnMore.label "Learn More"> -<!ENTITY crashReporterDesc.label "&brandShortName; submits crash reports to help &vendorShortName; make your browser more stable and secure"> -<!ENTITY enableCrashReporter.label "Enable Crash Reporter"> -<!ENTITY enableCrashReporter.accesskey "C"> -<!ENTITY crashReporterLearnMore.label "Learn More"> +<!ENTITY crashReporterDesc2.label "Crash reports help &vendorShortName; fix problems and make your browser more stable and secure"> +<!ENTITY alwaysSubmitCrashReports.label "Allow &brandShortName; to send backlogged crash reports on your behalf"> +<!ENTITY alwaysSubmitCrashReports.accesskey "c"> +<!ENTITY crashReporterLearnMore.label "Learn More"> <!ENTITY networkTab.label "Network"> diff --git a/browser/modules/ContentCrashHandlers.jsm b/browser/modules/ContentCrashHandlers.jsm index dbd5972ea6118dd1f6c7c6514b94388188e99179..285f5277640b23dc5d8356335a04c33fb5f2fe0b 100644 --- a/browser/modules/ContentCrashHandlers.jsm +++ b/browser/modules/ContentCrashHandlers.jsm @@ -8,7 +8,9 @@ var Cc = Components.classes; var Ci = Components.interfaces; var Cu = Components.utils; -this.EXPORTED_SYMBOLS = [ "TabCrashHandler", "PluginCrashReporter" ]; +this.EXPORTED_SYMBOLS = [ "TabCrashHandler", + "PluginCrashReporter", + "UnsubmittedCrashHandler" ]; Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/Services.jsm"); @@ -21,6 +23,21 @@ XPCOMUtils.defineLazyModuleGetter(this, "RemotePages", "resource://gre/modules/RemotePageManager.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "SessionStore", "resource:///modules/sessionstore/SessionStore.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "Task", + "resource://gre/modules/Task.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow", + "resource:///modules/RecentWindow.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "PluralForm", + "resource://gre/modules/PluralForm.jsm"); + +XPCOMUtils.defineLazyGetter(this, "gNavigatorBundle", function() { + const url = "chrome://browser/locale/browser.properties"; + return Services.strings.createBundle(url); +}); + +// We don't process crash reports older than 28 days, so don't bother +// submitting them +const PENDING_CRASH_REPORT_DAYS = 28; this.TabCrashHandler = { _crashedTabCount: 0, @@ -319,6 +336,204 @@ this.TabCrashHandler = { }, } +/** + * This component is responsible for scanning the pending + * crash report directory for reports, and (if enabled), to + * prompt the user to submit those reports. It might also + * submit those reports automatically without prompting if + * the user has opted in. + */ +this.UnsubmittedCrashHandler = { + init() { + if (this.initialized) { + return; + } + + this.initialized = true; + + let pref = "browser.crashReports.unsubmittedCheck.enabled"; + let shouldCheck = Services.prefs.getBoolPref(pref); + + if (shouldCheck) { + Services.obs.addObserver(this, "browser-delayed-startup-finished", + false); + } + }, + + observe(subject, topic, data) { + if (topic != "browser-delayed-startup-finished") { + return; + } + + Services.obs.removeObserver(this, topic); + this.checkForUnsubmittedCrashReports(); + }, + + /** + * Scans the profile directory for unsubmitted crash reports + * within the past PENDING_CRASH_REPORT_DAYS days. If it + * finds any, it will, if necessary, attempt to open a notification + * bar to prompt the user to submit them. + * + * @returns Promise + * Resolves after it tries to append a notification on + * the most recent browser window. If a notification + * cannot be shown, will resolve anyways. + */ + checkForUnsubmittedCrashReports: Task.async(function*() { + let dateLimit = new Date(); + dateLimit.setDate(dateLimit.getDate() - PENDING_CRASH_REPORT_DAYS); + + let reportIDs = []; + try { + reportIDs = yield CrashSubmit.pendingIDsAsync(dateLimit); + } catch (e) { + Cu.reportError(e); + return; + } + + if (reportIDs.length) { + if (CrashNotificationBar.autoSubmit) { + CrashNotificationBar.submitReports(reportIDs); + } else { + this.showPendingSubmissionsNotification(reportIDs); + } + } + }), + + /** + * Given an array of unsubmitted crash report IDs, try to open + * up a notification asking the user to submit them. + * + * @param reportIDs (Array<string>) + * The Array of report IDs to offer the user to send. + */ + showPendingSubmissionsNotification(reportIDs) { + let count = reportIDs.length; + if (!count) { + return; + } + + let messageTemplate = + gNavigatorBundle.GetStringFromName("pendingCrashReports2.label"); + + let message = PluralForm.get(count, messageTemplate).replace("#1", count); + + CrashNotificationBar.show({ + notificationID: "pending-crash-reports", + message, + reportIDs, + }); + }, +}; + +this.CrashNotificationBar = { + /** + * Attempts to show a notification bar to the user in the most + * recent browser window asking them to submit some crash report + * IDs. If a notification cannot be shown (for example, there + * is no browser window), this method exits silently. + * + * The notification will allow the user to submit their crash + * reports. If the user dismissed the notification, the crash + * reports will be marked to be ignored (though they can + * still be manually submitted via about:crashes). + * + * @param JS Object + * An Object with the following properties: + * + * notificationID (string) + * The ID for the notification to be opened. + * + * message (string) + * The message to be displayed in the notification. + * + * reportIDs (Array<string>) + * The array of report IDs to offer to the user. + */ + show({ notificationID, message, reportIDs }) { + let chromeWin = RecentWindow.getMostRecentBrowserWindow(); + if (!chromeWin) { + // Can't show a notification in this case. We'll hopefully + // get another opportunity to have the user submit their + // crash reports later. + return; + } + + let nb = chromeWin.document.getElementById("global-notificationbox"); + let notification = nb.getNotificationWithValue(notificationID); + if (notification) { + return; + } + + let buttons = [{ + label: gNavigatorBundle.GetStringFromName("pendingCrashReports.send"), + callback: () => { + this.submitReports(reportIDs); + }, + }, + { + label: gNavigatorBundle.GetStringFromName("pendingCrashReports.alwaysSend"), + callback: () => { + this.autoSubmit = true; + this.submitReports(reportIDs); + }, + }, + { + label: gNavigatorBundle.GetStringFromName("pendingCrashReports.viewAll"), + callback: function() { + chromeWin.openUILinkIn("about:crashes", "tab"); + return true; + }, + }]; + + let eventCallback = (eventType) => { + if (eventType == "dismissed") { + // The user intentionally dismissed the notification, + // which we interpret as meaning that they don't care + // to submit the reports. We'll ignore these particular + // reports going forward. + reportIDs.forEach(function(reportID) { + CrashSubmit.ignore(reportID); + }); + } + }; + + nb.appendNotification(message, notificationID, + "chrome://browser/skin/tab-crashed.svg", + nb.PRIORITY_INFO_HIGH, buttons, + eventCallback); + }, + + get autoSubmit() { + return Services.prefs + .getBoolPref("browser.crashReports.unsubmittedCheck.autoSubmit"); + }, + + set autoSubmit(val) { + Services.prefs.setBoolPref("browser.crashReports.unsubmittedCheck.autoSubmit", + val); + }, + + /** + * Attempt to submit reports to the crash report server. Each + * report will have the "SubmittedFromInfobar" extra key set + * to true. + * + * @param reportIDs (Array<string>) + * The array of reportIDs to submit. + */ + submitReports(reportIDs) { + for (let reportID of reportIDs) { + CrashSubmit.submit(reportID, { + extraExtraKeyVals: { + "SubmittedFromInfobar": true, + }, + }); + } + }, +}; + this.PluginCrashReporter = { /** * Makes the PluginCrashReporter ready to hear about and diff --git a/browser/themes/linux/browser.css b/browser/themes/linux/browser.css index 8435867433e267961bb0dcb2522a60ba17bd3e7c..5d2a63de8fe8e59cff82be12db01eaafd47b75b9 100644 --- a/browser/themes/linux/browser.css +++ b/browser/themes/linux/browser.css @@ -1745,7 +1745,6 @@ notification.pluginVulnerable > .notification-inner > .messageCloseButton:not(:h background-image: -moz-image-rect(url("chrome://global/skin/icons/close.svg"), 0, 80, 16, 64); } -%include ../shared/login-doorhanger.inc.css %include downloads/indicator.css diff --git a/browser/themes/osx/browser.css b/browser/themes/osx/browser.css index cec8b2b5d87348a71649e403a1e3ba7fb1607725..e7d74e229551e73c00d7e8e0dafdb66a36285745 100644 --- a/browser/themes/osx/browser.css +++ b/browser/themes/osx/browser.css @@ -3155,7 +3155,6 @@ menulist.translate-infobar-element > .menulist-dropmarker { %include ../../../devtools/client/themes/responsivedesign.inc.css %include ../../../devtools/client/themes/commandline.inc.css %include ../shared/plugin-doorhanger.inc.css -%include ../shared/login-doorhanger.inc.css %include downloads/indicator.css diff --git a/browser/themes/shared/login-doorhanger.inc.css b/browser/themes/shared/login-doorhanger.inc.css deleted file mode 100644 index 7304eeea90206d643e1a61b500b76587f148fe0f..0000000000000000000000000000000000000000 --- a/browser/themes/shared/login-doorhanger.inc.css +++ /dev/null @@ -1,79 +0,0 @@ -#notification-popup[popupid="login-fill"] > .panel-arrowcontainer > .panel-arrowcontent { - /* Since we display a sliding subview that extends to the border, we cannot - * keep the default padding of arrow panels. We use the same padding in the - * individual content views instead. Since we removed the padding, we also - * have to ensure the contents are clipped to the border box. */ - padding: 0; - overflow: hidden; -} - -#login-fill-mainview, -#login-fill-details { - padding: var(--panel-arrowcontent-padding); -} - -#login-fill-doorhanger[inDetailView] > #login-fill-mainview { - transform: translateX(-14px); -} - -#login-fill-mainview, -#login-fill-details { - transition: transform 150ms; -} - -#login-fill-doorhanger:not([inDetailView]) > #login-fill-details { - transform: translateX(105%); -} - -#login-fill-doorhanger:not([inDetailView]) > #login-fill-details:-moz-locale-dir(rtl) { - transform: translateX(-105%); -} - -#login-fill-doorhanger[inDetailView] > #login-fill-clickcapturer { - background-color: hsla(210,4%,10%,.1); -} - -#login-fill-testing { - color: #b33; - font-weight: bold; -} - -#login-fill-list { - border: 1px solid black; - max-height: 20em; -} - -.login-fill-item[disabled] { - color: #888; - background-color: #fff; -} - -.login-fill-item[disabled][selected] { - background-color: #eef; -} - -.login-hostname { - margin: 4px; - font-weight: bold; -} - -.login-fill-item.different-hostname > .login-hostname { - color: #888; - font-style: italic; -} - -.login-username { - margin: 4px; - color: #888; -} - -#login-fill-details { - padding: 4px; - background: var(--panel-arrowcontent-background); - color: var(--panel-arrowcontent-color); - background-clip: padding-box; - border-left: 1px solid hsla(210,4%,10%,.3); - box-shadow: 0 3px 5px hsla(210,4%,10%,.1), - 0 0 7px hsla(210,4%,10%,.1); - margin-inline-start: 38px; -} diff --git a/browser/themes/shared/notification-icons.inc.css b/browser/themes/shared/notification-icons.inc.css index 0766bf22d348b114b92a0a46a4bf121307e6032a..bc8b3256b52c8a17a250fb0a53c0886321910df5 100644 --- a/browser/themes/shared/notification-icons.inc.css +++ b/browser/themes/shared/notification-icons.inc.css @@ -129,11 +129,6 @@ list-style-image: url(chrome://browser/skin/notification-icons.svg#login-detailed); } -#login-fill-notification-icon { - /* Temporary solution until the capture and fill doorhangers are unified. */ - transform: scaleX(-1); -} - .camera-icon, .popup-notification-icon[popupid="webRTC-shareDevices"] { list-style-image: url(chrome://browser/skin/notification-icons.svg#camera); diff --git a/browser/themes/windows/browser.css b/browser/themes/windows/browser.css index c46d6ac33c5ea1c1394803514c25fceabf641ea9..f30abc4154ba58159648f609c99f39ce00c7a48d 100644 --- a/browser/themes/windows/browser.css +++ b/browser/themes/windows/browser.css @@ -2434,7 +2434,6 @@ notification.pluginVulnerable > .notification-inner > .messageCloseButton { } } -%include ../shared/login-doorhanger.inc.css %include downloads/indicator.css diff --git a/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/Preferences.jsm b/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/Preferences.jsm index c97a2c21d231d6be7206918b0503c99a1d7f1cee..0c351d2198356c78f01ae7f70becf5aaf1ed3cc5 100644 --- a/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/Preferences.jsm +++ b/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/Preferences.jsm @@ -10,21 +10,19 @@ const {classes: Cc, interfaces: Ci, utils: Cu} = Components; Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/Task.jsm"); -Cu.import("resource://gre/modules/Timer.jsm"); Cu.import("resource://testing-common/TestUtils.jsm"); Cu.import("resource://testing-common/ContentTask.jsm"); this.Preferences = { init(libDir) { - Services.prefs.setBoolPref("browser.preferences.inContent", true); - let panes = [ ["paneGeneral", null], ["paneSearch", null], ["paneContent", null], ["paneApplications", null], ["panePrivacy", null], + ["panePrivacy", null, DNTDialog], ["paneSecurity", null], ["paneSync", null], ["paneAdvanced", "generalTab"], @@ -32,40 +30,41 @@ this.Preferences = { ["paneAdvanced", "networkTab"], ["paneAdvanced", "updateTab"], ["paneAdvanced", "encryptionTab"], + ["paneAdvanced", "encryptionTab", certManager], ]; - for (let [primary, advanced] of panes) { + for (let [primary, advanced, customFn] of panes) { let configName = primary.replace(/^pane/, "prefs") + (advanced ? "-" + advanced : ""); + if (customFn) { + configName += "-" + customFn.name; + } this.configurations[configName] = {}; - this.configurations[configName].applyConfig = prefHelper.bind(null, primary, advanced); + this.configurations[configName].applyConfig = prefHelper.bind(null, primary, advanced, customFn); } }, - configurations: { - "panePrivacy-DNTDialog": { - applyConfig: Task.async(function*() { - let browserWindow = Services.wm.getMostRecentWindow("navigator:browser"); - yield prefHelper("panePrivacy", null); - - yield ContentTask.spawn(browserWindow.gBrowser.selectedBrowser, null, function* () { - content.document.getElementById("doNotTrackSettings").click(); - }); - }), - }, - }, + configurations: {}, }; -let prefHelper = Task.async(function*(primary, advanced) { +let prefHelper = Task.async(function*(primary, advanced = null, customFn = null) { let browserWindow = Services.wm.getMostRecentWindow("navigator:browser"); - let selectedBrowser = browserWindow.gBrowser; + let selectedBrowser = browserWindow.gBrowser.selectedBrowser; + + // close any dialog that might still be open + yield ContentTask.spawn(selectedBrowser, null, function*() { + if (!content.window.gSubDialog) { + return; + } + content.window.gSubDialog.close(); + }); + let readyPromise = null; if (selectedBrowser.currentURI.specIgnoringRef == "about:preferences") { - readyPromise = new Promise((resolve) => { - browserWindow.addEventListener("MozAfterPaint", function paneSwitch() { - browserWindow.removeEventListener("MozAfterPaint", paneSwitch); - resolve(); - }); - }); - + if (selectedBrowser.currentURI.spec == "about:preferences#" + primary.replace(/^pane/, "")) { + // We're already on the correct pane. + readyPromise = Promise.resolve(); + } else { + readyPromise = paintPromise(browserWindow); + } } else { readyPromise = TestUtils.topicObserved("advanced-pane-loaded"); } @@ -78,8 +77,30 @@ let prefHelper = Task.async(function*(primary, advanced) { yield readyPromise; - // close any dialog that might still be open - yield ContentTask.spawn(selectedBrowser.selectedBrowser, null, function*() { - content.window.gSubDialog.close(); - }); + if (customFn) { + let customPaintPromise = paintPromise(browserWindow); + yield* customFn(selectedBrowser); + yield customPaintPromise; + } }); + +function paintPromise(browserWindow) { + return new Promise((resolve) => { + browserWindow.addEventListener("MozAfterPaint", function onPaint() { + browserWindow.removeEventListener("MozAfterPaint", onPaint); + resolve(); + }); + }); +} + +function* DNTDialog(aBrowser) { + yield ContentTask.spawn(aBrowser, null, function* () { + content.document.getElementById("doNotTrackSettings").click(); + }); +} + +function* certManager(aBrowser) { + yield ContentTask.spawn(aBrowser, null, function* () { + content.document.getElementById("viewCertificatesButton").click(); + }); +} diff --git a/build/docs/toolchains.rst b/build/docs/toolchains.rst index e6553a562169bf71e556bb8ad3f4e7f5847fa62e..eba640fa06092ba90421d79587dd5feaec84800e 100644 --- a/build/docs/toolchains.rst +++ b/build/docs/toolchains.rst @@ -51,7 +51,7 @@ Once Visual Studio 2015 Community has been installed, from a checkout of mozilla-central, run something like the following to produce a ZIP archive:: - $ ./mach python build/windows_toolchain.py create-zip vs2015u2 + $ ./mach python build/windows_toolchain.py create-zip vs2015u3 The produced archive will be the argument to ``create-zip`` + ``.zip``. diff --git a/build/moz.configure/toolchain.configure b/build/moz.configure/toolchain.configure index bba62023c8365745dacd1594d5e24d273ff87e97..87bd50bef1c65d1c98e8e9025e11f62f0830ab78 100644 --- a/build/moz.configure/toolchain.configure +++ b/build/moz.configure/toolchain.configure @@ -274,9 +274,11 @@ def get_compiler_info(compiler, language): raise FatalCheckError( 'Unknown compiler or compiler not supported.') + # Metadata emitted by preprocessors such as GCC with LANG=ja_JP.utf-8 may + # have non-ASCII characters. Treat the output as bytearray. data = {} for line in result.splitlines(): - if line.startswith('%'): + if line.startswith(b'%'): k, _, v = line.partition(' ') k = k.lstrip('%') data[k] = v.replace(' ', '').lstrip('"').rstrip('"') diff --git a/build/win32/mozconfig.vs2015-win64 b/build/win32/mozconfig.vs2015-win64 index c7c13c0c91e7e208fc7082198a51d706f0c01062..b81afa681dc3dca483a73cc2b5cedd716634ebe5 100644 --- a/build/win32/mozconfig.vs2015-win64 +++ b/build/win32/mozconfig.vs2015-win64 @@ -1,6 +1,6 @@ if [ -z "${VSPATH}" ]; then TOOLTOOL_DIR=${TOOLTOOL_DIR:-$topsrcdir} - VSPATH="$(cd ${TOOLTOOL_DIR} && pwd)/vs2015u2" + VSPATH="$(cd ${TOOLTOOL_DIR} && pwd)/vs2015u3" fi VSWINPATH="$(cd ${VSPATH} && pwd -W)" @@ -12,8 +12,8 @@ export WIN_UCRT_REDIST_DIR="${VSPATH}/SDK/Redist/ucrt/DLLs/x86" export PATH="${VSPATH}/VC/bin/amd64_x86:${VSPATH}/VC/bin/amd64:${VSPATH}/VC/bin:${VSPATH}/SDK/bin/x86:${VSPATH}/SDK/bin/x64:${VSPATH}/DIA SDK/bin:${PATH}" export PATH="${VSPATH}/VC/redist/x86/Microsoft.VC140.CRT:${VSPATH}/VC/redist/x64/Microsoft.VC140.CRT:${VSPATH}/SDK/Redist/ucrt/DLLs/x86:${VSPATH}/SDK/Redist/ucrt/DLLs/x64:${PATH}" -export INCLUDE="${VSPATH}/VC/include:${VSPATH}/VC/atlmfc/include:${VSPATH}/SDK/Include/10.0.10586.0/ucrt:${VSPATH}/SDK/Include/10.0.10586.0/shared:${VSPATH}/SDK/Include/10.0.10586.0/um:${VSPATH}/SDK/Include/10.0.10586.0/winrt:${VSPATH}/DIA SDK/include" -export LIB="${VSPATH}/VC/lib:${VSPATH}/VC/atlmfc/lib:${VSPATH}/SDK/lib/10.0.10586.0/ucrt/x86:${VSPATH}/SDK/lib/10.0.10586.0/um/x86:${VSPATH}/DIA SDK/lib" +export INCLUDE="${VSPATH}/VC/include:${VSPATH}/VC/atlmfc/include:${VSPATH}/SDK/Include/10.0.14393.0/ucrt:${VSPATH}/SDK/Include/10.0.14393.0/shared:${VSPATH}/SDK/Include/10.0.14393.0/um:${VSPATH}/SDK/Include/10.0.14393.0/winrt:${VSPATH}/DIA SDK/include" +export LIB="${VSPATH}/VC/lib:${VSPATH}/VC/atlmfc/lib:${VSPATH}/SDK/lib/10.0.14393.0/ucrt/x86:${VSPATH}/SDK/lib/10.0.14393.0/um/x86:${VSPATH}/DIA SDK/lib" . $topsrcdir/build/mozconfig.vs-common diff --git a/build/win64/mozconfig.vs2015 b/build/win64/mozconfig.vs2015 index c38aae4b0e53cbf765ded35c63646794372e4d9c..e81a00064c21b56f2c463869bb135fdc1b6b71b5 100644 --- a/build/win64/mozconfig.vs2015 +++ b/build/win64/mozconfig.vs2015 @@ -1,6 +1,6 @@ if [ -z "${VSPATH}" ]; then TOOLTOOL_DIR=${TOOLTOOL_DIR:-$topsrcdir} - VSPATH="$(cd ${TOOLTOOL_DIR} && pwd)/vs2015u2" + VSPATH="$(cd ${TOOLTOOL_DIR} && pwd)/vs2015u3" fi VSWINPATH="$(cd ${VSPATH} && pwd -W)" @@ -11,8 +11,8 @@ export WIN_UCRT_REDIST_DIR="${VSPATH}/SDK/Redist/ucrt/DLLs/x64" export PATH="${VSPATH}/VC/bin/amd64:${VSPATH}/VC/bin:${VSPATH}/SDK/bin/x64:${VSPATH}/VC/redist/x64/Microsoft.VC140.CRT:${VSPATH}/SDK/Redist/ucrt/DLLs/x64:${VSPATH}/DIA SDK/bin/amd64:${PATH}" -export INCLUDE="${VSPATH}/VC/include:${VSPATH}/VC/atlmfc/include:${VSPATH}/SDK/Include/10.0.10586.0/ucrt:${VSPATH}/SDK/Include/10.0.10586.0/shared:${VSPATH}/SDK/Include/10.0.10586.0/um:${VSPATH}/SDK/Include/10.0.10586.0/winrt:${VSPATH}/DIA SDK/include" -export LIB="${VSPATH}/VC/lib/amd64:${VSPATH}/VC/atlmfc/lib/amd64:${VSPATH}/SDK/lib/10.0.10586.0/ucrt/x64:${VSPATH}/SDK/lib/10.0.10586.0/um/x64:${VSPATH}/DIA SDK/lib/amd64" +export INCLUDE="${VSPATH}/VC/include:${VSPATH}/VC/atlmfc/include:${VSPATH}/SDK/Include/10.0.14393.0/ucrt:${VSPATH}/SDK/Include/10.0.14393.0/shared:${VSPATH}/SDK/Include/10.0.14393.0/um:${VSPATH}/SDK/Include/10.0.14393.0/winrt:${VSPATH}/DIA SDK/include" +export LIB="${VSPATH}/VC/lib/amd64:${VSPATH}/VC/atlmfc/lib/amd64:${VSPATH}/SDK/lib/10.0.14393.0/ucrt/x64:${VSPATH}/SDK/lib/10.0.14393.0/um/x64:${VSPATH}/DIA SDK/lib/amd64" . $topsrcdir/build/mozconfig.vs-common diff --git a/build/windows_toolchain.py b/build/windows_toolchain.py index ff898375486f78f08f6b9d17b4001401ea299b8a..22e656eff3b015c720ab63e4b5c8432004b21cb8 100644 --- a/build/windows_toolchain.py +++ b/build/windows_toolchain.py @@ -84,7 +84,7 @@ VS_PATTERNS = [ }, ] -SDK_RELEASE = '10.0.10586.0' +SDK_RELEASE = '10.0.14393.0' # Files from the Windows 10 SDK to install. SDK_PATTERNS = [ diff --git a/config/msvc-stl-wrapper.template.h b/config/msvc-stl-wrapper.template.h index b8f774e88236763a6290c1bd760bfba72d845755..ed9d98b0dd1b89e7970e138c7f2b8117e662d6ce 100644 --- a/config/msvc-stl-wrapper.template.h +++ b/config/msvc-stl-wrapper.template.h @@ -59,7 +59,11 @@ #pragma warning( push ) #pragma warning( disable : 4275 4530 ) +#ifdef __clang__ +#include_next <${HEADER}> +#else #include <${HEADER_PATH}> +#endif #pragma warning( pop ) diff --git a/devtools/client/aboutdebugging/test/head.js b/devtools/client/aboutdebugging/test/head.js index 6db065d2d65b30fbdbf9ed4e3c7273c9fdb40249..a158826da29d7c92a0ca54c3db3e692db30be05a 100644 --- a/devtools/client/aboutdebugging/test/head.js +++ b/devtools/client/aboutdebugging/test/head.js @@ -82,11 +82,11 @@ function addTab(url, win, backgroundTab = false) { } let linkedBrowser = tab.linkedBrowser; - linkedBrowser.addEventListener("load", function onLoad() { - linkedBrowser.removeEventListener("load", onLoad, true); - info("Tab added and finished loading: " + url); - done(tab); - }, true); + BrowserTestUtils.browserLoaded(linkedBrowser) + .then(function () { + info("Tab added and finished loading: " + url); + done(tab); + }); }); } diff --git a/devtools/client/canvasdebugger/test/head.js b/devtools/client/canvasdebugger/test/head.js index 8f0da64ccd75785e98bdc7e65196930e77d4092c..a718551cee02b4d1af60bf83dec7dda169926ebb 100644 --- a/devtools/client/canvasdebugger/test/head.js +++ b/devtools/client/canvasdebugger/test/head.js @@ -81,11 +81,11 @@ function addTab(aUrl, aWindow) { let tab = targetBrowser.selectedTab = targetBrowser.addTab(aUrl); let linkedBrowser = tab.linkedBrowser; - linkedBrowser.addEventListener("load", function onLoad() { - linkedBrowser.removeEventListener("load", onLoad, true); - info("Tab added and finished loading: " + aUrl); - deferred.resolve(tab); - }, true); + BrowserTestUtils.browserLoaded(linkedBrowser) + .then(function () { + info("Tab added and finished loading: " + aUrl); + deferred.resolve(tab); + }); return deferred.promise; } diff --git a/devtools/client/debugger/test/mochitest/head.js b/devtools/client/debugger/test/mochitest/head.js index b7271007df74621024227624b9c2d5293531dc07..9ca0e0c8698c75fcc3159adafcfd32da3463ec52 100644 --- a/devtools/client/debugger/test/mochitest/head.js +++ b/devtools/client/debugger/test/mochitest/head.js @@ -91,11 +91,11 @@ this.addTab = function addTab(aUrl, aWindow) { info("Loading frame script with url " + FRAME_SCRIPT_URL + "."); linkedBrowser.messageManager.loadFrameScript(FRAME_SCRIPT_URL, false); - linkedBrowser.addEventListener("load", function onLoad() { - linkedBrowser.removeEventListener("load", onLoad, true); - info("Tab added and finished loading: " + aUrl); - deferred.resolve(tab); - }, true); + BrowserTestUtils.browserLoaded(linkedBrowser) + .then(function () { + info("Tab added and finished loading: " + aUrl); + deferred.resolve(tab); + }); return deferred.promise; }; diff --git a/devtools/client/framework/test/browser_keybindings_01.js b/devtools/client/framework/test/browser_keybindings_01.js index 54b5ca6b8ac3586b67186f4b77446611acde2b1e..4e4effb070c53c2a8b37bd79da78ba9735eff466 100644 --- a/devtools/client/framework/test/browser_keybindings_01.js +++ b/devtools/client/framework/test/browser_keybindings_01.js @@ -5,7 +5,9 @@ // Tests that the keybindings for opening and closing the inspector work as expected // Can probably make this a shared test that tests all of the tools global keybindings - +const TEST_URL = "data:text/html,<html><head><title>Test for the " + + "highlighter keybindings</title></head><body>" + + "<h1>Keybindings!</h1></body></html>" function test() { waitForExplicitFinish(); @@ -15,17 +17,11 @@ function test() let inspector; let keysetMap = { }; - gBrowser.selectedTab = gBrowser.addTab(); - gBrowser.selectedBrowser.addEventListener("load", function onload() { - gBrowser.selectedBrowser.removeEventListener("load", onload, true); + addTab(TEST_URL).then(function () { doc = content.document; node = doc.querySelector("h1"); waitForFocus(setupKeyBindingsTest); - }, true); - - content.location = "data:text/html,<html><head><title>Test for the " + - "highlighter keybindings</title></head><body>" + - "<h1>Keybindings!</h1></body></html>"; + }); function buildDevtoolsKeysetMap(keyset) { [].forEach.call(keyset.querySelectorAll("key"), function (key) { diff --git a/devtools/client/framework/test/browser_toolbox_custom_host.js b/devtools/client/framework/test/browser_toolbox_custom_host.js index 48700a5147c9e0c9538d64a4bed822aff2ebb0ea..8d5f2215d2b1544cfd21c940b9382cbcb5e44360 100644 --- a/devtools/client/framework/test/browser_toolbox_custom_host.js +++ b/devtools/client/framework/test/browser_toolbox_custom_host.js @@ -3,28 +3,25 @@ /* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ +const TEST_URL = "data:text/html,test custom host"; + function test() { let {Toolbox} = require("devtools/client/framework/toolbox"); - let toolbox, iframe, target, tab; - - gBrowser.selectedTab = gBrowser.addTab(); - target = TargetFactory.forTab(gBrowser.selectedTab); + let toolbox, iframe, target; window.addEventListener("message", onMessage); iframe = document.createElement("iframe"); document.documentElement.appendChild(iframe); - gBrowser.selectedBrowser.addEventListener("load", function onLoad(evt) { - gBrowser.selectedBrowser.removeEventListener(evt.type, onLoad, true); + addTab(TEST_URL).then(function (tab) { + target = TargetFactory.forTab(tab); let options = {customIframe: iframe}; gDevTools.showToolbox(target, null, Toolbox.HostType.CUSTOM, options) .then(testCustomHost, console.error) .then(null, console.error); - }, true); - - content.location = "data:text/html,test custom host"; + }); function onMessage(event) { info("onMessage: " + event.data); @@ -50,7 +47,7 @@ function test() { // toolbox.destroy() returns a singleton promise that ensures // everything is cleaned up before proceeding. toolbox.destroy().then(() => { - toolbox = iframe = target = tab = null; + toolbox = iframe = target = null; finish(); }); } diff --git a/devtools/client/framework/test/browser_toolbox_dynamic_registration.js b/devtools/client/framework/test/browser_toolbox_dynamic_registration.js index 7c7337fe671d742ec41eae9c37dec9c5a6ef3bb7..2583ca68e9fa1f8890cfb4c5b3d914a468f59f08 100644 --- a/devtools/client/framework/test/browser_toolbox_dynamic_registration.js +++ b/devtools/client/framework/test/browser_toolbox_dynamic_registration.js @@ -3,19 +3,16 @@ /* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ +const TEST_URL = "data:text/html,test for dynamically registering and unregistering tools"; + var toolbox; function test() { - gBrowser.selectedTab = gBrowser.addTab(); - let target = TargetFactory.forTab(gBrowser.selectedTab); - - gBrowser.selectedBrowser.addEventListener("load", function onLoad(evt) { - gBrowser.selectedBrowser.removeEventListener(evt.type, onLoad, true); + addTab(TEST_URL).then(tab => { + let target = TargetFactory.forTab(tab); gDevTools.showToolbox(target).then(testRegister); - }, true); - - content.location = "data:text/html,test for dynamically registering and unregistering tools"; + }); } function testRegister(aToolbox) diff --git a/devtools/client/framework/test/browser_toolbox_options_disable_buttons.js b/devtools/client/framework/test/browser_toolbox_options_disable_buttons.js index 5e2faae843c2269c5bdeda53bfa75ab0a5ca516a..a068f822e5dc69c48fd52b2058d92e2388a8f7ac 100644 --- a/devtools/client/framework/test/browser_toolbox_options_disable_buttons.js +++ b/devtools/client/framework/test/browser_toolbox_options_disable_buttons.js @@ -6,23 +6,19 @@ /* import-globals-from shared-head.js */ "use strict"; +const TEST_URL = "data:text/html;charset=utf8,test for dynamically " + + "registering and unregistering tools"; var doc = null, toolbox = null, panelWin = null, modifiedPrefs = []; function test() { - gBrowser.selectedTab = gBrowser.addTab(); - let target = TargetFactory.forTab(gBrowser.selectedTab); - - gBrowser.selectedBrowser.addEventListener("load", function onLoad(evt) { - gBrowser.selectedBrowser.removeEventListener(evt.type, onLoad, true); + addTab(TEST_URL).then(tab => { + let target = TargetFactory.forTab(tab); gDevTools.showToolbox(target) .then(testSelectTool) .then(testToggleToolboxButtons) .then(testPrefsAreRespectedWhenReopeningToolbox) .then(cleanup, errorHandler); - }, true); - - content.location = "data:text/html;charset=utf8,test for dynamically " + - "registering and unregistering tools"; + }); } function testPrefsAreRespectedWhenReopeningToolbox() { diff --git a/devtools/client/framework/test/browser_toolbox_options_disable_js.js b/devtools/client/framework/test/browser_toolbox_options_disable_js.js index 7db8ba2357a3da998854ad9a85377e5295e6b564..b0c14a805fd2da16184c71c0850f80b5fee8e8a3 100644 --- a/devtools/client/framework/test/browser_toolbox_options_disable_js.js +++ b/devtools/client/framework/test/browser_toolbox_options_disable_js.js @@ -8,15 +8,10 @@ const TEST_URI = URL_ROOT + "browser_toolbox_options_disable_js.html"; function test() { - gBrowser.selectedTab = gBrowser.addTab(); - let target = TargetFactory.forTab(gBrowser.selectedTab); - - gBrowser.selectedBrowser.addEventListener("load", function onLoad(evt) { - gBrowser.selectedBrowser.removeEventListener(evt.type, onLoad, true); + addTab(TEST_URI).then(tab => { + let target = TargetFactory.forTab(tab); gDevTools.showToolbox(target).then(testSelectTool); - }, true); - - BrowserTestUtils.loadURI(gBrowser.selectedBrowser, TEST_URI); + }); } function testSelectTool(toolbox) { diff --git a/devtools/client/framework/test/browser_toolbox_options_enable_serviceworkers_testing.js b/devtools/client/framework/test/browser_toolbox_options_enable_serviceworkers_testing.js index 95e2267c20ce929025e8bdf5343d4d3709492323..3273f439514e6595d2ed71bbb0c74558abfe0800 100644 --- a/devtools/client/framework/test/browser_toolbox_options_enable_serviceworkers_testing.js +++ b/devtools/client/framework/test/browser_toolbox_options_enable_serviceworkers_testing.js @@ -31,19 +31,15 @@ function test() { } function init() { - let tab = gBrowser.selectedTab = gBrowser.addTab(); - let target = TargetFactory.forTab(gBrowser.selectedTab); - let linkedBrowser = tab.linkedBrowser; + addTab(TEST_URI).then(tab => { + let target = TargetFactory.forTab(tab); + let linkedBrowser = tab.linkedBrowser; - linkedBrowser.messageManager.loadFrameScript(COMMON_FRAME_SCRIPT_URL, false); - linkedBrowser.messageManager.loadFrameScript(FRAME_SCRIPT_URL, false); + linkedBrowser.messageManager.loadFrameScript(COMMON_FRAME_SCRIPT_URL, false); + linkedBrowser.messageManager.loadFrameScript(FRAME_SCRIPT_URL, false); - gBrowser.selectedBrowser.addEventListener("load", function onLoad(evt) { - gBrowser.selectedBrowser.removeEventListener(evt.type, onLoad, true); gDevTools.showToolbox(target).then(testSelectTool); - }, true); - - content.location = TEST_URI; + }); } function testSelectTool(aToolbox) { diff --git a/devtools/client/framework/test/browser_toolbox_raise.js b/devtools/client/framework/test/browser_toolbox_raise.js index fe4a163747e6fe701553fc0bf6319e9039d74244..c1f26659fec0307201b7ab8fdf26516c89c0ca78 100644 --- a/devtools/client/framework/test/browser_toolbox_raise.js +++ b/devtools/client/framework/test/browser_toolbox_raise.js @@ -3,23 +3,20 @@ /* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ +const TEST_URL = "data:text/html,test for opening toolbox in different hosts"; + var {Toolbox} = require("devtools/client/framework/toolbox"); -var toolbox, target, tab1, tab2; +var toolbox, tab1, tab2; function test() { - gBrowser.selectedTab = tab1 = gBrowser.addTab(); - tab2 = gBrowser.addTab(); - target = TargetFactory.forTab(gBrowser.selectedTab); - - gBrowser.selectedBrowser.addEventListener("load", function onLoad(evt) { - gBrowser.selectedBrowser.removeEventListener(evt.type, onLoad, true); + addTab(TEST_URL).then(tab => { + tab2 = gBrowser.addTab(); + let target = TargetFactory.forTab(tab); gDevTools.showToolbox(target) .then(testBottomHost, console.error) .then(null, console.error); - }, true); - - content.location = "data:text/html,test for opening toolbox in different hosts"; + }); } function testBottomHost(aToolbox) { @@ -73,7 +70,7 @@ function cleanup() { Services.prefs.setCharPref("devtools.toolbox.host", Toolbox.HostType.BOTTOM); toolbox.destroy().then(function () { - toolbox = target = null; + toolbox = null; gBrowser.removeCurrentTab(); gBrowser.removeCurrentTab(); finish(); diff --git a/devtools/client/framework/test/browser_toolbox_ready.js b/devtools/client/framework/test/browser_toolbox_ready.js index 1d61474fc864a94f7c0d0cf4e76d9d7695159f3e..e1a59b3f0b642673290dbda284d2ac38ab4dbb19 100644 --- a/devtools/client/framework/test/browser_toolbox_ready.js +++ b/devtools/client/framework/test/browser_toolbox_ready.js @@ -3,25 +3,19 @@ /* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ -function test() { - gBrowser.selectedTab = gBrowser.addTab(); - let target = TargetFactory.forTab(gBrowser.selectedTab); +const TEST_URL = "data:text/html,test for toolbox being ready"; - const onLoad = Task.async(function* (evt) { - gBrowser.selectedBrowser.removeEventListener("load", onLoad); +add_task(function* () { + let tab = yield addTab(TEST_URL); + let target = TargetFactory.forTab(tab); - const toolbox = yield gDevTools.showToolbox(target, "webconsole"); - ok(toolbox.isReady, "toolbox isReady is set"); - ok(toolbox.threadClient, "toolbox has a thread client"); + const toolbox = yield gDevTools.showToolbox(target, "webconsole"); + ok(toolbox.isReady, "toolbox isReady is set"); + ok(toolbox.threadClient, "toolbox has a thread client"); - const toolbox2 = yield gDevTools.showToolbox(toolbox.target, toolbox.toolId); - is(toolbox2, toolbox, "same toolbox"); + const toolbox2 = yield gDevTools.showToolbox(toolbox.target, toolbox.toolId); + is(toolbox2, toolbox, "same toolbox"); - yield toolbox.destroy(); - gBrowser.removeCurrentTab(); - finish(); - }); - - gBrowser.selectedBrowser.addEventListener("load", onLoad, true); - content.location = "data:text/html,test for toolbox being ready"; -} + yield toolbox.destroy(); + gBrowser.removeCurrentTab(); +}); diff --git a/devtools/client/framework/test/browser_two_tabs.js b/devtools/client/framework/test/browser_two_tabs.js index 3f997ed48b267db04efdee1ab00c39fae4a26e28..08d5f2391b3cca837f80f26fa4a51dbcb6c97a7e 100644 --- a/devtools/client/framework/test/browser_two_tabs.js +++ b/devtools/client/framework/test/browser_two_tabs.js @@ -30,17 +30,14 @@ function test() { function openTabs() { // Open two tabs, select the second - gTab1 = gBrowser.addTab(TAB_URL_1); - gTab1.linkedBrowser.addEventListener("load", function onLoad1(evt) { - gTab1.linkedBrowser.removeEventListener("load", onLoad1); - - gTab2 = gBrowser.selectedTab = gBrowser.addTab(TAB_URL_2); - gTab2.linkedBrowser.addEventListener("load", function onLoad2(evt) { - gTab2.linkedBrowser.removeEventListener("load", onLoad2); + addTab(TAB_URL_1).then(tab1 => { + gTab1 = tab1; + addTab(TAB_URL_2).then(tab2 => { + gTab2 = tab2; connect(); - }, true); - }, true); + }); + }); } function connect() { diff --git a/devtools/client/framework/test/helper_disable_cache.js b/devtools/client/framework/test/helper_disable_cache.js index fbc1634e4a30a691e636882336e723e1b60e9541..5e2feef8f2ec0434a730234c2f2b46ff295083be 100644 --- a/devtools/client/framework/test/helper_disable_cache.js +++ b/devtools/client/framework/test/helper_disable_cache.js @@ -91,12 +91,10 @@ function reloadTab(tabX) { let def = defer(); let browser = gBrowser.selectedBrowser; - // once() doesn't work here so we use a standard handler instead. - browser.addEventListener("load", function onLoad() { - browser.removeEventListener("load", onLoad, true); + BrowserTestUtils.browserLoaded(browser).then(function () { info("Reloaded tab " + tabX.title); def.resolve(); - }, true); + }); info("Reloading tab " + tabX.title); let mm = getFrameScript(); diff --git a/devtools/client/framework/test/shared-head.js b/devtools/client/framework/test/shared-head.js index d51e487834bb40b869ff1b693682d96d3bb980a5..eef571b47923c708661394286a2d9b31c6adf159 100644 --- a/devtools/client/framework/test/shared-head.js +++ b/devtools/client/framework/test/shared-head.js @@ -113,7 +113,7 @@ var addTab = Task.async(function* (url) { info("Adding a new tab with URL: " + url); let tab = gBrowser.selectedTab = gBrowser.addTab(url); - yield once(gBrowser.selectedBrowser, "load", true); + yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser); info("Tab added and finished loading"); @@ -142,7 +142,7 @@ var removeTab = Task.async(function* (tab) { */ var refreshTab = Task.async(function*(tab) { info("Refreshing tab."); - const finished = once(gBrowser.selectedBrowser, "load", true); + const finished = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser); gBrowser.reloadTab(gBrowser.selectedTab); yield finished; info("Tab finished refreshing."); @@ -291,9 +291,7 @@ function waitForTick() { * @return A promise that resolves when the time is passed */ function wait(ms) { - let def = defer(); - content.setTimeout(def.resolve, ms); - return def.promise; + return new promise(resolve => setTimeout(resolve, ms)); } /** diff --git a/devtools/client/framework/toolbox.js b/devtools/client/framework/toolbox.js index dbe405c948568ba6c8450f3d7af6a61e1f5a8922..7d8626b7251c3a889d6082acde0ba458a859fe92 100644 --- a/devtools/client/framework/toolbox.js +++ b/devtools/client/framework/toolbox.js @@ -551,8 +551,7 @@ Toolbox.prototype = { this.toggleSplitConsole(); // If the debugger is paused, don't let the ESC key stop any pending // navigation. - let jsdebugger = this.getPanel("jsdebugger"); - if (jsdebugger && jsdebugger.panelWin.gThreadClient.state == "paused") { + if (this._threadClient.state == "paused") { e.preventDefault(); } } diff --git a/devtools/client/inspector/computed/test/browser_computed_search-filter_context-menu.js b/devtools/client/inspector/computed/test/browser_computed_search-filter_context-menu.js index 5120a3eb4a46be3d5aa67d622f03da29c86a6359..34d8fb2a2dbb53218e2e9af75ce8c6507df2fce0 100644 --- a/devtools/client/inspector/computed/test/browser_computed_search-filter_context-menu.js +++ b/devtools/client/inspector/computed/test/browser_computed_search-filter_context-menu.js @@ -52,7 +52,7 @@ add_task(function* () { EventUtils.synthesizeMouse(searchField, 2, 2, {type: "contextmenu", button: 2}, win); yield onContextMenuPopup; - yield waitForClipboard(() => cmdCopy.click(), TEST_INPUT); + yield waitForClipboardPromise(() => cmdCopy.click(), TEST_INPUT); searchContextMenu.hidePopup(); yield onContextMenuHidden; diff --git a/devtools/client/inspector/computed/test/browser_computed_select-and-copy-styles.js b/devtools/client/inspector/computed/test/browser_computed_select-and-copy-styles.js index 37b7f1a9e0c5e13e54a871459cdb3a1eda3cd867..ce8be59ad827839efc340d43db7135ef209e7648 100644 --- a/devtools/client/inspector/computed/test/browser_computed_select-and-copy-styles.js +++ b/devtools/client/inspector/computed/test/browser_computed_select-and-copy-styles.js @@ -62,7 +62,7 @@ function* checkCopySelection(view) { "font-variant-caps: small-caps;[\\r\\n]*"; try { - yield waitForClipboard(() => fireCopyEvent(props[0]), + yield waitForClipboardPromise(() => fireCopyEvent(props[0]), () => checkClipboardData(expectedPattern)); } catch (e) { failedClipboard(expectedPattern); @@ -84,7 +84,7 @@ function* checkSelectAll(view) { "font-variant-caps: small-caps;[\\r\\n]*"; try { - yield waitForClipboard(() => fireCopyEvent(prop), + yield waitForClipboardPromise(() => fireCopyEvent(prop), () => checkClipboardData(expectedPattern)); } catch (e) { failedClipboard(expectedPattern); diff --git a/devtools/client/inspector/markup/test/browser_markup_links_05.js b/devtools/client/inspector/markup/test/browser_markup_links_05.js index 08ce2c2cd7719d0e20f77c13af13ac3d92dbfa91..feaf257a83516d16b418125a5b86cf2fb05e4fe6 100644 --- a/devtools/client/inspector/markup/test/browser_markup_links_05.js +++ b/devtools/client/inspector/markup/test/browser_markup_links_05.js @@ -25,7 +25,7 @@ add_task(function* () { let onTabOpened = once(gBrowser.tabContainer, "TabOpen"); inspector.onFollowLink(); let {target: tab} = yield onTabOpened; - yield waitForTabLoad(tab); + yield BrowserTestUtils.browserLoaded(tab.linkedBrowser); ok(true, "A new tab opened"); is(tab.linkedBrowser.currentURI.spec, URL_ROOT + "doc_markup_tooltip.png", @@ -67,16 +67,3 @@ add_task(function* () { is(inspector.selection.nodeFront.tagName.toLowerCase(), "output", "The <output> node is still selected"); }); - -function waitForTabLoad(tab) { - let def = defer(); - tab.addEventListener("load", function onLoad(e) { - // Skip load event for about:blank - if (tab.linkedBrowser.currentURI.spec === "about:blank") { - return; - } - tab.removeEventListener("load", onLoad); - def.resolve(); - }); - return def.promise; -} diff --git a/devtools/client/inspector/markup/test/browser_markup_links_07.js b/devtools/client/inspector/markup/test/browser_markup_links_07.js index b322285db18bbbbef3e9decd57ef86ab1fc76057..793c1ee90922151566f46e36f5df0023b31c6bc7 100644 --- a/devtools/client/inspector/markup/test/browser_markup_links_07.js +++ b/devtools/client/inspector/markup/test/browser_markup_links_07.js @@ -58,19 +58,6 @@ add_task(function* () { yield followLinkNoNewNode(linkEl, true, inspector); }); -function waitForTabLoad(tab) { - let def = defer(); - tab.addEventListener("load", function onLoad() { - // Skip load event for about:blank - if (tab.linkedBrowser.currentURI.spec === "about:blank") { - return; - } - tab.removeEventListener("load", onLoad); - def.resolve(); - }); - return def.promise; -} - function performMouseDown(linkEl, metactrl) { let evt = linkEl.ownerDocument.createEvent("MouseEvents"); @@ -95,7 +82,7 @@ function* followLinkWaitForTab(linkEl, isMetaClick, expectedTabURI) { let onTabOpened = once(gBrowser.tabContainer, "TabOpen"); performMouseDown(linkEl, isMetaClick); let {target} = yield onTabOpened; - yield waitForTabLoad(target); + yield BrowserTestUtils.browserLoaded(target.linkedBrowser); ok(true, "A new tab opened"); is(target.linkedBrowser.currentURI.spec, expectedTabURI, "The URL for the new tab is correct"); diff --git a/devtools/client/inspector/markup/test/head.js b/devtools/client/inspector/markup/test/head.js index e38bce947269bf3544af9f498849c792b9a64419..f7d55a27211df35d58ed4a98a56414fe3aff0c1c 100644 --- a/devtools/client/inspector/markup/test/head.js +++ b/devtools/client/inspector/markup/test/head.js @@ -271,18 +271,6 @@ function searchUsingSelectorSearch(selector, inspector) { EventUtils.sendKey("return", inspector.panelWin); } -/** - * This shouldn't be used in the tests, but is useful when writing new tests or - * debugging existing tests in order to introduce delays in the test steps - * @param {Number} ms The time to wait - * @return A promise that resolves when the time is passed - */ -function wait(ms) { - let def = defer(); - setTimeout(def.resolve, ms); - return def.promise; -} - /** * Check to see if the inspector menu items for editing are disabled. * Things like Edit As HTML, Delete Node, etc. diff --git a/devtools/client/inspector/rules/test/browser_rules_copy_styles.js b/devtools/client/inspector/rules/test/browser_rules_copy_styles.js index e5e6f1ec92d939c76f9771791af3d22c2d8b6711..a6f991a60dbf2f1f989e578e49ef9945a360ab89 100644 --- a/devtools/client/inspector/rules/test/browser_rules_copy_styles.js +++ b/devtools/client/inspector/rules/test/browser_rules_copy_styles.js @@ -267,7 +267,7 @@ function* checkCopyStyle(view, node, menuItemLabel, expectedPattern, visible) { visible.copyRule); try { - yield waitForClipboard(() => menuItem.click(), + yield waitForClipboardPromise(() => menuItem.click(), () => checkClipboardData(expectedPattern)); } catch (e) { failedClipboard(expectedPattern); diff --git a/devtools/client/inspector/rules/test/browser_rules_search-filter_context-menu.js b/devtools/client/inspector/rules/test/browser_rules_search-filter_context-menu.js index 2ce7bf4eb0406a9ee3cd5e4fdb89da96d582e8c5..2b4120116f7ac1accb25fc7696938731398c5bef 100644 --- a/devtools/client/inspector/rules/test/browser_rules_search-filter_context-menu.js +++ b/devtools/client/inspector/rules/test/browser_rules_search-filter_context-menu.js @@ -51,7 +51,7 @@ add_task(function* () { EventUtils.synthesizeMouse(searchField, 2, 2, {type: "contextmenu", button: 2}, win); yield onContextMenuPopup; - yield waitForClipboard(() => cmdCopy.click(), TEST_INPUT); + yield waitForClipboardPromise(() => cmdCopy.click(), TEST_INPUT); searchContextMenu.hidePopup(); yield onContextMenuHidden; diff --git a/devtools/client/inspector/rules/test/browser_rules_select-and-copy-styles.js b/devtools/client/inspector/rules/test/browser_rules_select-and-copy-styles.js index 6a6946a84f5091cc2c089d6d6deb050a779e71b5..b3f4ef3648a6ef93cffdfb462f16c07397685ec4 100644 --- a/devtools/client/inspector/rules/test/browser_rules_select-and-copy-styles.js +++ b/devtools/client/inspector/rules/test/browser_rules_select-and-copy-styles.js @@ -75,7 +75,7 @@ function* checkCopySelection(view) { "Copy menu item is displayed as expected"); try { - yield waitForClipboard(() => menuitemCopy.click(), + yield waitForClipboardPromise(() => menuitemCopy.click(), () => checkClipboardData(expectedPattern)); } catch (e) { failedClipboard(expectedPattern); @@ -109,7 +109,7 @@ function* checkSelectAll(view) { "Copy menu item is displayed as expected"); try { - yield waitForClipboard(() => menuitemCopy.click(), + yield waitForClipboardPromise(() => menuitemCopy.click(), () => checkClipboardData(expectedPattern)); } catch (e) { failedClipboard(expectedPattern); @@ -137,7 +137,7 @@ function* checkCopyEditorValue(view) { "Copy menu item is displayed as expected"); try { - yield waitForClipboard(() => menuitemCopy.click(), + yield waitForClipboardPromise(() => menuitemCopy.click(), () => checkClipboardData(expectedPattern)); } catch (e) { failedClipboard(expectedPattern); diff --git a/devtools/client/inspector/shared/test/browser_styleinspector_context-menu-copy-color_02.js b/devtools/client/inspector/shared/test/browser_styleinspector_context-menu-copy-color_02.js index 5dee0e1ee9549042d1fb94cfa1da43d435eb311b..afae7a2b6f6f49e236672a5b2b14dec411e36ebe 100644 --- a/devtools/client/inspector/shared/test/browser_styleinspector_context-menu-copy-color_02.js +++ b/devtools/client/inspector/shared/test/browser_styleinspector_context-menu-copy-color_02.js @@ -39,7 +39,7 @@ function* testCopyToClipboard(inspector, view) { ok(menuitemCopyColor.visible, "Copy color is visible"); - yield waitForClipboard(() => menuitemCopyColor.click(), + yield waitForClipboardPromise(() => menuitemCopyColor.click(), "#123ABC"); EventUtils.synthesizeKey("VK_ESCAPE", { }); diff --git a/devtools/client/inspector/shared/test/browser_styleinspector_context-menu-copy-urls.js b/devtools/client/inspector/shared/test/browser_styleinspector_context-menu-copy-urls.js index 45d0a94fa62869b16ed6ad7efbd9b56972fa294c..7e74591ad69c7eefd6c435ae46863840a254e5c6 100644 --- a/devtools/client/inspector/shared/test/browser_styleinspector_context-menu-copy-urls.js +++ b/devtools/client/inspector/shared/test/browser_styleinspector_context-menu-copy-urls.js @@ -87,12 +87,12 @@ function* testCopyUrlToClipboard({view, inspector}, type, selector, expected) { if (type == "data-uri") { info("Click Copy Data URI and wait for clipboard"); - yield waitForClipboard(() => { + yield waitForClipboardPromise(() => { return menuitemCopyImageDataUrl.click(); }, expected); } else { info("Click Copy URL and wait for clipboard"); - yield waitForClipboard(() => { + yield waitForClipboardPromise(() => { return menuitemCopyUrl.click(); }, expected); } diff --git a/devtools/client/inspector/test/browser_inspector_highlighter-eyedropper-clipboard.js b/devtools/client/inspector/test/browser_inspector_highlighter-eyedropper-clipboard.js index 467984124c66bfa48d91b5dad2924afff06d0ba8..c287f29137ced38f40431c4ad885037b628dbdb5 100644 --- a/devtools/client/inspector/test/browser_inspector_highlighter-eyedropper-clipboard.js +++ b/devtools/client/inspector/test/browser_inspector_highlighter-eyedropper-clipboard.js @@ -23,7 +23,7 @@ add_task(function* () { info("Make sure to wait until the eyedropper is done taking a screenshot of the page"); yield waitForElementAttributeSet("root", "drawn", helper); - yield waitForClipboard(() => { + yield waitForClipboardPromise(() => { info("Activate the eyedropper so the background color is copied"); EventUtils.synthesizeKey("VK_RETURN", {}); }, "#FF0000"); diff --git a/devtools/client/inspector/test/browser_inspector_keyboard-shortcuts-copy-outerhtml.js b/devtools/client/inspector/test/browser_inspector_keyboard-shortcuts-copy-outerhtml.js index 38ad0583e89f964b37642beaa501b6c8046c0886..46b0ce5f5afa96ecd8f90b58df995a829a7217f7 100644 --- a/devtools/client/inspector/test/browser_inspector_keyboard-shortcuts-copy-outerhtml.js +++ b/devtools/client/inspector/test/browser_inspector_keyboard-shortcuts-copy-outerhtml.js @@ -34,7 +34,7 @@ function* setSelectionNodeFront(node, inspector) { function* checkClipboard(expectedText, node) { try { - yield waitForClipboard(() => fireCopyEvent(node), expectedText); + yield waitForClipboardPromise(() => fireCopyEvent(node), expectedText); ok(true, "Clipboard successfully filled with : " + expectedText); } catch (e) { ok(false, "Clipboard could not be filled with the expected text : " + diff --git a/devtools/client/inspector/test/browser_inspector_menu-02-copy-items.js b/devtools/client/inspector/test/browser_inspector_menu-02-copy-items.js index 2f5199801b2a9c2dea96d9eeff0ece0660563bc4..0c96e9bbea5e88894c098f672bee198ff48691a8 100644 --- a/devtools/client/inspector/test/browser_inspector_menu-02-copy-items.js +++ b/devtools/client/inspector/test/browser_inspector_menu-02-copy-items.js @@ -44,6 +44,6 @@ add_task(function* () { let item = allMenuItems.find(i => i.id === id); ok(item, "The popup has a " + desc + " menu item."); - yield waitForClipboard(() => item.click(), text); + yield waitForClipboardPromise(() => item.click(), text); } }); diff --git a/devtools/client/inspector/test/browser_inspector_search-filter_context-menu.js b/devtools/client/inspector/test/browser_inspector_search-filter_context-menu.js index 086d43f8c80a22c7d242d932dee8b341b15305c2..9784a6da4d8055b8929302a226a042127bd06cee 100644 --- a/devtools/client/inspector/test/browser_inspector_search-filter_context-menu.js +++ b/devtools/client/inspector/test/browser_inspector_search-filter_context-menu.js @@ -50,7 +50,7 @@ add_task(function* () { EventUtils.synthesizeMouse(searchBox, 2, 2, {type: "contextmenu", button: 2}, win); yield onContextMenuPopup; - yield waitForClipboard(() => cmdCopy.click(), TEST_INPUT); + yield waitForClipboardPromise(() => cmdCopy.click(), TEST_INPUT); searchContextMenu.hidePopup(); yield onContextMenuHidden; diff --git a/devtools/client/inspector/test/head.js b/devtools/client/inspector/test/head.js index b890da1cfeefb497cf377db875e3736cbd0f6b6b..141cb5c20843ed35ebed8b08817bfc5be836bcb6 100644 --- a/devtools/client/inspector/test/head.js +++ b/devtools/client/inspector/test/head.js @@ -618,23 +618,6 @@ function waitForStyleEditor(toolbox, href) { return def.promise; } -/** - * @see SimpleTest.waitForClipboard - * - * @param {Function} setup - * Function to execute before checking for the - * clipboard content - * @param {String|Function} expected - * An expected string or validator function - * @return a promise that resolves when the expected string has been found or - * the validator function has returned true, rejects otherwise. - */ -function waitForClipboard(setup, expected) { - let def = defer(); - SimpleTest.waitForClipboard(expected, setup, def.resolve, def.reject); - return def.promise; -} - /** * Checks if document's active element is within the given element. * @param {HTMLDocument} doc document with active element in question @@ -662,8 +645,7 @@ var waitForTab = Task.async(function* () { info("Waiting for a tab to open"); yield once(gBrowser.tabContainer, "TabOpen"); let tab = gBrowser.selectedTab; - let browser = tab.linkedBrowser; - yield once(browser, "load", true); + yield BrowserTestUtils.browserLoaded(tab.linkedBrowser); info("The tab load completed"); return tab; }); diff --git a/devtools/client/projecteditor/test/head.js b/devtools/client/projecteditor/test/head.js index bbda81262ad0b123ed3ad8fb8fddcc92c31c4efc..d5d9ce8497953dc1b824b94bd0dd279c9d743f79 100644 --- a/devtools/client/projecteditor/test/head.js +++ b/devtools/client/projecteditor/test/head.js @@ -57,15 +57,13 @@ function addTab(url) { info("Adding a new tab with URL: '" + url + "'"); let def = promise.defer(); - let tab = gBrowser.selectedTab = gBrowser.addTab(); - gBrowser.selectedBrowser.addEventListener("load", function onload() { - gBrowser.selectedBrowser.removeEventListener("load", onload, true); + let tab = gBrowser.selectedTab = gBrowser.addTab(url); + BrowserTestUtils.browserLoaded(tab.linkedBrowser).then(function () { info("URL '" + url + "' loading complete"); waitForFocus(() => { def.resolve(tab); }, content); - }, true); - content.location = url; + }); return def.promise; } diff --git a/devtools/client/responsive.html/actions/touch-simulation.js b/devtools/client/responsive.html/actions/touch-simulation.js index 9950d45fbb4b680691599baf572020c7445f3d6c..dc27f9a94830327b23ca776933be45bff08ebd44 100644 --- a/devtools/client/responsive.html/actions/touch-simulation.js +++ b/devtools/client/responsive.html/actions/touch-simulation.js @@ -12,7 +12,7 @@ const { module.exports = { - updateTouchSimulationEnabled(enabled) { + updateTouchSimulationEnabled(enabled = false) { return { type: UPDATE_TOUCH_SIMULATION_ENABLED, enabled, diff --git a/devtools/client/responsive.html/app.js b/devtools/client/responsive.html/app.js index 3b5bdc29544b8683bbdc4c2f424b067f02bce390..37cd7caf7cdcbd4774ff5d55517aece5ecfb3717 100644 --- a/devtools/client/responsive.html/app.js +++ b/devtools/client/responsive.html/app.js @@ -48,6 +48,7 @@ let App = createClass({ device, }, "*"); this.props.dispatch(changeDevice(id, device.name)); + this.props.dispatch(updateTouchSimulationEnabled(device.touch)); }, onContentResize({ width, height }) { diff --git a/devtools/client/responsive.html/manager.js b/devtools/client/responsive.html/manager.js index af040f9b8af7adf2cd825390ffca0ab20b92f432..158a9b856b70f23800d0d3a6a5dbe0a99f24d128 100644 --- a/devtools/client/responsive.html/manager.js +++ b/devtools/client/responsive.html/manager.js @@ -424,9 +424,10 @@ ResponsiveUI.prototype = { switch (event.data.type) { case "change-viewport-device": - let { userAgent, pixelRatio } = event.data.device; + let { userAgent, pixelRatio, touch } = event.data.device; this.updateUserAgent(userAgent); this.updateDPPX(pixelRatio); + this.updateTouchSimulation(touch); break; case "content-resize": let { width, height } = event.data; diff --git a/devtools/client/responsive.html/test/browser/browser_device_change.js b/devtools/client/responsive.html/test/browser/browser_device_change.js index 29e34372857c4e6e4384f907225b635fbb71af64..9e1c3e38eccaaa36c5ba5f53ad5ea67410f9adcd 100644 --- a/devtools/client/responsive.html/test/browser/browser_device_change.js +++ b/devtools/client/responsive.html/test/browser/browser_device_change.js @@ -40,31 +40,31 @@ addRDMTask(TEST_URL, function* ({ ui, manager }) { // Test defaults testViewportDimensions(ui, 320, 480); yield testUserAgent(ui, DEFAULT_UA); - testDevicePixelRatio(yield getViewportDevicePixelRatio(ui), DEFAULT_DPPX); + yield testDevicePixelRatio(ui, DEFAULT_DPPX); + yield testTouchEventsOverride(ui, false); testViewportSelectLabel(ui, "no device selected"); - let waitingPixelRatio = onceDevicePixelRatioChange(ui); - - // Test device with custom UA + // Test device with custom properties yield switchDevice(ui, "Fake Phone RDM Test"); yield waitForViewportResizeTo(ui, testDevice.width, testDevice.height); yield testUserAgent(ui, testDevice.userAgent); - - // Test device with custom pixelRatio - testDevicePixelRatio(yield waitingPixelRatio, testDevice.pixelRatio); - waitingPixelRatio = onceDevicePixelRatioChange(ui); + yield testDevicePixelRatio(ui, testDevice.pixelRatio); + yield testTouchEventsOverride(ui, true); // Test resetting device when resizing viewport yield testViewportResize(ui, ".viewport-vertical-resize-handle", [-10, -10], [testDevice.width, testDevice.height - 10], [0, -10], ui); yield testUserAgent(ui, DEFAULT_UA); + yield testDevicePixelRatio(ui, DEFAULT_DPPX); + yield testTouchEventsOverride(ui, false); testViewportSelectLabel(ui, "no device selected"); - testDevicePixelRatio(yield waitingPixelRatio, DEFAULT_DPPX); - // Test device where UA field is blank + // Test device with generic properties yield switchDevice(ui, "Laptop (1366 x 768)"); yield waitForViewportResizeTo(ui, 1366, 768); yield testUserAgent(ui, DEFAULT_UA); + yield testDevicePixelRatio(ui, 1); + yield testTouchEventsOverride(ui, false); ok(removeDevice(testDevice), "Test Device properly removed."); @@ -79,39 +79,37 @@ function testViewportDimensions(ui, w, h) { `${h}px`, `Viewport should have height of ${h}px`); } -function testViewportSelectLabel(ui, label) { +function testViewportSelectLabel(ui, expected) { let select = ui.toolWindow.document.querySelector(".viewport-device-selector"); - is(select.selectedOptions[0].textContent, label, - `Select label should be changed to ${label}`); + is(select.selectedOptions[0].textContent, expected, + `Select label should be changed to ${expected}`); } -function* testUserAgent(ui, value) { +function* testUserAgent(ui, expected) { let ua = yield ContentTask.spawn(ui.getViewportBrowser(), {}, function* () { return content.navigator.userAgent; }); - is(ua, value, `UA should be set to ${value}`); + is(ua, expected, `UA should be set to ${expected}`); } -function testDevicePixelRatio(dppx, expected) { +function* testDevicePixelRatio(ui, expected) { + let dppx = yield getViewportDevicePixelRatio(ui); is(dppx, expected, `devicePixelRatio should be set to ${expected}`); } +function* testTouchEventsOverride(ui, expected) { + let { document } = ui.toolWindow; + let touchButton = document.querySelector("#global-touch-simulation-button"); + + let flag = yield ui.emulationFront.getTouchEventsOverride(); + is(flag === Ci.nsIDocShell.TOUCHEVENTS_OVERRIDE_ENABLED, expected, + `Touch events override should be ${expected ? "enabled" : "disabled"}`); + is(touchButton.classList.contains("active"), expected, + `Touch simulation button should be ${expected ? "" : "not"} active.`); +} + function* getViewportDevicePixelRatio(ui) { return yield ContentTask.spawn(ui.getViewportBrowser(), {}, function* () { return content.devicePixelRatio; }); } - -function onceDevicePixelRatioChange(ui) { - return ContentTask.spawn(ui.getViewportBrowser(), {}, function* () { - let pixelRatio = content.devicePixelRatio; - let mql = content.matchMedia(`(resolution: ${pixelRatio}dppx)`); - - return new Promise(resolve => { - mql.addListener(function listener() { - mql.removeListener(listener); - resolve(content.devicePixelRatio); - }); - }); - }); -} diff --git a/devtools/client/responsivedesign/test/head.js b/devtools/client/responsivedesign/test/head.js index 6d557a342cbd2b4aab41c0370560380726fc979e..bcc297514feb9ad6f072e08394df172ebea13a90 100644 --- a/devtools/client/responsivedesign/test/head.js +++ b/devtools/client/responsivedesign/test/head.js @@ -195,18 +195,12 @@ var addTab = Task.async(function* (url) { let tab = gBrowser.selectedTab = gBrowser.addTab(url); let browser = tab.linkedBrowser; - yield once(browser, "load", true); + yield BrowserTestUtils.browserLoaded(browser); info("URL '" + url + "' loading complete"); return tab; }); -function wait(ms) { - let def = promise.defer(); - setTimeout(def.resolve, ms); - return def.promise; -} - /** * Waits for the next load to complete in the current browser. * diff --git a/devtools/client/shadereditor/test/head.js b/devtools/client/shadereditor/test/head.js index 9db351692348a91e30ec985baadf5b1842a06bbf..754a0605d9f1ae34c5894799f02416c2e2548ee0 100644 --- a/devtools/client/shadereditor/test/head.js +++ b/devtools/client/shadereditor/test/head.js @@ -76,11 +76,10 @@ function addTab(aUrl, aWindow) { let tab = targetBrowser.selectedTab = targetBrowser.addTab(aUrl); let linkedBrowser = tab.linkedBrowser; - linkedBrowser.addEventListener("load", function onLoad() { - linkedBrowser.removeEventListener("load", onLoad, true); + BrowserTestUtils.browserLoaded(linkedBrowser).then(function () { info("Tab added and finished loading: " + aUrl); deferred.resolve(tab); - }, true); + }); return deferred.promise; } diff --git a/devtools/client/sourceeditor/test/browser_codemirror.js b/devtools/client/sourceeditor/test/browser_codemirror.js index 7b9a4efbcd05b452ca9c96e62eb27d5e1d33c398..381a6530f9d21e1e388b86bc1eb25f0ef070a2e8 100644 --- a/devtools/client/sourceeditor/test/browser_codemirror.js +++ b/devtools/client/sourceeditor/test/browser_codemirror.js @@ -12,14 +12,7 @@ function test() { requestLongerTimeout(3); waitForExplicitFinish(); - let tab = gBrowser.addTab(); - gBrowser.selectedTab = tab; - - let browser = gBrowser.getBrowserForTab(tab); - browser.addEventListener("load", function onLoad() { - browser.removeEventListener("load", onLoad, true); - runCodeMirrorTest(browser); - }, true); - - browser.loadURI(URI); + addTab(URI).then(function (tab) { + runCodeMirrorTest(tab.linkedBrowser); + }); } diff --git a/devtools/client/sourceeditor/test/browser_css_autocompletion.js b/devtools/client/sourceeditor/test/browser_css_autocompletion.js index 40158dda91d32fc5a91aa7475218cf3702eecdc0..f4a913060f8f3608868a9a8779afc477a32a3826 100644 --- a/devtools/client/sourceeditor/test/browser_css_autocompletion.js +++ b/devtools/client/sourceeditor/test/browser_css_autocompletion.js @@ -79,13 +79,10 @@ let inspector; function test() { waitForExplicitFinish(); - gBrowser.selectedTab = gBrowser.addTab(); - gBrowser.selectedBrowser.addEventListener("load", function onload() { - gBrowser.selectedBrowser.removeEventListener("load", onload, true); + addTab(TEST_URI).then(function () { doc = content.document; runTests(); - }, true); - content.location = TEST_URI; + }); } function runTests() { diff --git a/devtools/client/sourceeditor/test/browser_vimemacs.js b/devtools/client/sourceeditor/test/browser_vimemacs.js index 21ad5cf7547fc183ed5ae0ba6b3d690e77a13fa4..46ff02b5e0ea3dd0bb82f87846724e625638bbf8 100644 --- a/devtools/client/sourceeditor/test/browser_vimemacs.js +++ b/devtools/client/sourceeditor/test/browser_vimemacs.js @@ -11,14 +11,7 @@ function test() { requestLongerTimeout(4); waitForExplicitFinish(); - let tab = gBrowser.addTab(); - gBrowser.selectedTab = tab; - - let browser = gBrowser.getBrowserForTab(tab); - browser.addEventListener("load", function onLoad() { - browser.removeEventListener("load", onLoad, true); - runCodeMirrorTest(browser); - }, true); - - browser.loadURI(URI); + addTab(URI).then(function (tab) { + runCodeMirrorTest(tab.linkedBrowser); + }); } diff --git a/devtools/client/sourceeditor/test/head.js b/devtools/client/sourceeditor/test/head.js index 6f283b637f2ab4c24ccf052f9e406bcebcd3cfe4..91f878f3d3576195d134e4812174e80d0dd462c8 100644 --- a/devtools/client/sourceeditor/test/head.js +++ b/devtools/client/sourceeditor/test/head.js @@ -22,18 +22,16 @@ SimpleTest.registerCleanupFunction(() => { function addTab(url, callback) { waitForExplicitFinish(); - gBrowser.selectedTab = gBrowser.addTab(); - content.location = url; - + gBrowser.selectedTab = gBrowser.addTab(url); let tab = gBrowser.selectedTab; let browser = gBrowser.getBrowserForTab(tab); - function onTabLoad() { - browser.removeEventListener("load", onTabLoad, true); - callback(browser, tab, browser.contentDocument); - } - - browser.addEventListener("load", onTabLoad, true); + return BrowserTestUtils.browserLoaded(browser).then(function () { + if (typeof(callback) == "function") { + callback(browser, tab, browser.contentDocument); + } + return tab; + }); } function promiseTab(url) { diff --git a/devtools/client/styleeditor/test/head.js b/devtools/client/styleeditor/test/head.js index 3183c0d444a3cc377f583935846e7e315bca3d49..c7abaa43570c54c46e7fed1b248338ccde383c65 100644 --- a/devtools/client/styleeditor/test/head.js +++ b/devtools/client/styleeditor/test/head.js @@ -29,11 +29,11 @@ var addTab = function (url, win) { let targetBrowser = targetWindow.gBrowser; let tab = targetBrowser.selectedTab = targetBrowser.addTab(url); - targetBrowser.selectedBrowser.addEventListener("load", function onload() { - targetBrowser.selectedBrowser.removeEventListener("load", onload, true); - info("URL '" + url + "' loading complete"); - def.resolve(tab); - }, true); + BrowserTestUtils.browserLoaded(targetBrowser.selectedBrowser) + .then(function () { + info("URL '" + url + "' loading complete"); + def.resolve(tab); + }); return def.promise; }; diff --git a/devtools/client/webaudioeditor/test/head.js b/devtools/client/webaudioeditor/test/head.js index 822113e81eede42abcb9904caa9a18c4b46053d4..7b0b0f01ad2bcd4a0e4b6bbe6e6dad14ef1e77b2 100644 --- a/devtools/client/webaudioeditor/test/head.js +++ b/devtools/client/webaudioeditor/test/head.js @@ -73,11 +73,10 @@ function addTab(aUrl, aWindow) { let tab = targetBrowser.selectedTab = targetBrowser.addTab(aUrl); let linkedBrowser = tab.linkedBrowser; - linkedBrowser.addEventListener("load", function onLoad() { - linkedBrowser.removeEventListener("load", onLoad, true); + BrowserTestUtils.browserLoaded(linkedBrowser).then(function () { info("Tab added and finished loading: " + aUrl); deferred.resolve(tab); - }, true); + }); return deferred.promise; } diff --git a/devtools/client/webconsole/test/browser_webconsole_output_06.js b/devtools/client/webconsole/test/browser_webconsole_output_06.js index 9bcfce9082d17f54cdbf5e0dff47c23f9b766a7a..ad69b390861b3cd52ffc04b87f4e3bae4f6538c4 100644 --- a/devtools/client/webconsole/test/browser_webconsole_output_06.js +++ b/devtools/client/webconsole/test/browser_webconsole_output_06.js @@ -198,7 +198,7 @@ var inputTests = [ // 21 { input: '({0: "a", 1: "b", length: 1})', - output: 'Object { 1: "b", length: 1, 1 more\u2026 }', + output: 'Object { 0: "a", 1: "b", length: 1 }', printOutput: "[object Object]", inspectable: true, variablesViewLabel: "Object", @@ -225,7 +225,7 @@ var inputTests = [ // 24 { input: '({0: "a", 2: "b", length: 2})', - output: 'Object { 2: "b", length: 2, 1 more\u2026 }', + output: 'Object { 0: "a", 2: "b", length: 2 }', printOutput: "[object Object]", inspectable: true, variablesViewLabel: "Object", @@ -243,7 +243,7 @@ var inputTests = [ // 26 { input: '({0: "a", b: "b", length: 1})', - output: 'Object { b: "b", length: 1, 1 more\u2026 }', + output: 'Object { 0: "a", b: "b", length: 1 }', printOutput: "[object Object]", inspectable: true, variablesViewLabel: "Object", @@ -252,7 +252,7 @@ var inputTests = [ // 27 { input: '({0: "a", b: "b", length: 2})', - output: 'Object { b: "b", length: 2, 1 more\u2026 }', + output: 'Object { 0: "a", b: "b", length: 2 }', printOutput: "[object Object]", inspectable: true, variablesViewLabel: "Object", diff --git a/devtools/client/webide/test/head.js b/devtools/client/webide/test/head.js index 5f9534530e91406d9adbe06556b3d5b30928e2bf..c0171c730b64732e422ef0fb647caf4a1f8d16b6 100644 --- a/devtools/client/webide/test/head.js +++ b/devtools/client/webide/test/head.js @@ -167,11 +167,10 @@ function addTab(aUrl, aWindow) { let tab = targetBrowser.selectedTab = targetBrowser.addTab(aUrl); let linkedBrowser = tab.linkedBrowser; - linkedBrowser.addEventListener("load", function onLoad() { - linkedBrowser.removeEventListener("load", onLoad, true); + BrowserTestUtils.browserLoaded(linkedBrowser).then(function () { info("Tab added and finished loading: " + aUrl); deferred.resolve(tab); - }, true); + }); return deferred.promise; } diff --git a/devtools/server/actors/object.js b/devtools/server/actors/object.js index 6816338c737e9b9199a66d73b2f1e6e62436292a..1f417b951593df525c088bffc9a8233fd10ae8b5 100644 --- a/devtools/server/actors/object.js +++ b/devtools/server/actors/object.js @@ -1090,7 +1090,7 @@ function enumWeakSetEntries(objectActor) { * having customized output. This object holds arrays mapped by * Debugger.Object.prototype.class. * - * In each array you can add functions that take two + * In each array you can add functions that take three * arguments: * - the ObjectActor instance and its hooks to make a preview for, * - the grip object being prepared for the client, @@ -1102,16 +1102,16 @@ function enumWeakSetEntries(objectActor) { * information for the debugger object, or true otherwise. */ DebuggerServer.ObjectActorPreviewers = { - String: [function (objectActor, grip) { - return wrappedPrimitivePreviewer("String", String, objectActor, grip); + String: [function (objectActor, grip, rawObj) { + return wrappedPrimitivePreviewer("String", String, objectActor, grip, rawObj); }], - Boolean: [function (objectActor, grip) { - return wrappedPrimitivePreviewer("Boolean", Boolean, objectActor, grip); + Boolean: [function (objectActor, grip, rawObj) { + return wrappedPrimitivePreviewer("Boolean", Boolean, objectActor, grip, rawObj); }], - Number: [function (objectActor, grip) { - return wrappedPrimitivePreviewer("Number", Number, objectActor, grip); + Number: [function (objectActor, grip, rawObj) { + return wrappedPrimitivePreviewer("Number", Number, objectActor, grip, rawObj); }], Function: [function ({obj, hooks}, grip) { @@ -1379,17 +1379,16 @@ DebuggerServer.ObjectActorPreviewers = { * The result grip to fill in * @return Booolean true if the object was handled, false otherwise */ -function wrappedPrimitivePreviewer(className, classObj, objectActor, grip) { +function wrappedPrimitivePreviewer(className, classObj, objectActor, grip, rawObj) { let {obj, hooks} = objectActor; if (!obj.proto || obj.proto.class != className) { return false; } - let raw = obj.unsafeDereference(); let v = null; try { - v = classObj.prototype.valueOf.call(raw); + v = classObj.prototype.valueOf.call(rawObj); } catch (ex) { // valueOf() can throw if the raw JS object is "misbehaved". return false; @@ -1399,7 +1398,7 @@ function wrappedPrimitivePreviewer(className, classObj, objectActor, grip) { return false; } - let canHandle = GenericObject(objectActor, grip, className === "String"); + let canHandle = GenericObject(objectActor, grip, rawObj, className === "String"); if (!canHandle) { return false; } @@ -1409,7 +1408,7 @@ function wrappedPrimitivePreviewer(className, classObj, objectActor, grip) { return true; } -function GenericObject(objectActor, grip, specialStringBehavior = false) { +function GenericObject(objectActor, grip, rawObj, specialStringBehavior = false) { let {obj, hooks} = objectActor; if (grip.preview || grip.displayString || hooks.getGripDepth() > 1) { return false; @@ -1859,7 +1858,9 @@ DebuggerServer.ObjectActorPreviewers.Object = [ return true; }, - GenericObject, + function Object(objectActor, grip, rawObj) { + return GenericObject(objectActor, grip, rawObj, /* specialStringBehavior = */ false); + }, ]; /** diff --git a/devtools/server/tests/browser/browser_canvasframe_helper_04.js b/devtools/server/tests/browser/browser_canvasframe_helper_04.js index b3c52c0723668c7ab5815b95bc0580858104d62b..d038f84a0f432ba51b19594b0f7cb79f0c7c3e83 100644 --- a/devtools/server/tests/browser/browser_canvasframe_helper_04.js +++ b/devtools/server/tests/browser/browser_canvasframe_helper_04.js @@ -65,7 +65,7 @@ add_task(function* () { is(mouseDownHandled, 1, "The mousedown event was handled once before navigation"); info("Navigating to a new page"); - let loaded = once(gBrowser.selectedBrowser, "load", true); + let loaded = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser); content.location = TEST_URL_2; yield loaded; doc = gBrowser.selectedBrowser.contentWindow.document; diff --git a/devtools/server/tests/browser/head.js b/devtools/server/tests/browser/head.js index 0a16876457b28ce5d84d9315ee0c8e24df1eae71..1e7f09d95764666bb2de8097d8dce00ff4fcef2f 100644 --- a/devtools/server/tests/browser/head.js +++ b/devtools/server/tests/browser/head.js @@ -33,7 +33,7 @@ waitForExplicitFinish(); var addTab = Task.async(function* (url) { info(`Adding a new tab with URL: ${url}`); let tab = gBrowser.selectedTab = gBrowser.addTab(url); - yield once(gBrowser.selectedBrowser, "load", true); + yield BrowserTestUtils.browserLoaded(tab.linkedBrowser); info(`Tab added and URL ${url} loaded`); diff --git a/dom/animation/test/css-animations/file_animation-id.html b/dom/animation/test/css-animations/file_animation-id.html index 412616a7ad388faa7315c9a1429bf90fddadbf9c..dbd5ee0eef37e5232c53a5057c7d1dc5325a2a55 100644 --- a/dom/animation/test/css-animations/file_animation-id.html +++ b/dom/animation/test/css-animations/file_animation-id.html @@ -18,14 +18,6 @@ test(function(t) { assert_equals(animation.id, 'anim', 'animation.id reflects the value set'); }, 'Animation.id for CSS Animations'); -test(function(t) { - var div = addDiv(t); - var animation = div.animate({}, 100 * MS_PER_SEC); - assert_equals(animation.id, '', 'id for CSS Animation is initially empty'); - animation.id = 'anim' - - assert_equals(animation.id, 'anim', 'animation.id reflects the value set'); -}, 'Animation.id for CSS Animations'); done(); </script> </body> diff --git a/dom/animation/test/css-transitions/file_animation-cancel.html b/dom/animation/test/css-transitions/file_animation-cancel.html index 7db609ad979c98da1b1714121beaa9d2e588c9fb..12417956e174b4d7520ff62a62e0c102930b4b14 100644 --- a/dom/animation/test/css-transitions/file_animation-cancel.html +++ b/dom/animation/test/css-transitions/file_animation-cancel.html @@ -5,7 +5,7 @@ <script> 'use strict'; -async_test(function(t) { +promise_test(function(t) { var div = addDiv(t, { style: 'margin-left: 0px' }); flushComputedStyle(div); @@ -14,17 +14,16 @@ async_test(function(t) { flushComputedStyle(div); var animation = div.getAnimations()[0]; - animation.ready.then(waitForFrame).then(t.step_func(function() { + return animation.ready.then(waitForFrame).then(function() { assert_not_equals(getComputedStyle(div).marginLeft, '1000px', 'transform style is animated before cancelling'); animation.cancel(); assert_equals(getComputedStyle(div).marginLeft, div.style.marginLeft, 'transform style is no longer animated after cancelling'); - t.done(); - })); + }); }, 'Animated style is cleared after cancelling a running CSS transition'); -async_test(function(t) { +promise_test(function(t) { var div = addDiv(t, { style: 'margin-left: 0px' }); flushComputedStyle(div); @@ -32,24 +31,22 @@ async_test(function(t) { div.style.marginLeft = '1000px'; flushComputedStyle(div); - div.addEventListener('transitionend', t.step_func(function() { + div.addEventListener('transitionend', function() { assert_unreached('Got unexpected end event on cancelled transition'); - })); + }); var animation = div.getAnimations()[0]; - animation.ready.then(t.step_func(function() { + return animation.ready.then(function() { // Seek to just before the end then cancel animation.currentTime = 99.9 * 1000; animation.cancel(); // Then wait a couple of frames and check that no event was dispatched return waitForAnimationFrames(2); - })).then(t.step_func(function() { - t.done(); - })); + }); }, 'Cancelled CSS transitions do not dispatch events'); -async_test(function(t) { +promise_test(function(t) { var div = addDiv(t, { style: 'margin-left: 0px' }); flushComputedStyle(div); @@ -58,7 +55,7 @@ async_test(function(t) { flushComputedStyle(div); var animation = div.getAnimations()[0]; - animation.ready.then(t.step_func(function() { + return animation.ready.then(function() { animation.cancel(); assert_equals(getComputedStyle(div).marginLeft, '1000px', 'margin-left style is not animated after cancelling'); @@ -66,14 +63,13 @@ async_test(function(t) { assert_equals(getComputedStyle(div).marginLeft, '0px', 'margin-left style is animated after re-starting transition'); return animation.ready; - })).then(t.step_func(function() { + }).then(function() { assert_equals(animation.playState, 'running', 'Transition succeeds in running after being re-started'); - t.done(); - })); + }); }, 'After cancelling a transition, it can still be re-used'); -async_test(function(t) { +promise_test(function(t) { var div = addDiv(t, { style: 'margin-left: 0px' }); flushComputedStyle(div); @@ -82,7 +78,7 @@ async_test(function(t) { flushComputedStyle(div); var animation = div.getAnimations()[0]; - animation.ready.then(t.step_func(function() { + return animation.ready.then(function() { animation.finish(); animation.cancel(); assert_equals(getComputedStyle(div).marginLeft, '1000px', @@ -91,11 +87,10 @@ async_test(function(t) { assert_equals(getComputedStyle(div).marginLeft, '0px', 'margin-left style is animated after re-starting transition'); return animation.ready; - })).then(t.step_func(function() { + }).then(function() { assert_equals(animation.playState, 'running', 'Transition succeeds in running after being re-started'); - t.done(); - })); + }); }, 'After cancelling a finished transition, it can still be re-used'); test(function(t) { @@ -123,6 +118,20 @@ test(function(t) { }, 'After cancelling a transition, updating transition properties doesn\'t make' + ' it live again'); +test(function(t) { + var div = addDiv(t, { style: 'margin-left: 0px' }); + flushComputedStyle(div); + + div.style.transition = 'margin-left 100s'; + div.style.marginLeft = '1000px'; + flushComputedStyle(div); + + var animation = div.getAnimations()[0]; + div.style.display = 'none'; + assert_equals(animation.playState, 'idle'); + assert_equals(getComputedStyle(div).marginLeft, '1000px'); +}, 'Setting display:none on an element cancels its transitions'); + done(); </script> </body> diff --git a/dom/canvas/test/webgl-mochitest/mochitest.ini b/dom/canvas/test/webgl-mochitest/mochitest.ini index 100dc6c6df3d0bf5eb9eba1ae70fe9ec278345c2..e29b2f12fce0f7d524e15f0ae72259f738a7828e 100644 --- a/dom/canvas/test/webgl-mochitest/mochitest.ini +++ b/dom/canvas/test/webgl-mochitest/mochitest.ini @@ -84,7 +84,7 @@ skip-if = android_version == '18' #Android 4.3 aws only; bug 1030942 skip-if = toolkit == 'android' #bug 865443- seperate suite - the non_conf* tests pass except for one on armv6 tests [test_webgl_compressed_texture_es3.html] [test_webgl_disjoint_timer_query.html] -fail-if = (os == 'win' && (os_version == '6.1' || os_version == '6.2')) +fail-if = (os == 'win' && (os_version == '6.1' || os_version == '6.2' || os_version == '10.0')) [test_webgl_force_enable.html] [test_webgl_request_context.html] skip-if = toolkit == 'android' #bug 865443- seperate suite - the non_conf* tests pass except for one on armv6 tests diff --git a/dom/html/HTMLMediaElement.cpp b/dom/html/HTMLMediaElement.cpp index 50e23c037a4435ae743adf4cc4db5ee87bfbceea..b3abd46abcb2343616aaa0c9e9cc12423a7cc82f 100644 --- a/dom/html/HTMLMediaElement.cpp +++ b/dom/html/HTMLMediaElement.cpp @@ -4344,7 +4344,7 @@ void HTMLMediaElement::MetadataLoaded(const MediaInfo* aInfo, } if (mIsEncrypted) { if (!mMediaSource && Preferences::GetBool("media.eme.mse-only", true)) { - DecodeError(); + DecodeError(NS_ERROR_DOM_MEDIA_FATAL_ERR); return; } @@ -4419,7 +4419,7 @@ void HTMLMediaElement::NetworkError() Error(nsIDOMMediaError::MEDIA_ERR_NETWORK); } -void HTMLMediaElement::DecodeError() +void HTMLMediaElement::DecodeError(const MediaResult& aError) { nsAutoString src; GetCurrentSrc(src); @@ -4443,7 +4443,7 @@ void HTMLMediaElement::DecodeError() NS_WARNING("Should know the source we were loading from!"); } } else { - Error(nsIDOMMediaError::MEDIA_ERR_DECODE); + Error(nsIDOMMediaError::MEDIA_ERR_DECODE, aError); } } @@ -4457,7 +4457,8 @@ void HTMLMediaElement::LoadAborted() Error(nsIDOMMediaError::MEDIA_ERR_ABORTED); } -void HTMLMediaElement::Error(uint16_t aErrorCode) +void HTMLMediaElement::Error(uint16_t aErrorCode, + const MediaResult& aErrorDetails) { NS_ASSERTION(aErrorCode == nsIDOMMediaError::MEDIA_ERR_DECODE || aErrorCode == nsIDOMMediaError::MEDIA_ERR_NETWORK || @@ -4470,8 +4471,12 @@ void HTMLMediaElement::Error(uint16_t aErrorCode) if (mError) { return; } + nsCString message; + if (NS_FAILED(aErrorDetails)) { + message = aErrorDetails.Description(); + } + mError = new MediaError(this, aErrorCode, message); - mError = new MediaError(this, aErrorCode); DispatchAsyncEvent(NS_LITERAL_STRING("error")); if (mReadyState == nsIDOMHTMLMediaElement::HAVE_NOTHING) { ChangeNetworkState(nsIDOMHTMLMediaElement::NETWORK_EMPTY); diff --git a/dom/html/HTMLMediaElement.h b/dom/html/HTMLMediaElement.h index 3484271be3fb34bfa177dceb4c324e64e5557fee..cd673782e081c7d4b4857b2fc321f7e7fa76a805 100644 --- a/dom/html/HTMLMediaElement.h +++ b/dom/html/HTMLMediaElement.h @@ -179,7 +179,7 @@ public: // Called by the video decoder object, on the main thread, when the // resource has a decode error during metadata loading or decoding. - virtual void DecodeError() final override; + virtual void DecodeError(const MediaResult& aError) final override; // Return true if error attribute is not null. virtual bool HasError() const final override; @@ -1127,7 +1127,7 @@ protected: * Resets the media element for an error condition as per aErrorCode. * aErrorCode must be one of nsIDOMHTMLMediaError codes. */ - void Error(uint16_t aErrorCode); + void Error(uint16_t aErrorCode, const MediaResult& aErrorDetails = NS_OK); /** * Returns the URL spec of the currentSrc. diff --git a/dom/html/MediaError.cpp b/dom/html/MediaError.cpp index ee11a36abce07f47c4ad0fc2d1048fdcf42009f2..f4a913a9756759d0882a331c8947b6ea8452cf2f 100644 --- a/dom/html/MediaError.cpp +++ b/dom/html/MediaError.cpp @@ -21,9 +21,11 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaError) NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMMediaError) NS_INTERFACE_MAP_END -MediaError::MediaError(HTMLMediaElement* aParent, uint16_t aCode) +MediaError::MediaError(HTMLMediaElement* aParent, uint16_t aCode, + const nsACString& aMessage) : mParent(aParent) , mCode(aCode) + , mMessage(aMessage) { } @@ -35,6 +37,12 @@ NS_IMETHODIMP MediaError::GetCode(uint16_t* aCode) return NS_OK; } +NS_IMETHODIMP MediaError::GetMessage(nsAString& aResult) +{ + CopyUTF8toUTF16(mMessage, aResult); + return NS_OK; +} + JSObject* MediaError::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) { diff --git a/dom/html/MediaError.h b/dom/html/MediaError.h index 068abc8d7795fa52b313c3a2fb757d19d5ce20f7..cebe01efdeb222384ed0198ca10ee521062589d9 100644 --- a/dom/html/MediaError.h +++ b/dom/html/MediaError.h @@ -22,7 +22,8 @@ class MediaError final : public nsIDOMMediaError, ~MediaError() {} public: - MediaError(HTMLMediaElement* aParent, uint16_t aCode); + MediaError(HTMLMediaElement* aParent, uint16_t aCode, + const nsACString& aMessage = nsCString()); // nsISupports NS_DECL_CYCLE_COLLECTING_ISUPPORTS @@ -48,6 +49,8 @@ private: // Error code const uint16_t mCode; + // Error details; + const nsCString mMessage; }; } // namespace dom diff --git a/dom/interfaces/html/nsIDOMMediaError.idl b/dom/interfaces/html/nsIDOMMediaError.idl index 75fc386518e0f131058accf527b1d89cee6f9f7a..39488dfd6f8ba9b003a99adc11781c9b93fd1cc0 100644 --- a/dom/interfaces/html/nsIDOMMediaError.idl +++ b/dom/interfaces/html/nsIDOMMediaError.idl @@ -12,11 +12,11 @@ interface nsIDOMMediaError : nsISupports the user agent at the user's requet */ const unsigned short MEDIA_ERR_ABORTED = 1; - /* A network error of some description caused the + /* A network error of some description caused the user agent to stop downloading the media resource */ const unsigned short MEDIA_ERR_NETWORK = 2; - /* An error of some description occurred while decoding + /* An error of some description occurred while decoding the media resource */ const unsigned short MEDIA_ERR_DECODE = 3; @@ -24,4 +24,6 @@ interface nsIDOMMediaError : nsISupports const unsigned short MEDIA_ERR_SRC_NOT_SUPPORTED = 4; readonly attribute unsigned short code; + + readonly attribute DOMString message; }; diff --git a/dom/ipc/ContentChild.cpp b/dom/ipc/ContentChild.cpp index a4bc0e0dd5483e20c70df569af866e05812aa6af..eef4387cbf11c751f6b0bf4a1acc87f9df03763b 100644 --- a/dom/ipc/ContentChild.cpp +++ b/dom/ipc/ContentChild.cpp @@ -1312,6 +1312,16 @@ StartMacOSContentSandbox() MOZ_CRASH("Failed to get NS_OS_TEMP_DIR path"); } + nsCOMPtr<nsIFile> profileDir; + ContentChild::GetSingleton()->GetProfileDir(getter_AddRefs(profileDir)); + nsCString profileDirPath; + if (profileDir) { + rv = profileDir->GetNativePath(profileDirPath); + if (NS_FAILED(rv) || profileDirPath.IsEmpty()) { + MOZ_CRASH("Failed to get profile path"); + } + } + MacSandboxInfo info; info.type = MacSandboxType_Content; info.level = info.level = sandboxLevel; @@ -1320,6 +1330,13 @@ StartMacOSContentSandbox() info.appDir.assign(appDir.get()); info.appTempDir.assign(tempDirPath.get()); + if (profileDir) { + info.hasSandboxedProfile = true; + info.profileDir.assign(profileDirPath.get()); + } else { + info.hasSandboxedProfile = false; + } + std::string err; if (!mozilla::StartMacSandbox(info, err)) { NS_WARNING(err.c_str()); diff --git a/dom/ipc/ContentChild.h b/dom/ipc/ContentChild.h index b86e4e1d312d497a68067aa77e6ddfdbd9fbe127..d8cef55f0d98129248f0e060a4de4dd958d1b16a 100644 --- a/dom/ipc/ContentChild.h +++ b/dom/ipc/ContentChild.h @@ -21,6 +21,9 @@ #include "nsWeakPtr.h" #include "nsIWindowProvider.h" +#if defined(XP_MACOSX) && defined(MOZ_CONTENT_SANDBOX) +#include "nsIFile.h" +#endif struct ChromePackage; class nsIObserver; @@ -114,6 +117,19 @@ public: void GetProcessName(nsACString& aName) const; +#if defined(XP_MACOSX) && defined(MOZ_CONTENT_SANDBOX) + void GetProfileDir(nsIFile** aProfileDir) const + { + *aProfileDir = mProfileDir; + NS_IF_ADDREF(*aProfileDir); + } + + void SetProfileDir(nsIFile* aProfileDir) + { + mProfileDir = aProfileDir; + } +#endif + bool IsAlive() const; bool IsShuttingDown() const; @@ -681,6 +697,10 @@ private: nsCOMPtr<nsIDomainPolicy> mPolicy; nsCOMPtr<nsITimer> mForceKillTimer; +#if defined(XP_MACOSX) && defined(MOZ_CONTENT_SANDBOX) + nsCOMPtr<nsIFile> mProfileDir; +#endif + // Hashtable to keep track of the pending GetFilesHelper objects. // This GetFilesHelperChild objects are removed when RecvGetFilesResponse is // received. diff --git a/dom/ipc/ContentProcess.cpp b/dom/ipc/ContentProcess.cpp index 978534fc3cfa8cf12fe2927d9511fe1a686d7b3a..66125f3322aa9e60a7987a595eac4289bdadb16c 100644 --- a/dom/ipc/ContentProcess.cpp +++ b/dom/ipc/ContentProcess.cpp @@ -114,6 +114,21 @@ ContentProcess::SetAppDir(const nsACString& aPath) mXREEmbed.SetAppDir(aPath); } +#if defined(XP_MACOSX) && defined(MOZ_CONTENT_SANDBOX) +void +ContentProcess::SetProfile(const nsACString& aProfile) +{ + bool flag; + nsresult rv = + XRE_GetFileFromPath(aProfile.BeginReading(), getter_AddRefs(mProfileDir)); + if (NS_FAILED(rv) || + NS_FAILED(mProfileDir->Exists(&flag)) || !flag) { + NS_WARNING("Invalid profile directory passed to content process."); + mProfileDir = nullptr; + } +} +#endif + bool ContentProcess::Init() { @@ -124,6 +139,10 @@ ContentProcess::Init() mContent.InitXPCOM(); mContent.InitGraphicsDeviceData(); +#if (defined(XP_MACOSX)) && defined(MOZ_CONTENT_SANDBOX) + mContent.SetProfileDir(mProfileDir); +#endif + #if (defined(XP_WIN) || defined(XP_MACOSX)) && defined(MOZ_CONTENT_SANDBOX) SetUpSandboxEnvironment(); #endif diff --git a/dom/ipc/ContentProcess.h b/dom/ipc/ContentProcess.h index 67ee74ec8f3d8eb4def3fe3d11643a39be550613..bf9968f8cad9cba0626bf3f259b2eca67d13cd6a 100644 --- a/dom/ipc/ContentProcess.h +++ b/dom/ipc/ContentProcess.h @@ -39,9 +39,18 @@ public: void SetAppDir(const nsACString& aPath); +#if defined(XP_MACOSX) && defined(MOZ_CONTENT_SANDBOX) + void SetProfile(const nsACString& aProfile); +#endif + private: ContentChild mContent; mozilla::ipc::ScopedXREEmbed mXREEmbed; + +#if defined(XP_MACOSX) && defined(MOZ_CONTENT_SANDBOX) + nsCOMPtr<nsIFile> mProfileDir; +#endif + #if defined(XP_WIN) // This object initializes and configures COM. mozilla::mscom::MainThreadRuntime mCOMRuntime; diff --git a/dom/media/ADTSDemuxer.cpp b/dom/media/ADTSDemuxer.cpp index 42a78e55e86ca58fcddd7c7f8a75353c40f72c4d..82075b65be46b1305e7859db48d04b1482aa3088 100644 --- a/dom/media/ADTSDemuxer.cpp +++ b/dom/media/ADTSDemuxer.cpp @@ -318,7 +318,7 @@ ADTSDemuxer::Init() ADTSLOG("Init() failure: waiting for data"); return InitPromise::CreateAndReject( - DemuxerFailureReason::DEMUXER_ERROR, __func__); + NS_ERROR_DOM_MEDIA_METADATA_ERR, __func__); } ADTSLOG("Init() successful"); @@ -515,10 +515,7 @@ ADTSTrackDemuxer::GetSamples(int32_t aNumSamples) aNumSamples, mOffset, mNumParsedFrames, mFrameIndex, mTotalFrameLen, mSamplesPerFrame, mSamplesPerSecond, mChannels); - if (!aNumSamples) { - return SamplesPromise::CreateAndReject( - DemuxerFailureReason::DEMUXER_ERROR, __func__); - } + MOZ_ASSERT(aNumSamples); RefPtr<SamplesHolder> frames = new SamplesHolder(); @@ -540,7 +537,7 @@ ADTSTrackDemuxer::GetSamples(int32_t aNumSamples) if (frames->mSamples.IsEmpty()) { return SamplesPromise::CreateAndReject( - DemuxerFailureReason::END_OF_STREAM, __func__); + NS_ERROR_DOM_MEDIA_END_OF_STREAM, __func__); } return SamplesPromise::CreateAndResolve(frames, __func__); @@ -562,7 +559,7 @@ ADTSTrackDemuxer::SkipToNextRandomAccessPoint(media::TimeUnit aTimeThreshold) { // Will not be called for audio-only resources. return SkipAccessPointPromise::CreateAndReject( - SkipFailureHolder(DemuxerFailureReason::DEMUXER_ERROR, 0), __func__); + SkipFailureHolder(NS_ERROR_DOM_MEDIA_DEMUXER_ERR, 0), __func__); } int64_t diff --git a/dom/media/AccurateSeekTask.cpp b/dom/media/AccurateSeekTask.cpp index 4703a430bbb2e243469115b3f5d71e7a9042ec52..95b4077f55c0bb4b14be31d0898b1977f2d5d6ef 100644 --- a/dom/media/AccurateSeekTask.cpp +++ b/dom/media/AccurateSeekTask.cpp @@ -61,7 +61,7 @@ AccurateSeekTask::Discard() AssertOwnerThread(); // Disconnect MDSM. - RejectIfExist(__func__); + RejectIfExist(NS_ERROR_DOM_MEDIA_CANCELED, __func__); // Disconnect MediaDecoderReaderWrapper. mSeekRequest.DisconnectIfExists(); @@ -120,7 +120,7 @@ AccurateSeekTask::DropAudioUpToSeekTarget(MediaData* aSample) CheckedInt64 sampleDuration = FramesToUsecs(audio->mFrames, mAudioRate); if (!sampleDuration.isValid()) { - return NS_ERROR_FAILURE; + return NS_ERROR_DOM_MEDIA_OVERFLOW_ERR; } if (audio->mTime + sampleDuration.value() <= mTarget.GetTime().ToMicroseconds()) { @@ -154,7 +154,7 @@ AccurateSeekTask::DropAudioUpToSeekTarget(MediaData* aSample) CheckedInt64 framesToPrune = UsecsToFrames(mTarget.GetTime().ToMicroseconds() - audio->mTime, mAudioRate); if (!framesToPrune.isValid()) { - return NS_ERROR_FAILURE; + return NS_ERROR_DOM_MEDIA_OVERFLOW_ERR; } if (framesToPrune.value() > audio->mFrames) { // We've messed up somehow. Don't try to trim frames, the |frames| @@ -174,7 +174,7 @@ AccurateSeekTask::DropAudioUpToSeekTarget(MediaData* aSample) frames * channels * sizeof(AudioDataValue)); CheckedInt64 duration = FramesToUsecs(frames, mAudioRate); if (!duration.isValid()) { - return NS_ERROR_FAILURE; + return NS_ERROR_DOM_MEDIA_OVERFLOW_ERR; } RefPtr<AudioData> data(new AudioData(audio->mOffset, mTarget.GetTime().ToMicroseconds(), @@ -260,7 +260,7 @@ AccurateSeekTask::OnSeekRejected(nsresult aResult) mSeekRequest.Complete(); MOZ_ASSERT(NS_FAILED(aResult), "Cancels should also disconnect mSeekRequest"); - RejectIfExist(__func__); + RejectIfExist(aResult, __func__); } void @@ -308,10 +308,13 @@ AccurateSeekTask::OnAudioDecoded(MediaData* aAudioSample) // Non-precise seek; we can stop the seek at the first sample. mSeekedAudioData = audio; mDoneAudioSeeking = true; - } else if (NS_FAILED(DropAudioUpToSeekTarget(audio))) { - CancelCallbacks(); - RejectIfExist(__func__); - return; + } else { + nsresult rv = DropAudioUpToSeekTarget(audio); + if (NS_FAILED(rv)) { + CancelCallbacks(); + RejectIfExist(rv, __func__); + return; + } } if (!mDoneAudioSeeking) { @@ -323,33 +326,26 @@ AccurateSeekTask::OnAudioDecoded(MediaData* aAudioSample) void AccurateSeekTask::OnNotDecoded(MediaData::Type aType, - MediaDecoderReader::NotDecodedReason aReason) + const MediaResult& aError) { AssertOwnerThread(); MOZ_ASSERT(!mSeekTaskPromise.IsEmpty(), "Seek shouldn't be finished"); - SAMPLE_LOG("OnNotDecoded type=%d reason=%u", aType, aReason); + SAMPLE_LOG("OnNotDecoded type=%d reason=%u", aType, aError.Code()); // Ignore pending requests from video-only seek. if (aType == MediaData::AUDIO_DATA && mTarget.IsVideoOnly()) { return; } - if (aReason == MediaDecoderReader::DECODE_ERROR) { - // If this is a decode error, delegate to the generic error path. - CancelCallbacks(); - RejectIfExist(__func__); - return; - } - // If the decoder is waiting for data, we tell it to call us back when the // data arrives. - if (aReason == MediaDecoderReader::WAITING_FOR_DATA) { + if (aError == NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA) { mReader->WaitForData(aType); return; } - if (aReason == MediaDecoderReader::CANCELED) { + if (aError == NS_ERROR_DOM_MEDIA_CANCELED) { if (aType == MediaData::AUDIO_DATA) { RequestAudioData(); } else { @@ -358,7 +354,7 @@ AccurateSeekTask::OnNotDecoded(MediaData::Type aType, return; } - if (aReason == MediaDecoderReader::END_OF_STREAM) { + if (aError == NS_ERROR_DOM_MEDIA_END_OF_STREAM) { if (aType == MediaData::AUDIO_DATA) { mIsAudioQueueFinished = true; mDoneAudioSeeking = true; @@ -372,7 +368,12 @@ AccurateSeekTask::OnNotDecoded(MediaData::Type aType, } } MaybeFinishSeek(); + return; } + + // This is a decode error, delegate to the generic error path. + CancelCallbacks(); + RejectIfExist(aError, __func__); } void @@ -395,10 +396,13 @@ AccurateSeekTask::OnVideoDecoded(MediaData* aVideoSample) // Non-precise seek. We can stop the seek at the first sample. mSeekedVideoData = video; mDoneVideoSeeking = true; - } else if (NS_FAILED(DropVideoUpToSeekTarget(video.get()))) { - CancelCallbacks(); - RejectIfExist(__func__); - return; + } else { + nsresult rv = DropVideoUpToSeekTarget(video.get()); + if (NS_FAILED(rv)) { + CancelCallbacks(); + RejectIfExist(rv, __func__); + return; + } } if (!mDoneVideoSeeking) { @@ -419,7 +423,7 @@ AccurateSeekTask::SetCallbacks() OnAudioDecoded(aData.as<MediaData*>()); } else { OnNotDecoded(MediaData::AUDIO_DATA, - aData.as<MediaDecoderReader::NotDecodedReason>()); + aData.as<MediaResult>()); } }); @@ -430,7 +434,7 @@ AccurateSeekTask::SetCallbacks() OnVideoDecoded(Get<0>(aData.as<Type>())); } else { OnNotDecoded(MediaData::VIDEO_DATA, - aData.as<MediaDecoderReader::NotDecodedReason>()); + aData.as<MediaResult>()); } }); diff --git a/dom/media/AccurateSeekTask.h b/dom/media/AccurateSeekTask.h index 8272820be75c1e3cc5e2ba2f74c33ea3daed3edf..e2e6771f1d9c00a77dc6fe18b70d9a3e72af3a7b 100644 --- a/dom/media/AccurateSeekTask.h +++ b/dom/media/AccurateSeekTask.h @@ -50,7 +50,7 @@ private: void OnVideoDecoded(MediaData* aVideoSample); - void OnNotDecoded(MediaData::Type, MediaDecoderReader::NotDecodedReason); + void OnNotDecoded(MediaData::Type, const MediaResult&); void SetCallbacks(); diff --git a/dom/media/Benchmark.cpp b/dom/media/Benchmark.cpp index b1d2ab265715b66e52243418fa9dd1561b8505d8..e647057b492edb0fa3fb06f989c3edf7d7451667 100644 --- a/dom/media/Benchmark.cpp +++ b/dom/media/Benchmark.cpp @@ -169,7 +169,7 @@ BenchmarkPlayback::DemuxSamples() } DemuxNextSample(); }, - [this, ref](DemuxerFailureReason aReason) { MainThreadShutdown(); }); + [this, ref](const MediaResult& aError) { MainThreadShutdown(); }); } void @@ -190,9 +190,9 @@ BenchmarkPlayback::DemuxNextSample() Dispatch(NS_NewRunnableFunction([this, ref]() { DemuxNextSample(); })); } }, - [this, ref](DemuxerFailureReason aReason) { - switch (aReason) { - case DemuxerFailureReason::END_OF_STREAM: + [this, ref](const MediaResult& aError) { + switch (aError.Code()) { + case NS_ERROR_DOM_MEDIA_END_OF_STREAM: InitDecoder(Move(*mTrackDemuxer->GetInfo())); break; default: @@ -218,7 +218,7 @@ BenchmarkPlayback::InitDecoder(TrackInfo&& aInfo) [this, ref](TrackInfo::TrackType aTrackType) { InputExhausted(); }, - [this, ref](MediaDataDecoder::DecoderFailureReason aReason) { + [this, ref](MediaResult aError) { MainThreadShutdown(); }); } @@ -281,7 +281,7 @@ BenchmarkPlayback::Output(MediaData* aData) } void -BenchmarkPlayback::Error(MediaDataDecoderError aError) +BenchmarkPlayback::Error(const MediaResult& aError) { RefPtr<Benchmark> ref(mMainThreadState); Dispatch(NS_NewRunnableFunction([this, ref]() { MainThreadShutdown(); })); diff --git a/dom/media/Benchmark.h b/dom/media/Benchmark.h index 44dccf3cc33f49d9b28677f7e63d1ca08a59aca4..bb14751f45c48ee291a4b1d233527e694625f4f5 100644 --- a/dom/media/Benchmark.h +++ b/dom/media/Benchmark.h @@ -32,7 +32,7 @@ class BenchmarkPlayback : public QueueObject, private MediaDataDecoderCallback // MediaDataDecoderCallback // Those methods are called on the MediaDataDecoder's task queue. void Output(MediaData* aData) override; - void Error(MediaDataDecoderError aError) override; + void Error(const MediaResult& aError) override; void InputExhausted() override; void DrainComplete() override; bool OnReaderTaskQueue() override; diff --git a/dom/media/MP3Demuxer.cpp b/dom/media/MP3Demuxer.cpp index 57aa1391dff9dfaeec9bf2b38c75fdc6ef108e36..7d478a41b1a8c3179de7006a343114338d99eb24 100644 --- a/dom/media/MP3Demuxer.cpp +++ b/dom/media/MP3Demuxer.cpp @@ -55,7 +55,7 @@ MP3Demuxer::Init() { MP3LOG("MP3Demuxer::Init() failure: waiting for data"); return InitPromise::CreateAndReject( - DemuxerFailureReason::DEMUXER_ERROR, __func__); + NS_ERROR_DOM_MEDIA_METADATA_ERR, __func__); } MP3LOG("MP3Demuxer::Init() successful"); @@ -276,7 +276,7 @@ MP3TrackDemuxer::GetSamples(int32_t aNumSamples) { if (!aNumSamples) { return SamplesPromise::CreateAndReject( - DemuxerFailureReason::DEMUXER_ERROR, __func__); + NS_ERROR_DOM_MEDIA_DEMUXER_ERR, __func__); } RefPtr<SamplesHolder> frames = new SamplesHolder(); @@ -300,7 +300,7 @@ MP3TrackDemuxer::GetSamples(int32_t aNumSamples) { if (frames->mSamples.IsEmpty()) { return SamplesPromise::CreateAndReject( - DemuxerFailureReason::END_OF_STREAM, __func__); + NS_ERROR_DOM_MEDIA_END_OF_STREAM, __func__); } return SamplesPromise::CreateAndResolve(frames, __func__); } @@ -317,7 +317,7 @@ RefPtr<MP3TrackDemuxer::SkipAccessPointPromise> MP3TrackDemuxer::SkipToNextRandomAccessPoint(TimeUnit aTimeThreshold) { // Will not be called for audio-only resources. return SkipAccessPointPromise::CreateAndReject( - SkipFailureHolder(DemuxerFailureReason::DEMUXER_ERROR, 0), __func__); + SkipFailureHolder(NS_ERROR_DOM_MEDIA_DEMUXER_ERR, 0), __func__); } int64_t diff --git a/dom/media/MediaDataDemuxer.h b/dom/media/MediaDataDemuxer.h index 4d22ec2b96184fe37247529d42ae2b3d141a93b3..cf2b265d58ca6947a9343881cebf1f4977080a0a 100644 --- a/dom/media/MediaDataDemuxer.h +++ b/dom/media/MediaDataDemuxer.h @@ -12,6 +12,7 @@ #include "MediaData.h" #include "MediaInfo.h" +#include "MediaResult.h" #include "TimeUnits.h" #include "nsISupportsImpl.h" #include "mozilla/RefPtr.h" @@ -22,16 +23,6 @@ namespace mozilla { class MediaTrackDemuxer; class TrackMetadataHolder; -enum class DemuxerFailureReason : int8_t -{ - WAITING_FOR_DATA, - END_OF_STREAM, - DEMUXER_ERROR, - CANCELED, - SHUTDOWN, -}; - - // Allows reading the media data: to retrieve the metadata and demux samples. // MediaDataDemuxer isn't designed to be thread safe. // When used by the MediaFormatDecoder, care is taken to ensure that the demuxer @@ -41,7 +32,7 @@ class MediaDataDemuxer public: NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaDataDemuxer) - typedef MozPromise<nsresult, DemuxerFailureReason, /* IsExclusive = */ true> InitPromise; + typedef MozPromise<nsresult, MediaResult, /* IsExclusive = */ true> InitPromise; // Initializes the demuxer. Other methods cannot be called unless // initialization has completed and succeeded. @@ -120,16 +111,16 @@ public: class SkipFailureHolder { public: - SkipFailureHolder(DemuxerFailureReason aFailure, uint32_t aSkipped) + SkipFailureHolder(const MediaResult& aFailure, uint32_t aSkipped) : mFailure(aFailure) , mSkipped(aSkipped) {} - DemuxerFailureReason mFailure; + MediaResult mFailure; uint32_t mSkipped; }; - typedef MozPromise<media::TimeUnit, DemuxerFailureReason, /* IsExclusive = */ true> SeekPromise; - typedef MozPromise<RefPtr<SamplesHolder>, DemuxerFailureReason, /* IsExclusive = */ true> SamplesPromise; + typedef MozPromise<media::TimeUnit, MediaResult, /* IsExclusive = */ true> SeekPromise; + typedef MozPromise<RefPtr<SamplesHolder>, MediaResult, /* IsExclusive = */ true> SamplesPromise; typedef MozPromise<uint32_t, SkipFailureHolder, /* IsExclusive = */ true> SkipAccessPointPromise; // Returns the TrackInfo (a.k.a Track Description) for this track. diff --git a/dom/media/MediaDecoder.cpp b/dom/media/MediaDecoder.cpp index ab2f6bab1b174a37820eea587ebb7f8fd3209a42..726df20db9eb70fc4eef6ac3cbd7c6c28bcc8aba 100644 --- a/dom/media/MediaDecoder.cpp +++ b/dom/media/MediaDecoder.cpp @@ -198,7 +198,7 @@ MediaDecoder::ResourceCallback::NotifyDecodeError() RefPtr<ResourceCallback> self = this; nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([=] () { if (self->mDecoder) { - self->mDecoder->DecodeError(); + self->mDecoder->DecodeError(NS_ERROR_DOM_MEDIA_FATAL_ERR); } }); AbstractThread::MainThread()->Dispatch(r.forget()); @@ -607,6 +607,7 @@ MediaDecoder::Shutdown() mMetadataLoadedListener.Disconnect(); mFirstFrameLoadedListener.Disconnect(); mOnPlaybackEvent.Disconnect(); + mOnPlaybackErrorEvent.Disconnect(); mOnMediaNotSeekable.Disconnect(); mDecoderStateMachine->BeginShutdown() @@ -661,9 +662,6 @@ MediaDecoder::OnPlaybackEvent(MediaEventType aEvent) case MediaEventType::SeekStarted: SeekingStarted(); break; - case MediaEventType::DecodeError: - DecodeError(); - break; case MediaEventType::Invalidate: Invalidate(); break; @@ -676,6 +674,12 @@ MediaDecoder::OnPlaybackEvent(MediaEventType aEvent) } } +void +MediaDecoder::OnPlaybackErrorEvent(const MediaResult& aError) +{ + DecodeError(aError); +} + void MediaDecoder::FinishShutdown() { @@ -749,6 +753,8 @@ MediaDecoder::SetStateMachineParameters() mOnPlaybackEvent = mDecoderStateMachine->OnPlaybackEvent().Connect( AbstractThread::MainThread(), this, &MediaDecoder::OnPlaybackEvent); + mOnPlaybackErrorEvent = mDecoderStateMachine->OnPlaybackErrorEvent().Connect( + AbstractThread::MainThread(), this, &MediaDecoder::OnPlaybackErrorEvent); mOnMediaNotSeekable = mDecoderStateMachine->OnMediaNotSeekable().Connect( AbstractThread::MainThread(), this, &MediaDecoder::OnMediaNotSeekable); } @@ -1006,11 +1012,11 @@ MediaDecoder::NetworkError() } void -MediaDecoder::DecodeError() +MediaDecoder::DecodeError(const MediaResult& aError) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(!IsShutdown()); - mOwner->DecodeError(); + mOwner->DecodeError(aError); MOZ_ASSERT(IsShutdown()); } diff --git a/dom/media/MediaDecoder.h b/dom/media/MediaDecoder.h index 26063b87890237b9ef0f65ba107ea99d66fc6390..4692eba41b65da590388b6d4f22ee39c1be2b176 100644 --- a/dom/media/MediaDecoder.h +++ b/dom/media/MediaDecoder.h @@ -436,7 +436,7 @@ private: int64_t GetDownloadPosition(); // Notifies the element that decoding has failed. - void DecodeError(); + void DecodeError(const MediaResult& aError); // Indicate whether the media is same-origin with the element. void UpdateSameOriginStatus(bool aSameOrigin); @@ -592,6 +592,7 @@ private: DataArrivedEvent() override { return &mDataArrivedEvent; } void OnPlaybackEvent(MediaEventType aEvent); + void OnPlaybackErrorEvent(const MediaResult& aError); void OnMediaNotSeekable() { @@ -731,6 +732,7 @@ protected: MediaEventListener mFirstFrameLoadedListener; MediaEventListener mOnPlaybackEvent; + MediaEventListener mOnPlaybackErrorEvent; MediaEventListener mOnMediaNotSeekable; protected: diff --git a/dom/media/MediaDecoderOwner.h b/dom/media/MediaDecoderOwner.h index bcc879da599099b59a42b1bcdfab31733a74ee47..22f634665cfb4a7970e3cea9e270b0bd1a7a35ef 100644 --- a/dom/media/MediaDecoderOwner.h +++ b/dom/media/MediaDecoderOwner.h @@ -11,6 +11,7 @@ namespace mozilla { class VideoFrameContainer; +class MediaResult; namespace dom { class HTMLMediaElement; @@ -67,7 +68,7 @@ public: // resource has a decode error during metadata loading or decoding. // The decoder owner should call Shutdown() on the decoder and drop the // reference to the decoder to prevent further calls into the decoder. - virtual void DecodeError() = 0; + virtual void DecodeError(const MediaResult& aError) = 0; // Return true if media element error attribute is not null. virtual bool HasError() const = 0; diff --git a/dom/media/MediaDecoderReader.cpp b/dom/media/MediaDecoderReader.cpp index be9b3f2ce06b7863b2d8945489f4c7437a80528b..238b461363d3399222155fd25fd473b2891e5f28 100644 --- a/dom/media/MediaDecoderReader.cpp +++ b/dom/media/MediaDecoderReader.cpp @@ -289,12 +289,12 @@ nsresult MediaDecoderReader::ResetDecode(TrackSet aTracks) { if (aTracks.contains(TrackInfo::kVideoTrack)) { VideoQueue().Reset(); - mBaseVideoPromise.RejectIfExists(CANCELED, __func__); + mBaseVideoPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__); } if (aTracks.contains(TrackInfo::kAudioTrack)) { AudioQueue().Reset(); - mBaseAudioPromise.RejectIfExists(CANCELED, __func__); + mBaseAudioPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__); } return NS_OK; @@ -323,7 +323,7 @@ MediaDecoderReader::DecodeToFirstVideoData() p->Resolve(self->VideoQueue().PeekFront(), __func__); }, [p] () { // We don't have a way to differentiate EOS, error, and shutdown here. :-( - p->Reject(END_OF_STREAM, __func__); + p->Reject(NS_ERROR_DOM_MEDIA_END_OF_STREAM, __func__); }); return p.forget(); @@ -360,8 +360,6 @@ MediaDecoderReader::GetBuffered() RefPtr<MediaDecoderReader::MetadataPromise> MediaDecoderReader::AsyncReadMetadata() { - typedef ReadMetadataFailureReason Reason; - MOZ_ASSERT(OnTaskQueue()); DECODER_LOG("MediaDecoderReader::AsyncReadMetadata"); @@ -374,7 +372,7 @@ MediaDecoderReader::AsyncReadMetadata() // error. if (NS_FAILED(rv) || !metadata->mInfo.HasValidMedia()) { DECODER_WARN("ReadMetadata failed, rv=%x HasValidMedia=%d", rv, metadata->mInfo.HasValidMedia()); - return MetadataPromise::CreateAndReject(Reason::METADATA_ERROR, __func__); + return MetadataPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_METADATA_ERR, __func__); } // Success! @@ -456,7 +454,7 @@ MediaDecoderReader::RequestVideoData(bool aSkipToNextKeyframe, RefPtr<VideoData> v = VideoQueue().PopFront(); mBaseVideoPromise.Resolve(v, __func__); } else if (VideoQueue().IsFinished()) { - mBaseVideoPromise.Reject(END_OF_STREAM, __func__); + mBaseVideoPromise.Reject(NS_ERROR_DOM_MEDIA_END_OF_STREAM, __func__); } else { MOZ_ASSERT(false, "Dropping this promise on the floor"); } @@ -488,7 +486,9 @@ MediaDecoderReader::RequestAudioData() RefPtr<AudioData> a = AudioQueue().PopFront(); mBaseAudioPromise.Resolve(a, __func__); } else if (AudioQueue().IsFinished()) { - mBaseAudioPromise.Reject(mHitAudioDecodeError ? DECODE_ERROR : END_OF_STREAM, __func__); + mBaseAudioPromise.Reject(mHitAudioDecodeError + ? NS_ERROR_DOM_MEDIA_FATAL_ERR + : NS_ERROR_DOM_MEDIA_END_OF_STREAM, __func__); mHitAudioDecodeError = false; } else { MOZ_ASSERT(false, "Dropping this promise on the floor"); @@ -503,8 +503,8 @@ MediaDecoderReader::Shutdown() MOZ_ASSERT(OnTaskQueue()); mShutdown = true; - mBaseAudioPromise.RejectIfExists(END_OF_STREAM, __func__); - mBaseVideoPromise.RejectIfExists(END_OF_STREAM, __func__); + mBaseAudioPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_END_OF_STREAM, __func__); + mBaseVideoPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_END_OF_STREAM, __func__); mDataArrivedListener.DisconnectIfExists(); diff --git a/dom/media/MediaDecoderReader.h b/dom/media/MediaDecoderReader.h index 6e3a02a805cc40958b6200a8ed4b3f4757a2c845..681c12fb27830b926f5adac65777e896cabdaa56 100644 --- a/dom/media/MediaDecoderReader.h +++ b/dom/media/MediaDecoderReader.h @@ -13,6 +13,7 @@ #include "AbstractMediaDecoder.h" #include "MediaInfo.h" #include "MediaData.h" +#include "MediaResult.h" #include "MediaMetadataManager.h" #include "MediaQueue.h" #include "MediaTimer.h" @@ -50,11 +51,6 @@ private: virtual ~MetadataHolder() {} }; -enum class ReadMetadataFailureReason : int8_t -{ - METADATA_ERROR -}; - // Encapsulates the decoding and reading of media data. Reading can either // synchronous and done on the calling "decode" thread, or asynchronous and // performed on a background thread, with the result being returned by @@ -68,19 +64,12 @@ class MediaDecoderReader { static const bool IsExclusive = true; public: - enum NotDecodedReason { - END_OF_STREAM, - DECODE_ERROR, - WAITING_FOR_DATA, - CANCELED - }; - using TrackSet = EnumSet<TrackInfo::TrackType>; using MetadataPromise = - MozPromise<RefPtr<MetadataHolder>, ReadMetadataFailureReason, IsExclusive>; + MozPromise<RefPtr<MetadataHolder>, MediaResult, IsExclusive>; using MediaDataPromise = - MozPromise<RefPtr<MediaData>, NotDecodedReason, IsExclusive>; + MozPromise<RefPtr<MediaData>, MediaResult, IsExclusive>; using SeekPromise = MozPromise<media::TimeUnit, nsresult, IsExclusive>; // Note that, conceptually, WaitForData makes sense in a non-exclusive sense. diff --git a/dom/media/MediaDecoderReaderWrapper.cpp b/dom/media/MediaDecoderReaderWrapper.cpp index 63379ad28bab9e78bca0dc3ef53c8e8ce8f0a1ca..455e86f4329018e74139c7be66025b6c117a1c0e 100644 --- a/dom/media/MediaDecoderReaderWrapper.cpp +++ b/dom/media/MediaDecoderReaderWrapper.cpp @@ -76,22 +76,22 @@ public: p->Resolve(data, __func__); }, [p] () { - p->Reject(MediaDecoderReader::CANCELED, __func__); + p->Reject(NS_ERROR_DOM_MEDIA_CANCELED, __func__); }); return p.forget(); } template<MediaData::Type SampleType> - void FirstSampleRejected(MediaDecoderReader::NotDecodedReason aReason) + void FirstSampleRejected(const MediaResult& aError) { MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); - if (aReason == MediaDecoderReader::DECODE_ERROR) { - mHaveStartTimePromise.RejectIfExists(false, __func__); - } else if (aReason == MediaDecoderReader::END_OF_STREAM) { + if (aError == NS_ERROR_DOM_MEDIA_END_OF_STREAM) { LOG("StartTimeRendezvous=%p SampleType(%d) Has no samples.", this, SampleType); MaybeSetChannelStartTime<SampleType>(INT64_MAX); + } else if (aError != NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA) { + mHaveStartTimePromise.RejectIfExists(false, __func__); } } @@ -200,9 +200,9 @@ MediaDecoderReaderWrapper::RequestAudioData() aAudioSample->AdjustForStartTime(self->StartTime().ToMicroseconds()); self->mAudioCallback.Notify(AsVariant(aAudioSample)); }, - [self] (MediaDecoderReader::NotDecodedReason aReason) { + [self] (const MediaResult& aError) { self->mAudioDataRequest.Complete(); - self->mAudioCallback.Notify(AsVariant(aReason)); + self->mAudioCallback.Notify(AsVariant(aError)); })); } @@ -240,9 +240,9 @@ MediaDecoderReaderWrapper::RequestVideoData(bool aSkipToNextKeyframe, aVideoSample->AdjustForStartTime(self->StartTime().ToMicroseconds()); self->mVideoCallback.Notify(AsVariant(MakeTuple(aVideoSample, videoDecodeStartTime))); }, - [self] (MediaDecoderReader::NotDecodedReason aReason) { + [self] (const MediaResult& aError) { self->mVideoDataRequest.Complete(); - self->mVideoCallback.Notify(AsVariant(aReason)); + self->mVideoCallback.Notify(AsVariant(aError)); })); } diff --git a/dom/media/MediaDecoderReaderWrapper.h b/dom/media/MediaDecoderReaderWrapper.h index 69d5969151a149dc1c64b2ae906c47f83eb2c4be..e712689d6c10861e1282b942c3bbe6a79d6a7a77 100644 --- a/dom/media/MediaDecoderReaderWrapper.h +++ b/dom/media/MediaDecoderReaderWrapper.h @@ -21,8 +21,8 @@ class StartTimeRendezvous; typedef MozPromise<bool, bool, /* isExclusive = */ false> HaveStartTimePromise; -typedef Variant<MediaData*, MediaDecoderReader::NotDecodedReason> AudioCallbackData; -typedef Variant<Tuple<MediaData*, TimeStamp>, MediaDecoderReader::NotDecodedReason> VideoCallbackData; +typedef Variant<MediaData*, MediaResult> AudioCallbackData; +typedef Variant<Tuple<MediaData*, TimeStamp>, MediaResult> VideoCallbackData; typedef Variant<MediaData::Type, WaitForDataRejectValue> WaitCallbackData; /** diff --git a/dom/media/MediaDecoderStateMachine.cpp b/dom/media/MediaDecoderStateMachine.cpp index 7cf2d402803ae462650c4043ed0280cc6fc39afd..34a348795e4fc2835070bf6d26767df1f88544e9 100644 --- a/dom/media/MediaDecoderStateMachine.cpp +++ b/dom/media/MediaDecoderStateMachine.cpp @@ -263,8 +263,8 @@ public: [this] (MetadataHolder* aMetadata) { OnMetadataRead(aMetadata); }, - [this] (ReadMetadataFailureReason aReason) { - OnMetadataNotRead(aReason); + [this] (const MediaResult& aError) { + OnMetadataNotRead(aError); })); } @@ -355,11 +355,11 @@ private: SetState(DECODER_STATE_DECODING_FIRSTFRAME); } - void OnMetadataNotRead(ReadMetadataFailureReason aReason) + void OnMetadataNotRead(const MediaResult& aError) { mMetadataRequest.Complete(); SWARN("Decode metadata failed, shutting down decoder"); - mMaster->DecodeError(); + mMaster->DecodeError(aError); } MozPromiseRequestHolder<MediaDecoderReader::MetadataPromise> mMetadataRequest; @@ -979,12 +979,12 @@ MediaDecoderStateMachine::OnVideoPopped(const RefPtr<MediaData>& aSample) void MediaDecoderStateMachine::OnNotDecoded(MediaData::Type aType, - MediaDecoderReader::NotDecodedReason aReason) + const MediaResult& aError) { MOZ_ASSERT(OnTaskQueue()); MOZ_ASSERT(mState != DECODER_STATE_SEEKING); - SAMPLE_LOG("OnNotDecoded (aType=%u, aReason=%u)", aType, aReason); + SAMPLE_LOG("OnNotDecoded (aType=%u, aError=%u)", aType, aError.Code()); bool isAudio = aType == MediaData::AUDIO_DATA; MOZ_ASSERT_IF(!isAudio, aType == MediaData::VIDEO_DATA); @@ -993,15 +993,9 @@ MediaDecoderStateMachine::OnNotDecoded(MediaData::Type aType, return; } - // If this is a decode error, delegate to the generic error path. - if (aReason == MediaDecoderReader::DECODE_ERROR) { - DecodeError(); - return; - } - // If the decoder is waiting for data, we tell it to call us back when the // data arrives. - if (aReason == MediaDecoderReader::WAITING_FOR_DATA) { + if (aError == NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA) { MOZ_ASSERT(mReader->IsWaitForDataSupported(), "Readers that send WAITING_FOR_DATA need to implement WaitForData"); mReader->WaitForData(aType); @@ -1017,7 +1011,7 @@ MediaDecoderStateMachine::OnNotDecoded(MediaData::Type aType, return; } - if (aReason == MediaDecoderReader::CANCELED) { + if (aError == NS_ERROR_DOM_MEDIA_CANCELED) { if (isAudio) { EnsureAudioDecodeTaskQueued(); } else { @@ -1026,9 +1020,14 @@ MediaDecoderStateMachine::OnNotDecoded(MediaData::Type aType, return; } + // If this is a decode error, delegate to the generic error path. + if (aError != NS_ERROR_DOM_MEDIA_END_OF_STREAM) { + DecodeError(aError); + return; + } + // This is an EOS. Finish off the queue, and then handle things based on our // state. - MOZ_ASSERT(aReason == MediaDecoderReader::END_OF_STREAM); if (isAudio) { AudioQueue().Finish(); StopPrerollingAudio(); @@ -1219,7 +1218,7 @@ MediaDecoderStateMachine::SetMediaDecoderReaderWrapperCallback() if (aData.is<MediaData*>()) { OnAudioDecoded(aData.as<MediaData*>()); } else { - OnNotDecoded(MediaData::AUDIO_DATA, aData.as<MediaDecoderReader::NotDecodedReason>()); + OnNotDecoded(MediaData::AUDIO_DATA, aData.as<MediaResult>()); } }); @@ -1230,7 +1229,7 @@ MediaDecoderStateMachine::SetMediaDecoderReaderWrapperCallback() auto&& v = aData.as<Type>(); OnVideoDecoded(Get<0>(v), Get<1>(v)); } else { - OnNotDecoded(MediaData::VIDEO_DATA, aData.as<MediaDecoderReader::NotDecodedReason>()); + OnNotDecoded(MediaData::VIDEO_DATA, aData.as<MediaResult>()); } }); @@ -2083,7 +2082,7 @@ MediaDecoderStateMachine::OnSeekTaskRejected(SeekTaskRejectValue aValue) StopPrerollingVideo(); } - DecodeError(); + DecodeError(aValue.mError); DiscardSeekTaskIfExist(); } @@ -2292,13 +2291,13 @@ bool MediaDecoderStateMachine::HasLowUndecodedData(int64_t aUsecs) } void -MediaDecoderStateMachine::DecodeError() +MediaDecoderStateMachine::DecodeError(const MediaResult& aError) { MOZ_ASSERT(OnTaskQueue()); MOZ_ASSERT(!IsShutdown()); DECODER_WARN("Decode error"); // Notify the decode error and MediaDecoder will shut down MDSM. - mOnPlaybackEvent.Notify(MediaEventType::DecodeError); + mOnPlaybackErrorEvent.Notify(aError); } void @@ -2919,7 +2918,7 @@ MediaDecoderStateMachine::OnMediaSinkVideoError() if (HasAudio()) { return; } - DecodeError(); + DecodeError(MediaResult(NS_ERROR_DOM_MEDIA_MEDIASINK_ERR, __func__)); } void MediaDecoderStateMachine::OnMediaSinkAudioComplete() @@ -2950,7 +2949,7 @@ void MediaDecoderStateMachine::OnMediaSinkAudioError() // Otherwise notify media decoder/element about this error for it makes // no sense to play an audio-only file without sound output. - DecodeError(); + DecodeError(MediaResult(NS_ERROR_DOM_MEDIA_MEDIASINK_ERR, __func__)); } #ifdef MOZ_EME diff --git a/dom/media/MediaDecoderStateMachine.h b/dom/media/MediaDecoderStateMachine.h index 2d40ec6615280867bbaeaad6b51f278ec1c39e22..70760ad7614f05699d89319cb79a872d47512e39 100644 --- a/dom/media/MediaDecoderStateMachine.h +++ b/dom/media/MediaDecoderStateMachine.h @@ -119,7 +119,6 @@ enum class MediaEventType : int8_t { PlaybackStopped, PlaybackEnded, SeekStarted, - DecodeError, Invalidate, EnterVideoSuspend, ExitVideoSuspend @@ -245,6 +244,8 @@ public: MediaEventSource<MediaEventType>& OnPlaybackEvent() { return mOnPlaybackEvent; } + MediaEventSource<MediaResult>& + OnPlaybackErrorEvent() { return mOnPlaybackErrorEvent; } size_t SizeOfVideoQueue() const; @@ -344,7 +345,7 @@ private: // Need to figure out a suitable API name for this case. void OnAudioDecoded(MediaData* aAudioSample); void OnVideoDecoded(MediaData* aVideoSample, TimeStamp aDecodeStartTime); - void OnNotDecoded(MediaData::Type aType, MediaDecoderReader::NotDecodedReason aReason); + void OnNotDecoded(MediaData::Type aType, const MediaResult& aError); // Resets all state related to decoding and playback, emptying all buffers // and aborting all pending operations on the decode task queue. @@ -481,7 +482,7 @@ protected: // event to the media element. This begins shutting down the decoder. // The decoder monitor must be held. This is only called on the // decode thread. - void DecodeError(); + void DecodeError(const MediaResult& aError); // Dispatches a LoadedMetadataEvent. // This is threadsafe and can be called on any thread. @@ -892,6 +893,7 @@ private: MediaDecoderEventVisibility> mFirstFrameLoadedEvent; MediaEventProducer<MediaEventType> mOnPlaybackEvent; + MediaEventProducer<MediaResult> mOnPlaybackErrorEvent; // True if audio is offloading. // Playback will not start when audio is offloading. diff --git a/dom/media/MediaFormatReader.cpp b/dom/media/MediaFormatReader.cpp index 2a88ff90852822e7b2c7a142c4d463902de6c4c5..dda2af89babb30ff3ce4dcbeaff5c8b7654acb22 100644 --- a/dom/media/MediaFormatReader.cpp +++ b/dom/media/MediaFormatReader.cpp @@ -93,14 +93,14 @@ MediaFormatReader::Shutdown() MOZ_ASSERT(OnTaskQueue()); mDemuxerInitRequest.DisconnectIfExists(); - mMetadataPromise.RejectIfExists(ReadMetadataFailureReason::METADATA_ERROR, __func__); - mSeekPromise.RejectIfExists(NS_ERROR_FAILURE, __func__); + mMetadataPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__); + mSeekPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__); mSkipRequest.DisconnectIfExists(); if (mAudio.mDecoder) { Reset(TrackInfo::kAudioTrack); if (mAudio.HasPromise()) { - mAudio.RejectPromise(CANCELED, __func__); + mAudio.RejectPromise(NS_ERROR_DOM_MEDIA_CANCELED, __func__); } mAudio.ShutdownDecoder(); } @@ -119,7 +119,7 @@ MediaFormatReader::Shutdown() if (mVideo.mDecoder) { Reset(TrackInfo::kVideoTrack); if (mVideo.HasPromise()) { - mVideo.RejectPromise(CANCELED, __func__); + mVideo.RejectPromise(NS_ERROR_DOM_MEDIA_CANCELED, __func__); } mVideo.ShutdownDecoder(); } @@ -283,7 +283,7 @@ MediaFormatReader::OnDemuxerInitDone(nsresult) // We currently only handle the first video track. mVideo.mTrackDemuxer = mDemuxer->GetTrackDemuxer(TrackInfo::kVideoTrack, 0); if (!mVideo.mTrackDemuxer) { - mMetadataPromise.Reject(ReadMetadataFailureReason::METADATA_ERROR, __func__); + mMetadataPromise.Reject(NS_ERROR_DOM_MEDIA_METADATA_ERR, __func__); return; } @@ -292,7 +292,7 @@ MediaFormatReader::OnDemuxerInitDone(nsresult) if (videoActive) { if (platform && !platform->SupportsMimeType(videoInfo->mMimeType, nullptr)) { // We have no decoder for this track. Error. - mMetadataPromise.Reject(ReadMetadataFailureReason::METADATA_ERROR, __func__); + mMetadataPromise.Reject(NS_ERROR_DOM_MEDIA_METADATA_ERR, __func__); return; } mInfo.mVideo = *videoInfo->GetAsVideoInfo(); @@ -312,7 +312,7 @@ MediaFormatReader::OnDemuxerInitDone(nsresult) if (audioActive) { mAudio.mTrackDemuxer = mDemuxer->GetTrackDemuxer(TrackInfo::kAudioTrack, 0); if (!mAudio.mTrackDemuxer) { - mMetadataPromise.Reject(ReadMetadataFailureReason::METADATA_ERROR, __func__); + mMetadataPromise.Reject(NS_ERROR_DOM_MEDIA_METADATA_ERR, __func__); return; } @@ -364,7 +364,7 @@ MediaFormatReader::OnDemuxerInitDone(nsresult) mDemuxer->IsSeekableOnlyInBufferedRanges(); if (!videoActive && !audioActive) { - mMetadataPromise.Reject(ReadMetadataFailureReason::METADATA_ERROR, __func__); + mMetadataPromise.Reject(NS_ERROR_DOM_MEDIA_METADATA_ERR, __func__); return; } @@ -376,13 +376,13 @@ MediaFormatReader::OnDemuxerInitDone(nsresult) } void -MediaFormatReader::OnDemuxerInitFailed(DemuxerFailureReason aFailure) +MediaFormatReader::OnDemuxerInitFailed(const MediaResult& aError) { mDemuxerInitRequest.Complete(); - mMetadataPromise.Reject(ReadMetadataFailureReason::METADATA_ERROR, __func__); + mMetadataPromise.Reject(aError, __func__); } -bool +MediaResult MediaFormatReader::EnsureDecoderCreated(TrackType aTrack) { MOZ_ASSERT(OnTaskQueue()); @@ -391,19 +391,17 @@ MediaFormatReader::EnsureDecoderCreated(TrackType aTrack) auto& decoder = GetDecoderData(aTrack); if (decoder.mDecoder) { - return true; + return NS_OK; } if (!mPlatform) { mPlatform = new PDMFactory(); - NS_ENSURE_TRUE(mPlatform, false); if (IsEncrypted()) { #ifdef MOZ_EME MOZ_ASSERT(mCDMProxy); mPlatform->SetCDMProxy(mCDMProxy); #else - // EME not supported. - return false; + return MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, "EME not supported"); #endif } } @@ -443,10 +441,10 @@ MediaFormatReader::EnsureDecoderCreated(TrackType aTrack) } if (decoder.mDecoder ) { decoder.mDescription = decoder.mDecoder->GetDescriptionName(); - } else { - decoder.mDescription = "error creating decoder"; + return NS_OK; } - return decoder.mDecoder != nullptr; + decoder.mDescription = "error creating decoder"; + return MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, "error creating decoder"); } bool @@ -479,11 +477,11 @@ MediaFormatReader::EnsureDecoderInitialized(TrackType aTrack) self->SetVideoDecodeThreshold(); self->ScheduleUpdate(aTrack); }, - [self, aTrack] (MediaDataDecoder::DecoderFailureReason aResult) { + [self, aTrack] (MediaResult aError) { auto& decoder = self->GetDecoderData(aTrack); decoder.mInitPromise.Complete(); decoder.ShutdownDecoder(); - self->NotifyError(aTrack); + self->NotifyError(aTrack, aError); })); return false; } @@ -534,21 +532,21 @@ MediaFormatReader::RequestVideoData(bool aSkipToNextKeyframe, if (!HasVideo()) { LOG("called with no video track"); - return MediaDataPromise::CreateAndReject(DECODE_ERROR, __func__); + return MediaDataPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); } if (IsSeeking()) { LOG("called mid-seek. Rejecting."); - return MediaDataPromise::CreateAndReject(CANCELED, __func__); + return MediaDataPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_CANCELED, __func__); } if (mShutdown) { NS_WARNING("RequestVideoData on shutdown MediaFormatReader!"); - return MediaDataPromise::CreateAndReject(CANCELED, __func__); + return MediaDataPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_CANCELED, __func__); } if (IsSuspended()) { - return MediaDataPromise::CreateAndReject(CANCELED, __func__); + return MediaDataPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_CANCELED, __func__); } media::TimeUnit timeThreshold{media::TimeUnit::FromMicroseconds(aTimeThreshold)}; @@ -568,37 +566,33 @@ MediaFormatReader::RequestVideoData(bool aSkipToNextKeyframe, } void -MediaFormatReader::OnDemuxFailed(TrackType aTrack, DemuxerFailureReason aFailure) +MediaFormatReader::OnDemuxFailed(TrackType aTrack, const MediaResult& aError) { MOZ_ASSERT(OnTaskQueue()); - LOG("Failed to demux %s, failure:%d", - aTrack == TrackType::kVideoTrack ? "video" : "audio", aFailure); + LOG("Failed to demux %s, failure:%u", + aTrack == TrackType::kVideoTrack ? "video" : "audio", aError.Code()); auto& decoder = GetDecoderData(aTrack); decoder.mDemuxRequest.Complete(); - switch (aFailure) { - case DemuxerFailureReason::END_OF_STREAM: + switch (aError.Code()) { + case NS_ERROR_DOM_MEDIA_END_OF_STREAM: if (!decoder.mWaitingForData) { decoder.mNeedDraining = true; } NotifyEndOfStream(aTrack); break; - case DemuxerFailureReason::DEMUXER_ERROR: - NotifyError(aTrack); - break; - case DemuxerFailureReason::WAITING_FOR_DATA: + case NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA: if (!decoder.mWaitingForData) { decoder.mNeedDraining = true; } NotifyWaitingForData(aTrack); break; - case DemuxerFailureReason::CANCELED: MOZ_FALLTHROUGH; - case DemuxerFailureReason::SHUTDOWN: + case NS_ERROR_DOM_MEDIA_CANCELED: if (decoder.HasPromise()) { - decoder.RejectPromise(CANCELED, __func__); + decoder.RejectPromise(NS_ERROR_DOM_MEDIA_CANCELED, __func__); } break; default: - MOZ_ASSERT(false); + NotifyError(aTrack, aError); break; } } @@ -638,21 +632,21 @@ MediaFormatReader::RequestAudioData() if (!HasAudio()) { LOG("called with no audio track"); - return MediaDataPromise::CreateAndReject(DECODE_ERROR, __func__); + return MediaDataPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); } if (IsSuspended()) { - return MediaDataPromise::CreateAndReject(CANCELED, __func__); + return MediaDataPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_CANCELED, __func__); } if (IsSeeking()) { LOG("called mid-seek. Rejecting."); - return MediaDataPromise::CreateAndReject(CANCELED, __func__); + return MediaDataPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_CANCELED, __func__); } if (mShutdown) { NS_WARNING("RequestAudioData on shutdown MediaFormatReader!"); - return MediaDataPromise::CreateAndReject(CANCELED, __func__); + return MediaDataPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_CANCELED, __func__); } RefPtr<MediaDataPromise> p = mAudio.EnsurePromise(__func__); @@ -723,7 +717,7 @@ MediaFormatReader::NotifyDrainComplete(TrackType aTrack) } void -MediaFormatReader::NotifyError(TrackType aTrack, MediaDataDecoderError aError) +MediaFormatReader::NotifyError(TrackType aTrack, const MediaResult& aError) { MOZ_ASSERT(OnTaskQueue()); LOGV("%s Decoding error", TrackTypeToStr(aTrack)); @@ -938,9 +932,10 @@ MediaFormatReader::HandleDemuxedSamples(TrackType aTrack, return; } - if (!EnsureDecoderCreated(aTrack)) { + MediaResult rv = EnsureDecoderCreated(aTrack); + if (NS_FAILED(rv)) { NS_WARNING("Error constructing decoders"); - NotifyError(aTrack); + NotifyError(aTrack, rv); return; } @@ -1052,24 +1047,23 @@ MediaFormatReader::InternalSeek(TrackType aTrack, const InternalSeekTarget& aTar self->SetVideoDecodeThreshold(); self->ScheduleUpdate(aTrack); }, - [self, aTrack] (DemuxerFailureReason aResult) { + [self, aTrack] (const MediaResult& aError) { auto& decoder = self->GetDecoderData(aTrack); decoder.mSeekRequest.Complete(); - switch (aResult) { - case DemuxerFailureReason::WAITING_FOR_DATA: + switch (aError.Code()) { + case NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA: self->NotifyWaitingForData(aTrack); break; - case DemuxerFailureReason::END_OF_STREAM: + case NS_ERROR_DOM_MEDIA_END_OF_STREAM: decoder.mTimeThreshold.reset(); self->NotifyEndOfStream(aTrack); break; - case DemuxerFailureReason::CANCELED: MOZ_FALLTHROUGH; - case DemuxerFailureReason::SHUTDOWN: + case NS_ERROR_DOM_MEDIA_CANCELED: decoder.mTimeThreshold.reset(); break; default: decoder.mTimeThreshold.reset(); - self->NotifyError(aTrack); + self->NotifyError(aTrack, aError); break; } })); @@ -1205,7 +1199,7 @@ MediaFormatReader::Update(TrackType aTrack) } } else if (decoder.HasFatalError()) { LOG("Rejecting %s promise: DECODE_ERROR", TrackTypeToStr(aTrack)); - decoder.RejectPromise(DECODE_ERROR, __func__); + decoder.RejectPromise(decoder.mError.ref(), __func__); return; } else if (decoder.mDrainComplete) { bool wasDraining = decoder.mDraining; @@ -1213,7 +1207,7 @@ MediaFormatReader::Update(TrackType aTrack) decoder.mDraining = false; if (decoder.mDemuxEOS) { LOG("Rejecting %s promise: EOS", TrackTypeToStr(aTrack)); - decoder.RejectPromise(END_OF_STREAM, __func__); + decoder.RejectPromise(NS_ERROR_DOM_MEDIA_END_OF_STREAM, __func__); } else if (decoder.mWaitingForData) { if (wasDraining && decoder.mLastSampleTime && !decoder.mNextStreamSourceID) { @@ -1226,7 +1220,7 @@ MediaFormatReader::Update(TrackType aTrack) } if (!decoder.mReceivedNewData) { LOG("Rejecting %s promise: WAITING_FOR_DATA", TrackTypeToStr(aTrack)); - decoder.RejectPromise(WAITING_FOR_DATA, __func__); + decoder.RejectPromise(NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA, __func__); } } // Now that draining has completed, we check if we have received @@ -1243,7 +1237,7 @@ MediaFormatReader::Update(TrackType aTrack) // There is no more samples left to be decoded and we are already in // EOS state. We can immediately reject the data promise. LOG("Rejecting %s promise: EOS", TrackTypeToStr(aTrack)); - decoder.RejectPromise(END_OF_STREAM, __func__); + decoder.RejectPromise(NS_ERROR_DOM_MEDIA_END_OF_STREAM, __func__); } } @@ -1252,14 +1246,13 @@ MediaFormatReader::Update(TrackType aTrack) return; } - if (decoder.mError && - decoder.mError.ref() == MediaDataDecoderError::DECODE_ERROR) { + if (decoder.mError && !decoder.HasFatalError()) { decoder.mDecodePending = false; - decoder.mError.reset(); if (++decoder.mNumOfConsecutiveError > decoder.mMaxConsecutiveError) { - NotifyError(aTrack); + NotifyError(aTrack, decoder.mError.ref()); return; } + decoder.mError.reset(); LOG("%s decoded error count %d", TrackTypeToStr(aTrack), decoder.mNumOfConsecutiveError); media::TimeUnit nextKeyframe; @@ -1398,7 +1391,7 @@ MediaFormatReader::ResetDecode(TrackSet aTracks) mVideo.ResetDemuxer(); Reset(TrackInfo::kVideoTrack); if (mVideo.HasPromise()) { - mVideo.RejectPromise(CANCELED, __func__); + mVideo.RejectPromise(NS_ERROR_DOM_MEDIA_CANCELED, __func__); } } @@ -1406,7 +1399,7 @@ MediaFormatReader::ResetDecode(TrackSet aTracks) mAudio.ResetDemuxer(); Reset(TrackInfo::kAudioTrack); if (mAudio.HasPromise()) { - mAudio.RejectPromise(CANCELED, __func__); + mAudio.RejectPromise(NS_ERROR_DOM_MEDIA_CANCELED, __func__); } } @@ -1418,7 +1411,7 @@ MediaFormatReader::Output(TrackType aTrack, MediaData* aSample) { if (!aSample) { NS_WARNING("MediaFormatReader::Output() passed a null sample"); - Error(aTrack); + Error(aTrack, MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR, __func__)); return; } @@ -1451,10 +1444,10 @@ MediaFormatReader::InputExhausted(TrackType aTrack) } void -MediaFormatReader::Error(TrackType aTrack, MediaDataDecoderError aError) +MediaFormatReader::Error(TrackType aTrack, const MediaResult& aError) { RefPtr<nsIRunnable> task = - NewRunnableMethod<TrackType, MediaDataDecoderError>( + NewRunnableMethod<TrackType, MediaResult>( this, &MediaFormatReader::NotifyError, aTrack, aError); OwnerThread()->Dispatch(task.forget()); } @@ -1567,9 +1560,9 @@ MediaFormatReader::OnVideoSkipFailed(MediaTrackDemuxer::SkipFailureHolder aFailu LOG("Skipping failed, skipped %u frames", aFailure.mSkipped); mSkipRequest.Complete(); - switch (aFailure.mFailure) { - case DemuxerFailureReason::END_OF_STREAM: MOZ_FALLTHROUGH; - case DemuxerFailureReason::WAITING_FOR_DATA: + switch (aFailure.mFailure.Code()) { + case NS_ERROR_DOM_MEDIA_END_OF_STREAM: + case NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA: // Some frames may have been output by the decoder since we initiated the // videoskip process and we know they would be late. DropDecodedSamples(TrackInfo::kVideoTrack); @@ -1577,14 +1570,13 @@ MediaFormatReader::OnVideoSkipFailed(MediaTrackDemuxer::SkipFailureHolder aFailu // normally. ScheduleUpdate(TrackInfo::kVideoTrack); break; - case DemuxerFailureReason::CANCELED: MOZ_FALLTHROUGH; - case DemuxerFailureReason::SHUTDOWN: + case NS_ERROR_DOM_MEDIA_CANCELED: if (mVideo.HasPromise()) { - mVideo.RejectPromise(CANCELED, __func__); + mVideo.RejectPromise(aFailure.mFailure, __func__); } break; default: - NotifyError(TrackType::kVideoTrack); + NotifyError(TrackType::kVideoTrack, aFailure.mFailure); break; } } @@ -1696,17 +1688,17 @@ MediaFormatReader::AttemptSeek() } void -MediaFormatReader::OnSeekFailed(TrackType aTrack, DemuxerFailureReason aResult) +MediaFormatReader::OnSeekFailed(TrackType aTrack, const MediaResult& aError) { MOZ_ASSERT(OnTaskQueue()); - LOGV("%s failure:%d", TrackTypeToStr(aTrack), aResult); + LOGV("%s failure:%u", TrackTypeToStr(aTrack), aError.Code()); if (aTrack == TrackType::kVideoTrack) { mVideo.mSeekRequest.Complete(); } else { mAudio.mSeekRequest.Complete(); } - if (aResult == DemuxerFailureReason::WAITING_FOR_DATA) { + if (aError == NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA) { if (HasVideo() && aTrack == TrackType::kAudioTrack && mFallbackSeekTime.isSome() && mPendingSeekTime.ref() != mFallbackSeekTime.ref()) { @@ -1740,7 +1732,7 @@ MediaFormatReader::OnSeekFailed(TrackType aTrack, DemuxerFailureReason aResult) } MOZ_ASSERT(!mVideo.mSeekRequest.Exists() && !mAudio.mSeekRequest.Exists()); mPendingSeekTime.reset(); - mSeekPromise.Reject(NS_ERROR_FAILURE, __func__); + mSeekPromise.Reject(aError, __func__); } void @@ -1781,10 +1773,10 @@ MediaFormatReader::OnVideoSeekCompleted(media::TimeUnit aTime) } void -MediaFormatReader::OnVideoSeekFailed(DemuxerFailureReason aFailure) +MediaFormatReader::OnVideoSeekFailed(const MediaResult& aError) { mPreviousDecodedKeyframeTime_us = sNoPreviousDecodedKeyframe; - OnSeekFailed(TrackType::kVideoTrack, aFailure); + OnSeekFailed(TrackType::kVideoTrack, aError); } void @@ -1848,9 +1840,9 @@ MediaFormatReader::OnAudioSeekCompleted(media::TimeUnit aTime) } void -MediaFormatReader::OnAudioSeekFailed(DemuxerFailureReason aFailure) +MediaFormatReader::OnAudioSeekFailed(const MediaResult& aError) { - OnSeekFailed(TrackType::kAudioTrack, aFailure); + OnSeekFailed(TrackType::kAudioTrack, aError); } media::TimeIntervals diff --git a/dom/media/MediaFormatReader.h b/dom/media/MediaFormatReader.h index b793f62efc2576d4894945dce073da2bedf605a8..64646ef28dc4b08380fbd279bd50cf3f99b77383 100644 --- a/dom/media/MediaFormatReader.h +++ b/dom/media/MediaFormatReader.h @@ -117,7 +117,7 @@ private: void NotifyDemuxer(); void ReturnOutput(MediaData* aData, TrackType aTrack); - bool EnsureDecoderCreated(TrackType aTrack); + MediaResult EnsureDecoderCreated(TrackType aTrack); bool EnsureDecoderInitialized(TrackType aTrack); // Enqueues a task to call Update(aTrack) on the decoder task queue. @@ -166,7 +166,7 @@ private: void NotifyNewOutput(TrackType aTrack, MediaData* aSample); void NotifyInputExhausted(TrackType aTrack); void NotifyDrainComplete(TrackType aTrack); - void NotifyError(TrackType aTrack, MediaDataDecoderError aError = MediaDataDecoderError::FATAL_ERROR); + void NotifyError(TrackType aTrack, const MediaResult& aError); void NotifyWaitingForData(TrackType aTrack); void NotifyEndOfStream(TrackType aTrack); @@ -179,7 +179,7 @@ private: // functions. void Output(TrackType aType, MediaData* aSample); void InputExhausted(TrackType aTrack); - void Error(TrackType aTrack, MediaDataDecoderError aError = MediaDataDecoderError::FATAL_ERROR); + void Error(TrackType aTrack, const MediaResult& aError); void Reset(TrackType aTrack); void DrainComplete(TrackType aTrack); void DropDecodedSamples(TrackType aTrack); @@ -206,7 +206,7 @@ private: void InputExhausted() override { mReader->InputExhausted(mType); } - void Error(MediaDataDecoderError aError) override { + void Error(const MediaResult& aError) override { mReader->Error(mType, aError); } void DrainComplete() override { @@ -327,10 +327,10 @@ private: uint32_t mNumOfConsecutiveError; uint32_t mMaxConsecutiveError; - Maybe<MediaDataDecoderError> mError; + Maybe<MediaResult> mError; bool HasFatalError() const { - return mError.isSome() && mError.ref() == MediaDataDecoderError::FATAL_ERROR; + return mError.isSome() && mError.ref() != NS_ERROR_DOM_MEDIA_DECODE_ERR; } // If set, all decoded samples prior mTimeThreshold will be dropped. @@ -354,7 +354,7 @@ private: virtual bool HasPromise() const = 0; virtual RefPtr<MediaDataPromise> EnsurePromise(const char* aMethodName) = 0; virtual void ResolvePromise(MediaData* aData, const char* aMethodName) = 0; - virtual void RejectPromise(MediaDecoderReader::NotDecodedReason aReason, + virtual void RejectPromise(const MediaResult& aError, const char* aMethodName) = 0; // Clear track demuxer related data. @@ -462,11 +462,11 @@ private: mHasPromise = false; } - void RejectPromise(MediaDecoderReader::NotDecodedReason aReason, + void RejectPromise(const MediaResult& aError, const char* aMethodName) override { MOZ_ASSERT(mOwner->OnTaskQueue()); - mPromise.Reject(aReason, aMethodName); + mPromise.Reject(aError, aMethodName); mHasPromise = false; } @@ -487,22 +487,22 @@ private: RefPtr<MediaDataDemuxer> mDemuxer; bool mDemuxerInitDone; void OnDemuxerInitDone(nsresult); - void OnDemuxerInitFailed(DemuxerFailureReason aFailure); + void OnDemuxerInitFailed(const MediaResult& aError); MozPromiseRequestHolder<MediaDataDemuxer::InitPromise> mDemuxerInitRequest; - void OnDemuxFailed(TrackType aTrack, DemuxerFailureReason aFailure); + void OnDemuxFailed(TrackType aTrack, const MediaResult& aError); void DoDemuxVideo(); void OnVideoDemuxCompleted(RefPtr<MediaTrackDemuxer::SamplesHolder> aSamples); - void OnVideoDemuxFailed(DemuxerFailureReason aFailure) + void OnVideoDemuxFailed(const MediaResult& aError) { - OnDemuxFailed(TrackType::kVideoTrack, aFailure); + OnDemuxFailed(TrackType::kVideoTrack, aError); } void DoDemuxAudio(); void OnAudioDemuxCompleted(RefPtr<MediaTrackDemuxer::SamplesHolder> aSamples); - void OnAudioDemuxFailed(DemuxerFailureReason aFailure) + void OnAudioDemuxFailed(const MediaResult& aError) { - OnDemuxFailed(TrackType::kAudioTrack, aFailure); + OnDemuxFailed(TrackType::kAudioTrack, aError); } void SkipVideoDemuxToNextKeyFrame(media::TimeUnit aTimeThreshold); @@ -550,15 +550,15 @@ private: } void ScheduleSeek(); void AttemptSeek(); - void OnSeekFailed(TrackType aTrack, DemuxerFailureReason aFailure); + void OnSeekFailed(TrackType aTrack, const MediaResult& aError); void DoVideoSeek(); void OnVideoSeekCompleted(media::TimeUnit aTime); - void OnVideoSeekFailed(DemuxerFailureReason aFailure); + void OnVideoSeekFailed(const MediaResult& aError); bool mSeekScheduled; void DoAudioSeek(); void OnAudioSeekCompleted(media::TimeUnit aTime); - void OnAudioSeekFailed(DemuxerFailureReason aFailure); + void OnAudioSeekFailed(const MediaResult& aError); // The SeekTarget that was last given to Seek() SeekTarget mOriginalSeekTarget; // Temporary seek information while we wait for the data diff --git a/dom/media/MediaResult.h b/dom/media/MediaResult.h new file mode 100644 index 0000000000000000000000000000000000000000..593f0a8c553cdb9d267d578ce4b190ef4e1c216e --- /dev/null +++ b/dom/media/MediaResult.h @@ -0,0 +1,60 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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/. */ + +#ifndef MediaResult_h_ +#define MediaResult_h_ + +#include "nsError.h" +#include "nsPrintfCString.h" + +// MediaResult can be used interchangeably with nsresult. +// It allows to store extra information such as where the error occurred. +// While nsresult is typically passed by value; due to its potential size, using +// MediaResult const references is recommended. +namespace mozilla { + +class MediaResult +{ +public: + MOZ_IMPLICIT MediaResult(nsresult aResult) + : mCode(aResult) + { + } + MediaResult(nsresult aResult, const nsACString& aMessage) + : mCode(aResult) + , mMessage(aMessage) + { + } + MediaResult(nsresult aResult, const char* aMessage) + : mCode(aResult) + , mMessage(aMessage) + { + } + MediaResult(const MediaResult& aOther) = default; + MediaResult(MediaResult&& aOther) = default; + MediaResult& operator=(const MediaResult& aOther) = default; + MediaResult& operator=(MediaResult&& aOther) = default; + + nsresult Code() const { return mCode; } + const nsCString& Message() const { return mMessage; } + + // Interoperations with nsresult. + bool operator==(nsresult aResult) const { return aResult == mCode; } + bool operator!=(nsresult aResult) const { return aResult != mCode; } + operator nsresult () const { return mCode; } + + nsCString Description() const + { + return nsPrintfCString("0x%08x: %s", mCode, mMessage.get()); + } + +private: + nsresult mCode; + nsCString mMessage; +}; + +} // namespace mozilla +#endif // MediaResult_h_ \ No newline at end of file diff --git a/dom/media/NextFrameSeekTask.cpp b/dom/media/NextFrameSeekTask.cpp index 3906493fe2bb7bed8c539cb18115d968c75602b5..d1257da727894f1c2a666051ad552eb5bec3a57f 100644 --- a/dom/media/NextFrameSeekTask.cpp +++ b/dom/media/NextFrameSeekTask.cpp @@ -53,7 +53,7 @@ NextFrameSeekTask::Discard() AssertOwnerThread(); // Disconnect MDSM. - RejectIfExist(__func__); + RejectIfExist(NS_ERROR_DOM_MEDIA_CANCELED, __func__); // Disconnect MediaDecoderReader. CancelCallbacks(); @@ -183,12 +183,12 @@ NextFrameSeekTask::OnAudioDecoded(MediaData* aAudioSample) } void -NextFrameSeekTask::OnAudioNotDecoded(MediaDecoderReader::NotDecodedReason aReason) +NextFrameSeekTask::OnAudioNotDecoded(const MediaResult& aError) { AssertOwnerThread(); MOZ_ASSERT(!mSeekTaskPromise.IsEmpty(), "Seek shouldn't be finished"); - SAMPLE_LOG("OnAudioNotDecoded (aReason=%u)", aReason); + SAMPLE_LOG("OnAudioNotDecoded (aError=%u)", aError.Code()); // We don't really handle audio deocde error here. Let MDSM to trigger further // audio decoding tasks if it needs to play audio, and MDSM will then receive @@ -224,37 +224,37 @@ NextFrameSeekTask::OnVideoDecoded(MediaData* aVideoSample) } void -NextFrameSeekTask::OnVideoNotDecoded(MediaDecoderReader::NotDecodedReason aReason) +NextFrameSeekTask::OnVideoNotDecoded(const MediaResult& aError) { AssertOwnerThread(); MOZ_ASSERT(!mSeekTaskPromise.IsEmpty(), "Seek shouldn't be finished"); - SAMPLE_LOG("OnVideoNotDecoded (aReason=%u)", aReason); + SAMPLE_LOG("OnVideoNotDecoded (aError=%u)", aError.Code()); - if (aReason == MediaDecoderReader::END_OF_STREAM) { + if (aError == NS_ERROR_DOM_MEDIA_END_OF_STREAM) { mIsVideoQueueFinished = true; } // Video seek not finished. if (NeedMoreVideo()) { - switch (aReason) { - case MediaDecoderReader::DECODE_ERROR: - // We might lose the audio sample after canceling the callbacks. - // However it doesn't really matter because MDSM is gonna shut down - // when seek fails. - CancelCallbacks(); - // Reject the promise since we can't finish video seek anyway. - RejectIfExist(__func__); - break; - case MediaDecoderReader::WAITING_FOR_DATA: + switch (aError.Code()) { + case NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA: mReader->WaitForData(MediaData::VIDEO_DATA); break; - case MediaDecoderReader::CANCELED: + case NS_ERROR_DOM_MEDIA_CANCELED: RequestVideoData(); break; - case MediaDecoderReader::END_OF_STREAM: + case NS_ERROR_DOM_MEDIA_END_OF_STREAM: MOZ_ASSERT(false, "Shouldn't want more data for ended video."); break; + default: + // We might lose the audio sample after canceling the callbacks. + // However it doesn't really matter because MDSM is gonna shut down + // when seek fails. + CancelCallbacks(); + // Reject the promise since we can't finish video seek anyway. + RejectIfExist(aError, __func__); + break; } return; } @@ -274,7 +274,7 @@ NextFrameSeekTask::SetCallbacks() if (aData.is<MediaData*>()) { OnAudioDecoded(aData.as<MediaData*>()); } else { - OnAudioNotDecoded(aData.as<MediaDecoderReader::NotDecodedReason>()); + OnAudioNotDecoded(aData.as<MediaResult>()); } }); @@ -284,7 +284,7 @@ NextFrameSeekTask::SetCallbacks() if (aData.is<Type>()) { OnVideoDecoded(Get<0>(aData.as<Type>())); } else { - OnVideoNotDecoded(aData.as<MediaDecoderReader::NotDecodedReason>()); + OnVideoNotDecoded(aData.as<MediaResult>()); } }); @@ -303,7 +303,7 @@ NextFrameSeekTask::SetCallbacks() } else { // Reject if we can't finish video seeking. CancelCallbacks(); - RejectIfExist(__func__); + RejectIfExist(NS_ERROR_DOM_MEDIA_CANCELED, __func__); } return; } diff --git a/dom/media/NextFrameSeekTask.h b/dom/media/NextFrameSeekTask.h index 2c07d980d0535c551109d34257b143e3d1651732..debd58c6668a2eed7384fa37932d362984eb5efc 100644 --- a/dom/media/NextFrameSeekTask.h +++ b/dom/media/NextFrameSeekTask.h @@ -57,11 +57,11 @@ private: void OnAudioDecoded(MediaData* aAudioSample); - void OnAudioNotDecoded(MediaDecoderReader::NotDecodedReason aReason); + void OnAudioNotDecoded(const MediaResult& aError); void OnVideoDecoded(MediaData* aVideoSample); - void OnVideoNotDecoded(MediaDecoderReader::NotDecodedReason aReason); + void OnVideoNotDecoded(const MediaResult& aError); void SetCallbacks(); diff --git a/dom/media/SeekTask.cpp b/dom/media/SeekTask.cpp index cafcc38f4dc936690fd21a6185a00b01d505ecc0..ee93033da1de0d75f24f5320bc59a1be8a2285cb 100644 --- a/dom/media/SeekTask.cpp +++ b/dom/media/SeekTask.cpp @@ -46,13 +46,14 @@ SeekTask::Resolve(const char* aCallSite) } void -SeekTask::RejectIfExist(const char* aCallSite) +SeekTask::RejectIfExist(const MediaResult& aError, const char* aCallSite) { AssertOwnerThread(); SeekTaskRejectValue val; val.mIsAudioQueueFinished = mIsAudioQueueFinished; val.mIsVideoQueueFinished = mIsVideoQueueFinished; + val.mError = aError; mSeekTaskPromise.RejectIfExists(val, aCallSite); } diff --git a/dom/media/SeekTask.h b/dom/media/SeekTask.h index 8e9500b357f51482233077244c1866de21b35358..e93e4bae37c112833e55262cafb43d60578c7d5f 100644 --- a/dom/media/SeekTask.h +++ b/dom/media/SeekTask.h @@ -8,6 +8,7 @@ #define SEEK_TASK_H #include "mozilla/MozPromise.h" +#include "MediaResult.h" #include "SeekTarget.h" namespace mozilla { @@ -30,8 +31,15 @@ struct SeekTaskResolveValue struct SeekTaskRejectValue { + SeekTaskRejectValue() + : mIsAudioQueueFinished(false) + , mIsVideoQueueFinished(false) + , mError(NS_ERROR_DOM_MEDIA_FATAL_ERR) + { + } bool mIsAudioQueueFinished; bool mIsVideoQueueFinished; + MediaResult mError; }; class SeekTask { @@ -62,7 +70,7 @@ protected: void Resolve(const char* aCallSite); - void RejectIfExist(const char* aCallSite); + void RejectIfExist(const MediaResult& aError, const char* aCallSite); void AssertOwnerThread() const; diff --git a/dom/media/eme/MediaKeys.cpp b/dom/media/eme/MediaKeys.cpp index 37bf89a10fed72c8d91bc488402d26a0179ca61e..e136850907da97af35f763b1f0ddc952f5e65a66 100644 --- a/dom/media/eme/MediaKeys.cpp +++ b/dom/media/eme/MediaKeys.cpp @@ -88,7 +88,7 @@ MediaKeys::Terminated() // Notify the element about that CDM has terminated. if (mElement) { - mElement->DecodeError(); + mElement->DecodeError(NS_ERROR_DOM_MEDIA_CDM_ERR); } Shutdown(); diff --git a/dom/media/flac/FlacDemuxer.cpp b/dom/media/flac/FlacDemuxer.cpp index 6f84ad39da26883985142689ba889f864f10e8b4..63b1f6c0553da53dd39a7972f8073da409dd9689 100644 --- a/dom/media/flac/FlacDemuxer.cpp +++ b/dom/media/flac/FlacDemuxer.cpp @@ -584,7 +584,7 @@ FlacDemuxer::Init() LOG("Init() failure: waiting for data"); return InitPromise::CreateAndReject( - DemuxerFailureReason::DEMUXER_ERROR, __func__); + NS_ERROR_DOM_MEDIA_DEMUXER_ERR, __func__); } LOG("Init() successful"); @@ -855,7 +855,7 @@ FlacTrackDemuxer::GetSamples(int32_t aNumSamples) if (!aNumSamples) { return SamplesPromise::CreateAndReject( - DemuxerFailureReason::DEMUXER_ERROR, __func__); + NS_ERROR_DOM_MEDIA_DEMUXER_ERR, __func__); } RefPtr<SamplesHolder> frames = new SamplesHolder(); @@ -875,7 +875,7 @@ FlacTrackDemuxer::GetSamples(int32_t aNumSamples) if (frames->mSamples.IsEmpty()) { return SamplesPromise::CreateAndReject( - DemuxerFailureReason::END_OF_STREAM, __func__); + NS_ERROR_DOM_MEDIA_END_OF_STREAM, __func__); } return SamplesPromise::CreateAndResolve(frames, __func__); @@ -899,7 +899,7 @@ FlacTrackDemuxer::SkipToNextRandomAccessPoint(TimeUnit aTimeThreshold) { // Will not be called for audio-only resources. return SkipAccessPointPromise::CreateAndReject( - SkipFailureHolder(DemuxerFailureReason::DEMUXER_ERROR, 0), __func__); + SkipFailureHolder(NS_ERROR_DOM_MEDIA_DEMUXER_ERR, 0), __func__); } int64_t diff --git a/dom/media/fmp4/MP4Decoder.cpp b/dom/media/fmp4/MP4Decoder.cpp index adcaabdc1b21a4f34b847044d53a908681689f87..c617ccfec91baeed604fe1edc77c886be8d669e4 100644 --- a/dom/media/fmp4/MP4Decoder.cpp +++ b/dom/media/fmp4/MP4Decoder.cpp @@ -280,7 +280,7 @@ MP4Decoder::IsVideoAccelerated(layers::LayersBackend aBackend, nsIGlobalObject* taskQueue->AwaitShutdownAndIdle(); promise->MaybeResolve(result); }, - [promise, decoder, taskQueue] (MediaDataDecoder::DecoderFailureReason aResult) { + [promise, decoder, taskQueue] (MediaResult aError) { decoder->Shutdown(); taskQueue->BeginShutdown(); taskQueue->AwaitShutdownAndIdle(); diff --git a/dom/media/fmp4/MP4Demuxer.cpp b/dom/media/fmp4/MP4Demuxer.cpp index 48a9968aa294f268c8a8a2d430da45f9e09e2b7b..4548258de03151b2192a5c96c6540e98a806671c 100644 --- a/dom/media/fmp4/MP4Demuxer.cpp +++ b/dom/media/fmp4/MP4Demuxer.cpp @@ -125,13 +125,13 @@ MP4Demuxer::Init() // Check that we have enough data to read the metadata. if (!mp4_demuxer::MP4Metadata::HasCompleteMetadata(stream)) { - return InitPromise::CreateAndReject(DemuxerFailureReason::DEMUXER_ERROR, __func__); + return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_DEMUXER_ERR, __func__); } mInitData = mp4_demuxer::MP4Metadata::Metadata(stream); if (!mInitData) { // OOM - return InitPromise::CreateAndReject(DemuxerFailureReason::DEMUXER_ERROR, __func__); + return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_DEMUXER_ERR, __func__); } RefPtr<mp4_demuxer::BufferStream> bufferstream = @@ -141,7 +141,7 @@ MP4Demuxer::Init() if (!mMetadata->GetNumberTracks(mozilla::TrackInfo::kAudioTrack) && !mMetadata->GetNumberTracks(mozilla::TrackInfo::kVideoTrack)) { - return InitPromise::CreateAndReject(DemuxerFailureReason::DEMUXER_ERROR, __func__); + return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_DEMUXER_ERR, __func__); } return InitPromise::CreateAndResolve(NS_OK, __func__); @@ -298,7 +298,7 @@ MP4TrackDemuxer::Seek(media::TimeUnit aTime) do { sample = GetNextSample(); if (!sample) { - return SeekPromise::CreateAndReject(DemuxerFailureReason::END_OF_STREAM, __func__); + return SeekPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_END_OF_STREAM, __func__); } if (!sample->Size()) { // This sample can't be decoded, continue searching. @@ -370,7 +370,7 @@ MP4TrackDemuxer::GetSamples(int32_t aNumSamples) EnsureUpToDateIndex(); RefPtr<SamplesHolder> samples = new SamplesHolder; if (!aNumSamples) { - return SamplesPromise::CreateAndReject(DemuxerFailureReason::DEMUXER_ERROR, __func__); + return SamplesPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_DEMUXER_ERR, __func__); } if (mQueuedSample) { @@ -390,7 +390,7 @@ MP4TrackDemuxer::GetSamples(int32_t aNumSamples) } if (samples->mSamples.IsEmpty()) { - return SamplesPromise::CreateAndReject(DemuxerFailureReason::END_OF_STREAM, __func__); + return SamplesPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_END_OF_STREAM, __func__); } else { for (const auto& sample : samples->mSamples) { // Collect telemetry from h264 Annex B SPS. @@ -461,7 +461,7 @@ MP4TrackDemuxer::SkipToNextRandomAccessPoint(media::TimeUnit aTimeThreshold) if (found) { return SkipAccessPointPromise::CreateAndResolve(parsed, __func__); } else { - SkipFailureHolder failure(DemuxerFailureReason::END_OF_STREAM, parsed); + SkipFailureHolder failure(NS_ERROR_DOM_MEDIA_END_OF_STREAM, parsed); return SkipAccessPointPromise::CreateAndReject(Move(failure), __func__); } } diff --git a/dom/media/gtest/MockMediaDecoderOwner.h b/dom/media/gtest/MockMediaDecoderOwner.h index 9990963de566ba956736468a7c95920509d0cf2e..81acc20a26bf87e36d853a1c117de45c3ece2215 100644 --- a/dom/media/gtest/MockMediaDecoderOwner.h +++ b/dom/media/gtest/MockMediaDecoderOwner.h @@ -25,7 +25,7 @@ public: { } void NetworkError() override {} - void DecodeError() override {} + void DecodeError(const MediaResult& aError) override {} bool HasError() const override { return false; } void LoadAborted() override {} void PlaybackEnded() override {} diff --git a/dom/media/gtest/TestMP4Demuxer.cpp b/dom/media/gtest/TestMP4Demuxer.cpp index 7fbcf940391f88c51633656eeb4dedfefac22ab8..fcfb4eacfd4342c98c49202470afa19f9cbd285e 100644 --- a/dom/media/gtest/TestMP4Demuxer.cpp +++ b/dom/media/gtest/TestMP4Demuxer.cpp @@ -119,11 +119,8 @@ public: binding->CheckTrackSamples(track); } }, - [binding] (DemuxerFailureReason aReason) { - if (aReason == DemuxerFailureReason::DEMUXER_ERROR) { - EXPECT_TRUE(false); - binding->mCheckTrackSamples.Reject(NS_ERROR_FAILURE, __func__); - } else if (aReason == DemuxerFailureReason::END_OF_STREAM) { + [binding] (const MediaResult& aError) { + if (aError == NS_ERROR_DOM_MEDIA_END_OF_STREAM) { EXPECT_TRUE(binding->mSamples.Length() > 1); for (uint32_t i = 0; i < (binding->mSamples.Length() - 1); i++) { EXPECT_LT(binding->mSamples[i]->mTimecode, binding->mSamples[i + 1]->mTimecode); @@ -132,6 +129,9 @@ public: } } binding->mCheckTrackSamples.Resolve(true, __func__); + } else { + EXPECT_TRUE(false); + binding->mCheckTrackSamples.Reject(aError, __func__); } } ); diff --git a/dom/media/gtest/TestMediaFormatReader.cpp b/dom/media/gtest/TestMediaFormatReader.cpp index b4325fa45b1d22487a738ff38cdaadb7dff73ca7..ad222e8b807405b7f6fa09edffc345e40ccd74e4 100644 --- a/dom/media/gtest/TestMediaFormatReader.cpp +++ b/dom/media/gtest/TestMediaFormatReader.cpp @@ -88,7 +88,7 @@ public: &MediaFormatReaderBinding::OnNotDemuxed); } - void OnMetadataNotRead(ReadMetadataFailureReason aReason) { + void OnMetadataNotRead(const MediaResult& aError) { EXPECT_TRUE(false); ReaderShutdown(); } @@ -107,7 +107,7 @@ public: ReaderShutdown(); } - void OnNotDemuxed(MediaDecoderReader::NotDecodedReason aReason) + void OnNotDemuxed(const MediaResult& aReason) { EXPECT_TRUE(false); ReaderShutdown(); diff --git a/dom/media/mediasource/MediaSource.cpp b/dom/media/mediasource/MediaSource.cpp index 7fd66b43d7abdeb80aef5d9e5fd9195bc22d0be3..85a0850fe94d1c74f83f9db1cb7df3d8e23c3d92 100644 --- a/dom/media/mediasource/MediaSource.cpp +++ b/dom/media/mediasource/MediaSource.cpp @@ -10,6 +10,7 @@ #include "DecoderTraits.h" #include "Benchmark.h" #include "DecoderDoctorDiagnostics.h" +#include "MediaResult.h" #include "MediaSourceUtils.h" #include "SourceBuffer.h" #include "SourceBufferList.h" @@ -330,13 +331,24 @@ MediaSource::EndOfStream(const Optional<MediaSourceEndOfStreamError>& aError, Er mDecoder->NetworkError(); break; case MediaSourceEndOfStreamError::Decode: - mDecoder->DecodeError(); + mDecoder->DecodeError(NS_ERROR_DOM_MEDIA_FATAL_ERR); break; default: aRv.Throw(NS_ERROR_DOM_TYPE_ERR); } } +void +MediaSource::EndOfStream(const MediaResult& aError) +{ + MOZ_ASSERT(NS_IsMainThread()); + MSE_API("EndOfStream(aError=%d)", aError.Code()); + + SetReadyState(MediaSourceReadyState::Ended); + mSourceBuffers->Ended(); + mDecoder->DecodeError(aError); +} + /* static */ bool MediaSource::IsTypeSupported(const GlobalObject& aOwner, const nsAString& aType) { diff --git a/dom/media/mediasource/MediaSource.h b/dom/media/mediasource/MediaSource.h index 867e955f90eaeffd3111735703263b11823ccafa..0d2dc0588866159393fffec33f939cdeb9a3a6a6 100644 --- a/dom/media/mediasource/MediaSource.h +++ b/dom/media/mediasource/MediaSource.h @@ -29,6 +29,7 @@ namespace mozilla { class ErrorResult; template <typename T> class AsyncEventRunner; +class MediaResult; namespace dom { @@ -60,6 +61,7 @@ public: void RemoveSourceBuffer(SourceBuffer& aSourceBuffer, ErrorResult& aRv); void EndOfStream(const Optional<MediaSourceEndOfStreamError>& aError, ErrorResult& aRv); + void EndOfStream(const MediaResult& aError); void SetLiveSeekableRange(double aStart, double aEnd, ErrorResult& aRv); void ClearLiveSeekableRange(ErrorResult& aRv); diff --git a/dom/media/mediasource/MediaSourceDemuxer.cpp b/dom/media/mediasource/MediaSourceDemuxer.cpp index 241a9aaa854bc023f822d738f9417cbfdf1de2a7..385f1bc2a36b6743a62a6623a4912b2e53c891d5 100644 --- a/dom/media/mediasource/MediaSourceDemuxer.cpp +++ b/dom/media/mediasource/MediaSourceDemuxer.cpp @@ -247,7 +247,7 @@ MediaSourceDemuxer::GetManager(TrackType aTrack) MediaSourceDemuxer::~MediaSourceDemuxer() { - mInitPromise.RejectIfExists(DemuxerFailureReason::SHUTDOWN, __func__); + mInitPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__); } void @@ -379,8 +379,6 @@ MediaSourceTrackDemuxer::BreakCycles() RefPtr<MediaSourceTrackDemuxer::SeekPromise> MediaSourceTrackDemuxer::DoSeek(media::TimeUnit aTime) { - typedef TrackBuffersManager::GetSampleResult Result; - TimeIntervals buffered = mManager->Buffered(mType); // Fuzz factor represents a +/- threshold. So when seeking it allows the gap // to be twice as big as the fuzz value. We only want to allow EOS_FUZZ gap. @@ -397,7 +395,7 @@ MediaSourceTrackDemuxer::DoSeek(media::TimeUnit aTime) if (!buffered.ContainsWithStrictEnd(seekTime)) { if (!buffered.ContainsWithStrictEnd(aTime)) { // We don't have the data to seek to. - return SeekPromise::CreateAndReject(DemuxerFailureReason::WAITING_FOR_DATA, + return SeekPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA, __func__); } // Theoretically we should reject the promise with WAITING_FOR_DATA, @@ -410,12 +408,12 @@ MediaSourceTrackDemuxer::DoSeek(media::TimeUnit aTime) seekTime = buffered[index].mStart; } seekTime = mManager->Seek(mType, seekTime, MediaSourceDemuxer::EOS_FUZZ); - Result result; + MediaResult result = NS_OK; RefPtr<MediaRawData> sample = mManager->GetSample(mType, media::TimeUnit(), result); - MOZ_ASSERT(result != Result::ERROR && sample); + MOZ_ASSERT(NS_SUCCEEDED(result) && sample); mNextSample = Some(sample); mReset = false; { @@ -429,8 +427,6 @@ MediaSourceTrackDemuxer::DoSeek(media::TimeUnit aTime) RefPtr<MediaSourceTrackDemuxer::SamplesPromise> MediaSourceTrackDemuxer::DoGetSamples(int32_t aNumSamples) { - typedef TrackBuffersManager::GetSampleResult Result; - if (mReset) { // If a seek (or reset) was recently performed, we ensure that the data // we are about to retrieve is still available. @@ -438,11 +434,11 @@ MediaSourceTrackDemuxer::DoGetSamples(int32_t aNumSamples) buffered.SetFuzz(MediaSourceDemuxer::EOS_FUZZ / 2); if (!buffered.Length() && mManager->IsEnded()) { - return SamplesPromise::CreateAndReject(DemuxerFailureReason::END_OF_STREAM, + return SamplesPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_END_OF_STREAM, __func__); } if (!buffered.ContainsWithStrictEnd(TimeUnit::FromMicroseconds(0))) { - return SamplesPromise::CreateAndReject(DemuxerFailureReason::WAITING_FOR_DATA, + return SamplesPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA, __func__); } mReset = false; @@ -452,16 +448,17 @@ MediaSourceTrackDemuxer::DoGetSamples(int32_t aNumSamples) sample = mNextSample.ref(); mNextSample.reset(); } else { - Result result; + MediaResult result = NS_OK; sample = mManager->GetSample(mType, MediaSourceDemuxer::EOS_FUZZ, result); if (!sample) { - if (result == Result::ERROR) { - return SamplesPromise::CreateAndReject(DemuxerFailureReason::DEMUXER_ERROR, __func__); + if (result == NS_ERROR_DOM_MEDIA_END_OF_STREAM || + result == NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA) { + return SamplesPromise::CreateAndReject( + (result == NS_ERROR_DOM_MEDIA_END_OF_STREAM && mManager->IsEnded()) + ? NS_ERROR_DOM_MEDIA_END_OF_STREAM + : NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA, __func__); } - return SamplesPromise::CreateAndReject( - (result == Result::EOS && mManager->IsEnded()) - ? DemuxerFailureReason::END_OF_STREAM - : DemuxerFailureReason::WAITING_FOR_DATA, __func__); + return SamplesPromise::CreateAndReject(result, __func__); } } RefPtr<SamplesHolder> samples = new SamplesHolder; @@ -492,8 +489,8 @@ MediaSourceTrackDemuxer::DoSkipToNextRandomAccessPoint(media::TimeUnit aTimeThre } } SkipFailureHolder holder( - mManager->IsEnded() ? DemuxerFailureReason::END_OF_STREAM : - DemuxerFailureReason::WAITING_FOR_DATA, parsed); + mManager->IsEnded() ? NS_ERROR_DOM_MEDIA_END_OF_STREAM : + NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA, parsed); return SkipAccessPointPromise::CreateAndReject(holder, __func__); } diff --git a/dom/media/mediasource/MediaSourceDemuxer.h b/dom/media/mediasource/MediaSourceDemuxer.h index 529a64c8e62a9604af28d4404ade1eb129fb8971..02c91e3bc4fb1f675ef080e57dc1311a39205284 100644 --- a/dom/media/mediasource/MediaSourceDemuxer.h +++ b/dom/media/mediasource/MediaSourceDemuxer.h @@ -20,6 +20,7 @@ namespace mozilla { +class MediaResult; class MediaSourceTrackDemuxer; class MediaSourceDemuxer : public MediaDataDemuxer @@ -119,7 +120,7 @@ private: RefPtr<SeekPromise> DoSeek(media::TimeUnit aTime); RefPtr<SamplesPromise> DoGetSamples(int32_t aNumSamples); RefPtr<SkipAccessPointPromise> DoSkipToNextRandomAccessPoint(media::TimeUnit aTimeThreadshold); - already_AddRefed<MediaRawData> GetSample(DemuxerFailureReason& aFailure); + already_AddRefed<MediaRawData> GetSample(MediaResult& aError); // Return the timestamp of the next keyframe after mLastSampleIndex. media::TimeUnit GetNextRandomAccessPoint(); diff --git a/dom/media/mediasource/SourceBuffer.cpp b/dom/media/mediasource/SourceBuffer.cpp index 8ed787fe40643389b3cc3801ca9b206011561424..6412f478892e2152deae03fac2e08a6cafde7410 100644 --- a/dom/media/mediasource/SourceBuffer.cpp +++ b/dom/media/mediasource/SourceBuffer.cpp @@ -445,24 +445,24 @@ SourceBuffer::AppendDataCompletedWithSuccess(SourceBufferTask::AppendBufferResul } void -SourceBuffer::AppendDataErrored(nsresult aError) +SourceBuffer::AppendDataErrored(const MediaResult& aError) { MOZ_ASSERT(mUpdating); mPendingAppend.Complete(); - switch (aError) { - case NS_ERROR_ABORT: + switch (aError.Code()) { + case NS_ERROR_DOM_MEDIA_CANCELED: // Nothing further to do as the trackbuffer has been shutdown. // or append was aborted and abort() has handled all the events. break; default: - AppendError(true); + AppendError(aError); break; } } void -SourceBuffer::AppendError(bool aDecoderError) +SourceBuffer::AppendError(const MediaResult& aDecodeError) { MOZ_ASSERT(NS_IsMainThread()); @@ -473,12 +473,9 @@ SourceBuffer::AppendError(bool aDecoderError) QueueAsyncSimpleEvent("error"); QueueAsyncSimpleEvent("updateend"); - if (aDecoderError) { - Optional<MediaSourceEndOfStreamError> decodeError( - MediaSourceEndOfStreamError::Decode); - ErrorResult dummy; - mMediaSource->EndOfStream(decodeError, dummy); - } + MOZ_ASSERT(NS_FAILED(aDecodeError)); + + mMediaSource->EndOfStream(aDecodeError); } already_AddRefed<MediaByteBuffer> diff --git a/dom/media/mediasource/SourceBuffer.h b/dom/media/mediasource/SourceBuffer.h index 2567e53942ed23301282d8749e3ee463f12edf41..440e8f60e5e6c943c9ba5decab47ebc7f6ad9f80 100644 --- a/dom/media/mediasource/SourceBuffer.h +++ b/dom/media/mediasource/SourceBuffer.h @@ -155,7 +155,7 @@ private: // Will call endOfStream() with "decode" error if aDecodeError is true. // 3.5.3 Append Error Algorithm // http://w3c.github.io/media-source/#sourcebuffer-append-error - void AppendError(bool aDecoderError); + void AppendError(const MediaResult& aDecodeError); // Implements the "Prepare Append Algorithm". Returns MediaByteBuffer object // on success or nullptr (with aRv set) on error. @@ -164,7 +164,7 @@ private: ErrorResult& aRv); void AppendDataCompletedWithSuccess(SourceBufferTask::AppendBufferResult aResult); - void AppendDataErrored(nsresult aError); + void AppendDataErrored(const MediaResult& aError); RefPtr<MediaSource> mMediaSource; diff --git a/dom/media/mediasource/SourceBufferTask.h b/dom/media/mediasource/SourceBufferTask.h index 2ba8ea0ef627693a36211371705735ebe2b1a750..868b608590f5327247fa86fb7df536fb72493f59 100644 --- a/dom/media/mediasource/SourceBufferTask.h +++ b/dom/media/mediasource/SourceBufferTask.h @@ -12,6 +12,7 @@ #include "mozilla/RefPtr.h" #include "SourceBufferAttributes.h" #include "TimeUnits.h" +#include "MediaResult.h" namespace mozilla { @@ -28,7 +29,7 @@ public: }; typedef Pair<bool, SourceBufferAttributes> AppendBufferResult; - typedef MozPromise<AppendBufferResult, nsresult, /* IsExclusive = */ true> AppendPromise; + typedef MozPromise<AppendBufferResult, MediaResult, /* IsExclusive = */ true> AppendPromise; typedef MozPromise<bool, nsresult, /* IsExclusive = */ true> RangeRemovalPromise; virtual Type GetType() const = 0; diff --git a/dom/media/mediasource/TrackBuffersManager.cpp b/dom/media/mediasource/TrackBuffersManager.cpp index 4ab95e3e2625f46502f22743abf33f5015f2ad03..219a279a13e5a36bbf710551f163e8d10783356f 100644 --- a/dom/media/mediasource/TrackBuffersManager.cpp +++ b/dom/media/mediasource/TrackBuffersManager.cpp @@ -287,17 +287,21 @@ TrackBuffersManager::EvictData(const TimeUnit& aPlaybackTime, int64_t aSize) return EvictDataResult::CANT_EVICT; } + EvictDataResult result; + if (mBufferFull && mEvictionState == EvictionState::EVICTION_COMPLETED) { - return EvictDataResult::BUFFER_FULL; + // Our buffer is currently full. We will make another eviction attempt. + // However, the current appendBuffer will fail as we can't know ahead of + // time if the eviction will later succeed. + result = EvictDataResult::BUFFER_FULL; + } else { + mEvictionState = EvictionState::EVICTION_NEEDED; + result = EvictDataResult::NO_DATA_EVICTED; } - - MSE_DEBUG("Reaching our size limit, schedule eviction of %lld bytes", toEvict); - - mEvictionState = EvictionState::EVICTION_NEEDED; - + MSE_DEBUG("Reached our size limit, schedule eviction of %lld bytes", toEvict); QueueTask(new EvictDataTask(aPlaybackTime, toEvict)); - return EvictDataResult::NO_DATA_EVICTED; + return result; } TimeIntervals @@ -717,7 +721,7 @@ TrackBuffersManager::SegmentParserLoop() self->ScheduleSegmentParserLoop(); } }, - [self] (nsresult aRejectValue) { + [self] (const MediaResult& aRejectValue) { self->mProcessingRequest.Complete(); self->RejectAppend(aRejectValue, __func__); })); @@ -743,9 +747,9 @@ TrackBuffersManager::NeedMoreData() } void -TrackBuffersManager::RejectAppend(nsresult aRejectValue, const char* aName) +TrackBuffersManager::RejectAppend(const MediaResult& aRejectValue, const char* aName) { - MSE_DEBUG("rv=%d", aRejectValue); + MSE_DEBUG("rv=%u", aRejectValue.Code()); MOZ_DIAGNOSTIC_ASSERT(mCurrentTask && mCurrentTask->GetType() == SourceBufferTask::Type::AppendBuffer); mCurrentTask->As<AppendBufferTask>()->mPromise.Reject(aRejectValue, __func__); @@ -1095,12 +1099,12 @@ TrackBuffersManager::OnDemuxerInitDone(nsresult) } void -TrackBuffersManager::OnDemuxerInitFailed(DemuxerFailureReason aFailure) +TrackBuffersManager::OnDemuxerInitFailed(const MediaResult& aError) { - MOZ_ASSERT(aFailure != DemuxerFailureReason::WAITING_FOR_DATA); + MOZ_ASSERT(aError != NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA); mDemuxerInitRequest.Complete(); - RejectAppend(NS_ERROR_FAILURE, __func__); + RejectAppend(aError, __func__); } RefPtr<TrackBuffersManager::CodedFrameProcessingPromise> @@ -1149,29 +1153,22 @@ TrackBuffersManager::CodedFrameProcessing() void TrackBuffersManager::OnDemuxFailed(TrackType aTrack, - DemuxerFailureReason aFailure) + const MediaResult& aError) { MOZ_ASSERT(OnTaskQueue()); - MSE_DEBUG("Failed to demux %s, failure:%d", - aTrack == TrackType::kVideoTrack ? "video" : "audio", aFailure); - switch (aFailure) { - case DemuxerFailureReason::END_OF_STREAM: - case DemuxerFailureReason::WAITING_FOR_DATA: + MSE_DEBUG("Failed to demux %s, failure:%u", + aTrack == TrackType::kVideoTrack ? "video" : "audio", aError.Code()); + switch (aError.Code()) { + case NS_ERROR_DOM_MEDIA_END_OF_STREAM: + case NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA: if (aTrack == TrackType::kVideoTrack) { DoDemuxAudio(); } else { CompleteCodedFrameProcessing(); } break; - case DemuxerFailureReason::DEMUXER_ERROR: - RejectProcessing(NS_ERROR_FAILURE, __func__); - break; - case DemuxerFailureReason::CANCELED: - case DemuxerFailureReason::SHUTDOWN: - RejectProcessing(NS_ERROR_ABORT, __func__); - break; default: - MOZ_ASSERT(false); + RejectProcessing(aError, __func__); break; } } @@ -1323,7 +1320,7 @@ TrackBuffersManager::CompleteCodedFrameProcessing() } void -TrackBuffersManager::RejectProcessing(nsresult aRejectValue, const char* aName) +TrackBuffersManager::RejectProcessing(const MediaResult& aRejectValue, const char* aName) { mProcessingPromise.RejectIfExists(aRejectValue, __func__); } @@ -2157,16 +2154,16 @@ TrackBuffersManager::GetSample(TrackInfo::TrackType aTrack, already_AddRefed<MediaRawData> TrackBuffersManager::GetSample(TrackInfo::TrackType aTrack, const TimeUnit& aFuzz, - GetSampleResult& aResult) + MediaResult& aResult) { MOZ_ASSERT(OnTaskQueue()); auto& trackData = GetTracksData(aTrack); const TrackBuffer& track = GetTrackBuffer(aTrack); - aResult = GetSampleResult::WAITING_FOR_DATA; + aResult = NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA; if (!track.Length()) { - aResult = GetSampleResult::EOS; + aResult = NS_ERROR_DOM_MEDIA_END_OF_STREAM; return nullptr; } @@ -2178,7 +2175,7 @@ TrackBuffersManager::GetSample(TrackInfo::TrackType aTrack, if (trackData.mNextGetSampleIndex.isSome()) { if (trackData.mNextGetSampleIndex.ref() >= track.Length()) { - aResult = GetSampleResult::EOS; + aResult = NS_ERROR_DOM_MEDIA_END_OF_STREAM; return nullptr; } const MediaRawData* sample = @@ -2193,7 +2190,7 @@ TrackBuffersManager::GetSample(TrackInfo::TrackType aTrack, RefPtr<MediaRawData> p = sample->Clone(); if (!p) { - aResult = GetSampleResult::ERROR; + aResult = MediaResult(NS_ERROR_OUT_OF_MEMORY, __func__); return nullptr; } trackData.mNextGetSampleIndex.ref()++; @@ -2219,7 +2216,7 @@ TrackBuffersManager::GetSample(TrackInfo::TrackType aTrack, trackData.mNextSampleTimecode = nextSampleTimecode; trackData.mNextSampleTime = nextSampleTime; } - aResult = GetSampleResult::NO_ERROR; + aResult = NS_OK; return p.forget(); } @@ -2227,7 +2224,7 @@ TrackBuffersManager::GetSample(TrackInfo::TrackType aTrack, track.LastElement()->mTimecode + track.LastElement()->mDuration) { // The next element is past our last sample. We're done. trackData.mNextGetSampleIndex = Some(uint32_t(track.Length())); - aResult = GetSampleResult::EOS; + aResult = NS_ERROR_DOM_MEDIA_END_OF_STREAM; return nullptr; } @@ -2244,7 +2241,7 @@ TrackBuffersManager::GetSample(TrackInfo::TrackType aTrack, RefPtr<MediaRawData> p = sample->Clone(); if (!p) { // OOM - aResult = GetSampleResult::ERROR; + aResult = MediaResult(NS_ERROR_OUT_OF_MEMORY, __func__); return nullptr; } trackData.mNextGetSampleIndex = Some(uint32_t(pos)+1); @@ -2252,7 +2249,7 @@ TrackBuffersManager::GetSample(TrackInfo::TrackType aTrack, TimeUnit::FromMicroseconds(sample->mTimecode + sample->mDuration); trackData.mNextSampleTime = TimeUnit::FromMicroseconds(sample->GetEndTime()); - aResult = GetSampleResult::NO_ERROR; + aResult = NS_OK; return p.forget(); } diff --git a/dom/media/mediasource/TrackBuffersManager.h b/dom/media/mediasource/TrackBuffersManager.h index ffea285a6289ab2182de0446e0284a442fa2ab2c..0bec46a47052f54d46e8e9b65937600e78f51ecc 100644 --- a/dom/media/mediasource/TrackBuffersManager.h +++ b/dom/media/mediasource/TrackBuffersManager.h @@ -15,6 +15,7 @@ #include "MediaData.h" #include "MediaDataDemuxer.h" +#include "MediaResult.h" #include "MediaSourceDecoder.h" #include "SourceBufferTask.h" #include "TimeUnits.h" @@ -154,17 +155,9 @@ public: const media::TimeUnit& aFuzz, bool& aFound); - enum class GetSampleResult - { - NO_ERROR, - ERROR, - WAITING_FOR_DATA, - EOS - }; - already_AddRefed<MediaRawData> GetSample(TrackInfo::TrackType aTrack, const media::TimeUnit& aFuzz, - GetSampleResult& aResult); + MediaResult& aResult); int32_t FindCurrentPosition(TrackInfo::TrackType aTrack, const media::TimeUnit& aFuzz); media::TimeUnit GetNextRandomAccessPoint(TrackInfo::TrackType aTrack, @@ -173,7 +166,7 @@ public: void AddSizeOfResources(MediaSourceDecoder::ResourceSizes* aSizes); private: - typedef MozPromise<bool, nsresult, /* IsExclusive = */ true> CodedFrameProcessingPromise; + typedef MozPromise<bool, MediaResult, /* IsExclusive = */ true> CodedFrameProcessingPromise; // for MediaSourceDemuxer::GetMozDebugReaderData friend class MediaSourceDemuxer; @@ -188,7 +181,7 @@ private: void CreateDemuxerforMIMEType(); void ResetDemuxingState(); void NeedMoreData(); - void RejectAppend(nsresult aRejectValue, const char* aName); + void RejectAppend(const MediaResult& aRejectValue, const char* aName); // Will return a promise that will be resolved once all frames of the current // media segment have been processed. RefPtr<CodedFrameProcessingPromise> CodedFrameProcessing(); @@ -243,25 +236,25 @@ private: Maybe<media::TimeUnit> mLastParsedEndTime; void OnDemuxerInitDone(nsresult); - void OnDemuxerInitFailed(DemuxerFailureReason aFailure); + void OnDemuxerInitFailed(const MediaResult& aFailure); void OnDemuxerResetDone(nsresult); MozPromiseRequestHolder<MediaDataDemuxer::InitPromise> mDemuxerInitRequest; bool mEncrypted; - void OnDemuxFailed(TrackType aTrack, DemuxerFailureReason aFailure); + void OnDemuxFailed(TrackType aTrack, const MediaResult& aError); void DoDemuxVideo(); void OnVideoDemuxCompleted(RefPtr<MediaTrackDemuxer::SamplesHolder> aSamples); - void OnVideoDemuxFailed(DemuxerFailureReason aFailure) + void OnVideoDemuxFailed(const MediaResult& aError) { mVideoTracks.mDemuxRequest.Complete(); - OnDemuxFailed(TrackType::kVideoTrack, aFailure); + OnDemuxFailed(TrackType::kVideoTrack, aError); } void DoDemuxAudio(); void OnAudioDemuxCompleted(RefPtr<MediaTrackDemuxer::SamplesHolder> aSamples); - void OnAudioDemuxFailed(DemuxerFailureReason aFailure) + void OnAudioDemuxFailed(const MediaResult& aError) { mAudioTracks.mDemuxRequest.Complete(); - OnDemuxFailed(TrackType::kAudioTrack, aFailure); + OnDemuxFailed(TrackType::kAudioTrack, aError); } void DoEvictData(const media::TimeUnit& aPlaybackTime, int64_t aSizeToEvict); @@ -379,7 +372,7 @@ private: const media::TimeUnit& aExpectedPts, const media::TimeUnit& aFuzz); void UpdateBufferedRanges(); - void RejectProcessing(nsresult aRejectValue, const char* aName); + void RejectProcessing(const MediaResult& aRejectValue, const char* aName); void ResolveProcessing(bool aResolveValue, const char* aName); MozPromiseRequestHolder<CodedFrameProcessingPromise> mProcessingRequest; MozPromiseHolder<CodedFrameProcessingPromise> mProcessingPromise; diff --git a/dom/media/moz.build b/dom/media/moz.build index 52eee2e8d0dabd8731d6322e163f71c58f49db0b..626ed72f7362a1a9071122932d4c9c3691754a10 100644 --- a/dom/media/moz.build +++ b/dom/media/moz.build @@ -121,6 +121,7 @@ EXPORTS += [ 'MediaRecorder.h', 'MediaResource.h', 'MediaResourceCallback.h', + 'MediaResult.h', 'MediaSegment.h', 'MediaStatistics.h', 'MediaStreamGraph.h', diff --git a/dom/media/ogg/OggDemuxer.cpp b/dom/media/ogg/OggDemuxer.cpp index 0afcb8ec432e6c86e76db37e42454254e3ecc486..a1608360c2ceab8a31e6da433a7922068b4c8d92 100644 --- a/dom/media/ogg/OggDemuxer.cpp +++ b/dom/media/ogg/OggDemuxer.cpp @@ -215,24 +215,19 @@ OggDemuxer::Init() { int ret = ogg_sync_init(OggSyncState(TrackInfo::kAudioTrack)); if (ret != 0) { - return InitPromise::CreateAndReject(DemuxerFailureReason::DEMUXER_ERROR, __func__); + return InitPromise::CreateAndReject(NS_ERROR_OUT_OF_MEMORY, __func__); } ret = ogg_sync_init(OggSyncState(TrackInfo::kVideoTrack)); if (ret != 0) { - return InitPromise::CreateAndReject(DemuxerFailureReason::DEMUXER_ERROR, __func__); + return InitPromise::CreateAndReject(NS_ERROR_OUT_OF_MEMORY, __func__); } - /* - if (InitBufferedState() != NS_OK) { - return InitPromise::CreateAndReject(DemuxerFailureReason::WAITING_FOR_DATA, __func__); - } - */ if (ReadMetadata() != NS_OK) { - return InitPromise::CreateAndReject(DemuxerFailureReason::DEMUXER_ERROR, __func__); + return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_METADATA_ERR, __func__); } if (!GetNumberTracks(TrackInfo::kAudioTrack) && !GetNumberTracks(TrackInfo::kVideoTrack)) { - return InitPromise::CreateAndReject(DemuxerFailureReason::DEMUXER_ERROR, __func__); + return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_METADATA_ERR, __func__); } return InitPromise::CreateAndResolve(NS_OK, __func__); @@ -1483,7 +1478,7 @@ OggTrackDemuxer::Seek(TimeUnit aTime) return SeekPromise::CreateAndResolve(seekTime, __func__); } else { - return SeekPromise::CreateAndReject(DemuxerFailureReason::DEMUXER_ERROR, __func__); + return SeekPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_DEMUXER_ERR, __func__); } } @@ -1516,7 +1511,7 @@ OggTrackDemuxer::GetSamples(int32_t aNumSamples) { RefPtr<SamplesHolder> samples = new SamplesHolder; if (!aNumSamples) { - return SamplesPromise::CreateAndReject(DemuxerFailureReason::DEMUXER_ERROR, __func__); + return SamplesPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_DEMUXER_ERR, __func__); } while (aNumSamples) { @@ -1529,7 +1524,7 @@ OggTrackDemuxer::GetSamples(int32_t aNumSamples) } if (samples->mSamples.IsEmpty()) { - return SamplesPromise::CreateAndReject(DemuxerFailureReason::END_OF_STREAM, __func__); + return SamplesPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_END_OF_STREAM, __func__); } else { return SamplesPromise::CreateAndResolve(samples, __func__); } @@ -1563,7 +1558,7 @@ OggTrackDemuxer::SkipToNextRandomAccessPoint(TimeUnit aTimeThreshold) parsed); return SkipAccessPointPromise::CreateAndResolve(parsed, __func__); } else { - SkipFailureHolder failure(DemuxerFailureReason::END_OF_STREAM, parsed); + SkipFailureHolder failure(NS_ERROR_DOM_MEDIA_END_OF_STREAM, parsed); return SkipAccessPointPromise::CreateAndReject(Move(failure), __func__); } } diff --git a/dom/media/omx/MediaOmxReader.cpp b/dom/media/omx/MediaOmxReader.cpp index 411801770a2c1c4b9044dc8abea32f5214d346ee..3d57ea6f7a943dc4d05e676021df8c94b240cdab 100644 --- a/dom/media/omx/MediaOmxReader.cpp +++ b/dom/media/omx/MediaOmxReader.cpp @@ -175,7 +175,7 @@ MediaOmxReader::Shutdown() void MediaOmxReader::ReleaseResources() { mMediaResourceRequest.DisconnectIfExists(); - mMetadataPromise.RejectIfExists(ReadMetadataFailureReason::METADATA_ERROR, __func__); + mMetadataPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_METADATA_ERR, __func__); ResetDecode(); // Before freeing a video codec, all video buffers needed to be released @@ -221,7 +221,7 @@ MediaOmxReader::AsyncReadMetadata() nsresult rv = InitOmxDecoder(); if (NS_FAILED(rv)) { return MediaDecoderReader::MetadataPromise::CreateAndReject( - ReadMetadataFailureReason::METADATA_ERROR, __func__); + NS_ERROR_DOM_MEDIA_METADATA_ERR, __func__); } bool isMP3 = mDecoder->GetResource()->GetContentType().EqualsASCII(AUDIO_MP3); @@ -243,7 +243,7 @@ MediaOmxReader::AsyncReadMetadata() self->HandleResourceAllocated(); }, [self] (bool) -> void { self->mMediaResourceRequest.Complete(); - self->mMetadataPromise.Reject(ReadMetadataFailureReason::METADATA_ERROR, __func__); + self->mMetadataPromise.Reject(NS_ERROR_DOM_MEDIA_METADATA_ERR, __func__); })); return p; @@ -255,7 +255,7 @@ void MediaOmxReader::HandleResourceAllocated() // After resources are available, set the metadata. if (!mOmxDecoder->EnsureMetadata()) { - mMetadataPromise.Reject(ReadMetadataFailureReason::METADATA_ERROR, __func__); + mMetadataPromise.Reject(NS_ERROR_DOM_MEDIA_METADATA_ERR, __func__); return; } @@ -289,7 +289,7 @@ void MediaOmxReader::HandleResourceAllocated() nsIntSize displaySize(displayWidth, displayHeight); nsIntSize frameSize(width, height); if (!IsValidVideoRegion(frameSize, pictureRect, displaySize)) { - mMetadataPromise.Reject(ReadMetadataFailureReason::METADATA_ERROR, __func__); + mMetadataPromise.Reject(NS_ERROR_DOM_MEDIA_METADATA_ERR, __func__); return; } diff --git a/dom/media/platforms/PDMFactory.cpp b/dom/media/platforms/PDMFactory.cpp index 14f29f2eaf1b3869af8af32022e1a343f31b106c..5bd783cb74af1fcd2843f53ee0941da719da2523 100644 --- a/dom/media/platforms/PDMFactory.cpp +++ b/dom/media/platforms/PDMFactory.cpp @@ -267,7 +267,7 @@ PDMFactory::CreateDecoderWithPDM(PlatformDecoderModule* aPDM, CreateDecoderParams params = aParams; params.mCallback = callback; - if (MP4Decoder::IsH264(config.mMimeType)) { + if (MP4Decoder::IsH264(config.mMimeType) && !aParams.mUseBlankDecoder) { RefPtr<H264Converter> h = new H264Converter(aPDM, params); const nsresult rv = h->GetLastError(); if (NS_SUCCEEDED(rv) || rv == NS_ERROR_NOT_INITIALIZED) { diff --git a/dom/media/platforms/PlatformDecoderModule.h b/dom/media/platforms/PlatformDecoderModule.h index fd79d980ba0d8d42eb10f87f03c63b79bd5f3aee..7d94f94e10d44390f348cc37d19deb5adda361bb 100644 --- a/dom/media/platforms/PlatformDecoderModule.h +++ b/dom/media/platforms/PlatformDecoderModule.h @@ -14,6 +14,7 @@ #include "mozilla/RefPtr.h" #include "GMPService.h" #include <queue> +#include "MediaResult.h" namespace mozilla { class TrackInfo; @@ -154,11 +155,6 @@ protected: CreateAudioDecoder(const CreateDecoderParams& aParams) = 0; }; -enum class MediaDataDecoderError : uint8_t{ - FATAL_ERROR, - DECODE_ERROR -}; - // A callback used by MediaDataDecoder to return output/errors to the // MediaFormatReader. // Implementation is threadsafe, and can be called on any thread. @@ -171,7 +167,7 @@ public: // Denotes an error in the decoding process. The reader will stop calling // the decoder. - virtual void Error(MediaDataDecoderError aError) = 0; + virtual void Error(const MediaResult& aError) = 0; // Denotes that the last input sample has been inserted into the decoder, // and no more output can be produced unless more input is sent. @@ -219,13 +215,8 @@ protected: virtual ~MediaDataDecoder() {}; public: - enum class DecoderFailureReason : uint8_t { - INIT_ERROR, - CANCELED - }; - typedef TrackInfo::TrackType TrackType; - typedef MozPromise<TrackType, DecoderFailureReason, /* IsExclusive = */ true> InitPromise; + typedef MozPromise<TrackType, MediaResult, /* IsExclusive = */ true> InitPromise; NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaDataDecoder) diff --git a/dom/media/platforms/agnostic/BlankDecoderModule.cpp b/dom/media/platforms/agnostic/BlankDecoderModule.cpp index 049c35f8511bd6bd4416ff6084bda39257a4d129..7609e377de90472d861391c78b573572a42b98c2 100644 --- a/dom/media/platforms/agnostic/BlankDecoderModule.cpp +++ b/dom/media/platforms/agnostic/BlankDecoderModule.cpp @@ -11,6 +11,7 @@ #include "mozilla/mozalloc.h" // for operator new, and new (fallible) #include "mozilla/RefPtr.h" #include "mozilla/TaskQueue.h" +#include "mp4_demuxer/AnnexB.h" #include "mp4_demuxer/H264.h" #include "MP4Decoder.h" #include "nsAutoPtr.h" @@ -34,7 +35,9 @@ public: , mCallback(aParams.mCallback) , mMaxRefFrames(aParams.mConfig.GetType() == TrackInfo::kVideoTrack && MP4Decoder::IsH264(aParams.mConfig.mMimeType) - ? mp4_demuxer::H264::ComputeMaxRefFrames(aParams.VideoConfig().mExtraData) + ? mp4_demuxer::AnnexB::HasSPS(aParams.VideoConfig().mExtraData) + ? mp4_demuxer::H264::ComputeMaxRefFrames(aParams.VideoConfig().mExtraData) + : 16 : 0) , mType(aParams.mConfig.GetType()) { @@ -79,7 +82,7 @@ private: void OutputFrame(MediaData* aData) { if (!aData) { - mCallback->Error(MediaDataDecoderError::FATAL_ERROR); + mCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__)); return; } diff --git a/dom/media/platforms/agnostic/OpusDecoder.cpp b/dom/media/platforms/agnostic/OpusDecoder.cpp index 59ad296c8ab94e1a8485b11bd8c1b33346d7e8b8..190ed787446be9e66a00a8a5f435f0b002212be7 100644 --- a/dom/media/platforms/agnostic/OpusDecoder.cpp +++ b/dom/media/platforms/agnostic/OpusDecoder.cpp @@ -61,14 +61,14 @@ OpusDataDecoder::Init() uint8_t *p = mInfo.mCodecSpecificConfig->Elements(); if (length < sizeof(uint64_t)) { OPUS_DEBUG("CodecSpecificConfig too short to read codecDelay!"); - return InitPromise::CreateAndReject(DecoderFailureReason::INIT_ERROR, __func__); + return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); } int64_t codecDelay = BigEndian::readUint64(p); length -= sizeof(uint64_t); p += sizeof(uint64_t); if (NS_FAILED(DecodeHeader(p, length))) { OPUS_DEBUG("Error decoding header!"); - return InitPromise::CreateAndReject(DecoderFailureReason::INIT_ERROR, __func__); + return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); } int r; @@ -84,7 +84,7 @@ OpusDataDecoder::Init() if (codecDelay != FramesToUsecs(mOpusParser->mPreSkip, mOpusParser->mRate).value()) { NS_WARNING("Invalid Opus header: CodecDelay and pre-skip do not match!"); - return InitPromise::CreateAndReject(DecoderFailureReason::INIT_ERROR, __func__); + return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); } if (mInfo.mRate != (uint32_t)mOpusParser->mRate) { @@ -95,7 +95,7 @@ OpusDataDecoder::Init() } return r == OPUS_OK ? InitPromise::CreateAndResolve(TrackInfo::kAudioTrack, __func__) - : InitPromise::CreateAndReject(DecoderFailureReason::INIT_ERROR, __func__); + : InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); } nsresult @@ -151,21 +151,15 @@ OpusDataDecoder::ProcessDecode(MediaRawData* aSample) return; } - DecodeError err = DoDecode(aSample); - switch (err) { - case DecodeError::FATAL_ERROR: - mCallback->Error(MediaDataDecoderError::FATAL_ERROR); - return; - case DecodeError::DECODE_ERROR: - mCallback->Error(MediaDataDecoderError::DECODE_ERROR); - break; - case DecodeError::DECODE_SUCCESS: - mCallback->InputExhausted(); - break; + MediaResult rv = DoDecode(aSample); + if (NS_FAILED(rv)) { + mCallback->Error(rv); + return; } + mCallback->InputExhausted(); } -OpusDataDecoder::DecodeError +MediaResult OpusDataDecoder::DoDecode(MediaRawData* aSample) { int64_t aDiscardPadding = 0; @@ -178,7 +172,7 @@ OpusDataDecoder::DoDecode(MediaRawData* aSample) // Discard padding should be used only on the final packet, so // decoding after a padding discard is invalid. OPUS_DEBUG("Opus error, discard padding on interstitial packet"); - return FATAL_ERROR; + return NS_ERROR_DOM_MEDIA_FATAL_ERR; } if (!mLastFrameTime || mLastFrameTime.ref() != aSample->mTime) { @@ -193,7 +187,7 @@ OpusDataDecoder::DoDecode(MediaRawData* aSample) if (frames_number <= 0) { OPUS_DEBUG("Invalid packet header: r=%ld length=%ld", frames_number, aSample->Size()); - return FATAL_ERROR; + return NS_ERROR_DOM_MEDIA_DECODE_ERR; } int32_t samples = opus_packet_get_samples_per_frame(aSample->Data(), @@ -204,12 +198,12 @@ OpusDataDecoder::DoDecode(MediaRawData* aSample) int32_t frames = frames_number*samples; if (frames < 120 || frames > 5760) { OPUS_DEBUG("Invalid packet frames: %ld", frames); - return FATAL_ERROR; + return NS_ERROR_DOM_MEDIA_DECODE_ERR; } AlignedAudioBuffer buffer(frames * channels); if (!buffer) { - return FATAL_ERROR; + return MediaResult(NS_ERROR_OUT_OF_MEMORY, __func__); } // Decode to the appropriate sample type. @@ -223,7 +217,7 @@ OpusDataDecoder::DoDecode(MediaRawData* aSample) buffer.get(), frames, false); #endif if (ret < 0) { - return DECODE_ERROR; + return NS_ERROR_DOM_MEDIA_DECODE_ERR; } NS_ASSERTION(ret == frames, "Opus decoded too few audio samples"); CheckedInt64 startTime = aSample->mTime; @@ -244,7 +238,7 @@ OpusDataDecoder::DoDecode(MediaRawData* aSample) if (aDiscardPadding < 0) { // Negative discard padding is invalid. OPUS_DEBUG("Opus error, negative discard padding"); - return FATAL_ERROR; + return NS_ERROR_DOM_MEDIA_FATAL_ERR; } if (aDiscardPadding > 0) { OPUS_DEBUG("OpusDecoder discardpadding %" PRId64 "", aDiscardPadding); @@ -253,12 +247,12 @@ OpusDataDecoder::DoDecode(MediaRawData* aSample) mOpusParser->mRate); if (!discardFrames.isValid()) { NS_WARNING("Int overflow in DiscardPadding"); - return FATAL_ERROR; + return NS_ERROR_DOM_MEDIA_OVERFLOW_ERR; } if (discardFrames.value() > frames) { // Discarding more than the entire packet is invalid. OPUS_DEBUG("Opus error, discard padding larger than packet"); - return FATAL_ERROR; + return NS_ERROR_DOM_MEDIA_FATAL_ERR; } OPUS_DEBUG("Opus decoder discarding %d of %d frames", int32_t(discardFrames.value()), frames); @@ -293,14 +287,14 @@ OpusDataDecoder::DoDecode(MediaRawData* aSample) CheckedInt64 duration = FramesToUsecs(frames, mOpusParser->mRate); if (!duration.isValid()) { NS_WARNING("OpusDataDecoder: Int overflow converting WebM audio duration"); - return FATAL_ERROR; + return NS_ERROR_DOM_MEDIA_OVERFLOW_ERR; } CheckedInt64 time = startTime - FramesToUsecs(mOpusParser->mPreSkip, mOpusParser->mRate) + FramesToUsecs(mFrames, mOpusParser->mRate); if (!time.isValid()) { NS_WARNING("OpusDataDecoder: Int overflow shifting tstamp by codec delay"); - return FATAL_ERROR; + return NS_ERROR_DOM_MEDIA_OVERFLOW_ERR; }; mCallback->Output(new AudioData(aSample->mOffset, @@ -311,7 +305,7 @@ OpusDataDecoder::DoDecode(MediaRawData* aSample) mOpusParser->mChannels, mOpusParser->mRate)); mFrames += frames; - return DECODE_SUCCESS; + return NS_OK; } void diff --git a/dom/media/platforms/agnostic/OpusDecoder.h b/dom/media/platforms/agnostic/OpusDecoder.h index 4e8acb6e10773f65cfb2016c0cbdeae90f97b39e..04f9860afac23a02adfe5594427e224cd8767125 100644 --- a/dom/media/platforms/agnostic/OpusDecoder.h +++ b/dom/media/platforms/agnostic/OpusDecoder.h @@ -41,16 +41,10 @@ public: static void AppendCodecDelay(MediaByteBuffer* config, uint64_t codecDelayUS); private: - enum DecodeError { - DECODE_SUCCESS, - DECODE_ERROR, - FATAL_ERROR - }; - nsresult DecodeHeader(const unsigned char* aData, size_t aLength); void ProcessDecode(MediaRawData* aSample); - DecodeError DoDecode(MediaRawData* aSample); + MediaResult DoDecode(MediaRawData* aSample); void ProcessDrain(); const AudioInfo& mInfo; diff --git a/dom/media/platforms/agnostic/TheoraDecoder.cpp b/dom/media/platforms/agnostic/TheoraDecoder.cpp index 375db566e4fa13df793373c2fcffac65ea18f251..76644ba66ef7af55c189b040a959b11580efc765 100644 --- a/dom/media/platforms/agnostic/TheoraDecoder.cpp +++ b/dom/media/platforms/agnostic/TheoraDecoder.cpp @@ -78,22 +78,22 @@ TheoraDecoder::Init() if (!XiphExtradataToHeaders(headers, headerLens, mInfo.mCodecSpecificConfig->Elements(), mInfo.mCodecSpecificConfig->Length())) { - return InitPromise::CreateAndReject(DecoderFailureReason::INIT_ERROR, __func__); + return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); } for (size_t i = 0; i < headers.Length(); i++) { if (NS_FAILED(DoDecodeHeader(headers[i], headerLens[i]))) { - return InitPromise::CreateAndReject(DecoderFailureReason::INIT_ERROR, __func__); + return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); } } if (mPacketCount != 3) { - return InitPromise::CreateAndReject(DecoderFailureReason::INIT_ERROR, __func__); + return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); } mTheoraDecoderContext = th_decode_alloc(&mTheoraInfo, mTheoraSetupInfo); if (mTheoraDecoderContext) { return InitPromise::CreateAndResolve(TrackInfo::kVideoTrack, __func__); } else { - return InitPromise::CreateAndReject(DecoderFailureReason::INIT_ERROR, __func__); + return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); } } @@ -123,7 +123,7 @@ TheoraDecoder::DoDecodeHeader(const unsigned char* aData, size_t aLength) return r > 0 ? NS_OK : NS_ERROR_FAILURE; } -int +MediaResult TheoraDecoder::DoDecode(MediaRawData* aSample) { MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn()); @@ -181,13 +181,13 @@ TheoraDecoder::DoDecode(MediaRawData* aSample) LOG("Image allocation error source %ldx%ld display %ldx%ld picture %ldx%ld", mTheoraInfo.frame_width, mTheoraInfo.frame_height, mInfo.mDisplay.width, mInfo.mDisplay.height, mInfo.mImage.width, mInfo.mImage.height); - return -1; + return MediaResult(NS_ERROR_OUT_OF_MEMORY, __func__); } mCallback->Output(v); - return 0; + return NS_OK; } else { LOG("Theora Decode error: %d", ret); - return -1; + return NS_ERROR_DOM_MEDIA_DECODE_ERR; } } @@ -198,8 +198,9 @@ TheoraDecoder::ProcessDecode(MediaRawData* aSample) if (mIsFlushing) { return; } - if (DoDecode(aSample) == -1) { - mCallback->Error(MediaDataDecoderError::DECODE_ERROR); + MediaResult rv = DoDecode(aSample); + if (NS_FAILED(rv)) { + mCallback->Error(rv); } else { mCallback->InputExhausted(); } diff --git a/dom/media/platforms/agnostic/TheoraDecoder.h b/dom/media/platforms/agnostic/TheoraDecoder.h index 33fc0e73b69b7391294b47f662dc6bb7ca7ddc20..4a5daebb9149113fdfa5e08a24b6ebfcf666ea55 100644 --- a/dom/media/platforms/agnostic/TheoraDecoder.h +++ b/dom/media/platforms/agnostic/TheoraDecoder.h @@ -41,7 +41,7 @@ private: nsresult DoDecodeHeader(const unsigned char* aData, size_t aLength); void ProcessDecode(MediaRawData* aSample); - int DoDecode(MediaRawData* aSample); + MediaResult DoDecode(MediaRawData* aSample); void ProcessDrain(); RefPtr<ImageContainer> mImageContainer; diff --git a/dom/media/platforms/agnostic/VPXDecoder.cpp b/dom/media/platforms/agnostic/VPXDecoder.cpp index 093030f09d0159cc1864a0a115e92bae47d5423b..458fcc6ca6d9ba39db4859321685623c8979eed6 100644 --- a/dom/media/platforms/agnostic/VPXDecoder.cpp +++ b/dom/media/platforms/agnostic/VPXDecoder.cpp @@ -80,7 +80,7 @@ VPXDecoder::Init() config.w = config.h = 0; // set after decode if (!dx || vpx_codec_dec_init(&mVPX, dx, &config, 0)) { - return InitPromise::CreateAndReject(DecoderFailureReason::INIT_ERROR, __func__); + return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); } return InitPromise::CreateAndResolve(TrackInfo::kVideoTrack, __func__); } @@ -97,7 +97,7 @@ VPXDecoder::Flush() mIsFlushing = false; } -int +MediaResult VPXDecoder::DoDecode(MediaRawData* aSample) { MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn()); @@ -116,7 +116,7 @@ VPXDecoder::DoDecode(MediaRawData* aSample) if (vpx_codec_err_t r = vpx_codec_decode(&mVPX, aSample->Data(), aSample->Size(), nullptr, 0)) { LOG("VPX Decode error: %s", vpx_codec_err_to_string(r)); - return -1; + return NS_ERROR_DOM_MEDIA_DECODE_ERR; } vpx_codec_iter_t iter = nullptr; @@ -157,7 +157,7 @@ VPXDecoder::DoDecode(MediaRawData* aSample) b.mPlanes[2].mWidth = img->d_w; } else { LOG("VPX Unknown image format"); - return -1; + return NS_ERROR_DOM_MEDIA_DECODE_ERR; } RefPtr<VideoData> v = @@ -176,11 +176,11 @@ VPXDecoder::DoDecode(MediaRawData* aSample) LOG("Image allocation error source %ldx%ld display %ldx%ld picture %ldx%ld", img->d_w, img->d_h, mInfo.mDisplay.width, mInfo.mDisplay.height, mInfo.mImage.width, mInfo.mImage.height); - return -1; + return MediaResult(NS_ERROR_OUT_OF_MEMORY, __func__); } mCallback->Output(v); } - return 0; + return NS_OK; } void @@ -190,8 +190,9 @@ VPXDecoder::ProcessDecode(MediaRawData* aSample) if (mIsFlushing) { return; } - if (DoDecode(aSample) == -1) { - mCallback->Error(MediaDataDecoderError::DECODE_ERROR); + MediaResult rv = DoDecode(aSample); + if (NS_FAILED(rv)) { + mCallback->Error(rv); } else { mCallback->InputExhausted(); } diff --git a/dom/media/platforms/agnostic/VPXDecoder.h b/dom/media/platforms/agnostic/VPXDecoder.h index e8a771b869107010cf11b25427d7ac99efd18474..d420ec069b7764f6dcbc027aba1373f1c8c556f8 100644 --- a/dom/media/platforms/agnostic/VPXDecoder.h +++ b/dom/media/platforms/agnostic/VPXDecoder.h @@ -48,7 +48,7 @@ public: private: void ProcessDecode(MediaRawData* aSample); - int DoDecode(MediaRawData* aSample); + MediaResult DoDecode(MediaRawData* aSample); void ProcessDrain(); const RefPtr<ImageContainer> mImageContainer; diff --git a/dom/media/platforms/agnostic/VorbisDecoder.cpp b/dom/media/platforms/agnostic/VorbisDecoder.cpp index 995f2624b4f6df8d93cb613779a12c7be052d084..9613924d06b5df40161eff3c9a5bb919d7a74db4 100644 --- a/dom/media/platforms/agnostic/VorbisDecoder.cpp +++ b/dom/media/platforms/agnostic/VorbisDecoder.cpp @@ -72,11 +72,11 @@ VorbisDataDecoder::Init() if (!XiphExtradataToHeaders(headers, headerLens, mInfo.mCodecSpecificConfig->Elements(), mInfo.mCodecSpecificConfig->Length())) { - return InitPromise::CreateAndReject(DecoderFailureReason::INIT_ERROR, __func__); + return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); } for (size_t i = 0; i < headers.Length(); i++) { if (NS_FAILED(DecodeHeader(headers[i], headerLens[i]))) { - return InitPromise::CreateAndReject(DecoderFailureReason::INIT_ERROR, __func__); + return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); } } @@ -84,12 +84,12 @@ VorbisDataDecoder::Init() int r = vorbis_synthesis_init(&mVorbisDsp, &mVorbisInfo); if (r) { - return InitPromise::CreateAndReject(DecoderFailureReason::INIT_ERROR, __func__); + return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); } r = vorbis_block_init(&mVorbisDsp, &mVorbisBlock); if (r) { - return InitPromise::CreateAndReject(DecoderFailureReason::INIT_ERROR, __func__); + return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); } if (mInfo.mRate != (uint32_t)mVorbisDsp.vi->rate) { @@ -103,7 +103,7 @@ VorbisDataDecoder::Init() AudioConfig::ChannelLayout layout(mVorbisDsp.vi->channels); if (!layout.IsValid()) { - return InitPromise::CreateAndReject(DecoderFailureReason::INIT_ERROR, __func__); + return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); } return InitPromise::CreateAndResolve(TrackInfo::kAudioTrack, __func__); @@ -137,14 +137,16 @@ VorbisDataDecoder::ProcessDecode(MediaRawData* aSample) if (mIsFlushing) { return; } - if (DoDecode(aSample) == -1) { - mCallback->Error(MediaDataDecoderError::DECODE_ERROR); + + MediaResult rv = DoDecode(aSample); + if (NS_FAILED(rv)) { + mCallback->Error(rv); } else { mCallback->InputExhausted(); } } -int +MediaResult VorbisDataDecoder::DoDecode(MediaRawData* aSample) { MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn()); @@ -167,26 +169,25 @@ VorbisDataDecoder::DoDecode(MediaRawData* aSample) aSample->mTimecode, mPacketCount++); if (vorbis_synthesis(&mVorbisBlock, &pkt) != 0) { - return -1; + return NS_ERROR_DOM_MEDIA_DECODE_ERR; } if (vorbis_synthesis_blockin(&mVorbisDsp, &mVorbisBlock) != 0) { - return -1; + return NS_ERROR_DOM_MEDIA_DECODE_ERR; } VorbisPCMValue** pcm = 0; int32_t frames = vorbis_synthesis_pcmout(&mVorbisDsp, &pcm); if (frames == 0) { - mCallback->InputExhausted(); - return 0; + return NS_OK; } while (frames > 0) { uint32_t channels = mVorbisDsp.vi->channels; uint32_t rate = mVorbisDsp.vi->rate; AlignedAudioBuffer buffer(frames*channels); if (!buffer) { - return -1; + return MediaResult(NS_ERROR_OUT_OF_MEMORY, __func__); } for (uint32_t j = 0; j < channels; ++j) { VorbisPCMValue* channel = pcm[j]; @@ -198,18 +199,18 @@ VorbisDataDecoder::DoDecode(MediaRawData* aSample) CheckedInt64 duration = FramesToUsecs(frames, rate); if (!duration.isValid()) { NS_WARNING("Int overflow converting WebM audio duration"); - return -1; + return NS_ERROR_DOM_MEDIA_OVERFLOW_ERR; } CheckedInt64 total_duration = FramesToUsecs(mFrames, rate); if (!total_duration.isValid()) { NS_WARNING("Int overflow converting WebM audio total_duration"); - return -1; + return NS_ERROR_DOM_MEDIA_OVERFLOW_ERR; } CheckedInt64 time = total_duration + aTstampUsecs; if (!time.isValid()) { NS_WARNING("Int overflow adding total_duration and aTstampUsecs"); - return -1; + return NS_ERROR_DOM_MEDIA_OVERFLOW_ERR; }; if (!mAudioConverter) { @@ -217,7 +218,7 @@ VorbisDataDecoder::DoDecode(MediaRawData* aSample) rate); AudioConfig out(channels, rate); if (!in.IsValid() || !out.IsValid()) { - return -1; + return NS_ERROR_DOM_MEDIA_FATAL_ERR; } mAudioConverter = MakeUnique<AudioConverter>(in, out); } @@ -235,13 +236,13 @@ VorbisDataDecoder::DoDecode(MediaRawData* aSample) rate)); mFrames += frames; if (vorbis_synthesis_read(&mVorbisDsp, frames) != 0) { - return -1; + return NS_ERROR_DOM_MEDIA_DECODE_ERR; } frames = vorbis_synthesis_pcmout(&mVorbisDsp, &pcm); } - return aTotalFrames > 0 ? 1 : 0; + return NS_OK; } void diff --git a/dom/media/platforms/agnostic/VorbisDecoder.h b/dom/media/platforms/agnostic/VorbisDecoder.h index 0054fd51695d5353b623b26153363ee5d495e75c..0ed7bb645377862b533423dfd9604768d77bc639 100644 --- a/dom/media/platforms/agnostic/VorbisDecoder.h +++ b/dom/media/platforms/agnostic/VorbisDecoder.h @@ -42,7 +42,7 @@ private: nsresult DecodeHeader(const unsigned char* aData, size_t aLength); void ProcessDecode(MediaRawData* aSample); - int DoDecode(MediaRawData* aSample); + MediaResult DoDecode(MediaRawData* aSample); void ProcessDrain(); const AudioInfo& mInfo; diff --git a/dom/media/platforms/agnostic/WAVDecoder.cpp b/dom/media/platforms/agnostic/WAVDecoder.cpp index 0824beb128edbc01e671241194e35d7a71e9d9d6..67a045287d49a0e99703d2ceaf7b731b7d54340b 100644 --- a/dom/media/platforms/agnostic/WAVDecoder.cpp +++ b/dom/media/platforms/agnostic/WAVDecoder.cpp @@ -65,14 +65,15 @@ WaveDataDecoder::Init() void WaveDataDecoder::Input(MediaRawData* aSample) { - if (!DoDecode(aSample)) { - mCallback->Error(MediaDataDecoderError::DECODE_ERROR); + MediaResult rv = DoDecode(aSample); + if (NS_FAILED(rv)) { + mCallback->Error(rv); } else { mCallback->InputExhausted(); } } -bool +MediaResult WaveDataDecoder::DoDecode(MediaRawData* aSample) { size_t aLength = aSample->Size(); @@ -84,7 +85,7 @@ WaveDataDecoder::DoDecode(MediaRawData* aSample) AlignedAudioBuffer buffer(frames * mInfo.mChannels); if (!buffer) { - return false; + return MediaResult(NS_ERROR_OUT_OF_MEMORY, __func__); } for (int i = 0; i < frames; ++i) { for (unsigned int j = 0; j < mInfo.mChannels; ++j) { @@ -126,7 +127,7 @@ WaveDataDecoder::DoDecode(MediaRawData* aSample) mInfo.mChannels, mInfo.mRate)); - return true; + return NS_OK; } void diff --git a/dom/media/platforms/agnostic/WAVDecoder.h b/dom/media/platforms/agnostic/WAVDecoder.h index 680c63db55bfcc828eceb0b82c8ee7a14d803bfc..dea980168118d9c754f732bb270dea2de943117d 100644 --- a/dom/media/platforms/agnostic/WAVDecoder.h +++ b/dom/media/platforms/agnostic/WAVDecoder.h @@ -31,7 +31,7 @@ public: } private: - bool DoDecode(MediaRawData* aSample); + MediaResult DoDecode(MediaRawData* aSample); const AudioInfo& mInfo; MediaDataDecoderCallback* mCallback; diff --git a/dom/media/platforms/agnostic/eme/EMEDecoderModule.cpp b/dom/media/platforms/agnostic/eme/EMEDecoderModule.cpp index b3903430168a56c832e3389f5058d25a33615f54..923970fd7db9cd0d1f433a8c1d5c9bd9f24434ad 100644 --- a/dom/media/platforms/agnostic/eme/EMEDecoderModule.cpp +++ b/dom/media/platforms/agnostic/eme/EMEDecoderModule.cpp @@ -93,7 +93,8 @@ public: Input(aDecrypted.mSample); } else if (aDecrypted.mStatus != Ok) { if (mCallback) { - mCallback->Error(MediaDataDecoderError::FATAL_ERROR); + mCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, + __func__)); } } else { MOZ_ASSERT(!mIsShutdown); diff --git a/dom/media/platforms/agnostic/gmp/GMPAudioDecoder.cpp b/dom/media/platforms/agnostic/gmp/GMPAudioDecoder.cpp index c9d8f4fe4a391275bd35e6c9f5cc2bc4c0c6030c..796efbdfd3cb7729541b595fba3533f9ffd72332 100644 --- a/dom/media/platforms/agnostic/gmp/GMPAudioDecoder.cpp +++ b/dom/media/platforms/agnostic/gmp/GMPAudioDecoder.cpp @@ -8,6 +8,7 @@ #include "nsServiceManagerUtils.h" #include "MediaInfo.h" #include "GMPDecoderModule.h" +#include "nsPrintfCString.h" namespace mozilla { @@ -31,7 +32,7 @@ AudioCallbackAdapter::Decoded(const nsTArray<int16_t>& aPCM, uint64_t aTimeStamp if (aRate == 0 || aChannels == 0) { NS_WARNING("Invalid rate or num channels returned on GMP audio samples"); - mCallback->Error(MediaDataDecoderError::FATAL_ERROR); + mCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__)); return; } @@ -39,7 +40,7 @@ AudioCallbackAdapter::Decoded(const nsTArray<int16_t>& aPCM, uint64_t aTimeStamp MOZ_ASSERT((aPCM.Length() % aChannels) == 0); AlignedAudioBuffer audioData(aPCM.Length()); if (!audioData) { - mCallback->Error(MediaDataDecoderError::FATAL_ERROR); + mCallback->Error(MediaResult(NS_ERROR_OUT_OF_MEMORY, __func__)); return; } @@ -52,7 +53,8 @@ AudioCallbackAdapter::Decoded(const nsTArray<int16_t>& aPCM, uint64_t aTimeStamp auto timestamp = UsecsToFrames(aTimeStamp, aRate); if (!timestamp.isValid()) { NS_WARNING("Invalid timestamp"); - mCallback->Error(MediaDataDecoderError::FATAL_ERROR); + mCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_OVERFLOW_ERR, + __func__)); return; } mAudioFrameOffset = timestamp.value(); @@ -62,7 +64,8 @@ AudioCallbackAdapter::Decoded(const nsTArray<int16_t>& aPCM, uint64_t aTimeStamp auto timestamp = FramesToUsecs(mAudioFrameOffset + mAudioFrameSum, aRate); if (!timestamp.isValid()) { NS_WARNING("Invalid timestamp on audio samples"); - mCallback->Error(MediaDataDecoderError::FATAL_ERROR); + mCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_OVERFLOW_ERR, + __func__)); return; } mAudioFrameSum += numFrames; @@ -70,7 +73,8 @@ AudioCallbackAdapter::Decoded(const nsTArray<int16_t>& aPCM, uint64_t aTimeStamp auto duration = FramesToUsecs(numFrames, aRate); if (!duration.isValid()) { NS_WARNING("Invalid duration on audio samples"); - mCallback->Error(MediaDataDecoderError::FATAL_ERROR); + mCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_OVERFLOW_ERR, + __func__)); return; } @@ -116,14 +120,16 @@ void AudioCallbackAdapter::Error(GMPErr aErr) { MOZ_ASSERT(IsOnGMPThread()); - mCallback->Error(MediaDataDecoderError::FATAL_ERROR); + mCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, + nsPrintfCString("%s: %d", __func__, aErr))); } void AudioCallbackAdapter::Terminated() { NS_WARNING("AAC GMP decoder terminated."); - mCallback->Error(MediaDataDecoderError::FATAL_ERROR); + mCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, + __func__)); } GMPAudioDecoderParams::GMPAudioDecoderParams(const CreateDecoderParams& aParams) @@ -190,7 +196,7 @@ GMPAudioDecoder::GMPInitDone(GMPAudioDecoderProxy* aGMP) MOZ_ASSERT(IsOnGMPThread()); if (!aGMP) { - mInitPromise.RejectIfExists(MediaDataDecoder::DecoderFailureReason::INIT_ERROR, __func__); + mInitPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); return; } if (mInitPromise.IsEmpty()) { @@ -211,7 +217,7 @@ GMPAudioDecoder::GMPInitDone(GMPAudioDecoderProxy* aGMP) mAdapter); if (NS_FAILED(rv)) { aGMP->Close(); - mInitPromise.Reject(MediaDataDecoder::DecoderFailureReason::INIT_ERROR, __func__); + mInitPromise.Reject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); return; } @@ -233,7 +239,7 @@ GMPAudioDecoder::Init() InitTags(tags); UniquePtr<GetGMPAudioDecoderCallback> callback(new GMPInitDoneCallback(this)); if (NS_FAILED(mMPS->GetGMPAudioDecoder(mCrashHelper, &tags, GetNodeId(), Move(callback)))) { - mInitPromise.Reject(MediaDataDecoder::DecoderFailureReason::INIT_ERROR, __func__); + mInitPromise.Reject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); } return promise; @@ -246,7 +252,7 @@ GMPAudioDecoder::Input(MediaRawData* aSample) RefPtr<MediaRawData> sample(aSample); if (!mGMP) { - mCallback->Error(MediaDataDecoderError::FATAL_ERROR); + mCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__)); return; } @@ -255,7 +261,9 @@ GMPAudioDecoder::Input(MediaRawData* aSample) gmp::GMPAudioSamplesImpl samples(sample, mConfig.mChannels, mConfig.mRate); nsresult rv = mGMP->Decode(samples); if (NS_FAILED(rv)) { - mCallback->Error(MediaDataDecoderError::DECODE_ERROR); + mCallback->Error( + MediaResult(rv, nsPrintfCString("%s: decode error (%d)", + __func__, rv))); } } @@ -283,7 +291,7 @@ GMPAudioDecoder::Drain() void GMPAudioDecoder::Shutdown() { - mInitPromise.RejectIfExists(MediaDataDecoder::DecoderFailureReason::CANCELED, __func__); + mInitPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__); if (!mGMP) { return; } diff --git a/dom/media/platforms/agnostic/gmp/GMPVideoDecoder.cpp b/dom/media/platforms/agnostic/gmp/GMPVideoDecoder.cpp index 4e36783193bc3a5a855a2ef4b81a79d242b01232..dad4466fd6e38ced88d579c873f28f8b4a2dafdb 100644 --- a/dom/media/platforms/agnostic/gmp/GMPVideoDecoder.cpp +++ b/dom/media/platforms/agnostic/gmp/GMPVideoDecoder.cpp @@ -54,7 +54,7 @@ VideoCallbackAdapter::Decoded(GMPVideoi420Frame* aDecodedFrame) if (v) { mCallback->Output(v); } else { - mCallback->Error(MediaDataDecoderError::FATAL_ERROR); + mCallback->Error(MediaResult(NS_ERROR_OUT_OF_MEMORY, __func__)); } } @@ -95,7 +95,8 @@ void VideoCallbackAdapter::Error(GMPErr aErr) { MOZ_ASSERT(IsOnGMPThread()); - mCallback->Error(MediaDataDecoderError::FATAL_ERROR); + mCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, + nsPrintfCString("%s: %d", __func__, aErr))); } void @@ -103,7 +104,7 @@ VideoCallbackAdapter::Terminated() { // Note that this *may* be called from the proxy thread also. NS_WARNING("GMP decoder terminated."); - mCallback->Error(MediaDataDecoderError::FATAL_ERROR); + mCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__)); } GMPVideoDecoderParams::GMPVideoDecoderParams(const CreateDecoderParams& aParams) @@ -183,14 +184,14 @@ GMPVideoDecoder::CreateFrame(MediaRawData* aSample) GMPVideoFrame* ftmp = nullptr; GMPErr err = mHost->CreateFrame(kGMPEncodedVideoFrame, &ftmp); if (GMP_FAILED(err)) { - mCallback->Error(MediaDataDecoderError::FATAL_ERROR); + mCallback->Error(MediaResult(NS_ERROR_OUT_OF_MEMORY, __func__)); return nullptr; } GMPUniquePtr<GMPVideoEncodedFrame> frame(static_cast<GMPVideoEncodedFrame*>(ftmp)); err = frame->CreateEmptyFrame(aSample->Size()); if (GMP_FAILED(err)) { - mCallback->Error(MediaDataDecoderError::FATAL_ERROR); + mCallback->Error(MediaResult(NS_ERROR_OUT_OF_MEMORY, __func__)); return nullptr; } @@ -232,7 +233,7 @@ GMPVideoDecoder::GMPInitDone(GMPVideoDecoderProxy* aGMP, GMPVideoHost* aHost) MOZ_ASSERT(IsOnGMPThread()); if (!aGMP) { - mInitPromise.RejectIfExists(MediaDataDecoder::DecoderFailureReason::INIT_ERROR, __func__); + mInitPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); return; } MOZ_ASSERT(aHost); @@ -261,7 +262,7 @@ GMPVideoDecoder::GMPInitDone(GMPVideoDecoderProxy* aGMP, GMPVideoHost* aHost) } else { // Unrecognized mime type aGMP->Close(); - mInitPromise.Reject(MediaDataDecoder::DecoderFailureReason::INIT_ERROR, __func__); + mInitPromise.Reject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); return; } codec.mWidth = mConfig.mImage.width; @@ -273,7 +274,7 @@ GMPVideoDecoder::GMPInitDone(GMPVideoDecoderProxy* aGMP, GMPVideoHost* aHost) PR_GetNumberOfProcessors()); if (NS_FAILED(rv)) { aGMP->Close(); - mInitPromise.Reject(MediaDataDecoder::DecoderFailureReason::INIT_ERROR, __func__); + mInitPromise.Reject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); return; } @@ -307,7 +308,7 @@ GMPVideoDecoder::Init() InitTags(tags); UniquePtr<GetGMPVideoDecoderCallback> callback(new GMPInitDoneCallback(this)); if (NS_FAILED(mMPS->GetGMPVideoDecoder(mCrashHelper, &tags, GetNodeId(), Move(callback)))) { - mInitPromise.Reject(MediaDataDecoder::DecoderFailureReason::INIT_ERROR, __func__); + mInitPromise.Reject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); } return promise; @@ -320,7 +321,8 @@ GMPVideoDecoder::Input(MediaRawData* aSample) RefPtr<MediaRawData> sample(aSample); if (!mGMP) { - mCallback->Error(MediaDataDecoderError::FATAL_ERROR); + mCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, + __func__)); return; } @@ -328,13 +330,13 @@ GMPVideoDecoder::Input(MediaRawData* aSample) GMPUniquePtr<GMPVideoEncodedFrame> frame = CreateFrame(sample); if (!frame) { - mCallback->Error(MediaDataDecoderError::FATAL_ERROR); + mCallback->Error(MediaResult(NS_ERROR_OUT_OF_MEMORY, __func__)); return; } nsTArray<uint8_t> info; // No codec specific per-frame info to pass. nsresult rv = mGMP->Decode(Move(frame), false, info, 0); if (NS_FAILED(rv)) { - mCallback->Error(MediaDataDecoderError::DECODE_ERROR); + mCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR, __func__)); } } @@ -362,7 +364,7 @@ GMPVideoDecoder::Drain() void GMPVideoDecoder::Shutdown() { - mInitPromise.RejectIfExists(MediaDataDecoder::DecoderFailureReason::CANCELED, __func__); + mInitPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__); // Note that this *may* be called from the proxy thread also. if (!mGMP) { return; diff --git a/dom/media/platforms/agnostic/gmp/MediaDataDecoderProxy.cpp b/dom/media/platforms/agnostic/gmp/MediaDataDecoderProxy.cpp index 63cb8ecfce20bebac9fd06eeeb051c45f587d84b..717c0f9cf9a71fa1652a92bf51ec68afcc808aca 100644 --- a/dom/media/platforms/agnostic/gmp/MediaDataDecoderProxy.cpp +++ b/dom/media/platforms/agnostic/gmp/MediaDataDecoderProxy.cpp @@ -10,7 +10,7 @@ namespace mozilla { void -MediaDataDecoderCallbackProxy::Error(MediaDataDecoderError aError) +MediaDataDecoderCallbackProxy::Error(const MediaResult& aError) { mProxyCallback->Error(aError); } diff --git a/dom/media/platforms/agnostic/gmp/MediaDataDecoderProxy.h b/dom/media/platforms/agnostic/gmp/MediaDataDecoderProxy.h index 261b5acde9bbc018ee0f5f301ee6f9c73a0482c3..735b6126ee67f2b226e7ebc70dfad55693d14fc7 100644 --- a/dom/media/platforms/agnostic/gmp/MediaDataDecoderProxy.h +++ b/dom/media/platforms/agnostic/gmp/MediaDataDecoderProxy.h @@ -74,7 +74,7 @@ public: mProxyCallback->Output(aData); } - void Error(MediaDataDecoderError aError) override; + void Error(const MediaResult& aError) override; void InputExhausted() override { mProxyCallback->InputExhausted(); diff --git a/dom/media/platforms/android/MediaCodecDataDecoder.cpp b/dom/media/platforms/android/MediaCodecDataDecoder.cpp index 93ead17cb667c4cb9eea68e20322760c8a9f0f7f..20ffeb384aeba22386cc3ec037c487fc85978155 100644 --- a/dom/media/platforms/android/MediaCodecDataDecoder.cpp +++ b/dom/media/platforms/android/MediaCodecDataDecoder.cpp @@ -75,11 +75,11 @@ public: mSurfaceTexture = AndroidSurfaceTexture::Create(); if (!mSurfaceTexture) { NS_WARNING("Failed to create SurfaceTexture for video decode\n"); - return InitPromise::CreateAndReject(DecoderFailureReason::INIT_ERROR, __func__); + return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); } if (NS_FAILED(InitDecoder(mSurfaceTexture->JavaSurface()))) { - return InitPromise::CreateAndReject(DecoderFailureReason::INIT_ERROR, __func__); + return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); } return InitPromise::CreateAndResolve(TrackInfo::kVideoTrack, __func__); @@ -260,7 +260,7 @@ MediaCodecDataDecoder::Init() return NS_SUCCEEDED(rv) ? InitPromise::CreateAndResolve(type, __func__) : InitPromise::CreateAndReject( - MediaDataDecoder::DecoderFailureReason::INIT_ERROR, __func__); + NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); } nsresult @@ -268,7 +268,8 @@ MediaCodecDataDecoder::InitDecoder(Surface::Param aSurface) { mDecoder = CreateDecoder(mMimeType); if (!mDecoder) { - INVOKE_CALLBACK(Error, MediaDataDecoderError::FATAL_ERROR); + INVOKE_CALLBACK(Error, + MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__)); return NS_ERROR_FAILURE; } @@ -295,7 +296,7 @@ static const int64_t kDecoderTimeout = 10000; INVOKE_CALLBACK(DrainComplete); \ SetState(ModuleState::kDecoding); \ } \ - INVOKE_CALLBACK(Error, MediaDataDecoderError::FATAL_ERROR); \ + INVOKE_CALLBACK(Error, MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__)); \ break; \ } @@ -535,7 +536,9 @@ MediaCodecDataDecoder::DecoderLoop() BREAK_ON_DECODER_ERROR(); } else if (outputStatus < 0) { NS_WARNING("Unknown error from decoder!"); - INVOKE_CALLBACK(Error, MediaDataDecoderError::DECODE_ERROR); + INVOKE_CALLBACK(Error, + MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR, + __func__)); // Don't break here just in case it's recoverable. If it's not, other // stuff will fail later and we'll bail out. } else { diff --git a/dom/media/platforms/android/RemoteDataDecoder.cpp b/dom/media/platforms/android/RemoteDataDecoder.cpp index cdc1d36cb6232b1222ca4d6ca7f44755dc6bc76d..56c79ac4c0a02743231deae86a19da8e2ff607b2 100644 --- a/dom/media/platforms/android/RemoteDataDecoder.cpp +++ b/dom/media/platforms/android/RemoteDataDecoder.cpp @@ -80,8 +80,8 @@ public: { if (mDecoderCallback) { mDecoderCallback->Error(aIsFatal ? - MediaDataDecoderError::FATAL_ERROR : - MediaDataDecoderError::DECODE_ERROR); + MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__) : + MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR, __func__)); } } @@ -196,7 +196,7 @@ public: mSurfaceTexture = AndroidSurfaceTexture::Create(); if (!mSurfaceTexture) { NS_WARNING("Failed to create SurfaceTexture for video decode\n"); - return InitPromise::CreateAndReject(DecoderFailureReason::INIT_ERROR, __func__); + return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); } // Register native methods. @@ -208,7 +208,7 @@ public: mJavaDecoder = CodecProxy::Create(mFormat, mSurfaceTexture->JavaSurface(), mJavaCallbacks); if (mJavaDecoder == nullptr) { - return InitPromise::CreateAndReject(DecoderFailureReason::INIT_ERROR, __func__); + return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); } mInputDurations.Clear(); @@ -306,7 +306,7 @@ public: mJavaDecoder = CodecProxy::Create(mFormat, nullptr, mJavaCallbacks); if (mJavaDecoder == nullptr) { - return InitPromise::CreateAndReject(DecoderFailureReason::INIT_ERROR, __func__); + return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); } return InitPromise::CreateAndResolve(TrackInfo::kAudioTrack, __func__); @@ -382,7 +382,8 @@ private: aFormat->GetInteger(NS_LITERAL_STRING("channel-count"), &mOutputChannels); AudioConfig::ChannelLayout layout(mOutputChannels); if (!layout.IsValid()) { - mDecoderCallback->Error(MediaDataDecoderError::FATAL_ERROR); + mDecoderCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, + __func__)); return; } aFormat->GetInteger(NS_LITERAL_STRING("sample-rate"), &mOutputSampleRate); @@ -476,7 +477,7 @@ RemoteDataDecoder::Input(MediaRawData* aSample) BufferInfo::LocalRef bufferInfo; nsresult rv = BufferInfo::New(&bufferInfo); if (NS_FAILED(rv)) { - mCallback->Error(MediaDataDecoderError::FATAL_ERROR); + mCallback->Error(MediaResult(NS_ERROR_OUT_OF_MEMORY, __func__)); return; } bufferInfo->Set(0, aSample->Size(), aSample->mTime, 0); diff --git a/dom/media/platforms/apple/AppleATDecoder.cpp b/dom/media/platforms/apple/AppleATDecoder.cpp index 0f486787b509a268c53ff4f6fd41f270a44dbd3a..28b4711e082c1ea25f4698bc38cc3c35b89dad35 100644 --- a/dom/media/platforms/apple/AppleATDecoder.cpp +++ b/dom/media/platforms/apple/AppleATDecoder.cpp @@ -57,7 +57,7 @@ AppleATDecoder::Init() { if (!mFormatID) { NS_ERROR("Non recognised format"); - return InitPromise::CreateAndReject(DecoderFailureReason::INIT_ERROR, __func__); + return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); } return InitPromise::CreateAndResolve(TrackType::kAudioTrack, __func__); @@ -194,7 +194,8 @@ AppleATDecoder::SubmitSample(MediaRawData* aSample) if (!mConverter) { rv = SetupDecoder(aSample); if (rv != NS_OK && rv != NS_ERROR_NOT_INITIALIZED) { - mCallback->Error(MediaDataDecoderError::FATAL_ERROR); + mCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, + __func__)); return; } } @@ -203,9 +204,10 @@ AppleATDecoder::SubmitSample(MediaRawData* aSample) if (rv == NS_OK) { for (size_t i = 0; i < mQueuedSamples.Length(); i++) { - if (NS_FAILED(DecodeSample(mQueuedSamples[i]))) { + rv = DecodeSample(mQueuedSamples[i]); + if (NS_FAILED(rv)) { mQueuedSamples.Clear(); - mCallback->Error(MediaDataDecoderError::DECODE_ERROR); + mCallback->Error(MediaResult(rv, __func__)); return; } } @@ -262,7 +264,7 @@ AppleATDecoder::DecodeSample(MediaRawData* aSample) if (rv && rv != kNoMoreDataErr) { LOG("Error decoding audio stream: %d\n", rv); - return NS_ERROR_FAILURE; + return NS_ERROR_DOM_MEDIA_DECODE_ERR; } if (numFrames) { @@ -283,7 +285,7 @@ AppleATDecoder::DecodeSample(MediaRawData* aSample) media::TimeUnit duration = FramesToTimeUnit(numFrames, rate); if (!duration.IsValid()) { NS_WARNING("Invalid count of accumulated audio samples"); - return NS_ERROR_FAILURE; + return NS_ERROR_DOM_MEDIA_OVERFLOW_ERR; } #ifdef LOG_SAMPLE_DECODE @@ -300,7 +302,7 @@ AppleATDecoder::DecodeSample(MediaRawData* aSample) AudioConfig in(*mChannelLayout.get(), rate); AudioConfig out(channels, rate); if (!in.IsValid() || !out.IsValid()) { - return NS_ERROR_FAILURE; + return NS_ERROR_DOM_MEDIA_DECODE_ERR; } mAudioConverter = MakeUnique<AudioConverter>(in, out); } diff --git a/dom/media/platforms/apple/AppleVTDecoder.cpp b/dom/media/platforms/apple/AppleVTDecoder.cpp index 457ce3ef53c0f055e161705630cb5d98233eb6ac..c58e77cab8d6cc0ab3a7bb8f14c0000b0bb04e4c 100644 --- a/dom/media/platforms/apple/AppleVTDecoder.cpp +++ b/dom/media/platforms/apple/AppleVTDecoder.cpp @@ -71,7 +71,7 @@ AppleVTDecoder::Init() return InitPromise::CreateAndResolve(TrackType::kVideoTrack, __func__); } - return InitPromise::CreateAndReject(DecoderFailureReason::INIT_ERROR, __func__); + return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); } void @@ -310,8 +310,8 @@ AppleVTDecoder::OutputFrame(CVPixelBufferRef aImage, CVReturn rv = CVPixelBufferLockBaseAddress(aImage, kCVPixelBufferLock_ReadOnly); if (rv != kCVReturnSuccess) { NS_ERROR("error locking pixel data"); - mCallback->Error(MediaDataDecoderError::DECODE_ERROR); - return NS_ERROR_FAILURE; + mCallback->Error(MediaResult(NS_ERROR_OUT_OF_MEMORY, __func__)); + return NS_ERROR_OUT_OF_MEMORY; } // Y plane. buffer.mPlanes[0].mData = @@ -376,8 +376,8 @@ AppleVTDecoder::OutputFrame(CVPixelBufferRef aImage, if (!data) { NS_ERROR("Couldn't create VideoData for frame"); - mCallback->Error(MediaDataDecoderError::FATAL_ERROR); - return NS_ERROR_FAILURE; + mCallback->Error(MediaResult(NS_ERROR_OUT_OF_MEMORY, __func__)); + return NS_ERROR_OUT_OF_MEMORY; } // Frames come out in DTS order but we need to output them @@ -420,7 +420,7 @@ TimingInfoFromSample(MediaRawData* aSample) return timestamp; } -nsresult +MediaResult AppleVTDecoder::DoDecode(MediaRawData* aSample) { AssertOnTaskQueueThread(); @@ -446,13 +446,15 @@ AppleVTDecoder::DoDecode(MediaRawData* aSample) block.receive()); if (rv != noErr) { NS_ERROR("Couldn't create CMBlockBuffer"); - return NS_ERROR_FAILURE; + mCallback->Error(MediaResult(NS_ERROR_OUT_OF_MEMORY, __func__)); + return MediaResult(NS_ERROR_OUT_OF_MEMORY, __func__); } CMSampleTimingInfo timestamp = TimingInfoFromSample(aSample); rv = CMSampleBufferCreate(kCFAllocatorDefault, block, true, 0, 0, mFormat, 1, 1, ×tamp, 0, NULL, sample.receive()); if (rv != noErr) { NS_ERROR("Couldn't create CMSampleBuffer"); - return NS_ERROR_FAILURE; + mCallback->Error(MediaResult(NS_ERROR_OUT_OF_MEMORY, __func__)); + return MediaResult(NS_ERROR_OUT_OF_MEMORY, __func__); } VTDecodeFrameFlags decodeFlags = @@ -465,8 +467,8 @@ AppleVTDecoder::DoDecode(MediaRawData* aSample) if (rv != noErr && !(infoFlags & kVTDecodeInfo_FrameDropped)) { LOG("AppleVTDecoder: Error %d VTDecompressionSessionDecodeFrame", rv); NS_WARNING("Couldn't pass frame to decoder"); - mCallback->Error(MediaDataDecoderError::DECODE_ERROR); - return NS_ERROR_FAILURE; + mCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR, __func__)); + return NS_ERROR_DOM_MEDIA_DECODE_ERR; } return NS_OK; diff --git a/dom/media/platforms/apple/AppleVTDecoder.h b/dom/media/platforms/apple/AppleVTDecoder.h index 8305d4d5238659583e4aa6d6995b2fa410c4e5b3..05d08c7c79a8d1cbfbb393822cc16ef40b11b81d 100644 --- a/dom/media/platforms/apple/AppleVTDecoder.h +++ b/dom/media/platforms/apple/AppleVTDecoder.h @@ -96,7 +96,7 @@ private: CFDictionaryRef CreateDecoderSpecification(); CFDictionaryRef CreateDecoderExtensions(); // Method to pass a frame to VideoToolbox for decoding. - nsresult DoDecode(MediaRawData* aSample); + MediaResult DoDecode(MediaRawData* aSample); const RefPtr<TaskQueue> mTaskQueue; const uint32_t mMaxRefFrames; diff --git a/dom/media/platforms/ffmpeg/FFmpegAudioDecoder.cpp b/dom/media/platforms/ffmpeg/FFmpegAudioDecoder.cpp index 508f25ffc91e3e1df280271e70c2157bdbf3596a..6addd8b75b1d2c1d674fbd30d9ed2fe41c0fb74c 100644 --- a/dom/media/platforms/ffmpeg/FFmpegAudioDecoder.cpp +++ b/dom/media/platforms/ffmpeg/FFmpegAudioDecoder.cpp @@ -33,7 +33,7 @@ FFmpegAudioDecoder<LIBAV_VER>::Init() nsresult rv = InitDecoder(); return rv == NS_OK ? InitPromise::CreateAndResolve(TrackInfo::kAudioTrack, __func__) - : InitPromise::CreateAndReject(DecoderFailureReason::INIT_ERROR, __func__); + : InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); } void @@ -117,7 +117,7 @@ CopyAndPackAudio(AVFrame* aFrame, uint32_t aNumChannels, uint32_t aNumAFrames) return audio; } -FFmpegAudioDecoder<LIBAV_VER>::DecodeResult +MediaResult FFmpegAudioDecoder<LIBAV_VER>::DoDecode(MediaRawData* aSample) { AVPacket packet; @@ -128,12 +128,11 @@ FFmpegAudioDecoder<LIBAV_VER>::DoDecode(MediaRawData* aSample) if (!PrepareFrame()) { NS_WARNING("FFmpeg audio decoder failed to allocate frame."); - return DecodeResult::FATAL_ERROR; + return MediaResult(NS_ERROR_OUT_OF_MEMORY, __func__); } int64_t samplePosition = aSample->mOffset; media::TimeUnit pts = media::TimeUnit::FromMicroseconds(aSample->mTime); - bool didOutput = false; while (packet.size > 0) { int decoded; @@ -142,7 +141,7 @@ FFmpegAudioDecoder<LIBAV_VER>::DoDecode(MediaRawData* aSample) if (bytesConsumed < 0) { NS_WARNING("FFmpeg audio decoder error."); - return DecodeResult::DECODE_ERROR; + return NS_ERROR_DOM_MEDIA_DECODE_ERR; } if (mFrame->format != AV_SAMPLE_FMT_FLT && @@ -152,14 +151,14 @@ FFmpegAudioDecoder<LIBAV_VER>::DoDecode(MediaRawData* aSample) mFrame->format != AV_SAMPLE_FMT_S32 && mFrame->format != AV_SAMPLE_FMT_S32P) { NS_WARNING("FFmpeg audio decoder outputs unsupported audio format."); - return DecodeResult::DECODE_ERROR; + return NS_ERROR_DOM_MEDIA_DECODE_ERR; } if (decoded) { uint32_t numChannels = mCodecContext->channels; AudioConfig::ChannelLayout layout(numChannels); if (!layout.IsValid()) { - return DecodeResult::FATAL_ERROR; + return NS_ERROR_DOM_MEDIA_FATAL_ERR; } uint32_t samplingRate = mCodecContext->sample_rate; @@ -171,7 +170,7 @@ FFmpegAudioDecoder<LIBAV_VER>::DoDecode(MediaRawData* aSample) FramesToTimeUnit(mFrame->nb_samples, samplingRate); if (!audio || !duration.IsValid()) { NS_WARNING("Invalid count of accumulated audio samples"); - return DecodeResult::DECODE_ERROR; + return NS_ERROR_DOM_MEDIA_OVERFLOW_ERR; } RefPtr<AudioData> data = new AudioData(samplePosition, @@ -182,19 +181,17 @@ FFmpegAudioDecoder<LIBAV_VER>::DoDecode(MediaRawData* aSample) numChannels, samplingRate); mCallback->Output(data); - didOutput = true; pts += duration; if (!pts.IsValid()) { NS_WARNING("Invalid count of accumulated audio samples"); - return DecodeResult::DECODE_ERROR; + return NS_ERROR_DOM_MEDIA_OVERFLOW_ERR; } } packet.data += bytesConsumed; packet.size -= bytesConsumed; samplePosition += bytesConsumed; } - - return didOutput ? DecodeResult::DECODE_FRAME : DecodeResult::DECODE_NO_FRAME; + return NS_OK; } void diff --git a/dom/media/platforms/ffmpeg/FFmpegAudioDecoder.h b/dom/media/platforms/ffmpeg/FFmpegAudioDecoder.h index 2bf4647eacb414ba7271fa46967d645a9f3e7d25..6adaeee14024d54a9ce58ac7898416826d7380c4 100644 --- a/dom/media/platforms/ffmpeg/FFmpegAudioDecoder.h +++ b/dom/media/platforms/ffmpeg/FFmpegAudioDecoder.h @@ -35,7 +35,7 @@ public: } private: - DecodeResult DoDecode(MediaRawData* aSample) override; + MediaResult DoDecode(MediaRawData* aSample) override; void ProcessDrain() override; }; diff --git a/dom/media/platforms/ffmpeg/FFmpegDataDecoder.cpp b/dom/media/platforms/ffmpeg/FFmpegDataDecoder.cpp index f566c3c1472c50d09391266f07e9a62a915546b6..0b31fb0f94f2a9e52a93059373a47519b8bb415e 100644 --- a/dom/media/platforms/ffmpeg/FFmpegDataDecoder.cpp +++ b/dom/media/platforms/ffmpeg/FFmpegDataDecoder.cpp @@ -109,19 +109,11 @@ FFmpegDataDecoder<LIBAV_VER>::ProcessDecode(MediaRawData* aSample) if (mIsFlushing) { return; } - switch (DoDecode(aSample)) { - case DecodeResult::DECODE_ERROR: - mCallback->Error(MediaDataDecoderError::DECODE_ERROR); - break; - case DecodeResult::FATAL_ERROR: - mCallback->Error(MediaDataDecoderError::FATAL_ERROR); - break; - case DecodeResult::DECODE_NO_FRAME: - case DecodeResult::DECODE_FRAME: - mCallback->InputExhausted(); - break; - default: - break; + MediaResult rv = DoDecode(aSample); + if (NS_FAILED(rv)) { + mCallback->Error(rv); + } else { + mCallback->InputExhausted(); } } diff --git a/dom/media/platforms/ffmpeg/FFmpegDataDecoder.h b/dom/media/platforms/ffmpeg/FFmpegDataDecoder.h index ecd579cefeb9ad9f5656948540e583258a5060bf..f9ff9d3c41eafc40db03eef2e5005d73dd9f9854 100644 --- a/dom/media/platforms/ffmpeg/FFmpegDataDecoder.h +++ b/dom/media/platforms/ffmpeg/FFmpegDataDecoder.h @@ -40,13 +40,6 @@ public: static AVCodec* FindAVCodec(FFmpegLibWrapper* aLib, AVCodecID aCodec); protected: - enum DecodeResult { - DECODE_FRAME, - DECODE_NO_FRAME, - DECODE_ERROR, - FATAL_ERROR - }; - // Flush and Drain operation, always run virtual void ProcessFlush(); virtual void ProcessShutdown(); @@ -64,7 +57,7 @@ protected: private: void ProcessDecode(MediaRawData* aSample); - virtual DecodeResult DoDecode(MediaRawData* aSample) = 0; + virtual MediaResult DoDecode(MediaRawData* aSample) = 0; virtual void ProcessDrain() = 0; static StaticMutex sMonitor; diff --git a/dom/media/platforms/ffmpeg/FFmpegVideoDecoder.cpp b/dom/media/platforms/ffmpeg/FFmpegVideoDecoder.cpp index 87f01a6cbf0656e509984ad11bff82d5960d8997..3f516ebe9fcf61d2817bdf32a2092cf298dd8cb4 100644 --- a/dom/media/platforms/ffmpeg/FFmpegVideoDecoder.cpp +++ b/dom/media/platforms/ffmpeg/FFmpegVideoDecoder.cpp @@ -121,7 +121,7 @@ RefPtr<MediaDataDecoder::InitPromise> FFmpegVideoDecoder<LIBAV_VER>::Init() { if (NS_FAILED(InitDecoder())) { - return InitPromise::CreateAndReject(DecoderFailureReason::INIT_ERROR, __func__); + return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); } return InitPromise::CreateAndResolve(TrackInfo::kVideoTrack, __func__); @@ -161,8 +161,15 @@ FFmpegVideoDecoder<LIBAV_VER>::InitCodecContext() } } -FFmpegVideoDecoder<LIBAV_VER>::DecodeResult +MediaResult FFmpegVideoDecoder<LIBAV_VER>::DoDecode(MediaRawData* aSample) +{ + bool gotFrame = false; + return DoDecode(aSample, &gotFrame); +} + +MediaResult +FFmpegVideoDecoder<LIBAV_VER>::DoDecode(MediaRawData* aSample, bool* aGotFrame) { uint8_t* inputData = const_cast<uint8_t*>(aSample->Data()); size_t inputSize = aSample->Size(); @@ -173,7 +180,6 @@ FFmpegVideoDecoder<LIBAV_VER>::DoDecode(MediaRawData* aSample) || mCodecID == AV_CODEC_ID_VP9 #endif )) { - bool gotFrame = false; while (inputSize) { uint8_t* data; int size; @@ -182,31 +188,31 @@ FFmpegVideoDecoder<LIBAV_VER>::DoDecode(MediaRawData* aSample) aSample->mTime, aSample->mTimecode, aSample->mOffset); if (size_t(len) > inputSize) { - return DecodeResult::DECODE_ERROR; + return NS_ERROR_DOM_MEDIA_DECODE_ERR; } inputData += len; inputSize -= len; if (size) { - switch (DoDecode(aSample, data, size)) { - case DecodeResult::DECODE_ERROR: - return DecodeResult::DECODE_ERROR; - case DecodeResult::DECODE_FRAME: - gotFrame = true; - break; - default: - break; + bool gotFrame = false; + MediaResult rv = DoDecode(aSample, data, size, &gotFrame); + if (NS_FAILED(rv)) { + return rv; + } + if (gotFrame && aGotFrame) { + *aGotFrame = true; } } } - return gotFrame ? DecodeResult::DECODE_FRAME : DecodeResult::DECODE_NO_FRAME; + return NS_OK; } #endif - return DoDecode(aSample, inputData, inputSize); + return DoDecode(aSample, inputData, inputSize, aGotFrame); } -FFmpegVideoDecoder<LIBAV_VER>::DecodeResult +MediaResult FFmpegVideoDecoder<LIBAV_VER>::DoDecode(MediaRawData* aSample, - uint8_t* aData, int aSize) + uint8_t* aData, int aSize, + bool* aGotFrame) { AVPacket packet; mLib->av_init_packet(&packet); @@ -227,7 +233,7 @@ FFmpegVideoDecoder<LIBAV_VER>::DoDecode(MediaRawData* aSample, if (!PrepareFrame()) { NS_WARNING("FFmpeg h264 decoder failed to allocate frame."); - return DecodeResult::FATAL_ERROR; + return MediaResult(NS_ERROR_OUT_OF_MEMORY, __func__); } // Required with old version of FFmpeg/LibAV @@ -245,71 +251,78 @@ FFmpegVideoDecoder<LIBAV_VER>::DoDecode(MediaRawData* aSample, if (bytesConsumed < 0) { NS_WARNING("FFmpeg video decoder error."); - return DecodeResult::DECODE_ERROR; + return NS_ERROR_DOM_MEDIA_DECODE_ERR; } - // If we've decoded a frame then we need to output it - if (decoded) { - int64_t pts = mPtsContext.GuessCorrectPts(mFrame->pkt_pts, mFrame->pkt_dts); - // Retrieve duration from dts. - // We use the first entry found matching this dts (this is done to - // handle damaged file with multiple frames with the same dts) - - int64_t duration; - if (!mDurationMap.Find(mFrame->pkt_dts, duration)) { - NS_WARNING("Unable to retrieve duration from map"); - duration = aSample->mDuration; - // dts are probably incorrectly reported ; so clear the map as we're - // unlikely to find them in the future anyway. This also guards - // against the map becoming extremely big. - mDurationMap.Clear(); - } - FFMPEG_LOG("Got one frame output with pts=%lld dts=%lld duration=%lld opaque=%lld", - pts, mFrame->pkt_dts, duration, mCodecContext->reordered_opaque); - - VideoData::YCbCrBuffer b; - b.mPlanes[0].mData = mFrame->data[0]; - b.mPlanes[1].mData = mFrame->data[1]; - b.mPlanes[2].mData = mFrame->data[2]; - - b.mPlanes[0].mStride = mFrame->linesize[0]; - b.mPlanes[1].mStride = mFrame->linesize[1]; - b.mPlanes[2].mStride = mFrame->linesize[2]; - - b.mPlanes[0].mOffset = b.mPlanes[0].mSkip = 0; - b.mPlanes[1].mOffset = b.mPlanes[1].mSkip = 0; - b.mPlanes[2].mOffset = b.mPlanes[2].mSkip = 0; - - b.mPlanes[0].mWidth = mFrame->width; - b.mPlanes[0].mHeight = mFrame->height; - if (mCodecContext->pix_fmt == AV_PIX_FMT_YUV444P) { - b.mPlanes[1].mWidth = b.mPlanes[2].mWidth = mFrame->width; - b.mPlanes[1].mHeight = b.mPlanes[2].mHeight = mFrame->height; - } else { - b.mPlanes[1].mWidth = b.mPlanes[2].mWidth = (mFrame->width + 1) >> 1; - b.mPlanes[1].mHeight = b.mPlanes[2].mHeight = (mFrame->height + 1) >> 1; + if (!decoded) { + if (aGotFrame) { + *aGotFrame = false; } + return NS_OK; + } - RefPtr<VideoData> v = - VideoData::CreateAndCopyData(mInfo, - mImageContainer, - aSample->mOffset, - pts, - duration, - b, - !!mFrame->key_frame, - -1, - mInfo.ScaledImageRect(mFrame->width, - mFrame->height)); - - if (!v) { - NS_WARNING("image allocation error."); - return DecodeResult::FATAL_ERROR; - } - mCallback->Output(v); - return DecodeResult::DECODE_FRAME; + // If we've decoded a frame then we need to output it + int64_t pts = mPtsContext.GuessCorrectPts(mFrame->pkt_pts, mFrame->pkt_dts); + // Retrieve duration from dts. + // We use the first entry found matching this dts (this is done to + // handle damaged file with multiple frames with the same dts) + + int64_t duration; + if (!mDurationMap.Find(mFrame->pkt_dts, duration)) { + NS_WARNING("Unable to retrieve duration from map"); + duration = aSample->mDuration; + // dts are probably incorrectly reported ; so clear the map as we're + // unlikely to find them in the future anyway. This also guards + // against the map becoming extremely big. + mDurationMap.Clear(); + } + FFMPEG_LOG("Got one frame output with pts=%lld dts=%lld duration=%lld opaque=%lld", + pts, mFrame->pkt_dts, duration, mCodecContext->reordered_opaque); + + VideoData::YCbCrBuffer b; + b.mPlanes[0].mData = mFrame->data[0]; + b.mPlanes[1].mData = mFrame->data[1]; + b.mPlanes[2].mData = mFrame->data[2]; + + b.mPlanes[0].mStride = mFrame->linesize[0]; + b.mPlanes[1].mStride = mFrame->linesize[1]; + b.mPlanes[2].mStride = mFrame->linesize[2]; + + b.mPlanes[0].mOffset = b.mPlanes[0].mSkip = 0; + b.mPlanes[1].mOffset = b.mPlanes[1].mSkip = 0; + b.mPlanes[2].mOffset = b.mPlanes[2].mSkip = 0; + + b.mPlanes[0].mWidth = mFrame->width; + b.mPlanes[0].mHeight = mFrame->height; + if (mCodecContext->pix_fmt == AV_PIX_FMT_YUV444P) { + b.mPlanes[1].mWidth = b.mPlanes[2].mWidth = mFrame->width; + b.mPlanes[1].mHeight = b.mPlanes[2].mHeight = mFrame->height; + } else { + b.mPlanes[1].mWidth = b.mPlanes[2].mWidth = (mFrame->width + 1) >> 1; + b.mPlanes[1].mHeight = b.mPlanes[2].mHeight = (mFrame->height + 1) >> 1; } - return DecodeResult::DECODE_NO_FRAME; + + RefPtr<VideoData> v = + VideoData::CreateAndCopyData(mInfo, + mImageContainer, + aSample->mOffset, + pts, + duration, + b, + !!mFrame->key_frame, + -1, + mInfo.ScaledImageRect(mFrame->width, + mFrame->height)); + + if (!v) { + NS_WARNING("image allocation error."); + return MediaResult(NS_ERROR_OUT_OF_MEMORY, __func__); + } + mCallback->Output(v); + if (aGotFrame) { + *aGotFrame = true; + } + return NS_OK; } void @@ -317,8 +330,8 @@ FFmpegVideoDecoder<LIBAV_VER>::ProcessDrain() { RefPtr<MediaRawData> empty(new MediaRawData()); empty->mTimecode = mLastInputDts; - while (DoDecode(empty) == DecodeResult::DECODE_FRAME) { - } + bool gotFrame = false; + while (NS_SUCCEEDED(DoDecode(empty, &gotFrame)) && gotFrame); mCallback->DrainComplete(); } diff --git a/dom/media/platforms/ffmpeg/FFmpegVideoDecoder.h b/dom/media/platforms/ffmpeg/FFmpegVideoDecoder.h index a814a763af178bc20340a3eb1dc750810dc5fb9c..786df0da1afa2ee094854ffa92ece5f8fcd9daab 100644 --- a/dom/media/platforms/ffmpeg/FFmpegVideoDecoder.h +++ b/dom/media/platforms/ffmpeg/FFmpegVideoDecoder.h @@ -46,8 +46,9 @@ public: static AVCodecID GetCodecId(const nsACString& aMimeType); private: - DecodeResult DoDecode(MediaRawData* aSample) override; - DecodeResult DoDecode(MediaRawData* aSample, uint8_t* aData, int aSize); + MediaResult DoDecode(MediaRawData* aSample) override; + MediaResult DoDecode(MediaRawData* aSample, bool* aGotFrame); + MediaResult DoDecode(MediaRawData* aSample, uint8_t* aData, int aSize, bool* aGotFrame); void ProcessDrain() override; void ProcessFlush() override; void OutputDelayedFrames(); diff --git a/dom/media/platforms/gonk/GonkAudioDecoderManager.cpp b/dom/media/platforms/gonk/GonkAudioDecoderManager.cpp index 97334b61be3255538694817ff949d8af63e40eeb..0bc3fbea9af0077f0f55648efc4ac28d5b0bf3bd 100644 --- a/dom/media/platforms/gonk/GonkAudioDecoderManager.cpp +++ b/dom/media/platforms/gonk/GonkAudioDecoderManager.cpp @@ -57,7 +57,7 @@ GonkAudioDecoderManager::Init() if (InitMediaCodecProxy()) { return InitPromise::CreateAndResolve(TrackType::kAudioTrack, __func__); } else { - return InitPromise::CreateAndReject(DecoderFailureReason::INIT_ERROR, __func__); + return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); } } diff --git a/dom/media/platforms/gonk/GonkMediaDataDecoder.cpp b/dom/media/platforms/gonk/GonkMediaDataDecoder.cpp index 9cc05ed7c2a8432e9e605278233b9df71402ce0d..6d59d72e115a5b06c0d6c32d2cd0185b88a118b6 100644 --- a/dom/media/platforms/gonk/GonkMediaDataDecoder.cpp +++ b/dom/media/platforms/gonk/GonkMediaDataDecoder.cpp @@ -141,7 +141,7 @@ GonkDecoderManager::Shutdown() mDecoder = nullptr; } - mInitPromise.RejectIfExists(DecoderFailureReason::CANCELED, __func__); + mInitPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__); return NS_OK; } @@ -175,7 +175,8 @@ GonkDecoderManager::ProcessInput(bool aEndOfStream) } } else { GMDD_LOG("input processed: error#%d", rv); - mDecodeCallback->Error(MediaDataDecoderError::FATAL_ERROR); + mDecodeCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, + __func__)); } } @@ -189,7 +190,8 @@ GonkDecoderManager::ProcessFlush() mWaitOutput.Clear(); if (mDecoder->flush() != OK) { GMDD_LOG("flush error"); - mDecodeCallback->Error(MediaDataDecoderError::FATAL_ERROR); + mDecodeCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, + __func__)); } mIsFlushing = false; lock.NotifyAll(); @@ -225,7 +227,8 @@ GonkDecoderManager::ProcessToDo(bool aEndOfStream) mToDo.clear(); if (NumQueuedSamples() > 0 && ProcessQueuedSamples() < 0) { - mDecodeCallback->Error(MediaDataDecoderError::FATAL_ERROR); + mDecodeCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, + __func__)); return; } @@ -252,7 +255,8 @@ GonkDecoderManager::ProcessToDo(bool aEndOfStream) } else if (rv == NS_ERROR_NOT_AVAILABLE) { break; } else { - mDecodeCallback->Error(MediaDataDecoderError::FATAL_ERROR); + mDecodeCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, + __func__)); return; } } @@ -280,7 +284,8 @@ GonkDecoderManager::ResetEOS() mWaitOutput.Clear(); if (mDecoder->flush() != OK) { GMDD_LOG("flush error"); - mDecodeCallback->Error(MediaDataDecoderError::FATAL_ERROR); + mDecodeCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, + __func__)); } } diff --git a/dom/media/platforms/gonk/GonkMediaDataDecoder.h b/dom/media/platforms/gonk/GonkMediaDataDecoder.h index a5283e0adf185d13444185a112c18065c12ef137..bba2a8645d7a4286757fe35eb7aacbe0ed525c57 100644 --- a/dom/media/platforms/gonk/GonkMediaDataDecoder.h +++ b/dom/media/platforms/gonk/GonkMediaDataDecoder.h @@ -23,7 +23,6 @@ class GonkDecoderManager : public android::AHandler { public: typedef TrackInfo::TrackType TrackType; typedef MediaDataDecoder::InitPromise InitPromise; - typedef MediaDataDecoder::DecoderFailureReason DecoderFailureReason; virtual ~GonkDecoderManager() {} diff --git a/dom/media/platforms/gonk/GonkVideoDecoderManager.cpp b/dom/media/platforms/gonk/GonkVideoDecoderManager.cpp index d827390f947c78bb31c45f803f211454483a5174..23ac2d5c3aced99316f4123fe3dd36bbdbcde507 100644 --- a/dom/media/platforms/gonk/GonkVideoDecoderManager.cpp +++ b/dom/media/platforms/gonk/GonkVideoDecoderManager.cpp @@ -130,25 +130,25 @@ GonkVideoDecoderManager::Init() if (uint32_t(mConfig.mImage.width * mConfig.mImage.height) > maxWidth * maxHeight) { GVDM_LOG("Video resolution exceeds hw codec capability"); - return InitPromise::CreateAndReject(DecoderFailureReason::INIT_ERROR, __func__); + return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); } // Validate the container-reported frame and pictureRect sizes. This ensures // that our video frame creation code doesn't overflow. if (!IsValidVideoRegion(mConfig.mImage, mConfig.ImageRect(), mConfig.mDisplay)) { GVDM_LOG("It is not a valid region"); - return InitPromise::CreateAndReject(DecoderFailureReason::INIT_ERROR, __func__); + return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); } mReaderTaskQueue = AbstractThread::GetCurrent()->AsTaskQueue(); MOZ_ASSERT(mReaderTaskQueue); if (mDecodeLooper.get() != nullptr) { - return InitPromise::CreateAndReject(DecoderFailureReason::INIT_ERROR, __func__); + return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); } if (!InitLoopers(MediaData::VIDEO_DATA)) { - return InitPromise::CreateAndReject(DecoderFailureReason::INIT_ERROR, __func__); + return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); } RefPtr<InitPromise> p = mInitPromise.Ensure(__func__); @@ -672,7 +672,7 @@ GonkVideoDecoderManager::codecReserved() if (rv != OK) { GVDM_LOG("Failed to configure codec!!!!"); - mInitPromise.Reject(DecoderFailureReason::INIT_ERROR, __func__); + mInitPromise.Reject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); return; } @@ -683,7 +683,7 @@ void GonkVideoDecoderManager::codecCanceled() { GVDM_LOG("codecCanceled"); - mInitPromise.RejectIfExists(DecoderFailureReason::CANCELED, __func__); + mInitPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__); } // Called on GonkDecoderManager::mTaskLooper thread. diff --git a/dom/media/platforms/omx/OmxDataDecoder.cpp b/dom/media/platforms/omx/OmxDataDecoder.cpp index 1e12c13d6c93ea0a1eb93b43ee03df3053625b0c..33f9dad303f5cd3f39a45cd83ebc18d39b43796d 100644 --- a/dom/media/platforms/omx/OmxDataDecoder.cpp +++ b/dom/media/platforms/omx/OmxDataDecoder.cpp @@ -169,7 +169,7 @@ OmxDataDecoder::Init() MOZ_ASSERT(self->mOmxState != OMX_StateIdle); }, [self] () { - self->RejectInitPromise(DecoderFailureReason::INIT_ERROR, __func__); + self->RejectInitPromise(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); }); return p; @@ -430,9 +430,9 @@ OmxDataDecoder::EmptyBufferFailure(OmxBufferFailureHolder aFailureHolder) } void -OmxDataDecoder::NotifyError(OMX_ERRORTYPE aOmxError, const char* aLine, MediaDataDecoderError aError) +OmxDataDecoder::NotifyError(OMX_ERRORTYPE aOmxError, const char* aLine, const MediaResult& aError) { - LOG("NotifyError %d (%d) at %s", aOmxError, aError, aLine); + LOG("NotifyError %d (%d) at %s", aOmxError, aError.Code(), aLine); mCallback->Error(aError); } @@ -551,13 +551,13 @@ OmxDataDecoder::ResolveInitPromise(const char* aMethodName) } void -OmxDataDecoder::RejectInitPromise(DecoderFailureReason aReason, const char* aMethodName) +OmxDataDecoder::RejectInitPromise(MediaResult aError, const char* aMethodName) { RefPtr<OmxDataDecoder> self = this; nsCOMPtr<nsIRunnable> r = - NS_NewRunnableFunction([self, aReason, aMethodName] () { + NS_NewRunnableFunction([self, aError, aMethodName] () { MOZ_ASSERT(self->mReaderTaskQueue->IsCurrentThreadIn()); - self->mInitPromise.RejectIfExists(aReason, aMethodName); + self->mInitPromise.RejectIfExists(aError, aMethodName); }); mReaderTaskQueue->Dispatch(r.forget()); } @@ -583,7 +583,7 @@ OmxDataDecoder::OmxStateRunner() MOZ_ASSERT(self->mOmxState == OMX_StateIdle); }, [self] () { - self->RejectInitPromise(DecoderFailureReason::INIT_ERROR, __func__); + self->RejectInitPromise(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); }); // Allocate input and output buffers. @@ -591,7 +591,7 @@ OmxDataDecoder::OmxStateRunner() for(const auto id : types) { if (NS_FAILED(AllocateBuffers(id))) { LOG("Failed to allocate buffer on port %d", id); - RejectInitPromise(DecoderFailureReason::INIT_ERROR, __func__); + RejectInitPromise(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); break; } } @@ -606,7 +606,7 @@ OmxDataDecoder::OmxStateRunner() self->ResolveInitPromise(__func__); }, [self] () { - self->RejectInitPromise(DecoderFailureReason::INIT_ERROR, __func__); + self->RejectInitPromise(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); }); } else if (mOmxState == OMX_StateExecuting) { // Configure codec once it gets OMX_StateExecuting state. @@ -690,7 +690,8 @@ OmxDataDecoder::Event(OMX_EVENTTYPE aEvent, OMX_U32 aData1, OMX_U32 aData2) { // Got error during decoding, send msg to MFR skipping to next key frame. if (aEvent == OMX_EventError && mOmxState == OMX_StateExecuting) { - NotifyError((OMX_ERRORTYPE)aData1, __func__, MediaDataDecoderError::DECODE_ERROR); + NotifyError((OMX_ERRORTYPE)aData1, __func__, + MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR, __func__)); return true; } LOG("WARNING: got none handle event: %d, aData1: %d, aData2: %d", diff --git a/dom/media/platforms/omx/OmxDataDecoder.h b/dom/media/platforms/omx/OmxDataDecoder.h index 53700544aa0d48d6520db83eef52c4c9c669fb9d..ea75b2a2ac75d372dcd05c45a1ccc8e9fe951c26 100644 --- a/dom/media/platforms/omx/OmxDataDecoder.h +++ b/dom/media/platforms/omx/OmxDataDecoder.h @@ -87,7 +87,7 @@ protected: void ResolveInitPromise(const char* aMethodName); - void RejectInitPromise(DecoderFailureReason aReason, const char* aMethodName); + void RejectInitPromise(MediaResult aError, const char* aMethodName); void OmxStateRunner(); @@ -103,7 +103,7 @@ protected: void NotifyError(OMX_ERRORTYPE aOmxError, const char* aLine, - MediaDataDecoderError aError = MediaDataDecoderError::FATAL_ERROR); + const MediaResult& aError = MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR)); // Configure audio/video codec. // Some codec may just ignore this and rely on codec specific data in diff --git a/dom/media/platforms/wmf/WMFMediaDataDecoder.cpp b/dom/media/platforms/wmf/WMFMediaDataDecoder.cpp index 8b9423c4ae5f60b677a13cfd6294e0503ce93408..72087cb53d9f28687cdc9160648695d81117f8e4 100644 --- a/dom/media/platforms/wmf/WMFMediaDataDecoder.cpp +++ b/dom/media/platforms/wmf/WMFMediaDataDecoder.cpp @@ -123,7 +123,7 @@ WMFMediaDataDecoder::ProcessDecode(MediaRawData* aSample) HRESULT hr = mMFTManager->Input(aSample); if (FAILED(hr)) { NS_WARNING("MFTManager rejected sample"); - mCallback->Error(MediaDataDecoderError::DECODE_ERROR); + mCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR, __func__)); if (!mRecordedError) { SendTelemetry(hr); mRecordedError = true; @@ -150,7 +150,7 @@ WMFMediaDataDecoder::ProcessOutput() mCallback->InputExhausted(); } else if (FAILED(hr)) { NS_WARNING("WMFMediaDataDecoder failed to output data"); - mCallback->Error(MediaDataDecoderError::DECODE_ERROR); + mCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR, __func__)); if (!mRecordedError) { SendTelemetry(hr); mRecordedError = true; diff --git a/dom/media/platforms/wrappers/FuzzingWrapper.cpp b/dom/media/platforms/wrappers/FuzzingWrapper.cpp index 0cbaf2741cad1310be53a52b7fb1a847d58de188..bf5a5f68b7af732d99c1ecfc417883e5ab5c7903 100644 --- a/dom/media/platforms/wrappers/FuzzingWrapper.cpp +++ b/dom/media/platforms/wrappers/FuzzingWrapper.cpp @@ -171,19 +171,17 @@ DecoderCallbackFuzzingWrapper::Output(MediaData* aData) } void -DecoderCallbackFuzzingWrapper::Error(MediaDataDecoderError aError) +DecoderCallbackFuzzingWrapper::Error(const MediaResult& aError) { if (!mTaskQueue->IsCurrentThreadIn()) { - mTaskQueue->Dispatch( - NewRunnableMethod<MediaDataDecoderError>(this, - &DecoderCallbackFuzzingWrapper::Error, - aError)); + mTaskQueue->Dispatch(NewRunnableMethod<MediaResult>( + this, &DecoderCallbackFuzzingWrapper::Error, aError)); return; } CFW_LOGV(""); MOZ_ASSERT(mCallback); ClearDelayedOutput(); - mCallback->Error(MediaDataDecoderError::FATAL_ERROR); + mCallback->Error(aError); } void diff --git a/dom/media/platforms/wrappers/FuzzingWrapper.h b/dom/media/platforms/wrappers/FuzzingWrapper.h index 9a012d515c23f1b24efa326b3cce481d91d196c5..d2898a50f51c307b454d9b332bdba60c3dce4a8c 100644 --- a/dom/media/platforms/wrappers/FuzzingWrapper.h +++ b/dom/media/platforms/wrappers/FuzzingWrapper.h @@ -60,7 +60,7 @@ private: // MediaDataDecoderCallback implementation. void Output(MediaData* aData) override; - void Error(MediaDataDecoderError aError) override; + void Error(const MediaResult& aError) override; void InputExhausted() override; void DrainComplete() override; void ReleaseMediaResources() override; diff --git a/dom/media/platforms/wrappers/H264Converter.cpp b/dom/media/platforms/wrappers/H264Converter.cpp index b00c50b74cfe87bb8edccd732a1b3da1f2718110..25e6e8804d7e9683f5c3beccac43ce984400560d 100644 --- a/dom/media/platforms/wrappers/H264Converter.cpp +++ b/dom/media/platforms/wrappers/H264Converter.cpp @@ -55,7 +55,7 @@ H264Converter::Input(MediaRawData* aSample) if (!mp4_demuxer::AnnexB::ConvertSampleToAVCC(aSample)) { // We need AVCC content to be able to later parse the SPS. // This is a no-op if the data is already AVCC. - mCallback->Error(MediaDataDecoderError::DECODE_ERROR); + mCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__)); return; } @@ -88,7 +88,7 @@ H264Converter::Input(MediaRawData* aSample) rv = CheckForSPSChange(aSample); } if (NS_FAILED(rv)) { - mCallback->Error(MediaDataDecoderError::DECODE_ERROR); + mCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__)); return; } @@ -99,7 +99,7 @@ H264Converter::Input(MediaRawData* aSample) if (!mNeedAVCC && !mp4_demuxer::AnnexB::ConvertSampleToAnnexB(aSample)) { - mCallback->Error(MediaDataDecoderError::FATAL_ERROR); + mCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__)); return; } @@ -249,10 +249,11 @@ H264Converter::OnDecoderInitDone(const TrackType aTrackType) } void -H264Converter::OnDecoderInitFailed(MediaDataDecoder::DecoderFailureReason aReason) +H264Converter::OnDecoderInitFailed(MediaResult aError) { mInitPromiseRequest.Complete(); - mCallback->Error(MediaDataDecoderError::FATAL_ERROR); + mCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, + __func__)); } nsresult diff --git a/dom/media/platforms/wrappers/H264Converter.h b/dom/media/platforms/wrappers/H264Converter.h index 1db44ce90eaf9054d0cd41e7c426f055e0e0665e..ab60d8dea678bb32a02ad001297f2246ee35c214 100644 --- a/dom/media/platforms/wrappers/H264Converter.h +++ b/dom/media/platforms/wrappers/H264Converter.h @@ -51,7 +51,7 @@ private: void UpdateConfigFromExtraData(MediaByteBuffer* aExtraData); void OnDecoderInitDone(const TrackType aTrackType); - void OnDecoderInitFailed(MediaDataDecoder::DecoderFailureReason aReason); + void OnDecoderInitFailed(MediaResult aError); RefPtr<PlatformDecoderModule> mPDM; VideoInfo mOriginalConfig; diff --git a/dom/media/test/test_decode_error.html b/dom/media/test/test_decode_error.html index 58d4c429997e81c3ec07ff9297586ccf6dbe0424..2a5d9998c3b582ac5dbb65d560a49f16696de738 100644 --- a/dom/media/test/test_decode_error.html +++ b/dom/media/test/test_decode_error.html @@ -27,6 +27,8 @@ function startTest(test, token) { ok(el.error, "Element 'error' attr expected to have a value"); ok(el.error instanceof MediaError, "Element 'error' attr expected to be MediaError"); is(el.error.code, MediaError.MEDIA_ERR_DECODE, "Expected a decode error"); + ok(typeof el.error.message === 'string' || el.error.essage instanceof String, "Element 'message' attr expected to be a string"); + ok(el.error.message.length > 0, "Element 'message' attr has content"); el._sawError = true; manager.finished(token); }, false); @@ -46,7 +48,12 @@ function startTest(test, token) { } SimpleTest.waitForExplicitFinish(); -SpecialPowers.pushPrefEnv({"set": [["media.cache_size", 40000]]}, beginTest); +SpecialPowers.pushPrefEnv({ + "set": [ + ["media.cache_size", 40000], + ["dom.MediaError.message.enabled", true] + ] +}, beginTest); function beginTest() { manager.runTests(gDecodeErrorTests, startTest); } diff --git a/dom/media/wave/WaveDemuxer.cpp b/dom/media/wave/WaveDemuxer.cpp index 2b38d23e7048ba42b8eb7276d91bc5c168fdb440..23aba9a04ca6c6dc8c797d5f5114464d5f0e8bf3 100644 --- a/dom/media/wave/WaveDemuxer.cpp +++ b/dom/media/wave/WaveDemuxer.cpp @@ -43,7 +43,7 @@ WAVDemuxer::Init() { if (!InitInternal()) { return InitPromise::CreateAndReject( - DemuxerFailureReason::DEMUXER_ERROR, __func__); + NS_ERROR_DOM_MEDIA_METADATA_ERR, __func__); } return InitPromise::CreateAndResolve(NS_OK, __func__); } @@ -340,10 +340,7 @@ WAVTrackDemuxer::ScanUntil(const TimeUnit& aTime) RefPtr<WAVTrackDemuxer::SamplesPromise> WAVTrackDemuxer::GetSamples(int32_t aNumSamples) { - if (!aNumSamples) { - return SamplesPromise::CreateAndReject( - DemuxerFailureReason::DEMUXER_ERROR, __func__); - } + MOZ_ASSERT(aNumSamples); RefPtr<SamplesHolder> datachunks = new SamplesHolder(); @@ -357,7 +354,7 @@ WAVTrackDemuxer::GetSamples(int32_t aNumSamples) if (datachunks->mSamples.IsEmpty()) { return SamplesPromise::CreateAndReject( - DemuxerFailureReason::END_OF_STREAM, __func__); + NS_ERROR_DOM_MEDIA_END_OF_STREAM, __func__); } return SamplesPromise::CreateAndResolve(datachunks, __func__); @@ -377,7 +374,7 @@ RefPtr<WAVTrackDemuxer::SkipAccessPointPromise> WAVTrackDemuxer::SkipToNextRandomAccessPoint(TimeUnit aTimeThreshold) { return SkipAccessPointPromise::CreateAndReject( - SkipFailureHolder(DemuxerFailureReason::DEMUXER_ERROR, 0), __func__); + SkipFailureHolder(NS_ERROR_DOM_MEDIA_DEMUXER_ERR, 0), __func__); } int64_t diff --git a/dom/media/webaudio/MediaBufferDecoder.cpp b/dom/media/webaudio/MediaBufferDecoder.cpp index a3b5eb72165a44ed6034550dcd81844c4c1bb6c2..e9f1d5a47c721a32f30c17b8f8309a980c10aada 100644 --- a/dom/media/webaudio/MediaBufferDecoder.cpp +++ b/dom/media/webaudio/MediaBufferDecoder.cpp @@ -132,10 +132,10 @@ private: void Decode(); void OnMetadataRead(MetadataHolder* aMetadata); - void OnMetadataNotRead(ReadMetadataFailureReason aReason); + void OnMetadataNotRead(const MediaResult& aError); void RequestSample(); void SampleDecoded(MediaData* aData); - void SampleNotDecoded(MediaDecoderReader::NotDecodedReason aReason); + void SampleNotDecoded(const MediaResult& aError); void FinishDecode(); void AllocateBuffer(); void CallbackTheResult(); @@ -310,7 +310,7 @@ MediaDecodeTask::OnMetadataRead(MetadataHolder* aMetadata) } void -MediaDecodeTask::OnMetadataNotRead(ReadMetadataFailureReason aReason) +MediaDecodeTask::OnMetadataNotRead(const MediaResult& aReason) { mDecoderReader->Shutdown(); ReportFailureOnMainThread(WebAudioDecodeJob::InvalidContent); @@ -337,15 +337,14 @@ MediaDecodeTask::SampleDecoded(MediaData* aData) } void -MediaDecodeTask::SampleNotDecoded(MediaDecoderReader::NotDecodedReason aReason) +MediaDecodeTask::SampleNotDecoded(const MediaResult& aError) { MOZ_ASSERT(!NS_IsMainThread()); - if (aReason == MediaDecoderReader::DECODE_ERROR) { + if (aError == NS_ERROR_DOM_MEDIA_END_OF_STREAM) { + FinishDecode(); + } else { mDecoderReader->Shutdown(); ReportFailureOnMainThread(WebAudioDecodeJob::InvalidContent); - } else { - MOZ_ASSERT(aReason == MediaDecoderReader::END_OF_STREAM); - FinishDecode(); } } diff --git a/dom/media/webm/WebMDemuxer.cpp b/dom/media/webm/WebMDemuxer.cpp index ae729ff541eb40f17edab6a3847cb70bf95095cc..2f39daab7558949851542b78c3c7d51fa020ae22 100644 --- a/dom/media/webm/WebMDemuxer.cpp +++ b/dom/media/webm/WebMDemuxer.cpp @@ -188,12 +188,12 @@ WebMDemuxer::Init() InitBufferedState(); if (NS_FAILED(ReadMetadata())) { - return InitPromise::CreateAndReject(DemuxerFailureReason::DEMUXER_ERROR, __func__); + return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_METADATA_ERR, __func__); } if (!GetNumberTracks(TrackInfo::kAudioTrack) && !GetNumberTracks(TrackInfo::kVideoTrack)) { - return InitPromise::CreateAndReject(DemuxerFailureReason::DEMUXER_ERROR, __func__); + return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_METADATA_ERR, __func__); } return InitPromise::CreateAndResolve(NS_OK, __func__); @@ -955,9 +955,7 @@ RefPtr<WebMTrackDemuxer::SamplesPromise> WebMTrackDemuxer::GetSamples(int32_t aNumSamples) { RefPtr<SamplesHolder> samples = new SamplesHolder; - if (!aNumSamples) { - return SamplesPromise::CreateAndReject(DemuxerFailureReason::DEMUXER_ERROR, __func__); - } + MOZ_ASSERT(aNumSamples); while (aNumSamples) { RefPtr<MediaRawData> sample(NextSample()); @@ -973,7 +971,7 @@ WebMTrackDemuxer::GetSamples(int32_t aNumSamples) } if (samples->mSamples.IsEmpty()) { - return SamplesPromise::CreateAndReject(DemuxerFailureReason::END_OF_STREAM, __func__); + return SamplesPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_END_OF_STREAM, __func__); } else { UpdateSamples(samples->mSamples); return SamplesPromise::CreateAndResolve(samples, __func__); @@ -1109,7 +1107,7 @@ WebMTrackDemuxer::SkipToNextRandomAccessPoint(media::TimeUnit aTimeThreshold) parsed); return SkipAccessPointPromise::CreateAndResolve(parsed, __func__); } else { - SkipFailureHolder failure(DemuxerFailureReason::END_OF_STREAM, parsed); + SkipFailureHolder failure(NS_ERROR_DOM_MEDIA_END_OF_STREAM, parsed); return SkipAccessPointPromise::CreateAndReject(Move(failure), __func__); } } diff --git a/dom/webidl/MediaError.webidl b/dom/webidl/MediaError.webidl index 6ee572067d79964cb86e0ccc25a22a61282f2603..3b8a0bbe948ff0a31f30557fbdc30edc92dbcd81 100644 --- a/dom/webidl/MediaError.webidl +++ b/dom/webidl/MediaError.webidl @@ -19,4 +19,6 @@ interface MediaError { [Constant] readonly attribute unsigned short code; + [Pref="dom.MediaError.message.enabled"] + readonly attribute DOMString message; }; diff --git a/ipc/glue/GeckoChildProcessHost.cpp b/ipc/glue/GeckoChildProcessHost.cpp index d4f323dcbac2df990b97d675507b520c71fdcdac..3431f628e3077fde8d2096b6ec59600097d19c9e 100644 --- a/ipc/glue/GeckoChildProcessHost.cpp +++ b/ipc/glue/GeckoChildProcessHost.cpp @@ -23,6 +23,10 @@ #include "prenv.h" #include "nsXPCOMPrivate.h" +#if defined(XP_MACOSX) && defined(MOZ_CONTENT_SANDBOX) +#include "nsAppDirectoryServiceDefs.h" +#endif + #include "nsExceptionHandler.h" #include "nsDirectoryServiceDefs.h" @@ -608,6 +612,20 @@ AddAppDirToCommandLine(std::vector<std::string>& aCmdLine) aCmdLine.push_back(path.get()); #endif } + +#if defined(XP_MACOSX) && defined(MOZ_CONTENT_SANDBOX) + // Full path to the profile dir + nsCOMPtr<nsIFile> profileDir; + rv = directoryService->Get(NS_APP_USER_PROFILE_50_DIR, + NS_GET_IID(nsIFile), + getter_AddRefs(profileDir)); + if (NS_SUCCEEDED(rv)) { + nsAutoCString path; + MOZ_ALWAYS_SUCCEEDS(profileDir->GetNativePath(path)); + aCmdLine.push_back("-profile"); + aCmdLine.push_back(path.get()); + } +#endif } } } diff --git a/js/src/devtools/automation/winbuildenv.sh b/js/src/devtools/automation/winbuildenv.sh index 3467e4ce96f1252b37fb44364cfbbdcd90176e02..f9d862ac45d926979234dfd08439445bd8073f64 100644 --- a/js/src/devtools/automation/winbuildenv.sh +++ b/js/src/devtools/automation/winbuildenv.sh @@ -10,7 +10,7 @@ topsrcdir="$SOURCE" # Tooltool installs in parent of topsrcdir for spidermonkey builds. # Resolve that path since the mozconfigs assume tooltool installs in # topsrcdir. -VSPATH="$(cd ${topsrcdir}/.. && pwd)/vs2015u2" +VSPATH="$(cd ${topsrcdir}/.. && pwd)/vs2015u3" # When running on a developer machine, several variables will already # have the right settings and we will need to keep them since the diff --git a/js/src/old-configure.in b/js/src/old-configure.in index 1cb4204aeeb3ff3e87d537fe43d78a90652d5935..9ff3a842ef7d7330dc2d10508935d2d8e9ce8fda 100644 --- a/js/src/old-configure.in +++ b/js/src/old-configure.in @@ -791,6 +791,10 @@ case "$target" in # use such pragmas, so just ignore them. CFLAGS="$CFLAGS -Wno-unknown-pragmas" CXXFLAGS="$CXXFLAGS -Wno-unknown-pragmas" + # We get errors about various #pragma intrinsic directives from + # clang-cl, and we don't need to hear about those. + CFLAGS="$CFLAGS -Wno-ignored-pragmas" + CXXFLAGS="$CXXFLAGS -Wno-ignored-pragmas" # clang-cl's Intrin.h marks things like _ReadWriteBarrier # as __attribute((__deprecated__)). This is nice to know, # but since we don't get the equivalent warning from MSVC, diff --git a/layout/generic/nsSubDocumentFrame.cpp b/layout/generic/nsSubDocumentFrame.cpp index 6439347a0a21d9c04ea30851fdd86e0e778d119a..d87d8d70b28174b21c505725343bb2ccdc615bcb 100644 --- a/layout/generic/nsSubDocumentFrame.cpp +++ b/layout/generic/nsSubDocumentFrame.cpp @@ -41,10 +41,13 @@ #include "nsIPermissionManager.h" #include "nsServiceManagerUtils.h" #include "nsIDOMMutationEvent.h" +#include "mozilla/Preferences.h" using namespace mozilla; using mozilla::layout::RenderFrameParent; +static bool sShowPreviousPage = true; + static nsIDocument* GetDocumentFromView(nsView* aView) { @@ -107,6 +110,12 @@ nsSubDocumentFrame::Init(nsIContent* aContent, nsCOMPtr<nsIDOMHTMLFrameElement> frameElem = do_QueryInterface(aContent); mIsInline = frameElem ? false : true; + static bool addedShowPreviousPage = false; + if (!addedShowPreviousPage) { + Preferences::AddBoolVarCache(&sShowPreviousPage, "layout.show_previous_page", true); + addedShowPreviousPage = true; + } + nsAtomicContainerFrame::Init(aContent, aParent, aPrevInFlow); // We are going to create an inner view. If we need a view for the @@ -227,7 +236,7 @@ nsSubDocumentFrame::GetSubdocumentPresShellForPainting(uint32_t aFlags) } if (frame) { nsIPresShell* ps = frame->PresContext()->PresShell(); - if (!presShell || (ps && !ps->IsPaintingSuppressed())) { + if (!presShell || (ps && !ps->IsPaintingSuppressed() && sShowPreviousPage)) { subdocView = nextView; subdocRootFrame = frame; presShell = ps; diff --git a/media/mtransport/test/ice_unittest.cpp b/media/mtransport/test/ice_unittest.cpp index 48fab1225a4702a0eea3b1608c8c8d2c57ee13fa..62d54c4637d70f9e2be8cce504d83a966d56d8df 100644 --- a/media/mtransport/test/ice_unittest.cpp +++ b/media/mtransport/test/ice_unittest.cpp @@ -2880,6 +2880,24 @@ TEST_F(WebRtcIceConnectTest, TestConnectSymmetricNat) { Connect(); } +TEST_F(WebRtcIceConnectTest, TestConnectSymmetricNatAndNoNat) { + p1_ = MakeUnique<IceTestPeer>("P1", test_utils_, true, false, false); + p1_->UseNat(); + p1_->SetFilteringType(TestNat::PORT_DEPENDENT); + p1_->SetMappingType(TestNat::PORT_DEPENDENT); + + p2_ = MakeUnique<IceTestPeer>("P2", test_utils_, false, false, false); + initted_ = true; + + AddStream(1); + p1_->SetExpectedTypes(NrIceCandidate::Type::ICE_PEER_REFLEXIVE, + NrIceCandidate::Type::ICE_HOST); + p2_->SetExpectedTypes(NrIceCandidate::Type::ICE_HOST, + NrIceCandidate::Type::ICE_PEER_REFLEXIVE); + ASSERT_TRUE(Gather()); + Connect(); +} + TEST_F(WebRtcIceConnectTest, TestGatherNatBlocksUDP) { if (turn_server_.empty()) return; diff --git a/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java b/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java index 5370869d1dd5c76f9165acb87da4999f31e11f2e..eec54d78f7eff01184e9834fb653e7c0d0367005 100644 --- a/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java +++ b/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java @@ -2521,6 +2521,11 @@ public abstract class GeckoApp if (tab.isExternal()) { moveTaskToBack(true); + Tab nextSelectedTab = Tabs.getInstance().getNextTab(tab); + if (nextSelectedTab != null) { + int nextSelectedTabId = nextSelectedTab.getId(); + GeckoAppShell.notifyObservers("Tab:KeepZombified", Integer.toString(nextSelectedTabId)); + } tabs.closeTab(tab); return; } diff --git a/mobile/android/base/java/org/mozilla/gecko/tabqueue/TabReceivedService.java b/mobile/android/base/java/org/mozilla/gecko/tabqueue/TabReceivedService.java index e56ac5f322d7d4dc020b1ad0cf5087a6b3b0491c..4f5baacdbdf56a96094d3b4aa38ee8b878225a11 100644 --- a/mobile/android/base/java/org/mozilla/gecko/tabqueue/TabReceivedService.java +++ b/mobile/android/base/java/org/mozilla/gecko/tabqueue/TabReceivedService.java @@ -16,6 +16,8 @@ import android.content.Intent; import android.content.SharedPreferences; import android.content.res.Resources; import android.database.Cursor; +import android.media.RingtoneManager; +import android.net.Uri; import android.support.annotation.Nullable; import android.support.annotation.WorkerThread; import android.support.v4.app.NotificationCompat; @@ -67,6 +69,13 @@ public class TabReceivedService extends IntentService { builder.setContentText(uri); builder.setContentIntent(contentIntent); + // Trigger "heads-up" notification mode on supported Android versions. + builder.setPriority(NotificationCompat.PRIORITY_HIGH); + final Uri notificationSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION); + if (notificationSoundUri != null) { + builder.setSound(notificationSoundUri); + } + final SharedPreferences prefs = GeckoSharedPrefs.forApp(this); final int notificationId = getNextNotificationId(prefs.getInt(PREF_NOTIFICATION_ID, 0)); final NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this); diff --git a/mobile/android/components/LoginManagerPrompter.js b/mobile/android/components/LoginManagerPrompter.js index de4c7616f44b3d431adcc2dc4d9eeeb3a75111c4..e70afbe147c4f232a32ce2e7b7f8bf3cbd1948f1 100644 --- a/mobile/android/components/LoginManagerPrompter.js +++ b/mobile/android/components/LoginManagerPrompter.js @@ -129,6 +129,7 @@ LoginManagerPrompter.prototype = { promptToSavePassword : function (aLogin) { this._showSaveLoginNotification(aLogin); Services.telemetry.getHistogramById("PWMGR_PROMPT_REMEMBER_ACTION").add(PROMPT_DISPLAYED); + Services.obs.notifyObservers(aLogin, "passwordmgr-prompt-save", null); }, /* @@ -228,6 +229,8 @@ LoginManagerPrompter.prototype = { promptToChangePassword : function (aOldLogin, aNewLogin) { this._showChangeLoginNotification(aOldLogin, aNewLogin.password); Services.telemetry.getHistogramById("PWMGR_PROMPT_UPDATE_ACTION").add(PROMPT_DISPLAYED); + let oldGUID = aOldLogin.QueryInterface(Ci.nsILoginMetaInfo).guid; + Services.obs.notifyObservers(aNewLogin, "passwordmgr-prompt-change", oldGUID); }, /* diff --git a/mobile/android/components/SessionStore.js b/mobile/android/components/SessionStore.js index f56b22bdc6a586d33240ad17186b54f975be54c5..5bfd05410894082d104a6cf4d04172f4818ffdf2 100644 --- a/mobile/android/components/SessionStore.js +++ b/mobile/android/components/SessionStore.js @@ -86,6 +86,11 @@ SessionStore.prototype = { // Whether or not to send notifications for changes to the closed tabs. _notifyClosedTabs: false, + // If we're simultaneously closing both a tab and Firefox, we don't want + // to bother reloading the newly selected tab if it is zombified. + // The Java UI will tell us which tab to watch out for. + _keepAsZombieTabId: -1, + init: function ss_init() { loggingEnabled = Services.prefs.getBoolPref("browser.sessionstore.debug_logging"); @@ -141,6 +146,7 @@ SessionStore.prototype = { observerService.addObserver(this, "quit-application", true); observerService.addObserver(this, "Session:Restore", true); observerService.addObserver(this, "Session:NotifyLocationChange", true); + observerService.addObserver(this, "Tab:KeepZombified", true); observerService.addObserver(this, "application-background", true); observerService.addObserver(this, "application-foreground", true); observerService.addObserver(this, "ClosedTabs:StartNotifications", true); @@ -284,6 +290,13 @@ SessionStore.prototype = { } break; } + case "Tab:KeepZombified": { + if (aData >= 0) { + this._keepAsZombieTabId = aData; + log("Tab:KeepZombified " + aData); + } + break; + } case "application-background": // We receive this notification when Android's onPause callback is // executed. After onPause, the application may be terminated at any @@ -301,6 +314,15 @@ SessionStore.prototype = { log("application-foreground"); this._interval = Services.prefs.getIntPref("browser.sessionstore.interval"); this._minSaveDelay = MINIMUM_SAVE_DELAY; + + // If we skipped restoring a zombified tab before backgrounding, + // we might have to do it now instead. + let window = Services.wm.getMostRecentWindow("navigator:browser"); + let tab = window.BrowserApp.selectedTab; + + if (tab.browser.__SS_restore) { + this._restoreZombieTab(tab.browser, tab.id); + } break; case "ClosedTabs:StartNotifications": this._notifyClosedTabs = true; @@ -388,7 +410,8 @@ SessionStore.prototype = { } break; } - case "pageshow": { + case "pageshow": + case "AboutReaderContentReady": { let browser = aEvent.currentTarget; // Skip subframe pageshows. @@ -396,12 +419,19 @@ SessionStore.prototype = { return; } + if (browser.currentURI.spec.startsWith("about:reader") && + !browser.contentDocument.body.classList.contains("loaded")) { + // Don't restore the scroll position of an about:reader page at this point; + // wait for the custom event dispatched from AboutReader.jsm instead. + return; + } + // Restoring the scroll position needs to happen after the zoom level has been // restored, which is done by the MobileViewportManager either on first paint // or on load, whichever comes first. // In the latter case, our load handler runs before the MVM's one, which is the // wrong way around, so we have to use a later event instead. - log("pageshow for tab " + window.BrowserApp.getTabForBrowser(browser).id); + log(aEvent.type + " for tab " + window.BrowserApp.getTabForBrowser(browser).id); if (browser.__SS_restoreDataOnPageshow) { delete browser.__SS_restoreDataOnPageshow; this._restoreScrollPosition(browser.__SS_data.scrolldata, browser); @@ -514,6 +544,7 @@ SessionStore.prototype = { // Gecko might set the initial zoom level after the JS "load" event, // so we have to restore zoom and scroll position after that. aBrowser.addEventListener("pageshow", this, true); + aBrowser.addEventListener("AboutReaderContentReady", this, true); // Use a combination of events to watch for text data changes aBrowser.addEventListener("change", this, true); @@ -537,6 +568,7 @@ SessionStore.prototype = { aBrowser.removeEventListener("DOMTitleChanged", this, true); aBrowser.removeEventListener("load", this, true); aBrowser.removeEventListener("pageshow", this, true); + aBrowser.removeListener("AboutReaderContentReady", this, true); aBrowser.removeEventListener("change", this, true); aBrowser.removeEventListener("input", this, true); aBrowser.removeEventListener("DOMAutoComplete", this, true); @@ -657,13 +689,14 @@ SessionStore.prototype = { // Restore the resurrected browser if (aBrowser.__SS_restore) { - let data = aBrowser.__SS_data; - this._restoreTab(data, aBrowser); - - delete aBrowser.__SS_restore; - aBrowser.removeAttribute("pending"); - log("onTabSelect() restored zombie tab " + tabId); + if (tabId != this._keepAsZombieTabId) { + this._restoreZombieTab(aBrowser, tabId); + } else { + log("keeping as zombie tab " + tabId); + } } + // The tab id passed through Tab:KeepZombified is valid for one TabSelect only. + this._keepAsZombieTabId = -1; log("onTabSelect() ran for tab " + tabId); this.saveStateDelayed(); @@ -677,6 +710,15 @@ SessionStore.prototype = { } }, + _restoreZombieTab: function ss_restoreZombieTab(aBrowser, aTabId) { + let data = aBrowser.__SS_data; + this._restoreTab(data, aBrowser); + + delete aBrowser.__SS_restore; + aBrowser.removeAttribute("pending"); + log("restoring zombie tab " + aTabId); + }, + onTabInput: function ss_onTabInput(aWindow, aBrowser) { // If this browser belongs to a zombie tab or the initial restore hasn't yet finished, // skip any session save activity. diff --git a/mobile/android/tests/browser/chrome/test_session_scroll_position.html b/mobile/android/tests/browser/chrome/test_session_scroll_position.html index fb0b4c8aac452ccbcd728a67880a4bbac3ab7d12..cfbeb5164b7eaea4b650a00ff2bde049eb649221 100644 --- a/mobile/android/tests/browser/chrome/test_session_scroll_position.html +++ b/mobile/android/tests/browser/chrome/test_session_scroll_position.html @@ -34,6 +34,8 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=810981 const URL = "http://example.org/chrome/mobile/android/tests/browser/chrome/basic_article_mobile.html"; // Something to test the zoom level scaling on rotation with. const URL_desktop = "http://example.org/chrome/mobile/android/tests/browser/chrome/basic_article.html"; + // Reader mode URL + const URL_reader = "about:reader?url=http%3A%2F%2Fexample.org%2Fchrome%2Fmobile%2Fandroid%2Ftests%2Fbrowser%2Fchrome%2Fbasic_article_mobile.html"; function dispatchUIEvent(browser, type) { let event = browser.contentDocument.createEvent("UIEvents"); @@ -109,6 +111,61 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=810981 BrowserApp.closeTab(BrowserApp.getTabForBrowser(browser)); }); + add_task(function* test_sessionStoreScrollPositionReaderMode() { + const SCROLL_X = 0; + const SCROLL_Y = 44; + + chromeWin = Services.wm.getMostRecentWindow("navigator:browser"); + let BrowserApp = chromeWin.BrowserApp; + + // Creates a tab, sets a scroll position and closes the tab. + function createAndRemoveReaderTab() { + return Task.spawn(function () { + // Create a new tab. + tabScroll = BrowserApp.addTab(URL_reader); + let browser = tabScroll.browser; + yield promiseBrowserEvent(browser, "AboutReaderContentReady"); + + // Modify scroll position. + setScrollPosition(browser, SCROLL_X, SCROLL_Y); + yield promiseTabEvent(browser, "SSTabScrollCaptured"); + + // Check that we've actually scrolled. + let ifreq = browser.contentWindow.QueryInterface(Ci.nsIInterfaceRequestor); + let utils = ifreq.getInterface(Ci.nsIDOMWindowUtils); + let scrollX = {}, scrollY = {}; + utils.getScrollXY(false, scrollX, scrollY); + is(scrollX.value, SCROLL_X, "scrollX set correctly"); + is(scrollY.value, SCROLL_Y, "scrollY set correctly"); + + // Remove the tab. + BrowserApp.closeTab(tabScroll); + yield promiseTabEvent(browser, "SSTabCloseProcessed"); + }); + } + + yield createAndRemoveReaderTab(); + let state = ss.getClosedTabs(chromeWin); + let [{scrolldata}] = state; + is(scrolldata.scroll, SCROLL_X + "," + SCROLL_Y, "stored scroll position is correct"); + + // Restore the closed tab. + let closedTabData = ss.getClosedTabs(chromeWin)[0]; + let browser = ss.undoCloseTab(chromeWin, closedTabData); + yield promiseBrowserEvent(browser, "AboutReaderContentReady"); + + // Check the scroll position. + let ifreq = browser.contentWindow.QueryInterface(Ci.nsIInterfaceRequestor); + let utils = ifreq.getInterface(Ci.nsIDOMWindowUtils); + let scrollX = {}, scrollY = {}; + utils.getScrollXY(false, scrollX, scrollY); + is(scrollX.value, SCROLL_X, "scrollX restored correctly"); + is(scrollY.value, SCROLL_Y, "scrollY restored correctly"); + + // Remove the tab. + BrowserApp.closeTab(BrowserApp.getTabForBrowser(browser)); + }); + add_task(function* test_sessionStoreZoomLevel() { const ZOOM = 4.2; const SCROLL_X = 42; diff --git a/mobile/android/tests/browser/chrome/test_session_zombification.html b/mobile/android/tests/browser/chrome/test_session_zombification.html index baafed5b734f6e968c7a14df280b33df8672ca58..eba255ff62ec35ff4dda36d9c6282a9ec15eb923 100644 --- a/mobile/android/tests/browser/chrome/test_session_zombification.html +++ b/mobile/android/tests/browser/chrome/test_session_zombification.html @@ -96,6 +96,81 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1044556 is(tabTest.browser.currentURI.spec, url2, "Test tab is showing the second URL."); }); + add_task(function* test_sessionStoreKeepAsZombie() { + chromeWin = Services.wm.getMostRecentWindow("navigator:browser"); + let BrowserApp = chromeWin.BrowserApp; + let observerService = Services.obs; + + SimpleTest.registerCleanupFunction(function() { + BrowserApp.closeTab(tabBlank); + BrowserApp.closeTab(tabTest); + }); + + // Add a new tab with some content + tabTest = BrowserApp.addTab(url1 , { selected: true, parentId: BrowserApp.selectedTab.id }); + yield promiseBrowserEvent(tabTest.browser, "DOMContentLoaded"); + + // Add a new tab with a blank page + tabBlank = BrowserApp.addTab("about:blank", { selected: true, parentId: BrowserApp.selectedTab.id }); + yield promiseTabEvent(BrowserApp.deck, "TabSelect"); + is(BrowserApp.selectedTab, tabBlank, "Test tab is in background."); + + // Zombify the backgrounded test tab + MemoryObserver.zombify(tabTest); + + // Check that the test tab is actually zombified + ok(tabTest.browser.__SS_restore, "Test tab is set for delay loading."); + is(tabTest.browser.currentURI.spec, "about:blank", "Test tab is zombified."); + + // Tell the session store that it shouldn't restore that tab on selecting + observerService.notifyObservers(null, "Tab:KeepZombified", tabTest.id); + + // Switch back to the test tab and check that it remains zombified + BrowserApp.selectTab(tabTest); + yield promiseTabEvent(BrowserApp.deck, "TabSelect"); + is(BrowserApp.selectedTab, tabTest, "Test tab is selected."); + ok(tabTest.browser.__SS_restore, "Test tab is still set for delay loading."); + + // Switch to the other tab and back again + BrowserApp.selectTab(tabBlank); + yield promiseTabEvent(BrowserApp.deck, "TabSelect"); + is(BrowserApp.selectedTab, tabBlank, "Test tab is in background."); + BrowserApp.selectTab(tabTest); + + // "Tab:KeepZombified should be good for one TabSelect only + yield promiseBrowserEvent(tabTest.browser, "DOMContentLoaded"); + is(BrowserApp.selectedTab, tabTest, "Test tab is selected."); + + // Check that the test tab is no longer a zombie and has loaded the correct url + ok(!tabTest.browser.__SS_restore, "Test tab is no longer set for delay loading."); + is(tabTest.browser.currentURI.spec, url1, "Test tab is showing the test URL."); + + // Zombify the test tab again + BrowserApp.selectTab(tabBlank); + yield promiseTabEvent(BrowserApp.deck, "TabSelect"); + is(BrowserApp.selectedTab, tabBlank, "Test tab is in background."); + MemoryObserver.zombify(tabTest); + ok(tabTest.browser.__SS_restore, "Test tab is set for delay loading."); + is(tabTest.browser.currentURI.spec, "about:blank", "Test tab is zombified."); + + // Tell the session store that it shouldn't restore that tab on selecting + observerService.notifyObservers(null, "Tab:KeepZombified", tabTest.id); + + // Switch back to the test tab and check that it remains zombified + BrowserApp.selectTab(tabTest); + yield promiseTabEvent(BrowserApp.deck, "TabSelect"); + is(BrowserApp.selectedTab, tabTest, "Test tab is selected."); + ok(tabTest.browser.__SS_restore, "Test tab is still set for delay loading."); + + // Fake an "application-foreground" notification + observerService.notifyObservers(null, "application-foreground", null); + + // The test tab should now start reloading + yield promiseBrowserEvent(tabTest.browser, "DOMContentLoaded"); + ok(!tabTest.browser.__SS_restore, "Test tab is no longer set for delay loading."); + is(tabTest.browser.currentURI.spec, url1, "Test tab is showing the test URL."); + }); + </script> </head> <body> diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js index 45dcf351614a21c70c560e3e50a27faf36860a6e..bd33703315f33d0ddff353e1ba1971985660fcc3 100644 --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -297,6 +297,13 @@ pref("mathml.disabled", false); // Enable scale transform for stretchy MathML operators. See bug 414277. pref("mathml.scale_stretchy_operators.enabled", true); +// Disable MediaError.message. +#ifdef RELEASE_BUILD +pref("dom.MediaError.message.enabled", false); +#else +pref("dom.MediaError.message.enabled", true); +#endif + // Media cache size in kilobytes pref("media.cache_size", 512000); // When a network connection is suspended, don't resume it until the @@ -4313,7 +4320,6 @@ pref("signon.autofillForms", true); pref("signon.autologin.proxy", false); pref("signon.formlessCapture.enabled", true); pref("signon.storeWhenAutocompleteOff", true); -pref("signon.ui.experimental", false); pref("signon.debug", false); pref("signon.recipes.path", "chrome://passwordmgr/content/recipes.json"); pref("signon.schemeUpgrades", false); diff --git a/old-configure.in b/old-configure.in index 545e82f83d56ed8d2e57fb943967b721196e3d97..f9526eab531c776ad0009b7279d49c24cd356183 100644 --- a/old-configure.in +++ b/old-configure.in @@ -1143,6 +1143,10 @@ case "$target" in # use such pragmas, so just ignore them. CFLAGS="$CFLAGS -Wno-unknown-pragmas" CXXFLAGS="$CXXFLAGS -Wno-unknown-pragmas" + # We get errors about various #pragma intrinsic directives from + # clang-cl, and we don't need to hear about those. + CFLAGS="$CFLAGS -Wno-ignored-pragmas" + CXXFLAGS="$CXXFLAGS -Wno-ignored-pragmas" # clang-cl's Intrin.h marks things like _ReadWriteBarrier # as __attribute((__deprecated__)). This is nice to know, # but since we don't get the equivalent warning from MSVC, diff --git a/security/manager/pki/resources/content/certManager.xul b/security/manager/pki/resources/content/certManager.xul index 3522d60f22f387ce1fb6ab5c36802e6c10aea945..3ea5862e49c8c59094784ba2e9807c0a267e8f55 100644 --- a/security/manager/pki/resources/content/certManager.xul +++ b/security/manager/pki/resources/content/certManager.xul @@ -13,14 +13,14 @@ <!DOCTYPE dialog SYSTEM "chrome://pippki/locale/certManager.dtd"> -<dialog id="certmanager" +<dialog id="certmanager" windowtype="mozilla:certmanager" - xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" title="&certmgr.title;" onload="LoadCerts();" onunload="DeregisterSmartCardObservers();" buttons="accept" - style="width: 48em; height: 32em;" + style="width: 63em; height: 32em;" persist="screenX screenY width height"> <stringbundle id="pippki_bundle" src="chrome://pippki/locale/pippki.properties"/> diff --git a/security/nss/automation/taskcluster/windows/releng.manifest b/security/nss/automation/taskcluster/windows/releng.manifest index b3f4498540b8afde95b7372edf534718be85533d..403be2b04627c4e9a4cce2062c14a0bc4b6b935b 100644 --- a/security/nss/automation/taskcluster/windows/releng.manifest +++ b/security/nss/automation/taskcluster/windows/releng.manifest @@ -1,10 +1,10 @@ [ { - "version": "Visual Studio 2015 Update 2 / SDK 10.0.10586.0/212", - "size": 332442800, - "digest": "995394a4a515c7cb0f8595f26f5395361a638870dd0bbfcc22193fe1d98a0c47126057d5999cc494f3f3eac5cb49160e79757c468f83ee5797298e286ef6252c", + "version": "Visual Studio 2015 Update 3 14.0.25425.01 / SDK 10.0.14393.0", + "size": 326656969, + "digest": "babc414ffc0457d27f5a1ed24a8e4873afbe2f1c1a4075469a27c005e1babc3b2a788f643f825efedff95b79686664c67ec4340ed535487168a3482e68559bc7", "algorithm": "sha512", - "filename": "vs2015u2.zip", + "filename": "vs2015u3.zip", "unpack": true } ] diff --git a/security/nss/automation/taskcluster/windows/setup.sh b/security/nss/automation/taskcluster/windows/setup.sh index 80cee2850e155c16651023b4ffb593ac2871ebdd..32732774a4888990746c2558ebebeefb97c67c86 100644 --- a/security/nss/automation/taskcluster/windows/setup.sh +++ b/security/nss/automation/taskcluster/windows/setup.sh @@ -18,7 +18,7 @@ hg_clone() { hg_clone https://hg.mozilla.org/build/tools tools default tools/scripts/tooltool/tooltool_wrapper.sh $(dirname $0)/releng.manifest https://api.pub.build.mozilla.org/tooltool/ non-existant-file.sh /c/mozilla-build/python/python.exe /c/builds/tooltool.py --authentication-file /c/builds/relengapi.tok -c /c/builds/tooltool_cache -VSPATH="$(pwd)/vs2015u2" +VSPATH="$(pwd)/vs2015u3" export WINDOWSSDKDIR="${VSPATH}/SDK" export WIN32_REDIST_DIR="${VSPATH}/VC/redist/x64/Microsoft.VC140.CRT" @@ -26,5 +26,5 @@ export WIN_UCRT_REDIST_DIR="${VSPATH}/SDK/Redist/ucrt/DLLs/x64" export PATH="${VSPATH}/VC/bin/amd64:${VSPATH}/VC/bin:${VSPATH}/SDK/bin/x64:${VSPATH}/VC/redist/x64/Microsoft.VC140.CRT:${VSPATH}/SDK/Redist/ucrt/DLLs/x64:${PATH}" -export INCLUDE="${VSPATH}/VC/include:${VSPATH}/SDK/Include/10.0.10586.0/ucrt:${VSPATH}/SDK/Include/10.0.10586.0/shared:${VSPATH}/SDK/Include/10.0.10586.0/um" -export LIB="${VSPATH}/VC/lib/amd64:${VSPATH}/SDK/lib/10.0.10586.0/ucrt/x64:${VSPATH}/SDK/lib/10.0.10586.0/um/x64" +export INCLUDE="${VSPATH}/VC/include:${VSPATH}/SDK/Include/10.0.14393.0/ucrt:${VSPATH}/SDK/Include/10.0.14393.0/shared:${VSPATH}/SDK/Include/10.0.14393.0/um" +export LIB="${VSPATH}/VC/lib/amd64:${VSPATH}/SDK/lib/10.0.14393.0/ucrt/x64:${VSPATH}/SDK/lib/10.0.14393.0/um/x64" diff --git a/security/sandbox/mac/Sandbox.h b/security/sandbox/mac/Sandbox.h index 525544e8f3191a503d1789062a27609f87a047e4..b2e1a7ec5430d1c0831cdab61b97d314db1692d2 100644 --- a/security/sandbox/mac/Sandbox.h +++ b/security/sandbox/mac/Sandbox.h @@ -39,16 +39,21 @@ typedef struct _MacSandboxInfo { _MacSandboxInfo() : type(MacSandboxType_Default), level(0) {} _MacSandboxInfo(const struct _MacSandboxInfo& other) - : type(other.type), level(other.level), pluginInfo(other.pluginInfo), + : type(other.type), level(other.level), + hasSandboxedProfile(other.hasSandboxedProfile), + pluginInfo(other.pluginInfo), appPath(other.appPath), appBinaryPath(other.appBinaryPath), - appDir(other.appDir), appTempDir(other.appTempDir) {} + appDir(other.appDir), appTempDir(other.appTempDir), + profileDir(other.profileDir) {} MacSandboxType type; int32_t level; + bool hasSandboxedProfile; MacSandboxPluginInfo pluginInfo; std::string appPath; std::string appBinaryPath; std::string appDir; std::string appTempDir; + std::string profileDir; } MacSandboxInfo; namespace mozilla { diff --git a/security/sandbox/mac/Sandbox.mm b/security/sandbox/mac/Sandbox.mm index 09af10280a4e66d78b6af47f54a03b1cb0c345c1..dcceb9e1c8f663283d45129b1e35c9f2c9400f9b 100644 --- a/security/sandbox/mac/Sandbox.mm +++ b/security/sandbox/mac/Sandbox.mm @@ -157,6 +157,8 @@ static const char contentSandboxRules[] = "(define appBinaryPath \"%s\")\n" "(define appDir \"%s\")\n" "(define appTempDir \"%s\")\n" + "(define hasProfileDir %d)\n" + "(define profileDir \"%s\")\n" "(define home-path \"%s\")\n" "\n" "; Allow read access to standard system paths.\n" @@ -232,6 +234,9 @@ static const char contentSandboxRules[] = " (define (home-literal home-relative-literal)\n" " (resolving-literal (string-append home-path home-relative-literal)))\n" "\n" + " (define (profile-subpath profile-relative-subpath)\n" + " (resolving-subpath (string-append profileDir profile-relative-subpath)))\n" + "\n" " (define (container-regex container-relative-regex)\n" " (resolving-regex (string-append \"^\" (regex-quote container-path) container-relative-regex)))\n" " (define (container-subpath container-relative-subpath)\n" @@ -368,19 +373,27 @@ static const char contentSandboxRules[] = " (var-folders2-regex \"/[^/]+\\.mozrunner/extensions/[^/]+/chrome/[^/]+/content/[^/]+\\.j(s|ar)$\"))\n" "\n" " (allow file-write* (var-folders2-regex \"/org\\.chromium\\.[a-zA-Z0-9]*$\"))\n" + "\n" + "; Per-user and system-wide Extensions dir\n" " (allow file-read*\n" " (home-regex \"/Library/Application Support/[^/]+/Extensions/[^/]/\")\n" - " (resolving-regex \"/Library/Application Support/[^/]+/Extensions/[^/]/\")\n" - " (home-regex \"/Library/Application Support/Firefox/Profiles/[^/]+/extensions/\")\n" - " (home-regex \"/Library/Application Support/Firefox/Profiles/[^/]+/weave/\"))\n" + " (resolving-regex \"/Library/Application Support/[^/]+/Extensions/[^/]/\"))\n" "\n" - "; the following rules should be removed when printing and \n" + "; Profile subdirectories\n" + " (if (not (zero? hasProfileDir)) (allow file-read*\n" + " (profile-subpath \"/extensions\")\n" + " (profile-subpath \"/weave\")))\n" + "\n" + "; the following rules should be removed when printing and\n" "; opening a file from disk are brokered through the main process\n" - " (if\n" - " (< sandbox-level 2)\n" - " (allow file*\n" - " (require-not\n" - " (home-subpath \"/Library\")))\n" + " (if (< sandbox-level 2)\n" + " (if (not (zero? hasProfileDir))\n" + " (allow file*\n" + " (require-all\n" + " (require-not (home-subpath \"/Library\"))\n" + " (require-not (subpath profileDir))))\n" + " (allow file*\n" + " (require-not (home-subpath \"/Library\"))))\n" " (allow file*\n" " (require-all\n" " (subpath home-path)\n" @@ -497,6 +510,8 @@ bool StartMacSandbox(MacSandboxInfo aInfo, std::string &aErrorMessage) aInfo.appBinaryPath.c_str(), aInfo.appDir.c_str(), aInfo.appTempDir.c_str(), + aInfo.hasSandboxedProfile ? 1 : 0, + aInfo.profileDir.c_str(), getenv("HOME")); } else { fprintf(stderr, diff --git a/security/sandbox/win/src/sandboxbroker/sandboxBroker.cpp b/security/sandbox/win/src/sandboxbroker/sandboxBroker.cpp index 2c8940c6eb04f98872372f86513db9ce40482177..f9c1e45a5644e93b5099a5edc6df35062c07580f 100644 --- a/security/sandbox/win/src/sandboxbroker/sandboxBroker.cpp +++ b/security/sandbox/win/src/sandboxbroker/sandboxBroker.cpp @@ -8,6 +8,7 @@ #include "base/win/windows_version.h" #include "mozilla/Assertions.h" +#include "mozilla/Logging.h" #include "sandbox/win/src/sandbox.h" #include "sandbox/win/src/security_level.h" @@ -16,6 +17,10 @@ namespace mozilla sandbox::BrokerServices *SandboxBroker::sBrokerService = nullptr; +static LazyLogModule sSandboxBrokerLog("SandboxBroker"); + +#define LOG_E(...) MOZ_LOG(sSandboxBrokerLog, LogLevel::Error, (__VA_ARGS__)) + /* static */ void SandboxBroker::Initialize(sandbox::BrokerServices* aBrokerServices) @@ -122,7 +127,7 @@ SandboxBroker::SetSecurityLevelForContentProcess(int32_t aSandboxLevel) delayedIntegrityLevel = sandbox::INTEGRITY_LEVEL_LOW; } - sandbox::ResultCode result = mPolicy->SetJobLevel(jobLevel, + sandbox::ResultCode result = mPolicy->SetJobLevel(jobLevel, 0 /* ui_exceptions */); MOZ_RELEASE_ASSERT(sandbox::SBOX_ALL_OK == result, "Setting job level failed, have you set memory limit when jobLevel == JOB_NONE?"); @@ -458,7 +463,12 @@ SandboxBroker::AllowReadFile(wchar_t const *file) mPolicy->AddRule(sandbox::TargetPolicy::SUBSYS_FILES, sandbox::TargetPolicy::FILES_ALLOW_READONLY, file); - return (sandbox::SBOX_ALL_OK == result); + if (sandbox::SBOX_ALL_OK != result) { + LOG_E("Failed (ResultCode %d) to add read access to: %S", result, file); + return false; + } + + return true; } bool @@ -472,7 +482,13 @@ SandboxBroker::AllowReadWriteFile(wchar_t const *file) mPolicy->AddRule(sandbox::TargetPolicy::SUBSYS_FILES, sandbox::TargetPolicy::FILES_ALLOW_ANY, file); - return (sandbox::SBOX_ALL_OK == result); + if (sandbox::SBOX_ALL_OK != result) { + LOG_E("Failed (ResultCode %d) to add read/write access to: %S", + result, file); + return false; + } + + return true; } bool @@ -486,7 +502,12 @@ SandboxBroker::AllowDirectory(wchar_t const *dir) mPolicy->AddRule(sandbox::TargetPolicy::SUBSYS_FILES, sandbox::TargetPolicy::FILES_ALLOW_DIR_ANY, dir); - return (sandbox::SBOX_ALL_OK == result); + if (sandbox::SBOX_ALL_OK != result) { + LOG_E("Failed (ResultCode %d) to add directory access to: %S", result, dir); + return false; + } + + return true; } bool diff --git a/services/crypto/modules/utils.js b/services/crypto/modules/utils.js index 0c727c2c507403a8d382b3393387ed65e158f85c..c17f5dfa185a4220bee362aec55836171b3d5e37 100644 --- a/services/crypto/modules/utils.js +++ b/services/crypto/modules/utils.js @@ -106,6 +106,13 @@ this.CryptoUtils = { return CommonUtils.encodeBase32(CryptoUtils.UTF8AndSHA1(message)); }, + sha256(message) { + let hasher = Cc["@mozilla.org/security/hash;1"] + .createInstance(Ci.nsICryptoHash); + hasher.init(hasher.SHA256); + return CommonUtils.bytesAsHex(CryptoUtils.digestUTF8(message, hasher)); + }, + /** * Produce an HMAC key object from a key string. */ diff --git a/services/sync/modules/browserid_identity.js b/services/sync/modules/browserid_identity.js index c2edab785dc5da52a7337d33bfe01975f0792844..db382151842fc57f0d90b14cedf4bd10b3826176 100644 --- a/services/sync/modules/browserid_identity.js +++ b/services/sync/modules/browserid_identity.js @@ -121,6 +121,10 @@ this.BrowserIDManager.prototype = { return this._token.hashed_fxa_uid }, + deviceID() { + return this._signedInUser && this._signedInUser.deviceId; + }, + initialize: function() { for (let topic of OBSERVER_TOPICS) { Services.obs.addObserver(this, topic, false); diff --git a/services/sync/modules/engines.js b/services/sync/modules/engines.js index fb371692a34551918fdd5e03917c86bf60eff3c8..83b3d63f463a23a3b898c3b3dc5b4d8d15516237 100644 --- a/services/sync/modules/engines.js +++ b/services/sync/modules/engines.js @@ -7,7 +7,8 @@ this.EXPORTED_SYMBOLS = [ "Engine", "SyncEngine", "Tracker", - "Store" + "Store", + "Changeset" ]; var {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components; @@ -131,26 +132,30 @@ Tracker.prototype = { this._ignored.splice(index, 1); }, + _saveChangedID(id, when) { + this._log.trace(`Adding changed ID: ${id}, ${JSON.stringify(when)}`); + this.changedIDs[id] = when; + this.saveChangedIDs(this.onSavedChangedIDs); + }, + addChangedID: function (id, when) { if (!id) { this._log.warn("Attempted to add undefined ID to tracker"); return false; } - if (this.ignoreAll || (id in this._ignored)) { + if (this.ignoreAll || this._ignored.includes(id)) { return false; } // Default to the current time in seconds if no time is provided. if (when == null) { - when = Math.floor(Date.now() / 1000); + when = Date.now() / 1000; } // Add/update the entry if we have a newer time. if ((this.changedIDs[id] || -Infinity) < when) { - this._log.trace("Adding changed ID: " + id + ", " + when); - this.changedIDs[id] = when; - this.saveChangedIDs(this.onSavedChangedIDs); + this._saveChangedID(id, when); } return true; @@ -161,8 +166,9 @@ Tracker.prototype = { this._log.warn("Attempted to remove undefined ID to tracker"); return false; } - if (this.ignoreAll || (id in this._ignored)) + if (this.ignoreAll || this._ignored.includes(id)) { return false; + } if (this.changedIDs[id] != null) { this._log.trace("Removing changed ID " + id); delete this.changedIDs[id]; @@ -862,9 +868,8 @@ SyncEngine.prototype = { }, /* - * Returns a mapping of IDs -> changed timestamp. Engine implementations - * can override this method to bypass the tracker for certain or all - * changed items. + * Returns a changeset for this sync. Engine implementations can override this + * method to bypass the tracker for certain or all changed items. */ getChangedIDs: function () { return this._tracker.changedIDs; @@ -932,20 +937,16 @@ SyncEngine.prototype = { // this._modified to the tracker. this.lastSyncLocal = Date.now(); if (this.lastSync) { - this._modified = this.getChangedIDs(); + this._modified = this.pullNewChanges(); } else { - // Mark all items to be uploaded, but treat them as changed from long ago this._log.debug("First sync, uploading all items"); - this._modified = {}; - for (let id in this._store.getAllIDs()) { - this._modified[id] = 0; - } + this._modified = this.pullAllChanges(); } // Clear the tracker now. If the sync fails we'll add the ones we failed // to upload back. this._tracker.clearChangedIDs(); - this._log.info(Object.keys(this._modified).length + + this._log.info(this._modified.count() + " outgoing items pre-reconciliation"); // Keep track of what to delete at the end of sync @@ -1293,12 +1294,12 @@ SyncEngine.prototype = { // because some state may change during the course of this function and we // need to operate on the original values. let existsLocally = this._store.itemExists(item.id); - let locallyModified = item.id in this._modified; + let locallyModified = this._modified.has(item.id); // TODO Handle clock drift better. Tracked in bug 721181. let remoteAge = AsyncResource.serverTime - item.modified; let localAge = locallyModified ? - (Date.now() / 1000 - this._modified[item.id]) : null; + (Date.now() / 1000 - this._modified.getModifiedTimestamp(item.id)) : null; let remoteIsNewer = remoteAge < localAge; this._log.trace("Reconciling " + item.id + ". exists=" + @@ -1369,13 +1370,13 @@ SyncEngine.prototype = { // If the local item was modified, we carry its metadata forward so // appropriate reconciling can be performed. - if (dupeID in this._modified) { + if (this._modified.has(dupeID)) { locallyModified = true; - localAge = Date.now() / 1000 - this._modified[dupeID]; + localAge = Date.now() / 1000 - + this._modified.getModifiedTimestamp(dupeID); remoteIsNewer = remoteAge < localAge; - this._modified[item.id] = this._modified[dupeID]; - delete this._modified[dupeID]; + this._modified.swap(dupeID, item.id); } else { locallyModified = false; localAge = null; @@ -1409,7 +1410,7 @@ SyncEngine.prototype = { if (remoteIsNewer) { this._log.trace("Applying incoming because local item was deleted " + "before the incoming item was changed."); - delete this._modified[item.id]; + this._modified.delete(item.id); return true; } @@ -1435,7 +1436,7 @@ SyncEngine.prototype = { this._log.trace("Ignoring incoming item because the local item is " + "identical."); - delete this._modified[item.id]; + this._modified.delete(item.id); return false; } @@ -1460,7 +1461,7 @@ SyncEngine.prototype = { _uploadOutgoing: function () { this._log.trace("Uploading local changes to server."); - let modifiedIDs = Object.keys(this._modified); + let modifiedIDs = this._modified.ids(); if (modifiedIDs.length) { this._log.trace("Preparing " + modifiedIDs.length + " outgoing records"); @@ -1504,7 +1505,7 @@ SyncEngine.prototype = { counts.failed += failed.length; for (let id of successful) { - delete this._modified[id]; + this._modified.delete(id); } this._onRecordsWritten(successful, failed); @@ -1588,10 +1589,8 @@ SyncEngine.prototype = { } // Mark failed WBOs as changed again so they are reuploaded next time. - for (let [id, when] of Object.entries(this._modified)) { - this._tracker.addChangedID(id, when); - } - this._modified = {}; + this.trackRemainingChanges(); + this._modified.clear(); }, _sync: function () { @@ -1677,5 +1676,108 @@ SyncEngine.prototype = { return (this.service.handleHMACEvent() && mayRetry) ? SyncEngine.kRecoveryStrategy.retry : SyncEngine.kRecoveryStrategy.error; - } + }, + + /** + * Returns a changeset containing all items in the store. The default + * implementation returns a changeset with timestamps from long ago, to + * ensure we always use the remote version if one exists. + * + * This function is only called for the first sync. Subsequent syncs call + * `pullNewChanges`. + * + * @return A `Changeset` object. + */ + pullAllChanges() { + let changeset = new Changeset(); + for (let id in this._store.getAllIDs()) { + changeset.set(id, 0); + } + return changeset; + }, + + /* + * Returns a changeset containing entries for all currently tracked items. + * The default implementation returns a changeset with timestamps indicating + * when the item was added to the tracker. + * + * @return A `Changeset` object. + */ + pullNewChanges() { + return new Changeset(this.getChangedIDs()); + }, + + /** + * Adds all remaining changeset entries back to the tracker, typically for + * items that failed to upload. This method is called at the end of each sync. + * + */ + trackRemainingChanges() { + for (let [id, change] of this._modified.entries()) { + this._tracker.addChangedID(id, change); + } + }, }; + +/** + * A changeset is created for each sync in `Engine::get{Changed, All}IDs`, + * and stores opaque change data for tracked IDs. The default implementation + * only records timestamps, though engines can extend this to store additional + * data for each entry. + */ +class Changeset { + // Creates a changeset with an initial set of tracked entries. + constructor(changes = {}) { + this.changes = changes; + } + + // Returns the last modified time, in seconds, for an entry in the changeset. + // `id` is guaranteed to be in the set. + getModifiedTimestamp(id) { + return this.changes[id]; + } + + // Adds a change for a tracked ID to the changeset. + set(id, change) { + this.changes[id] = change; + } + + // Indicates whether an entry is in the changeset. + has(id) { + return id in this.changes; + } + + // Deletes an entry from the changeset. Used to clean up entries for + // reconciled and successfully uploaded records. + delete(id) { + delete this.changes[id]; + } + + // Swaps two entries in the changeset. Used when reconciling duplicates that + // have local changes. + swap(oldID, newID) { + this.changes[newID] = this.changes[oldID]; + delete this.changes[oldID]; + } + + // Returns an array of all tracked IDs in this changeset. + ids() { + return Object.keys(this.changes); + } + + // Returns an array of `[id, change]` tuples. Used to repopulate the tracker + // with entries for failed uploads at the end of a sync. + entries() { + return Object.entries(this.changes); + } + + // Returns the number of entries in this changeset. + count() { + return this.ids().length; + } + + // Clears the changeset. + clear() { + this.changes = {}; + } +} diff --git a/services/sync/modules/engines/bookmarks.js b/services/sync/modules/engines/bookmarks.js index 6366afd992d3ce3ce596ce3937d6a979077e322d..f27fa0bc3d6ea2a4dd2003e0dec4f4ea4fa3f0b0 100644 --- a/services/sync/modules/engines/bookmarks.js +++ b/services/sync/modules/engines/bookmarks.js @@ -33,6 +33,8 @@ const { SOURCE_IMPORT_REPLACE, } = Ci.nsINavBookmarksService; +const SQLITE_MAX_VARIABLE_NUMBER = 999; + // Maps Sync record property names to `PlacesSyncUtils` bookmark properties. const RECORD_PROPS_TO_BOOKMARK_PROPS = { title: "title", @@ -426,7 +428,87 @@ BookmarksEngine.prototype = { // We must return a string, not an object, and the entries in the GUIDMap // are created via "new String()" making them an object. return mapped ? mapped.toString() : mapped; - } + }, + + pullAllChanges() { + return new BookmarksChangeset(this._store.getAllIDs()); + }, + + pullNewChanges() { + let modifiedGUIDs = this._getModifiedGUIDs(); + if (!modifiedGUIDs.length) { + return new BookmarksChangeset(this._tracker.changedIDs); + } + + // We don't use `PlacesUtils.promiseDBConnection` here because + // `getChangedIDs` might be called while we're in a batch, meaning we + // won't see any changes until the batch finishes and the transaction + // commits. + let db = PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase) + .DBConnection; + + // Filter out tags, organizer queries, and other descendants that we're + // not tracking. We chunk `modifiedGUIDs` because SQLite limits the number + // of bound parameters per query. + for (let startIndex = 0; + startIndex < modifiedGUIDs.length; + startIndex += SQLITE_MAX_VARIABLE_NUMBER) { + + let chunkLength = Math.min(startIndex + SQLITE_MAX_VARIABLE_NUMBER, + modifiedGUIDs.length); + + let query = ` + WITH RECURSIVE + modifiedGuids(guid) AS ( + VALUES ${new Array(chunkLength).fill("(?)").join(", ")} + ), + syncedItems(id) AS ( + VALUES ${getChangeRootIds().map(id => `(${id})`).join(", ")} + UNION ALL + SELECT b.id + FROM moz_bookmarks b + JOIN syncedItems s ON b.parent = s.id + ) + SELECT b.guid, b.id + FROM modifiedGuids m + JOIN moz_bookmarks b ON b.guid = m.guid + LEFT JOIN syncedItems s ON b.id = s.id + WHERE s.id IS NULL + `; + + let statement = db.createAsyncStatement(query); + try { + for (let i = 0; i < chunkLength; i++) { + statement.bindByIndex(i, modifiedGUIDs[startIndex + i]); + } + let results = Async.querySpinningly(statement, ["id", "guid"]); + for (let { id, guid } of results) { + let syncID = BookmarkSpecialIds.specialGUIDForId(id) || guid; + this._tracker.removeChangedID(syncID); + } + } finally { + statement.finalize(); + } + } + + return new BookmarksChangeset(this._tracker.changedIDs); + }, + + // Returns an array of Places GUIDs for all changed items. Ignores deletions, + // which won't exist in the DB and shouldn't be removed from the tracker. + _getModifiedGUIDs() { + let guids = []; + for (let syncID in this._tracker.changedIDs) { + if (this._tracker.changedIDs[syncID].deleted === true) { + // The `===` check also filters out old persisted timestamps, + // which won't have a `deleted` property. + continue; + } + let guid = BookmarkSpecialIds.syncIDToPlacesGUID(syncID); + guids.push(guid); + } + return guids; + }, }; function BookmarksStore(name, engine) { @@ -639,13 +721,6 @@ BookmarksStore.prototype = { )); }, - _getNode: function BStore__getNode(folder) { - let query = PlacesUtils.history.getNewQuery(); - query.setFolders([folder], 1); - return PlacesUtils.history.executeQuery( - query, PlacesUtils.history.getNewQueryOptions()).root; - }, - _getTags: function BStore__getTags(uri) { try { if (typeof(uri) == "string") @@ -828,51 +903,30 @@ BookmarksStore.prototype = { return index; }, - _getChildren: function BStore_getChildren(guid, items) { - let node = guid; // the recursion case - if (typeof(node) == "string") { // callers will give us the guid as the first arg - let nodeID = this.idForGUID(guid, true); - if (!nodeID) { - this._log.debug("No node for GUID " + guid + "; returning no children."); - return items; - } - node = this._getNode(nodeID); - } - - if (node.type == node.RESULT_TYPE_FOLDER) { - node.QueryInterface(Ci.nsINavHistoryQueryResultNode); - node.containerOpen = true; - try { - // Remember all the children GUIDs and recursively get more - for (let i = 0; i < node.childCount; i++) { - let child = node.getChild(i); - items[this.GUIDForId(child.itemId)] = true; - this._getChildren(child, items); - } - } - finally { - node.containerOpen = false; - } - } - - return items; - }, - getAllIDs: function BStore_getAllIDs() { - let items = {"menu": true, - "toolbar": true, - "unfiled": true, - }; - // We also want "mobile" but only if a local mobile folder already exists - // (otherwise we'll later end up creating it, which we want to avoid until - // we actually need it.) - if (BookmarkSpecialIds.findMobileRoot(false)) { - items["mobile"] = true; - } - for (let guid of BookmarkSpecialIds.guids) { - if (guid != "places" && guid != "tags") - this._getChildren(guid, items); + let items = {}; + + let query = ` + WITH RECURSIVE + changeRootContents(id) AS ( + VALUES ${getChangeRootIds().map(id => `(${id})`).join(", ")} + UNION ALL + SELECT b.id + FROM moz_bookmarks b + JOIN changeRootContents c ON b.parent = c.id + ) + SELECT id, guid + FROM changeRootContents + JOIN moz_bookmarks USING (id) + `; + + let statement = this._getStmt(query); + let results = Async.querySpinningly(statement, ["id", "guid"]); + for (let { id, guid } of results) { + let syncID = BookmarkSpecialIds.specialGUIDForId(id) || guid; + items[syncID] = { modified: 0, deleted: false }; } + return items; }, @@ -964,16 +1018,50 @@ BookmarksTracker.prototype = { Ci.nsISupportsWeakReference ]), + addChangedID(id, change) { + if (!id) { + this._log.warn("Attempted to add undefined ID to tracker"); + return false; + } + if (this._ignored.includes(id)) { + return false; + } + let shouldSaveChange = false; + let currentChange = this.changedIDs[id]; + if (currentChange) { + if (typeof currentChange == "number") { + // Allow raw timestamps for backward-compatibility with persisted + // changed IDs. The new format uses tuples to track deleted items. + shouldSaveChange = currentChange < change.modified; + } else { + shouldSaveChange = currentChange.modified < change.modified || + currentChange.deleted != change.deleted; + } + } else { + shouldSaveChange = true; + } + if (shouldSaveChange) { + this._saveChangedID(id, change); + } + return true; + }, + /** * Add a bookmark GUID to be uploaded and bump up the sync score. * - * @param itemGuid - * GUID of the bookmark to upload. + * @param itemId + * The Places item ID of the bookmark to upload. + * @param guid + * The Places GUID of the bookmark to upload. + * @param isTombstone + * Whether we're uploading a tombstone for a removed bookmark. */ - _add: function BMT__add(itemId, guid) { + _add: function BMT__add(itemId, guid, isTombstone = false) { guid = BookmarkSpecialIds.specialGUIDForId(itemId) || guid; - if (this.addChangedID(guid)) + let info = { modified: Date.now() / 1000, deleted: isTombstone }; + if (this.addChangedID(guid, info)) { this._upScore(); + } }, /* Every add/remove/change will trigger a sync for MULTI_DEVICE (except in @@ -986,59 +1074,10 @@ BookmarksTracker.prototype = { } }, - /** - * Determine if a change should be ignored. - * - * @param itemId - * Item under consideration to ignore - * @param folder (optional) - * Folder of the item being changed - * @param guid - * Places GUID of the item being changed - * @param source - * A change source constant from `nsINavBookmarksService::SOURCE_*`. - */ - _ignore: function BMT__ignore(itemId, folder, guid, source) { - if (IGNORED_SOURCES.includes(source)) { - return true; - } - - // Get the folder id if we weren't given one. - if (folder == null) { - try { - folder = PlacesUtils.bookmarks.getFolderIdForItem(itemId); - } catch (ex) { - this._log.debug("getFolderIdForItem(" + itemId + - ") threw; calling _ensureMobileQuery."); - // I'm guessing that gFIFI can throw, and perhaps that's why - // _ensureMobileQuery is here at all. Try not to call it. - this._ensureMobileQuery(); - folder = PlacesUtils.bookmarks.getFolderIdForItem(itemId); - } - } - - // Ignore changes to tags (folders under the tags folder). - let tags = BookmarkSpecialIds.tags; - if (folder == tags) - return true; - - // Ignore tag items (the actual instance of a tag for a bookmark). - if (PlacesUtils.bookmarks.getFolderIdForItem(folder) == tags) - return true; - - // Make sure to remove items that have the exclude annotation. - if (PlacesUtils.annotations.itemHasAnnotation(itemId, BookmarkAnnos.EXCLUDEBACKUP_ANNO)) { - this.removeChangedID(guid); - return true; - } - - return false; - }, - onItemAdded: function BMT_onItemAdded(itemId, folder, index, itemType, uri, title, dateAdded, guid, parentGuid, source) { - if (this._ignore(itemId, folder, guid, source)) { + if (IGNORED_SOURCES.includes(source)) { return; } @@ -1049,12 +1088,50 @@ BookmarksTracker.prototype = { onItemRemoved: function (itemId, parentId, index, type, uri, guid, parentGuid, source) { - if (this._ignore(itemId, parentId, guid, source)) { + if (IGNORED_SOURCES.includes(source)) { return; } + // Ignore changes to tags (folders under the tags folder). + if (parentId == PlacesUtils.tagsFolderId) { + return; + } + + let grandParentId = -1; + try { + grandParentId = PlacesUtils.bookmarks.getFolderIdForItem(parentId); + } catch (ex) { + // `getFolderIdForItem` can throw if the item no longer exists, such as + // when we've removed a subtree using `removeFolderChildren`. + return; + } + + // Ignore tag items (the actual instance of a tag for a bookmark). + if (grandParentId == PlacesUtils.tagsFolderId) { + return; + } + + /** + * The above checks are incomplete: we can still write tombstones for + * items that we don't track, and upload extraneous roots. + * + * Consider the left pane root: it's a child of the Places root, and has + * children and grandchildren. `PlacesUIUtils` can create, delete, and + * recreate it as needed. We can't determine ancestors when the root or its + * children are deleted, because they've already been removed from the + * database when `onItemRemoved` is called. Likewise, we can't check their + * "exclude from backup" annos, because they've *also* been removed. + * + * So, we end up writing tombstones for the left pane queries and left + * pane root. For good measure, we'll also upload the Places root, because + * it's the parent of the left pane root. + * + * As a workaround, we can track the parent GUID and reconstruct the item's + * ancestry at sync time. This is complicated, and the previous behavior was + * already wrong, so we'll wait for bug 1258127 to fix this generally. + */ this._log.trace("onItemRemoved: " + itemId); - this._add(itemId, guid); + this._add(itemId, guid, /* isTombstone */ true); this._add(parentId, parentGuid); }, @@ -1098,6 +1175,10 @@ BookmarksTracker.prototype = { onItemChanged: function BMT_onItemChanged(itemId, property, isAnno, value, lastModified, itemType, parentId, guid, parentGuid, source) { + if (IGNORED_SOURCES.includes(source)) { + return; + } + if (isAnno && (ANNOS_TO_TRACK.indexOf(property) == -1)) // Ignore annotations except for the ones that we sync. return; @@ -1106,10 +1187,6 @@ BookmarksTracker.prototype = { if (property == "favicon") return; - if (this._ignore(itemId, parentId, guid, source)) { - return; - } - this._log.trace("onItemChanged: " + itemId + (", " + property + (isAnno? " (anno)" : "")) + (value ? (" = \"" + value + "\"") : "")); @@ -1120,7 +1197,7 @@ BookmarksTracker.prototype = { newParent, newIndex, itemType, guid, oldParentGuid, newParentGuid, source) { - if (this._ignore(itemId, newParent, guid, source)) { + if (IGNORED_SOURCES.includes(source)) { return; } @@ -1146,3 +1223,26 @@ BookmarksTracker.prototype = { }, onItemVisited: function () {} }; + +// Returns an array of root IDs to recursively query for synced bookmarks. +// Items in other roots, including tags and organizer queries, will be +// ignored. +function getChangeRootIds() { + let rootIds = [ + PlacesUtils.bookmarksMenuFolderId, + PlacesUtils.toolbarFolderId, + PlacesUtils.unfiledBookmarksFolderId, + ]; + let mobileRootId = BookmarkSpecialIds.findMobileRoot(false); + if (mobileRootId) { + rootIds.push(mobileRootId); + } + return rootIds; +} + +class BookmarksChangeset extends Changeset { + getModifiedTimestamp(id) { + let change = this.changes[id]; + return change ? change.modified : Number.NaN; + } +} diff --git a/services/sync/modules/engines/clients.js b/services/sync/modules/engines/clients.js index 8e28f13c4c1b6936d6a274080ee1d7bcac27e5d1..18e27e2f2144d0baf50f5c9b0923093e5963a907 100644 --- a/services/sync/modules/engines/clients.js +++ b/services/sync/modules/engines/clients.js @@ -311,7 +311,7 @@ ClientEngine.prototype = { const clientWithPendingCommands = Object.keys(this._currentlySyncingCommands); for (let clientId of clientWithPendingCommands) { if (this._store._remoteClients[clientId] || this.localID == clientId) { - this._modified[clientId] = 0; + this._modified.set(clientId, 0); } } SyncEngine.prototype._uploadOutgoing.call(this); diff --git a/services/sync/modules/telemetry.js b/services/sync/modules/telemetry.js index 2b2c3211b91b17ed98a7f5b9c30833ae10a3fa1e..a470e5a90048d6e69706bbc06c1c3d01f2348484 100644 --- a/services/sync/modules/telemetry.js +++ b/services/sync/modules/telemetry.js @@ -204,6 +204,7 @@ class TelemetryRecord { took: this.took, failureReason: this.failureReason, status: this.status, + deviceID: this.deviceID, }; let engines = []; for (let engine of this.engines) { @@ -228,8 +229,16 @@ class TelemetryRecord { try { this.uid = Weave.Service.identity.hashedUID(); + let deviceID = Weave.Service.identity.deviceID(); + if (deviceID) { + // Combine the raw device id with the metrics uid to create a stable + // unique identifier that can't be mapped back to the user's FxA + // identity without knowing the metrics HMAC key. + this.deviceID = Utils.sha256(deviceID + this.uid); + } } catch (e) { this.uid = "0".repeat(32); + this.deviceID = undefined; } // Check for engine statuses. -- We do this now, and not in engine.finished diff --git a/services/sync/modules/util.js b/services/sync/modules/util.js index c4664ec42612d6736d504e83363819eb02967a8b..ddc9fc44060b05b437e47dd06514fc3f9938f8d4 100644 --- a/services/sync/modules/util.js +++ b/services/sync/modules/util.js @@ -52,6 +52,7 @@ this.Utils = { digestBytes: CryptoUtils.digestBytes, sha1: CryptoUtils.sha1, sha1Base32: CryptoUtils.sha1Base32, + sha256: CryptoUtils.sha256, makeHMACKey: CryptoUtils.makeHMACKey, makeHMACHasher: CryptoUtils.makeHMACHasher, hkdfExpand: CryptoUtils.hkdfExpand, diff --git a/services/sync/tests/unit/sync_ping_schema.json b/services/sync/tests/unit/sync_ping_schema.json index 1590c202ca8741dba97be5f55319082b530e7da0..827be58fb07cb4396edc1b5c48d777d71b7f0800 100644 --- a/services/sync/tests/unit/sync_ping_schema.json +++ b/services/sync/tests/unit/sync_ping_schema.json @@ -26,6 +26,10 @@ "type": "string", "pattern": "^[0-9a-f]{32}$" }, + "deviceID": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + }, "status": { "type": "object", "anyOf": [ diff --git a/services/sync/tests/unit/test_bookmark_engine.js b/services/sync/tests/unit/test_bookmark_engine.js index e9dd2eb68fd7216ea9750bd8e49d65a10f1278de..fe443c75876798d33cfb1b7d262afbc7e6480e2f 100644 --- a/services/sync/tests/unit/test_bookmark_engine.js +++ b/services/sync/tests/unit/test_bookmark_engine.js @@ -132,9 +132,6 @@ add_task(function* bad_record_allIDs() { _("Record is " + badRecordID); _("Type: " + PlacesUtils.bookmarks.getItemType(badRecordID)); - _("Fetching children."); - store._getChildren("toolbar", {}); - _("Fetching all IDs."); let all = store.getAllIDs(); diff --git a/services/sync/tests/unit/test_bookmark_places_query_rewriting.js b/services/sync/tests/unit/test_bookmark_places_query_rewriting.js index bcc25c1ec528247ace5e19c11c75835248f97ebb..0ddf81583eb2744c49789791c23053420dc09d08 100644 --- a/services/sync/tests/unit/test_bookmark_places_query_rewriting.js +++ b/services/sync/tests/unit/test_bookmark_places_query_rewriting.js @@ -33,15 +33,18 @@ function run_test() { _("Folder name: " + tagRecord.folderName); store.applyIncoming(tagRecord); - let tags = store._getNode(PlacesUtils.tagsFolderId); - tags.containerOpen = true; + let tags = PlacesUtils.getFolderContents(PlacesUtils.tagsFolderId).root; let tagID; - for (let i = 0; i < tags.childCount; ++i) { - let child = tags.getChild(i); - if (child.title == "bar") - tagID = child.itemId; + try { + for (let i = 0; i < tags.childCount; ++i) { + let child = tags.getChild(i); + if (child.title == "bar") { + tagID = child.itemId; + } + } + } finally { + tags.containerOpen = false; } - tags.containerOpen = false; _("Tag ID: " + tagID); let insertedRecord = store.createRecord("abcdefabcdef", "bookmarks"); diff --git a/services/sync/tests/unit/test_bookmark_tracker.js b/services/sync/tests/unit/test_bookmark_tracker.js index 55a98d481737960c7a24f26a2934f08148ca08a6..7773faae89c398c51905cd3b05b7944ebb4eb899 100644 --- a/services/sync/tests/unit/test_bookmark_tracker.js +++ b/services/sync/tests/unit/test_bookmark_tracker.js @@ -22,8 +22,9 @@ const DAY_IN_MS = 24 * 60 * 60 * 1000; // Test helpers. function* verifyTrackerEmpty() { - do_check_empty(tracker.changedIDs); - do_check_eq(tracker.score, 0); + let changes = engine.pullNewChanges(); + equal(changes.count(), 0); + equal(tracker.score, 0); } function* resetTracker() { @@ -48,9 +49,12 @@ function* stopTracking() { } function* verifyTrackedItems(tracked) { - let trackedIDs = new Set(Object.keys(tracker.changedIDs)); + let changes = engine.pullNewChanges(); + let trackedIDs = new Set(changes.ids()); for (let guid of tracked) { - ok(tracker.changedIDs[guid] > 0, `${guid} should be tracked`); + ok(changes.has(guid), `${guid} should be tracked`); + ok(changes.getModifiedTimestamp(guid) > 0, + `${guid} should have a modified time`); trackedIDs.delete(guid); } equal(trackedIDs.size, 0, `Unhandled tracked IDs: ${ @@ -58,7 +62,8 @@ function* verifyTrackedItems(tracked) { } function* verifyTrackedCount(expected) { - do_check_attribute_count(tracker.changedIDs, expected); + let changes = engine.pullNewChanges(); + equal(changes.count(), expected); } add_task(function* test_tracking() { @@ -389,7 +394,7 @@ add_task(function* test_onItemTagged() { // bookmark should be tracked, folder should not be. yield verifyTrackedItems([bGUID]); - do_check_eq(tracker.score, SCORE_INCREMENT_XLARGE); + do_check_eq(tracker.score, SCORE_INCREMENT_XLARGE * 5); } finally { _("Clean up."); yield cleanup(); @@ -522,7 +527,7 @@ add_task(function* test_async_onItemTagged() { }); yield verifyTrackedItems([fxBmk1.guid, fxBmk2.guid]); - do_check_eq(tracker.score, SCORE_INCREMENT_XLARGE * 2); + do_check_eq(tracker.score, SCORE_INCREMENT_XLARGE * 6); } finally { _("Clean up."); yield cleanup(); @@ -700,31 +705,73 @@ add_task(function* test_onItemAnnoChanged() { } }); -add_task(function* test_onItemExcluded() { - _("Items excluded from backups should not be tracked"); +add_task(function* test_onItemAdded_filtered_root() { + _("Items outside the change roots should not be tracked"); + + try { + yield startTracking(); + + _("Create a new root"); + let rootID = PlacesUtils.bookmarks.createFolder( + PlacesUtils.bookmarks.placesRoot, + "New root", + PlacesUtils.bookmarks.DEFAULT_INDEX); + let rootGUID = engine._store.GUIDForId(rootID); + _(`New root GUID: ${rootGUID}`); + + _("Insert a bookmark underneath the new root"); + let untrackedBmkID = PlacesUtils.bookmarks.insertBookmark( + rootID, + Utils.makeURI("http://getthunderbird.com"), + PlacesUtils.bookmarks.DEFAULT_INDEX, + "Get Thunderbird!"); + let untrackedBmkGUID = engine._store.GUIDForId(untrackedBmkID); + _(`New untracked bookmark GUID: ${untrackedBmkGUID}`); + + _("Insert a bookmark underneath the Places root"); + let rootBmkID = PlacesUtils.bookmarks.insertBookmark( + PlacesUtils.bookmarks.placesRoot, + Utils.makeURI("http://getfirefox.com"), + PlacesUtils.bookmarks.DEFAULT_INDEX, "Get Firefox!"); + let rootBmkGUID = engine._store.GUIDForId(rootBmkID); + _(`New Places root bookmark GUID: ${rootBmkGUID}`); + + _("New root and bookmark should be ignored"); + yield verifyTrackedItems([]); + // ...But we'll still increment the score and filter out the changes at + // sync time. + do_check_eq(tracker.score, SCORE_INCREMENT_XLARGE * 6); + } finally { + _("Clean up."); + yield cleanup(); + } +}); + +add_task(function* test_onItemDeleted_filtered_root() { + _("Deleted items outside the change roots should be tracked"); try { yield stopTracking(); - _("Create a bookmark"); - let b = PlacesUtils.bookmarks.insertBookmark( - PlacesUtils.bookmarks.bookmarksMenuFolder, + _("Insert a bookmark underneath the Places root"); + let rootBmkID = PlacesUtils.bookmarks.insertBookmark( + PlacesUtils.bookmarks.placesRoot, Utils.makeURI("http://getfirefox.com"), PlacesUtils.bookmarks.DEFAULT_INDEX, "Get Firefox!"); - let bGUID = engine._store.GUIDForId(b); + let rootBmkGUID = engine._store.GUIDForId(rootBmkID); + _(`New Places root bookmark GUID: ${rootBmkGUID}`); yield startTracking(); - _("Exclude the bookmark from backups"); - PlacesUtils.annotations.setItemAnnotation( - b, BookmarkAnnos.EXCLUDEBACKUP_ANNO, "Don't back this up", 0, - PlacesUtils.annotations.EXPIRE_NEVER); - - _("Modify the bookmark"); - PlacesUtils.bookmarks.setItemTitle(b, "Download Firefox"); + PlacesUtils.bookmarks.removeItem(rootBmkID); - _("Excluded items should be ignored"); - yield verifyTrackerEmpty(); + // We shouldn't upload tombstones for items in filtered roots, but the + // `onItemRemoved` observer doesn't have enough context to determine + // the root, so we'll end up uploading it. + yield verifyTrackedItems([rootBmkGUID]); + // We'll increment the counter twice (once for the removed item, and once + // for the Places root), then filter out the root. + do_check_eq(tracker.score, SCORE_INCREMENT_XLARGE * 2); } finally { _("Clean up."); yield cleanup(); @@ -1254,22 +1301,43 @@ add_task(function* test_async_onItemDeleted_eraseEverything() { url: "https://developer.mozilla.org", title: "MDN", }); + _(`MDN GUID: ${mdnBmk.guid}`); let bugsFolder = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_FOLDER, parentGuid: PlacesUtils.bookmarks.toolbarGuid, title: "Bugs", }); + _(`Bugs folder GUID: ${bugsFolder.guid}`); let bzBmk = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK, parentGuid: bugsFolder.guid, url: "https://bugzilla.mozilla.org", title: "Bugzilla", }); + _(`Bugzilla GUID: ${bzBmk.guid}`); + let bugsChildFolder = yield PlacesUtils.bookmarks.insert({ + type: PlacesUtils.bookmarks.TYPE_FOLDER, + parentGuid: bugsFolder.guid, + title: "Bugs child", + }); + _(`Bugs child GUID: ${bugsChildFolder.guid}`); + let bugsGrandChildBmk = yield PlacesUtils.bookmarks.insert({ + type: PlacesUtils.bookmarks.TYPE_BOOKMARK, + parentGuid: bugsChildFolder.guid, + url: "https://example.com", + title: "Bugs grandchild", + }); + _(`Bugs grandchild GUID: ${bugsGrandChildBmk.guid}`); yield startTracking(); yield PlacesUtils.bookmarks.eraseEverything(); + // `eraseEverything` removes all items from the database before notifying + // observers. Because of this, grandchild lookup in the tracker's + // `onItemRemoved` observer will fail. That means we won't track + // (bzBmk.guid, bugsGrandChildBmk.guid, bugsChildFolder.guid), even + // though we should. yield verifyTrackedItems(["menu", mozBmk.guid, mdnBmk.guid, "toolbar", bugsFolder.guid]); do_check_eq(tracker.score, SCORE_INCREMENT_XLARGE * 6); diff --git a/taskcluster/taskgraph/task/nightly_fennec.py b/taskcluster/taskgraph/task/nightly_fennec.py index b9f08f6ed01dfb64e1e6f4d95e7c37690c23cbdb..37ad2b2bf3f00dec859b0e9b2fb4303a10c74b74 100644 --- a/taskcluster/taskgraph/task/nightly_fennec.py +++ b/taskcluster/taskgraph/task/nightly_fennec.py @@ -110,7 +110,10 @@ def query_vcs_info(repository, revision): class NightlyFennecTask(base.Task): def __init__(self, *args, **kwargs): - self.task_dict = kwargs.pop('task_dict') + try: + self.task_dict = kwargs.pop('task_dict') + except KeyError: + pass super(NightlyFennecTask, self).__init__(*args, **kwargs) @classmethod diff --git a/testing/marionette/driver.js b/testing/marionette/driver.js index c9de0e24ea62e0bfa76332720275a3f9e0c8464b..193aeaed4a40188f10ab1c7ca00a90a65332a6b0 100644 --- a/testing/marionette/driver.js +++ b/testing/marionette/driver.js @@ -2576,7 +2576,7 @@ GeckoDriver.prototype.quitApplication = function(cmd, resp) { } let flags = Ci.nsIAppStartup.eAttemptQuit; - for (let k of cmd.parameters.flags) { + for (let k of cmd.parameters.flags || []) { flags |= Ci.nsIAppStartup[k]; } diff --git a/testing/mozharness/mozharness/base/script.py b/testing/mozharness/mozharness/base/script.py index ffbb460a3e8d81246e53b71d27a44b481c2345be..fa24b16d70ec6b420edfd6228ad6a09b430b966b 100755 --- a/testing/mozharness/mozharness/base/script.py +++ b/testing/mozharness/mozharness/base/script.py @@ -50,7 +50,7 @@ try: except ImportError: import json -from cStringIO import StringIO +from io import BytesIO from mozprocess import ProcessHandler from mozharness.base.config import BaseConfig @@ -58,6 +58,10 @@ from mozharness.base.log import SimpleFileLogger, MultiFileLogger, \ LogMixin, OutputParser, DEBUG, INFO, ERROR, FATAL +class FetchedIncorrectFilesize(Exception): + pass + + def platform_name(): pm = PlatformMixin() @@ -341,6 +345,71 @@ class ScriptMixin(PlatformMixin): url_quoted = urllib2.quote(url, safe='%/:=&?~#+!$,;\'@()*[]|') return urllib2.urlopen(url_quoted, **kwargs) + + + def fetch_url_into_memory(self, url): + ''' Downloads a file from a url into memory instead of disk. + + Args: + url (str): URL path where the file to be downloaded is located. + + Raises: + IOError: When the url points to a file on disk and cannot be found + FetchedIncorrectFilesize: When the size of the fetched file does not match the + expected file size. + ValueError: When the scheme of a url is not what is expected. + + Returns: + BytesIO: contents of url + ''' + self.info('Fetch {} into memory'.format(url)) + parsed_url = urlparse.urlparse(url) + + if parsed_url.scheme in ('', 'file'): + if not os.path.isfile(url): + raise IOError('Could not find file to extract: {}'.format(url)) + + expected_file_size = os.stat(url.replace('file://', '')).st_size + + # In case we're referrencing a file without file:// + if parsed_url.scheme == '': + url = 'file://%s' % os.path.abspath(url) + parsed_url = urlparse.urlparse(url) + + request = urllib2.Request(url) + # Exceptions to be retried: + # Bug 1300663 - HTTPError: HTTP Error 404: Not Found + # Bug 1300413 - HTTPError: HTTP Error 500: Internal Server Error + # Bug 1300943 - HTTPError: HTTP Error 503: Service Unavailable + # Bug 1300953 - URLError: <urlopen error [Errno -2] Name or service not known> + # Bug 1301594 - URLError: <urlopen error [Errno 10054] An existing connection was ... + # Bug 1301597 - URLError: <urlopen error [Errno 8] _ssl.c:504: EOF occurred in ... + # Bug 1301855 - URLError: <urlopen error [Errno 60] Operation timed out> + # Bug 1302237 - URLError: <urlopen error [Errno 104] Connection reset by peer> + # Bug 1301807 - BadStatusLine: '' + response = urllib2.urlopen(request) + + if parsed_url.scheme in ('http', 'https'): + expected_file_size = int(response.headers.get('Content-Length')) + + self.info('Expected file size: {}'.format(expected_file_size)) + self.debug('Url: {}'.format(url)) + self.debug('Content-Encoding {}'.format(response.headers.get('Content-Encoding'))) + + file_contents = response.read() + obtained_file_size = len(file_contents) + + if obtained_file_size != expected_file_size: + raise FetchedIncorrectFilesize( + 'The expected file size is {} while we got instead {}'.format( + expected_file_size, obtained_file_size) + ) + + # Use BytesIO instead of StringIO + # http://stackoverflow.com/questions/34162017/unzip-buffer-with-python/34162395#34162395 + return BytesIO(file_contents) + + def _download_file(self, url, file_name): """ Helper script for download_file() Additionaly this function logs all exceptions as warnings before @@ -470,50 +539,56 @@ class ScriptMixin(PlatformMixin): yield entry - def unzip(self, file_object, extract_to='.', extract_dirs='*', verbose=False): + def unzip(self, compressed_file, extract_to, extract_dirs='*', verbose=False): """This method allows to extract a zip file without writing to disk first. Args: - file_object (object): Any file like object that is seekable. - extract_to (str, optional): where to extract the compressed file. + compressed_file (object): File-like object with the contents of a compressed zip file. + extract_to (str): where to extract the compressed file. extract_dirs (list, optional): directories inside the archive file to extract. Defaults to '*'. - """ - compressed_file = StringIO(file_object.read()) - try: - with zipfile.ZipFile(compressed_file) as bundle: - entries = self._filter_entries(bundle.namelist(), extract_dirs) - - for entry in entries: - if verbose: - self.info(' {}'.format(entry)) - bundle.extract(entry, path=extract_to) + verbose (bool, optional): whether or not extracted content should be displayed. + Defaults to False. - # ZipFile doesn't preserve permissions during extraction: - # http://bugs.python.org/issue15795 - fname = os.path.realpath(os.path.join(extract_to, entry)) + Raises: + zipfile.BadZipFile: on contents of zipfile being invalid + """ + with zipfile.ZipFile(compressed_file) as bundle: + entries = self._filter_entries(bundle.namelist(), extract_dirs) + + for entry in entries: + if verbose: + self.info(' {}'.format(entry)) + + # Exception to be retried: + # Bug 1301645 - BadZipfile: Bad CRC-32 for file ... + # http://stackoverflow.com/questions/5624669/strange-badzipfile-bad-crc-32-problem/5626098#5626098 + # Bug 1301802 - error: Error -3 while decompressing: invalid stored block lengths + bundle.extract(entry, path=extract_to) + + # ZipFile doesn't preserve permissions during extraction: + # http://bugs.python.org/issue15795 + fname = os.path.realpath(os.path.join(extract_to, entry)) + try: + # getinfo() can raise KeyError mode = bundle.getinfo(entry).external_attr >> 16 & 0x1FF # Only set permissions if attributes are available. Otherwise all # permissions will be removed eg. on Windows. if mode: os.chmod(fname, mode) - except zipfile.BadZipfile as e: - self.exception('{}'.format(e.message)) + except KeyError: + self.warning('{} was not found in the zip file'.format(entry)) - def deflate(self, file_object, mode, extract_to='.', extract_dirs='*', verbose=False): - """This method allows to extract a tar, tar.bz2 and tar.gz file without writing to disk first. + def deflate(self, compressed_file, mode, extract_to='.', *args, **kwargs): + """This method allows to extract a compressed file from a tar, tar.bz2 and tar.gz files. Args: - file_object (object): Any file like object that is seekable. + compressed_file (object): File-like object with the contents of a compressed file. + mode (str): string of the form 'filemode[:compression]' (e.g. 'r:gz' or 'r:bz2') extract_to (str, optional): where to extract the compressed file. - extract_dirs (list, optional): directories inside the archive file to extract. - Defaults to `*`. - verbose (bool, optional): whether or not extracted content should be displayed. - Defaults to False. """ - compressed_file = StringIO(file_object.read()) t = tarfile.open(fileobj=compressed_file, mode=mode) t.extractall(path=extract_to) @@ -527,90 +602,84 @@ class ScriptMixin(PlatformMixin): be extracted to. extract_dirs (list, optional): directories inside the archive to extract. Defaults to `*`. It currently only applies to zip files. - - Raises: - IOError: on `filename` file not found. + verbose (bool, optional): whether or not extracted content should be displayed. + Defaults to False. """ - # Many scripts overwrite this method and set extract_dirs to None - extract_dirs = '*' if extract_dirs is None else extract_dirs - EXTENSION_TO_MIMETYPE = { - 'bz2': 'application/x-bzip2', - 'gz': 'application/x-gzip', - 'tar': 'application/x-tar', - 'zip': 'application/zip', - } - MIMETYPES = { - 'application/x-bzip2': { - 'function': self.deflate, - 'kwargs': {'mode': 'r:bz2'}, - }, - 'application/x-gzip': { - 'function': self.deflate, - 'kwargs': {'mode': 'r:gz'}, - }, - 'application/x-tar': { - 'function': self.deflate, - 'kwargs': {'mode': 'r'}, - }, - 'application/zip': { - 'function': self.unzip, - }, - } - - parsed_url = urlparse.urlparse(url) - - # In case we're referrencing a file without file:// - if parsed_url.scheme == '': - if not os.path.isfile(url): - raise IOError('Could not find file to extract: {}'.format(url)) - - url = 'file://%s' % os.path.abspath(url) - parsed_fd = urlparse.urlparse(url) - - request = urllib2.Request(url) - response = urllib2.urlopen(request) + def _determine_extraction_method_and_kwargs(url): + EXTENSION_TO_MIMETYPE = { + 'bz2': 'application/x-bzip2', + 'gz': 'application/x-gzip', + 'tar': 'application/x-tar', + 'zip': 'application/zip', + } + MIMETYPES = { + 'application/x-bzip2': { + 'function': self.deflate, + 'kwargs': {'mode': 'r:bz2'}, + }, + 'application/x-gzip': { + 'function': self.deflate, + 'kwargs': {'mode': 'r:gz'}, + }, + 'application/x-tar': { + 'function': self.deflate, + 'kwargs': {'mode': 'r'}, + }, + 'application/zip': { + 'function': self.unzip, + }, + } - if parsed_url.scheme == 'file': filename = url.split('/')[-1] # XXX: bz2/gz instead of tar.{bz2/gz} extension = filename[filename.rfind('.')+1:] mimetype = EXTENSION_TO_MIMETYPE[extension] - else: - mimetype = response.headers.type + self.debug('Mimetype: {}'.format(mimetype)) - self.debug('Url: {}'.format(url)) - self.debug('Mimetype: {}'.format(mimetype)) - self.debug('Content-Encoding {}'.format(response.headers.get('Content-Encoding'))) + function = MIMETYPES[mimetype]['function'] + kwargs = { + 'compressed_file': compressed_file, + 'extract_to': extract_to, + 'extract_dirs': extract_dirs, + 'verbose': verbose, + } + kwargs.update(MIMETYPES[mimetype].get('kwargs', {})) - function = MIMETYPES[mimetype]['function'] - kwargs = { - 'file_object': response, - 'extract_to': extract_to, - 'extract_dirs': extract_dirs, - 'verbose': verbose, - } - kwargs.update(MIMETYPES[mimetype].get('kwargs', {})) + return function, kwargs + # Many scripts overwrite this method and set extract_dirs to None + extract_dirs = '*' if extract_dirs is None else extract_dirs self.info('Downloading and extracting to {} these dirs {} from {}'.format( extract_to, ', '.join(extract_dirs), url, )) + + # 1) Let's fetch the file retry_args = dict( - failure_status=None, - retry_exceptions=(urllib2.HTTPError, urllib2.URLError, - httplib.BadStatusLine, - socket.timeout, socket.error), + retry_exceptions=( + urllib2.HTTPError, + urllib2.URLError, + httplib.BadStatusLine, + socket.timeout, + socket.error, + FetchedIncorrectFilesize, + ), error_message="Can't download from {}".format(url), error_level=FATAL, ) - self.retry( - function, - kwargs=kwargs, + compressed_file = self.retry( + self.fetch_url_into_memory, + kwargs={'url': url}, **retry_args ) + # 2) We're guaranteed to have download the file with error_level=FATAL + # Let's unpack the file + function, kwargs = _determine_extraction_method_and_kwargs(url) + function(**kwargs) + def load_json_url(self, url, error_level=None, *args, **kwargs): """ Returns a json object from a url (it retries). """ diff --git a/testing/web-platform/README.md b/testing/web-platform/README.md index ec22f1f3c26e9b01b4d640937e86e1a512ed0f47..f51e473bb2c2c6e75c8ab27004ecff74291e9664 100644 --- a/testing/web-platform/README.md +++ b/testing/web-platform/README.md @@ -40,7 +40,8 @@ FAQ It is important to note that in order for the tests to run the manifest file must be updated; this should not be done by hand, but - by running `mach web-platform-tests --manifest-update`. + by running `mach wpt-manifest-update` (or `mach web-platform-tests + --manifest-update`, if you also wish to run some tests). `mach web-platform-tests-create <path>` is a helper script designed to help create new web-platform-tests. It opens a locally configured diff --git a/testing/web-platform/mach_commands.py b/testing/web-platform/mach_commands.py index 4f8b2aa37568e80ae063dfdd2cf4340fc9ab91dd..4c41283e761c426538877661f9cef261f6677644 100644 --- a/testing/web-platform/mach_commands.py +++ b/testing/web-platform/mach_commands.py @@ -236,6 +236,27 @@ testing/web-platform/tests for tests that may be shared proc.wait() +class WPTManifestUpdater(MozbuildObject): + def run_update(self): + import imp + from wptrunner import wptlogging + from wptrunner.wptcommandline import get_test_paths, set_from_config + from wptrunner.testloader import ManifestLoader + + wpt_dir = os.path.abspath(os.path.join(self.topsrcdir, 'testing', 'web-platform')) + + localpaths = imp.load_source("localpaths", + os.path.join(wpt_dir, "tests", "tools", "localpaths.py")) + kwargs = {"config": os.path.join(wpt_dir, "wptrunner.ini"), + "tests_root": None, + "metadata_root": None} + + wptlogging.setup({}, {"mach": sys.stdout}) + set_from_config(kwargs) + test_paths = get_test_paths(kwargs["config"]) + ManifestLoader(test_paths, force_manifest_update=True).load() + + def create_parser_wpt(): from wptrunner import wptcommandline return wptcommandline.create_parser(["firefox"]) @@ -292,6 +313,13 @@ class MachCommands(MachCommandBase): else: return wpt_runner.run_tests(**params) + @Command("wpt", + category="testing", + conditions=[conditions.is_firefox], + parser=create_parser_wpt) + def run_wpt(self, **params): + return self.run_web_platform_tests(**params) + @Command("web-platform-tests-update", category="testing", parser=create_parser_update) @@ -302,6 +330,12 @@ class MachCommands(MachCommandBase): wpt_updater = self._spawn(WebPlatformTestsUpdater) return wpt_updater.run_update(**params) + @Command("wpt-update", + category="testing", + parser=create_parser_update) + def update_wpt(self, **params): + return self.update_web_platform_tests(**params) + def setup(self): self._activate_virtualenv() @@ -314,6 +348,13 @@ class MachCommands(MachCommandBase): wpt_reduce = self._spawn(WebPlatformTestsReduce) return wpt_reduce.run_reduce(**params) + @Command("wpt-reduce", + category="testing", + conditions=[conditions.is_firefox], + parser=create_parser_reduce) + def unstable_wpt(self, **params): + return self.unstable_web_platform_tests(**params) + @Command("web-platform-tests-create", category="testing", conditions=[conditions.is_firefox], @@ -322,3 +363,17 @@ class MachCommands(MachCommandBase): self.setup() wpt_creator = self._spawn(WebPlatformTestsCreator) wpt_creator.run_create(self._mach_context, **params) + + @Command("wpt-create", + category="testing", + conditions=[conditions.is_firefox], + parser=create_parser_create) + def create_wpt(self, **params): + return self.create_web_platform_test(**params) + + @Command("wpt-manifest-update", + category="testing") + def wpt_manifest_update(self, **parms): + self.setup() + wpt_manifest_updater = self._spawn(WPTManifestUpdater) + wpt_manifest_updater.run_update() diff --git a/testing/web-platform/tests/web-animations/interfaces/Animation/id.html b/testing/web-platform/tests/web-animations/interfaces/Animation/id.html index 9d293733756350262046ff5d42b98c024bdce88b..2fadd562369951e9305e563846d1898e14f9af2a 100644 --- a/testing/web-platform/tests/web-animations/interfaces/Animation/id.html +++ b/testing/web-platform/tests/web-animations/interfaces/Animation/id.html @@ -13,11 +13,16 @@ test(function(t) { var div = createDiv(t); var animation = div.animate({}, 100 * MS_PER_SEC); - assert_equals(animation.id, '', 'id for CSS Animation is initially empty'); - animation.id = 'anim' + assert_equals(animation.id, '', 'id for Animation is initially empty'); +}, 'Animation.id initial value'); + +test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + animation.id = 'anim'; assert_equals(animation.id, 'anim', 'animation.id reflects the value set'); -}, 'Animation.id for CSS Animations'); +}, 'Animation.id setter'); </script> </body> diff --git a/toolkit/components/passwordmgr/LoginDoorhangers.jsm b/toolkit/components/passwordmgr/LoginDoorhangers.jsm deleted file mode 100644 index beec28078414ae791bdd4a354746663998abcba7..0000000000000000000000000000000000000000 --- a/toolkit/components/passwordmgr/LoginDoorhangers.jsm +++ /dev/null @@ -1,391 +0,0 @@ -/* 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"; - -this.EXPORTED_SYMBOLS = [ - "LoginDoorhangers", -]; - -const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components; - -Cu.import("resource://gre/modules/Services.jsm"); -Cu.import("resource://gre/modules/LoginManagerParent.jsm"); - -const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; - -// Helper function needed because the "disabled" property may not be available -// if the XBL binding of the UI control has not been constructed yet. -function setDisabled(element, disabled) { - if (disabled) { - element.setAttribute("disabled", "true"); - } else { - element.removeAttribute("disabled"); - } -} - -this.LoginDoorhangers = {}; - -/** - * Doorhanger for selecting and filling logins. - * - * @param {Object} properties - * Properties from this object will be applied to the new instance. - */ -this.LoginDoorhangers.FillDoorhanger = function (properties) { - // Set up infrastructure to access our elements and listen to events. - this.el = new Proxy({}, { - get: (target, name) => { - return this.chromeDocument.getElementById("login-fill-" + name); - }, - }); - this.eventHandlers = []; - for (let elementName of Object.keys(this.events)) { - let handlers = this.events[elementName]; - for (let eventName of Object.keys(handlers)) { - let handler = handlers[eventName]; - this.eventHandlers.push([elementName, eventName, handler.bind(this)]); - } - } - for (let name of Object.getOwnPropertyNames(properties)) { - this[name] = properties[name]; - } -}; - -this.LoginDoorhangers.FillDoorhanger.prototype = { - /** - * Whether the elements for this doorhanger are currently in the document. - */ - bound: false, - - /** - * Associates the doorhanger with its browser. When the tab associated to this - * browser is selected, the anchor icon for the doorhanger will appear. - * - * The browser may change during the lifetime of the doorhanger, in case the - * web page is moved to a different chrome window by the swapDocShells method. - */ - set browser(browser) { - const MAX_DATE_VALUE = new Date(8640000000000000); - - this._browser = browser; - - let doorhanger = this; - let PopupNotifications = this.chromeDocument.defaultView.PopupNotifications; - let notification = PopupNotifications.show( - browser, - "login-fill", - "", - "login-fill-notification-icon", - null, - null, - { - dismissed: true, - // This will make the anchor persist forever even if the popup is not - // visible. We'll remove the notification manually when the page - // changes, after we had time to check its final state asynchronously. - timeout: MAX_DATE_VALUE, - eventCallback: function (topic, otherBrowser) { - switch (topic) { - case "shown": - // Since we specified the "dismissed" option, this event will only - // be called after the "show" method returns, so the reference to - // "this.notification" will be available in "bind" at this point. - doorhanger.promiseHidden = - new Promise(resolve => doorhanger.onUnbind = resolve); - doorhanger.bind(); - break; - - case "dismissed": - case "removed": - if (doorhanger.bound) { - doorhanger.unbind(); - doorhanger.onUnbind(); - } - break; - - case "swapping": - doorhanger._browser = otherBrowser; - return true; - } - return false; - }, - } - ); - - this.notification = notification; - notification.doorhanger = this; - }, - get browser() { - return this._browser; - }, - _browser: null, - - /** - * DOM document to which the doorhanger is currently associated. - * - * This may change during the lifetime of the doorhanger, in case the web page - * is moved to a different chrome window by the swapDocShells method. - */ - get chromeDocument() { - return this.browser.ownerDocument; - }, - - /** - * Hides this notification, if the notification panel is currently open. - */ - hide() { - let PopupNotifications = this.chromeDocument.defaultView.PopupNotifications; - if (PopupNotifications.isPanelOpen) { - PopupNotifications.panel.hidePopup(); - } - }, - - /** - * Promise resolved as soon as the notification is hidden. - */ - promiseHidden: Promise.resolve(), - - /** - * Removes the doorhanger from the browser. - */ - remove() { - this.notification.remove(); - }, - - /** - * Binds this doorhanger to its UI controls. - */ - bind() { - // Since this may ask for the master password, we must do it at bind time. - if (this.autoDetailLogin) { - let formLogins = Services.logins.findLogins({}, this.loginFormOrigin, "", - null); - if (formLogins.length == 1) { - this.detailLogin = formLogins[0]; - } - this.autoDetailLogin = false; - } - - this.el.filter.setAttribute("value", this.filterString); - this.refreshList(); - this.refreshDetailView(); - - this.eventHandlers.forEach(([elementName, eventName, handler]) => { - this.el[elementName].addEventListener(eventName, handler, true); - }); - - // Move the main element to the notification panel for displaying. - this.notification.owner.panel.firstElementChild.appendChild(this.el.doorhanger); - this.el.doorhanger.hidden = false; - - this.bound = true; - }, - - /** - * Unbinds this doorhanger from its UI controls. - */ - unbind() { - this.bound = false; - - this.eventHandlers.forEach(([elementName, eventName, handler]) => { - this.el[elementName].removeEventListener(eventName, handler, true); - }); - - this.clearList(); - - // Place the element back in the document for the next time we need it. - this.el.doorhanger.hidden = true; - this.chromeDocument.getElementById("mainPopupSet") - .appendChild(this.el.doorhanger); - }, - - /** - * Origin for which the manual fill UI should be displayed, for example - * "http://www.example.com". - */ - loginFormOrigin: "", - - /** - * When no login form is present on the page, we may still display a list of - * logins, but we cannot offer manual filling. - */ - loginFormPresent: false, - - /** - * User-editable string used to filter the list of all logins. - */ - filterString: "", - - /** - * Show login details automatically when the panel is first opened. - */ - autoDetailLogin: false, - - /** - * Indicates which particular login to show in the detail view. - */ - set detailLogin(detailLogin) { - this._detailLogin = detailLogin; - if (this.bound) { - this.refreshDetailView(); - } - }, - get detailLogin() { - return this._detailLogin; - }, - _detailLogin: null, - - /** - * Prototype functions for event handling. - */ - events: { - mainview: { - focus(event) { - // If keyboard focus returns to any control in the the main view (for - // example using SHIFT+TAB) close the details view. - this.detailLogin = null; - }, - }, - filter: { - input(event) { - this.filterString = this.el.filter.value; - this.refreshList(); - }, - }, - list: { - click(event) { - if (event.button == 0 && this.el.list.selectedItem) { - this.displaySelectedLoginDetails(); - } - }, - keypress(event) { - if (event.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_RETURN && - this.el.list.selectedItem) { - this.displaySelectedLoginDetails(); - } - }, - }, - clickcapturer: { - click(event) { - this.detailLogin = null; - }, - }, - details: { - transitionend(event) { - // We must set focus to the detail controls only when the transition has - // ended, otherwise focus will interfere with the animation. We do this - // only when we're showing the detail view, not when leaving. - if (event.target == this.el.details && this.detailLogin) { - if (this.loginFormPresent) { - this.el.use.focus(); - } else { - this.el.username.focus(); - } - } - }, - }, - use: { - command(event) { - this.fillLogin(); - }, - }, - }, - - /** - * Rebuilds the list of logins. - */ - refreshList() { - this.clearList(); - - let formLogins = Services.logins.findLogins({}, "", "", null); - let filterToUse = this.filterString.trim().toLowerCase(); - if (filterToUse) { - formLogins = formLogins.filter(login => { - return login.hostname.toLowerCase().indexOf(filterToUse) != -1 || - login.username.toLowerCase().indexOf(filterToUse) != -1 ; - }); - } - - for (let { hostname, username } of formLogins) { - let item = this.chromeDocument.createElementNS(XUL_NS, "richlistitem"); - item.classList.add("login-fill-item"); - item.setAttribute("hostname", hostname); - item.setAttribute("username", username); - if (hostname != this.loginFormOrigin) { - item.classList.add("different-hostname"); - } - this.el.list.appendChild(item); - } - }, - - /** - * Clears the list of logins. - */ - clearList() { - let list = this.el.list; - while (list.firstChild) { - list.firstChild.remove(); - } - }, - - /** - * Updates all the controls of the detail view based on the chosen login. - */ - refreshDetailView() { - if (this.detailLogin) { - this.el.username.setAttribute("value", this.detailLogin.username); - this.el.password.setAttribute("value", this.detailLogin.password); - this.el.doorhanger.setAttribute("inDetailView", "true"); - setDisabled(this.el.username, false); - setDisabled(this.el.use, !this.loginFormPresent); - } else { - this.el.doorhanger.removeAttribute("inDetailView"); - // We must disable all the detail controls to ensure they cannot be - // selected with the keyboard while they are outside the visible area. - setDisabled(this.el.username, true); - setDisabled(this.el.use, true); - } - }, - - displaySelectedLoginDetails() { - let selectedItem = this.el.list.selectedItem; - let hostLogins = Services.logins.findLogins({}, - selectedItem.getAttribute("hostname"), "", null); - let login = hostLogins.find(login => { - return login.username == selectedItem.getAttribute("username"); - }); - if (!login) { - Cu.reportError("The selected login has been removed in the meantime."); - return; - } - this.detailLogin = login; - }, - - fillLogin() { - LoginManagerParent.fillForm({ - browser: this.browser, - loginFormOrigin: this.loginFormOrigin, - login: this.detailLogin, - }).catch(Cu.reportError); - this.hide(); - }, -}; - -/** - * Retrieves an existing FillDoorhanger associated with a browser, or null if an - * associated doorhanger of that type cannot be found. - * - * @param An object with the following properties: - * { - * browser: - * The <browser> element to which the doorhanger is associated. - * } - */ -this.LoginDoorhangers.FillDoorhanger.find = function ({ browser }) { - let PopupNotifications = browser.ownerDocument.defaultView.PopupNotifications; - let notification = PopupNotifications.getNotification("login-fill", - browser); - return notification && notification.doorhanger; -}; diff --git a/toolkit/components/passwordmgr/LoginManagerContent.jsm b/toolkit/components/passwordmgr/LoginManagerContent.jsm index 8d0c8d83b9b9935e53ed051641b46d9ea0e2ecea..177082be1da5defff12f10b18d71c38e04c20a73 100644 --- a/toolkit/components/passwordmgr/LoginManagerContent.jsm +++ b/toolkit/components/passwordmgr/LoginManagerContent.jsm @@ -408,7 +408,7 @@ var LoginManagerContent = { * @param {Window} window */ _fetchLoginsFromParentAndFillForm(form, window) { - this._updateLoginFormPresence(window); + this._detectInsecureFormLikes(window); let messageManager = messageManagerFromWindow(window); messageManager.sendAsyncMessage("LoginStats:LoginEncountered"); @@ -423,7 +423,7 @@ var LoginManagerContent = { }, onPageShow(event, window) { - this._updateLoginFormPresence(window); + this._detectInsecureFormLikes(window); }, /** @@ -448,38 +448,11 @@ var LoginManagerContent = { }, /** - * Compute whether there is a login form on any frame of the current page, and - * notify the parent process. This is one of the factors used to control the - * visibility of the password fill doorhanger anchor. + * Compute whether there is an insecure login form on any frame of the current page, and + * notify the parent process. This is used to control whether insecure password UI appears. */ - _updateLoginFormPresence(topWindow) { - log("_updateLoginFormPresence", topWindow.location.href); - // For the login form presence notification, we currently support only one - // origin for each browser, so the form origin will always match the origin - // of the top level document. - let loginFormOrigin = - LoginUtils._getPasswordOrigin(topWindow.document.documentURI); - - // Returns the first known loginForm present in this window or in any - // same-origin subframes. Returns null if no loginForm is currently present. - let getFirstLoginForm = thisWindow => { - let loginForms = this.stateForDocument(thisWindow.document).loginFormRootElements; - if (loginForms.size) { - return [...loginForms][0]; - } - for (let i = 0; i < thisWindow.frames.length; i++) { - let frame = thisWindow.frames[i]; - if (LoginUtils._getPasswordOrigin(frame.document.documentURI) != - loginFormOrigin) { - continue; - } - let loginForm = getFirstLoginForm(frame); - if (loginForm) { - return loginForm; - } - } - return null; - }; + _detectInsecureFormLikes(topWindow) { + log("_detectInsecureFormLikes", topWindow.location.href); // Returns true if this window or any subframes have insecure login forms. let hasInsecureLoginForms = (thisWindow, parentIsInsecure) => { @@ -491,16 +464,8 @@ var LoginManagerContent = { frame => hasInsecureLoginForms(frame, isInsecure)); }; - // Store the actual form to use on the state for the top-level document. - let topState = this.stateForDocument(topWindow.document); - topState.loginFormForFill = getFirstLoginForm(topWindow); - log("_updateLoginFormPresence: topState.loginFormForFill", topState.loginFormForFill); - - // Determine whether to show the anchor icon for the current tab. let messageManager = messageManagerFromWindow(topWindow); - messageManager.sendAsyncMessage("RemoteLogins:updateLoginFormPresence", { - loginFormOrigin, - loginFormPresent: !!topState.loginFormForFill, + messageManager.sendAsyncMessage("RemoteLogins:insecureLoginFormPresent", { hasInsecureLoginForms: hasInsecureLoginForms(topWindow, false), }); }, @@ -526,14 +491,12 @@ var LoginManagerContent = { * recipes: * Fill recipes transmitted together with the original message. * inputElement: - * Optional input password element from the form we want to fill. + * Username or password input element from the form we want to fill. * } */ fillForm({ topDocument, loginFormOrigin, loginsFound, recipes, inputElement }) { - let topState = this.stateForDocument(topDocument); - if (!inputElement && !topState.loginFormForFill) { - log("fillForm: There is no login form anymore. The form may have been", - "removed or the document may have changed."); + if (!inputElement) { + log("fillForm: No input element specified"); return; } if (LoginUtils._getPasswordOrigin(topDocument.documentURI) != loginFormOrigin) { @@ -546,18 +509,15 @@ var LoginManagerContent = { return; } } - let form = topState.loginFormForFill; + let clobberUsername = true; let options = { inputElement, }; - // If we have a target input, fills it's form. - if (inputElement) { - form = FormLikeFactory.createFromField(inputElement); - if (inputElement.type == "password") { - clobberUsername = false; - } + let form = FormLikeFactory.createFromField(inputElement); + if (inputElement.type == "password") { + clobberUsername = false; } this._fillForm(form, true, clobberUsername, true, true, loginsFound, recipes, options); }, diff --git a/toolkit/components/passwordmgr/LoginManagerParent.jsm b/toolkit/components/passwordmgr/LoginManagerParent.jsm index 7a222c47efeb5d94b9f87e04fe4a57e5577134e0..7cf6381ba4ca53fb8f72f37304bc8b062463c5b6 100644 --- a/toolkit/components/passwordmgr/LoginManagerParent.jsm +++ b/toolkit/components/passwordmgr/LoginManagerParent.jsm @@ -17,8 +17,6 @@ XPCOMUtils.defineLazyModuleGetter(this, "AutoCompletePopup", "resource://gre/modules/AutoCompletePopup.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "DeferredTask", "resource://gre/modules/DeferredTask.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "LoginDoorhangers", - "resource://gre/modules/LoginDoorhangers.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "LoginHelper", "resource://gre/modules/LoginHelper.jsm"); @@ -48,7 +46,7 @@ var LoginManagerParent = { mm.addMessageListener("RemoteLogins:onFormSubmit", this); mm.addMessageListener("RemoteLogins:autoCompleteLogins", this); mm.addMessageListener("RemoteLogins:removeLogin", this); - mm.addMessageListener("RemoteLogins:updateLoginFormPresence", this); + mm.addMessageListener("RemoteLogins:insecureLoginFormPresent", this); XPCOMUtils.defineLazyGetter(this, "recipeParentPromise", () => { const { LoginRecipesParent } = Cu.import("resource://gre/modules/LoginRecipes.jsm", {}); @@ -89,8 +87,8 @@ var LoginManagerParent = { break; } - case "RemoteLogins:updateLoginFormPresence": { - this.updateLoginFormPresence(msg.target, data); + case "RemoteLogins:insecureLoginFormPresent": { + this.setHasInsecureLoginForms(msg.target, data.hasInsecureLoginForms); break; } @@ -470,83 +468,18 @@ var LoginManagerParent = { }, /** - * Called to indicate whether a login form on the currently loaded page is - * present or not. This is one of the factors used to control the visibility - * of the password fill doorhanger. + * Called to indicate whether an insecure password field is present so + * insecure password UI can know when to show. */ - updateLoginFormPresence(browser, { loginFormOrigin, loginFormPresent, - hasInsecureLoginForms }) { - const ANCHOR_DELAY_MS = 200; - + setHasInsecureLoginForms(browser, hasInsecureLoginForms) { let state = this.stateForBrowser(browser); // Update the data to use to the latest known values. Since messages are // processed in order, this will always be the latest version to use. - state.loginFormOrigin = loginFormOrigin; - state.loginFormPresent = loginFormPresent; state.hasInsecureLoginForms = hasInsecureLoginForms; // Report the insecure login form state immediately. browser.dispatchEvent(new browser.ownerDocument.defaultView .CustomEvent("InsecureLoginFormsStateChange")); - - // Apply the data to the currently displayed login fill icon later. - if (!state.anchorDeferredTask) { - state.anchorDeferredTask = new DeferredTask( - () => this.updateLoginAnchor(browser), - ANCHOR_DELAY_MS - ); - } - state.anchorDeferredTask.arm(); }, - - updateLoginAnchor: Task.async(function* (browser) { - // Once this preference is removed, this version of the fill doorhanger - // should be enabled for Desktop only, and not for Android or B2G. - if (!Services.prefs.getBoolPref("signon.ui.experimental")) { - return; - } - - // Copy the state to use for this execution of the task. These will not - // change during this execution of the asynchronous function, but in case a - // change happens in the state, the function will be retriggered. - let { loginFormOrigin, loginFormPresent } = this.stateForBrowser(browser); - - yield Services.logins.initializationPromise; - - // Check if there are form logins for the site, ignoring formSubmitURL. - let hasLogins = loginFormOrigin && - LoginHelper.searchLoginsWithObject({ - httpRealm: null, - hostname: loginFormOrigin, - schemeUpgrades: LoginHelper.schemeUpgrades, - }).length > 0; - - let showLoginAnchor = loginFormPresent || hasLogins; - - let fillDoorhanger = LoginDoorhangers.FillDoorhanger.find({ browser }); - if (fillDoorhanger) { - if (!showLoginAnchor) { - fillDoorhanger.remove(); - return; - } - // We should only update the state of the doorhanger while it is hidden. - yield fillDoorhanger.promiseHidden; - fillDoorhanger.loginFormPresent = loginFormPresent; - fillDoorhanger.loginFormOrigin = loginFormOrigin; - fillDoorhanger.filterString = hasLogins ? loginFormOrigin : ""; - fillDoorhanger.detailLogin = null; - fillDoorhanger.autoDetailLogin = true; - return; - } - if (showLoginAnchor) { - fillDoorhanger = new LoginDoorhangers.FillDoorhanger({ - browser, - loginFormPresent, - loginFormOrigin, - filterString: hasLogins ? loginFormOrigin : "", - autoDetailLogin: true, - }); - } - }), }; diff --git a/toolkit/components/passwordmgr/content/login.xml b/toolkit/components/passwordmgr/content/login.xml deleted file mode 100644 index 42cf4afecb7a070bfad431ef38131225726bdbfc..0000000000000000000000000000000000000000 --- a/toolkit/components/passwordmgr/content/login.xml +++ /dev/null @@ -1,22 +0,0 @@ -<?xml version="1.0"?> -<!-- 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/. --> - -<!DOCTYPE bindings SYSTEM "chrome://passwordmgr/locale/passwordManager.dtd"> - -<bindings id="login-bindings" - xmlns="http://www.mozilla.org/xbl" - xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" - xmlns:xbl="http://www.mozilla.org/xbl"> - - <binding id="login" - extends="chrome://global/content/bindings/richlistbox.xml#richlistitem"> - <content orient="vertical"> - <xul:label class="login-hostname" crop="end" - xbl:inherits="value=hostname"/> - <xul:label class="login-username" crop="end" - xbl:inherits="value=username"/> - </content> - </binding> -</bindings> diff --git a/toolkit/components/passwordmgr/jar.mn b/toolkit/components/passwordmgr/jar.mn index 5af9f7535c75e91810a4ef762a5d96fb8a5bfa3a..9fa574e4931fbb21c6d049b39076d40ebdb3bee3 100644 --- a/toolkit/components/passwordmgr/jar.mn +++ b/toolkit/components/passwordmgr/jar.mn @@ -4,7 +4,6 @@ toolkit.jar: % content passwordmgr %content/passwordmgr/ - content/passwordmgr/login.xml (content/login.xml) * content/passwordmgr/passwordManager.xul (content/passwordManager.xul) content/passwordmgr/passwordManager.js (content/passwordManager.js) content/passwordmgr/recipes.json (content/recipes.json) diff --git a/toolkit/components/passwordmgr/moz.build b/toolkit/components/passwordmgr/moz.build index ae27753598f3d8151530faf40d01941e3129e434..72c8c70a470d0de88d41cb7c02f28a84b70f2f12 100644 --- a/toolkit/components/passwordmgr/moz.build +++ b/toolkit/components/passwordmgr/moz.build @@ -69,7 +69,6 @@ if CONFIG['OS_TARGET'] == 'WINNT': if CONFIG['MOZ_BUILD_APP'] == 'browser': EXTRA_JS_MODULES += [ - 'LoginDoorhangers.jsm', 'LoginManagerContextMenu.jsm', ] diff --git a/toolkit/components/passwordmgr/nsLoginManagerPrompter.js b/toolkit/components/passwordmgr/nsLoginManagerPrompter.js index 352a0470afc5c004e482576edbf64d16742faaaa..3c393f026eb7d13146cb5a05d34ccb3e6c332314 100644 --- a/toolkit/components/passwordmgr/nsLoginManagerPrompter.js +++ b/toolkit/components/passwordmgr/nsLoginManagerPrompter.js @@ -1065,6 +1065,8 @@ LoginManagerPrompter.prototype = { this._showLoginNotification(aNotifyObj, "password-save", notificationText, buttons); } + + Services.obs.notifyObservers(aLogin, "passwordmgr-prompt-save", null); }, _removeLoginNotifications : function () { @@ -1144,6 +1146,8 @@ LoginManagerPrompter.prototype = { // userChoice == 1 --> just ignore the login. this.log("Ignoring login."); } + + Services.obs.notifyObservers(aLogin, "passwordmgr-prompt-save", null); }, @@ -1235,6 +1239,9 @@ LoginManagerPrompter.prototype = { this._showLoginNotification(aNotifyObj, "password-change", notificationText, buttons); } + + let oldGUID = aOldLogin.QueryInterface(Ci.nsILoginMetaInfo).guid; + Services.obs.notifyObservers(aNewLogin, "passwordmgr-prompt-change", oldGUID); }, @@ -1265,6 +1272,9 @@ LoginManagerPrompter.prototype = { this.log("Updating password for user " + aOldLogin.username); this._updateLogin(aOldLogin, aNewLogin); } + + let oldGUID = aOldLogin.QueryInterface(Ci.nsILoginMetaInfo).guid; + Services.obs.notifyObservers(aNewLogin, "passwordmgr-prompt-change", oldGUID); }, diff --git a/toolkit/components/passwordmgr/test/browser/browser.ini b/toolkit/components/passwordmgr/test/browser/browser.ini index 9eedd58e0701630365ee76ad06d9526562401260..b99b0899c235d93e2188279a21334a04895f0999 100644 --- a/toolkit/components/passwordmgr/test/browser/browser.ini +++ b/toolkit/components/passwordmgr/test/browser/browser.ini @@ -15,7 +15,6 @@ support-files = streamConverter_content.sjs [browser_capture_doorhanger.js] -skip-if = e10s # Bug 1277105 support-files = subtst_notifications_1.html subtst_notifications_2.html @@ -39,7 +38,6 @@ support-files = [browser_DOMFormHasPassword.js] [browser_DOMInputPasswordAdded.js] [browser_exceptions_dialog.js] -[browser_filldoorhanger.js] [browser_formless_submit_chrome.js] [browser_hasInsecureLoginForms.js] [browser_hasInsecureLoginForms_streamConverter.js] diff --git a/toolkit/components/passwordmgr/test/browser/browser_capture_doorhanger.js b/toolkit/components/passwordmgr/test/browser/browser_capture_doorhanger.js index 18fb807664a6fa20ea22ff28113ca3ad8e7aa9a3..dc8b33c97bc5e3fa53e2cff2181f0761986bef19 100644 --- a/toolkit/components/passwordmgr/test/browser/browser_capture_doorhanger.js +++ b/toolkit/components/passwordmgr/test/browser/browser_capture_doorhanger.js @@ -50,6 +50,8 @@ add_task(function* test_clickNever() { ok(notif, "got notification popup"); is(true, Services.logins.getLoginSavingEnabled("http://example.com"), "Checking for login saving enabled"); + + yield* checkDoorhangerUsernamePassword("notifyu1", "notifyp1"); clickDoorhangerButton(notif, NEVER_BUTTON); }); @@ -77,6 +79,8 @@ add_task(function* test_clickRemember() { ok(notif, "got notification popup"); is(Services.logins.getAllLogins().length, 0, "Should not have any logins yet"); + + yield* checkDoorhangerUsernamePassword("notifyu1", "notifyp1"); clickDoorhangerButton(notif, REMEMBER_BUTTON); }); @@ -271,6 +275,8 @@ add_task(function* test_changeUPLoginOnUPForm_dont() { is(fieldValues.password, "pass2", "Checking submitted password"); let notif = getCaptureDoorhanger("password-change"); ok(notif, "got notification popup"); + + yield* checkDoorhangerUsernamePassword("notifyu1", "pass2"); clickDoorhangerButton(notif, DONT_CHANGE_BUTTON); }); @@ -293,7 +299,10 @@ add_task(function* test_changeUPLoginOnUPForm_change() { is(fieldValues.password, "pass2", "Checking submitted password"); let notif = getCaptureDoorhanger("password-change"); ok(notif, "got notification popup"); + + yield* checkDoorhangerUsernamePassword("notifyu1", "pass2"); clickDoorhangerButton(notif, CHANGE_BUTTON); + ok(!getCaptureDoorhanger("password-change"), "popup should be gone"); }); @@ -321,7 +330,10 @@ add_task(function* test_changePLoginOnUPForm() { is(fieldValues.password, "pass2", "Checking submitted password"); let notif = getCaptureDoorhanger("password-change"); ok(notif, "got notification popup"); + + yield* checkDoorhangerUsernamePassword("", "pass2"); clickDoorhangerButton(notif, CHANGE_BUTTON); + ok(!getCaptureDoorhanger("password-change"), "popup should be gone"); }); @@ -343,7 +355,10 @@ add_task(function* test_changePLoginOnPForm() { is(fieldValues.password, "notifyp1", "Checking submitted password"); let notif = getCaptureDoorhanger("password-change"); ok(notif, "got notification popup"); + + yield* checkDoorhangerUsernamePassword("", "notifyp1"); clickDoorhangerButton(notif, CHANGE_BUTTON); + ok(!getCaptureDoorhanger("password-change"), "popup should be gone"); }); @@ -490,7 +505,10 @@ add_task(function* test_changeUPLoginOnPUpdateForm() { is(fieldValues.password, "pass2", "Checking submitted password"); let notif = getCaptureDoorhanger("password-change"); ok(notif, "got notification popup"); + + yield* checkDoorhangerUsernamePassword("notifyu1", "pass2"); clickDoorhangerButton(notif, CHANGE_BUTTON); + ok(!getCaptureDoorhanger("password-change"), "popup should be gone"); }); @@ -522,7 +540,9 @@ add_task(function* test_recipeCaptureFields_NewLogin() { let logins = Services.logins.getAllLogins(); is(logins.length, 0, "Should not have any logins yet"); + yield* checkDoorhangerUsernamePassword("notifyu1", "notifyp1"); clickDoorhangerButton(notif, REMEMBER_BUTTON); + }, "http://example.org"); // The recipe is for example.org let logins = Services.logins.getAllLogins(); @@ -615,7 +635,10 @@ add_task(function* test_httpsUpgradeCaptureFields_changePW() { is(fieldValues.password, "pass2", "Checking submitted password"); let notif = getCaptureDoorhanger("password-change"); ok(notif, "checking for a change popup"); + + yield* checkDoorhangerUsernamePassword("notifyu1", "pass2"); clickDoorhangerButton(notif, CHANGE_BUTTON); + ok(!getCaptureDoorhanger("password-change"), "popup should be gone"); }, "https://example.com"); // This is HTTPS whereas the saved login is HTTP @@ -643,6 +666,8 @@ add_task(function* test_httpsUpgradeCaptureFields_captureMatchingHTTP() { ok(notif, "got notification popup"); is(Services.logins.getAllLogins().length, 1, "Should only have the HTTPS login"); + + yield* checkDoorhangerUsernamePassword("notifyu1", "notifyp1"); clickDoorhangerButton(notif, REMEMBER_BUTTON); }); diff --git a/toolkit/components/passwordmgr/test/browser/browser_capture_doorhanger_window_open.js b/toolkit/components/passwordmgr/test/browser/browser_capture_doorhanger_window_open.js index 40861c48219064bf38466b60e054b50106809977..1bcfec5eb169633673c804f285c9c028417ff194 100644 --- a/toolkit/components/passwordmgr/test/browser/browser_capture_doorhanger_window_open.js +++ b/toolkit/components/passwordmgr/test/browser/browser_capture_doorhanger_window_open.js @@ -33,17 +33,6 @@ function withTestTabUntilStorageChange(aPageFile, aTaskFn) { }); } -function* checkDoorhangerUsernamePassword(username, password) { - yield BrowserTestUtils.waitForCondition(() => { - return document.getElementById("password-notification-username").value == username; - }, "Wait for nsLoginManagerPrompter writeDataToUI()"); - is(document.getElementById("password-notification-username").value, username, - "Check doorhanger username"); - is(document.getElementById("password-notification-password").value, password, - "Check doorhanger password"); -} - - add_task(function* setup() { yield SimpleTest.promiseFocus(window); }); diff --git a/toolkit/components/passwordmgr/test/browser/browser_filldoorhanger.js b/toolkit/components/passwordmgr/test/browser/browser_filldoorhanger.js deleted file mode 100644 index 37421180141c45977d26b2b6f98b2ebc306becf1..0000000000000000000000000000000000000000 --- a/toolkit/components/passwordmgr/test/browser/browser_filldoorhanger.js +++ /dev/null @@ -1,75 +0,0 @@ -/** - * All these tests require the experimental login fill UI to be enabled. We also - * disable autofill for login forms for easier testing of manual fill. - */ -add_task(function* test_initialize() { - Services.prefs.setBoolPref("signon.ui.experimental", true); - Services.prefs.setBoolPref("signon.autofillForms", false); - registerCleanupFunction(function () { - Services.prefs.clearUserPref("signon.ui.experimental"); - Services.prefs.clearUserPref("signon.autofillForms"); - }); -}); - -/** - * Tests manual fill when the page has a login form. - */ -add_task(function* test_fill() { - Services.logins.addLogin(LoginTestUtils.testData.formLogin({ - hostname: "https://example.com", - formSubmitURL: "https://example.com", - username: "username", - password: "password", - })); - - // The anchor icon may be shown during the initial page load in the new tab, - // so we have to set up the observers first. When we receive the notification - // from PopupNotifications.jsm, we check it is the one for the right anchor. - let anchor = document.getElementById("login-fill-notification-icon"); - let promiseAnchorShown = - TestUtils.topicObserved("PopupNotifications-updateNotShowing", - () => anchor.hasAttribute("showing")); - - yield BrowserTestUtils.withNewTab({ - gBrowser, - url: "https://example.com/browser/toolkit/components/" + - "passwordmgr/test/browser/form_basic.html", - }, function* (browser) { - yield promiseAnchorShown; - - let promiseShown = BrowserTestUtils.waitForEvent(PopupNotifications.panel, - "Shown"); - anchor.click(); - yield promiseShown; - - let list = document.getElementById("login-fill-list"); - Assert.equal(list.childNodes.length, 1, - "list.childNodes.length === 1"); - - // The button will be focused after the "transitionend" event. - list.focus(); - yield new Promise(resolve => executeSoon(resolve)); - let details = document.getElementById("login-fill-details"); - let promiseSubview = BrowserTestUtils.waitForEvent(details, - "transitionend", true, - e => e.target == details); - EventUtils.sendMouseEvent({ type: "click" }, list.childNodes[0]); - yield promiseSubview; - - // Clicking the button will dismiss the panel. - let promiseHidden = BrowserTestUtils.waitForEvent(PopupNotifications.panel, - "popuphidden"); - document.getElementById("login-fill-use").click(); - yield promiseHidden; - - yield ContentTask.spawn(browser, null, function* () { - let doc = content.document; - Assert.equal(doc.getElementById("form-basic-username").value, "username", - "result.username === \"username\""); - Assert.equal(doc.getElementById("form-basic-password").value, "password", - "result.password === \"password\""); - }); - }); - - Services.logins.removeAllLogins(); -}); diff --git a/toolkit/components/passwordmgr/test/browser/head.js b/toolkit/components/passwordmgr/test/browser/head.js index 781b723c74d5153f75ceb48945f27c3ea0e87fe5..786e7697b619c878dccb9a9c02010db57ad8a40e 100644 --- a/toolkit/components/passwordmgr/test/browser/head.js +++ b/toolkit/components/passwordmgr/test/browser/head.js @@ -118,4 +118,20 @@ function clickDoorhangerButton(aPopup, aButtonIndex) { } } +/** + * Checks the doorhanger's username and password. + * + * @param {String} username The username. + * @param {String} password The password. + */ +function* checkDoorhangerUsernamePassword(username, password) { + yield BrowserTestUtils.waitForCondition(() => { + return document.getElementById("password-notification-username").value == username; + }, "Wait for nsLoginManagerPrompter writeDataToUI()"); + is(document.getElementById("password-notification-username").value, username, + "Check doorhanger username"); + is(document.getElementById("password-notification-password").value, password, + "Check doorhanger password"); +} + // End popup notification (doorhanger) functions // diff --git a/toolkit/components/passwordmgr/test/chrome/chrome.ini b/toolkit/components/passwordmgr/test/chrome/chrome.ini index b5ada17cd80311cc9e5341551a6545ccac6d8ee4..c3e96c23fbbdf6dda140f796f8f722167c5017a1 100644 --- a/toolkit/components/passwordmgr/test/chrome/chrome.ini +++ b/toolkit/components/passwordmgr/test/chrome/chrome.ini @@ -5,7 +5,7 @@ skip-if = buildapp == 'b2g' || os == 'android' skip-if = true # Bug 1173337 support-files = ../formsubmit.sjs - ../notification_common.js + notification_common.js privbrowsing_perwindowpb_iframe.html subtst_privbrowsing_1.html subtst_privbrowsing_2.html diff --git a/toolkit/components/passwordmgr/test/notification_common.js b/toolkit/components/passwordmgr/test/chrome/notification_common.js similarity index 100% rename from toolkit/components/passwordmgr/test/notification_common.js rename to toolkit/components/passwordmgr/test/chrome/notification_common.js diff --git a/toolkit/components/passwordmgr/test/mochitest.ini b/toolkit/components/passwordmgr/test/mochitest.ini index bb5cb948191224b90dd09285025dda9117cd2b92..41bd9b657688eedc8820995c2ef7abde70075c72 100644 --- a/toolkit/components/passwordmgr/test/mochitest.ini +++ b/toolkit/components/passwordmgr/test/mochitest.ini @@ -4,7 +4,6 @@ support-files = authenticate.sjs blank.html formsubmit.sjs - notification_common.js prompt_common.js pwmgr_common.js subtst_master_pass.html diff --git a/toolkit/components/passwordmgr/test/mochitest/mochitest.ini b/toolkit/components/passwordmgr/test/mochitest/mochitest.ini index c3baaa86ecac6abe798adbc1aaf15aaf499e4b1e..a4b46a4760aa667b34eefe90fc507bb06b9d18d8 100644 --- a/toolkit/components/passwordmgr/test/mochitest/mochitest.ini +++ b/toolkit/components/passwordmgr/test/mochitest/mochitest.ini @@ -9,7 +9,6 @@ support-files = ../blank.html ../browser/form_basic.html ../browser/form_cross_origin_secure_action.html - ../notification_common.js ../pwmgr_common.js auth2/authenticate.sjs @@ -49,7 +48,7 @@ skip-if = toolkit == 'android' # autocomplete [test_prompt.html] skip-if = os == "linux" || toolkit == 'android' # Tests desktop prompts [test_prompt_http.html] -skip-if = e10s || os == "linux" || toolkit == 'android' # Tests desktop prompts +skip-if = os == "linux" || toolkit == 'android' # Tests desktop prompts [test_prompt_promptAuth.html] skip-if = os == "linux" || toolkit == 'android' # Tests desktop prompts [test_prompt_promptAuth_proxy.html] diff --git a/toolkit/components/passwordmgr/test/mochitest/test_prompt_http.html b/toolkit/components/passwordmgr/test/mochitest/test_prompt_http.html index 632bbc7f40aa7835913d34def5b9aa033270b480..026847b9a6818934f726d7706ccb08c820613d42 100644 --- a/toolkit/components/passwordmgr/test/mochitest/test_prompt_http.html +++ b/toolkit/components/passwordmgr/test/mochitest/test_prompt_http.html @@ -7,7 +7,6 @@ <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script> <script type="text/javascript" src="pwmgr_common.js"></script> <script type="text/javascript" src="prompt_common.js"></script> - <script type="text/javascript" src="notification_common.js"></script> <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> </head> <body> @@ -146,40 +145,13 @@ add_task(function* test_iframe() { }; promptDone = handlePrompt(state, action); iframeLoaded = onloadPromiseFor("iframe"); + let promptShownPromise = promisePromptShown("passwordmgr-prompt-change"); iframe.src = "authenticate.sjs?user=mochiuser1&pass=mochipass1-new"; yield promptDone; yield iframeLoaded; checkEchoedAuthInfo({user: "mochiuser1", pass: "mochipass1-new"}, iframe.contentDocument); - - - var pwchanged = promiseStorageChanged(["modifyLogin"]); - - // Check for the popup notification, and change the password. - var popupNotifications = getPopupNotifications(window.top); - popup = getPopup(popupNotifications, "password-change"); - ok(popup, "got popup notification"); - clickPopupButton(popup, kChangeButton); - popup.remove(); - - yield pwchanged; - - // Housekeeping: change it back - runInParent(() => { - const { classes: Cc, interfaces: Ci, utils: Cu } = Components; - Cu.import("resource://gre/modules/Services.jsm"); - - var tmpLogin = Cc["@mozilla.org/login-manager/loginInfo;1"]. - createInstance(Ci.nsILoginInfo); - tmpLogin.init("http://mochi.test:8888", null, "mochitest", - "mochiuser1", "mochipass1-new", "", ""); - var login3A = Cc["@mozilla.org/login-manager/loginInfo;1"]. - createInstance(Ci.nsILoginInfo); - login3A.init("http://mochi.test:8888", null, "mochitest", - "mochiuser1", "mochipass1", "", ""); - - Services.logins.modifyLogin(tmpLogin, login3A); - }); + yield promptShownPromise; // Same as last test, but for a realm we haven't already authenticated // to (but have an existing saved login for, so that we'll trigger @@ -205,24 +177,15 @@ add_task(function* test_iframe() { }; promptDone = handlePrompt(state, action); iframeLoaded = onloadPromiseFor("iframe"); + promptShownPromise = promisePromptShown("passwordmgr-prompt-change"); iframe.src = "authenticate.sjs?user=mochiuser3&pass=mochipass3-new&realm=mochitest3"; yield promptDone; yield iframeLoaded; checkEchoedAuthInfo({user: "mochiuser3", pass: "mochipass3-new"}, iframe.contentDocument); + yield promptShownPromise; - pwchanged = promiseStorageChanged(["modifyLogin"]); - - // Check for the popup notification, and change the password. - popup = getPopup(popupNotifications, "password-change"); - ok(popup, "got popup notification"); - clickPopupButton(popup, kChangeButton); - popup.remove(); - - yield pwchanged; - - // Housekeeping: change it back to the original login4. Actually, - // just delete it and we'll re-add it as the next test. + // Housekeeping: Delete login4 to test the save prompt in the next test. runInParent(() => { const { classes: Cc, interfaces: Ci, utils: Cu } = Components; Cu.import("resource://gre/modules/Services.jsm"); @@ -230,7 +193,7 @@ add_task(function* test_iframe() { var tmpLogin = Cc["@mozilla.org/login-manager/loginInfo;1"]. createInstance(Ci.nsILoginInfo); tmpLogin.init("http://mochi.test:8888", null, "mochitest3", - "mochiuser3", "mochipass3-new", "", ""); + "mochiuser3", "mochipass3-old", "", ""); Services.logins.removeLogin(tmpLogin); // Clear cached auth from this subtest, and avoid leaking due to bug 459620. @@ -263,20 +226,13 @@ add_task(function* test_iframe() { promptDone = handlePrompt(state, action); iframeLoaded = onloadPromiseFor("iframe"); + promptShownPromise = promisePromptShown("passwordmgr-prompt-save"); iframe.src = "authenticate.sjs?user=mochiuser3&pass=mochipass3-old&realm=mochitest3"; yield promptDone; yield iframeLoaded; checkEchoedAuthInfo({user: "mochiuser3", pass: "mochipass3-old"}, iframe.contentDocument); - - var pwsaved = promiseStorageChanged(["addLogin"]); - - // Check for the popup notification, and change the password. - popup = getPopup(popupNotifications, "password-save"); - ok(popup, "got popup notification"); - clickPopupButton(popup, kRememberButton); - popup.remove(); - yield pwsaved; + yield promptShownPromise; }); </script> </pre> diff --git a/toolkit/components/passwordmgr/test/pwmgr_common.js b/toolkit/components/passwordmgr/test/pwmgr_common.js index 0eabac1b2b34cb7003b3960e7075bdade20aa0bf..fc06e4aec8cf6681ed1e16b9a1798a470d783a4c 100644 --- a/toolkit/components/passwordmgr/test/pwmgr_common.js +++ b/toolkit/components/passwordmgr/test/pwmgr_common.js @@ -320,6 +320,17 @@ function promiseStorageChanged(expectedChangeTypes) { }); } +function promisePromptShown(expectedTopic) { + return new Promise((resolve, reject) => { + function onPromptShown({ topic, data }) { + is(topic, expectedTopic, "Check expected prompt topic"); + chromeScript.removeMessageListener("promptShown", onPromptShown); + resolve(); + } + chromeScript.addMessageListener("promptShown", onPromptShown); + }); +} + /** * Run a function synchronously in the parent process and destroy it in the test cleanup function. * @param {Function|String} aFunctionOrURL - either a function that will be stringified and run @@ -372,6 +383,15 @@ if (this.addMessageListener) { } Services.obs.addObserver(onStorageChanged, "passwordmgr-storage-changed", false); + function onPrompt(subject, topic, data) { + sendAsyncMessage("promptShown", { + topic, + data, + }); + } + Services.obs.addObserver(onPrompt, "passwordmgr-prompt-change", false); + Services.obs.addObserver(onPrompt, "passwordmgr-prompt-save", false); + addMessageListener("setupParent", ({selfFilling = false} = {selfFilling: false}) => { // Force LoginManagerParent to init for the tests since it's normally delayed // by apps such as on Android. @@ -432,6 +452,18 @@ if (this.addMessageListener) { if (LoginManagerParent._recipeManager) { LoginManagerParent._recipeManager.reset(); } + + // Cleanup PopupNotifications (if on a relevant platform) + let chromeWin = Services.wm.getMostRecentWindow("navigator:browser"); + if (chromeWin && chromeWin.PopupNotifications) { + let notes = chromeWin.PopupNotifications._currentNotifications; + if (notes.length > 0) { + dump("Removing " + notes.length + " popup notifications.\n"); + } + for (let note of notes) { + note.remove(); + } + } }); }); diff --git a/toolkit/components/telemetry/Histograms.json b/toolkit/components/telemetry/Histograms.json index 6fd6048c6934f1c115c5dbcc3d285c262a620443..9dc410c046332f9ca1eeb5760959436280d91198 100644 --- a/toolkit/components/telemetry/Histograms.json +++ b/toolkit/components/telemetry/Histograms.json @@ -60,38 +60,43 @@ "alert_emails": ["benjamin@smedbergs.us"] }, "APPLICATION_REPUTATION_SHOULD_BLOCK": { + "alert_emails": ["safebrowsing-telemetry@mozilla.org"], "expires_in_version": "never", "kind": "boolean", - "description": "Application reputation verdict (shouldBlock=false is OK)" + "description": "Overall (local or remote) application reputation verdict (shouldBlock=false is OK)." }, "APPLICATION_REPUTATION_LOCAL": { + "alert_emails": ["safebrowsing-telemetry@mozilla.org"], "expires_in_version": "never", "kind": "enumerated", "n_values": 3, "description": "Application reputation local results (0=ALLOW, 1=BLOCK, 2=NONE)" }, "APPLICATION_REPUTATION_SERVER": { + "alert_emails": ["safebrowsing-telemetry@mozilla.org"], "expires_in_version": "never", "kind": "enumerated", "n_values": 3, - "description": "Application reputation remote status (0=OK, 1=FAIL, 2=INVALID)" + "description": "Status of the application reputation remote lookup (0=OK, 1=failed, 2=invalid protobuf response)" }, "APPLICATION_REPUTATION_SERVER_VERDICT": { - "expires_in_version": "52", + "alert_emails": ["safebrowsing-telemetry@mozilla.org"], + "expires_in_version": "56", "releaseChannelCollection": "opt-out", "bug_numbers": [1272788], "kind": "enumerated", "n_values": 8, - "description": "Application reputation remote response (0=SAFE, 1=DANGEROUS, 2=UNCOMMON, 3=POTENTIALLY_UNWANTED, 4=DANGEROUS_HOST)" + "description": "Application reputation remote verdict (0=SAFE, 1=DANGEROUS, 2=UNCOMMON, 3=POTENTIALLY_UNWANTED, 4=DANGEROUS_HOST, 5=UNKNOWN)" }, "APPLICATION_REPUTATION_COUNT": { + "alert_emails": ["safebrowsing-telemetry@mozilla.org"], "expires_in_version": "never", "kind": "boolean", "description": "Application reputation query count (both local and remote)" }, "APPLICATION_REPUTATION_REMOTE_LOOKUP_TIMEOUT": { - "alert_emails": ["gcp@mozilla.com", "francois@mozilla.com"], - "expires_in_version": "55", + "alert_emails": ["safebrowsing-telemetry@mozilla.org"], + "expires_in_version": "56", "kind": "boolean", "bug_numbers": [1172689], "description": "Recorded when application reputation remote lookup is performed, `true` is recorded if the lookup times out." @@ -3621,6 +3626,7 @@ "description": "Time spent checking for and notifying listeners that the user is idle (ms)" }, "URLCLASSIFIER_LOOKUP_TIME": { + "alert_emails": ["safebrowsing-telemetry@mozilla.org"], "expires_in_version": "never", "kind": "exponential", "high": 500, @@ -3628,6 +3634,7 @@ "description": "Time spent per dbservice lookup (ms)" }, "URLCLASSIFIER_CL_CHECK_TIME": { + "alert_emails": ["safebrowsing-telemetry@mozilla.org"], "expires_in_version": "never", "kind": "exponential", "high": 500, @@ -3635,6 +3642,7 @@ "description": "Time spent per classifier lookup (ms)" }, "URLCLASSIFIER_CL_UPDATE_TIME": { + "alert_emails": ["safebrowsing-telemetry@mozilla.org"], "expires_in_version": "never", "kind": "exponential", "low": 20, @@ -3643,6 +3651,7 @@ "description": "Time spent per classifier update (ms)" }, "URLCLASSIFIER_PS_FILELOAD_TIME": { + "alert_emails": ["safebrowsing-telemetry@mozilla.org"], "expires_in_version": "never", "kind": "exponential", "high": 1000, @@ -3650,13 +3659,15 @@ "description": "Time spent loading PrefixSet from file (ms)" }, "URLCLASSIFIER_PS_FALLOCATE_TIME": { - "expires_in_version": "default", + "alert_emails": ["safebrowsing-telemetry@mozilla.org"], + "expires_in_version": "never", "kind": "exponential", "high": 1000, "n_buckets": 10, "description": "Time spent fallocating PrefixSet (ms)" }, "URLCLASSIFIER_PS_CONSTRUCT_TIME": { + "alert_emails": ["safebrowsing-telemetry@mozilla.org"], "expires_in_version": "never", "kind": "exponential", "high": 5000, @@ -3664,6 +3675,7 @@ "description": "Time spent constructing PrefixSet from DB (ms)" }, "URLCLASSIFIER_LC_PREFIXES": { + "alert_emails": ["safebrowsing-telemetry@mozilla.org"], "expires_in_version": "never", "kind": "linear", "high": 1500000, @@ -3671,19 +3683,15 @@ "description": "Size of the prefix cache in entries" }, "URLCLASSIFIER_LC_COMPLETIONS": { + "alert_emails": ["safebrowsing-telemetry@mozilla.org"], "expires_in_version": "never", "kind": "exponential", "high": 200, "n_buckets": 10, "description": "Size of the completion cache in entries" }, - "URLCLASSIFIER_PS_FAILURE": { - "expires_in_version": "default", - "kind": "boolean", - "description": "Did UrlClassifier fail to construct the PrefixSet?" - }, "URLCLASSIFIER_UPDATE_REMOTE_STATUS": { - "alert_emails": ["gcp@mozilla.com", "francois@mozilla.com"], + "alert_emails": ["safebrowsing-telemetry@mozilla.org"], "expires_in_version": "never", "kind": "enumerated", "n_values": 16, @@ -3691,7 +3699,7 @@ "description": "Server HTTP status code from SafeBrowsing database updates. (0=1xx, 1=200, 2=2xx, 3=204, 4=3xx, 5=400, 6=4xx, 7=403, 8=404, 9=408, 10=413, 11=5xx, 12=502|504|511, 13=503, 14=505, 15=Other)" }, "URLCLASSIFIER_COMPLETE_REMOTE_STATUS": { - "alert_emails": ["gcp@mozilla.com", "francois@mozilla.com"], + "alert_emails": ["safebrowsing-telemetry@mozilla.org"], "expires_in_version": "never", "kind": "enumerated", "n_values": 16, @@ -3699,8 +3707,8 @@ "description": "Server HTTP status code from remote SafeBrowsing gethash lookups. (0=1xx, 1=200, 2=2xx, 3=204, 4=3xx, 5=400, 6=4xx, 7=403, 8=404, 9=408, 10=413, 11=5xx, 12=502|504|511, 13=503, 14=505, 15=Other)" }, "URLCLASSIFIER_COMPLETE_TIMEOUT": { - "alert_emails": ["gcp@mozilla.com", "francois@mozilla.com"], - "expires_in_version": "52", + "alert_emails": ["safebrowsing-telemetry@mozilla.org"], + "expires_in_version": "56", "kind": "boolean", "bug_numbers": [1172688], "description": "This metric is recorded every time a gethash lookup is performed, `true` is recorded if the lookup times out." @@ -5536,6 +5544,14 @@ "kind": "count", "keyed": true, "description": "a testing histogram; not meant to be touched" + }, + "TELEMETRY_TEST_KEYED_BOOLEAN": { + "alert_emails": ["telemetry-client-dev@mozilla.com"], + "expires_in_version": "never", + "kind": "boolean", + "keyed": true, + "bug_numbers": [1299144], + "description": "a testing histogram; not meant to be touched" }, "TELEMETRY_TEST_RELEASE_OPTOUT": { "expires_in_version": "never", diff --git a/toolkit/components/telemetry/Telemetry.cpp b/toolkit/components/telemetry/Telemetry.cpp index f9854c70cd21020e6d0e24e20696d0a88179e03b..931574f6135d74bd05b102faf61a68d65b9e5d85 100644 --- a/toolkit/components/telemetry/Telemetry.cpp +++ b/toolkit/components/telemetry/Telemetry.cpp @@ -1030,17 +1030,6 @@ TelemetryImpl::InitMemoryReporter() { RegisterWeakMemoryReporter(this); } -NS_IMETHODIMP -TelemetryImpl::NewKeyedHistogram(const nsACString &name, const nsACString &expiration, uint32_t histogramType, - uint32_t min, uint32_t max, uint32_t bucketCount, JSContext *cx, - uint8_t optArgCount, JS::MutableHandle<JS::Value> ret) -{ - return TelemetryHistogram::NewKeyedHistogram(name, expiration, histogramType, - min, max, bucketCount, - cx, optArgCount, ret); -} - - bool TelemetryImpl::ReflectSQL(const SlowSQLEntryType *entry, const Stat *stat, diff --git a/toolkit/components/telemetry/TelemetryHistogram.cpp b/toolkit/components/telemetry/TelemetryHistogram.cpp index c8711bee124357e3a0dfba0ca7eab8eefb726ef7..1aae6bb539f7b285cd95354fe6ebc6533903543d 100644 --- a/toolkit/components/telemetry/TelemetryHistogram.cpp +++ b/toolkit/components/telemetry/TelemetryHistogram.cpp @@ -231,12 +231,6 @@ internal_IsHistogramEnumId(mozilla::Telemetry::ID aID) return aID < mozilla::Telemetry::HistogramCount; } -bool -internal_IsValidHistogramName(const nsACString& name) -{ - return !FindInReadable(NS_LITERAL_CSTRING(KEYED_HISTOGRAM_NAME_SEPARATOR), name); -} - // Note: this is completely unrelated to mozilla::IsEmpty. bool internal_IsEmpty(const Histogram *h) @@ -2012,42 +2006,6 @@ TelemetryHistogram::GetHistogramName(mozilla::Telemetry::ID id) return h.id(); } -nsresult -TelemetryHistogram::NewKeyedHistogram(const nsACString &name, - const nsACString &expiration, - uint32_t histogramType, - uint32_t min, uint32_t max, - uint32_t bucketCount, JSContext *cx, - uint8_t optArgCount, - JS::MutableHandle<JS::Value> ret) -{ - KeyedHistogram* keyed = nullptr; - { - StaticMutexAutoLock locker(gTelemetryHistogramMutex); - if (!internal_IsValidHistogramName(name)) { - return NS_ERROR_INVALID_ARG; - } - - nsresult rv - = internal_CheckHistogramArguments(histogramType, min, max, - bucketCount, optArgCount == 3); - if (NS_FAILED(rv)) { - return rv; - } - - keyed = new KeyedHistogram(name, expiration, histogramType, - min, max, bucketCount, - nsITelemetry::DATASET_RELEASE_CHANNEL_OPTIN); - if (MOZ_UNLIKELY(!gKeyedHistograms.Put(name, keyed, mozilla::fallible))) { - delete keyed; - return NS_ERROR_OUT_OF_MEMORY; - } - } - - // Runs without protection from |gTelemetryHistogramMutex| - return internal_WrapAndReturnKeyedHistogram(keyed, cx, ret); -} - nsresult TelemetryHistogram::HistogramFrom(const nsACString &name, const nsACString &existing_name, diff --git a/toolkit/components/telemetry/TelemetryHistogram.h b/toolkit/components/telemetry/TelemetryHistogram.h index 4bb83dbf5f309519fa66812c752f8547786d659d..2995ae4c0208d4c8fe9b02e20ea28184be677d7e 100644 --- a/toolkit/components/telemetry/TelemetryHistogram.h +++ b/toolkit/components/telemetry/TelemetryHistogram.h @@ -56,12 +56,6 @@ GetKeyedHistogramById(const nsACString &name, JSContext *cx, const char* GetHistogramName(mozilla::Telemetry::ID id); -nsresult -NewKeyedHistogram(const nsACString &name, const nsACString &expiration, - uint32_t histogramType, uint32_t min, uint32_t max, - uint32_t bucketCount, JSContext *cx, - uint8_t optArgCount, JS::MutableHandle<JS::Value> ret); - nsresult HistogramFrom(const nsACString &name, const nsACString &existing_name, JSContext *cx, JS::MutableHandle<JS::Value> ret); diff --git a/toolkit/components/telemetry/docs/data/sync-ping.rst b/toolkit/components/telemetry/docs/data/sync-ping.rst index 77af96f6dd4304ac4946ed2c4389a76e543eb521..a2066c0269b76dd410e5a21b3d6c8290b3eefcb3 100644 --- a/toolkit/components/telemetry/docs/data/sync-ping.rst +++ b/toolkit/components/telemetry/docs/data/sync-ping.rst @@ -25,6 +25,7 @@ Structure: when: <integer milliseconds since epoch>, took: <integer duration in milliseconds>, uid: <string>, // Hashed FxA unique ID, or string of 32 zeros. + deviceID: <string>, // Hashed FxA Device ID, hex string of 64 characters, not included if the user is not logged in. didLogin: <bool>, // Optional, is this the first sync after login? Excluded if we don't know. why: <string>, // Optional, why the sync occured, excluded if we don't know. diff --git a/toolkit/components/telemetry/histogram-whitelists.json b/toolkit/components/telemetry/histogram-whitelists.json index ea916e400af31ea371865e187ee02eb275dd21c8..2ca545dfb0ead99de3391d0b2221d3eca7264827 100644 --- a/toolkit/components/telemetry/histogram-whitelists.json +++ b/toolkit/components/telemetry/histogram-whitelists.json @@ -6,11 +6,6 @@ "A11Y_ISIMPLEDOM_USAGE_FLAG", "A11Y_UPDATE_TIME", "ADDON_SHIM_USAGE", - "APPLICATION_REPUTATION_COUNT", - "APPLICATION_REPUTATION_LOCAL", - "APPLICATION_REPUTATION_SERVER", - "APPLICATION_REPUTATION_SERVER_VERDICT", - "APPLICATION_REPUTATION_SHOULD_BLOCK", "AUDIOSTREAM_FIRST_OPEN_MS", "AUDIOSTREAM_LATER_OPEN_MS", "AUTO_REJECTED_TRANSLATION_OFFERS", @@ -749,15 +744,6 @@ "TRANSLATED_PAGES_BY_LANGUAGE", "TRANSLATION_OPPORTUNITIES", "TRANSLATION_OPPORTUNITIES_BY_LANGUAGE", - "URLCLASSIFIER_CL_CHECK_TIME", - "URLCLASSIFIER_CL_UPDATE_TIME", - "URLCLASSIFIER_LC_COMPLETIONS", - "URLCLASSIFIER_LC_PREFIXES", - "URLCLASSIFIER_LOOKUP_TIME", - "URLCLASSIFIER_PS_CONSTRUCT_TIME", - "URLCLASSIFIER_PS_FAILURE", - "URLCLASSIFIER_PS_FALLOCATE_TIME", - "URLCLASSIFIER_PS_FILELOAD_TIME", "VIDEO_CANPLAYTYPE_H264_CONSTRAINT_SET_FLAG", "VIDEO_CANPLAYTYPE_H264_LEVEL", "VIDEO_CANPLAYTYPE_H264_PROFILE", @@ -1812,7 +1798,6 @@ "URLCLASSIFIER_LC_PREFIXES", "URLCLASSIFIER_LOOKUP_TIME", "URLCLASSIFIER_PS_CONSTRUCT_TIME", - "URLCLASSIFIER_PS_FAILURE", "URLCLASSIFIER_PS_FALLOCATE_TIME", "URLCLASSIFIER_PS_FILELOAD_TIME", "VIDEO_CANPLAYTYPE_H264_CONSTRAINT_SET_FLAG", diff --git a/toolkit/components/telemetry/nsITelemetry.idl b/toolkit/components/telemetry/nsITelemetry.idl index 23e5da6abfd167d91c1a028e65e010bcece5ad08..c65e99796b41bd33ca62e49ef2acd9e79041131e 100644 --- a/toolkit/components/telemetry/nsITelemetry.idl +++ b/toolkit/components/telemetry/nsITelemetry.idl @@ -214,30 +214,6 @@ interface nsITelemetry : nsISupports [implicit_jscontext] readonly attribute jsval keyedHistogramSnapshots; - /** - * Create and return a keyed histogram. Parameters: - * - * @param name Unique histogram name - * @param expiration Expiration version - * @param type - HISTOGRAM_EXPONENTIAL, HISTOGRAM_LINEAR, HISTOGRAM_BOOLEAN, HISTOGRAM_FLAG or HISTOGRAM_COUNT - * @param min - Minimal bucket size - * @param max - Maximum bucket size - * @param bucket_count - number of buckets in the histogram. - * The returned object has the following functions: - * add(string key, [optional] int) - Add an int value to the histogram for that key. If no histogram for that key exists yet, it is created. - * snapshot([optional] string key) - If key is provided, returns a snapshot for the histogram with that key or null. If key is not provided, returns the snapshots of all the registered keys in the form {key1: snapshot1, key2: snapshot2, ...}. - * keys() - Returns an array with the string keys of the currently registered histograms - * clear() - Clears the registered histograms from this. - * dataset() - identifies what dataset this is in: DATASET_RELEASE_CHANNEL_OPTOUT or ...OPTIN - */ - [implicit_jscontext, optional_argc] - jsval newKeyedHistogram(in ACString name, - in ACString expiration, - in unsigned long histogram_type, - [optional] in uint32_t min, - [optional] in uint32_t max, - [optional] in uint32_t bucket_count); - /** * Returns an array whose values are the names of histograms defined * in Histograms.json. @@ -248,10 +224,15 @@ interface nsITelemetry : nsISupports [retval, array, size_is(count)] out string histograms); /** - * Same as newKeyedHistogram above, but for histograms registered in TelemetryHistograms.h. + * Create and return a histogram registered in TelemetryHistograms.h. * * @param id - unique identifier from TelemetryHistograms.h - * The returned object has the same functions as a histogram returned from newKeyedHistogram. + * The returned object has the following functions: + * add(string key, [optional] int) - Add an int value to the histogram for that key. If no histogram for that key exists yet, it is created. + * snapshot([optional] string key) - If key is provided, returns a snapshot for the histogram with that key or null. If key is not provided, returns the snapshots of all the registered keys in the form {key1: snapshot1, key2: snapshot2, ...}. + * keys() - Returns an array with the string keys of the currently registered histograms + * clear() - Clears the registered histograms from this. + * dataset() - identifies what dataset this is in: DATASET_RELEASE_CHANNEL_OPTOUT or ...OPTIN */ [implicit_jscontext] jsval getKeyedHistogramById(in ACString id); diff --git a/toolkit/components/telemetry/tests/unit/test_nsITelemetry.js b/toolkit/components/telemetry/tests/unit/test_nsITelemetry.js index 0525517863867afcc62ee2e1b587af59065b717b..d93f0b470af9bdb662ce37f9ce408c66b1a4805e 100644 --- a/toolkit/components/telemetry/tests/unit/test_nsITelemetry.js +++ b/toolkit/components/telemetry/tests/unit/test_nsITelemetry.js @@ -461,15 +461,6 @@ add_task(function* test_keyed_histogram() { // Check that invalid names get rejected. let threw = false; - try { - Telemetry.newKeyedHistogram("test::invalid # histogram", "never", Telemetry.HISTOGRAM_BOOLEAN); - } catch (e) { - // This should throw as we reject names with the # separator - threw = true; - } - Assert.ok(threw, "newKeyedHistogram should have thrown"); - - threw = false; try { Telemetry.getKeyedHistogramById("test::unknown histogram", "never", Telemetry.HISTOGRAM_BOOLEAN); } catch (e) { @@ -480,7 +471,7 @@ add_task(function* test_keyed_histogram() { }); add_task(function* test_keyed_boolean_histogram() { - const KEYED_ID = "test::keyed::boolean"; + const KEYED_ID = "TELEMETRY_TEST_KEYED_BOOLEAN"; let KEYS = numberRange(0, 2).map(i => "key" + (i + 1)); KEYS.push("漢語"); let histogramBase = { @@ -495,7 +486,7 @@ add_task(function* test_keyed_boolean_histogram() { let testKeys = []; let testSnapShot = {}; - let h = Telemetry.newKeyedHistogram(KEYED_ID, "never", Telemetry.HISTOGRAM_BOOLEAN); + let h = Telemetry.getKeyedHistogramById(KEYED_ID); for (let i=0; i<2; ++i) { let key = KEYS[i]; h.add(key, true); @@ -528,7 +519,7 @@ add_task(function* test_keyed_boolean_histogram() { }); add_task(function* test_keyed_count_histogram() { - const KEYED_ID = "test::keyed::count"; + const KEYED_ID = "TELEMETRY_TEST_KEYED_COUNT"; const KEYS = numberRange(0, 5).map(i => "key" + (i + 1)); let histogramBase = { "min": 1, @@ -542,7 +533,7 @@ add_task(function* test_keyed_count_histogram() { let testKeys = []; let testSnapShot = {}; - let h = Telemetry.newKeyedHistogram(KEYED_ID, "never", Telemetry.HISTOGRAM_COUNT); + let h = Telemetry.getKeyedHistogramById(KEYED_ID); for (let i=0; i<4; ++i) { let key = KEYS[i]; let value = i*2 + 1; @@ -583,8 +574,8 @@ add_task(function* test_keyed_count_histogram() { }); add_task(function* test_keyed_flag_histogram() { - const KEYED_ID = "test::keyed::flag"; - let h = Telemetry.newKeyedHistogram(KEYED_ID, "never", Telemetry.HISTOGRAM_FLAG); + const KEYED_ID = "TELEMETRY_TEST_KEYED_FLAG"; + let h = Telemetry.getKeyedHistogramById(KEYED_ID); const KEY = "default"; h.add(KEY, true); @@ -634,12 +625,6 @@ add_task(function* test_keyed_histogram_recording() { Assert.equal(h.snapshot(TEST_KEY).sum, 0, "The keyed histograms should not record any data."); - // Runtime created histograms should not be recorded. - h = Telemetry.newKeyedHistogram("test::runtime_keyed_boolean", "never", Telemetry.HISTOGRAM_BOOLEAN); - h.add(TEST_KEY, 1); - Assert.equal(h.snapshot(TEST_KEY).sum, 0, - "The keyed histogram should not record any data."); - // Check that extended histograms are recorded when required. Telemetry.canRecordExtended = true; diff --git a/toolkit/components/thumbnails/test/browser_thumbnails_bg_no_cookies_stored.js b/toolkit/components/thumbnails/test/browser_thumbnails_bg_no_cookies_stored.js index be3ee5ab4dc30157f49fa25385ee7d3a07e2aec5..16aa382d7140eebdca447a6574906f61f7411ed2 100644 --- a/toolkit/components/thumbnails/test/browser_thumbnails_bg_no_cookies_stored.js +++ b/toolkit/components/thumbnails/test/browser_thumbnails_bg_no_cookies_stored.js @@ -15,14 +15,15 @@ function* runTests() { removeThumbnail(url); // now load it up in a browser - it should *not* be red, otherwise the // cookie above was saved. - let tab = gBrowser.loadOneTab(url, { inBackground: false }); + let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, url); let browser = tab.linkedBrowser; - yield whenLoaded(browser); // The root element of the page shouldn't be red. - let redStr = "rgb(255, 0, 0)"; - isnot(browser.contentDocument.documentElement.style.backgroundColor, - redStr, - "The page shouldn't be red."); + yield ContentTask.spawn(browser, null, function() { + Assert.notEqual(content.document.documentElement.style.backgroundColor, + "rgb(255, 0, 0)", + "The page shouldn't be red."); + }); + gBrowser.removeTab(tab); } diff --git a/toolkit/components/url-classifier/LookupCache.cpp b/toolkit/components/url-classifier/LookupCache.cpp index d03f1ce052d5dd87d97b417cb647b5265c554328..98e334db1e7dfe8a47a8f2e0fb9fd41acaf4727b 100644 --- a/toolkit/components/url-classifier/LookupCache.cpp +++ b/toolkit/components/url-classifier/LookupCache.cpp @@ -499,9 +499,7 @@ LookupCache::ConstructPrefixSet(AddPrefixArray& aAddPrefixes) // construct new one, replace old entries nsresult rv = mPrefixSet->SetPrefixes(array.Elements(), array.Length()); - if (NS_FAILED(rv)) { - goto error_bailout; - } + NS_ENSURE_SUCCESS(rv, rv); #ifdef DEBUG uint32_t size; @@ -512,10 +510,6 @@ LookupCache::ConstructPrefixSet(AddPrefixArray& aAddPrefixes) mPrimed = true; return NS_OK; - - error_bailout: - Telemetry::Accumulate(Telemetry::URLCLASSIFIER_PS_FAILURE, 1); - return rv; } nsresult diff --git a/toolkit/components/url-classifier/content/listmanager.js b/toolkit/components/url-classifier/content/listmanager.js index 60b37442cdfd9dd5daa63bab0f03caeea13da76f..aeca28ae5b9b38785fe021154aadf16efc1262ad 100644 --- a/toolkit/components/url-classifier/content/listmanager.js +++ b/toolkit/components/url-classifier/content/listmanager.js @@ -388,7 +388,12 @@ PROT_ListManager.prototype.makeUpdateRequest_ = function(updateUrl, tableData) { } if (useProtobuf) { - let tableArray = streamerMap.tableList.split(','); + let tableArray = []; + Object.keys(streamerMap.tableNames).forEach(aTableName => { + if (streamerMap.tableNames[aTableName]) { + tableArray.push(aTableName); + } + }); // The state is a byte stream which server told us from the // last table update. The state would be used to do the partial diff --git a/toolkit/components/url-classifier/nsUrlClassifierUtils.cpp b/toolkit/components/url-classifier/nsUrlClassifierUtils.cpp index 4677b27497391cd74c0e1192fa5aef3c1c7f04ac..fa2f52523a0239776368763da378bd194ed426fd 100644 --- a/toolkit/components/url-classifier/nsUrlClassifierUtils.cpp +++ b/toolkit/components/url-classifier/nsUrlClassifierUtils.cpp @@ -218,6 +218,7 @@ static const struct { // For testing purpose. { "test-phish-proto", SOCIAL_ENGINEERING_PUBLIC}, // 2 + { "test-unwanted-proto", UNWANTED_SOFTWARE}, // 3 }; NS_IMETHODIMP diff --git a/toolkit/components/url-classifier/tests/unit/test_listmanager.js b/toolkit/components/url-classifier/tests/unit/test_listmanager.js index e7d4353aa778f398fdc1ec685949f89ab944fe4f..d6d8a56ddffed060327b4b2faa1250a4f8849ee1 100644 --- a/toolkit/components/url-classifier/tests/unit/test_listmanager.js +++ b/toolkit/components/url-classifier/tests/unit/test_listmanager.js @@ -31,13 +31,19 @@ const TEST_TABLE_DATA_LIST = [ } ]; -// This table has a different update URL (for v4). +// These tables have a different update URL (for v4). const TEST_TABLE_DATA_V4 = { tableName: "test-phish-proto", providerName: "google4", updateUrl: "http://localhost:5555/safebrowsing/update?", gethashUrl: "http://localhost:5555/safebrowsing/gethash-v4", }; +const TEST_TABLE_DATA_V4_DISABLED = { + tableName: "test-unwanted-proto", + providerName: "google4", + updateUrl: "http://localhost:5555/safebrowsing/update?", + gethashUrl: "http://localhost:5555/safebrowsing/gethash-v4", +}; const PREF_NEXTUPDATETIME = "browser.safebrowsing.provider.google.nextupdatetime"; const PREF_NEXTUPDATETIME_V4 = "browser.safebrowsing.provider.google4.nextupdatetime"; @@ -79,6 +85,12 @@ gListManager.registerTable(TEST_TABLE_DATA_V4.tableName, TEST_TABLE_DATA_V4.updateUrl, TEST_TABLE_DATA_V4.gethashUrl); +// To test Bug 1302044. +gListManager.registerTable(TEST_TABLE_DATA_V4_DISABLED.tableName, + TEST_TABLE_DATA_V4_DISABLED.providerName, + TEST_TABLE_DATA_V4_DISABLED.updateUrl, + TEST_TABLE_DATA_V4_DISABLED.gethashUrl); + const SERVER_INVOLVED_TEST_CASE_LIST = [ // - Do table0 update. // - Server would respond "a:5:32:32\n[DATA]". @@ -122,7 +134,12 @@ const SERVER_INVOLVED_TEST_CASE_LIST = [ TEST_TABLE_DATA_LIST.forEach(function(t) { gListManager.enableUpdate(t.tableName); }); + + // We register two v4 tables but only enable one of them + // to verify that the disabled tables are not updated. + // See Bug 1302044. gListManager.enableUpdate(TEST_TABLE_DATA_V4.tableName); + gListManager.disableUpdate(TEST_TABLE_DATA_V4_DISABLED.tableName); // Expected results for v2. gExpectedUpdateRequest = TEST_TABLE_DATA_LIST[0].tableName + ";a:5:s:2-12\n" + diff --git a/toolkit/components/url-classifier/tests/unit/test_threat_type_conversion.js b/toolkit/components/url-classifier/tests/unit/test_threat_type_conversion.js index c20d3563ee08b43aa54a3a5b81f5f4408204e857..f7c51b9562a77d63a0b7090d1c7fb4e419c04fb5 100644 --- a/toolkit/components/url-classifier/tests/unit/test_threat_type_conversion.js +++ b/toolkit/components/url-classifier/tests/unit/test_threat_type_conversion.js @@ -22,7 +22,7 @@ function run_test() { // Test threat type to list name conversion. equal(urlUtils.convertThreatTypeToListNames(1), "goog-malware-proto"); equal(urlUtils.convertThreatTypeToListNames(2), "googpub-phish-proto,test-phish-proto"); - equal(urlUtils.convertThreatTypeToListNames(3), "goog-unwanted-proto"); + equal(urlUtils.convertThreatTypeToListNames(3), "goog-unwanted-proto,test-unwanted-proto"); equal(urlUtils.convertThreatTypeToListNames(5), "goog-phish-proto"); try { diff --git a/toolkit/content/tests/chrome/test_notificationbox.xul b/toolkit/content/tests/chrome/test_notificationbox.xul index b1b47be16b159f3bc88ab05d0e582aca9e131910..a99d0824e8c49b0e2356b271b46c54868f836dff 100644 --- a/toolkit/content/tests/chrome/test_notificationbox.xul +++ b/toolkit/content/tests/chrome/test_notificationbox.xul @@ -48,22 +48,35 @@ function testtag_notificationbox(nb) runTimedTests(tests, -1, nb, null); } -var notification_last_event, notification_last_event_item; +var notification_last_events = []; function notification_eventCallback(event) { - notification_last_event = event; - notification_last_event_item = this; + notification_last_events.push({ actualEvent: event , item: this }); } -function testtag_notification_eventCallback(expectedEvent, ntf, testName) +/** + * For any notifications that have the notification_eventCallback on + * them, we will have recorded instances of those callbacks firing + * and stored them. This checks to see that the expected event types + * are being fired in order, and targeting the right item. + * + * @param {Array<string>} expectedEvents + * The list of event types, in order, that we expect to have been + * fired on the item. + * @param {<xul:notification>} ntf + * The notification we expect the callback to have been fired from. + * @param {string} testName + * The name of the current test, for logging. + */ +function testtag_notification_eventCallback(expectedEvents, ntf, testName) { - SimpleTest.is(notification_last_event, expectedEvent, - testName + ": event name"); - SimpleTest.is(notification_last_event_item, ntf, - testName + ": event item"); - - notification_last_event = null; - notification_last_event_item = null; + for (let i = 0; i < expectedEvents; ++i) { + let expected = expectedEvents[i]; + let { actualEvent, item } = notification_last_events[i]; + SimpleTest.is(actualEvent, expected, testName + ": event name"); + SimpleTest.is(item, ntf, testName + ": event item"); + } + notification_last_events = []; } var tests = @@ -133,7 +146,36 @@ var tests = testtag_notificationbox_State(nb, "removeNotification with callback", null, 0); - testtag_notification_eventCallback("removed", ntf, "removeNotification()"); + testtag_notification_eventCallback(["removed"], ntf, "removeNotification()"); + return ntf; + } + }, + { + test: function(nb, ntf) { + var ntf = nb.appendNotification("Notification", "note", "happy.png", + nb.PRIORITY_INFO_LOW, + testtag_notificationbox_buttons, + notification_eventCallback); + SimpleTest.is(ntf && ntf.localName == "notification", true, "append notification with callback"); + return ntf; + }, + result: function(nb, ntf) { + testtag_notificationbox_State(nb, "append with callback", ntf, 1); + return ntf; + } + }, + { + test: function(rb, ntf) { + // Dismissing the notification instead of removing it should + // fire a dismissed "event" on the callback, followed by + // a removed "event". + ntf.dismiss(); + return ntf; + }, + result: function(nb, ntf) { + testtag_notificationbox_State(nb, "called dismiss()", null, 0); + testtag_notification_eventCallback(["dismissed", "removed"], ntf, + "dismiss()"); return ntf; } }, diff --git a/toolkit/content/widgets/notification.xml b/toolkit/content/widgets/notification.xml index c3e20f9474eac60fc8cfd12968e22324f77135c4..db8c8cd6fa910907cbdba422004e72eaad043051 100644 --- a/toolkit/content/widgets/notification.xml +++ b/toolkit/content/widgets/notification.xml @@ -395,7 +395,7 @@ class="messageCloseButton close-icon tabbable" xbl:inherits="hidden=hideclose" tooltiptext="&closeNotification.tooltip;" - oncommand="document.getBindingParent(this).close();"/> + oncommand="document.getBindingParent(this).dismiss();"/> </xul:hbox> </content> <resources> @@ -430,6 +430,21 @@ </getter> </property> + <!-- This method should only be called when the user has + manually closed the notification. If you want to + programmatically close the notification, you should + call close() instead. --> + <method name="dismiss"> + <body> + <![CDATA[ + if (this.eventCallback) { + this.eventCallback("dismissed"); + } + this.close(); + ]]> + </body> + </method> + <method name="close"> <body> <![CDATA[ diff --git a/toolkit/modules/FinderHighlighter.jsm b/toolkit/modules/FinderHighlighter.jsm index 894b13cb5585598a11f639700551ffa01da49d26..6b40701ce4fc2f69401cbfcf241a8de3be81670d 100644 --- a/toolkit/modules/FinderHighlighter.jsm +++ b/toolkit/modules/FinderHighlighter.jsm @@ -78,7 +78,7 @@ const kModalStyles = { ["padding", "0 1px 2px 1px !important"], ["position", "absolute"] ], - maskRectBrightText: [ "background", "#000" ] + maskRectBrightText: [ ["background", "#000"] ] }; const kModalOutlineAnim = { "keyframes": [ @@ -444,7 +444,7 @@ FinderHighlighter.prototype = { }, /** - * Invalidates the list by clearing the map of highglighted ranges that we + * Invalidates the list by clearing the map of highlighted ranges that we * keep to build the mask for. */ clear(window = null) { @@ -460,7 +460,6 @@ FinderHighlighter.prototype = { let dict = this.getForWindow(window.top); if (dict.animation) dict.animation.finish(); - dict.currentFoundRange = null; dict.dynamicRangesSet.clear(); dict.frames.clear(); dict.modalHighlightRectsMap.clear(); @@ -476,6 +475,7 @@ FinderHighlighter.prototype = { let window = this.finder._getWindow(); let dict = this.getForWindow(window); this.clear(window); + dict.currentFoundRange = null; if (!dict.modalHighlightOutline) return; @@ -747,7 +747,8 @@ FinderHighlighter.prototype = { /** * Read and store the rectangles that encompass the entire region of a range - * for use by the drawing function of the highlighter. + * for use by the drawing function of the highlighter and store them in the + * cache. * * @param {nsIDOMRange} range Range to fetch the rectangles from * @param {Boolean} [checkIfDynamic] Whether we should check if the range @@ -785,6 +786,7 @@ FinderHighlighter.prototype = { rects.add(rect); } + // Only fetch the rect at this point, if not passed in as argument. dict = dict || this.getForWindow(window.top); dict.modalHighlightRectsMap.set(range, rects); if (checkIfDynamic && this._isInDynamicContainer(range)) @@ -960,6 +962,8 @@ FinderHighlighter.prototype = { const rectStyle = this._getStyleString(kModalStyles.maskRect, dict.brightText ? kModalStyles.maskRectBrightText : []); for (let [range, rects] of dict.modalHighlightRectsMap) { + if (this._checkOverlap(dict.currentFoundRange, range)) + continue; if (dict.updateAllRanges) rects = this._updateRangeRects(range); for (let rect of rects) { @@ -1202,6 +1206,8 @@ FinderHighlighter.prototype = { * @returns true if they intersect, false otherwise */ _checkOverlap(selectionRange, findRange) { + if (!selectionRange || !findRange) + return false; // The ranges overlap if one of the following is true: // 1) At least one of the endpoints of the deleted selection // is in the find selection diff --git a/toolkit/modules/tests/browser/browser_FinderHighlighter.js b/toolkit/modules/tests/browser/browser_FinderHighlighter.js index e51603cc5f7a494a18d38c7e65ae69cdf88319c5..1164f9d6b65c81dc1d35d1733482ce64116bd5ef 100644 --- a/toolkit/modules/tests/browser/browser_FinderHighlighter.js +++ b/toolkit/modules/tests/browser/browser_FinderHighlighter.js @@ -142,22 +142,22 @@ add_task(function* setup() { add_task(function* testModalResults() { let tests = new Map([ ["Roland", { - rectCount: 2, + rectCount: 1, insertCalls: [2, 4], removeCalls: [1, 2] }], ["ro", { - rectCount: 41, + rectCount: 40, insertCalls: [1, 4], removeCalls: [1, 3] }], ["new", { - rectCount: 2, + rectCount: 1, insertCalls: [1, 4], removeCalls: [1, 3] }], ["o", { - rectCount: 492, + rectCount: 491, insertCalls: [1, 5], removeCalls: [1, 4] }] @@ -192,7 +192,7 @@ add_task(function* testModalSwitching() { let word = "Roland"; let expectedResult = { - rectCount: 2, + rectCount: 1, insertCalls: [2, 4], removeCalls: [1, 2] }; @@ -228,7 +228,7 @@ add_task(function* testDarkPageDetection() { let word = "Roland"; let expectedResult = { - rectCount: 2, + rectCount: 1, insertCalls: [2, 4], removeCalls: [1, 2] }; @@ -248,7 +248,7 @@ add_task(function* testDarkPageDetection() { let word = "Roland"; let expectedResult = { - rectCount: 2, + rectCount: 1, insertCalls: [2, 4], removeCalls: [1, 2] }; @@ -285,7 +285,7 @@ add_task(function* testHighlightAllToggle() { let word = "Roland"; let expectedResult = { - rectCount: 2, + rectCount: 1, insertCalls: [2, 4], removeCalls: [1, 2] }; diff --git a/toolkit/mozapps/extensions/internal/AddonTestUtils.jsm b/toolkit/mozapps/extensions/internal/AddonTestUtils.jsm index f2b6980f0842c0dfceb283eb4699dcd7dce21ae5..bc5d362ba9a21889819f8f2fcac5be599d6c384d 100644 --- a/toolkit/mozapps/extensions/internal/AddonTestUtils.jsm +++ b/toolkit/mozapps/extensions/internal/AddonTestUtils.jsm @@ -641,7 +641,8 @@ var AddonTestUtils = { let props = ["id", "version", "type", "internalName", "updateURL", "updateKey", "optionsURL", "optionsType", "aboutURL", "iconURL", "icon64URL", - "skinnable", "bootstrap", "unpack", "strictCompatibility", "multiprocessCompatible"]; + "skinnable", "bootstrap", "unpack", "strictCompatibility", + "multiprocessCompatible", "hasEmbeddedWebExtension"]; rdf += this._writeProps(data, props); rdf += this._writeLocaleStrings(data); diff --git a/toolkit/mozapps/extensions/internal/XPIProvider.jsm b/toolkit/mozapps/extensions/internal/XPIProvider.jsm index ffe5a2222c25a96f10a67de84cac9ce96f2a382f..6034751e7943ed8b4f13242fc9eb73890f2ac02a 100644 --- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm +++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm @@ -59,6 +59,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "AppConstants", "resource://gre/modules/AppConstants.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "isAddonPartOfE10SRollout", "resource://gre/modules/addons/E10SAddonsRollout.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "LegacyExtensionsUtils", + "resource://gre/modules/LegacyExtensionsUtils.jsm"); XPCOMUtils.defineLazyServiceGetter(this, "Blocklist", "@mozilla.org/extensions/blocklist;1", @@ -177,7 +179,7 @@ const TOOLKIT_ID = "toolkit@mozilla.org"; const XPI_SIGNATURE_CHECK_PERIOD = 24 * 60 * 60; -XPCOMUtils.defineConstant(this, "DB_SCHEMA", 17); +XPCOMUtils.defineConstant(this, "DB_SCHEMA", 18); const NOTIFICATION_TOOLBOXPROCESS_LOADED = "ToolboxProcessLoaded"; @@ -803,6 +805,7 @@ function createAddonDetails(id, aAddon) { multiprocessCompatible: aAddon.multiprocessCompatible, runInSafeMode: aAddon.runInSafeMode, dependencies: aAddon.dependencies, + hasEmbeddedWebExtension: aAddon.hasEmbeddedWebExtension, }; } @@ -1152,6 +1155,7 @@ function loadManifestFromRDF(aUri, aStream) { if (addon.type == "extension") { addon.bootstrap = getRDFProperty(ds, root, "bootstrap") == "true"; addon.multiprocessCompatible = getRDFProperty(ds, root, "multiprocessCompatible") == "true"; + addon.hasEmbeddedWebExtension = getRDFProperty(ds, root, "hasEmbeddedWebExtension") == "true"; if (addon.optionsType && addon.optionsType != AddonManager.OPTIONS_TYPE_DIALOG && addon.optionsType != AddonManager.OPTIONS_TYPE_INLINE && @@ -1300,6 +1304,21 @@ function defineSyncGUID(aAddon) { }); } +// Generate a unique ID based on the path to this temporary add-on location. +function generateTemporaryInstallID(aFile) { + const hasher = Cc["@mozilla.org/security/hash;1"] + .createInstance(Ci.nsICryptoHash); + hasher.init(hasher.SHA1); + const data = new TextEncoder().encode(aFile.path); + // Make it so this ID cannot be guessed. + const sess = TEMP_INSTALL_ID_GEN_SESSION; + hasher.update(sess, sess.length); + hasher.update(data, data.length); + let id = `${getHashStringForCrypto(hasher)}@temporary-addon`; + logger.info(`Generated temp id ${id} (${sess.join("")}) for ${aFile.path}`); + return id; +} + /** * Loads an AddonInternal object from an add-on extracted in a directory. * @@ -1375,19 +1394,7 @@ var loadManifestFromDir = Task.async(function*(aDir, aInstallLocation) { addon = yield loadManifestFromWebManifest(uri); if (!addon.id) { if (aInstallLocation == TemporaryInstallLocation) { - // Generate a unique ID based on the directory path of - // this temporary add-on location. - const hasher = Cc["@mozilla.org/security/hash;1"] - .createInstance(Ci.nsICryptoHash); - hasher.init(hasher.SHA1); - const data = new TextEncoder().encode(aDir.path); - // Make it so this ID cannot be guessed. - const sess = TEMP_INSTALL_ID_GEN_SESSION; - hasher.update(sess, sess.length); - hasher.update(data, data.length); - addon.id = `${getHashStringForCrypto(hasher)}@temporary-addon`; - logger.info( - `Generated temp id ${addon.id} (${sess.join("")}) for ${aDir.path}`); + addon.id = generateTemporaryInstallID(aDir); } else { addon.id = aDir.leafName; } @@ -1475,10 +1482,15 @@ var loadManifestFromZipReader = Task.async(function*(aZipReader, aInstallLocatio let {signedState, cert} = yield verifyZipSignedState(aZipReader.file, addon); addon.signedState = signedState; - if (isWebExtension && !addon.id && cert) { - addon.id = cert.commonName; - if (!gIDTest.test(addon.id)) { - throw new Error(`Webextension is signed with an invalid id (${addon.id})`); + if (isWebExtension && !addon.id) { + if (cert) { + addon.id = cert.commonName; + if (!gIDTest.test(addon.id)) { + throw new Error(`Webextension is signed with an invalid id (${addon.id})`); + } + } + if (!addon.id && aInstallLocation == TemporaryInstallLocation) { + addon.id = generateTemporaryInstallID(aZipReader.file); } } addon.appDisabled = !isUsableAddon(addon); @@ -4819,8 +4831,7 @@ this.XPIProvider = { */ callBootstrapMethod: function(aAddon, aFile, aMethod, aReason, aExtraParams) { if (!aAddon.id || !aAddon.version || !aAddon.type) { - logger.error(new Error("aAddon must include an id, version, and type")); - return; + throw new Error("aAddon must include an id, version, and type"); } // Only run in safe mode if allowed to @@ -4892,6 +4903,17 @@ this.XPIProvider = { } } + if (aAddon.hasEmbeddedWebExtension) { + if (aMethod == "startup") { + const webExtension = LegacyExtensionsUtils.getEmbeddedExtensionFor(params); + params.webExtension = { + startup: () => webExtension.startup(), + }; + } else if (aMethod == "shutdown") { + LegacyExtensionsUtils.getEmbeddedExtensionFor(params).shutdown(); + } + } + logger.debug("Calling bootstrap method " + aMethod + " on " + aAddon.id + " version " + aAddon.version); try { @@ -6937,6 +6959,7 @@ AddonInternal.prototype = { * add-ons is not installed and enabled. */ dependencies: Object.freeze([]), + hasEmbeddedWebExtension: false, get selectedLocale() { if (this._selectedLocale) @@ -7254,6 +7277,10 @@ AddonWrapper.prototype = { return addonFor(this).seen; }, + get hasEmbeddedWebExtension() { + return addonFor(this).hasEmbeddedWebExtension; + }, + markAsSeen: function() { addonFor(this).seen = true; XPIDatabase.saveChanges(); diff --git a/toolkit/mozapps/extensions/internal/XPIProviderUtils.js b/toolkit/mozapps/extensions/internal/XPIProviderUtils.js index 7ffde0c87a561d5ccc862457bfd9cb33e87465d5..fb307835acb62c6dd0be12fd11043a615db35239 100644 --- a/toolkit/mozapps/extensions/internal/XPIProviderUtils.js +++ b/toolkit/mozapps/extensions/internal/XPIProviderUtils.js @@ -87,7 +87,7 @@ const PROP_JSON_FIELDS = ["id", "syncGUID", "location", "version", "type", "softDisabled", "foreignInstall", "hasBinaryComponents", "strictCompatibility", "locales", "targetApplications", "targetPlatforms", "multiprocessCompatible", "signedState", - "seen", "dependencies"]; + "seen", "dependencies", "hasEmbeddedWebExtension"]; // Properties that should be migrated where possible from an old database. These // shouldn't include properties that can be read directly from install.rdf files @@ -2156,6 +2156,7 @@ this.XPIDatabaseReconcile = { multiprocessCompatible: currentAddon.multiprocessCompatible, runInSafeMode: canRunInSafeMode(currentAddon), dependencies: currentAddon.dependencies, + hasEmbeddedWebExtension: currentAddon.hasEmbeddedWebExtension, }; } diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/BootstrapMonitor.jsm b/toolkit/mozapps/extensions/test/xpcshell/data/BootstrapMonitor.jsm index dfc0eaf3207a19332429eba8a3b2bf680aee8f31..7c1e4aa9d6cc70aa478c2782803b01aecab7bc8a 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/data/BootstrapMonitor.jsm +++ b/toolkit/mozapps/extensions/test/xpcshell/data/BootstrapMonitor.jsm @@ -12,7 +12,9 @@ function notify(event, originalMethod, data, reason) { reason }; - Services.obs.notifyObservers(null, "bootstrapmonitor-event", JSON.stringify(info)); + let subject = {wrappedJSObject: {data}}; + + Services.obs.notifyObservers(subject, "bootstrapmonitor-event", JSON.stringify(info)); // If the bootstrap scope already declares a method call it if (originalMethod) diff --git a/toolkit/mozapps/extensions/test/xpcshell/head_addons.js b/toolkit/mozapps/extensions/test/xpcshell/head_addons.js index 8e8b066e1a9b6af37d207b1282f1966c9ad7eb92..e1a2f3f6ca3f5e7fae2604bccd71e4b1ecf2788c 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/head_addons.js +++ b/toolkit/mozapps/extensions/test/xpcshell/head_addons.js @@ -236,6 +236,17 @@ this.BootstrapMonitor = { let id = info.data.id; let installPath = new FileUtils.File(info.data.installPath); + if (subject && subject.wrappedJSObject) { + // NOTE: in some of the new tests, we need to received the real objects instead of + // their JSON representations, but most of the current tests expect intallPath + // and resourceURI to have been converted to strings. + const {installPath, resourceURI} = info.data; + info.data = Object.assign({}, subject.wrappedJSObject.data, { + installPath, + resourceURI, + }); + } + // If this is the install event the add-ons shouldn't already be installed if (info.event == "install") { this.checkAddonNotInstalled(id); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_webextension_embedded.js b/toolkit/mozapps/extensions/test/xpcshell/test_webextension_embedded.js new file mode 100644 index 0000000000000000000000000000000000000000..3b7198d4751e00b23c22dd449560c56aff54e5ba --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/test_webextension_embedded.js @@ -0,0 +1,235 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +BootstrapMonitor.init(); + +const profileDir = gProfD.clone(); +profileDir.append("extensions"); + +createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "49"); +startupManager(); + +// NOTE: the following import needs to be called after the `createAppInfo` +// or it will fail Extension.jsm internally imports AddonManager.jsm and +// AddonManager will raise a ReferenceError exception because it tried to +// access an undefined `Services.appinfo` object. +const { Management } = Components.utils.import("resource://gre/modules/Extension.jsm", {}); + +const { + EmbeddedExtensionManager, + LegacyExtensionsUtils, +} = Components.utils.import("resource://gre/modules/LegacyExtensionsUtils.jsm"); + +// Wait the startup of the embedded webextension. +function promiseWebExtensionStartup() { + return new Promise(resolve => { + let listener = (event, extension) => { + Management.off("startup", listener); + resolve(extension); + }; + + Management.on("startup", listener); + }); +} + +function promiseWebExtensionShutdown() { + return new Promise(resolve => { + let listener = (event, extension) => { + Management.off("shutdown", listener); + resolve(extension); + }; + + Management.on("shutdown", listener); + }); +} + +const BOOTSTRAP = String.raw` + Components.utils.import("resource://xpcshell-data/BootstrapMonitor.jsm").monitor(this); +`; + +const EMBEDDED_WEBEXT_MANIFEST = JSON.stringify({ + name: "embedded webextension addon", + manifest_version: 2, + version: "1.0", +}); + +/** + * This test case checks that an addon with hasEmbeddedWebExtension set to true + * in its install.rdf gets the expected `embeddedWebExtension` object in the + * parameters of its bootstrap methods. + */ +add_task(function* run_embedded_webext_bootstrap() { + const ID = "embedded-webextension-addon2@tests.mozilla.org"; + + const xpiFile = createTempXPIFile({ + id: ID, + name: "Test Add-on", + version: "1.0", + bootstrap: true, + hasEmbeddedWebExtension: true, + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "1", + maxVersion: "1.9.2" + }] + }, { + "bootstrap.js": BOOTSTRAP, + "webextension/manifest.json": EMBEDDED_WEBEXT_MANIFEST, + }); + + yield AddonManager.installTemporaryAddon(xpiFile); + + let addon = yield promiseAddonByID(ID); + + notEqual(addon, null, "Got an addon object as expected"); + equal(addon.version, "1.0", "Got the expected version"); + equal(addon.hasEmbeddedWebExtension, true, + "Got the expected hasEmbeddedWebExtension value"); + + // Check that the addon has been installed and started. + BootstrapMonitor.checkAddonInstalled(ID, "1.0"); + + let installInfo = BootstrapMonitor.installed.get(ID); + ok(!("webExtension" in installInfo.data), + "No webExtension property is expected in the install bootstrap method params"); + + BootstrapMonitor.checkAddonStarted(ID, "1.0"); + + let startupInfo = BootstrapMonitor.started.get(ID); + + ok(("webExtension" in startupInfo.data), + "Got an webExtension property in the startup bootstrap method params"); + + ok(("startup" in startupInfo.data.webExtension), + "Got the expected 'startup' property in the webExtension object"); + + const waitForWebExtensionStartup = promiseWebExtensionStartup(); + + const embeddedAPI = yield startupInfo.data.webExtension.startup(); + + // WebExtension startup should have been fully resolved. + yield waitForWebExtensionStartup; + + Assert.deepEqual( + Object.keys(embeddedAPI.browser.runtime).sort(), + ["onConnect", "onMessage"], + `Got the expected 'runtime' in the 'browser' API object` + ); + + // Uninstall the addon and wait that the embedded webextension has been stopped and + // test the params of the shutdown and uninstall bootstrap method. + let waitForWebExtensionShutdown = promiseWebExtensionShutdown(); + let waitUninstall = promiseAddonEvent("onUninstalled"); + addon.uninstall(); + yield waitForWebExtensionShutdown; + yield waitUninstall; + + BootstrapMonitor.checkAddonNotStarted(ID, "1.0"); + + let shutdownInfo = BootstrapMonitor.stopped.get(ID); + ok(!("webExtension" in shutdownInfo.data), + "No webExtension property is expected in the shutdown bootstrap method params"); + + let uninstallInfo = BootstrapMonitor.uninstalled.get(ID); + ok(!("webExtension" in uninstallInfo.data), + "No webExtension property is expected in the uninstall bootstrap method params"); +}); + +/** + * This test case checks that an addon with hasEmbeddedWebExtension can be reloaded + * without raising unexpected exceptions due to race conditions. + */ +add_task(function* reload_embedded_webext_bootstrap() { + const ID = "embedded-webextension-addon2@tests.mozilla.org"; + + // No embedded webextension should be currently around. + equal(EmbeddedExtensionManager.embeddedExtensionsByAddonId.size, 0, + "No embedded extension instance should be tracked here"); + + const xpiFile = createTempXPIFile({ + id: ID, + name: "Test Add-on", + version: "1.0", + bootstrap: true, + hasEmbeddedWebExtension: true, + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "1", + maxVersion: "1.9.2" + }] + }, { + "bootstrap.js": BOOTSTRAP, + "webextension/manifest.json": EMBEDDED_WEBEXT_MANIFEST, + }); + + yield AddonManager.installTemporaryAddon(xpiFile); + + let addon = yield promiseAddonByID(ID); + + notEqual(addon, null, "Got an addon object as expected"); + equal(addon.version, "1.0", "Got the expected version"); + equal(addon.isActive, true, "The Addon is active"); + equal(addon.appDisabled, false, "The addon is not app disabled"); + equal(addon.userDisabled, false, "The addon is not user disabled"); + + // Check that the addon has been installed and started. + BootstrapMonitor.checkAddonInstalled(ID, "1.0"); + BootstrapMonitor.checkAddonStarted(ID, "1.0"); + + // Only one embedded extension. + equal(EmbeddedExtensionManager.embeddedExtensionsByAddonId.size, 1, + "Got the expected number of tracked extension instances"); + + const embeddedWebExtension = EmbeddedExtensionManager.embeddedExtensionsByAddonId.get(ID); + + let startupInfo = BootstrapMonitor.started.get(ID); + yield startupInfo.data.webExtension.startup(); + + const waitForAddonDisabled = promiseAddonEvent("onDisabled"); + addon.userDisabled = true; + yield waitForAddonDisabled; + + // No embedded webextension should be currently around. + equal(EmbeddedExtensionManager.embeddedExtensionsByAddonId.size, 0, + "No embedded extension instance should be tracked here"); + + const waitForAddonEnabled = promiseAddonEvent("onEnabled"); + addon.userDisabled = false; + yield waitForAddonEnabled; + + // Only one embedded extension. + equal(EmbeddedExtensionManager.embeddedExtensionsByAddonId.size, 1, + "Got the expected number of tracked extension instances"); + + const embeddedWebExtensionAfterEnabled = EmbeddedExtensionManager.embeddedExtensionsByAddonId.get(ID); + notEqual(embeddedWebExtensionAfterEnabled, embeddedWebExtension, + "Got a new EmbeddedExtension instance after the addon has been disabled and then enabled"); + + startupInfo = BootstrapMonitor.started.get(ID); + yield startupInfo.data.webExtension.startup(); + + const waitForReinstalled = promiseAddonEvent("onInstalled"); + addon.reload(); + yield waitForReinstalled; + + // No leaked embedded extension after the previous reloads. + equal(EmbeddedExtensionManager.embeddedExtensionsByAddonId.size, 1, + "Got the expected number of tracked extension instances"); + + const embeddedWebExtensionAfterReload = EmbeddedExtensionManager.embeddedExtensionsByAddonId.get(ID); + notEqual(embeddedWebExtensionAfterReload, embeddedWebExtensionAfterEnabled, + "Got a new EmbeddedExtension instance after the addon has been reloaded"); + + startupInfo = BootstrapMonitor.started.get(ID); + yield startupInfo.data.webExtension.startup(); + + // Uninstall the test addon + let waitUninstalled = promiseAddonEvent("onUninstalled"); + addon.uninstall(); + yield waitUninstalled; + + // No leaked embedded extension after uninstalling. + equal(EmbeddedExtensionManager.embeddedExtensionsByAddonId.size, 0, + "No embedded extension instance should be tracked after the addon uninstall"); +}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_webextension_install.js b/toolkit/mozapps/extensions/test/xpcshell/test_webextension_install.js index 96a103e0574fafd1b7afac422767fecc5cde988c..839c64f410baf851ea123b4ce5607febf4894f6c 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_webextension_install.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_webextension_install.js @@ -69,10 +69,7 @@ add_task(function* test_implicit_id_temp() { // We should be able to temporarily install an unsigned web extension // that does not have an ID in its manifest. add_task(function* test_unsigned_no_id_temp_install() { - if (!TEST_UNPACKED) { - do_print("This test does not apply when using packed extensions"); - return; - } + AddonTestUtils.useRealCertChecks = true; const manifest = { name: "no ID", description: "extension without an ID", @@ -97,16 +94,15 @@ add_task(function* test_unsigned_no_id_temp_install() { equal(secondAddon.id, addon.id, "Reinstalled add-on has the expected ID"); secondAddon.uninstall(); + Services.obs.notifyObservers(addonDir, "flush-cache-entry", null); addonDir.remove(true); + AddonTestUtils.useRealCertChecks = false; }); // We should be able to install two extensions from manifests without IDs // at different locations and get two unique extensions. add_task(function* test_multiple_no_id_extensions() { - if (!TEST_UNPACKED) { - do_print("This test does not apply when using packed extensions"); - return; - } + AddonTestUtils.useRealCertChecks = true; const manifest = { name: "no ID", description: "extension without an ID", @@ -132,9 +128,12 @@ add_task(function* test_multiple_no_id_extensions() { equal(filtered.length, 2, "Two add-ons are installed with the same name"); firstAddon.uninstall(); + Services.obs.notifyObservers(firstAddonDir, "flush-cache-entry", null); firstAddonDir.remove(true); secondAddon.uninstall(); + Services.obs.notifyObservers(secondAddonDir, "flush-cache-entry", null); secondAddonDir.remove(true); + AddonTestUtils.useRealCertChecks = false; }); // Test that we can get the ID from browser_specific_settings diff --git a/toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini b/toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini index 02cd906b0b18949c693c5183f0d6028ed164b15d..29265a0a52f021ca4d6c4ff446612b1b5ec9c2ce 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini +++ b/toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini @@ -315,6 +315,9 @@ tags = webextensions [test_webextension_install.js] skip-if = appname == "thunderbird" tags = webextensions +[test_webextension_embedded.js] +skip-if = appname == "thunderbird" +tags = webextensions [test_bootstrap_globals.js] [test_bug1180901_2.js] skip-if = os != "win" diff --git a/toolkit/xre/nsEmbedFunctions.cpp b/toolkit/xre/nsEmbedFunctions.cpp index fdb6bbd07a90312ef4a85066ac280966e10242cc..19fb12860cf50c991eebee1049f1e9b31a459936 100644 --- a/toolkit/xre/nsEmbedFunctions.cpp +++ b/toolkit/xre/nsEmbedFunctions.cpp @@ -606,13 +606,44 @@ XRE_InitChildProcess(int aArgc, case GeckoProcessType_Content: { process = new ContentProcess(parentPID); // If passed in grab the application path for xpcom init - nsCString appDir; + bool foundAppdir = false; + +#if defined(XP_MACOSX) && defined(MOZ_CONTENT_SANDBOX) + // If passed in grab the profile path for sandboxing + bool foundProfile = false; +#endif + for (int idx = aArgc; idx > 0; idx--) { if (aArgv[idx] && !strcmp(aArgv[idx], "-appdir")) { + MOZ_ASSERT(!foundAppdir); + if (foundAppdir) { + continue; + } + nsCString appDir; appDir.Assign(nsDependentCString(aArgv[idx+1])); static_cast<ContentProcess*>(process.get())->SetAppDir(appDir); + foundAppdir = true; + } + +#if defined(XP_MACOSX) && defined(MOZ_CONTENT_SANDBOX) + if (aArgv[idx] && !strcmp(aArgv[idx], "-profile")) { + MOZ_ASSERT(!foundProfile); + if (foundProfile) { + continue; + } + nsCString profile; + profile.Assign(nsDependentCString(aArgv[idx+1])); + static_cast<ContentProcess*>(process.get())->SetProfile(profile); + foundProfile = true; + } + if (foundProfile && foundAppdir) { + break; + } +#else + if (foundAppdir) { break; } +#endif /* XP_MACOSX && MOZ_CONTENT_SANDBOX */ } } break; diff --git a/xpcom/base/ErrorList.h b/xpcom/base/ErrorList.h index f1f0a07103346005f19ec06176617c29aa03ead1..711de7ca7058a76edc5c5a1f8f3ee48465edd2dd 100644 --- a/xpcom/base/ErrorList.h +++ b/xpcom/base/ErrorList.h @@ -976,6 +976,18 @@ ERROR(NS_ERROR_DOM_MEDIA_ABORT_ERR, FAILURE(1)), ERROR(NS_ERROR_DOM_MEDIA_NOT_ALLOWED_ERR, FAILURE(2)), ERROR(NS_ERROR_DOM_MEDIA_NOT_SUPPORTED_ERR, FAILURE(3)), + + /* HTMLMediaElement internal decoding error */ + ERROR(NS_ERROR_DOM_MEDIA_DECODE_ERR, FAILURE(4)), + ERROR(NS_ERROR_DOM_MEDIA_FATAL_ERR, FAILURE(5)), + ERROR(NS_ERROR_DOM_MEDIA_METADATA_ERR, FAILURE(6)), + ERROR(NS_ERROR_DOM_MEDIA_OVERFLOW_ERR, FAILURE(7)), + ERROR(NS_ERROR_DOM_MEDIA_END_OF_STREAM, FAILURE(8)), + ERROR(NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA, FAILURE(9)), + ERROR(NS_ERROR_DOM_MEDIA_CANCELED, FAILURE(10)), + ERROR(NS_ERROR_DOM_MEDIA_MEDIASINK_ERR, FAILURE(11)), + ERROR(NS_ERROR_DOM_MEDIA_DEMUXER_ERR, FAILURE(12)), + ERROR(NS_ERROR_DOM_MEDIA_CDM_ERR, FAILURE(13)), #undef MODULE /* ======================================================================= */ diff --git a/xpcom/base/nsCycleCollector.cpp b/xpcom/base/nsCycleCollector.cpp index 61a339ee2f3da6aa2d9a29bd003db743aafadb2b..4d3e6c6ec58a728d1b28d02e5e9e1b9e765ba977 100644 --- a/xpcom/base/nsCycleCollector.cpp +++ b/xpcom/base/nsCycleCollector.cpp @@ -1330,7 +1330,7 @@ public: SliceBudget& aBudget, nsICycleCollectorListener* aManualListener, bool aPreferShorterSlices = false); - void Shutdown(); + void Shutdown(bool aDoCollect); bool IsIdle() const { return mIncrementalPhase == IdlePhase; } @@ -3435,14 +3435,14 @@ nsCycleCollector::RegisterJSRuntime(CycleCollectedJSRuntime* aJSRuntime) MOZ_RELEASE_ASSERT(!mJSRuntime, "Multiple registrations of JS runtime in cycle collector"); mJSRuntime = aJSRuntime; + if (!NS_IsMainThread()) { + return; + } + // We can't register as a reporter in nsCycleCollector() because that runs // before the memory reporter manager is initialized. So we do it here // instead. - static bool registered = false; - if (!registered) { - RegisterWeakMemoryReporter(this); - registered = true; - } + RegisterWeakMemoryReporter(this); } void @@ -3876,17 +3876,14 @@ nsCycleCollector::SuspectedCount() } void -nsCycleCollector::Shutdown() +nsCycleCollector::Shutdown(bool aDoCollect) { CheckThreadSafety(); // Always delete snow white objects. FreeSnowWhite(true); -#ifndef NS_FREE_PERMANENT_DATA - if (PR_GetEnv("MOZ_CC_RUN_DURING_SHUTDOWN")) -#endif - { + if (aDoCollect) { ShutdownCollect(); } } @@ -4196,7 +4193,7 @@ nsCycleCollector_finishAnyCurrentCollection() } void -nsCycleCollector_shutdown() +nsCycleCollector_shutdown(bool aDoCollect) { CollectorData* data = sCollectorData.get(); @@ -4205,7 +4202,7 @@ nsCycleCollector_shutdown() PROFILER_LABEL("nsCycleCollector", "shutdown", js::ProfileEntry::Category::CC); - data->mCollector->Shutdown(); + data->mCollector->Shutdown(aDoCollect); data->mCollector = nullptr; if (data->mRuntime) { // Run any remaining tasks that may have been enqueued via diff --git a/xpcom/base/nsCycleCollector.h b/xpcom/base/nsCycleCollector.h index 3988702e48d6ed6a7d401dbc52482143634209b3..75b65704b9315e550b1262adea159d0b2c635bfd 100644 --- a/xpcom/base/nsCycleCollector.h +++ b/xpcom/base/nsCycleCollector.h @@ -51,7 +51,10 @@ void nsCycleCollector_collectSlice(js::SliceBudget& budget, bool aPreferShorterSlices = false); uint32_t nsCycleCollector_suspectedCount(); -void nsCycleCollector_shutdown(); + +// If aDoCollect is true, then run the GC and CC a few times before +// shutting down the CC completely. +void nsCycleCollector_shutdown(bool aDoCollect = true); // Helpers for interacting with JS void nsCycleCollector_registerJSRuntime(mozilla::CycleCollectedJSRuntime* aRt); diff --git a/xpcom/build/XPCOMInit.cpp b/xpcom/build/XPCOMInit.cpp index 1a71d0fcdbde24d15d3983aef72381b89ae47f8a..3988ce7e8241fc3bd3642ba1a9feb4dc950718f0 100644 --- a/xpcom/build/XPCOMInit.cpp +++ b/xpcom/build/XPCOMInit.cpp @@ -984,7 +984,13 @@ ShutdownXPCOM(nsIServiceManager* aServMgr) moduleLoaders = nullptr; } - nsCycleCollector_shutdown(); + bool shutdownCollect; +#ifdef NS_FREE_PERMANENT_DATA + shutdownCollect = true; +#else + shutdownCollect = !!PR_GetEnv("MOZ_CC_RUN_DURING_SHUTDOWN"); +#endif + nsCycleCollector_shutdown(shutdownCollect); PROFILER_MARKER("Shutdown xpcom"); // If we are doing any shutdown checks, poison writes.