author | Phil Ringnalda <philringnalda@gmail.com> |
Wed, 02 Nov 2016 19:28:38 -0700 | |
changeset 320733 | ade8d4a63e57560410de106450f37b50ed71cca5 |
parent 320535 | ac55a6776435142feebf3c20bbabfee100686416 (current diff) |
parent 320732 | 439965937366d0c2a504f8ca8cab1ed07be24fe7 (diff) |
child 320734 | e6ade90721b518675f3bad9a05ea83e3c3bfd286 |
child 320777 | ae743ad1f3260fc23be090535ff86f7097e144bb |
push id | 30902 |
push user | philringnalda@gmail.com |
push date | Thu, 03 Nov 2016 02:30:31 +0000 |
treeherder | mozilla-central@ade8d4a63e57 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | merge |
milestone | 52.0a1 |
first release with | nightly linux32
ade8d4a63e57
/
52.0a1
/
20161103030205
/
files
nightly linux64
ade8d4a63e57
/
52.0a1
/
20161103030205
/
files
nightly mac
ade8d4a63e57
/
52.0a1
/
20161103030205
/
files
nightly win32
ade8d4a63e57
/
52.0a1
/
20161103030205
/
files
nightly win64
ade8d4a63e57
/
52.0a1
/
20161103030205
/
files
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
releases | nightly linux32
52.0a1
/
20161103030205
/
pushlog to previous
nightly linux64
52.0a1
/
20161103030205
/
pushlog to previous
nightly mac
52.0a1
/
20161103030205
/
pushlog to previous
nightly win32
52.0a1
/
20161103030205
/
pushlog to previous
nightly win64
52.0a1
/
20161103030205
/
pushlog to previous
|
--- a/accessible/windows/msaa/AccessibleWrap.cpp +++ b/accessible/windows/msaa/AccessibleWrap.cpp @@ -1449,16 +1449,20 @@ GetProxiedAccessibleInSubtree(const DocA } MOZ_ASSERT(aDoc->IsTopLevel()); if (!aDoc->IsTopLevel()) { return nullptr; } wrapper->GetNativeInterface(getter_AddRefs(comProxy)); + MOZ_ASSERT(comProxy); + if (!comProxy) { + return nullptr; + } RefPtr<IDispatch> disp; if (FAILED(comProxy->get_accChild(aVarChild, getter_AddRefs(disp)))) { return nullptr; } return disp.forget(); }
--- a/browser/base/content/browser-devedition.js +++ b/browser/base/content/browser-devedition.js @@ -20,16 +20,17 @@ var DevEdition = { let theme = LightweightThemeManager.currentTheme; return theme && theme.id == "firefox-devedition@mozilla.org"; }, init: function () { this.initialized = true; Services.prefs.addObserver(this._devtoolsThemePrefName, this, false); Services.obs.addObserver(this, "lightweight-theme-styling-update", false); + Services.obs.addObserver(this, "lightweight-theme-window-updated", false); this._updateDevtoolsThemeAttribute(); if (this.isThemeCurrentlyApplied) { this._toggleStyleSheet(true); } }, createStyleSheet: function() { @@ -44,16 +45,18 @@ var DevEdition = { observe: function (subject, topic, data) { if (topic == "lightweight-theme-styling-update") { let newTheme = JSON.parse(data); if (newTheme && newTheme.id == "firefox-devedition@mozilla.org") { this._toggleStyleSheet(true); } else { this._toggleStyleSheet(false); } + } else if (topic == "lightweight-theme-window-updated" && subject == window) { + this._updateLWTBrightness(); } if (topic == "nsPref:changed" && data == this._devtoolsThemePrefName) { this._updateDevtoolsThemeAttribute(); } }, _inferBrightness: function() { @@ -62,24 +65,33 @@ var DevEdition = { if (this.isStyleSheetEnabled && document.documentElement.getAttribute("devtoolstheme") == "dark") { document.documentElement.setAttribute("brighttitlebarforeground", "true"); } else { document.documentElement.removeAttribute("brighttitlebarforeground"); } }, + _updateLWTBrightness() { + if (this.isThemeCurrentlyApplied) { + let devtoolsTheme = Services.prefs.getCharPref(this._devtoolsThemePrefName); + let textColor = devtoolsTheme == "dark" ? "bright" : "dark"; + document.documentElement.setAttribute("lwthemetextcolor", textColor); + } + }, + _updateDevtoolsThemeAttribute: function() { // Set an attribute on root element to make it possible // to change colors based on the selected devtools theme. let devtoolsTheme = Services.prefs.getCharPref(this._devtoolsThemePrefName); if (devtoolsTheme != "dark") { devtoolsTheme = "light"; } document.documentElement.setAttribute("devtoolstheme", devtoolsTheme); + this._updateLWTBrightness(); this._inferBrightness(); }, handleEvent: function(e) { if (e.type === "load") { this.styleSheet.removeEventListener("load", this); this.refreshBrowserDisplay(); } @@ -108,16 +120,17 @@ var DevEdition = { this.styleSheet.sheet.disabled = true; this.refreshBrowserDisplay(); } }, uninit: function () { Services.prefs.removeObserver(this._devtoolsThemePrefName, this); Services.obs.removeObserver(this, "lightweight-theme-styling-update", false); + Services.obs.removeObserver(this, "lightweight-theme-window-updated", false); if (this.styleSheet) { this.styleSheet.removeEventListener("load", this); } this.styleSheet = null; } }; // If the DevEdition theme is going to be applied in gBrowserInit.onLoad,
--- a/browser/base/content/browser.js +++ b/browser/base/content/browser.js @@ -3570,36 +3570,28 @@ const BrowserSearch = { } win = window.openDialog(getBrowserURL(), "_blank", "chrome,all,dialog=no", "about:blank"); Services.obs.addObserver(observer, "browser-delayed-startup-finished", false); } return; } - let openSearchPageIfFieldIsNotActive = function(aSearchBar) { + let focusUrlBarIfSearchFieldIsNotActive = function(aSearchBar) { if (!aSearchBar || document.activeElement != aSearchBar.textbox.inputField) { - let url = gBrowser.currentURI.spec.toLowerCase(); - let mm = gBrowser.selectedBrowser.messageManager; - let newTabRemoted = Services.prefs.getBoolPref("browser.newtabpage.remote"); - let localNewTabEnabled = url === "about:newtab" && !newTabRemoted && NewTabUtils.allPages.enabled; - if (url === "about:home" || localNewTabEnabled) { - ContentSearch.focusInput(mm); - } else { - openUILinkIn("about:home", "current"); - } + focusAndSelectUrlBar(); } }; let searchBar = this.searchBar; let placement = CustomizableUI.getPlacementOfWidget("search-container"); let focusSearchBar = () => { searchBar = this.searchBar; searchBar.select(); - openSearchPageIfFieldIsNotActive(searchBar); + focusUrlBarIfSearchFieldIsNotActive(searchBar); }; if (placement && placement.area == CustomizableUI.AREA_PANEL) { // The panel is not constructed until the first time it is shown. PanelUI.show().then(focusSearchBar); return; } if (placement && placement.area == CustomizableUI.AREA_NAVBAR && searchBar && searchBar.parentNode.getAttribute("overflowedItem") == "true") { @@ -3609,17 +3601,17 @@ const BrowserSearch = { }); return; } if (searchBar) { if (window.fullScreen) FullScreen.showNavToolbox(); searchBar.select(); } - openSearchPageIfFieldIsNotActive(searchBar); + focusUrlBarIfSearchFieldIsNotActive(searchBar); }, /** * Loads a search results page, given a set of search terms. Uses the current * engine if the search bar is visible, or the default engine otherwise. * * @param searchText * The search terms to use for the search.
--- a/browser/base/content/test/general/browser_aboutHome.js +++ b/browser/base/content/test/general/browser_aboutHome.js @@ -471,44 +471,16 @@ add_task(function* () { yield ContentTaskUtils.waitForCondition(() => doc.activeElement === searchInput, "Search input should be the active element."); is(searchInput.value, "a", "Search input should be 'a'."); }); }); }); add_task(function* () { - info("Cmd+k should focus the search box in the page when the search box in the toolbar is absent"); - - // Remove the search bar from toolbar - CustomizableUI.removeWidgetFromArea("search-container"); - - yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:home" }, function* (browser) { - yield BrowserTestUtils.synthesizeMouseAtCenter("#brandLogo", {}, browser); - yield ContentTask.spawn(browser, null, function* () { - let doc = content.document; - isnot(doc.getElementById("searchText"), doc.activeElement, - "Search input should not be the active element."); - }); - - EventUtils.synthesizeKey("k", { accelKey: true }); - - yield ContentTask.spawn(browser, null, function* () { - let doc = content.document; - let searchInput = doc.getElementById("searchText"); - - yield ContentTaskUtils.waitForCondition(() => doc.activeElement === searchInput, - "Search input should be the active element."); - }); - }); - - CustomizableUI.reset(); -}); - -add_task(function* () { info("Cmd+k should focus the search box in the toolbar when it's present"); yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:home" }, function* (browser) { yield BrowserTestUtils.synthesizeMouseAtCenter("#brandLogo", {}, browser); let doc = window.document; let searchInput = doc.getElementById("searchbar").textbox.inputField; isnot(searchInput, doc.activeElement, "Search bar should not be the active element.");
--- a/browser/base/content/test/newtab/browser.ini +++ b/browser/base/content/test/newtab/browser.ini @@ -40,11 +40,12 @@ support-files = searchEngineNoLogo.xml searchEngineFavicon.xml searchEngine1xLogo.xml searchEngine2xLogo.xml searchEngine1x2xLogo.xml ../general/searchSuggestionEngine.xml ../general/searchSuggestionEngine.sjs [browser_newtab_sponsored_icon_click.js] +skip-if = true # Bug 1314619 [browser_newtab_undo.js] [browser_newtab_unpin.js] [browser_newtab_update.js]
--- a/browser/base/content/test/newtab/browser_newtab_search.js +++ b/browser/base/content/test/newtab/browser_newtab_search.js @@ -173,75 +173,16 @@ add_task(function* () { EventUtils.synthesizeKey("a", { accelKey: true }); EventUtils.synthesizeKey("VK_DELETE", {}); yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () { Assert.ok(content.document.getElementById("searchSuggestionTable").hidden, "Search suggestion table hidden"); }); - // Remove the search bar from toolbar - CustomizableUI.removeWidgetFromArea("search-container"); - // Focus a different element than the search input from the page. - yield BrowserTestUtils.synthesizeMouseAtCenter("#newtab-customize-button", { }, gBrowser.selectedBrowser); - - yield ContentTask.spawn(gBrowser.selectedBrowser, { }, function* () { - let input = content.document.getElementById("newtab-search-text"); - Assert.notEqual(input, content.document.activeElement, "Search input should not be focused"); - }); - - // Test that Ctrl/Cmd + K will focus the input field from the page. - let focusPromise = promiseSearchEvents(["FocusInput"]); - EventUtils.synthesizeKey("k", { accelKey: true }); - yield focusPromise; - - yield ContentTask.spawn(gBrowser.selectedBrowser, { }, function* () { - let input = content.document.getElementById("newtab-search-text"); - Assert.equal(input, content.document.activeElement, "Search input should be focused"); - }); - - // Reset changes made to toolbar - CustomizableUI.reset(); - - // Test that Ctrl/Cmd + K will focus the search bar from toolbar. - EventUtils.synthesizeKey("k", { accelKey: true }); - let searchBar = document.getElementById("searchbar"); - is(searchBar.textbox.inputField, document.activeElement, "Toolbar's search bar should be focused"); - - // Test that Ctrl/Cmd + K will focus the search bar from a new about:home page if - // the newtab is disabled from `NewTabUtils.allPages.enabled`. - let tab = yield* addNewTabPageTab(); - // Remove the search bar from toolbar - CustomizableUI.removeWidgetFromArea("search-container"); - NewTabUtils.allPages.enabled = false; - EventUtils.synthesizeKey("k", { accelKey: true }); - - - let aboutHomeLoaded = new Promise(resolve => { - tab.linkedBrowser.addEventListener("AboutHomeLoadSnippetsCompleted", function loadListener(event) { - tab.linkedBrowser.removeEventListener("AboutHomeLoadSnippetsCompleted", loadListener, true); - resolve(); - }, true, true); - }); - - tab.linkedBrowser.loadURI("about:home"); - yield aboutHomeLoaded; - - yield ContentTask.spawn(gBrowser.selectedBrowser, { }, function* () { - Assert.equal(content.document.documentURI.toLowerCase(), "about:home", - "New tab's uri should be about:home"); - let searchInput = content.document.getElementById("searchText"); - Assert.equal(searchInput, content.document.activeElement, - "Search input must be the selected element"); - }); - - NewTabUtils.allPages.enabled = true; - CustomizableUI.reset(); - yield BrowserTestUtils.removeTab(gBrowser.selectedTab); - // Done. Revert the current engine and remove the new engines. searchEventsPromise = promiseSearchEvents(["CurrentEngine"]); Services.search.currentEngine = oldCurrentEngine; yield searchEventsPromise; let events = Array(gNewEngines.length).fill("CurrentState", 0, gNewEngines.length); searchEventsPromise = promiseSearchEvents(events);
--- a/browser/base/content/test/urlbar/browser.ini +++ b/browser/base/content/test/urlbar/browser.ini @@ -50,16 +50,17 @@ skip-if = os == 'linux' # Bug 1104755 subsuite = clipboard support-files = authenticate.sjs [browser_urlbarDecode.js] [browser_urlbarDelete.js] [browser_urlbarEnter.js] [browser_urlbarEnterAfterMouseOver.js] skip-if = os == "linux" # Bug 1073339 - Investigate autocomplete test unreliability on Linux/e10s +[browser_urlbarFocusedCmdK.js] [browser_urlbarHashChangeProxyState.js] [browser_urlbarKeepStateAcrossTabSwitches.js] [browser_urlbarOneOffs.js] [browser_urlbarPrivateBrowsingWindowChange.js] [browser_urlbarRaceWithTabs.js] [browser_urlbarRevert.js] [browser_urlbarSearchSingleWordNotification.js] [browser_urlbarSearchSuggestions.js]
--- a/browser/base/content/test/urlbar/browser_urlbarCopying.js +++ b/browser/base/content/test/urlbar/browser_urlbarCopying.js @@ -189,16 +189,11 @@ function testCopy(copyVal, targetValue, gURLBar.select(); } goDoCommand("cmd_copy"); }, cb, cb); } function loadURL(aURL, aCB) { - gBrowser.selectedBrowser.addEventListener("load", function () { - gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true); - is(gBrowser.currentURI.spec, aURL, "loaded expected URL"); - aCB(); - }, true); - - gBrowser.loadURI(aURL); + BrowserTestUtils.loadURI(gBrowser.selectedBrowser, aURL); + BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, false, aURL).then(aCB); }
new file mode 100644 --- /dev/null +++ b/browser/base/content/test/urlbar/browser_urlbarFocusedCmdK.js @@ -0,0 +1,17 @@ +/* Any copyright is dedicated to the Public Domain. +* http://creativecommons.org/publicdomain/zero/1.0/ */ + +add_task(function*() { + // Remove the search bar from toolbar + CustomizableUI.removeWidgetFromArea("search-container"); + + // Test that Ctrl/Cmd + K will focus the url bar + let focusPromise = BrowserTestUtils.waitForEvent(gURLBar, "focus"); + EventUtils.synthesizeKey("k", { accelKey: true }); + yield focusPromise; + Assert.equal(document.activeElement, gURLBar.inputField, "URL Bar should be focused"); + + // Reset changes made to toolbar + CustomizableUI.reset(); +}); +
--- a/browser/components/customizableui/test/browser_901207_searchbar_in_panel.js +++ b/browser/components/customizableui/test/browser_901207_searchbar_in_panel.js @@ -1,25 +1,14 @@ /* 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"; -var openUILinkInCalled = false; -var expectOpenUILinkInCall = false; -this.originalOpenUILinkIn = openUILinkIn; -openUILinkIn = (aUrl, aWhichTab) => { - is(aUrl, "about:home", "about:home should be requested to open."); - is(aWhichTab, "current", "Should use the current tab for the search page."); - openUILinkInCalled = true; - if (!expectOpenUILinkInCall) { - ok(false, "OpenUILinkIn was called when it shouldn't have been."); - } -}; logActiveElement(); function* waitForSearchBarFocus() { let searchbar = document.getElementById("searchbar"); yield waitForCondition(function () { logActiveElement(); return document.activeElement === searchbar.textbox.inputField; @@ -100,40 +89,16 @@ add_task(function*() { let placement = CustomizableUI.getPlacementOfWidget("search-container"); is(placement.area, CustomizableUI.AREA_NAVBAR, "Should be in nav-bar"); sendWebSearchKeyCommand(); yield waitForSearchBarFocus(); }); -// Ctrl+K should open the search page if the search bar has been customized out. -add_task(function*() { - try { - expectOpenUILinkInCall = true; - CustomizableUI.removeWidgetFromArea("search-container"); - let placement = CustomizableUI.getPlacementOfWidget("search-container"); - is(placement, null, "Search container should be in palette"); - - openUILinkInCalled = false; - - sendWebSearchKeyCommand(); - yield waitForCondition(() => openUILinkInCalled); - ok(openUILinkInCalled, "The search page should have been opened.") - expectOpenUILinkInCall = false; - } catch (e) { - ok(false, e); - } - CustomizableUI.reset(); -}); - -registerCleanupFunction(function() { - openUILinkIn = this.originalOpenUILinkIn; - delete this.originalOpenUILinkIn; -}); function sendWebSearchKeyCommand() { if (Services.appinfo.OS === "Darwin") EventUtils.synthesizeKey("k", { accelKey: true }); else EventUtils.synthesizeKey("k", { ctrlKey: true }); }
--- a/browser/components/preferences/in-content/tests/browser_security.js +++ b/browser/components/preferences/in-content/tests/browser_security.js @@ -118,13 +118,13 @@ add_task(function*() { "malware table doesn't include test-unwanted-simple"); let sortedMalware = malwareTable.slice(0); sortedMalware.sort(); Assert.deepEqual(malwareTable, sortedMalware, "malware table has been sorted"); yield BrowserTestUtils.removeTab(gBrowser.selectedTab); } - yield checkPrefSwitch(true, true); - yield checkPrefSwitch(false, true); - yield checkPrefSwitch(true, false); - yield checkPrefSwitch(false, false); + yield* checkPrefSwitch(true, true); + yield* checkPrefSwitch(false, true); + yield* checkPrefSwitch(true, false); + yield* checkPrefSwitch(false, false); });
--- a/browser/components/sessionstore/ContentRestore.jsm +++ b/browser/components/sessionstore/ContentRestore.jsm @@ -19,17 +19,17 @@ XPCOMUtils.defineLazyModuleGetter(this, "resource:///modules/sessionstore/PageStyle.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "ScrollPosition", "resource://gre/modules/ScrollPosition.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "SessionHistory", "resource:///modules/sessionstore/SessionHistory.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "SessionStorage", "resource:///modules/sessionstore/SessionStorage.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "Utils", - "resource:///modules/sessionstore/Utils.jsm"); + "resource://gre/modules/sessionstore/Utils.jsm"); /** * This module implements the content side of session restoration. The chrome * side is handled by SessionStore.jsm. The functions in this module are called * by content-sessionStore.js based on messages received from SessionStore.jsm * (or, in one case, based on a "load" event). Each tab has its own * ContentRestore instance, constructed by content-sessionStore.js. *
--- a/browser/components/sessionstore/SessionCookies.jsm +++ b/browser/components/sessionstore/SessionCookies.jsm @@ -8,17 +8,17 @@ this.EXPORTED_SYMBOLS = ["SessionCookies const Cu = Components.utils; const Ci = Components.interfaces; Cu.import("resource://gre/modules/Services.jsm", this); Cu.import("resource://gre/modules/XPCOMUtils.jsm", this); XPCOMUtils.defineLazyModuleGetter(this, "Utils", - "resource:///modules/sessionstore/Utils.jsm"); + "resource://gre/modules/sessionstore/Utils.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "PrivacyLevel", "resource:///modules/sessionstore/PrivacyLevel.jsm"); // MAX_EXPIRY should be 2^63-1, but JavaScript can't handle that precision. const MAX_EXPIRY = Math.pow(2, 62); /** * The external API implemented by the SessionCookies module.
--- a/browser/components/sessionstore/SessionHistory.jsm +++ b/browser/components/sessionstore/SessionHistory.jsm @@ -9,17 +9,17 @@ this.EXPORTED_SYMBOLS = ["SessionHistory const Cu = Components.utils; const Cc = Components.classes; const Ci = Components.interfaces; Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/XPCOMUtils.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "Utils", - "resource:///modules/sessionstore/Utils.jsm"); + "resource://gre/modules/sessionstore/Utils.jsm"); function debug(msg) { Services.console.logStringMessage("SessionHistory: " + msg); } /** * The external API exported by this module. */
--- a/browser/components/sessionstore/SessionStore.jsm +++ b/browser/components/sessionstore/SessionStore.jsm @@ -175,17 +175,17 @@ XPCOMUtils.defineLazyModuleGetter(this, "resource:///modules/ContentCrashHandlers.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "TabState", "resource:///modules/sessionstore/TabState.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "TabStateCache", "resource:///modules/sessionstore/TabStateCache.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "TabStateFlusher", "resource:///modules/sessionstore/TabStateFlusher.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "Utils", - "resource:///modules/sessionstore/Utils.jsm"); + "resource://gre/modules/sessionstore/Utils.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "ViewSourceBrowser", "resource://gre/modules/ViewSourceBrowser.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "AsyncShutdown", "resource://gre/modules/AsyncShutdown.jsm"); /** * |true| if we are in debug mode, |false| otherwise. * Debug mode is controlled by preference browser.sessionstore.debug
--- a/browser/components/sessionstore/TabState.jsm +++ b/browser/components/sessionstore/TabState.jsm @@ -12,17 +12,17 @@ Cu.import("resource://gre/modules/XPCOMU XPCOMUtils.defineLazyModuleGetter(this, "PrivacyFilter", "resource:///modules/sessionstore/PrivacyFilter.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "TabStateCache", "resource:///modules/sessionstore/TabStateCache.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "TabAttributes", "resource:///modules/sessionstore/TabAttributes.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "Utils", - "resource:///modules/sessionstore/Utils.jsm"); + "resource://gre/modules/sessionstore/Utils.jsm"); /** * Module that contains tab state collection methods. */ this.TabState = Object.freeze({ update: function (browser, data) { TabStateInternal.update(browser, data); },
--- a/browser/components/sessionstore/moz.build +++ b/browser/components/sessionstore/moz.build @@ -41,13 +41,12 @@ EXTRA_JS_MODULES.sessionstore = [ 'SessionStore.jsm', 'SessionWorker.js', 'SessionWorker.jsm', 'StartupPerformance.jsm', 'TabAttributes.jsm', 'TabState.jsm', 'TabStateCache.jsm', 'TabStateFlusher.jsm', - 'Utils.jsm', ] with Files('**'): BUG_COMPONENT = ('Firefox', 'Session Restore')
--- a/browser/locales/filter.py +++ b/browser/locales/filter.py @@ -12,22 +12,19 @@ def test(mod, path, entity = None): "other-licenses/branding/firefox", "browser/branding/official", "services/sync"): return "ignore" if mod not in ("browser", "extensions/spellcheck"): # we only have exceptions for browser and extensions/spellcheck return "error" if not entity: - # the only files to ignore are spell checkers and search + # the only files to ignore are spell checkers if mod == "extensions/spellcheck": return "ignore" - # browser - if (re.match(r"searchplugins\/.+\.xml", path)): - return "ignore" return "error" if mod == "extensions/spellcheck": # l10n ships en-US dictionary or something, do compare return "error" if path == "defines.inc": return "ignore" if entity == "MOZ_LANGPACK_CONTRIBUTORS" else "error" if mod == "browser" and path == "chrome/browser-region/region.properties":
--- a/browser/locales/jar.mn +++ b/browser/locales/jar.mn @@ -85,17 +85,17 @@ locale/browser/syncSetup.dtd (%chrome/browser/syncSetup.dtd) locale/browser/syncSetup.properties (%chrome/browser/syncSetup.properties) locale/browser/syncGenericChange.properties (%chrome/browser/syncGenericChange.properties) locale/browser/syncKey.dtd (%chrome/browser/syncKey.dtd) locale/browser/syncQuota.dtd (%chrome/browser/syncQuota.dtd) locale/browser/syncQuota.properties (%chrome/browser/syncQuota.properties) % resource search-plugins chrome://browser/locale/searchplugins/ #if BUILD_FASTER - locale/browser/searchplugins/ (%searchplugins/*.xml) + locale/browser/searchplugins/ (searchplugins/*.xml) locale/browser/searchplugins/list.json (search/list.json) #else locale/browser/searchplugins/ (.deps/generated_@AB_CD@/*.xml) locale/browser/searchplugins/list.json (.deps/generated_@AB_CD@/list.json) #endif % locale browser-region @AB_CD@ %locale/browser-region/ locale/browser-region/region.properties (%chrome/browser-region/region.properties) # the following files are browser-specific overrides
--- a/browser/modules/ContentCrashHandlers.jsm +++ b/browser/modules/ContentCrashHandlers.jsm @@ -741,17 +741,20 @@ this.UnsubmittedCrashHandler = { * YYYYMMDD. * * @param someDate (Date, optional) * The Date to convert to the string. If not provided, * defaults to today's date. * @returns String */ dateString(someDate = new Date()) { - return someDate.toLocaleFormat("%Y%m%d"); + let year = String(someDate.getFullYear()).padStart(4, "0"); + let month = String(someDate.getMonth() + 1).padStart(2, "0"); + let day = String(someDate.getDate()).padStart(2, "0"); + return year + month + day; }, /** * 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. *
--- a/browser/modules/test/browser.ini +++ b/browser/modules/test/browser.ini @@ -32,12 +32,9 @@ support-files = usageTelemetrySearchSuggestions.sjs usageTelemetrySearchSuggestions.xml [browser_UsageTelemetry_searchbar.js] support-files = usageTelemetrySearchSuggestions.sjs usageTelemetrySearchSuggestions.xml [browser_UsageTelemetry_content.js] [browser_UsageTelemetry_content_aboutHome.js] -# Disabled for intermittent failures. -# Re-enabling this test is tracked in bug 1313825. -skip-if = true [browser_urlBar_zoom.js]
--- a/browser/modules/test/browser_UnsubmittedCrashHandler.js +++ b/browser/modules/test/browser_UnsubmittedCrashHandler.js @@ -441,17 +441,17 @@ add_task(function* test_can_ignore() { * lastShownDate is set for today. */ add_task(function* test_last_shown_date() { yield createPendingCrashReports(1); let notification = yield UnsubmittedCrashHandler.checkForUnsubmittedCrashReports(); Assert.ok(notification, "There should be a notification"); - let today = new Date().toLocaleFormat("%Y%m%d"); + let today = UnsubmittedCrashHandler.dateString(new Date()); let lastShownDate = UnsubmittedCrashHandler.prefs.getCharPref("lastShownDate"); Assert.equal(today, lastShownDate, "Last shown date should be today."); UnsubmittedCrashHandler.prefs.clearUserPref("lastShownDate"); gNotificationBox.removeNotification(notification, true); clearPendingCrashReports(); @@ -536,17 +536,17 @@ add_task(function* test_dont_decrement_c gNotificationBox.removeNotification(notification, true); let shutdownWhileShowing = UnsubmittedCrashHandler.prefs.getBoolPref("shutdownWhileShowing"); Assert.ok(shutdownWhileShowing, "We should have noticed that we uninitted while showing " + "the notification."); - let today = new Date().toLocaleFormat("%Y%m%d"); + let today = UnsubmittedCrashHandler.dateString(new Date()); let lastShownDate = UnsubmittedCrashHandler.prefs.getCharPref("lastShownDate"); Assert.equal(today, lastShownDate, "Last shown date should be today."); UnsubmittedCrashHandler.init(); notification = @@ -585,17 +585,17 @@ add_task(function* test_decrement_chance let shutdownWhileShowing = UnsubmittedCrashHandler.prefs.getBoolPref("shutdownWhileShowing"); Assert.ok(shutdownWhileShowing, "We should have noticed that we uninitted while showing " + "the notification."); // Now pretend that the notification was shown yesterday. - let yesterday = new Date(Date.now() - DAY).toLocaleFormat("%Y%m%d"); + let yesterday = UnsubmittedCrashHandler.dateString(new Date(Date.now() - DAY)); UnsubmittedCrashHandler.prefs.setCharPref("lastShownDate", yesterday); UnsubmittedCrashHandler.init(); notification = yield UnsubmittedCrashHandler.checkForUnsubmittedCrashReports(); Assert.ok(notification, "There should still be a notification"); @@ -613,47 +613,47 @@ add_task(function* test_decrement_chance /** * Tests that if we've shutdown too many times showing the * notification, and we've run out of chances, then * browser.crashReports.unsubmittedCheck.suppressUntilDate is * set for some days into the future. */ add_task(function* test_can_suppress_after_chances() { // Pretend that a notification was shown yesterday. - let yesterday = new Date(Date.now() - DAY).toLocaleFormat("%Y%m%d"); + let yesterday = UnsubmittedCrashHandler.dateString(new Date(Date.now() - DAY)); UnsubmittedCrashHandler.prefs.setCharPref("lastShownDate", yesterday); UnsubmittedCrashHandler.prefs.setBoolPref("shutdownWhileShowing", true); UnsubmittedCrashHandler.prefs.setIntPref("chancesUntilSuppress", 0); yield createPendingCrashReports(1); let notification = yield UnsubmittedCrashHandler.checkForUnsubmittedCrashReports(); Assert.equal(notification, null, "There should be no notification if we've run out of chances"); // We should have set suppressUntilDate into the future let suppressUntilDate = UnsubmittedCrashHandler.prefs.getCharPref("suppressUntilDate"); - let today = new Date().toLocaleFormat("%Y%m%d"); + let today = UnsubmittedCrashHandler.dateString(new Date()); Assert.ok(suppressUntilDate > today, "We should be suppressing until some days into the future."); UnsubmittedCrashHandler.prefs.clearUserPref("chancesUntilSuppress"); UnsubmittedCrashHandler.prefs.clearUserPref("suppressUntilDate"); UnsubmittedCrashHandler.prefs.clearUserPref("lastShownDate"); clearPendingCrashReports(); }); /** * Tests that if there's a suppression date set, then no notification * will be shown even if there are pending crash reports. */ add_task(function* test_suppression() { - let future = new Date(Date.now() + (DAY * 5)).toLocaleFormat("%Y%m%d"); + let future = UnsubmittedCrashHandler.dateString(new Date(Date.now() + (DAY * 5))); UnsubmittedCrashHandler.prefs.setCharPref("suppressUntilDate", future); UnsubmittedCrashHandler.uninit(); UnsubmittedCrashHandler.init(); Assert.ok(UnsubmittedCrashHandler.suppressed, "The UnsubmittedCrashHandler should be suppressed."); UnsubmittedCrashHandler.prefs.clearUserPref("suppressUntilDate"); @@ -661,17 +661,17 @@ add_task(function* test_suppression() { UnsubmittedCrashHandler.init(); }); /** * Tests that if there's a suppression date set, but we've exceeded * it, then we can show the notification again. */ add_task(function* test_end_suppression() { - let yesterday = new Date(Date.now() - DAY).toLocaleFormat("%Y%m%d"); + let yesterday = UnsubmittedCrashHandler.dateString(new Date(Date.now() - DAY)); UnsubmittedCrashHandler.prefs.setCharPref("suppressUntilDate", yesterday); UnsubmittedCrashHandler.uninit(); UnsubmittedCrashHandler.init(); Assert.ok(!UnsubmittedCrashHandler.suppressed, "The UnsubmittedCrashHandler should not be suppressed."); Assert.ok(!UnsubmittedCrashHandler.prefs.prefHasUserValue("suppressUntilDate"), "The suppression date should been cleared from preferences.");
--- a/browser/modules/test/browser_UsageTelemetry_content_aboutHome.js +++ b/browser/modules/test/browser_UsageTelemetry_content_aboutHome.js @@ -34,24 +34,31 @@ add_task(function* setup() { Services.search.removeEngine(engineOneOff); }); }); add_task(function* test_abouthome_simpleQuery() { // Let's reset the counts. Services.telemetry.clearScalars(); - let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:home"); - yield new Promise(resolve => { + let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser); + + info("Setup waiting for AboutHomeLoadSnippetsCompleted."); + let promiseAboutHomeLoaded = new Promise(resolve => { tab.linkedBrowser.addEventListener("AboutHomeLoadSnippetsCompleted", function loadListener(event) { tab.linkedBrowser.removeEventListener("AboutHomeLoadSnippetsCompleted", loadListener, true); resolve(); }, true, true); }); + info("Load about:home."); + tab.linkedBrowser.loadURI("about:home"); + info("Wait for AboutHomeLoadSnippetsCompleted."); + yield promiseAboutHomeLoaded; + info("Trigger a simple serch, just test + enter."); let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser); yield typeInSearchField(tab.linkedBrowser, "test query", "searchText"); yield BrowserTestUtils.synthesizeKey("VK_RETURN", {}, tab.linkedBrowser); yield p; // Check if the scalars contain the expected values. const scalars =
--- a/browser/themes/osx/devedition.css +++ b/browser/themes/osx/devedition.css @@ -82,21 +82,16 @@ /* Use smaller back button icon */ @media (min-resolution: 2dppx) { #back-button:hover:active:not([disabled="true"]) { -moz-image-region: rect(36px, 108px, 72px, 72px); } } -#forward-button:hover:active:not(:-moz-lwtheme) { - background-image: none; - box-shadow: none; -} - /* Don't use the default background for tabs toolbar */ #TabsToolbar { -moz-appearance: none !important; } /* Prevent the hover styling from on the identity icon from overlapping the urlbar border. */ #identity-box {
--- a/browser/themes/osx/places/organizer.css +++ b/browser/themes/osx/places/organizer.css @@ -44,85 +44,69 @@ } #placesList > treechildren::-moz-tree-twisty { -moz-appearance: none; padding: 0 2px; list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-collapsed"); } -#placesList > treechildren::-moz-tree-twisty(selected) { +#placesList > treechildren::-moz-tree-twisty(closed, selected) { list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-collapsed-inverted"); } #placesList > treechildren::-moz-tree-twisty(open) { list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-expanded"); } #placesList > treechildren::-moz-tree-twisty(open, selected) { list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-expanded-inverted"); } -#placesList > treechildren:-moz-locale-dir(rtl)::-moz-tree-twisty { +#placesList > treechildren:-moz-locale-dir(rtl)::-moz-tree-twisty(closed) { list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-collapsed-rtl"); } -#placesList > treechildren:-moz-locale-dir(rtl)::-moz-tree-twisty(selected) { +#placesList > treechildren:-moz-locale-dir(rtl)::-moz-tree-twisty(closed, selected) { list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-collapsed-inverted-rtl"); } -#placesList > treechildren:-moz-locale-dir(rtl)::-moz-tree-twisty(open) { - list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-expanded"); -} - -#placesList > treechildren:-moz-locale-dir(rtl)::-moz-tree-twisty(open, selected) { - list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-expanded-inverted"); -} - @media (-moz-mac-yosemite-theme) { #placesList > treechildren::-moz-tree-cell-text(selected) { color: -moz-dialogtext; font-weight: 500; } #placesList > treechildren::-moz-tree-cell-text(selected, focus) { color: #fff; } - #placesList > treechildren::-moz-tree-twisty(selected) { + #placesList > treechildren::-moz-tree-twisty(closed, selected) { list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-collapsed"); } - #placesList > treechildren::-moz-tree-twisty(selected, focus) { + #placesList > treechildren::-moz-tree-twisty(closed, selected, focus) { list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-collapsed-inverted"); } #placesList > treechildren::-moz-tree-twisty(open, selected) { list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-expanded"); } #placesList > treechildren::-moz-tree-twisty(open, selected, focus) { list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-expanded-inverted"); } - #placesList > treechildren:-moz-locale-dir(rtl)::-moz-tree-twisty(selected) { + #placesList > treechildren:-moz-locale-dir(rtl)::-moz-tree-twisty(closed, selected) { list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-collapsed-rtl"); } - #placesList > treechildren:-moz-locale-dir(rtl)::-moz-tree-twisty(selected, focus) { + #placesList > treechildren:-moz-locale-dir(rtl)::-moz-tree-twisty(closed, selected, focus) { list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-collapsed-inverted-rtl"); } - - #placesList > treechildren:-moz-locale-dir(rtl)::-moz-tree-twisty(open, selected) { - list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-expanded"); - } - - #placesList > treechildren:-moz-locale-dir(rtl)::-moz-tree-twisty(open, selected, focus) { - list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-expanded-inverted"); - } } #placesToolbar { padding: 0 4px 3px; } #placesView { border-top: none !important;
--- a/browser/themes/osx/places/places.css +++ b/browser/themes/osx/places/places.css @@ -62,85 +62,69 @@ } .sidebar-placesTreechildren::-moz-tree-twisty { -moz-appearance: none; padding: 0 2px; list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-collapsed"); } -.sidebar-placesTreechildren::-moz-tree-twisty(selected) { +.sidebar-placesTreechildren::-moz-tree-twisty(closed, selected) { list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-collapsed-inverted"); } .sidebar-placesTreechildren::-moz-tree-twisty(open) { list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-expanded"); } .sidebar-placesTreechildren::-moz-tree-twisty(open, selected) { list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-expanded-inverted"); } -.sidebar-placesTreechildren:-moz-locale-dir(rtl)::-moz-tree-twisty { +.sidebar-placesTreechildren:-moz-locale-dir(rtl)::-moz-tree-twisty(closed) { list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-collapsed-rtl"); } -.sidebar-placesTreechildren:-moz-locale-dir(rtl)::-moz-tree-twisty(selected) { +.sidebar-placesTreechildren:-moz-locale-dir(rtl)::-moz-tree-twisty(closed, selected) { list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-collapsed-inverted-rtl"); } -.sidebar-placesTreechildren:-moz-locale-dir(rtl)::-moz-tree-twisty(open) { - list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-expanded"); -} - -.sidebar-placesTreechildren:-moz-locale-dir(rtl)::-moz-tree-twisty(open, selected) { - list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-expanded-inverted"); -} - @media (-moz-mac-yosemite-theme) { .sidebar-placesTreechildren::-moz-tree-cell-text(selected) { color: -moz-dialogtext; font-weight: 500; } .sidebar-placesTreechildren::-moz-tree-cell-text(selected, focus) { color: #fff; } - .sidebar-placesTreechildren::-moz-tree-twisty(selected) { + .sidebar-placesTreechildren::-moz-tree-twisty(closed, selected) { list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-collapsed"); } - .sidebar-placesTreechildren::-moz-tree-twisty(selected, focus) { + .sidebar-placesTreechildren::-moz-tree-twisty(closed, selected, focus) { list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-collapsed-inverted"); } .sidebar-placesTreechildren::-moz-tree-twisty(open, selected) { list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-expanded"); } .sidebar-placesTreechildren::-moz-tree-twisty(open, selected, focus) { list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-expanded-inverted"); } - .sidebar-placesTreechildren:-moz-locale-dir(rtl)::-moz-tree-twisty(selected) { + .sidebar-placesTreechildren:-moz-locale-dir(rtl)::-moz-tree-twisty(closed, selected) { list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-collapsed-rtl"); } - .sidebar-placesTreechildren:-moz-locale-dir(rtl)::-moz-tree-twisty(selected, focus) { + .sidebar-placesTreechildren:-moz-locale-dir(rtl)::-moz-tree-twisty(closed, selected, focus) { list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-collapsed-inverted-rtl") } - - .sidebar-placesTreechildren:-moz-locale-dir(rtl)::-moz-tree-twisty(open, selected) { - list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-expanded"); - } - - .sidebar-placesTreechildren:-moz-locale-dir(rtl)::-moz-tree-twisty(open, selected, focus) { - list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-expanded-inverted"); - } } #viewButton { -moz-appearance: none; padding-bottom: 1px; padding-inline-start: 5px; padding-inline-end: 0px; margin: 0;
--- a/browser/themes/shared/devedition.inc.css +++ b/browser/themes/shared/devedition.inc.css @@ -179,17 +179,16 @@ /* Using toolbar[brighttext] instead of important to override linux */ toolbar[brighttext] #downloads-indicator-counter { text-shadow: var(--toolbarbutton-text-shadow); color: var(--chrome-color); } #TabsToolbar { text-shadow: none !important; - color: var(--chrome-color) !important; /* Make sure that the brighttext attribute is added */ } /* URL bar and search bar*/ #urlbar, #navigator-toolbox .searchbar-textbox { background-color: var(--url-and-searchbar-background-color) !important; background-image: none !important; color: inherit !important;
--- a/build/gyp.mozbuild +++ b/build/gyp.mozbuild @@ -104,16 +104,17 @@ flavors = { 'NetBSD': 'netbsd', 'OpenBSD': 'openbsd', } gyp_vars['OS'] = flavors.get(os) arches = { 'x86_64': 'x64', 'x86': 'ia32', + 'aarch64': 'arm64', } gyp_vars['target_arch'] = arches.get(CONFIG['CPU_ARCH'], CONFIG['CPU_ARCH']) if CONFIG['ARM_ARCH']: if int(CONFIG['ARM_ARCH']) < 7: gyp_vars['armv7'] = 0 gyp_vars['arm_neon_optional'] = 0
--- a/caps/BasePrincipal.cpp +++ b/caps/BasePrincipal.cpp @@ -18,16 +18,17 @@ #include "nsPrincipal.h" #include "nsNetUtil.h" #include "nsIURIWithPrincipal.h" #include "nsNullPrincipal.h" #include "nsScriptSecurityManager.h" #include "nsServiceManagerUtils.h" +#include "mozilla/dom/ChromeUtils.h" #include "mozilla/dom/CSPDictionariesBinding.h" #include "mozilla/dom/quota/QuotaManager.h" #include "mozilla/dom/ToJSValue.h" #include "mozilla/dom/URLSearchParams.h" namespace mozilla { using dom::URLParams; @@ -390,16 +391,26 @@ BasePrincipal::GetOriginNoSuffix(nsACStr { return GetOriginInternal(aOrigin); } bool BasePrincipal::Subsumes(nsIPrincipal* aOther, DocumentDomainConsideration aConsideration) { MOZ_ASSERT(aOther); + + // Expanded principals handle origin attributes for each of their + // sub-principals individually, null principals do only simple checks for + // pointer equality, and system principals are immune to origin attributes + // checks, so only do this check for codebase principals. + if (Kind() == eCodebasePrincipal && + OriginAttributesRef() != Cast(aOther)->OriginAttributesRef()) { + return false; + } + return SubsumesInternal(aOther, aConsideration); } NS_IMETHODIMP BasePrincipal::Equals(nsIPrincipal *aOther, bool *aResult) { NS_ENSURE_TRUE(aOther, NS_ERROR_INVALID_ARG); *aResult = Subsumes(aOther, DontConsiderDocumentDomain) && @@ -411,16 +422,32 @@ NS_IMETHODIMP BasePrincipal::EqualsConsideringDomain(nsIPrincipal *aOther, bool *aResult) { NS_ENSURE_TRUE(aOther, NS_ERROR_INVALID_ARG); *aResult = Subsumes(aOther, ConsiderDocumentDomain) && Cast(aOther)->Subsumes(this, ConsiderDocumentDomain); return NS_OK; } +bool +BasePrincipal::EqualsIgnoringAddonId(nsIPrincipal *aOther) +{ + MOZ_ASSERT(aOther); + + // Note that this will not work for expanded principals, nor is it intended + // to. + if (!dom::ChromeUtils::IsOriginAttributesEqualIgnoringAddonId( + OriginAttributesRef(), Cast(aOther)->OriginAttributesRef())) { + return false; + } + + return SubsumesInternal(aOther, DontConsiderDocumentDomain) && + Cast(aOther)->SubsumesInternal(this, DontConsiderDocumentDomain); +} + NS_IMETHODIMP BasePrincipal::Subsumes(nsIPrincipal *aOther, bool *aResult) { NS_ENSURE_TRUE(aOther, NS_ERROR_INVALID_ARG); *aResult = Subsumes(aOther, DontConsiderDocumentDomain); return NS_OK; }
--- a/caps/BasePrincipal.h +++ b/caps/BasePrincipal.h @@ -284,16 +284,18 @@ public: NS_IMETHOD GetAppStatus(uint16_t* aAppStatus) final; NS_IMETHOD GetAppId(uint32_t* aAppStatus) final; NS_IMETHOD GetAddonId(nsAString& aAddonId) final; NS_IMETHOD GetIsInIsolatedMozBrowserElement(bool* aIsInIsolatedMozBrowserElement) final; NS_IMETHOD GetUnknownAppId(bool* aUnknownAppId) final; NS_IMETHOD GetUserContextId(uint32_t* aUserContextId) final; NS_IMETHOD GetPrivateBrowsingId(uint32_t* aPrivateBrowsingId) final; + bool EqualsIgnoringAddonId(nsIPrincipal *aOther); + virtual bool AddonHasPermission(const nsAString& aPerm); virtual bool IsOnCSSUnprefixingWhitelist() override { return false; } virtual bool IsCodebasePrincipal() const { return false; }; static BasePrincipal* Cast(nsIPrincipal* aPrin) { return static_cast<BasePrincipal*>(aPrin); } static already_AddRefed<BasePrincipal> @@ -316,16 +318,18 @@ public: virtual PrincipalKind Kind() = 0; already_AddRefed<BasePrincipal> CloneStrippingUserContextIdAndFirstPartyDomain(); protected: virtual ~BasePrincipal(); virtual nsresult GetOriginInternal(nsACString& aOrigin) = 0; + // Note that this does not check OriginAttributes. Callers that depend on + // those must call Subsumes instead. virtual bool SubsumesInternal(nsIPrincipal* aOther, DocumentDomainConsideration aConsider) = 0; // Internal, side-effect-free check to determine whether the concrete // principal would allow the load ignoring any common behavior implemented in // BasePrincipal::CheckMayLoad. virtual bool MayLoadInternal(nsIURI* aURI) = 0; friend class ::nsExpandedPrincipal;
--- a/caps/nsPrincipal.cpp +++ b/caps/nsPrincipal.cpp @@ -192,20 +192,16 @@ nsPrincipal::SubsumesInternal(nsIPrincip { MOZ_ASSERT(aOther); // For nsPrincipal, Subsumes is equivalent to Equals. if (aOther == this) { return true; } - if (OriginAttributesRef() != Cast(aOther)->OriginAttributesRef()) { - return false; - } - // If either the subject or the object has changed its principal by // explicitly setting document.domain then the other must also have // done so in order to be considered the same origin. This prevents // DNS spoofing based on document.domain (154930) nsresult rv; if (aConsideration == ConsiderDocumentDomain) { // Get .domain on each principal. nsCOMPtr<nsIURI> thisDomain, otherDomain; @@ -726,16 +722,19 @@ nsExpandedPrincipal::SubsumesInternal(ns { // If aOther is an ExpandedPrincipal too, we break it down into its component // nsIPrincipals, and check subsumes on each one. nsCOMPtr<nsIExpandedPrincipal> expanded = do_QueryInterface(aOther); if (expanded) { nsTArray< nsCOMPtr<nsIPrincipal> >* otherList; expanded->GetWhiteList(&otherList); for (uint32_t i = 0; i < otherList->Length(); ++i){ + // Use SubsumesInternal rather than Subsumes here, since OriginAttribute + // checks are only done between non-expanded sub-principals, and we don't + // need to incur the extra virtual call overhead. if (!SubsumesInternal((*otherList)[i], aConsideration)) { return false; } } return true; } // We're dealing with a regular principal. One of our principals must subsume
--- a/devtools/client/inspector/components/box-model.js +++ b/devtools/client/inspector/components/box-model.js @@ -4,17 +4,17 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; const {Task} = require("devtools/shared/task"); const {InplaceEditor, editableItem} = require("devtools/client/shared/inplace-editor"); -const {ReflowFront} = require("devtools/shared/fronts/layout"); +const {ReflowFront} = require("devtools/shared/fronts/reflow"); const {LocalizationHelper} = require("devtools/shared/l10n"); const {getCssProperties} = require("devtools/shared/fronts/css-properties"); const STRINGS_URI = "devtools/locale/shared.properties"; const STRINGS_INSPECTOR = "devtools-shared/locale/styleinspector.properties"; const SHARED_L10N = new LocalizationHelper(STRINGS_URI); const INSPECTOR_L10N = new LocalizationHelper(STRINGS_INSPECTOR); const NUMERIC = /^-?[\d\.]+$/;
--- a/devtools/client/shared/view-source.js +++ b/devtools/client/shared/view-source.js @@ -50,46 +50,52 @@ exports.viewSourceInStyleEditor = Task.a * * @return {Promise<boolean>} */ exports.viewSourceInDebugger = Task.async(function* (toolbox, sourceURL, sourceLine) { // If the Debugger was already open, switch to it and try to show the // source immediately. Otherwise, initialize it and wait for the sources // to be added first. let debuggerAlreadyOpen = toolbox.getPanel("jsdebugger"); - let { panelWin: dbg } = yield toolbox.loadTool("jsdebugger"); + let dbg = yield toolbox.loadTool("jsdebugger"); // New debugger frontend if (Services.prefs.getBoolPref("devtools.debugger.new-debugger-frontend")) { yield toolbox.selectTool("jsdebugger"); - // TODO: Properly handle case where source will never exist in the - // debugger - dbg.actions.selectSourceURL(sourceURL, { line: sourceLine }); - return true; + const source = dbg._selectors().getSourceByURL(dbg._getState(), sourceURL); + if (source) { + dbg._actions().selectSourceByURL(sourceURL, { line: sourceLine }); + return true; + } + + exports.viewSource(toolbox, sourceURL, sourceLine); + return false; } + const win = dbg.panelWin; + // Old debugger frontend if (!debuggerAlreadyOpen) { - yield dbg.DebuggerController.waitForSourcesLoaded(); + yield win.DebuggerController.waitForSourcesLoaded(); } - let { DebuggerView } = dbg; + let { DebuggerView } = win; let { Sources } = DebuggerView; let item = Sources.getItemForAttachment(a => a.source.url === sourceURL); if (item) { yield toolbox.selectTool("jsdebugger"); // Determine if the source has already finished loading. There's two cases // in which we need to wait for the source to be shown: // 1) The requested source is not yet selected and will be shown once it is // selected and loaded // 2) The requested source is selected BUT the source text is still loading. const { actor } = item.attachment.source; - const state = dbg.DebuggerController.getState(); + const state = win.DebuggerController.getState(); // (1) Is the source selected? const selected = state.sources.selectedSource; const isSelected = selected === actor; // (2) Has the source text finished loading? let isLoading = false; @@ -101,17 +107,17 @@ exports.viewSourceInDebugger = Task.asyn isLoading = sourceTextInfo && sourceTextInfo.loading; } // Select the requested source DebuggerView.setEditorLocation(actor, sourceLine, { noDebug: true }); // Wait for it to load if (!isSelected || isLoading) { - yield dbg.DebuggerController.waitForSourceShown(sourceURL); + yield win.DebuggerController.waitForSourceShown(sourceURL); } return true; } // If not found, still attempt to open in View Source exports.viewSource(toolbox, sourceURL, sourceLine); return false; });
--- a/devtools/client/webconsole/test/browser_webconsole_bug_646025_console_file_location.js +++ b/devtools/client/webconsole/test/browser_webconsole_bug_646025_console_file_location.js @@ -22,36 +22,36 @@ add_task(function* () { BrowserTestUtils.loadURI(gBrowser.selectedBrowser, TEST_URI2); yield waitForMessages({ webconsole: hud, messages: [{ text: "message for level log", category: CATEGORY_WEBDEV, severity: SEVERITY_LOG, - source: { url: "test-file-location.js", line: 6 }, + source: { url: "test-file-location.js", line: 8 }, }, { text: "message for level info", category: CATEGORY_WEBDEV, severity: SEVERITY_INFO, - source: { url: "test-file-location.js", line: 7 }, + source: { url: "test-file-location.js", line: 9 }, }, { text: "message for level warn", category: CATEGORY_WEBDEV, severity: SEVERITY_WARNING, - source: { url: "test-file-location.js", line: 8 }, + source: { url: "test-file-location.js", line: 10 }, }, { text: "message for level error", category: CATEGORY_WEBDEV, severity: SEVERITY_ERROR, - source: { url: "test-file-location.js", line: 9 }, + source: { url: "test-file-location.js", line: 11 }, }, { text: "message for level debug", category: CATEGORY_WEBDEV, severity: SEVERITY_LOG, - source: { url: "test-file-location.js", line: 10 }, + source: { url: "test-file-location.js", line: 12 }, }], }); });
--- a/devtools/client/webconsole/test/browser_webconsole_dont_navigate_on_doubleclick.js +++ b/devtools/client/webconsole/test/browser_webconsole_dont_navigate_on_doubleclick.js @@ -36,19 +36,19 @@ function test() { category: CATEGORY_NETWORK }] }); let networkEventMessage = messages[0].matched.values().next().value; let urlNode = networkEventMessage.querySelector(".url"); let deferred = promise.defer(); - urlNode.addEventListener("click", function onClick(aEvent) { + urlNode.addEventListener("click", function onClick(event) { urlNode.removeEventListener("click", onClick); - ok(aEvent.defaultPrevented, "The default action was prevented."); + ok(event.defaultPrevented, "The default action was prevented."); deferred.resolve(); }); EventUtils.synthesizeMouseAtCenter(urlNode, {clickCount: 2}, hud.iframeWindow); yield deferred.promise;
--- a/devtools/client/webconsole/test/browser_webconsole_view_source.js +++ b/devtools/client/webconsole/test/browser_webconsole_view_source.js @@ -8,23 +8,27 @@ // have their locations opened in Debugger, we need to test a security message in // order to have it opened in the standard View Source window. "use strict"; const TEST_URI = "https://example.com/browser/devtools/client/webconsole/" + "test/test-mixedcontent-securityerrors.html"; -// Force the old debugger UI since it's directly used (see Bug 1301705) -Services.prefs.setBoolPref("devtools.debugger.new-debugger-frontend", false); -registerCleanupFunction(function* () { +add_task(function* () { + yield actuallyTest(); +}); + +add_task(function* () { + Services.prefs.setBoolPref("devtools.debugger.new-debugger-frontend", false); + yield actuallyTest(); Services.prefs.clearUserPref("devtools.debugger.new-debugger-frontend"); }); -add_task(function* () { +var actuallyTest = Task.async(function*() { yield loadTab(TEST_URI); let hud = yield openConsole(null); info("console opened"); let [result] = yield waitForMessages({ webconsole: hud, messages: [{ text: "Blocked loading mixed active content",
--- a/devtools/client/webconsole/test/test-file-location.js +++ b/devtools/client/webconsole/test/test-file-location.js @@ -1,10 +1,12 @@ /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ /* vim: set ft=javascript ts=2 et sw=2 tw=80: */ /* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + console.log("message for level log"); console.info("message for level info"); console.warn("message for level warn"); console.error("message for level error"); console.debug("message for level debug");
--- a/devtools/server/actors/inspector.js +++ b/devtools/server/actors/inspector.js @@ -68,17 +68,17 @@ const { const {EyeDropper} = require("devtools/server/actors/highlighters/eye-dropper"); const { isAnonymous, isNativeAnonymous, isXBLAnonymous, isShadowAnonymous, getFrameElement } = require("devtools/shared/layout/utils"); -const {getLayoutChangesObserver, releaseLayoutChangesObserver} = require("devtools/server/actors/layout"); +const {getLayoutChangesObserver, releaseLayoutChangesObserver} = require("devtools/server/actors/reflow"); const nodeFilterConstants = require("devtools/shared/dom-node-filter-constants"); const {EventParsers} = require("devtools/server/event-parsers"); const {nodeSpec, nodeListSpec, walkerSpec, inspectorSpec} = require("devtools/shared/specs/inspector"); const FONT_FAMILY_PREVIEW_TEXT = "The quick brown fox jumps over the lazy dog"; const FONT_FAMILY_PREVIEW_TEXT_SIZE = 20; const PSEUDO_CLASSES = [":hover", ":active", ":focus"];
--- a/devtools/server/actors/moz.build +++ b/devtools/server/actors/moz.build @@ -32,28 +32,28 @@ DevToolsModules( 'eventlooplag.js', 'frame.js', 'framerate.js', 'gcli.js', 'heap-snapshot-file.js', 'highlighters.css', 'highlighters.js', 'inspector.js', - 'layout.js', 'memory.js', 'monitor.js', 'object.js', 'performance-entries.js', 'performance-recording.js', 'performance.js', 'preference.js', 'pretty-print-worker.js', 'process.js', 'profiler.js', 'promises.js', + 'reflow.js', 'root.js', 'script.js', 'settings.js', 'source.js', 'storage.js', 'string.js', 'styleeditor.js', 'styles.js',
rename from devtools/server/actors/layout.js rename to devtools/server/actors/reflow.js --- a/devtools/server/actors/layout.js +++ b/devtools/server/actors/reflow.js @@ -26,17 +26,17 @@ const {Ci} = require("chrome"); const {XPCOMUtils} = require("resource://gre/modules/XPCOMUtils.jsm"); const protocol = require("devtools/shared/protocol"); const {method, Arg} = protocol; const events = require("sdk/event/core"); const Heritage = require("sdk/core/heritage"); const EventEmitter = require("devtools/shared/event-emitter"); -const {reflowSpec} = require("devtools/shared/specs/layout"); +const {reflowSpec} = require("devtools/shared/specs/reflow"); /** * The reflow actor tracks reflows and emits events about them. */ var ReflowActor = exports.ReflowActor = protocol.ActorClassWithSpec(reflowSpec, { initialize: function (conn, tabActor) { protocol.Actor.prototype.initialize.call(this, conn);
--- a/devtools/server/main.js +++ b/devtools/server/main.js @@ -502,17 +502,17 @@ var DebuggerServer = { constructor: "FramerateActor", type: { tab: true } }); this.registerModule("devtools/server/actors/eventlooplag", { prefix: "eventLoopLag", constructor: "EventLoopLagActor", type: { tab: true } }); - this.registerModule("devtools/server/actors/layout", { + this.registerModule("devtools/server/actors/reflow", { prefix: "reflow", constructor: "ReflowActor", type: { tab: true } }); this.registerModule("devtools/server/actors/css-properties", { prefix: "cssProperties", constructor: "CssPropertiesActor", type: { tab: true }
--- a/devtools/server/tests/unit/test_layout-reflows-observer.js +++ b/devtools/server/tests/unit/test_layout-reflows-observer.js @@ -2,17 +2,17 @@ http://creativecommons.org/publicdomain/zero/1.0/ */ // Test the LayoutChangesObserver var { getLayoutChangesObserver, releaseLayoutChangesObserver, LayoutChangesObserver -} = require("devtools/server/actors/layout"); +} = require("devtools/server/actors/reflow"); // Override set/clearTimeout on LayoutChangesObserver to avoid depending on // time in this unit test. This means that LayoutChangesObserver.eventLoopTimer // will be the timeout callback instead of the timeout itself, so test cases // will need to execute it to fake a timeout LayoutChangesObserver.prototype._setTimeout = cb => cb; LayoutChangesObserver.prototype._clearTimeout = function () {};
--- a/devtools/shared/fronts/moz.build +++ b/devtools/shared/fronts/moz.build @@ -16,24 +16,24 @@ DevToolsModules( 'director-manager.js', 'director-registry.js', 'emulation.js', 'eventlooplag.js', 'framerate.js', 'gcli.js', 'highlighters.js', 'inspector.js', - 'layout.js', 'memory.js', 'performance-entries.js', 'performance-recording.js', 'performance.js', 'preference.js', 'profiler.js', 'promises.js', + 'reflow.js', 'settings.js', 'storage.js', 'string.js', 'styles.js', 'stylesheets.js', 'timeline.js', 'webaudio.js', 'webgl.js'
rename from devtools/shared/fronts/layout.js rename to devtools/shared/fronts/reflow.js --- a/devtools/shared/fronts/layout.js +++ b/devtools/shared/fronts/reflow.js @@ -1,14 +1,15 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + "use strict"; -const {reflowSpec} = require("devtools/shared/specs/layout"); +const {reflowSpec} = require("devtools/shared/specs/reflow"); const protocol = require("devtools/shared/protocol"); /** * Usage example of the reflow front: * * let front = ReflowFront(toolbox.target.client, toolbox.target.form); * front.on("reflows", this._onReflows); * front.start();
--- a/devtools/shared/layout/utils.js +++ b/devtools/shared/layout/utils.js @@ -2,17 +2,17 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; const { Ci, Cc } = require("chrome"); const nodeFilterConstants = require("devtools/shared/dom-node-filter-constants"); -loader.lazyRequireGetter(this, "setIgnoreLayoutChanges", "devtools/server/actors/layout", true); +loader.lazyRequireGetter(this, "setIgnoreLayoutChanges", "devtools/server/actors/reflow", true); exports.setIgnoreLayoutChanges = (...args) => this.setIgnoreLayoutChanges(...args); /** * Returns the `DOMWindowUtils` for the window given. * * @param {DOMWindow} win * @returns {DOMWindowUtils}
--- a/devtools/shared/specs/moz.build +++ b/devtools/shared/specs/moz.build @@ -20,25 +20,25 @@ DevToolsModules( 'environment.js', 'eventlooplag.js', 'frame.js', 'framerate.js', 'gcli.js', 'heap-snapshot-file.js', 'highlighters.js', 'inspector.js', - 'layout.js', 'memory.js', 'node.js', 'performance-entries.js', 'performance-recording.js', 'performance.js', 'preference.js', 'profiler.js', 'promises.js', + 'reflow.js', 'script.js', 'settings.js', 'source.js', 'storage.js', 'string.js', 'styleeditor.js', 'styles.js', 'stylesheets.js',
rename from devtools/shared/specs/layout.js rename to devtools/shared/specs/reflow.js --- a/devtools/shared/specs/layout.js +++ b/devtools/shared/specs/reflow.js @@ -1,11 +1,12 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + "use strict"; const {Arg, generateActorSpec} = require("devtools/shared/protocol"); const reflowSpec = generateActorSpec({ typeName: "reflow", events: {
--- a/dom/base/File.cpp +++ b/dom/base/File.cpp @@ -12,17 +12,16 @@ #include "nsContentUtils.h" #include "nsError.h" #include "nsICharsetDetector.h" #include "nsIConverterInputStream.h" #include "nsIDocument.h" #include "nsIFileStreams.h" #include "nsIInputStream.h" #include "nsIIPCSerializableInputStream.h" -#include "nsIMemoryReporter.h" #include "nsIMIMEService.h" #include "nsISeekableStream.h" #include "nsIUnicharInputStream.h" #include "nsIUnicodeDecoder.h" #include "nsIRemoteBlob.h" #include "nsNetCID.h" #include "nsNetUtil.h" #include "nsIUUIDGenerator.h" @@ -187,18 +186,18 @@ Blob::Create(nsISupports* aParent, const MOZ_ASSERT(!blob->mImpl->IsFile()); return blob.forget(); } /* static */ already_AddRefed<Blob> Blob::CreateStringBlob(nsISupports* aParent, const nsACString& aData, const nsAString& aContentType) { - RefPtr<Blob> blob = Blob::Create(aParent, - new BlobImplString(aData, aContentType)); + RefPtr<BlobImpl> blobImpl = BlobImplString::Create(aData, aContentType); + RefPtr<Blob> blob = Blob::Create(aParent, blobImpl); MOZ_ASSERT(!blob->mImpl->IsFile()); return blob.forget(); } /* static */ already_AddRefed<Blob> Blob::CreateMemoryBlob(nsISupports* aParent, void* aMemoryBuffer, uint64_t aLength, const nsAString& aContentType) { @@ -1046,17 +1045,37 @@ EmptyBlobImpl::GetInternalStream(nsIInpu aRv.Throw(rv); return; } } //////////////////////////////////////////////////////////////////////////// // BlobImplString implementation -NS_IMPL_ISUPPORTS_INHERITED0(BlobImplString, BlobImpl) +NS_IMPL_ISUPPORTS_INHERITED(BlobImplString, BlobImpl, nsIMemoryReporter) + +/* static */ already_AddRefed<BlobImplString> +BlobImplString::Create(const nsACString& aData, const nsAString& aContentType) +{ + RefPtr<BlobImplString> blobImpl = new BlobImplString(aData, aContentType); + RegisterWeakMemoryReporter(blobImpl); + return blobImpl.forget(); +} + +BlobImplString::BlobImplString(const nsACString& aData, + const nsAString& aContentType) + : BlobImplBase(aContentType, aData.Length()) + , mData(aData) +{ +} + +BlobImplString::~BlobImplString() +{ + UnregisterWeakMemoryReporter(this); +} already_AddRefed<BlobImpl> BlobImplString::CreateSlice(uint64_t aStart, uint64_t aLength, const nsAString& aContentType, ErrorResult& aRv) { RefPtr<BlobImpl> impl = new BlobImplString(Substring(mData, aStart, aLength), @@ -1065,16 +1084,27 @@ BlobImplString::CreateSlice(uint64_t aSt } void BlobImplString::GetInternalStream(nsIInputStream** aStream, ErrorResult& aRv) { aRv = NS_NewCStringInputStream(aStream, mData); } +NS_IMETHODIMP +BlobImplString::CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) +{ + MOZ_COLLECT_REPORT( + "explicit/dom/memory-file-data/string", KIND_HEAP, UNITS_BYTES, + mData.SizeOfExcludingThisIfUnshared(MallocSizeOf), + "Memory used to back a File/Blob based on a string."); + return NS_OK; +} + //////////////////////////////////////////////////////////////////////////// // BlobImplMemory implementation NS_IMPL_ISUPPORTS_INHERITED0(BlobImplMemory, BlobImpl) already_AddRefed<BlobImpl> BlobImplMemory::CreateSlice(uint64_t aStart, uint64_t aLength, const nsAString& aContentType,
--- a/dom/base/File.h +++ b/dom/base/File.h @@ -14,16 +14,17 @@ #include "mozilla/StaticMutex.h" #include "mozilla/StaticPtr.h" #include "mozilla/dom/BindingDeclarations.h" #include "mozilla/dom/Date.h" #include "nsCycleCollectionParticipant.h" #include "nsCOMPtr.h" #include "nsIDOMBlob.h" #include "nsIFile.h" +#include "nsIMemoryReporter.h" #include "nsIMutable.h" #include "nsIXMLHttpRequest.h" #include "nsString.h" #include "nsTemporaryFileInputStream.h" #include "nsWrapperCache.h" #include "nsWeakReference.h" class nsIFile; @@ -520,37 +521,42 @@ protected: uint64_t mLength; int64_t mLastModificationDate; const uint64_t mSerialNumber; }; class BlobImplString final : public BlobImplBase + , public nsIMemoryReporter { + MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf) + public: NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIMEMORYREPORTER - BlobImplString(const nsACString& aData, const nsAString& aContentType) - : BlobImplBase(aContentType, aData.Length()) - , mData(aData) - {} + static already_AddRefed<BlobImplString> + Create(const nsACString& aData, const nsAString& aContentType); virtual void GetInternalStream(nsIInputStream** aStream, ErrorResult& aRv) override; virtual already_AddRefed<BlobImpl> CreateSlice(uint64_t aStart, uint64_t aLength, const nsAString& aContentType, ErrorResult& aRv) override; private: - ~BlobImplString() {} + BlobImplString(const nsACString& aData, const nsAString& aContentType); + + ~BlobImplString(); nsCString mData; }; + /** * This class may be used off the main thread, and in particular, its * constructor and destructor may not run on the same thread. Be careful! */ class BlobImplMemory final : public BlobImplBase { public: NS_DECL_ISUPPORTS_INHERITED
--- a/dom/base/PostMessageEvent.cpp +++ b/dom/base/PostMessageEvent.cpp @@ -102,23 +102,29 @@ PostMessageEvent::Run() if (NS_WARN_IF(!targetPrin)) return NS_OK; // Note: This is contrary to the spec with respect to file: URLs, which // the spec groups into a single origin, but given we intentionally // don't do that in other places it seems better to hold the line for // now. Long-term, we want HTML5 to address this so that we can // be compliant while being safer. - if (!targetPrin->Equals(mProvidedPrincipal)) { + if (!BasePrincipal::Cast(targetPrin)->EqualsIgnoringAddonId(mProvidedPrincipal)) { nsAutoString providedOrigin, targetOrigin; nsresult rv = nsContentUtils::GetUTFOrigin(targetPrin, targetOrigin); NS_ENSURE_SUCCESS(rv, rv); rv = nsContentUtils::GetUTFOrigin(mProvidedPrincipal, providedOrigin); NS_ENSURE_SUCCESS(rv, rv); + MOZ_DIAGNOSTIC_ASSERT(providedOrigin != targetOrigin || + (BasePrincipal::Cast(mProvidedPrincipal)->OriginAttributesRef() == + BasePrincipal::Cast(targetPrin)->OriginAttributesRef()), + "Unexpected postMessage call to a window with mismatched " + "origin attributes"); + const char16_t* params[] = { providedOrigin.get(), targetOrigin.get() }; nsContentUtils::ReportToConsole(nsIScriptError::errorFlag, NS_LITERAL_CSTRING("DOM Window"), sourceDocument, nsContentUtils::eDOM_PROPERTIES, "TargetPrincipalDoesNotMatch", params, ArrayLength(params));
--- a/dom/base/nsFocusManager.cpp +++ b/dom/base/nsFocusManager.cpp @@ -2016,44 +2016,112 @@ public: nsCOMPtr<nsISupports> mTarget; RefPtr<nsPresContext> mContext; EventMessage mEventMessage; bool mWindowRaised; bool mIsRefocus; nsCOMPtr<EventTarget> mRelatedTarget; }; +class FocusInOutEvent : public Runnable +{ +public: + FocusInOutEvent(nsISupports* aTarget, EventMessage aEventMessage, + nsPresContext* aContext, + nsPIDOMWindowOuter* aOriginalFocusedWindow, + nsIContent* aOriginalFocusedContent, + EventTarget* aRelatedTarget) + : mTarget(aTarget) + , mContext(aContext) + , mEventMessage(aEventMessage) + , mOriginalFocusedWindow(aOriginalFocusedWindow) + , mOriginalFocusedContent(aOriginalFocusedContent) + , mRelatedTarget(aRelatedTarget) + { + } + + NS_IMETHOD Run() override + { + nsCOMPtr<nsIContent> originalWindowFocus = mOriginalFocusedWindow ? + mOriginalFocusedWindow->GetFocusedNode() : + nullptr; + // Blink does not check that focus is the same after blur, but WebKit does. + // Opt to follow Blink's behavior (see bug 687787). + if (mEventMessage == eFocusOut || + originalWindowFocus == mOriginalFocusedContent) { + InternalFocusEvent event(true, mEventMessage); + event.mFlags.mBubbles = true; + event.mFlags.mCancelable = false; + event.mRelatedTarget = mRelatedTarget; + return EventDispatcher::Dispatch(mTarget, mContext, &event); + } + return NS_OK; + } + + nsCOMPtr<nsISupports> mTarget; + RefPtr<nsPresContext> mContext; + EventMessage mEventMessage; + nsCOMPtr<nsPIDOMWindowOuter> mOriginalFocusedWindow; + nsCOMPtr<nsIContent> mOriginalFocusedContent; + nsCOMPtr<EventTarget> mRelatedTarget; +}; + static nsIDocument* GetDocumentHelper(EventTarget* aTarget) { nsCOMPtr<nsINode> node = do_QueryInterface(aTarget); if (!node) { nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(aTarget); return win ? win->GetExtantDoc() : nullptr; } return node->OwnerDoc(); } +void nsFocusManager::SendFocusInOrOutEvent(EventMessage aEventMessage, + nsIPresShell* aPresShell, + nsISupports* aTarget, + nsPIDOMWindowOuter* aCurrentFocusedWindow, + nsIContent* aCurrentFocusedContent, + EventTarget* aRelatedTarget) +{ + NS_ASSERTION(aEventMessage == eFocusIn || aEventMessage == eFocusOut, + "Wrong event type for SendFocusInOrOutEvent"); + + nsContentUtils::AddScriptRunner( + new FocusInOutEvent( + aTarget, + aEventMessage, + aPresShell->GetPresContext(), + aCurrentFocusedWindow, + aCurrentFocusedContent, + aRelatedTarget)); +} + void nsFocusManager::SendFocusOrBlurEvent(EventMessage aEventMessage, nsIPresShell* aPresShell, nsIDocument* aDocument, nsISupports* aTarget, uint32_t aFocusMethod, bool aWindowRaised, bool aIsRefocus, EventTarget* aRelatedTarget) { NS_ASSERTION(aEventMessage == eFocus || aEventMessage == eBlur, "Wrong event type for SendFocusOrBlurEvent"); nsCOMPtr<EventTarget> eventTarget = do_QueryInterface(aTarget); nsCOMPtr<nsIDocument> eventTargetDoc = GetDocumentHelper(eventTarget); nsCOMPtr<nsIDocument> relatedTargetDoc = GetDocumentHelper(aRelatedTarget); + nsCOMPtr<nsPIDOMWindowOuter> currentWindow = mFocusedWindow; + nsCOMPtr<nsPIDOMWindowInner> targetWindow = do_QueryInterface(aTarget); + nsCOMPtr<nsIDocument> targetDocument = do_QueryInterface(aTarget); + nsCOMPtr<nsIContent> currentFocusedContent = currentWindow ? + currentWindow->GetFocusedNode() : nullptr; // set aRelatedTarget to null if it's not in the same document as eventTarget if (eventTargetDoc != relatedTargetDoc) { aRelatedTarget = nullptr; } bool dontDispatchEvent = eventTargetDoc && nsContentUtils::IsUserFocusIgnored(eventTargetDoc); @@ -2094,16 +2162,29 @@ nsFocusManager::SendFocusOrBlurEvent(Eve } } #endif if (!dontDispatchEvent) { nsContentUtils::AddScriptRunner( new FocusBlurEvent(aTarget, aEventMessage, aPresShell->GetPresContext(), aWindowRaised, aIsRefocus, aRelatedTarget)); + + // Check that the target is not a window or document before firing + // focusin/focusout. Other browsers do not fire focusin/focusout on window, + // despite being required in the spec, so follow their behavior. + // + // As for document, we should not even fire focus/blur, but until then, we + // need this check. targetDocument should be removed once bug 1228802 is + // resolved. + if (!targetWindow && !targetDocument) { + EventMessage focusInOrOutMessage = aEventMessage == eFocus ? eFocusIn : eFocusOut; + SendFocusInOrOutEvent(focusInOrOutMessage, aPresShell, aTarget, + currentWindow, currentFocusedContent, aRelatedTarget); + } } } void nsFocusManager::ScrollIntoView(nsIPresShell* aPresShell, nsIContent* aContent, uint32_t aFlags) {
--- a/dom/base/nsFocusManager.h +++ b/dom/base/nsFocusManager.h @@ -292,16 +292,40 @@ protected: nsIDocument* aDocument, nsISupports* aTarget, uint32_t aFocusMethod, bool aWindowRaised, bool aIsRefocus = false, mozilla::dom::EventTarget* aRelatedTarget = nullptr); /** + * Send a focusin or focusout event + * + * aEventMessage should be either eFocusIn or eFocusOut. + * + * aTarget is the content the event will fire on (the object that gained + * focus for focusin, the object blurred for focusout). + * + * aCurrentFocusedWindow is the window focused before the focus/blur event + * was fired. + * + * aCurrentFocusedContent is the content focused before the focus/blur event + * was fired. + * + * aRelatedTarget is the content related to the event (the object + * losing focus for focusin, the object getting focus for focusout). + */ + void SendFocusInOrOutEvent(mozilla::EventMessage aEventMessage, + nsIPresShell* aPresShell, + nsISupports* aTarget, + nsPIDOMWindowOuter* aCurrentFocusedWindow, + nsIContent* aCurrentFocusedContent, + mozilla::dom::EventTarget* aRelatedTarget = nullptr); + + /** * Scrolls aContent into view unless the FLAG_NOSCROLL flag is set. */ void ScrollIntoView(nsIPresShell* aPresShell, nsIContent* aContent, uint32_t aFlags); /** * Raises the top-level window aWindow at the widget level.
--- a/dom/base/nsGkAtomList.h +++ b/dom/base/nsGkAtomList.h @@ -789,16 +789,18 @@ GK_ATOM(onenabled, "onenabled") GK_ATOM(onenterpincodereq, "onenterpincodereq") GK_ATOM(onemergencycbmodechange, "onemergencycbmodechange") GK_ATOM(onerror, "onerror") GK_ATOM(onevicted, "onevicted") GK_ATOM(onfailed, "onfailed") GK_ATOM(onfetch, "onfetch") GK_ATOM(onfinish, "onfinish") GK_ATOM(onfocus, "onfocus") +GK_ATOM(onfocusin, "onfocusin") +GK_ATOM(onfocusout, "onfocusout") GK_ATOM(onfrequencychange, "onfrequencychange") GK_ATOM(onfullscreenchange, "onfullscreenchange") GK_ATOM(onfullscreenerror, "onfullscreenerror") GK_ATOM(onspeakerforcedchange, "onspeakerforcedchange") GK_ATOM(onget, "onget") GK_ATOM(ongroupchange, "ongroupchange") GK_ATOM(onhashchange, "onhashchange") GK_ATOM(onheadphoneschange, "onheadphoneschange")
--- a/dom/base/nsGlobalWindow.cpp +++ b/dom/base/nsGlobalWindow.cpp @@ -1316,30 +1316,32 @@ nsGlobalWindow::nsGlobalWindow(nsGlobalW } // We could have failed the first time through trying // to create the entropy collector, so we should // try to get one until we succeed. gRefCnt++; - if (gRefCnt == 1) { + static bool sFirstTime = true; + if (sFirstTime) { Preferences::AddIntVarCache(&gMinTimeoutValue, "dom.min_timeout_value", DEFAULT_MIN_TIMEOUT_VALUE); Preferences::AddIntVarCache(&gMinBackgroundTimeoutValue, "dom.min_background_timeout_value", DEFAULT_MIN_BACKGROUND_TIMEOUT_VALUE); Preferences::AddBoolVarCache(&sIdleObserversAPIFuzzTimeDisabled, "dom.idle-observers-api.fuzz_time.disabled", false); Preferences::AddUintVarCache(&gThrottledIdlePeriodLength, "dom.idle_period.throttled_length", DEFAULT_THROTTLED_IDLE_PERIOD_LENGTH); + sFirstTime = false; } if (gDumpFile == nullptr) { const nsAdoptingCString& fname = Preferences::GetCString("browser.dom.window.dump.file"); if (!fname.IsEmpty()) { // if this fails to open, Dump() knows to just go to stdout // on null.
--- a/dom/cache/test/mochitest/browser_cache_pb_window.js +++ b/dom/cache/test/mochitest/browser_cache_pb_window.js @@ -69,14 +69,13 @@ function test() { privateWin.addEventListener('load', function() { Promise.all([ testMatch(privateWin), testHas(privateWin), testOpen(privateWin), testDelete(privateWin), testKeys(privateWin) ]).then(function() { - privateWin.close(); - finish(); + BrowserTestUtils.closeWindow(privateWin).then(finish); }); }); }); }
--- a/dom/canvas/CanvasRenderingContext2D.cpp +++ b/dom/canvas/CanvasRenderingContext2D.cpp @@ -4938,16 +4938,21 @@ CanvasRenderingContext2D::DrawImage(cons gfx::Rect bounds; if (NeedToCalculateBounds()) { bounds = gfx::Rect(aDx, aDy, aDw, aDh); bounds = mTarget->GetTransform().TransformBounds(bounds); } + if (!IsTargetValid()) { + gfxCriticalError() << "Unexpected invalid target in a Canvas2d."; + return; + } + if (srcSurf) { gfx::Rect sourceRect(aSx, aSy, aSw, aSh); if (element == mCanvasElement) { // srcSurf is a snapshot of mTarget. If we draw to mTarget now, we'll // trigger a COW copy of the whole canvas into srcSurf. That's a huge // waste if sourceRect doesn't cover the whole canvas. // We avoid copying the whole canvas by manually copying just the part // that we need.
--- a/dom/canvas/test/test_capture.html +++ b/dom/canvas/test/test_capture.html @@ -93,16 +93,29 @@ function checkDrawImageNotCleanRed() { .then(() => h.waitForPixelColor(vmanual, h.green, 0, "should still be green")) .then(() => h.requestFrame(vmanual)) .then(() => h.waitForPixelColorTimeout(vmanual, h.red, 0, 1000, "should not become red")) .catch(err => ok(false, "checkDrawImageNotCleanRed failed: ", err)) .then(() => drawing.stop()); } +function checkEndedOnStop() { + let promises = [vauto, vmanual, vrate].map(elem => { + elem.srcObject.getTracks()[0].stop(); + return new Promise(resolve => + elem.addEventListener("ended", function endedListener(event) { + ok(true, "Element " + elem.id + " ended."); + resolve(); + elem.removeEventListener("ended", endedListener); + })); + }); + return Promise.all(promises); +} + function finish() { ok(true, 'Test complete.'); SimpleTest.finish(); } function beginTest() { h = new CaptureStreamTestHelper2D(); @@ -112,16 +125,17 @@ function beginTest() { vrate = h.createAndAppendElement('video', 'vrate'); Promise.resolve() .then(checkDrawColorInitialRed) .then(checkDrawColorGreen) .then(checkRequestFrameOrderGuarantee) .then(checkDrawColorGreen) // Restore video elements to green. .then(checkDrawImageNotCleanRed) + .then(checkEndedOnStop) .then(finish); } SimpleTest.waitForExplicitFinish(); beginTest(); </script>
--- a/dom/canvas/test/webgl-mochitest/test_capture.html +++ b/dom/canvas/test/webgl-mochitest/test_capture.html @@ -110,16 +110,30 @@ function checkRequestFrameOrderGuarantee return Promise.resolve() .then(() => h.waitForPixelColor(vmanual, h.red, 0, "should still be red")) .then(() => h.drawColor(c, h.green)) // 1. Draw canvas green .then(() => h.requestFrame(vmanual)) // 2. Immediately request a frame .then(() => h.waitForPixelColor(vmanual, h.green, 0, "should become green after call order test")) } +function checkEndedOnStop() { + let promises = [vauto, vmanual, vrate].map(elem => { + elem.srcObject.getTracks()[0].stop(); + return new Promise(resolve => + elem.addEventListener("ended", function endedListener(event) { + ok(true, "Element " + elem.id + " ended."); + resolve(); + elem.removeEventListener("ended", endedListener); + })); + }); + return Promise.all(promises); +} + + function finish() { ok(true, 'Test complete.'); SimpleTest.finish(); } function beginTest() { h = new CaptureStreamTestHelperWebGL(); @@ -177,16 +191,17 @@ function beginTest() { // Run tests. Promise.resolve() .then(checkClearColorInitialRed) .then(checkDrawColorGreen) .then(checkClearColorRed) .then(checkRequestFrameOrderGuarantee) + .then(checkEndedOnStop) .then(finish); } SimpleTest.waitForExplicitFinish(); beginTest(); </script>
--- a/dom/events/ClipboardEvent.cpp +++ b/dom/events/ClipboardEvent.cpp @@ -48,16 +48,18 @@ ClipboardEvent::InitClipboardEvent(const return rv.StealNSResult(); } void ClipboardEvent::InitClipboardEvent(const nsAString& aType, bool aCanBubble, bool aCancelable, DataTransfer* aClipboardData) { + NS_ENSURE_TRUE_VOID(!mEvent->mFlags.mIsBeingDispatched); + Event::InitEvent(aType, aCanBubble, aCancelable); mEvent->AsClipboardEvent()->mClipboardData = aClipboardData; } already_AddRefed<ClipboardEvent> ClipboardEvent::Constructor(const GlobalObject& aGlobal, const nsAString& aType, const ClipboardEventInit& aParam,
--- a/dom/events/CommandEvent.cpp +++ b/dom/events/CommandEvent.cpp @@ -46,16 +46,18 @@ CommandEvent::GetCommand(nsAString& aCom } NS_IMETHODIMP CommandEvent::InitCommandEvent(const nsAString& aTypeArg, bool aCanBubbleArg, bool aCancelableArg, const nsAString& aCommand) { + NS_ENSURE_TRUE(!mEvent->mFlags.mIsBeingDispatched, NS_OK); + Event::InitEvent(aTypeArg, aCanBubbleArg, aCancelableArg); mEvent->AsCommandEvent()->mCommand = NS_Atomize(aCommand); return NS_OK; } } // namespace dom } // namespace mozilla
--- a/dom/events/CompositionEvent.cpp +++ b/dom/events/CompositionEvent.cpp @@ -59,16 +59,18 @@ CompositionEvent::GetLocale(nsAString& a void CompositionEvent::InitCompositionEvent(const nsAString& aType, bool aCanBubble, bool aCancelable, nsGlobalWindow* aView, const nsAString& aData, const nsAString& aLocale) { + NS_ENSURE_TRUE_VOID(!mEvent->mFlags.mIsBeingDispatched); + UIEvent::InitUIEvent(aType, aCanBubble, aCancelable, aView, 0); mData = aData; mLocale = aLocale; } void CompositionEvent::GetRanges(TextClauseArray& aRanges) {
--- a/dom/events/CustomEvent.cpp +++ b/dom/events/CustomEvent.cpp @@ -71,16 +71,18 @@ CustomEvent::WrapObjectInternal(JSContex } NS_IMETHODIMP CustomEvent::InitCustomEvent(const nsAString& aType, bool aCanBubble, bool aCancelable, nsIVariant* aDetail) { + NS_ENSURE_TRUE(!mEvent->mFlags.mIsBeingDispatched, NS_OK); + AutoJSAPI jsapi; NS_ENSURE_STATE(jsapi.Init(GetParentObject())); JSContext* cx = jsapi.cx(); JS::Rooted<JS::Value> detail(cx); if (!aDetail) { detail = JS::NullValue(); } else if (NS_WARN_IF(!VariantToJsval(cx, aDetail, &detail))) { @@ -97,16 +99,18 @@ CustomEvent::InitCustomEvent(const nsASt void CustomEvent::InitCustomEvent(JSContext* aCx, const nsAString& aType, bool aCanBubble, bool aCancelable, JS::Handle<JS::Value> aDetail, ErrorResult& aRv) { + NS_ENSURE_TRUE_VOID(!mEvent->mFlags.mIsBeingDispatched); + Event::InitEvent(aType, aCanBubble, aCancelable); mDetail = aDetail; } NS_IMETHODIMP CustomEvent::GetDetail(nsIVariant** aDetail) { if (mDetail.isNull()) {
--- a/dom/events/DeviceMotionEvent.cpp +++ b/dom/events/DeviceMotionEvent.cpp @@ -46,16 +46,18 @@ DeviceMotionEvent::InitDeviceMotionEvent bool aCanBubble, bool aCancelable, const DeviceAccelerationInit& aAcceleration, const DeviceAccelerationInit& aAccelIncludingGravity, const DeviceRotationRateInit& aRotationRate, Nullable<double> aInterval, Nullable<uint64_t> aTimeStamp) { + NS_ENSURE_TRUE_VOID(!mEvent->mFlags.mIsBeingDispatched); + Event::InitEvent(aType, aCanBubble, aCancelable); mAcceleration = new DeviceAcceleration(this, aAcceleration.mX, aAcceleration.mY, aAcceleration.mZ); mAccelerationIncludingGravity = new DeviceAcceleration(this, aAccelIncludingGravity.mX,
--- a/dom/events/DragEvent.cpp +++ b/dom/events/DragEvent.cpp @@ -50,16 +50,18 @@ DragEvent::InitDragEvent(const nsAString bool aCtrlKey, bool aAltKey, bool aShiftKey, bool aMetaKey, uint16_t aButton, EventTarget* aRelatedTarget, DataTransfer* aDataTransfer) { + NS_ENSURE_TRUE_VOID(!mEvent->mFlags.mIsBeingDispatched); + MouseEvent::InitMouseEvent(aType, aCanBubble, aCancelable, aView, aDetail, aScreenX, aScreenY, aClientX, aClientY, aCtrlKey, aAltKey, aShiftKey, aMetaKey, aButton, aRelatedTarget); if (mEventIsInternal) { mEvent->AsDragEvent()->mDataTransfer = aDataTransfer; } }
--- a/dom/events/EventNameList.h +++ b/dom/events/EventNameList.h @@ -498,16 +498,24 @@ FORWARDED_EVENT(blur, ERROR_EVENT(error, eLoadError, EventNameType_All, eBasicEventClass) FORWARDED_EVENT(focus, eFocus, EventNameType_HTMLXUL, eFocusEventClass) +FORWARDED_EVENT(focusin, + eFocusIn, + EventNameType_HTMLXUL, + eFocusEventClass) +FORWARDED_EVENT(focusout, + eFocusOut, + EventNameType_HTMLXUL, + eFocusEventClass) FORWARDED_EVENT(load, eLoad, EventNameType_All, eBasicEventClass) FORWARDED_EVENT(resize, eResize, EventNameType_All, eBasicEventClass)
--- a/dom/events/FocusEvent.cpp +++ b/dom/events/FocusEvent.cpp @@ -44,16 +44,18 @@ FocusEvent::GetRelatedTarget() void FocusEvent::InitFocusEvent(const nsAString& aType, bool aCanBubble, bool aCancelable, nsGlobalWindow* aView, int32_t aDetail, EventTarget* aRelatedTarget) { + MOZ_ASSERT(!mEvent->mFlags.mIsBeingDispatched); + UIEvent::InitUIEvent(aType, aCanBubble, aCancelable, aView, aDetail); mEvent->AsFocusEvent()->mRelatedTarget = aRelatedTarget; } already_AddRefed<FocusEvent> FocusEvent::Constructor(const GlobalObject& aGlobal, const nsAString& aType, const FocusEventInit& aParam,
--- a/dom/events/KeyboardEvent.cpp +++ b/dom/events/KeyboardEvent.cpp @@ -332,16 +332,18 @@ KeyboardEvent::InitKeyEvent(const nsAStr mozIDOMWindow* aView, bool aCtrlKey, bool aAltKey, bool aShiftKey, bool aMetaKey, uint32_t aKeyCode, uint32_t aCharCode) { + NS_ENSURE_TRUE(!mEvent->mFlags.mIsBeingDispatched, NS_OK); + UIEvent::InitUIEvent(aType, aCanBubble, aCancelable, aView, 0); WidgetKeyboardEvent* keyEvent = mEvent->AsKeyboardEvent(); keyEvent->InitBasicModifiers(aCtrlKey, aAltKey, aShiftKey, aMetaKey); keyEvent->mKeyCode = aKeyCode; keyEvent->mCharCode = aCharCode; return NS_OK;
--- a/dom/events/MessageEvent.cpp +++ b/dom/events/MessageEvent.cpp @@ -141,16 +141,18 @@ void MessageEvent::InitMessageEvent(JSContext* aCx, const nsAString& aType, bool aCanBubble, bool aCancelable, JS::Handle<JS::Value> aData, const nsAString& aOrigin, const nsAString& aLastEventId, const Nullable<WindowProxyOrMessagePort>& aSource, const Sequence<OwningNonNull<MessagePort>>& aPorts) { + NS_ENSURE_TRUE_VOID(!mEvent->mFlags.mIsBeingDispatched); + Event::InitEvent(aType, aCanBubble, aCancelable); mData = aData; mozilla::HoldJSObjects(this); mOrigin = aOrigin; mLastEventId = aLastEventId; mWindowSource = nullptr; mPortSource = nullptr;
--- a/dom/events/MouseEvent.cpp +++ b/dom/events/MouseEvent.cpp @@ -62,16 +62,18 @@ MouseEvent::InitMouseEvent(const nsAStri int32_t aClientY, bool aCtrlKey, bool aAltKey, bool aShiftKey, bool aMetaKey, uint16_t aButton, EventTarget* aRelatedTarget) { + NS_ENSURE_TRUE_VOID(!mEvent->mFlags.mIsBeingDispatched); + UIEvent::InitUIEvent(aType, aCanBubble, aCancelable, aView, aDetail); switch(mEvent->mClass) { case eMouseEventClass: case eMouseScrollEventClass: case eWheelEventClass: case eDragEventClass: case ePointerEventClass: @@ -133,16 +135,18 @@ MouseEvent::InitMouseEvent(const nsAStri int32_t aScreenX, int32_t aScreenY, int32_t aClientX, int32_t aClientY, int16_t aButton, EventTarget* aRelatedTarget, const nsAString& aModifiersList) { + NS_ENSURE_TRUE_VOID(!mEvent->mFlags.mIsBeingDispatched); + Modifiers modifiers = ComputeModifierState(aModifiersList); InitMouseEvent(aType, aCanBubble, aCancelable, aView, aDetail, aScreenX, aScreenY, aClientX, aClientY, (modifiers & MODIFIER_CONTROL) != 0, (modifiers & MODIFIER_ALT) != 0, (modifiers & MODIFIER_SHIFT) != 0, (modifiers & MODIFIER_META) != 0, @@ -205,16 +209,18 @@ MouseEvent::InitNSMouseEvent(const nsASt bool aAltKey, bool aShiftKey, bool aMetaKey, uint16_t aButton, EventTarget* aRelatedTarget, float aPressure, uint16_t aInputSource) { + NS_ENSURE_TRUE_VOID(!mEvent->mFlags.mIsBeingDispatched); + MouseEvent::InitMouseEvent(aType, aCanBubble, aCancelable, aView, aDetail, aScreenX, aScreenY, aClientX, aClientY, aCtrlKey, aAltKey, aShiftKey, aMetaKey, aButton, aRelatedTarget); WidgetMouseEventBase* mouseEventBase = mEvent->AsMouseEventBase(); mouseEventBase->pressure = aPressure;
--- a/dom/events/MouseScrollEvent.cpp +++ b/dom/events/MouseScrollEvent.cpp @@ -51,16 +51,18 @@ MouseScrollEvent::InitMouseScrollEvent(c bool aCtrlKey, bool aAltKey, bool aShiftKey, bool aMetaKey, uint16_t aButton, EventTarget* aRelatedTarget, int32_t aAxis) { + NS_ENSURE_TRUE_VOID(!mEvent->mFlags.mIsBeingDispatched); + MouseEvent::InitMouseEvent(aType, aCanBubble, aCancelable, aView, aDetail, aScreenX, aScreenY, aClientX, aClientY, aCtrlKey, aAltKey, aShiftKey, aMetaKey, aButton, aRelatedTarget); mEvent->AsMouseScrollEvent()->mIsHorizontal = (aAxis == nsIDOMMouseScrollEvent::HORIZONTAL_AXIS); }
--- a/dom/events/MutationEvent.cpp +++ b/dom/events/MutationEvent.cpp @@ -91,16 +91,18 @@ MutationEvent::InitMutationEvent(const n bool aCanBubbleArg, bool aCancelableArg, nsIDOMNode* aRelatedNodeArg, const nsAString& aPrevValueArg, const nsAString& aNewValueArg, const nsAString& aAttrNameArg, uint16_t aAttrChangeArg) { + NS_ENSURE_TRUE(!mEvent->mFlags.mIsBeingDispatched, NS_OK); + Event::InitEvent(aTypeArg, aCanBubbleArg, aCancelableArg); InternalMutationEvent* mutation = mEvent->AsMutationEvent(); mutation->mRelatedNode = aRelatedNodeArg; if (!aPrevValueArg.IsEmpty()) mutation->mPrevAttrValue = NS_Atomize(aPrevValueArg); if (!aNewValueArg.IsEmpty()) mutation->mNewAttrValue = NS_Atomize(aNewValueArg);
--- a/dom/events/ScrollAreaEvent.cpp +++ b/dom/events/ScrollAreaEvent.cpp @@ -34,16 +34,18 @@ ScrollAreaEvent::InitScrollAreaEvent(con bool aCancelable, nsGlobalWindow* aView, int32_t aDetail, float aX, float aY, float aWidth, float aHeight) { + NS_ENSURE_TRUE_VOID(!mEvent->mFlags.mIsBeingDispatched); + UIEvent::InitUIEvent(aEventType, aCanBubble, aCancelable, aView, aDetail); mClientArea->SetRect(aX, aY, aWidth, aHeight); } NS_IMETHODIMP_(void) ScrollAreaEvent::Serialize(IPC::Message* aMsg, bool aSerializeInterfaceType) {
--- a/dom/events/SimpleGestureEvent.cpp +++ b/dom/events/SimpleGestureEvent.cpp @@ -119,16 +119,18 @@ SimpleGestureEvent::InitSimpleGestureEve bool aMetaKeyArg, uint16_t aButton, EventTarget* aRelatedTarget, uint32_t aAllowedDirectionsArg, uint32_t aDirectionArg, double aDeltaArg, uint32_t aClickCountArg) { + NS_ENSURE_TRUE_VOID(!mEvent->mFlags.mIsBeingDispatched); + MouseEvent::InitMouseEvent(aTypeArg, aCanBubbleArg, aCancelableArg, aViewArg, aDetailArg, aScreenX, aScreenY, aClientX, aClientY, aCtrlKeyArg, aAltKeyArg, aShiftKeyArg, aMetaKeyArg, aButton, aRelatedTarget); WidgetSimpleGestureEvent* simpleGestureEvent = mEvent->AsSimpleGestureEvent(); simpleGestureEvent->mAllowedDirections = aAllowedDirectionsArg;
--- a/dom/events/StorageEvent.cpp +++ b/dom/events/StorageEvent.cpp @@ -82,16 +82,18 @@ StorageEvent::Constructor(const GlobalOb void StorageEvent::InitStorageEvent(const nsAString& aType, bool aCanBubble, bool aCancelable, const nsAString& aKey, const nsAString& aOldValue, const nsAString& aNewValue, const nsAString& aURL, DOMStorage* aStorageArea) { + NS_ENSURE_TRUE_VOID(!mEvent->mFlags.mIsBeingDispatched); + InitEvent(aType, aCanBubble, aCancelable); mKey = aKey; mOldValue = aOldValue; mNewValue = aNewValue; mUrl = aURL; mStorageArea = aStorageArea; }
--- a/dom/events/TouchEvent.cpp +++ b/dom/events/TouchEvent.cpp @@ -89,16 +89,18 @@ TouchEvent::InitTouchEvent(const nsAStri bool aCtrlKey, bool aAltKey, bool aShiftKey, bool aMetaKey, TouchList* aTouches, TouchList* aTargetTouches, TouchList* aChangedTouches) { + NS_ENSURE_TRUE_VOID(!mEvent->mFlags.mIsBeingDispatched); + UIEvent::InitUIEvent(aType, aCanBubble, aCancelable, aView, aDetail); mEvent->AsInputEvent()->InitBasicModifiers(aCtrlKey, aAltKey, aShiftKey, aMetaKey); mTouches = aTouches; mTargetTouches = aTargetTouches; mChangedTouches = aChangedTouches; }
--- a/dom/events/UIEvent.cpp +++ b/dom/events/UIEvent.cpp @@ -164,16 +164,18 @@ UIEvent::InitUIEvent(const nsAString& ty NS_IMETHODIMP UIEvent::InitUIEvent(const nsAString& typeArg, bool canBubbleArg, bool cancelableArg, mozIDOMWindow* viewArg, int32_t detailArg) { + NS_ENSURE_TRUE(!mEvent->mFlags.mIsBeingDispatched, NS_OK); + Event::InitEvent(typeArg, canBubbleArg, cancelableArg); mDetail = detailArg; mView = viewArg ? nsPIDOMWindowInner::From(viewArg)->GetOuterWindow() : nullptr; return NS_OK; }
--- a/dom/events/WheelEvent.cpp +++ b/dom/events/WheelEvent.cpp @@ -55,16 +55,18 @@ WheelEvent::InitWheelEvent(const nsAStri uint16_t aButton, EventTarget* aRelatedTarget, const nsAString& aModifiersList, double aDeltaX, double aDeltaY, double aDeltaZ, uint32_t aDeltaMode) { + NS_ENSURE_TRUE_VOID(!mEvent->mFlags.mIsBeingDispatched); + MouseEvent::InitMouseEvent(aType, aCanBubble, aCancelable, aView, aDetail, aScreenX, aScreenY, aClientX, aClientY, aButton, aRelatedTarget, aModifiersList); WidgetWheelEvent* wheelEvent = mEvent->AsWheelEvent(); wheelEvent->mDeltaX = aDeltaX; wheelEvent->mDeltaY = aDeltaY; wheelEvent->mDeltaZ = aDeltaZ;
--- a/dom/events/XULCommandEvent.cpp +++ b/dom/events/XULCommandEvent.cpp @@ -108,16 +108,18 @@ XULCommandEvent::InitCommandEvent(const mozIDOMWindow* aView, int32_t aDetail, bool aCtrlKey, bool aAltKey, bool aShiftKey, bool aMetaKey, nsIDOMEvent* aSourceEvent) { + NS_ENSURE_TRUE(!mEvent->mFlags.mIsBeingDispatched, NS_OK); + auto* view = nsGlobalWindow::Cast(nsPIDOMWindowInner::From(aView)); UIEvent::InitUIEvent(aType, aCanBubble, aCancelable, view, aDetail); mEvent->AsInputEvent()->InitBasicModifiers(aCtrlKey, aAltKey, aShiftKey, aMetaKey); mSourceEvent = aSourceEvent; return NS_OK;
--- a/dom/events/test/mochitest.ini +++ b/dom/events/test/mochitest.ini @@ -197,8 +197,9 @@ support-files = [test_bug1013412.html] skip-if = buildapp == 'b2g' # no wheel events on b2g [test_dom_activate_event.html] [test_bug1264380.html] run-if = (e10s && os != "win") # Bug 1270043, crash at windows platforms; Bug1264380 comment 20, nsDragService::InvokeDragSessionImpl behaves differently among platform implementations in non-e10s mode which prevents us to check the validity of nsIDragService::getCurrentSession() consistently via synthesize mouse clicks in non-e10s mode. [test_passive_listeners.html] [test_paste_image.html] [test_messageEvent_init.html] +[test_bug687787.html]
new file mode 100644 --- /dev/null +++ b/dom/events/test/test_bug687787.html @@ -0,0 +1,617 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=687787 +--> +<head> + <title>Test for Bug 687787</title> + <script type="text/javascript" src="/MochiKit/MochiKit.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=687787">Mozilla Bug 687787</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> + +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var content = document.getElementById('content'); +var eventStack = []; + +function _callback(e){ + var event = {'type' : e.type, 'target' : e.target, 'relatedTarget' : e.relatedTarget } + eventStack.push(event); +} + +function clearEventStack(){ + eventStack = []; +} + +window.addEventListener("focus", _callback, true); +window.addEventListener("focusin", _callback, true); +window.addEventListener("focusout", _callback, true); +window.addEventListener("blur", _callback, true); + +function CompareEventToExpected(e, expected) { + if (expected == null || e == null) + return false; + if (e.type == expected.type && e.target == expected.target && e.relatedTarget == expected.relatedTarget) + return true; + return false; +} + +function TestEventOrderNormal() { + + var input1 = document.createElement('input'); + var input2 = document.createElement('input'); + var input3 = document.createElement('input'); + var content = document.getElementById('content'); + + input1.setAttribute('id', 'input1'); + input2.setAttribute('id', 'input2'); + input3.setAttribute('id', 'input3'); + input1.setAttribute('type', 'text'); + input2.setAttribute('type', 'text'); + input3.setAttribute('type', 'text'); + + content.appendChild(input1); + content.appendChild(input2); + content.appendChild(input3); + content.style.display = 'block' + + expectedEventOrder = [ + {'type' : 'blur', + 'target' : input1, + 'relatedTarget' : input2}, + {'type' : 'focusout', + 'target' : input1, + 'relatedTarget' : input2}, + {'type' : 'focus', + 'target' : input2, + 'relatedTarget' : input1}, + {'type' : 'focusin', + 'target' : input2, + 'relatedTarget' : input1}, + {'type' : 'blur', + 'target' : input2, + 'relatedTarget' : input3}, + {'type' : 'focusout', + 'target' : input2, + 'relatedTarget' : input3}, + {'type' : 'focus', + 'target' : input3, + 'relatedTarget' : input2}, + {'type' : 'focusin', + 'target' : input3, + 'relatedTarget' : input2}, + ] + + input1.focus(); + clearEventStack(); + + input2.focus(); + input3.focus(); + + for (var i = 0; i < expectedEventOrder.length || i < eventStack.length ; i++) { + ok(CompareEventToExpected(expectedEventOrder[i], eventStack[i]), 'Normal event order is correct: Event ' + i + ': ' + + 'Expected (' + + expectedEventOrder[i].type + ',' + + (expectedEventOrder[i].target ? expectedEventOrder[i].target.id : null) + ',' + + (expectedEventOrder[i].relatedTarget ? expectedEventOrder[i].relatedTarget.id : null) + '), ' + + 'Actual (' + + eventStack[i].type + ',' + + (eventStack[i].target ? eventStack[i].target.id : null) + ',' + + (eventStack[i].relatedTarget ? eventStack[i].relatedTarget.id : null) + ')'); + } + + content.innerHTML = ''; +} + +function TestEventOrderNormalFiresAtRightTime() { + + var input1 = document.createElement('input'); + var input2 = document.createElement('input'); + var input3 = document.createElement('input'); + var content = document.getElementById('content'); + + input1.setAttribute('id', 'input1'); + input2.setAttribute('id', 'input2'); + input3.setAttribute('id', 'input3'); + input1.setAttribute('type', 'text'); + input2.setAttribute('type', 'text'); + input3.setAttribute('type', 'text'); + + input1.onblur = function(e) + { + ok(document.activeElement == document.body, 'input1: not focused when blur fires') + } + + input1.addEventListener('focusout', function(e) + { + ok(document.activeElement == document.body, 'input1: not focused when focusout fires') + }); + + input2.onfocus = function(e) + { + ok(document.activeElement == input2, 'input2: focused when focus fires') + } + + input2.addEventListener('focusin', function(e) + { + ok(document.activeElement == input2, 'input2: focused when focusin fires') + }); + + content.appendChild(input1); + content.appendChild(input2); + content.style.display = 'block' + + expectedEventOrder = [ + {'type' : 'blur', + 'target' : input1, + 'relatedTarget' : input2}, + {'type' : 'focusout', + 'target' : input1, + 'relatedTarget' : input2}, + {'type' : 'focus', + 'target' : input2, + 'relatedTarget' : input1}, + {'type' : 'focusin', + 'target' : input2, + 'relatedTarget' : input1}, + ] + + input1.focus(); + clearEventStack(); + + input2.focus(); + + for (var i = 0; i < expectedEventOrder.length || i < eventStack.length ; i++) { + ok(CompareEventToExpected(expectedEventOrder[i], eventStack[i]), 'Normal event order is correct: Event ' + i + ': ' + + 'Expected (' + + expectedEventOrder[i].type + ',' + + (expectedEventOrder[i].target ? expectedEventOrder[i].target.id : null) + ',' + + (expectedEventOrder[i].relatedTarget ? expectedEventOrder[i].relatedTarget.id : null) + '), ' + + 'Actual (' + + eventStack[i].type + ',' + + (eventStack[i].target ? eventStack[i].target.id : null) + ',' + + (eventStack[i].relatedTarget ? eventStack[i].relatedTarget.id : null) + ')'); + } + + content.innerHTML = ''; +} + +function TestFocusOutRedirectsFocus() { + + var input1 = document.createElement('input'); + var input2 = document.createElement('input'); + var input3 = document.createElement('input'); + var content = document.getElementById('content'); + + input1.setAttribute('id', 'input1'); + input2.setAttribute('id', 'input2'); + input3.setAttribute('id', 'input3'); + input1.setAttribute('type', 'text'); + input2.setAttribute('type', 'text'); + input3.setAttribute('type', 'text'); + input1.addEventListener('focusout', function () { + input3.focus(); + }); + + content.appendChild(input1); + content.appendChild(input2); + content.appendChild(input3); + content.style.display = 'block' + + expectedEventOrder = [ + {'type' : 'blur', + 'target' : input1, + 'relatedTarget' : input2}, + {'type' : 'focusout', + 'target' : input1, + 'relatedTarget' : input2}, + {'type' : 'focus', + 'target' : input3, + 'relatedTarget' : null}, + {'type' : 'focusin', + 'target' : input3, + 'relatedTarget' : null}, + ] + + input1.focus(); + clearEventStack(); + input2.focus(); + + for (var i = 0; i < expectedEventOrder.length || i < eventStack.length; i++) { + ok(CompareEventToExpected(expectedEventOrder[i], eventStack[i]), 'Normal event order is correct: Event ' + i + ': ' + + 'Expected (' + + expectedEventOrder[i].type + ',' + + (expectedEventOrder[i].target ? expectedEventOrder[i].target.id : null) + ',' + + (expectedEventOrder[i].relatedTarget ? expectedEventOrder[i].relatedTarget.id : null) + '), ' + + 'Actual (' + + eventStack[i].type + ',' + + (eventStack[i].target ? eventStack[i].target.id : null) + ',' + + (eventStack[i].relatedTarget ? eventStack[i].relatedTarget.id : null) + ')'); + } + + content.innerHTML = ''; +} + +function TestFocusInRedirectsFocus() { + + var input1 = document.createElement('input'); + var input2 = document.createElement('input'); + var input3 = document.createElement('input'); + var content = document.getElementById('content'); + + input1.setAttribute('id', 'input1'); + input2.setAttribute('id', 'input2'); + input3.setAttribute('id', 'input3'); + input1.setAttribute('type', 'text'); + input2.setAttribute('type', 'text'); + input3.setAttribute('type', 'text'); + input2.addEventListener('focusin', function () { + input3.focus(); + }); + + content.appendChild(input1); + content.appendChild(input2); + content.appendChild(input3); + content.style.display = 'block' + + expectedEventOrder = [ + {'type' : 'blur', + 'target' : input1, + 'relatedTarget' : input2}, + {'type' : 'focusout', + 'target' : input1, + 'relatedTarget' : input2}, + {'type' : 'focus', + 'target' : input2, + 'relatedTarget' : input1}, + {'type' : 'focusin', + 'target' : input2, + 'relatedTarget' : input1}, + {'type' : 'blur', + 'target' : input2, + 'relatedTarget' : input3}, + {'type' : 'focusout', + 'target' : input2, + 'relatedTarget' : input3}, + {'type' : 'focus', + 'target' : input3, + 'relatedTarget' : input2}, + {'type' : 'focusin', + 'target' : input3, + 'relatedTarget' : input2}, + ] + + input1.focus(); + clearEventStack(); + input2.focus(); + + for (var i = 0; i < expectedEventOrder.length || i < eventStack.length; i++) { + ok(CompareEventToExpected(expectedEventOrder[i], eventStack[i]), 'Normal event order is correct: Event ' + i + ': ' + + 'Expected (' + + expectedEventOrder[i].type + ',' + + (expectedEventOrder[i].target ? expectedEventOrder[i].target.id : null) + ',' + + (expectedEventOrder[i].relatedTarget ? expectedEventOrder[i].relatedTarget.id : null) + '), ' + + 'Actual (' + + eventStack[i].type + ',' + + (eventStack[i].target ? eventStack[i].target.id : null) + ',' + + (eventStack[i].relatedTarget ? eventStack[i].relatedTarget.id : null) + ')'); + } + + content.innerHTML = ''; +} + +function TestBlurRedirectsFocus() { + + var input1 = document.createElement('input'); + var input2 = document.createElement('input'); + var input3 = document.createElement('input'); + var content = document.getElementById('content'); + + input1.setAttribute('id', 'input1'); + input2.setAttribute('id', 'input2'); + input3.setAttribute('id', 'input3'); + input1.setAttribute('type', 'text'); + input2.setAttribute('type', 'text'); + input3.setAttribute('type', 'text'); + input1.onblur = function () { + input3.focus(); + } + + content.appendChild(input1); + content.appendChild(input2); + content.appendChild(input3); + content.style.display = 'block' + + expectedEventOrder = [ + {'type' : 'blur', + 'target' : input1, + 'relatedTarget' : input2}, + {'type' : 'focus', + 'target' : input3, + 'relatedTarget' : null}, + {'type' : 'focusin', + 'target' : input3, + 'relatedTarget' : null}, + {'type' : 'focusout', + 'target' : input1, + 'relatedTarget' : input2}, + ] + + input1.focus(); + clearEventStack(); + input2.focus(); + + for (var i = 0; i < expectedEventOrder.length || i < eventStack.length; i++) { + ok(CompareEventToExpected(expectedEventOrder[i], eventStack[i]), 'Normal event order is correct: Event ' + i + ': ' + + 'Expected (' + + expectedEventOrder[i].type + ',' + + (expectedEventOrder[i].target ? expectedEventOrder[i].target.id : null) + ',' + + (expectedEventOrder[i].relatedTarget ? expectedEventOrder[i].relatedTarget.id : null) + '), ' + + 'Actual (' + + eventStack[i].type + ',' + + (eventStack[i].target ? eventStack[i].target.id : null) + ',' + + (eventStack[i].relatedTarget ? eventStack[i].relatedTarget.id : null) + ')'); + } + + content.innerHTML = ''; +} + +function TestFocusRedirectsFocus() { + + var input1 = document.createElement('input'); + var input2 = document.createElement('input'); + var input3 = document.createElement('input'); + var content = document.getElementById('content'); + + input1.setAttribute('id', 'input1'); + input2.setAttribute('id', 'input2'); + input3.setAttribute('id', 'input3'); + input1.setAttribute('type', 'text'); + input2.setAttribute('type', 'text'); + input3.setAttribute('type', 'text'); + input2.onfocus = function () { + input3.focus(); + } + + content.appendChild(input1); + content.appendChild(input2); + content.appendChild(input3); + content.style.display = 'block' + + expectedEventOrder = [ + {'type' : 'blur', + 'target' : input1, + 'relatedTarget' : input2}, + {'type' : 'focusout', + 'target' : input1, + 'relatedTarget' : input2}, + {'type' : 'focus', + 'target' : input2, + 'relatedTarget' : input1}, + {'type' : 'blur', + 'target' : input2, + 'relatedTarget' : input3}, + {'type' : 'focusout', + 'target' : input2, + 'relatedTarget' : input3}, + {'type' : 'focus', + 'target' : input3, + 'relatedTarget' : input2}, + {'type' : 'focusin', + 'target' : input3, + 'relatedTarget' : input2}, + ] + + input1.focus(); + clearEventStack(); + input2.focus(); + + for (var i = 0; i < expectedEventOrder.length || i < eventStack.length; i++) { + ok(CompareEventToExpected(expectedEventOrder[i], eventStack[i]), 'Normal event order is correct: Event ' + i + ': ' + + 'Expected (' + + expectedEventOrder[i].type + ',' + + (expectedEventOrder[i].target ? expectedEventOrder[i].target.id : null) + ',' + + (expectedEventOrder[i].relatedTarget ? expectedEventOrder[i].relatedTarget.id : null) + '), ' + + 'Actual (' + + eventStack[i].type + ',' + + (eventStack[i].target ? eventStack[i].target.id : null) + ',' + + (eventStack[i].relatedTarget ? eventStack[i].relatedTarget.id : null) + ')'); + } + + content.innerHTML = ''; +} + +function TestEventOrderDifferentDocument() { + + var input1 = document.createElement('input'); + var input2 = document.createElement('input'); + var iframe1 = document.createElement('iframe'); + var content = document.getElementById('content'); + + input1.setAttribute('id', 'input1'); + input2.setAttribute('id', 'input2'); + iframe1.setAttribute('id', 'iframe1'); + input1.setAttribute('type', 'text'); + input2.setAttribute('type', 'text'); + + content.appendChild(input1); + content.appendChild(iframe1); + iframe1.contentDocument.body.appendChild(input2); + content.style.display = 'block' + + iframe1.contentDocument.addEventListener("focus", _callback, true); + iframe1.contentDocument.addEventListener("focusin", _callback, true); + iframe1.contentDocument.addEventListener("focusout", _callback, true); + iframe1.contentDocument.addEventListener("blur", _callback, true); + + expectedEventOrder = [ + {'type' : 'blur', + 'target' : input1, + 'relatedTarget' : null}, + {'type' : 'focusout', + 'target' : input1, + 'relatedTarget' : null}, + {'type' : 'blur', + 'target' : document, + 'relatedTarget' : null}, + {'type' : 'blur', + 'target' : window, + 'relatedTarget' : null}, + {'type' : 'focus', + 'target' : iframe1.contentDocument, + 'relatedTarget' : null}, + {'type' : 'focus', + 'target' : input2, + 'relatedTarget' : null}, + {'type' : 'focusin', + 'target' : input2, + 'relatedTarget' : null}, + ] + + input1.focus(); + clearEventStack(); + input2.focus(); + + for (var i = 0; i < expectedEventOrder.length || i < eventStack.length; i++) { + ok(CompareEventToExpected(expectedEventOrder[i], eventStack[i]), 'Normal event order is correct: Event ' + i + ': ' + + 'Expected (' + + expectedEventOrder[i].type + ',' + + (expectedEventOrder[i].target ? expectedEventOrder[i].target.id : null) + ',' + + (expectedEventOrder[i].relatedTarget ? expectedEventOrder[i].relatedTarget.id : null) + '), ' + + 'Actual (' + + eventStack[i].type + ',' + + (eventStack[i].target ? eventStack[i].target.id : null) + ',' + + (eventStack[i].relatedTarget ? eventStack[i].relatedTarget.id : null) + ')'); + } + + content.innerHTML = ''; +} + + +function TestFocusOutMovesTarget() { + + var input1 = document.createElement('input'); + var input2 = document.createElement('input'); + var iframe1 = document.createElement('iframe'); + var content = document.getElementById('content'); + + input1.setAttribute('id', 'input1'); + input2.setAttribute('id', 'input2'); + iframe1.setAttribute('id', 'iframe1'); + input1.setAttribute('type', 'text'); + input2.setAttribute('type', 'text'); + + input1.addEventListener('focusout', function () { + iframe1.contentDocument.body.appendChild(input2); + }); + + content.appendChild(input1); + content.appendChild(input2); + content.appendChild(iframe1); + content.style.display = 'block' + + iframe1.contentDocument.addEventListener("focus", _callback, true); + iframe1.contentDocument.addEventListener("focusin", _callback, true); + iframe1.contentDocument.addEventListener("focusout", _callback, true); + iframe1.contentDocument.addEventListener("blur", _callback, true); + + expectedEventOrder = [ + {'type' : 'blur', + 'target' : input1, + 'relatedTarget' : input2}, + {'type' : 'focusout', + 'target' : input1, + 'relatedTarget' : input2}, + {'type' : 'focus', + 'target' : input2, + 'relatedTarget' : null}, + {'type' : 'focusin', + 'target' : input2, + 'relatedTarget' : null}, + ] + + input1.focus(); + clearEventStack(); + input2.focus(); + + for (var i = 0; i < expectedEventOrder.length || i < eventStack.length; i++) { + ok(CompareEventToExpected(expectedEventOrder[i], eventStack[i]), 'Normal event order is correct: Event ' + i + ': ' + + 'Expected (' + + expectedEventOrder[i].type + ',' + + (expectedEventOrder[i].target ? expectedEventOrder[i].target.id : null) + ',' + + (expectedEventOrder[i].relatedTarget ? expectedEventOrder[i].relatedTarget.id : null) + '), ' + + 'Actual (' + + eventStack[i].type + ',' + + (eventStack[i].target ? eventStack[i].target.id : null) + ',' + + (eventStack[i].relatedTarget ? eventStack[i].relatedTarget.id : null) + ')'); + } + + content.innerHTML = ''; +} + +function TestBlurWindowAndRefocusInputOnlyFiresFocusInOnInput() { + + var input1 = document.createElement('input'); + var content = document.getElementById('content'); + + input1.setAttribute('id', 'input1'); + input1.setAttribute('type', 'text'); + + content.appendChild(input1); + + expectedEventOrder = [ + {'type' : 'focus', + 'target' : document, + 'relatedTarget' : null}, + {'type' : 'focus', + 'target' : window, + 'relatedTarget' : null}, + {'type' : 'focus', + 'target' : input1, + 'relatedTarget' : null}, + {'type' : 'focusin', + 'target' : input1, + 'relatedTarget' : null}, + ] + + window.blur(); + clearEventStack(); + input1.focus(); + + for (var i = 0; i < expectedEventOrder.length || i < eventStack.length; i++) { + ok(CompareEventToExpected(expectedEventOrder[i], eventStack[i]), 'Normal event order is correct: Event ' + i + ': ' + + 'Expected (' + + expectedEventOrder[i].type + ',' + + (expectedEventOrder[i].target ? expectedEventOrder[i].target.id : null) + ',' + + (expectedEventOrder[i].relatedTarget ? expectedEventOrder[i].relatedTarget.id : null) + '), ' + + 'Actual (' + + eventStack[i].type + ',' + + (eventStack[i].target ? eventStack[i].target.id : null) + ',' + + (eventStack[i].relatedTarget ? eventStack[i].relatedTarget.id : null) + ')'); + } + + content.innerHTML = ''; +} + +TestEventOrderNormal(); +TestEventOrderNormalFiresAtRightTime(); +TestFocusOutRedirectsFocus(); +TestFocusInRedirectsFocus(); +TestBlurRedirectsFocus(); +TestFocusRedirectsFocus(); +TestFocusOutMovesTarget(); +TestEventOrderDifferentDocument(); +TestBlurWindowAndRefocusInputOnlyFiresFocusInOnInput(); + +</script> +</pre> +</body> +</html>
--- a/dom/html/HTMLCanvasElement.cpp +++ b/dom/html/HTMLCanvasElement.cpp @@ -115,16 +115,18 @@ public: if (mOwningElement->IsWriteOnly()) { return; } if (mOwningElement->IsContextCleanForFrameCapture()) { return; } + mOwningElement->ProcessDestroyedFrameListeners(); + if (!mOwningElement->IsFrameCaptureRequested()) { return; } RefPtr<SourceSurface> snapshot = mOwningElement->GetSurfaceSnapshot(nullptr); if (!snapshot) { return; } @@ -645,16 +647,59 @@ PrintCallback* HTMLCanvasElement::GetMozPrintCallback() const { if (mOriginalCanvas) { return mOriginalCanvas->GetMozPrintCallback(); } return mPrintCallback; } +class CanvasCaptureTrackSource : public MediaStreamTrackSource +{ +public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(CanvasCaptureTrackSource, + MediaStreamTrackSource) + + CanvasCaptureTrackSource(nsIPrincipal* aPrincipal, + CanvasCaptureMediaStream* aCaptureStream) + : MediaStreamTrackSource(aPrincipal, nsString()) + , mCaptureStream(aCaptureStream) {} + + MediaSourceEnum GetMediaSource() const override + { + return MediaSourceEnum::Other; + } + + void Stop() override + { + if (!mCaptureStream) { + NS_ERROR("No stream"); + return; + } + + mCaptureStream->StopCapture(); + } + +private: + virtual ~CanvasCaptureTrackSource() {} + + RefPtr<CanvasCaptureMediaStream> mCaptureStream; +}; + +NS_IMPL_ADDREF_INHERITED(CanvasCaptureTrackSource, + MediaStreamTrackSource) +NS_IMPL_RELEASE_INHERITED(CanvasCaptureTrackSource, + MediaStreamTrackSource) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(CanvasCaptureTrackSource) +NS_INTERFACE_MAP_END_INHERITING(MediaStreamTrackSource) +NS_IMPL_CYCLE_COLLECTION_INHERITED(CanvasCaptureTrackSource, + MediaStreamTrackSource, + mCaptureStream) + already_AddRefed<CanvasCaptureMediaStream> HTMLCanvasElement::CaptureStream(const Optional<double>& aFrameRate, ErrorResult& aRv) { if (IsWriteOnly()) { aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); return nullptr; } @@ -683,17 +728,17 @@ HTMLCanvasElement::CaptureStream(const O stream->Init(aFrameRate, videoTrackId, principal); if (NS_FAILED(rv)) { aRv.Throw(rv); return nullptr; } RefPtr<MediaStreamTrack> track = stream->CreateDOMTrack(videoTrackId, MediaSegment::VIDEO, - new BasicUnstoppableTrackSource(principal)); + new CanvasCaptureTrackSource(principal, stream)); stream->AddTrackInternal(track); rv = RegisterFrameCaptureListener(stream->FrameCaptureListener()); if (NS_FAILED(rv)) { aRv.Throw(rv); return nullptr; } @@ -1194,39 +1239,49 @@ HTMLCanvasElement::IsFrameCaptureRequest if (listener->FrameCaptureRequested()) { return true; } } return false; } void -HTMLCanvasElement::SetFrameCapture(already_AddRefed<SourceSurface> aSurface) +HTMLCanvasElement::ProcessDestroyedFrameListeners() { - RefPtr<SourceSurface> surface = aSurface; - RefPtr<SourceSurfaceImage> image = new SourceSurfaceImage(surface->GetSize(), surface); - // Loop backwards to allow removing elements in the loop. for (int i = mRequestedFrameListeners.Length() - 1; i >= 0; --i) { WeakPtr<FrameCaptureListener> listener = mRequestedFrameListeners[i]; if (!listener) { // listener was destroyed. Remove it from the list. mRequestedFrameListeners.RemoveElementAt(i); continue; } - - RefPtr<Image> imageRefCopy = image.get(); - listener->NewFrame(imageRefCopy.forget()); } if (mRequestedFrameListeners.IsEmpty()) { mRequestedFrameRefreshObserver->Unregister(); } } +void +HTMLCanvasElement::SetFrameCapture(already_AddRefed<SourceSurface> aSurface) +{ + RefPtr<SourceSurface> surface = aSurface; + RefPtr<SourceSurfaceImage> image = new SourceSurfaceImage(surface->GetSize(), surface); + + for (WeakPtr<FrameCaptureListener> listener : mRequestedFrameListeners) { + if (!listener) { + continue; + } + + RefPtr<Image> imageRefCopy = image.get(); + listener->NewFrame(imageRefCopy.forget()); + } +} + already_AddRefed<SourceSurface> HTMLCanvasElement::GetSurfaceSnapshot(bool* aPremultAlpha) { if (!mCurrentContext) return nullptr; return mCurrentContext->GetSurfaceSnapshot(aPremultAlpha); }
--- a/dom/html/HTMLCanvasElement.h +++ b/dom/html/HTMLCanvasElement.h @@ -272,16 +272,22 @@ public: /* * Returns true when there is at least one registered FrameCaptureListener * that has requested a frame capture. */ bool IsFrameCaptureRequested() const; /* + * Processes destroyed FrameCaptureListeners and removes them if necessary. + * Should there be none left, the FrameRefreshObserver will be unregistered. + */ + void ProcessDestroyedFrameListeners(); + + /* * Called by the RefreshDriver hook when a frame has been captured. * Makes a copy of the provided surface and hands it to all * FrameCaptureListeners having requested frame capture. */ void SetFrameCapture(already_AddRefed<gfx::SourceSurface> aSurface); virtual bool ParseAttribute(int32_t aNamespaceID, nsIAtom* aAttribute,
--- a/dom/html/HTMLMediaElement.cpp +++ b/dom/html/HTMLMediaElement.cpp @@ -863,21 +863,18 @@ NS_IMETHODIMP HTMLMediaElement::GetMozAu *aAutoplayEnabled = mAutoplayEnabled; return NS_OK; } bool HTMLMediaElement::Ended() { - if (MediaStream* stream = GetSrcMediaStream()) { - return stream->IsFinished(); - } - - return mDecoder && mDecoder->IsEnded(); + return (mDecoder && mDecoder->IsEnded()) || + (mSrcStream && !mSrcStream->Active()); } NS_IMETHODIMP HTMLMediaElement::GetEnded(bool* aEnded) { *aEnded = Ended(); return NS_OK; } @@ -1357,17 +1354,17 @@ void HTMLMediaElement::NotifyMediaTrackD // for instance if the MediaInputPort was destroyed in the same // iteration as it was added. MediaStreamTrack* outputTrack = ms.mStream->FindOwnedDOMTrack( ms.mTrackPorts[i].second()->GetDestination(), ms.mTrackPorts[i].second()->GetDestinationTrackId()); MOZ_ASSERT(outputTrack); if (outputTrack) { NS_DispatchToMainThread( - NewRunnableMethod(outputTrack, &MediaStreamTrack::NotifyEnded)); + NewRunnableMethod(outputTrack, &MediaStreamTrack::OverrideEnded)); } ms.mTrackPorts[i].second()->Destroy(); ms.mTrackPorts.RemoveElementAt(i); break; } } #ifdef DEBUG @@ -1395,16 +1392,45 @@ void HTMLMediaElement::NotifyMediaStream // We are a video element and HasVideo() changed so update the screen // wakelock NotifyOwnerDocumentActivityChanged(); } mWatchManager.ManualNotify(&HTMLMediaElement::UpdateReadyStateInternal); } +void +HTMLMediaElement::NotifyOutputTrackStopped(DOMMediaStream* aOwningStream, + TrackID aDestinationTrackID) +{ + for (OutputMediaStream& ms : mOutputStreams) { + if (!ms.mCapturingMediaStream) { + continue; + } + + if (ms.mStream != aOwningStream) { + continue; + } + + for (int32_t i = ms.mTrackPorts.Length() - 1; i >= 0; --i) { + MediaInputPort* port = ms.mTrackPorts[i].second(); + if (port->GetDestinationTrackId() != aDestinationTrackID) { + continue; + } + + port->Destroy(); + ms.mTrackPorts.RemoveElementAt(i); + return; + } + } + + // An output track ended but its port is already gone. + // It was probably cleared by the removal of the source MediaTrack. +} + void HTMLMediaElement::LoadFromSourceChildren() { NS_ASSERTION(mDelayingLoadEvent, "Should delay load event (if in document) during load"); NS_ASSERTION(mIsLoadingFromSourceChildren, "Must remember we're loading from source children"); nsIDocument* parentDoc = OwnerDoc()->GetParentDocument(); @@ -2201,90 +2227,98 @@ class HTMLMediaElement::StreamCaptureTra public MediaStreamTrackSource, public MediaStreamTrackSource::Sink { public: NS_DECL_ISUPPORTS_INHERITED NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(StreamCaptureTrackSource, MediaStreamTrackSource) - explicit StreamCaptureTrackSource(MediaStreamTrackSource* aCapturedTrackSource) + StreamCaptureTrackSource(HTMLMediaElement* aElement, + MediaStreamTrackSource* aCapturedTrackSource, + DOMMediaStream* aOwningStream, + TrackID aDestinationTrackID) : MediaStreamTrackSource(aCapturedTrackSource->GetPrincipal(), - true, nsString()) + , mElement(aElement) , mCapturedTrackSource(aCapturedTrackSource) + , mOwningStream(aOwningStream) + , mDestinationTrackID(aDestinationTrackID) { } void Destroy() override { - MOZ_ASSERT(mCapturedTrackSource); + if (mCapturedTrackSource) { + mCapturedTrackSource->UnregisterSink(this); + mCapturedTrackSource = nullptr; + } } MediaSourceEnum GetMediaSource() const override { return MediaSourceEnum::Other; } CORSMode GetCORSMode() const override { return mCapturedTrackSource->GetCORSMode(); } - already_AddRefed<PledgeVoid> - ApplyConstraints(nsPIDOMWindowInner* aWindow, - const dom::MediaTrackConstraints& aConstraints) override - { - RefPtr<PledgeVoid> p = new PledgeVoid(); - p->Reject(new dom::MediaStreamError(aWindow, - NS_LITERAL_STRING("OverconstrainedError"), - NS_LITERAL_STRING(""))); - return p.forget(); - } - void Stop() override { - NS_ERROR("We're reporting remote=true to not be stoppable. " - "Stop() should not be called."); + if (mElement && mElement->mSrcStream) { + // Only notify if we're still playing the source stream. GC might have + // cleared it before the track sources. + mElement->NotifyOutputTrackStopped(mOwningStream, mDestinationTrackID); + } + mElement = nullptr; + mOwningStream = nullptr; + + Destroy(); } void PrincipalChanged() override { mPrincipal = mCapturedTrackSource->GetPrincipal(); MediaStreamTrackSource::PrincipalChanged(); } private: virtual ~StreamCaptureTrackSource() {} + RefPtr<HTMLMediaElement> mElement; RefPtr<MediaStreamTrackSource> mCapturedTrackSource; + RefPtr<DOMMediaStream> mOwningStream; + TrackID mDestinationTrackID; }; NS_IMPL_ADDREF_INHERITED(HTMLMediaElement::StreamCaptureTrackSource, MediaStreamTrackSource) NS_IMPL_RELEASE_INHERITED(HTMLMediaElement::StreamCaptureTrackSource, MediaStreamTrackSource) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(HTMLMediaElement::StreamCaptureTrackSource) NS_INTERFACE_MAP_END_INHERITING(MediaStreamTrackSource) NS_IMPL_CYCLE_COLLECTION_INHERITED(HTMLMediaElement::StreamCaptureTrackSource, MediaStreamTrackSource, - mCapturedTrackSource) + mElement, + mCapturedTrackSource, + mOwningStream) class HTMLMediaElement::DecoderCaptureTrackSource : public MediaStreamTrackSource, public DecoderPrincipalChangeObserver { public: NS_DECL_ISUPPORTS_INHERITED NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(DecoderCaptureTrackSource, MediaStreamTrackSource) explicit DecoderCaptureTrackSource(HTMLMediaElement* aElement) : MediaStreamTrackSource(nsCOMPtr<nsIPrincipal>(aElement->GetCurrentPrincipal()).get(), - true, nsString()) , mElement(aElement) { MOZ_ASSERT(mElement); mElement->AddDecoderPrincipalChangeObserver(this); } void Destroy() override @@ -2300,31 +2334,22 @@ public: return MediaSourceEnum::Other; } CORSMode GetCORSMode() const override { return mElement->GetCORSMode(); } - already_AddRefed<PledgeVoid> - ApplyConstraints(nsPIDOMWindowInner* aWindow, - const dom::MediaTrackConstraints& aConstraints) override - { - RefPtr<PledgeVoid> p = new PledgeVoid(); - p->Reject(new dom::MediaStreamError(aWindow, - NS_LITERAL_STRING("OverconstrainedError"), - NS_LITERAL_STRING(""))); - return p.forget(); - } - void Stop() override { - NS_ERROR("We're reporting remote=true to not be stoppable. " - "Stop() should not be called."); + // We don't notify the source that a track was stopped since it will keep + // producing tracks until the element ends. The decoder also needs the + // tracks it created to be live at the source since the decoder's clock is + // based on MediaStreams during capture. } void NotifyDecoderPrincipalChanged() override { nsCOMPtr<nsIPrincipal> newPrincipal = mElement->GetCurrentPrincipal(); if (nsContentUtils::CombineResourcePrincipals(&mPrincipal, newPrincipal)) { PrincipalChanged(); } @@ -2457,17 +2482,20 @@ HTMLMediaElement::AddCaptureMediaTrackTo for (auto pair : aOutputStream.mTrackPorts) { MOZ_ASSERT(pair.first() != aTrack->GetId(), "Captured track already captured to output stream"); } #endif TrackID destinationTrackID = aOutputStream.mNextAvailableTrackID++; RefPtr<MediaStreamTrackSource> source = - new StreamCaptureTrackSource(&inputTrack->GetSource()); + new StreamCaptureTrackSource(this, + &inputTrack->GetSource(), + aOutputStream.mStream, + destinationTrackID); MediaSegment::Type type = inputTrack->AsAudioStreamTrack() ? MediaSegment::AUDIO : MediaSegment::VIDEO; RefPtr<MediaStreamTrack> track = aOutputStream.mStream->CreateDOMTrack(destinationTrackID, type, source); @@ -2521,16 +2549,17 @@ HTMLMediaElement::CaptureStreamInternal( if (!mOutputStreams.IsEmpty() && aGraph != mOutputStreams[0].mStream->GetInputStream()->Graph()) { return nullptr; } OutputMediaStream* out = mOutputStreams.AppendElement(); MediaStreamTrackSourceGetter* getter = new CaptureStreamTrackSourceGetter(this); out->mStream = DOMMediaStream::CreateTrackUnionStreamAsInput(window, aGraph, getter); + out->mStream->SetInactiveOnFinish(); out->mFinishWhenEnded = aFinishWhenEnded; out->mCapturingAudioOnly = aCaptureAudio; if (aCaptureAudio) { if (mSrcStream) { // We don't support applying volume and mute to the captured stream, when // capturing a MediaStream. nsContentUtils::ReportToConsole(nsIScriptError::errorFlag, @@ -3865,35 +3894,23 @@ public: WatchTarget(aName), mElement(aElement), mHaveCurrentData(false), mBlocked(false), mFinished(false), mMutex(aName), mPendingNotifyOutput(false) {} - void Forget() { mElement = nullptr; } + void Forget() + { + mElement = nullptr; + NotifyWatchers(); + } // Main thread - void DoNotifyFinished() - { - mFinished = true; - if (mElement) { - RefPtr<HTMLMediaElement> deathGrip = mElement; - - // Update NextFrameStatus() to move to NEXT_FRAME_UNAVAILABLE and - // HAVE_CURRENT_DATA. - mElement = nullptr; - // NotifyWatchers before calling PlaybackEnded since PlaybackEnded - // can remove watchers. - NotifyWatchers(); - - deathGrip->PlaybackEnded(); - } - } MediaDecoderOwner::NextFrameStatus NextFrameStatus() { if (!mElement || !mHaveCurrentData || mFinished) { return MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE; } return mBlocked ? MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE_BUFFERING @@ -3939,25 +3956,16 @@ public: nsCOMPtr<nsIRunnable> event; if (aBlocked == BLOCKED) { event = NewRunnableMethod(this, &StreamListener::DoNotifyBlocked); } else { event = NewRunnableMethod(this, &StreamListener::DoNotifyUnblocked); } aGraph->DispatchToMainThreadAfterStreamStateUpdate(event.forget()); } - virtual void NotifyEvent(MediaStreamGraph* aGraph, - MediaStreamGraphEvent event) override - { - if (event == MediaStreamGraphEvent::EVENT_FINISHED) { - nsCOMPtr<nsIRunnable> event = - NewRunnableMethod(this, &StreamListener::DoNotifyFinished); - aGraph->DispatchToMainThreadAfterStreamStateUpdate(event.forget()); - } - } virtual void NotifyHasCurrentData(MediaStreamGraph* aGraph) override { MutexAutoLock lock(mMutex); nsCOMPtr<nsIRunnable> event = NewRunnableMethod(this, &StreamListener::DoNotifyHaveCurrentData); aGraph->DispatchToMainThreadAfterStreamStateUpdate(event.forget()); } virtual void NotifyOutput(MediaStreamGraph* aGraph, @@ -4014,16 +4022,34 @@ public: mElement->NotifyMediaStreamTrackAdded(aTrack); } void NotifyTrackRemoved(const RefPtr<MediaStreamTrack>& aTrack) override { mElement->NotifyMediaStreamTrackRemoved(aTrack); } + void NotifyActive() override + { + LOG(LogLevel::Debug, ("%p, mSrcStream %p became active", + mElement, mElement->mSrcStream.get())); + mElement->CheckAutoplayDataReady(); + } + + void NotifyInactive() override + { + LOG(LogLevel::Debug, ("%p, mSrcStream %p became inactive", + mElement, mElement->mSrcStream.get())); + MOZ_ASSERT(!mElement->mSrcStream->Active()); + if (mElement->mMediaStreamListener) { + mElement->mMediaStreamListener->Forget(); + } + mElement->PlaybackEnded(); + } + protected: HTMLMediaElement* const mElement; }; void HTMLMediaElement::UpdateSrcMediaStreamPlaying(uint32_t aFlags) { if (!mSrcStream) { return; @@ -4242,20 +4268,20 @@ HTMLMediaElement::NotifyMediaStreamTrack this, aTrack->AsAudioStreamTrack() ? "Audio" : "Video", NS_ConvertUTF16toUTF8(id).get())); if (MediaTrack* t = AudioTracks()->GetTrackById(id)) { AudioTracks()->RemoveTrack(t); } else if (MediaTrack* t = VideoTracks()->GetTrackById(id)) { VideoTracks()->RemoveTrack(t); } else { - // XXX (bug 1208328) Uncomment this when DOMMediaStream doesn't call - // NotifyTrackRemoved multiple times for the same track, i.e., when it - // implements the "addtrack" and "removetrack" events. - // NS_ASSERTION(false, "MediaStreamTrack ended but did not exist in track lists"); + NS_ASSERTION(aTrack->AsVideoStreamTrack() && !IsVideo(), + "MediaStreamTrack ended but did not exist in track lists. " + "This is only allowed if a video element ends and we are an " + "audio element."); return; } } void HTMLMediaElement::ProcessMediaFragmentURI() { nsMediaFragmentURIParser parser(mLoadingSrc); @@ -4475,16 +4501,22 @@ void HTMLMediaElement::PlaybackEnded() if (HasAttr(kNameSpaceID_None, nsGkAtoms::loop)) { SetCurrentTime(0); return; } Pause(); + if (mSrcStream) { + // A MediaStream that goes from inactive to active shall be eligible for + // autoplay again according to the mediacapture-main spec. + mAutoplaying = true; + } + FireTimeUpdate(false); DispatchAsyncEvent(NS_LITERAL_STRING("ended")); } void HTMLMediaElement::SeekStarted() { DispatchAsyncEvent(NS_LITERAL_STRING("seeking")); } @@ -4916,19 +4948,20 @@ void HTMLMediaElement::ChangeNetworkStat // Changing mNetworkState affects AddRemoveSelfReference(). AddRemoveSelfReference(); } bool HTMLMediaElement::CanActivateAutoplay() { // For stream inputs, we activate autoplay on HAVE_NOTHING because // this element itself might be blocking the stream from making progress by - // being paused. We also activate autopaly when playing a media source since - // the data download is controlled by the script and there is no way to - // evaluate MediaDecoder::CanPlayThrough(). + // being paused. We only check that it has data by checking its active state. + // We also activate autoplay when playing a media source since the data + // download is controlled by the script and there is no way to evaluate + // MediaDecoder::CanPlayThrough(). if (!HasAttr(kNameSpaceID_None, nsGkAtoms::autoplay) || !mAutoplayEnabled) { return false; } if (!mAutoplaying) { return false; } @@ -4942,17 +4975,18 @@ bool HTMLMediaElement::CanActivateAutopl } if (mPausedForInactiveDocumentOrChannel) { return false; } bool hasData = (mDecoder && mReadyState >= nsIDOMHTMLMediaElement::HAVE_ENOUGH_DATA) || - mSrcStream || mMediaSource; + (mSrcStream && mSrcStream->Active()) || + mMediaSource; return hasData; } void HTMLMediaElement::CheckAutoplayDataReady() { if (!CanActivateAutoplay()) { return;
--- a/dom/html/HTMLMediaElement.h +++ b/dom/html/HTMLMediaElement.h @@ -350,16 +350,23 @@ public: */ void NotifyMediaTrackDisabled(MediaTrack* aTrack); /** * Called when tracks become available to the source media stream. */ void NotifyMediaStreamTracksAvailable(DOMMediaStream* aStream); + /** + * Called when a captured MediaStreamTrack is stopped so we can clean up its + * MediaInputPort. + */ + void NotifyOutputTrackStopped(DOMMediaStream* aOwningStream, + TrackID aDestinationTrackID); + virtual bool IsNodeOfType(uint32_t aFlags) const override; /** * Returns the current load ID. Asynchronous events store the ID that was * current when they were enqueued, and if it has changed when they come to * fire, they consider themselves cancelled, and don't fire. */ uint32_t GetCurrentLoadID() { return mCurrentLoadID; }
--- a/dom/html/nsGenericHTMLElement.h +++ b/dom/html/nsGenericHTMLElement.h @@ -967,17 +967,18 @@ protected: void SetHTMLBoolAttr(nsIAtom* aName, bool aValue, mozilla::ErrorResult& aError) { if (aValue) { SetHTMLAttr(aName, EmptyString(), aError); } else { UnsetHTMLAttr(aName, aError); } } - void SetHTMLIntAttr(nsIAtom* aName, int32_t aValue, mozilla::ErrorResult& aError) + template<typename T> + void SetHTMLIntAttr(nsIAtom* aName, T aValue, mozilla::ErrorResult& aError) { nsAutoString value; value.AppendInt(aValue); SetHTMLAttr(aName, value, aError); } /**
--- a/dom/indexedDB/IDBObjectStore.cpp +++ b/dom/indexedDB/IDBObjectStore.cpp @@ -1424,16 +1424,44 @@ IDBObjectStore::AddOrPut(JSContext* aCx, StructuredCloneWriteInfo cloneWriteInfo(mTransaction->Database()); nsTArray<IndexUpdateInfo> updateInfo; aRv = GetAddInfo(aCx, value, aKey, cloneWriteInfo, key, updateInfo); if (aRv.Failed()) { return nullptr; } + // Check the size limit of the serialized message which mainly consists of + // a StructuredCloneBuffer, an encoded object key, and the encoded index keys. + // kMaxIDBMsgOverhead covers the minor stuff not included in this calculation + // because the precise calculation would slow down this AddOrPut operation. + static const size_t kMaxIDBMsgOverhead = 1024 * 1024; // 1MB + const uint32_t maximalSizeFromPref = + IndexedDatabaseManager::MaxSerializedMsgSize(); + MOZ_ASSERT(maximalSizeFromPref > kMaxIDBMsgOverhead); + const size_t kMaxMessageSize = maximalSizeFromPref - kMaxIDBMsgOverhead; + + size_t indexUpdateInfoSize = 0; + for (size_t i = 0; i < updateInfo.Length(); i++) { + indexUpdateInfoSize += updateInfo[i].value().GetBuffer().Length(); + indexUpdateInfoSize += updateInfo[i].localizedValue().GetBuffer().Length(); + } + + size_t messageSize = cloneWriteInfo.mCloneBuffer.data().Size() + + key.GetBuffer().Length() + indexUpdateInfoSize; + + if (messageSize > kMaxMessageSize) { + IDB_REPORT_INTERNAL_ERR(); + aRv.ThrowDOMException(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR, + nsPrintfCString("The serialized value is too large" + " (size=%zu bytes, max=%zu bytes).", + messageSize, kMaxMessageSize)); + return nullptr; + } + ObjectStoreAddPutParams commonParams; commonParams.objectStoreId() = Id(); commonParams.cloneInfo().data().data = Move(cloneWriteInfo.mCloneBuffer.data()); commonParams.cloneInfo().offsetToKeyProp() = cloneWriteInfo.mOffsetToKeyProp; commonParams.key() = key; commonParams.indexUpdateInfos().SwapElements(updateInfo); // Convert any blobs or mutable files into FileAddInfo.
--- a/dom/indexedDB/IndexedDatabaseManager.cpp +++ b/dom/indexedDB/IndexedDatabaseManager.cpp @@ -1,16 +1,17 @@ /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* 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/. */ #include "IndexedDatabaseManager.h" +#include "chrome/common/ipc_channel.h" // for IPC::Channel::kMaximumMessageSize #include "nsIConsoleService.h" #include "nsIDiskSpaceWatcher.h" #include "nsIDOMWindow.h" #include "nsIEventTarget.h" #include "nsIFile.h" #include "nsIObserverService.h" #include "nsIScriptError.h" #include "nsIScriptGlobalObject.h" @@ -133,22 +134,26 @@ NS_DEFINE_IID(kIDBRequestIID, PRIVATE_ID const uint32_t kDeleteTimeoutMs = 1000; // The threshold we use for structured clone data storing. // Anything smaller than the threshold is compressed and stored in the database. // Anything larger is compressed and stored outside the database. const int32_t kDefaultDataThresholdBytes = 1024 * 1024; // 1MB +// The maximal size of a serialized object to be transfered through IPC. +const int32_t kDefaultMaxSerializedMsgSize = IPC::Channel::kMaximumMessageSize; + #define IDB_PREF_BRANCH_ROOT "dom.indexedDB." const char kTestingPref[] = IDB_PREF_BRANCH_ROOT "testing"; const char kPrefExperimental[] = IDB_PREF_BRANCH_ROOT "experimental"; const char kPrefFileHandle[] = "dom.fileHandle.enabled"; const char kDataThresholdPref[] = IDB_PREF_BRANCH_ROOT "dataThreshold"; +const char kPrefMaxSerilizedMsgSize[] = IDB_PREF_BRANCH_ROOT "maxSerializedMsgSize"; #define IDB_PREF_LOGGING_BRANCH_ROOT IDB_PREF_BRANCH_ROOT "logging." const char kPrefLoggingEnabled[] = IDB_PREF_LOGGING_BRANCH_ROOT "enabled"; const char kPrefLoggingDetails[] = IDB_PREF_LOGGING_BRANCH_ROOT "details"; #if defined(DEBUG) || defined(MOZ_ENABLE_PROFILER_SPS) const char kPrefLoggingProfiler[] = @@ -161,16 +166,17 @@ const char kPrefLoggingProfiler[] = StaticRefPtr<IndexedDatabaseManager> gDBManager; Atomic<bool> gInitialized(false); Atomic<bool> gClosed(false); Atomic<bool> gTestingMode(false); Atomic<bool> gExperimentalFeaturesEnabled(false); Atomic<bool> gFileHandleEnabled(false); Atomic<int32_t> gDataThresholdBytes(0); +Atomic<int32_t> gMaxSerializedMsgSize(0); class DeleteFilesRunnable final : public nsIRunnable , public OpenDirectoryListener { typedef mozilla::dom::quota::DirectoryLock DirectoryLock; enum State @@ -264,16 +270,28 @@ DataThresholdPrefChangedCallback(const c // The magic -1 is for use only by tests that depend on stable blob file id's. if (dataThresholdBytes == -1) { dataThresholdBytes = INT32_MAX; } gDataThresholdBytes = dataThresholdBytes; } +void +MaxSerializedMsgSizePrefChangeCallback(const char* aPrefName, void* aClosure) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!strcmp(aPrefName, kPrefMaxSerilizedMsgSize)); + MOZ_ASSERT(!aClosure); + + gMaxSerializedMsgSize = + Preferences::GetInt(aPrefName, kDefaultMaxSerializedMsgSize); + MOZ_ASSERT(gMaxSerializedMsgSize > 0); +} + } // namespace IndexedDatabaseManager::IndexedDatabaseManager() : mFileMutex("IndexedDatabaseManager.mFileMutex") , mBackgroundActor(nullptr) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); } @@ -404,16 +422,19 @@ IndexedDatabaseManager::Init() kPrefLoggingProfiler); #endif Preferences::RegisterCallbackAndCall(LoggingModePrefChangedCallback, kPrefLoggingEnabled); Preferences::RegisterCallbackAndCall(DataThresholdPrefChangedCallback, kDataThresholdPref); + Preferences::RegisterCallbackAndCall(MaxSerializedMsgSizePrefChangeCallback, + kPrefMaxSerilizedMsgSize); + #ifdef ENABLE_INTL_API const nsAdoptingCString& acceptLang = Preferences::GetLocalizedCString("intl.accept_languages"); // Split values on commas. nsCCharSeparatedTokenizer langTokenizer(acceptLang, ','); while (langTokenizer.hasMoreTokens()) { nsAutoCString lang(langTokenizer.nextToken()); @@ -467,16 +488,19 @@ IndexedDatabaseManager::Destroy() kPrefLoggingProfiler); #endif Preferences::UnregisterCallback(LoggingModePrefChangedCallback, kPrefLoggingEnabled); Preferences::UnregisterCallback(DataThresholdPrefChangedCallback, kDataThresholdPref); + Preferences::UnregisterCallback(MaxSerializedMsgSizePrefChangeCallback, + kPrefMaxSerilizedMsgSize); + delete this; } // static nsresult IndexedDatabaseManager::CommonPostHandleEvent(EventChainPostVisitor& aVisitor, IDBFactory* aFactory) { @@ -778,16 +802,27 @@ uint32_t IndexedDatabaseManager::DataThreshold() { MOZ_ASSERT(gDBManager, "DataThreshold() called before indexedDB has been initialized!"); return gDataThresholdBytes; } +// static +uint32_t +IndexedDatabaseManager::MaxSerializedMsgSize() +{ + MOZ_ASSERT(gDBManager, + "MaxSerializedMsgSize() called before indexedDB has been initialized!"); + MOZ_ASSERT(gMaxSerializedMsgSize > 0); + + return gMaxSerializedMsgSize; +} + void IndexedDatabaseManager::ClearBackgroundActor() { MOZ_ASSERT(NS_IsMainThread()); mBackgroundActor = nullptr; }
--- a/dom/indexedDB/IndexedDatabaseManager.h +++ b/dom/indexedDB/IndexedDatabaseManager.h @@ -129,16 +129,19 @@ public: ExperimentalFeaturesEnabled(JSContext* aCx, JSObject* aGlobal); static bool IsFileHandleEnabled(); static uint32_t DataThreshold(); + static uint32_t + MaxSerializedMsgSize(); + void ClearBackgroundActor(); void NoteLiveQuotaManager(QuotaManager* aQuotaManager); void NoteShuttingDownQuotaManager();
--- a/dom/indexedDB/test/mochitest.ini +++ b/dom/indexedDB/test/mochitest.ini @@ -62,16 +62,17 @@ support-files = unit/test_invalid_version.js unit/test_invalidate.js unit/test_key_requirements.js unit/test_keys.js unit/test_locale_aware_indexes.js unit/test_locale_aware_index_getAll.js unit/test_locale_aware_index_getAllObjects.js unit/test_lowDiskSpace.js + unit/test_maximal_serialized_object_size.js unit/test_multientry.js unit/test_names_sorted.js unit/test_objectCursors.js unit/test_objectStore_getAllKeys.js unit/test_objectStore_inline_autoincrement_key_added_on_put.js unit/test_objectStore_openKeyCursor.js unit/test_objectStore_remove_values.js unit/test_object_identity.js @@ -290,16 +291,17 @@ skip-if = true [test_key_requirements.html] skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Bug 931116 [test_keys.html] skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Bug 931116 [test_leaving_page.html] skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Bug 931116 [test_lowDiskSpace.html] skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Bug 931116 +[test_maximal_serialized_object_size.html] [test_message_manager_ipc.html] # This test is only supposed to run in the main process. skip-if = buildapp == 'b2g' || buildapp == 'mulet' || e10s [test_multientry.html] skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Bug 931116 [test_names_sorted.html] skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Bug 931116 [test_objectCursors.html]
new file mode 100644 --- /dev/null +++ b/dom/indexedDB/test/test_maximal_serialized_object_size.html @@ -0,0 +1,18 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<html> +<head> + <title>Test Maximal Size of a Serialized Object</title> + + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + + <script type="text/javascript;version=1.7" src="unit/test_maximal_serialized_object_size.js"></script> + <script type="text/javascript;version=1.7" src="helpers.js"></script> +</head> + +<body onload="runTest();"></body> + +</html>
new file mode 100644 --- /dev/null +++ b/dom/indexedDB/test/unit/test_maximal_serialized_object_size.js @@ -0,0 +1,95 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +var disableWorkerTest = "Need a way to set temporary prefs from a worker"; + +var testGenerator = testSteps(); + +function testSteps() +{ + const name = this.window ? + window.location.pathname : "test_maximal_serialized_object_size.js"; + const megaBytes = 1024 * 1024; + const kMessageOverhead = 1; // in MB + const kMaxIpcMessageSize = 20; // in MB + const kMaxIdbMessageSize = kMaxIpcMessageSize - kMessageOverhead; + + let chunks = new Array(kMaxIdbMessageSize); + for (let i = 0; i < kMaxIdbMessageSize; i++) { + chunks[i] = new ArrayBuffer(1 * megaBytes); + } + + if (this.window) { + SpecialPowers.pushPrefEnv( + { "set": [["dom.indexedDB.maxSerializedMsgSize", + kMaxIpcMessageSize * megaBytes ]] + }, + continueToNextStep + ); + yield undefined; + } else { + setMaxSerializedMsgSize(kMaxIpcMessageSize * megaBytes); + } + + let openRequest = indexedDB.open(name, 1); + openRequest.onerror = errorHandler; + openRequest.onupgradeneeded = grabEventAndContinueHandler; + openRequest.onsuccess = unexpectedSuccessHandler; + let event = yield undefined; + + let db = event.target.result; + let txn = event.target.transaction; + + is(db.objectStoreNames.length, 0, "Correct objectStoreNames list"); + + let objectStore = db.createObjectStore("test store", { keyPath: "id" }); + is(db.objectStoreNames.length, 1, "Correct objectStoreNames list"); + is(db.objectStoreNames.item(0), objectStore.name, "Correct object store name"); + + function testTooLargeError(aOperation, aObject) { + try { + objectStore[aOperation](aObject).onerror = errorHandler; + ok(false, "UnknownError is expected to be thrown!"); + } catch (e) { + ok(e instanceof DOMException, "got a DOM exception"); + is(e.name, "UnknownError", "correct error"); + ok(!!e.message, "Error message: " + e.message); + ok(e.message.startsWith("The serialized value is too large"), + "Correct error message prefix."); + } + } + + info("Verify IDBObjectStore.add() - object is too large"); + testTooLargeError("add", { id: 1, data: chunks }); + + info("Verify IDBObjectStore.add() - object size is closed to the maximal size."); + chunks.length = chunks.length - 1; + let request = objectStore.add({ id: 1, data: chunks }); + request.onerror = errorHandler; + request.onsuccess = grabEventAndContinueHandler; + yield undefined; + + info("Verify IDBObjectStore.add() - object key is too large"); + chunks.length = 10; + testTooLargeError("add", { id: chunks }); + + objectStore.createIndex("index name", "index"); + ok(objectStore.index("index name"), "Index created."); + + info("Verify IDBObjectStore.add() - index key is too large"); + testTooLargeError("add", { id: 2, index: chunks }); + + info("Verify IDBObjectStore.add() - object key and index key are too large"); + let indexChunks = chunks.splice(0, 5); + testTooLargeError("add", { id: chunks, index: indexChunks }); + + openRequest.onsuccess = continueToNextStep; + yield undefined; + + db.close(); + + finishTest(); + yield undefined; +}
--- a/dom/indexedDB/test/unit/xpcshell-head-parent-process.js +++ b/dom/indexedDB/test/unit/xpcshell-head-parent-process.js @@ -525,16 +525,22 @@ function setTemporaryStorageLimit(limit) } function setDataThreshold(threshold) { info("Setting data threshold to " + threshold); SpecialPowers.setIntPref("dom.indexedDB.dataThreshold", threshold); } +function setMaxSerializedMsgSize(aSize) +{ + info("Setting maximal size of a serialized message to " + aSize); + SpecialPowers.setIntPref("dom.indexedDB.maxSerializedMsgSize", aSize); +} + function getPrincipal(url) { let uri = Cc["@mozilla.org/network/io-service;1"] .getService(Ci.nsIIOService) .newURI(url, null, null); let ssm = Cc["@mozilla.org/scriptsecuritymanager;1"] .getService(Ci.nsIScriptSecurityManager); return ssm.createCodebasePrincipal(uri, {});
--- a/dom/indexedDB/test/unit/xpcshell-parent-process.ini +++ b/dom/indexedDB/test/unit/xpcshell-parent-process.ini @@ -39,16 +39,17 @@ support-files = [test_idbSubdirUpgrade.js] [test_globalObjects_ipc.js] skip-if = toolkit == 'android' [test_idle_maintenance.js] [test_invalidate.js] # disabled for the moment. skip-if = true [test_lowDiskSpace.js] +[test_maximal_serialized_object_size.js] [test_metadata2Restore.js] [test_metadataRestore.js] [test_mutableFileUpgrade.js] [test_oldDirectories.js] [test_quotaExceeded_recovery.js] [test_readwriteflush_disabled.js] [test_schema18upgrade.js] [test_schema21upgrade.js]
--- a/dom/ipc/ContentParent.cpp +++ b/dom/ipc/ContentParent.cpp @@ -737,24 +737,24 @@ ContentParent::JoinAllSubprocesses() sCanLaunchSubprocesses = false; } /*static*/ already_AddRefed<ContentParent> ContentParent::GetNewOrUsedBrowserProcess(bool aForBrowserElement, ProcessPriority aPriority, ContentParent* aOpener, - bool aFreshProcess) + bool aLargeAllocationProcess) { nsTArray<ContentParent*>* contentParents; int32_t maxContentParents; // Decide which pool of content parents we are going to be pulling from based - // on the aFreshProcess flag. - if (aFreshProcess) { + // on the aLargeAllocationProcess flag. + if (aLargeAllocationProcess) { if (!sLargeAllocationContentParents) { sLargeAllocationContentParents = new nsTArray<ContentParent*>(); } contentParents = sLargeAllocationContentParents; maxContentParents = Preferences::GetInt("dom.ipc.dedicatedProcessCount", 2); } else { if (!sNonAppContentParents) { @@ -765,25 +765,27 @@ ContentParent::GetNewOrUsedBrowserProces maxContentParents = Preferences::GetInt("dom.ipc.processCount", 1); } if (maxContentParents < 1) { maxContentParents = 1; } if (contentParents->Length() >= uint32_t(maxContentParents)) { - uint32_t startIdx = rand() % contentParents->Length(); + uint32_t maxSelectable = std::min(static_cast<uint32_t>(contentParents->Length()), + static_cast<uint32_t>(maxContentParents)); + uint32_t startIdx = rand() % maxSelectable; uint32_t currIdx = startIdx; do { RefPtr<ContentParent> p = (*contentParents)[currIdx]; NS_ASSERTION(p->IsAlive(), "Non-alive contentparent in sNonAppContntParents?"); if (p->mOpener == aOpener) { return p.forget(); } - currIdx = (currIdx + 1) % contentParents->Length(); + currIdx = (currIdx + 1) % maxSelectable; } while (currIdx != startIdx); } // Try to take and transform the preallocated process into browser. RefPtr<ContentParent> p = PreallocatedProcessManager::Take(); if (p) { p->TransformPreallocatedIntoBrowser(aOpener); } else { @@ -794,16 +796,19 @@ ContentParent::GetNewOrUsedBrowserProces /* isForPreallocated = */ false); if (!p->LaunchSubprocess(aPriority)) { return nullptr; } p->Init(); } + + p->mLargeAllocationProcess = aLargeAllocationProcess; + p->ForwardKnownInfo(); contentParents->AppendElement(p); return p.forget(); } /*static*/ ProcessPriority ContentParent::GetInitialProcessPriority(Element* aFrameElement) @@ -1740,23 +1745,23 @@ ContentParent::RecvAllocateLayerTreeId(c return AllocateLayerTreeId(contentParent, browserParent, aTabId, aId); } bool ContentParent::RecvDeallocateLayerTreeId(const uint64_t& aId) { GPUProcessManager* gpu = GPUProcessManager::Get(); - if (!gpu->IsLayerTreeIdMapped(aId, this->OtherPid())) + if (!gpu->IsLayerTreeIdMapped(aId, OtherPid())) { // You can't deallocate layer tree ids that you didn't allocate KillHard("DeallocateLayerTreeId"); } - gpu->DeallocateLayerTreeId(aId); + gpu->UnmapLayerTreeId(aId, OtherPid()); return true; } namespace { void DelayedDeleteSubprocess(GeckoChildProcessHost* aSubprocess) @@ -1953,16 +1958,22 @@ ContentParent::NotifyTabDestroying(const return; } ++cp->mNumDestroyingTabs; nsTArray<TabId> tabIds = cpm->GetTabParentsByProcessId(aCpId); if (static_cast<size_t>(cp->mNumDestroyingTabs) != tabIds.Length()) { return; } + uint32_t numberOfParents = sNonAppContentParents ? sNonAppContentParents->Length() : 0; + int32_t processesToKeepAlive = Preferences::GetInt("dom.ipc.keepProcessesAlive", 0); + if (!cp->mLargeAllocationProcess && static_cast<int32_t>(numberOfParents) <= processesToKeepAlive) { + return; + } + // We're dying now, so prevent this content process from being // recycled during its shutdown procedure. cp->MarkAsDead(); cp->StartForceKillTimer(); } else { ContentChild::GetSingleton()->SendNotifyTabDestroying(aTabId, aCpId); } } @@ -2001,17 +2012,25 @@ ContentParent::NotifyTabDestroyed(const Unused << PContentPermissionRequestParent::Send__delete__(permissionRequestParent); } // There can be more than one PBrowser for a given app process // because of popup windows. When the last one closes, shut // us down. ContentProcessManager* cpm = ContentProcessManager::GetSingleton(); nsTArray<TabId> tabIds = cpm->GetTabParentsByProcessId(this->ChildID()); - if (tabIds.Length() == 1) { + + // We might want to keep alive some content processes for testing, because of performance + // reasons, but we don't want to alter behavior if the pref is not set. + uint32_t numberOfParents = sNonAppContentParents ? sNonAppContentParents->Length() : 0; + int32_t processesToKeepAlive = Preferences::GetInt("dom.ipc.keepProcessesAlive", 0); + bool shouldKeepAliveAny = !mLargeAllocationProcess && processesToKeepAlive > 0; + bool shouldKeepAliveThis = shouldKeepAliveAny && static_cast<int32_t>(numberOfParents) <= processesToKeepAlive; + + if (tabIds.Length() == 1 && !shouldKeepAliveThis) { // In the case of normal shutdown, send a shutdown message to child to // allow it to perform shutdown tasks. MessageLoop::current()->PostTask(NewRunnableMethod <ShutDownMethod>(this, &ContentParent::ShutDownProcess, SEND_SHUTDOWN_MESSAGE)); } } @@ -2092,16 +2111,17 @@ ContentParent::LaunchSubprocess(ProcessP ContentParent::ContentParent(mozIApplication* aApp, ContentParent* aOpener, bool aIsForBrowser, bool aIsForPreallocated) : nsIContentParent() , mOpener(aOpener) , mIsForBrowser(aIsForBrowser) + , mLargeAllocationProcess(false) { InitializeMembers(); // Perform common initialization. // No more than one of !!aApp, aIsForBrowser, aIsForPreallocated should be // true. MOZ_ASSERT(!!aApp + aIsForBrowser + aIsForPreallocated <= 1); mMetamorphosed = true;
--- a/dom/ipc/ContentParent.h +++ b/dom/ipc/ContentParent.h @@ -131,17 +131,17 @@ public: * 2. remote xul <browser> * 3. normal iframe */ static already_AddRefed<ContentParent> GetNewOrUsedBrowserProcess(bool aForBrowserElement = false, hal::ProcessPriority aPriority = hal::ProcessPriority::PROCESS_PRIORITY_FOREGROUND, ContentParent* aOpener = nullptr, - bool aFreshProcess = false); + bool aLargeAllocationProcess = false); /** * Create a subprocess suitable for use as a preallocated app process. */ static already_AddRefed<ContentParent> PreallocateAppProcess(); /** * Get or create a content process for the given TabContext. aFrameElement @@ -1185,16 +1185,17 @@ private: RefPtr<embedding::PrintingParent> mPrintingParent; #endif // This hashtable is used to run GetFilesHelper objects in the parent process. // GetFilesHelper can be aborted by receiving RecvDeleteGetFilesRequest. nsRefPtrHashtable<nsIDHashKey, GetFilesHelper> mGetFilesPendingRequests; nsTArray<nsCString> mBlobURLs; + bool mLargeAllocationProcess; }; } // namespace dom } // namespace mozilla class ParentIdleListener : public nsIObserver { friend class mozilla::dom::ContentParent;
--- a/dom/ipc/TabChild.cpp +++ b/dom/ipc/TabChild.cpp @@ -1835,17 +1835,17 @@ TabChild::RecvPluginEvent(const WidgetPl // If not consumed, we should call default action SendDefaultProcOfPluginEvent(aEvent); } return true; } void TabChild::RequestNativeKeyBindings(AutoCacheNativeKeyCommands* aAutoCache, - WidgetKeyboardEvent* aEvent) + const WidgetKeyboardEvent* aEvent) { MaybeNativeKeyBinding maybeBindings; if (!SendRequestNativeKeyBindings(*aEvent, &maybeBindings)) { return; } if (maybeBindings.type() == MaybeNativeKeyBinding::TNativeKeyBinding) { const NativeKeyBinding& bindings = maybeBindings;
--- a/dom/ipc/TabChild.h +++ b/dom/ipc/TabChild.h @@ -508,17 +508,17 @@ public: ScreenOrientationInternal GetOrientation() const { return mOrientation; } void SetBackgroundColor(const nscolor& aColor); void NotifyPainted(); void RequestNativeKeyBindings(mozilla::widget::AutoCacheNativeKeyCommands* aAutoCache, - WidgetKeyboardEvent* aEvent); + const WidgetKeyboardEvent* aEvent); /** * Signal to this TabChild that it should be made visible: * activated widget, retained layer tree, etc. (Respectively, * made not visible.) */ void MakeVisible(); void MakeHidden();
--- a/dom/ipc/TabParent.cpp +++ b/dom/ipc/TabParent.cpp @@ -933,17 +933,19 @@ TabParent::RecvPDocAccessibleConstructor MOZ_ASSERT(aParentID); if (!aParentID) { return false; } auto parentDoc = static_cast<a11y::DocAccessibleParent*>(aParentDoc); bool added = parentDoc->AddChildDoc(doc, aParentID); #ifdef XP_WIN - a11y::WrapperFor(doc)->SetID(aMsaaID); + if (added) { + a11y::WrapperFor(doc)->SetID(aMsaaID); + } #endif return added; } else { // null aParentDoc means this document is at the top level in the child // process. That means it makes no sense to get an id for an accessible // that is its parent. MOZ_ASSERT(!aParentID); if (aParentID) {
--- a/dom/media/CanvasCaptureMediaStream.cpp +++ b/dom/media/CanvasCaptureMediaStream.cpp @@ -233,17 +233,16 @@ JSObject* CanvasCaptureMediaStream::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) { return dom::CanvasCaptureMediaStreamBinding::Wrap(aCx, this, aGivenProto); } void CanvasCaptureMediaStream::RequestFrame() { - MOZ_ASSERT(mOutputStreamDriver); if (mOutputStreamDriver) { mOutputStreamDriver->RequestFrameCapture(); } } nsresult CanvasCaptureMediaStream::Init(const dom::Optional<double>& aFPS, const TrackID& aTrackId, @@ -278,11 +277,22 @@ CanvasCaptureMediaStream::CreateSourceSt } FrameCaptureListener* CanvasCaptureMediaStream::FrameCaptureListener() { return mOutputStreamDriver; } +void +CanvasCaptureMediaStream::StopCapture() +{ + if (!mOutputStreamDriver) { + return; + } + + mOutputStreamDriver->Forget(); + mOutputStreamDriver = nullptr; +} + } // namespace dom } // namespace mozilla
--- a/dom/media/CanvasCaptureMediaStream.h +++ b/dom/media/CanvasCaptureMediaStream.h @@ -107,19 +107,25 @@ public: nsresult Init(const dom::Optional<double>& aFPS, const TrackID& aTrackId, nsIPrincipal* aPrincipal); JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override; // WebIDL HTMLCanvasElement* Canvas() const { return mCanvas; } void RequestFrame(); + dom::FrameCaptureListener* FrameCaptureListener(); /** + * Stops capturing for this stream at mCanvas. + */ + void StopCapture(); + + /** * Create a CanvasCaptureMediaStream whose underlying stream is a SourceMediaStream. */ static already_AddRefed<CanvasCaptureMediaStream> CreateSourceStream(nsPIDOMWindowInner* aWindow, HTMLCanvasElement* aCanvas); protected: ~CanvasCaptureMediaStream();
--- a/dom/media/DOMMediaStream.cpp +++ b/dom/media/DOMMediaStream.cpp @@ -47,16 +47,27 @@ using namespace mozilla::dom; using namespace mozilla::layers; using namespace mozilla::media; static LazyLogModule gMediaStreamLog("MediaStream"); #define LOG(type, msg) MOZ_LOG(gMediaStreamLog, type, msg) const TrackID TRACK_VIDEO_PRIMARY = 1; +static bool +ContainsLiveTracks(nsTArray<RefPtr<DOMMediaStream::TrackPort>>& aTracks) +{ + for (auto& port : aTracks) { + if (port->GetTrack()->ReadyState() == MediaStreamTrackState::Live) { + return true; + } + } + + return false; +} DOMMediaStream::TrackPort::TrackPort(MediaInputPort* aInputPort, MediaStreamTrack* aTrack, const InputPortOwnership aOwnership) : mInputPort(aInputPort) , mTrack(aTrack) , mOwnership(aOwnership) { @@ -160,17 +171,17 @@ public: if (mStream->mTrackSourceGetter) { source = mStream->mTrackSourceGetter->GetMediaStreamTrackSource(aTrackID); } if (!source) { NS_ASSERTION(false, "Dynamic track created without an explicit TrackSource"); nsPIDOMWindowInner* window = mStream->GetParentObject(); nsIDocument* doc = window ? window->GetExtantDoc() : nullptr; nsIPrincipal* principal = doc ? doc->NodePrincipal() : nullptr; - source = new BasicUnstoppableTrackSource(principal); + source = new BasicTrackSource(principal); } RefPtr<MediaStreamTrack> newTrack = mStream->CreateDOMTrack(aTrackID, aType, source); NS_DispatchToMainThread(NewRunnableMethod<RefPtr<MediaStreamTrack>>( mStream, &DOMMediaStream::AddTrackInternal, newTrack)); } @@ -184,17 +195,17 @@ public: } RefPtr<MediaStreamTrack> track = mStream->FindOwnedDOMTrack(aInputStream, aInputTrackID, aTrackID); NS_ASSERTION(track, "Owned MediaStreamTracks must be known by the DOMMediaStream"); if (track) { LOG(LogLevel::Debug, ("DOMMediaStream %p MediaStreamTrack %p ended at the source. Marking it ended.", mStream, track.get())); - track->NotifyEnded(); + track->OverrideEnded(); } } void NotifyQueuedTrackChanges(MediaStreamGraph* aGraph, TrackID aID, StreamTime aTrackOffset, TrackEventCommand aTrackEvents, const MediaSegment& aQueuedMedia, MediaStream* aInputStream, TrackID aInputTrackID) override @@ -231,114 +242,131 @@ public: {} void Forget() { MOZ_ASSERT(NS_IsMainThread()); mStream = nullptr; } - void DoNotifyTrackEnded(MediaStream* aInputStream, - TrackID aInputTrackID) - { - MOZ_ASSERT(NS_IsMainThread()); - - if (!mStream) { - return; - } - - LOG(LogLevel::Debug, ("DOMMediaStream %p Track %u of stream %p ended", - mStream, aInputTrackID, aInputStream)); - - RefPtr<MediaStreamTrack> track = - mStream->FindPlaybackDOMTrack(aInputStream, aInputTrackID); - if (!track) { - LOG(LogLevel::Debug, ("DOMMediaStream %p Not a playback track.", mStream)); - return; - } - - LOG(LogLevel::Debug, ("DOMMediaStream %p Playback track; notifying stream listeners.", - mStream)); - mStream->NotifyTrackRemoved(track); - - RefPtr<TrackPort> endedPort = mStream->FindPlaybackTrackPort(*track); - NS_ASSERTION(endedPort, "Playback track should have a TrackPort"); - if (endedPort && IsTrackIDExplicit(endedPort->GetSourceTrackId())) { - // If a track connected to a locked-track input port ends, we destroy the - // port to allow our playback stream to finish. - // XXX (bug 1208316) This should not be necessary when MediaStreams don't - // finish but instead become inactive. - endedPort->DestroyInputPort(); - } - } - void DoNotifyFinishedTrackCreation() { MOZ_ASSERT(NS_IsMainThread()); if (!mStream) { return; } // The owned stream listener adds its tracks after another main thread // dispatch. We have to do the same to notify of created tracks to stay // in sync. (Or NotifyTracksCreated is called before tracks are added). NS_DispatchToMainThread( NewRunnableMethod(mStream, &DOMMediaStream::NotifyTracksCreated)); } - // The methods below are called on the MediaStreamGraph thread. - - void NotifyQueuedTrackChanges(MediaStreamGraph* aGraph, TrackID aID, - StreamTime aTrackOffset, TrackEventCommand aTrackEvents, - const MediaSegment& aQueuedMedia, - MediaStream* aInputStream, - TrackID aInputTrackID) override + void DoNotifyFinished() { - if (aTrackEvents & TrackEventCommand::TRACK_EVENT_ENDED) { - nsCOMPtr<nsIRunnable> runnable = - NewRunnableMethod<RefPtr<MediaStream>, TrackID>( - this, &PlaybackStreamListener::DoNotifyTrackEnded, aInputStream, aInputTrackID); - aGraph->DispatchToMainThreadAfterStreamStateUpdate(runnable.forget()); + MOZ_ASSERT(NS_IsMainThread()); + + if (!mStream) { + return; } + + mStream->NotifyFinished(); } + // The methods below are called on the MediaStreamGraph thread. + void NotifyFinishedTrackCreation(MediaStreamGraph* aGraph) override { nsCOMPtr<nsIRunnable> runnable = NewRunnableMethod(this, &PlaybackStreamListener::DoNotifyFinishedTrackCreation); aGraph->DispatchToMainThreadAfterStreamStateUpdate(runnable.forget()); } + + void NotifyEvent(MediaStreamGraph* aGraph, + MediaStreamGraphEvent event) override + { + if (event == MediaStreamGraphEvent::EVENT_FINISHED) { + aGraph->DispatchToMainThreadAfterStreamStateUpdate( + NewRunnableMethod(this, &PlaybackStreamListener::DoNotifyFinished)); + } + } + private: // These fields may only be accessed on the main thread DOMMediaStream* mStream; }; +class DOMMediaStream::PlaybackTrackListener : public MediaStreamTrackConsumer +{ +public: + explicit PlaybackTrackListener(DOMMediaStream* aStream) : + mStream(aStream) {} + + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(PlaybackTrackListener, + MediaStreamTrackConsumer) + + void NotifyEnded(MediaStreamTrack* aTrack) override + { + if (!mStream) { + MOZ_ASSERT(false); + return; + } + + if (!aTrack) { + MOZ_ASSERT(false); + return; + } + + MOZ_ASSERT(mStream->HasTrack(*aTrack)); + mStream->NotifyTrackRemoved(aTrack); + } + +protected: + virtual ~PlaybackTrackListener() {} + + RefPtr<DOMMediaStream> mStream; +}; + +NS_IMPL_ADDREF_INHERITED(DOMMediaStream::PlaybackTrackListener, + MediaStreamTrackConsumer) +NS_IMPL_RELEASE_INHERITED(DOMMediaStream::PlaybackTrackListener, + MediaStreamTrackConsumer) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(DOMMediaStream::PlaybackTrackListener) +NS_INTERFACE_MAP_END_INHERITING(MediaStreamTrackConsumer) +NS_IMPL_CYCLE_COLLECTION_INHERITED(DOMMediaStream::PlaybackTrackListener, + MediaStreamTrackConsumer, + mStream) + NS_IMPL_CYCLE_COLLECTION_CLASS(DOMMediaStream) NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(DOMMediaStream, DOMEventTargetHelper) tmp->Destroy(); NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow) NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwnedTracks) NS_IMPL_CYCLE_COLLECTION_UNLINK(mTracks) NS_IMPL_CYCLE_COLLECTION_UNLINK(mConsumersToKeepAlive) NS_IMPL_CYCLE_COLLECTION_UNLINK(mTrackSourceGetter) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mPlaybackTrackListener) NS_IMPL_CYCLE_COLLECTION_UNLINK(mPrincipal) NS_IMPL_CYCLE_COLLECTION_UNLINK(mVideoPrincipal) NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(DOMMediaStream, DOMEventTargetHelper) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwnedTracks) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTracks) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mConsumersToKeepAlive) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTrackSourceGetter) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPlaybackTrackListener) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPrincipal) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mVideoPrincipal) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_ADDREF_INHERITED(DOMMediaStream, DOMEventTargetHelper) NS_IMPL_RELEASE_INHERITED(DOMMediaStream, DOMEventTargetHelper) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(DOMMediaStream) @@ -361,17 +389,19 @@ NS_IMPL_RELEASE_INHERITED(DOMAudioNodeMe NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(DOMAudioNodeMediaStream) NS_INTERFACE_MAP_END_INHERITING(DOMMediaStream) DOMMediaStream::DOMMediaStream(nsPIDOMWindowInner* aWindow, MediaStreamTrackSourceGetter* aTrackSourceGetter) : mLogicalStreamStartTime(0), mWindow(aWindow), mInputStream(nullptr), mOwnedStream(nullptr), mPlaybackStream(nullptr), mTracksPendingRemoval(0), mTrackSourceGetter(aTrackSourceGetter), - mTracksCreated(false), mNotifiedOfMediaStreamGraphShutdown(false) + mPlaybackTrackListener(MakeAndAddRef<PlaybackTrackListener>(this)), + mTracksCreated(false), mNotifiedOfMediaStreamGraphShutdown(false), + mActive(false), mSetInactiveOnFinish(false) { nsresult rv; nsCOMPtr<nsIUUIDGenerator> uuidgen = do_GetService("@mozilla.org/uuid-generator;1", &rv); if (NS_SUCCEEDED(rv) && uuidgen) { nsID uuid; memset(&uuid, 0, sizeof(uuid)); @@ -399,18 +429,22 @@ DOMMediaStream::Destroy() } if (mPlaybackListener) { mPlaybackListener->Forget(); mPlaybackListener = nullptr; } for (const RefPtr<TrackPort>& info : mTracks) { // We must remove ourselves from each track's principal change observer list // before we die. CC may have cleared info->mTrack so guard against it. - if (info->GetTrack()) { - info->GetTrack()->RemovePrincipalChangeObserver(this); + MediaStreamTrack* track = info->GetTrack(); + if (track) { + track->RemovePrincipalChangeObserver(this); + if (!track->Ended()) { + track->RemoveConsumer(mPlaybackTrackListener); + } } } if (mPlaybackPort) { mPlaybackPort->Destroy(); mPlaybackPort = nullptr; } if (mOwnedPort) { mOwnedPort->Destroy(); @@ -613,23 +647,23 @@ DOMMediaStream::RemoveTrack(MediaStreamT // If the track comes from a TRACK_ANY input port (i.e., mOwnedPort), we need // to block it in the port. Doing this for a locked track is still OK as it // will first block the track, then destroy the port. Both cause the track to // end. // If the track has already ended, it's input port might be gone, so in those // cases blocking the underlying track should be avoided. if (!aTrack.Ended()) { BlockPlaybackTrack(toRemove); + + bool removed = mTracks.RemoveElement(toRemove); + if (removed) { + NotifyTrackRemoved(&aTrack); + } } - DebugOnly<bool> removed = mTracks.RemoveElement(toRemove); - MOZ_ASSERT(removed); - - NotifyTrackRemoved(&aTrack); - LOG(LogLevel::Debug, ("DOMMediaStream %p Removed track %p", this, &aTrack)); } class ClonedStreamSourceGetter : public MediaStreamTrackSourceGetter { public: NS_DECL_ISUPPORTS_INHERITED @@ -725,16 +759,22 @@ DOMMediaStream::CloneInternal(TrackForwa TRACK_ANY, TRACK_ANY, 0, 0, &tracksToBlock); } } return newStream.forget(); } +bool +DOMMediaStream::Active() const +{ + return mActive; +} + MediaStreamTrack* DOMMediaStream::GetTrackById(const nsAString& aId) const { for (const RefPtr<TrackPort>& info : mTracks) { nsString id; info->GetTrack()->GetId(id); if (id == aId) { return info->GetTrack(); @@ -782,22 +822,28 @@ void DOMMediaStream::RemoveDirectListener(DirectMediaStreamListener* aListener) { if (GetInputStream() && GetInputStream()->AsSourceStream()) { GetInputStream()->AsSourceStream()->RemoveDirectListener(aListener); } } bool -DOMMediaStream::IsFinished() +DOMMediaStream::IsFinished() const { return !mPlaybackStream || mPlaybackStream->IsFinished(); } void +DOMMediaStream::SetInactiveOnFinish() +{ + mSetInactiveOnFinish = true; +} + +void DOMMediaStream::InitSourceStream(MediaStreamGraph* aGraph) { InitInputStreamCommon(aGraph->CreateSourceStream(), aGraph); InitOwnedStreamCommon(aGraph); InitPlaybackStreamCommon(aGraph); } void @@ -808,18 +854,18 @@ DOMMediaStream::InitTrackUnionStream(Med InitPlaybackStreamCommon(aGraph); } void DOMMediaStream::InitAudioCaptureStream(nsIPrincipal* aPrincipal, MediaStreamGraph* aGraph) { const TrackID AUDIO_TRACK = 1; - RefPtr<BasicUnstoppableTrackSource> audioCaptureSource = - new BasicUnstoppableTrackSource(aPrincipal, MediaSourceEnum::AudioCapture); + RefPtr<BasicTrackSource> audioCaptureSource = + new BasicTrackSource(aPrincipal, MediaSourceEnum::AudioCapture); AudioCaptureStream* audioCaptureStream = static_cast<AudioCaptureStream*>(aGraph->CreateAudioCaptureStream(AUDIO_TRACK)); InitInputStreamCommon(audioCaptureStream, aGraph); InitOwnedStreamCommon(aGraph); InitPlaybackStreamCommon(aGraph); RefPtr<MediaStreamTrack> track = CreateDOMTrack(AUDIO_TRACK, MediaSegment::AUDIO, audioCaptureSource); @@ -1175,16 +1221,55 @@ DOMMediaStream::OnTracksAvailable(OnTrac void DOMMediaStream::NotifyTracksCreated() { mTracksCreated = true; CheckTracksAvailable(); } void +DOMMediaStream::NotifyFinished() +{ + if (!mSetInactiveOnFinish) { + return; + } + + if (!mActive) { + // This can happen if the stream never became active. + return; + } + + MOZ_ASSERT(!ContainsLiveTracks(mTracks)); + mActive = false; + NotifyInactive(); +} + +void +DOMMediaStream::NotifyActive() +{ + LOG(LogLevel::Info, ("DOMMediaStream %p NotifyActive(). ", this)); + + MOZ_ASSERT(mActive); + for (int32_t i = mTrackListeners.Length() - 1; i >= 0; --i) { + mTrackListeners[i]->NotifyActive(); + } +} + +void +DOMMediaStream::NotifyInactive() +{ + LOG(LogLevel::Info, ("DOMMediaStream %p NotifyInactive(). ", this)); + + MOZ_ASSERT(!mActive); + for (int32_t i = mTrackListeners.Length() - 1; i >= 0; --i) { + mTrackListeners[i]->NotifyInactive(); + } +} + +void DOMMediaStream::CheckTracksAvailable() { if (!mTracksCreated) { return; } nsTArray<nsAutoPtr<OnTracksAvailableCallback> > callbacks; callbacks.SwapElements(mRunOnTracksAvailable); @@ -1234,36 +1319,66 @@ DOMMediaStream::NotifyTrackAdded(const R } } else { LOG(LogLevel::Debug, ("DOMMediaStream %p saw a track get added. " "Recomputing principal.", this)); RecomputePrincipal(); } aTrack->AddPrincipalChangeObserver(this); + aTrack->AddConsumer(mPlaybackTrackListener); for (int32_t i = mTrackListeners.Length() - 1; i >= 0; --i) { mTrackListeners[i]->NotifyTrackAdded(aTrack); } + + if (mActive) { + return; + } + + // Check if we became active. + if (ContainsLiveTracks(mTracks)) { + mActive = true; + NotifyActive(); + } } void DOMMediaStream::NotifyTrackRemoved(const RefPtr<MediaStreamTrack>& aTrack) { MOZ_ASSERT(NS_IsMainThread()); + aTrack->RemoveConsumer(mPlaybackTrackListener); aTrack->RemovePrincipalChangeObserver(this); for (int32_t i = mTrackListeners.Length() - 1; i >= 0; --i) { mTrackListeners[i]->NotifyTrackRemoved(aTrack); + } // Don't call RecomputePrincipal here as the track may still exist in the // playback stream in the MediaStreamGraph. It will instead be called when the // track has been confirmed removed by the graph. See BlockPlaybackTrack(). + + if (!mActive) { + NS_ASSERTION(false, "Shouldn't remove a live track if already inactive"); + return; + } + + if (mSetInactiveOnFinish) { + // For compatibility with mozCaptureStream we in some cases do not go + // inactive until the playback stream finishes. + return; + } + + // Check if we became inactive. + if (!ContainsLiveTracks(mTracks)) { + mActive = false; + NotifyInactive(); + } } nsresult DOMMediaStream::DispatchTrackEvent(const nsAString& aName, const RefPtr<MediaStreamTrack>& aTrack) { MOZ_ASSERT(aName == NS_LITERAL_STRING("addtrack"), "Only 'addtrack' is supported at this time");
--- a/dom/media/DOMMediaStream.h +++ b/dom/media/DOMMediaStream.h @@ -221,28 +221,40 @@ class DOMMediaStream : public DOMEventTa public: typedef dom::MediaTrackConstraints MediaTrackConstraints; class TrackListener { public: virtual ~TrackListener() {} /** - * Called when the DOMMediaStream has a new track added, either by - * JS (addTrack()) or the source creating one. + * Called when the DOMMediaStream has a live track added, either by + * script (addTrack()) or the source creating one. */ virtual void NotifyTrackAdded(const RefPtr<MediaStreamTrack>& aTrack) {}; /** - * Called when the DOMMediaStream removes a track, either by - * JS (removeTrack()) or the source ending it. + * Called when the DOMMediaStream removes a live track from playback, either + * by script (removeTrack(), track.stop()) or the source ending it. */ virtual void NotifyTrackRemoved(const RefPtr<MediaStreamTrack>& aTrack) {}; + + /** + * Called when the DOMMediaStream has become active. + */ + virtual void + NotifyActive() {}; + + /** + * Called when the DOMMediaStream has become inactive. + */ + virtual void + NotifyInactive() {}; }; /** * TrackPort is a representation of a MediaStreamTrack-MediaInputPort pair * that make up a link between the Owned stream and the Playback stream. * * Semantically, the track is the identifier/key and the port the value of this * connection. @@ -358,16 +370,18 @@ public: void GetTracks(nsTArray<RefPtr<MediaStreamTrack> >& aTracks) const; MediaStreamTrack* GetTrackById(const nsAString& aId) const; void AddTrack(MediaStreamTrack& aTrack); void RemoveTrack(MediaStreamTrack& aTrack); /** Identical to CloneInternal(TrackForwardingOption::EXPLICIT) */ already_AddRefed<DOMMediaStream> Clone(); + bool Active() const; + IMPL_EVENT_HANDLER(addtrack) // NON-WebIDL /** * Option to provide to CloneInternal() of which tracks should be forwarded * from the source stream (`this`) to the returned stream clone. * @@ -440,17 +454,26 @@ public: * queuing. Returns a bool to let us know if direct data will be delivered. */ bool AddDirectListener(DirectMediaStreamListener *aListener); void RemoveDirectListener(DirectMediaStreamListener *aListener); virtual DOMLocalMediaStream* AsDOMLocalMediaStream() { return nullptr; } virtual DOMHwMediaStream* AsDOMHwMediaStream() { return nullptr; } - bool IsFinished(); + /** + * Legacy method that returns true when the playback stream has finished. + */ + bool IsFinished() const; + + /** + * Becomes inactive only when the playback stream has finished. + */ + void SetInactiveOnFinish(); + /** * Returns a principal indicating who may access this stream. The stream contents * can only be accessed by principals subsuming this principal. */ nsIPrincipal* GetPrincipal() { return mPrincipal; } /** * Returns a principal indicating who may access video data of this stream. @@ -594,32 +617,44 @@ protected: void InitPlaybackStreamCommon(MediaStreamGraph* aGraph); void CheckTracksAvailable(); // Called when MediaStreamGraph has finished an iteration where tracks were // created. void NotifyTracksCreated(); + // Called when our playback stream has finished in the MediaStreamGraph. + void NotifyFinished(); + + // Dispatches NotifyActive() to all registered track listeners. + void NotifyActive(); + + // Dispatches NotifyInactive() to all registered track listeners. + void NotifyInactive(); + // Dispatches NotifyTrackAdded() to all registered track listeners. void NotifyTrackAdded(const RefPtr<MediaStreamTrack>& aTrack); // Dispatches NotifyTrackRemoved() to all registered track listeners. void NotifyTrackRemoved(const RefPtr<MediaStreamTrack>& aTrack); // Dispatches "addtrack" or "removetrack". nsresult DispatchTrackEvent(const nsAString& aName, const RefPtr<MediaStreamTrack>& aTrack); class OwnedStreamListener; friend class OwnedStreamListener; class PlaybackStreamListener; friend class PlaybackStreamListener; + class PlaybackTrackListener; + friend class PlaybackTrackListener; + /** * Block a track in our playback stream. Calls NotifyPlaybackTrackBlocked() * after the MediaStreamGraph has applied the block and the track is no longer * live. */ void BlockPlaybackTrack(TrackPort* aTrack); /** @@ -679,31 +714,42 @@ protected: // Listener tracking changes to mOwnedStream. We use this to notify the // MediaStreamTracks we own about state changes. RefPtr<OwnedStreamListener> mOwnedListener; // Listener tracking changes to mPlaybackStream. This drives state changes // in this DOMMediaStream and notifications to mTrackListeners. RefPtr<PlaybackStreamListener> mPlaybackListener; + // Listener tracking when live MediaStreamTracks in mTracks end. + RefPtr<PlaybackTrackListener> mPlaybackTrackListener; + nsTArray<nsAutoPtr<OnTracksAvailableCallback> > mRunOnTracksAvailable; // Set to true after MediaStreamGraph has created tracks for mPlaybackStream. bool mTracksCreated; nsString mID; // Keep these alive while the stream is alive. nsTArray<nsCOMPtr<nsISupports>> mConsumersToKeepAlive; bool mNotifiedOfMediaStreamGraphShutdown; // The track listeners subscribe to changes in this stream's track set. nsTArray<TrackListener*> mTrackListeners; + // True if this stream has live tracks. + bool mActive; + + // True if this stream only sets mActive to false when its playback stream + // finishes. This is a hack to maintain legacy functionality for playing a + // HTMLMediaElement::MozCaptureStream(). See bug 1302379. + bool mSetInactiveOnFinish; + private: void NotifyPrincipalChanged(); // Principal identifying who may access the collected contents of this stream. // If null, this stream can be used by anyone because it has no content yet. nsCOMPtr<nsIPrincipal> mPrincipal; // Video principal is used by video element as access is requested to its // image data. nsCOMPtr<nsIPrincipal> mVideoPrincipal;
--- a/dom/media/MediaManager.cpp +++ b/dom/media/MediaManager.cpp @@ -129,17 +129,17 @@ namespace mozilla { LogModule* GetMediaManagerLog() { static LazyLogModule sLog("MediaManager"); return sLog; } #define LOG(msg) MOZ_LOG(GetMediaManagerLog(), mozilla::LogLevel::Debug, msg) -using dom::BasicUnstoppableTrackSource; +using dom::BasicTrackSource; using dom::ConstrainDOMStringParameters; using dom::File; using dom::GetUserMediaRequest; using dom::MediaSourceEnum; using dom::MediaStreamConstraints; using dom::MediaStreamError; using dom::MediaStreamTrack; using dom::MediaStreamTrackSource; @@ -1017,17 +1017,17 @@ public: already_AddRefed<dom::MediaStreamTrackSource> GetMediaStreamTrackSource(TrackID aInputTrackID) override { NS_ASSERTION(kAudioTrack != aInputTrackID, "Only fake tracks should appear dynamically"); NS_ASSERTION(kVideoTrack != aInputTrackID, "Only fake tracks should appear dynamically"); - return do_AddRef(new BasicUnstoppableTrackSource(mPrincipal)); + return do_AddRef(new BasicTrackSource(mPrincipal)); } protected: virtual ~FakeTrackSourceGetter() {} nsCOMPtr<nsIPrincipal> mPrincipal; }; @@ -1165,17 +1165,17 @@ public: { public: LocalTrackSource(nsIPrincipal* aPrincipal, const nsString& aLabel, GetUserMediaCallbackMediaStreamListener* aListener, const MediaSourceEnum aSource, const TrackID aTrackID, const PeerIdentity* aPeerIdentity) - : MediaStreamTrackSource(aPrincipal, false, aLabel), mListener(aListener), + : MediaStreamTrackSource(aPrincipal, aLabel), mListener(aListener), mSource(aSource), mTrackID(aTrackID), mPeerIdentity(aPeerIdentity) {} MediaSourceEnum GetMediaSource() const override { return mSource; } const PeerIdentity* GetPeerIdentity() const override
--- a/dom/media/MediaPrefs.h +++ b/dom/media/MediaPrefs.h @@ -119,21 +119,17 @@ private: #endif #ifdef MOZ_FFVPX DECL_MEDIA_PREF("media.ffvpx.enabled", PDMFFVPXEnabled, bool, true); #endif #ifdef XP_WIN DECL_MEDIA_PREF("media.wmf.enabled", PDMWMFEnabled, bool, true); DECL_MEDIA_PREF("media.decoder-doctor.wmf-disabled-is-failure", DecoderDoctorWMFDisabledIsFailure, bool, false); DECL_MEDIA_PREF("media.wmf.vp9.enabled", PDMWMFVP9DecoderEnabled, bool, true); - DECL_MEDIA_PREF("media.wmf.low-latency.enabled", PDMWMFLowLatencyEnabled, bool, false); DECL_MEDIA_PREF("media.wmf.decoder.thread-count", PDMWMFThreadCount, int32_t, -1); - DECL_MEDIA_PREF("media.wmf.skip-blacklist", PDMWMFSkipBlacklist, bool, false); - DECL_MEDIA_PREF("media.windows-media-foundation.max-dxva-videos", PDMWMFMaxDXVAVideos, uint32_t, 8); - DECL_MEDIA_PREF("media.windows-media-foundation.allow-d3d11-dxva", PDMWMFAllowD3D11, bool, true); #endif DECL_MEDIA_PREF("media.decoder.fuzzing.enabled", PDMFuzzingEnabled, bool, false); DECL_MEDIA_PREF("media.decoder.fuzzing.video-output-minimum-interval-ms", PDMFuzzingInterval, uint32_t, 0); DECL_MEDIA_PREF("media.decoder.fuzzing.dont-delay-inputexhausted", PDMFuzzingDelayInputExhausted, bool, true); DECL_MEDIA_PREF("media.gmp.decoder.enabled", PDMGMPEnabled, bool, true); DECL_MEDIA_PREF("media.gmp.decoder.aac", GMPAACPreferred, uint32_t, 0); DECL_MEDIA_PREF("media.gmp.decoder.h264", GMPH264Preferred, uint32_t, 0);
--- a/dom/media/MediaStreamTrack.cpp +++ b/dom/media/MediaStreamTrack.cpp @@ -48,16 +48,24 @@ MediaStreamTrackSource::ApplyConstraints { RefPtr<PledgeVoid> p = new PledgeVoid(); p->Reject(new MediaStreamError(aWindow, NS_LITERAL_STRING("OverconstrainedError"), NS_LITERAL_STRING(""))); return p.forget(); } +NS_IMPL_CYCLE_COLLECTING_ADDREF(MediaStreamTrackConsumer) +NS_IMPL_CYCLE_COLLECTING_RELEASE(MediaStreamTrackConsumer) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaStreamTrackConsumer) + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTION_0(MediaStreamTrackConsumer) + /** * PrincipalHandleListener monitors changes in PrincipalHandle of the media flowing * through the MediaStreamGraph. * * When the main thread principal for a MediaStreamTrack changes, its principal * will be set to the combination of the previous principal and the new one. * * As a PrincipalHandle change later happens on the MediaStreamGraph thread, we will @@ -111,18 +119,17 @@ protected: MediaStreamTrack::MediaStreamTrack(DOMMediaStream* aStream, TrackID aTrackID, TrackID aInputTrackID, MediaStreamTrackSource* aSource, const MediaTrackConstraints& aConstraints) : mOwningStream(aStream), mTrackID(aTrackID), mInputTrackID(aInputTrackID), mSource(aSource), mPrincipal(aSource->GetPrincipal()), mReadyState(MediaStreamTrackState::Live), - mEnabled(true), mRemote(aSource->IsRemote()), - mConstraints(aConstraints) + mEnabled(true), mConstraints(aConstraints) { GetSource().RegisterSink(this); mPrincipalHandleListener = new PrincipalHandleListener(this); AddListener(mPrincipalHandleListener); nsresult rv; @@ -166,25 +173,27 @@ MediaStreamTrack::Destroy() } } NS_IMPL_CYCLE_COLLECTION_CLASS(MediaStreamTrack) NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(MediaStreamTrack, DOMEventTargetHelper) tmp->Destroy(); + NS_IMPL_CYCLE_COLLECTION_UNLINK(mConsumers) NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwningStream) NS_IMPL_CYCLE_COLLECTION_UNLINK(mSource) NS_IMPL_CYCLE_COLLECTION_UNLINK(mOriginalTrack) NS_IMPL_CYCLE_COLLECTION_UNLINK(mPrincipal) NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingPrincipal) NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(MediaStreamTrack, DOMEventTargetHelper) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mConsumers) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwningStream) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSource) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOriginalTrack) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPrincipal) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingPrincipal) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_ADDREF_INHERITED(MediaStreamTrack, DOMEventTargetHelper) @@ -221,35 +230,32 @@ MediaStreamTrack::Stop() { LOG(LogLevel::Info, ("MediaStreamTrack %p Stop()", this)); if (Ended()) { LOG(LogLevel::Warning, ("MediaStreamTrack %p Already ended", this)); return; } - if (mRemote) { - LOG(LogLevel::Warning, ("MediaStreamTrack %p is remote. Can't be stopped.", this)); - return; - } - if (!mSource) { MOZ_ASSERT(false); return; } mSource->UnregisterSink(this); MOZ_ASSERT(mOwningStream, "Every MediaStreamTrack needs an owning DOMMediaStream"); DOMMediaStream::TrackPort* port = mOwningStream->FindOwnedTrackPort(*this); MOZ_ASSERT(port, "A MediaStreamTrack must exist in its owning DOMMediaStream"); RefPtr<Pledge<bool>> p = port->BlockSourceTrackId(mInputTrackID, BlockingMode::CREATION); Unused << p; mReadyState = MediaStreamTrackState::Ended; + + NotifyEnded(); } void MediaStreamTrack::GetConstraints(dom::MediaTrackConstraints& aResult) { aResult = mConstraints; } @@ -352,30 +358,55 @@ MediaStreamTrack::NotifyPrincipalHandleC this, GetPrincipalFromHandle(handle), mPrincipal.get(), mPendingPrincipal.get())); if (PrincipalHandleMatches(handle, mPendingPrincipal)) { SetPrincipal(mPendingPrincipal); mPendingPrincipal = nullptr; } } +void +MediaStreamTrack::NotifyEnded() +{ + MOZ_ASSERT(mReadyState == MediaStreamTrackState::Ended); + + for (int32_t i = mConsumers.Length() - 1; i >= 0; --i) { + // Loop backwards by index in case the consumer removes itself in the + // callback. + mConsumers[i]->NotifyEnded(this); + } +} + bool MediaStreamTrack::AddPrincipalChangeObserver( PrincipalChangeObserver<MediaStreamTrack>* aObserver) { return mPrincipalChangeObservers.AppendElement(aObserver) != nullptr; } bool MediaStreamTrack::RemovePrincipalChangeObserver( PrincipalChangeObserver<MediaStreamTrack>* aObserver) { return mPrincipalChangeObservers.RemoveElement(aObserver); } +void +MediaStreamTrack::AddConsumer(MediaStreamTrackConsumer* aConsumer) +{ + MOZ_ASSERT(!mConsumers.Contains(aConsumer)); + mConsumers.AppendElement(aConsumer); +} + +void +MediaStreamTrack::RemoveConsumer(MediaStreamTrackConsumer* aConsumer) +{ + mConsumers.RemoveElement(aConsumer); +} + already_AddRefed<MediaStreamTrack> MediaStreamTrack::Clone() { // MediaStreamTracks are currently governed by streams, so we need a dummy // DOMMediaStream to own our track clone. The dummy will never see any // dynamically created tracks (no input stream) so no need for a SourceGetter. RefPtr<DOMMediaStream> newStream = new DOMMediaStream(mOwningStream->GetParentObject(), nullptr); @@ -399,17 +430,17 @@ MediaStreamTrack::SetReadyState(MediaStr mSource) { mSource->UnregisterSink(this); } mReadyState = aState; } void -MediaStreamTrack::NotifyEnded() +MediaStreamTrack::OverrideEnded() { MOZ_ASSERT(NS_IsMainThread()); if (Ended()) { return; } LOG(LogLevel::Info, ("MediaStreamTrack %p ended", this)); @@ -418,16 +449,18 @@ MediaStreamTrack::NotifyEnded() MOZ_ASSERT(false); return; } mSource->UnregisterSink(this); mReadyState = MediaStreamTrackState::Ended; + NotifyEnded(); + DispatchTrustedEvent(NS_LITERAL_STRING("ended")); } DOMMediaStream* MediaStreamTrack::GetInputDOMStream() { MediaStreamTrack* originalTrack = mOriginalTrack ? mOriginalTrack.get() : this;
--- a/dom/media/MediaStreamTrack.h +++ b/dom/media/MediaStreamTrack.h @@ -56,20 +56,18 @@ class MediaStreamTrackSource : public ns public: class Sink { public: virtual void PrincipalChanged() = 0; }; MediaStreamTrackSource(nsIPrincipal* aPrincipal, - const bool aIsRemote, const nsString& aLabel) : mPrincipal(aPrincipal), - mIsRemote(aIsRemote), mLabel(aLabel), mStopped(false) { MOZ_COUNT_CTOR(MediaStreamTrackSource); } /** * Use to clean up any resources that have to be cleaned before the @@ -102,22 +100,16 @@ public: * nsNullPrincipal. * * A track's PeerIdentity is immutable and will not change during the track's * lifetime. */ virtual const PeerIdentity* GetPeerIdentity() const { return nullptr; } /** - * Indicates whether the track is remote or not per the MediaCapture and - * Streams spec. - */ - virtual bool IsRemote() const { return mIsRemote; } - - /** * MediaStreamTrack::GetLabel (see spec) calls through to here. */ void GetLabel(nsAString& aLabel) { aLabel.Assign(mLabel); } /** * Forwards a photo request to backends that support it. Other backends return * NS_ERROR_NOT_IMPLEMENTED to indicate that a MediaStreamGraph-based fallback * should be used. @@ -159,17 +151,18 @@ public: /** * Called by each MediaStreamTrack clone on Stop() if supported by the * source (us) or destruction. */ void UnregisterSink(Sink* aSink) { MOZ_ASSERT(NS_IsMainThread()); - if (mSinks.RemoveElement(aSink) && mSinks.IsEmpty() && !IsRemote()) { + if (mSinks.RemoveElement(aSink) && mSinks.IsEmpty()) { + MOZ_ASSERT(!mStopped); Stop(); mStopped = true; } } protected: virtual ~MediaStreamTrackSource() { @@ -188,54 +181,69 @@ protected: } // Principal identifying who may access the contents of this source. nsCOMPtr<nsIPrincipal> mPrincipal; // Currently registered sinks. nsTArray<Sink*> mSinks; - // True if this is a remote track source, i.e., a PeerConnection. - const bool mIsRemote; - // The label of the track we are the source of per the MediaStreamTrack spec. const nsString mLabel; - // True if this source is not remote, all MediaStreamTrack users have - // unregistered from this source and Stop() has been called. + // True if all MediaStreamTrack users have unregistered from this source and + // Stop() has been called. bool mStopped; }; /** - * Basic implementation of MediaStreamTrackSource that ignores Stop(). + * Basic implementation of MediaStreamTrackSource that doesn't forward Stop(). */ -class BasicUnstoppableTrackSource : public MediaStreamTrackSource +class BasicTrackSource : public MediaStreamTrackSource { public: - explicit BasicUnstoppableTrackSource(nsIPrincipal* aPrincipal, - const MediaSourceEnum aMediaSource = - MediaSourceEnum::Other) - : MediaStreamTrackSource(aPrincipal, true, nsString()) + explicit BasicTrackSource(nsIPrincipal* aPrincipal, + const MediaSourceEnum aMediaSource = + MediaSourceEnum::Other) + : MediaStreamTrackSource(aPrincipal, nsString()) , mMediaSource(aMediaSource) {} MediaSourceEnum GetMediaSource() const override { return mMediaSource; } - void - GetSettings(dom::MediaTrackSettings& aResult) override {} - void Stop() override {} protected: - ~BasicUnstoppableTrackSource() {} + ~BasicTrackSource() {} const MediaSourceEnum mMediaSource; }; /** + * Base class that consumers of a MediaStreamTrack can use to get notifications + * about state changes in the track. + */ +class MediaStreamTrackConsumer : public nsISupports +{ +public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_CLASS(MediaStreamTrackConsumer) + + /** + * Called when the track's readyState transitions to "ended". + * Unlike the "ended" event exposed to script this is called for any reason, + * including MediaStreamTrack::Stop(). + */ + virtual void NotifyEnded(MediaStreamTrack* aTrack) {}; + +protected: + virtual ~MediaStreamTrackConsumer() {} +}; + +/** * Class representing a track in a DOMMediaStream. */ class MediaStreamTrack : public DOMEventTargetHelper, public MediaStreamTrackSource::Sink { // DOMMediaStream owns MediaStreamTrack instances, and requires access to // some internal state, e.g., GetInputStream(), GetOwnedStream(). friend class mozilla::DOMMediaStream; @@ -301,31 +309,37 @@ public: /** * Notified by the MediaStreamGraph, through our owning MediaStream on the * main thread. * * Note that this sets the track to ended and raises the "ended" event * synchronously. */ - void NotifyEnded(); + void OverrideEnded(); /** * Get this track's principal. */ nsIPrincipal* GetPrincipal() const { return mPrincipal; } /** * Called by the PrincipalHandleListener when this track's PrincipalHandle changes on * the MediaStreamGraph thread. When the PrincipalHandle matches the pending * principal we know that the principal change has propagated to consumers. */ void NotifyPrincipalHandleChanged(const PrincipalHandle& aPrincipalHandle); /** + * Called when this track's readyState transitions to "ended". + * Notifies all MediaStreamTrackConsumers that this track ended. + */ + void NotifyEnded(); + + /** * Get this track's CORS mode. */ CORSMode GetCORSMode() const { return GetSource().GetCORSMode(); } /** * Get this track's PeerIdentity. */ const PeerIdentity* GetPeerIdentity() const { return GetSource().GetPeerIdentity(); } @@ -359,16 +373,28 @@ public: /** * Remove an added PrincipalChangeObserver from this track. * * Returns true if it was successfully removed. */ bool RemovePrincipalChangeObserver(PrincipalChangeObserver<MediaStreamTrack>* aObserver); /** + * Add a MediaStreamTrackConsumer to this track. + * + * Adding the same consumer multiple times is prohibited. + */ + void AddConsumer(MediaStreamTrackConsumer* aConsumer); + + /** + * Remove an added MediaStreamTrackConsumer from this track. + */ + void RemoveConsumer(MediaStreamTrackConsumer* aConsumer); + + /** * Adds a MediaStreamTrackListener to the MediaStreamGraph representation of * this track. */ void AddListener(MediaStreamTrackListener* aListener); /** * Removes a MediaStreamTrackListener from the MediaStreamGraph representation * of this track. @@ -424,31 +450,32 @@ protected: * source as this MediaStreamTrack. * aTrackID is the TrackID the new track will have in its owned stream. */ virtual already_AddRefed<MediaStreamTrack> CloneInternal(DOMMediaStream* aOwningStream, TrackID aTrackID) = 0; nsTArray<PrincipalChangeObserver<MediaStreamTrack>*> mPrincipalChangeObservers; + nsTArray<RefPtr<MediaStreamTrackConsumer>> mConsumers; + RefPtr<DOMMediaStream> mOwningStream; TrackID mTrackID; TrackID mInputTrackID; RefPtr<MediaStreamTrackSource> mSource; RefPtr<MediaStreamTrack> mOriginalTrack; nsCOMPtr<nsIPrincipal> mPrincipal; nsCOMPtr<nsIPrincipal> mPendingPrincipal; RefPtr<PrincipalHandleListener> mPrincipalHandleListener; // Keep tracking MediaStreamTrackListener and DirectMediaStreamTrackListener, // so we can remove them in |Destory|. nsTArray<RefPtr<MediaStreamTrackListener>> mTrackListeners; nsTArray<RefPtr<DirectMediaStreamTrackListener>> mDirectTrackListeners; nsString mID; MediaStreamTrackState mReadyState; bool mEnabled; - const bool mRemote; dom::MediaTrackConstraints mConstraints; }; } // namespace dom } // namespace mozilla #endif /* MEDIASTREAMTRACK_H_ */
--- a/dom/media/ipc/RemoteVideoDecoder.cpp +++ b/dom/media/ipc/RemoteVideoDecoder.cpp @@ -120,17 +120,17 @@ RemoteVideoDecoder::SetSeekThreshold(con self->mActor->SetSeekThreshold(time); }), NS_DISPATCH_NORMAL); } nsresult RemoteDecoderModule::Startup() { - if (!VideoDecoderManagerChild::GetSingleton()) { + if (!VideoDecoderManagerChild::GetManagerThread()) { return NS_ERROR_FAILURE; } return mWrapped->Startup(); } bool RemoteDecoderModule::SupportsMimeType(const nsACString& aMimeType, DecoderDoctorDiagnostics* aDiagnostics) const @@ -142,17 +142,18 @@ PlatformDecoderModule::ConversionRequire RemoteDecoderModule::DecoderNeedsConversion(const TrackInfo& aConfig) const { return mWrapped->DecoderNeedsConversion(aConfig); } already_AddRefed<MediaDataDecoder> RemoteDecoderModule::CreateVideoDecoder(const CreateDecoderParams& aParams) { - if (!aParams.mKnowsCompositor) { + if (!aParams.mKnowsCompositor || + aParams.mKnowsCompositor->GetTextureFactoryIdentifier().mParentProcessType != GeckoProcessType_GPU) { return nullptr; } MediaDataDecoderCallback* callback = aParams.mCallback; MOZ_ASSERT(callback->OnReaderTaskQueue()); RefPtr<RemoteVideoDecoder> object = new RemoteVideoDecoder(callback); VideoInfo info = aParams.VideoConfig();
--- a/dom/media/ipc/VideoDecoderChild.cpp +++ b/dom/media/ipc/VideoDecoderChild.cpp @@ -16,17 +16,17 @@ namespace dom { using base::Thread; using namespace ipc; using namespace layers; using namespace gfx; VideoDecoderChild::VideoDecoderChild() : mThread(VideoDecoderManagerChild::GetManagerThread()) - , mCanSend(true) + , mCanSend(false) , mInitialized(false) , mIsHardwareAccelerated(false) { } VideoDecoderChild::~VideoDecoderChild() { AssertOnManagerThread(); @@ -37,17 +37,17 @@ bool VideoDecoderChild::RecvOutput(const VideoDataIPDL& aData) { AssertOnManagerThread(); VideoInfo info(aData.display().width, aData.display().height); // The Image here creates a TextureData object that takes ownership // of the SurfaceDescriptor, and is responsible for making sure that // it gets deallocated. - RefPtr<Image> image = new GPUVideoImage(aData.sd(), aData.display()); + RefPtr<Image> image = new GPUVideoImage(GetManager(), aData.sd(), aData.display()); RefPtr<VideoData> video = VideoData::CreateFromImage(info, aData.base().offset(), aData.base().time(), aData.base().duration(), image, aData.base().keyframe(), aData.base().timecode(), @@ -112,21 +112,27 @@ VideoDecoderChild::ActorDestroy(ActorDes mCanSend = false; } void VideoDecoderChild::InitIPDL(MediaDataDecoderCallback* aCallback, const VideoInfo& aVideoInfo, layers::KnowsCompositor* aKnowsCompositor) { - VideoDecoderManagerChild::GetSingleton()->SendPVideoDecoderConstructor(this); + RefPtr<VideoDecoderManagerChild> manager = VideoDecoderManagerChild::GetSingleton(); + if (!manager) { + return; + } mIPDLSelfRef = this; mCallback = aCallback; mVideoInfo = aVideoInfo; mKnowsCompositor = aKnowsCompositor; + if (manager->SendPVideoDecoderConstructor(this)) { + mCanSend = true; + } } void VideoDecoderChild::DestroyIPDL() { if (mCanSend) { PVideoDecoderChild::Send__delete__(this); } @@ -228,10 +234,19 @@ VideoDecoderChild::SetSeekThreshold(cons } void VideoDecoderChild::AssertOnManagerThread() { MOZ_ASSERT(NS_GetCurrentThread() == mThread); } +VideoDecoderManagerChild* +VideoDecoderChild::GetManager() +{ + if (!mCanSend) { + return nullptr; + } + return static_cast<VideoDecoderManagerChild*>(Manager()); +} + } // namespace dom } // namespace mozilla
--- a/dom/media/ipc/VideoDecoderChild.h +++ b/dom/media/ipc/VideoDecoderChild.h @@ -11,16 +11,17 @@ #include "MediaData.h" #include "PlatformDecoderModule.h" namespace mozilla { namespace dom { class RemoteVideoDecoder; class RemoteDecoderModule; +class VideoDecoderManagerChild; class VideoDecoderChild final : public PVideoDecoderChild { public: explicit VideoDecoderChild(); NS_INLINE_DECL_THREADSAFE_REFCOUNTING(VideoDecoderChild) @@ -46,16 +47,18 @@ public: void InitIPDL(MediaDataDecoderCallback* aCallback, const VideoInfo& aVideoInfo, layers::KnowsCompositor* aKnowsCompositor); void DestroyIPDL(); // Called from IPDL when our actor has been destroyed void IPDLActorDestroyed(); + VideoDecoderManagerChild* GetManager(); + private: ~VideoDecoderChild(); void AssertOnManagerThread(); RefPtr<VideoDecoderChild> mIPDLSelfRef; RefPtr<nsIThread> mThread;
--- a/dom/media/ipc/VideoDecoderManagerChild.cpp +++ b/dom/media/ipc/VideoDecoderManagerChild.cpp @@ -3,26 +3,30 @@ /* 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/. */ #include "VideoDecoderManagerChild.h" #include "VideoDecoderChild.h" #include "mozilla/dom/ContentChild.h" #include "MediaPrefs.h" #include "nsThreadUtils.h" +#include "mozilla/ipc/ProtocolUtils.h" namespace mozilla { namespace dom { using namespace ipc; using namespace layers; using namespace gfx; +// Only modified on the main-thread StaticRefPtr<nsIThread> sVideoDecoderChildThread; StaticRefPtr<AbstractThread> sVideoDecoderChildAbstractThread; + +// Only accessed from sVideoDecoderChildThread static StaticRefPtr<VideoDecoderManagerChild> sDecoderManager; /* static */ void VideoDecoderManagerChild::Initialize() { MOZ_ASSERT(NS_IsMainThread()); MediaPrefs::GetSingleton(); @@ -41,60 +45,68 @@ VideoDecoderManagerChild::Initialize() RefPtr<nsIThread> childThread; nsresult rv = NS_NewNamedThread("VideoChild", getter_AddRefs(childThread)); NS_ENSURE_SUCCESS_VOID(rv); sVideoDecoderChildThread = childThread; sVideoDecoderChildAbstractThread = AbstractThread::CreateXPCOMThreadWrapper(childThread, false); } - - Endpoint<PVideoDecoderManagerChild> endpoint; - if (!ContentChild::GetSingleton()->SendInitVideoDecoderManager(&endpoint)) { - return; - } - - if (!endpoint.IsValid()) { - return; - } - - sDecoderManager = new VideoDecoderManagerChild(); - - RefPtr<Runnable> task = NewRunnableMethod<Endpoint<PVideoDecoderManagerChild>&&>( - sDecoderManager, &VideoDecoderManagerChild::Open, Move(endpoint)); - sVideoDecoderChildThread->Dispatch(task.forget(), NS_DISPATCH_NORMAL); #else return; #endif } /* static */ void VideoDecoderManagerChild::Shutdown() { MOZ_ASSERT(NS_IsMainThread()); if (sVideoDecoderChildThread) { - MOZ_ASSERT(sDecoderManager); - sVideoDecoderChildThread->Dispatch(NS_NewRunnableFunction([]() { - sDecoderManager->Close(); - }), NS_DISPATCH_SYNC); - - sDecoderManager = nullptr; + if (sDecoderManager) { + sDecoderManager->Close(); + sDecoderManager = nullptr; + } + }), NS_DISPATCH_NORMAL); sVideoDecoderChildAbstractThread = nullptr; sVideoDecoderChildThread->Shutdown(); sVideoDecoderChildThread = nullptr; } } /* static */ VideoDecoderManagerChild* VideoDecoderManagerChild::GetSingleton() { + MOZ_ASSERT(NS_GetCurrentThread() == GetManagerThread()); + + if (!sDecoderManager || !sDecoderManager->mCanSend) { + RefPtr<VideoDecoderManagerChild> manager; + + NS_DispatchToMainThread(NS_NewRunnableFunction([&]() { + Endpoint<PVideoDecoderManagerChild> endpoint; + if (!ContentChild::GetSingleton()->SendInitVideoDecoderManager(&endpoint)) { + return; + } + + if (!endpoint.IsValid()) { + return; + } + + manager = new VideoDecoderManagerChild(); + + RefPtr<Runnable> task = NewRunnableMethod<Endpoint<PVideoDecoderManagerChild>&&>( + manager, &VideoDecoderManagerChild::Open, Move(endpoint)); + sVideoDecoderChildThread->Dispatch(task.forget(), NS_DISPATCH_NORMAL); + }), NS_DISPATCH_SYNC); + + sDecoderManager = manager; + } return sDecoderManager; } /* static */ nsIThread* VideoDecoderManagerChild::GetManagerThread() { return sVideoDecoderChildThread; } @@ -118,35 +130,43 @@ VideoDecoderManagerChild::DeallocPVideoD child->IPDLActorDestroyed(); return true; } void VideoDecoderManagerChild::Open(Endpoint<PVideoDecoderManagerChild>&& aEndpoint) { if (!aEndpoint.Bind(this)) { - // We can't recover from this. - MOZ_CRASH("Failed to bind VideoDecoderChild to endpoint"); + return; } AddRef(); + mCanSend = true; +} + +void +VideoDecoderManagerChild::ActorDestroy(ActorDestroyReason aWhy) +{ + mCanSend = false; } void VideoDecoderManagerChild::DeallocPVideoDecoderManagerChild() { Release(); } void VideoDecoderManagerChild::DeallocateSurfaceDescriptorGPUVideo(const SurfaceDescriptorGPUVideo& aSD) { RefPtr<VideoDecoderManagerChild> ref = this; SurfaceDescriptorGPUVideo sd = Move(aSD); sVideoDecoderChildThread->Dispatch(NS_NewRunnableFunction([ref, sd]() { - ref->SendDeallocateSurfaceDescriptorGPUVideo(sd); + if (ref->mCanSend) { + ref->SendDeallocateSurfaceDescriptorGPUVideo(sd); + } }), NS_DISPATCH_NORMAL); } void VideoDecoderManagerChild::FatalError(const char* const aName, const char* const aMsg) const { dom::ContentChild::FatalErrorIfNotUsingGPUProcess(aName, aMsg, OtherPid()); }
--- a/dom/media/ipc/VideoDecoderManagerChild.h +++ b/dom/media/ipc/VideoDecoderManagerChild.h @@ -12,39 +12,48 @@ namespace mozilla { namespace dom { class VideoDecoderManagerChild final : public PVideoDecoderManagerChild { public: NS_INLINE_DECL_THREADSAFE_REFCOUNTING(VideoDecoderManagerChild) + // Can only be called from the manager thread static VideoDecoderManagerChild* GetSingleton(); + + // Can be called from any thread. static nsIThread* GetManagerThread(); static AbstractThread* GetManagerAbstractThread(); - // Can be called from any thread, dispatches the request to the IPDL thread internally. + // Can be called from any thread, dispatches the request to the IPDL thread internally + // and will be ignored if the IPDL actor has been destroyed. void DeallocateSurfaceDescriptorGPUVideo(const SurfaceDescriptorGPUVideo& aSD); - void DeallocPVideoDecoderManagerChild() override; - - void FatalError(const char* const aName, const char* const aMsg) const override; - // Main thread only static void Initialize(); static void Shutdown(); protected: + void ActorDestroy(ActorDestroyReason aWhy) override; + void DeallocPVideoDecoderManagerChild() override; + + void FatalError(const char* const aName, const char* const aMsg) const override; + PVideoDecoderChild* AllocPVideoDecoderChild() override; bool DeallocPVideoDecoderChild(PVideoDecoderChild* actor) override; private: VideoDecoderManagerChild() + : mCanSend(false) {} ~VideoDecoderManagerChild() {} void Open(Endpoint<PVideoDecoderManagerChild>&& aEndpoint); + + // Should only ever be accessed on the manager thread. + bool mCanSend; }; } // namespace dom } // namespace mozilla #endif // include_dom_ipc_VideoDecoderManagerChild_h
--- a/dom/media/platforms/PDMFactory.cpp +++ b/dom/media/platforms/PDMFactory.cpp @@ -369,20 +369,18 @@ PDMFactory::CreatePDMs() // compatibility mode on Windows 7 (it does happen!) we may crash trying // to startup WMF. So we need to detect the OS version here, as in // compatibility mode IsVistaOrLater() and friends behave as if we're on // the emulated version of Windows. See bug 1279171. // Additionally, we don't want to start the RemoteDecoderModule if we // expect it's not going to work (i.e. on Windows older than Vista). m = new WMFDecoderModule(); RefPtr<PlatformDecoderModule> remote = new dom::RemoteDecoderModule(m); - mWMFFailedToLoad = !StartupPDM(remote); - if (mWMFFailedToLoad) { - mWMFFailedToLoad = !StartupPDM(m); - } + StartupPDM(remote); + mWMFFailedToLoad = !StartupPDM(m); } else { mWMFFailedToLoad = MediaPrefs::DecoderDoctorWMFDisabledIsFailure(); } #endif #ifdef MOZ_FFVPX if (MediaPrefs::PDMFFVPXEnabled()) { m = FFVPXRuntimeLinker::CreateDecoderModule(); StartupPDM(m);
--- a/dom/media/platforms/wmf/DXVA2Manager.cpp +++ b/dom/media/platforms/wmf/DXVA2Manager.cpp @@ -12,17 +12,17 @@ #include "D3D9SurfaceImage.h" #include "mozilla/gfx/DeviceManagerDx.h" #include "mozilla/layers/D3D11ShareHandleImage.h" #include "mozilla/layers/ImageBridgeChild.h" #include "mozilla/layers/TextureForwarder.h" #include "mozilla/Telemetry.h" #include "MediaTelemetryConstants.h" #include "mfapi.h" -#include "MediaPrefs.h" +#include "gfxPrefs.h" #include "MFTDecoder.h" #include "DriverCrashGuard.h" #include "nsPrintfCString.h" #include "gfxCrashReporterUtils.h" const CLSID CLSID_VideoProcessorMFT = { 0x88753b26, @@ -403,17 +403,17 @@ D3D9DXVA2Manager::Init(layers::KnowsComp D3DADAPTER_IDENTIFIER9 adapter; hr = d3d9Ex->GetAdapterIdentifier(D3DADAPTER_DEFAULT, 0, &adapter); if (!SUCCEEDED(hr)) { aFailureReason = nsPrintfCString("IDirect3D9Ex::GetAdapterIdentifier failed with error %X", hr); return hr; } - if (adapter.VendorId == 0x1022 && !MediaPrefs::PDMWMFSkipBlacklist()) { + if (adapter.VendorId == 0x1022 && !gfxPrefs::PDMWMFSkipBlacklist()) { for (size_t i = 0; i < MOZ_ARRAY_LENGTH(sAMDPreUVD4); i++) { if (adapter.DeviceId == sAMDPreUVD4[i]) { mIsAMDPreUVD4 = true; break; } } } @@ -498,21 +498,17 @@ DXVA2Manager* DXVA2Manager::CreateD3D9DXVA(layers::KnowsCompositor* aKnowsCompositor, nsACString& aFailureReason) { MOZ_ASSERT(NS_IsMainThread()); HRESULT hr; // DXVA processing takes up a lot of GPU resources, so limit the number of // videos we use DXVA with at any one time. - uint32_t dxvaLimit = 4; - // TODO: Sync this value across to the GPU process. - if (XRE_GetProcessType() != GeckoProcessType_GPU) { - dxvaLimit = MediaPrefs::PDMWMFMaxDXVAVideos(); - } + uint32_t dxvaLimit = gfxPrefs::PDMWMFMaxDXVAVideos(); if (sDXVAVideosCount == dxvaLimit) { aFailureReason.AssignLiteral("Too many DXVA videos playing"); return nullptr; } nsAutoPtr<D3D9DXVA2Manager> d3d9Manager(new D3D9DXVA2Manager()); hr = d3d9Manager->Init(aKnowsCompositor, aFailureReason); @@ -740,17 +736,17 @@ D3D11DXVA2Manager::Init(layers::KnowsCom DXGI_ADAPTER_DESC adapterDesc; hr = adapter->GetDesc(&adapterDesc); if (!SUCCEEDED(hr)) { aFailureReason = nsPrintfCString("IDXGIAdapter::GetDesc failed with code %X", hr); return hr; } - if (adapterDesc.VendorId == 0x1022 && !MediaPrefs::PDMWMFSkipBlacklist()) { + if (adapterDesc.VendorId == 0x1022 && !gfxPrefs::PDMWMFSkipBlacklist()) { for (size_t i = 0; i < MOZ_ARRAY_LENGTH(sAMDPreUVD4); i++) { if (adapterDesc.DeviceId == sAMDPreUVD4[i]) { mIsAMDPreUVD4 = true; break; } } } @@ -919,21 +915,17 @@ D3D11DXVA2Manager::ConfigureForSize(uint /* static */ DXVA2Manager* DXVA2Manager::CreateD3D11DXVA(layers::KnowsCompositor* aKnowsCompositor, nsACString& aFailureReason) { // DXVA processing takes up a lot of GPU resources, so limit the number of // videos we use DXVA with at any one time. - uint32_t dxvaLimit = 4; - // TODO: Sync this value across to the GPU process. - if (XRE_GetProcessType() != GeckoProcessType_GPU) { - dxvaLimit = MediaPrefs::PDMWMFMaxDXVAVideos(); - } + uint32_t dxvaLimit = gfxPrefs::PDMWMFMaxDXVAVideos(); if (sDXVAVideosCount == dxvaLimit) { aFailureReason.AssignLiteral("Too many DXVA videos playing"); return nullptr; } nsAutoPtr<D3D11DXVA2Manager> manager(new D3D11DXVA2Manager()); HRESULT hr = manager->Init(aKnowsCompositor, aFailureReason);
--- a/dom/media/platforms/wmf/WMFVideoMFTManager.cpp +++ b/dom/media/platforms/wmf/WMFVideoMFTManager.cpp @@ -3,29 +3,27 @@ /* 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/. */ #include <algorithm> #include <winsdkver.h> #include "WMFVideoMFTManager.h" #include "MediaDecoderReader.h" -#include "MediaPrefs.h" +#include "gfxPrefs.h" #include "WMFUtils.h" #include "ImageContainer.h" #include "VideoUtils.h" #include "DXVA2Manager.h" #include "nsThreadUtils.h" #include "Layers.h" #include "mozilla/ClearOnShutdown.h" #include "mozilla/layers/LayersTypes.h" #include "MediaInfo.h" -#include "MediaPrefs.h" #include "mozilla/Logging.h" -#include "mozilla/Preferences.h" #include "nsWindowsHelpers.h" #include "gfx2DGlue.h" #include "gfxWindowsPlatform.h" #include "IMFYCbCrImage.h" #include "mozilla/WindowsVersion.h" #include "mozilla/Telemetry.h" #include "nsPrintfCString.h" #include "MediaTelemetryConstants.h" @@ -165,55 +163,47 @@ struct D3DDLLBlacklistingCache nsCString mBlacklistedDLL; }; StaticAutoPtr<D3DDLLBlacklistingCache> sD3D11BlacklistingCache; StaticAutoPtr<D3DDLLBlacklistingCache> sD3D9BlacklistingCache; // If a blacklisted DLL is found, return its information, otherwise "". static const nsCString& FindDXVABlacklistedDLL(StaticAutoPtr<D3DDLLBlacklistingCache>& aDLLBlacklistingCache, + const nsCString& aBlacklist, const char* aDLLBlacklistPrefName) { NS_ASSERTION(NS_IsMainThread(), "Must be on main thread."); if (!aDLLBlacklistingCache) { // First time here, create persistent data that will be reused in all // D3D11-blacklisting checks. aDLLBlacklistingCache = new D3DDLLBlacklistingCache(); ClearOnShutdown(&aDLLBlacklistingCache); } - if (XRE_GetProcessType() == GeckoProcessType_GPU) { - // The blacklist code doesn't support running in - // the GPU process yet. - aDLLBlacklistingCache->mBlacklistPref.SetLength(0); - aDLLBlacklistingCache->mBlacklistedDLL.SetLength(0); - return aDLLBlacklistingCache->mBlacklistedDLL; - } - - nsAdoptingCString blacklist = Preferences::GetCString(aDLLBlacklistPrefName); - if (blacklist.IsEmpty()) { + if (aBlacklist.IsEmpty()) { // Empty blacklist -> No blacklisting. aDLLBlacklistingCache->mBlacklistPref.SetLength(0); aDLLBlacklistingCache->mBlacklistedDLL.SetLength(0); return aDLLBlacklistingCache->mBlacklistedDLL; } // Detect changes in pref. - if (aDLLBlacklistingCache->mBlacklistPref.Equals(blacklist)) { + if (aDLLBlacklistingCache->mBlacklistPref.Equals(aBlacklist)) { // Same blacklist -> Return same result (i.e., don't check DLLs again). return aDLLBlacklistingCache->mBlacklistedDLL; } // Adopt new pref now, so we don't work on it again. - aDLLBlacklistingCache->mBlacklistPref = blacklist; + aDLLBlacklistingCache->mBlacklistPref = aBlacklist; // media.wmf.disable-d3d*-for-dlls format: (whitespace is trimmed) // "dll1.dll: 1.2.3.4[, more versions...][; more dlls...]" nsTArray<nsCString> dlls; - SplitAt(";", blacklist, dlls); + SplitAt(";", aBlacklist, dlls); for (const auto& dll : dlls) { nsTArray<nsCString> nameAndVersions; SplitAt(":", dll, nameAndVersions); if (nameAndVersions.Length() != 2) { NS_WARNING(nsPrintfCString("Skipping incorrect '%s' dll:versions format", aDLLBlacklistPrefName).get()); continue; } @@ -288,22 +278,24 @@ FindDXVABlacklistedDLL(StaticAutoPtr<D3D // No blacklisted DLL. aDLLBlacklistingCache->mBlacklistedDLL.SetLength(0); return aDLLBlacklistingCache->mBlacklistedDLL; } static const nsCString& FindD3D11BlacklistedDLL() { return FindDXVABlacklistedDLL(sD3D11BlacklistingCache, + gfx::gfxVars::PDMWMFDisableD3D11Dlls(), "media.wmf.disable-d3d11-for-dlls"); } static const nsCString& FindD3D9BlacklistedDLL() { return FindDXVABlacklistedDLL(sD3D9BlacklistingCache, + gfx::gfxVars::PDMWMFDisableD3D9Dlls(), "media.wmf.disable-d3d9-for-dlls"); } class CreateDXVAManagerEvent : public Runnable { public: CreateDXVAManagerEvent(LayersBackend aBackend, layers::KnowsCompositor* aKnowsCompositor, nsCString& aFailureReason) @@ -311,20 +303,18 @@ public: , mKnowsCompositor(aKnowsCompositor) , mFailureReason(aFailureReason) {} NS_IMETHOD Run() override { NS_ASSERTION(NS_IsMainThread(), "Must be on main thread."); nsACString* failureReason = &mFailureReason; nsCString secondFailureReason; - bool allowD3D11 = (XRE_GetProcessType() == GeckoProcessType_GPU) || - MediaPrefs::PDMWMFAllowD3D11(); if (mBackend == LayersBackend::LAYERS_D3D11 && - allowD3D11 && IsWin8OrLater()) { + gfxPrefs::PDMWMFAllowD3D11() && IsWin8OrLater()) { const nsCString& blacklistedDLL = FindD3D11BlacklistedDLL(); if (!blacklistedDLL.IsEmpty()) { failureReason->AppendPrintf("D3D11 blacklisted with DLL %s", blacklistedDLL.get()); } else { mDXVA2Manager = DXVA2Manager::CreateD3D11DXVA(mKnowsCompositor, *failureReason); if (mDXVA2Manager) { return NS_OK; @@ -442,18 +432,17 @@ WMFVideoMFTManager::InitInternal(bool aF NS_ENSURE_TRUE(SUCCEEDED(hr), false); RefPtr<IMFAttributes> attr(decoder->GetAttributes()); UINT32 aware = 0; if (attr) { attr->GetUINT32(MF_SA_D3D_AWARE, &aware); attr->SetUINT32(CODECAPI_AVDecNumWorkerThreads, WMFDecoderModule::GetNumDecoderThreads()); - if ((XRE_GetProcessType() != GeckoProcessType_GPU) && - MediaPrefs::PDMWMFLowLatencyEnabled()) { + if (gfxPrefs::PDMWMFLowLatencyEnabled()) { hr = attr->SetUINT32(CODECAPI_AVLowLatencyMode, TRUE); if (SUCCEEDED(hr)) { LOG("Enabling Low Latency Mode"); } else { LOG("Couldn't enable Low Latency Mode"); } } }
--- a/dom/media/tests/mochitest/head.js +++ b/dom/media/tests/mochitest/head.js @@ -228,57 +228,68 @@ function realCreateHTML(meta) { document.body.insertBefore(display, test); var content = document.createElement('div'); content.setAttribute('id', 'content'); content.style.display = meta.visible ? 'block' : "none"; document.body.appendChild(content); } -function getMediaElement(label, direction, streamId) { - var id = label + '_' + direction + '_' + streamId; - return document.getElementById(id); -} - /** - * Create the HTML element if it doesn't exist yet and attach - * it to the content node. + * Creates an element of the given type, assigns the given id, sets the controls + * and autoplay attributes and adds it to the content node. * - * @param {string} label - * Prefix to use for the element - * @param {direction} "local" or "remote" - * @param {stream} A MediaStream id. - * @param {audioOnly} Use <audio> element instead of <video> - * @return {HTMLMediaElement} The created HTML media element + * @param {string} type + * Defining if we should create an "audio" or "video" element + * @param {string} id + * A string to use as the element id. */ -function createMediaElement(label, direction, streamId, audioOnly) { - var id = label + '_' + direction + '_' + streamId; - var element = document.getElementById(id); - - // Sanity check that we haven't created the element already - if (element) { - return element; - } - - if (!audioOnly) { - // Even if this is just audio now, we might add video later. - element = document.createElement('video'); - } else { - element = document.createElement('audio'); - } +function createMediaElement(type, id) { + const element = document.createElement(type); element.setAttribute('id', id); element.setAttribute('height', 100); element.setAttribute('width', 150); element.setAttribute('controls', 'controls'); element.setAttribute('autoplay', 'autoplay'); document.getElementById('content').appendChild(element); return element; } +/** + * Returns an existing element for the given track with the given idPrefix, + * as it was added by createMediaElementForTrack(). + * + * @param {MediaStreamTrack} track + * Track used as the element's source. + * @param {string} idPrefix + * A string to use as the element id. The track id will also be appended. + */ +function getMediaElementForTrack(track, idPrefix) { + return document.getElementById(idPrefix + '_' + track.id); +} + +/** + * Create a media element with a track as source and attach it to the content + * node. + * + * @param {MediaStreamTrack} track + * Track for use as source. + * @param {string} idPrefix + * A string to use as the element id. The track id will also be appended. + * @return {HTMLMediaElement} The created HTML media element + */ +function createMediaElementForTrack(track, idPrefix) { + const id = idPrefix + '_' + track.id; + const element = createMediaElement(track.kind, id); + element.srcObject = new MediaStream([track]); + + return element; +} + /** * Wrapper function for mediaDevices.getUserMedia used by some tests. Whether * to use fake devices or not is now determined in pref further below instead. * * @param {Dictionary} constraints * The constraints for this mozGetUserMedia callback */ @@ -425,32 +436,36 @@ function checkMediaStreamCloneAgainstOri isnot(clone, original, "Stream clone should be different from the original"); isnot(clone.id, original.id, "Stream clone's id should be different from the original's"); is(clone.getAudioTracks().length, original.getAudioTracks().length, "All audio tracks should get cloned"); is(clone.getVideoTracks().length, original.getVideoTracks().length, "All video tracks should get cloned"); + is(clone.active, original.active, + "Active state should be preserved"); original.getTracks() .forEach(t => ok(!clone.getTrackById(t.id), "The clone's tracks should be originals")); } function checkMediaStreamTrackCloneAgainstOriginal(clone, original) { isnot(clone.id.length, 0, "Track clone should have an id string"); isnot(clone, original, "Track clone should be different from the original"); isnot(clone.id, original.id, "Track clone's id should be different from the original's"); is(clone.kind, original.kind, "Track clone's kind should be same as the original's"); is(clone.enabled, original.enabled, "Track clone's kind should be same as the original's"); + is(clone.readyState, original.readyState, + "Track clone's readyState should be same as the original's"); } /*** Utility methods */ /** The dreadful setTimeout, use sparingly */ function wait(time, message) { return new Promise(r => setTimeout(() => r(message), time)); }
--- a/dom/media/tests/mochitest/mediaStreamPlayback.js +++ b/dom/media/tests/mochitest/mediaStreamPlayback.js @@ -43,44 +43,20 @@ MediaStreamPlayback.prototype = { * Stops the local media stream's tracks while it's currently in playback in * a media element. * * Precondition: The media stream and element should both be actively * being played. All the stream's tracks must be local. */ stopTracksForStreamInMediaPlayback : function () { var elem = this.mediaElement; - var waitForEnded = () => new Promise(resolve => { - elem.addEventListener('ended', function ended() { - elem.removeEventListener('ended', ended); - resolve(); - }); - }); - - var noTrackEnded = Promise.all(this.mediaStream.getTracks().map(t => { - let onNextLoop = wait(0); - let p = Promise.race([ - onNextLoop, - haveEvent(t, "ended", onNextLoop) - .then(() => Promise.reject("Unexpected ended event for track " + t.id), - () => Promise.resolve()) - ]); - t.stop(); - return p; - })); - - // XXX (bug 1208316) When we implement MediaStream.active, do not stop - // the stream. We just do it now so the media element will raise 'ended'. - if (!this.mediaStream.stop) { - return; - } - this.mediaStream.stop(); - return timeout(waitForEnded(), ENDED_TIMEOUT_LENGTH, "ended event never fired") - .then(() => ok(true, "ended event successfully fired")) - .then(() => noTrackEnded); + return Promise.all([ + haveEvent(elem, "ended", wait(ENDED_TIMEOUT_LENGTH, new Error("Timeout"))), + ...this.mediaStream.getTracks().map(t => (t.stop(), haveNoEvent(t, "ended"))) + ]); }, /** * Starts media with a media stream, runs it until a canplaythrough and * timeupdate event fires, and detaches from the element without stopping media. * * @param {Boolean} isResume specifies if this media element is being resumed * from a previous run
--- a/dom/media/tests/mochitest/mochitest.ini +++ b/dom/media/tests/mochitest/mochitest.ini @@ -36,16 +36,17 @@ skip-if = android_version == '18' # andr [test_dataChannel_basicVideo.html] skip-if = android_version == '18' # android(Bug 1189784, timeouts on 4.3 emulator) [test_dataChannel_bug1013809.html] skip-if = android_version == '18' # android(Bug 1189784, timeouts on 4.3 emulator) [test_dataChannel_noOffer.html] [test_enumerateDevices.html] [test_ondevicechange.html] skip-if = os == 'android' +[test_getUserMedia_active_autoplay.html] [test_getUserMedia_audioCapture.html] skip-if = android_version == '18' # android(Bug 1189784, timeouts on 4.3 emulator) [test_getUserMedia_addTrackRemoveTrack.html] [test_getUserMedia_addtrack_removetrack_events.html] [test_getUserMedia_basicAudio.html] [test_getUserMedia_basicVideo.html] [test_getUserMedia_basicVideo_playAfterLoadedmetadata.html] [test_getUserMedia_basicScreenshare.html]
--- a/dom/media/tests/mochitest/pc.js +++ b/dom/media/tests/mochitest/pc.js @@ -857,33 +857,33 @@ PeerConnectionWrapper.prototype = { get iceConnectionState() { return this._pc.iceConnectionState; }, setIdentityProvider: function(provider, protocol, identity) { this._pc.setIdentityProvider(provider, protocol, identity); }, - ensureMediaElement : function(track, stream, direction) { - var element = getMediaElement(this.label, direction, stream.id); + ensureMediaElement : function(track, direction) { + const idPrefix = [this.label, direction].join('_'); + var element = getMediaElementForTrack(track, idPrefix); if (!element) { - element = createMediaElement(this.label, direction, stream.id, - this.audioElementsOnly); + element = createMediaElementForTrack(track, idPrefix); if (direction == "local") { this.localMediaElements.push(element); } else if (direction == "remote") { this.remoteMediaElements.push(element); } } // We do this regardless, because sometimes we end up with a new stream with // an old id (ie; the rollback tests cause the same stream to be added // twice) - element.srcObject = stream; + element.srcObject = new MediaStream([track]); element.play(); }, /** * Attaches a local track to this RTCPeerConnection using * RTCPeerConnection.addTrack(). * * Also creates a media element playing a MediaStream containing all @@ -907,24 +907,24 @@ PeerConnectionWrapper.prototype = { this.expectedLocalTrackInfoById[track.id] = { type: track.kind, streamId: stream.id, }; // This will create one media element per track, which might not be how // we set up things with the RTCPeerConnection. It's the only way // we can ensure all sent tracks are flowing however. - this.ensureMediaElement(track, new MediaStream([track]), "local"); + this.ensureMediaElement(track, "local"); return this.observedNegotiationNeeded; }, /** * Callback when we get local media. Also an appropriate HTML media element - * will be created, which may be obtained later with |getMediaElement|. + * will be created and added to the content node. * * @param {MediaStream} stream * Media stream to handle */ attachLocalStream : function(stream) { info("Got local media stream: (" + stream.id + ")"); this.expectNegotiationNeeded(); @@ -945,17 +945,17 @@ PeerConnectionWrapper.prototype = { stream.getTracks().forEach(track => { ok(track.id, "track has id"); ok(track.kind, "track has kind"); this.expectedLocalTrackInfoById[track.id] = { type: track.kind, streamId: stream.id }; - this.ensureMediaElement(track, stream, "local"); + this.ensureMediaElement(track, "local"); }); }, removeSender : function(index) { var sender = this._pc.getSenders()[index]; delete this.expectedLocalTrackInfoById[sender.track.id]; this.expectNegotiationNeeded(); this._pc.removeTrack(sender); @@ -1176,17 +1176,17 @@ PeerConnectionWrapper.prototype = { this._pc.addEventListener('track', event => { info(this + ": 'ontrack' event fired for " + JSON.stringify(event.track)); this.checkTrackIsExpected(event.track, this.expectedRemoteTrackInfoById, this.observedRemoteTrackInfoById); ok(this.isTrackOnPC(event.track), "Found track " + event.track.id); - this.ensureMediaElement(event.track, event.streams[0], 'remote'); + this.ensureMediaElement(event.track, 'remote'); }); }, /** * Either adds a given ICE candidate right away or stores it to be added * later, depending on the state of the PeerConnection. * * @param {object} candidate @@ -1363,55 +1363,52 @@ PeerConnectionWrapper.prototype = { id => { if (!this.observedRemoteTrackInfoById[id].negotiated) { delete this.observedRemoteTrackInfoById[id]; } }); }, /** - * Check that media flow is present on the given media element by waiting for - * it to reach ready state HAVE_ENOUGH_DATA and progress time further than - * the start of the check. + * Check that media flow is present for the given media element by checking + * that it reaches ready state HAVE_ENOUGH_DATA and progresses time further + * than the start of the check. * * This ensures, that the stream being played is producing - * data and that at least one video frame has been displayed. + * data and, in case it contains a video track, that at least one video frame + * has been displayed. * - * @param {object} element - * A media element to wait for data flow on. + * @param {HTMLMediaElement} track + * The media element to check * @returns {Promise} - * A promise that resolves when media is flowing. + * A promise that resolves when media data is flowing. */ waitForMediaElementFlow : function(element) { - return new Promise(resolve => { - info("Checking data flow to element: " + element.id); - if (element.ended && element.readyState >= element.HAVE_CURRENT_DATA) { - resolve(); - return; - } - var haveEnoughData = false; - var oncanplay = () => { - info("Element " + element.id + " saw 'canplay', " + - "meaning HAVE_ENOUGH_DATA was just reached."); - haveEnoughData = true; - element.removeEventListener("canplay", oncanplay); - }; - var ontimeupdate = () => { - info("Element " + element.id + " saw 'timeupdate'" + - ", currentTime=" + element.currentTime + - "s, readyState=" + element.readyState); - if (haveEnoughData || element.readyState == element.HAVE_ENOUGH_DATA) { - element.removeEventListener("timeupdate", ontimeupdate); - ok(true, "Media flowing for element: " + element.id); - resolve(); - } - }; - element.addEventListener("canplay", oncanplay); - element.addEventListener("timeupdate", ontimeupdate); - }); + info("Checking data flow for element: " + element.id); + is(element.ended, !element.srcObject.active, + "Element ended should be the inverse of the MediaStream's active state"); + if (element.ended) { + is(element.readyState, element.HAVE_CURRENT_DATA, + "Element " + element.id + " is ended and should have had data"); + return Promise.resolve(); + } + + const haveEnoughData = (element.readyState == element.HAVE_ENOUGH_DATA ? + Promise.resolve() : + haveEvent(element, "canplay", wait(60000, + new Error("Timeout for element " + element.id)))) + .then(_ => info("Element " + element.id + " has enough data.")); + + const startTime = element.currentTime; + const timeProgressed = timeout( + listenUntil(element, "timeupdate", _ => element.currentTime > startTime), + 60000, "Element " + element.id + " should progress currentTime") + .then(); + + return Promise.all([haveEnoughData, timeProgressed]); }, /** * Wait for RTP packet flow for the given MediaStreamTrack. * * @param {object} track * A MediaStreamTrack to wait for data flow on. * @returns {Promise}
new file mode 100644 --- /dev/null +++ b/dom/media/tests/mochitest/test_getUserMedia_active_autoplay.html @@ -0,0 +1,61 @@ +<!DOCTYPE HTML> +<html> +<head> + <script type="application/javascript" src="mediaStreamPlayback.js"></script> +</head> +<body> +<pre id="test"> +<video id="testAutoplay" autoplay></video> +<script type="application/javascript"> +"use strict"; + +const video = document.getElementById("testAutoplay"); +var stream; +var otherVideoTrack; +var otherAudioTrack; + +createHTML({ + title: "MediaStream can be autoplayed in media element after going inactive and then active", + bug: "1208316" +}); + +runTest(() => getUserMedia({audio: true, video: true}).then(s => { + stream = s; + otherVideoTrack = stream.getVideoTracks()[0].clone(); + otherAudioTrack = stream.getAudioTracks()[0].clone(); + + video.srcObject = stream; + return haveEvent(video, "playing", wait(5000, new Error("Timeout"))); +}) +.then(() => { + ok(!video.ended, "Video element should be playing after adding a gUM stream"); + stream.getTracks().forEach(t => t.stop()); + return haveEvent(video, "ended", wait(5000, new Error("Timeout"))); +}) +.then(() => { + ok(video.ended, "Video element should be ended"); + stream.addTrack(otherVideoTrack); + return haveEvent(video, "playing", wait(5000, new Error("Timeout"))); +}) +.then(() => { + ok(!video.ended, "Video element should be playing after adding a video track"); + stream.getTracks().forEach(t => t.stop()); + return haveEvent(video, "ended", wait(5000, new Error("Timeout"))); +}) +.then(() => { + ok(video.ended, "Video element should be ended"); + stream.addTrack(otherAudioTrack); + return haveEvent(video, "playing", wait(5000, new Error("Timeout"))); +}) +.then(() => { + ok(!video.ended, "Video element should be playing after adding a audio track"); + stream.getTracks().forEach(t => t.stop()); + return haveEvent(video, "ended", wait(5000, new Error("Timeout"))); +}) +.then(() => { + ok(video.ended, "Video element should be ended"); +})); +</script> +</pre> +</body> +</html>
--- a/dom/media/tests/mochitest/test_getUserMedia_mediaElementCapture_audio.html +++ b/dom/media/tests/mochitest/test_getUserMedia_mediaElementCapture_audio.html @@ -14,17 +14,17 @@ createHTML({ visible: true }); var audioContext; var gUMAudioElement; var analyser; runTest(() => getUserMedia({audio: true}) .then(stream => { - gUMAudioElement = createMediaElement("gUMAudio", "local", "gUMAudio", true); + gUMAudioElement = createMediaElement("audio", "gUMAudio"); gUMAudioElement.srcObject = stream; audioContext = new AudioContext(); info("Capturing"); analyser = new AudioStreamAnalyser(audioContext, gUMAudioElement.mozCaptureStream()); analyser.enableDebugCanvas();
--- a/dom/media/tests/mochitest/test_getUserMedia_mediaElementCapture_tracks.html +++ b/dom/media/tests/mochitest/test_getUserMedia_mediaElementCapture_tracks.html @@ -21,34 +21,34 @@ var videoCaptureStream; var untilEndedElement; var streamUntilEnded; var tracks = []; runTest(() => getUserMedia({audio: true, video: true}) .then(stream => { // We need to test with multiple tracks. We add an extra of each kind. stream.getTracks().forEach(t => stream.addTrack(t.clone())); - audioElement = createMediaElement("gUMAudio", "local", "gUMAudio", true); + audioElement = createMediaElement("audio", "gUMAudio"); audioElement.srcObject = stream; return haveEvent(audioElement, "loadedmetadata", wait(50000, new Error("Timeout"))); }) .then(() => { info("Capturing audio element (loadedmetadata -> captureStream)"); audioCaptureStream = audioElement.mozCaptureStream(); is(audioCaptureStream.getAudioTracks().length, 2, "audio element should capture two audio tracks"); is(audioCaptureStream.getVideoTracks().length, 0, "audio element should not capture any video tracks"); return haveNoEvent(audioCaptureStream, "addtrack"); }) .then(() => { - videoElement = createMediaElement("gUMVideo", "local", "gUMVideo", false); + videoElement = createMediaElement("video", "gUMVideo"); info("Capturing video element (captureStream -> loadedmetadata)"); videoCaptureStream = videoElement.mozCaptureStream(); videoElement.srcObject = audioElement.srcObject.clone(); is(videoCaptureStream.getTracks().length, 0, "video element should have no tracks before metadata known"); @@ -138,17 +138,17 @@ runTest(() => getUserMedia({audio: true, .filter(t => t.readyState == "ended").length, 1, "Captured video stream should have one ended video tracks"); is(videoCaptureStream.getVideoTracks() .filter(t => t.readyState == "live").length, 1, "Captured video stream should have one live video track"); info("Testing CaptureStreamUntilEnded"); untilEndedElement = - createMediaElement("gUMVideoUntilEnded", "local", "gUMVideoUntilEnded", false); + createMediaElement("video", "gUMVideoUntilEnded"); untilEndedElement.srcObject = audioElement.srcObject; return haveEvent(untilEndedElement, "loadedmetadata", wait(50000, new Error("Timeout"))); }) .then(() => { streamUntilEnded = untilEndedElement.mozCaptureStreamUntilEnded();
--- a/dom/media/tests/mochitest/test_getUserMedia_mediaElementCapture_video.html +++ b/dom/media/tests/mochitest/test_getUserMedia_mediaElementCapture_video.html @@ -57,26 +57,34 @@ var checkVideoPaused = video => checkHas Array.slice(startPixel.data) + "]. Pass=" + result); return result; }, pausedTimeout); }).then(result => ok(!result, "Frame shouldn't change within " + pausedTimeout / 1000 + " seconds.")); runTest(() => getUserMedia({video: true, fake: true}) .then(stream => { gUMVideoElement = - createMediaElement("gUMVideo", "local", "gUMVideo", false); + createMediaElement("video", "gUMVideo"); gUMVideoElement.srcObject = stream; gUMVideoElement.play(); info("Capturing"); captureStreamElement = - createMediaElement("captureStream", "local", "captureStream", false); + createMediaElement("video", "captureStream"); captureStreamElement.srcObject = gUMVideoElement.mozCaptureStream(); captureStreamElement.play(); + // Adding a dummy audio track to the stream will keep a consuming media + // element from ending. + // We could also solve it by repeatedly play()ing or autoplay, but then we + // wouldn't be sure the media element stopped rendering video because it + // went to the ended state or because there were no frames for the track. + let osc = createOscillatorStream(new AudioContext(), 1000); + captureStreamElement.srcObject.addTrack(osc.getTracks()[0]); + return checkVideoPlaying(captureStreamElement); }) .then(() => { info("Video flowing. Pausing."); gUMVideoElement.pause(); return checkVideoPaused(captureStreamElement); })
--- a/dom/media/tests/mochitest/test_getUserMedia_mediaStreamConstructors.html +++ b/dom/media/tests/mochitest/test_getUserMedia_mediaStreamConstructors.html @@ -15,74 +15,104 @@ var audioContext = new AudioContext(); var videoElement; runTest(() => Promise.resolve() .then(() => videoElement = createMediaElement('video', 'constructorsTest')) .then(() => getUserMedia({video: true})).then(gUMStream => { info("Test default constructor with video"); + ok(gUMStream.active, "gUMStream with one track should be active"); var track = gUMStream.getTracks()[0]; var stream = new MediaStream(); + ok(!stream.active, "New MediaStream should be inactive"); checkMediaStreamContains(stream, [], "Default constructed stream"); stream.addTrack(track); + ok(stream.active, "MediaStream should be active after adding a track"); checkMediaStreamContains(stream, [track], "Added video track"); var playback = new MediaStreamPlayback(videoElement, stream); - return playback.playMedia(false).then(() => gUMStream.stop()); + return playback.playMedia(false).then(() => { + ok(!gUMStream.active, "gUMStream should be inactive after stopping"); + ok(!stream.active, "stream with stopped tracks should be inactive"); + }); }) .then(() => getUserMedia({video: true})).then(gUMStream => { info("Test copy constructor with gUM stream"); + ok(gUMStream.active, "gUMStream with one track should be active"); var track = gUMStream.getTracks()[0]; var stream = new MediaStream(gUMStream); + ok(stream.active, "List constructed MediaStream should be active"); checkMediaStreamContains(stream, [track], "Copy constructed video track"); var playback = new MediaStreamPlayback(videoElement, stream); - return playback.playMedia(false).then(() => gUMStream.stop()); + return playback.playMedia(false).then(() => { + ok(!gUMStream.active, "gUMStream should be inactive after stopping"); + ok(!stream.active, "stream with stopped tracks should be inactive"); + }); }) .then(() => getUserMedia({video: true})).then(gUMStream => { info("Test list constructor with empty list"); + ok(gUMStream.active, "gUMStream with one track should be active"); var track = gUMStream.getTracks()[0]; var stream = new MediaStream([]); + ok(!stream.active, "Empty-list constructed MediaStream should be inactive"); checkMediaStreamContains(stream, [], "Empty-list constructed stream"); stream.addTrack(track); + ok(stream.active, "MediaStream should be active after adding a track"); checkMediaStreamContains(stream, [track], "Added video track"); var playback = new MediaStreamPlayback(videoElement, stream); - return playback.playMedia(false).then(() => gUMStream.stop()); + return playback.playMedia(false).then(() => { + ok(!gUMStream.active, "gUMStream should be inactive after stopping"); + ok(!stream.active, "stream with stopped tracks should be inactive"); + }); }) .then(() => getUserMedia({audio: true, video: true})).then(gUMStream => { info("Test list constructor with a gUM audio/video stream"); + ok(gUMStream.active, "gUMStream with two tracks should be active"); var audioTrack = gUMStream.getAudioTracks()[0]; var videoTrack = gUMStream.getVideoTracks()[0]; var stream = new MediaStream([audioTrack, videoTrack]); + ok(stream.active, "List constructed MediaStream should be active"); checkMediaStreamContains(stream, [audioTrack, videoTrack], "List constructed audio and video tracks"); var playback = new MediaStreamPlayback(videoElement, stream); - return playback.playMedia(false).then(() => gUMStream.stop()); + return playback.playMedia(false).then(() => { + ok(!gUMStream.active, "gUMStream should be inactive after stopping"); + ok(!stream.active, "stream with stopped tracks should be inactive"); + }); }) .then(() => getUserMedia({video: true})).then(gUMStream => { info("Test list constructor with gUM-video and WebAudio tracks"); + ok(gUMStream.active, "gUMStream with one track should be active"); var audioStream = createOscillatorStream(audioContext, 2000); + ok(audioStream.active, "WebAudio stream should be active"); + var audioTrack = audioStream.getTracks()[0]; var videoTrack = gUMStream.getTracks()[0]; var stream = new MediaStream([audioTrack, videoTrack]); + ok(stream.active, "List constructed MediaStream should be active"); checkMediaStreamContains(stream, [audioTrack, videoTrack], "List constructed WebAudio and gUM-video tracks"); var playback = new MediaStreamPlayback(videoElement, stream); - return playback.playMedia(false).then(() => gUMStream.stop()); + return playback.playMedia(false).then(() => { + gUMStream.getTracks().forEach(t => t.stop()); + ok(!gUMStream.active, "gUMStream should be inactive after stopping"); + ok(!stream.active, "stream with stopped tracks should be inactive"); + }); }) .then(() => { var osc1k = createOscillatorStream(audioContext, 1000); var audioTrack1k = osc1k.getTracks()[0]; var osc5k = createOscillatorStream(audioContext, 5000); var audioTrack5k = osc5k.getTracks()[0]; @@ -96,16 +126,18 @@ return analyser.waitForAnalysisSuccess(array => array[analyser.binIndexForFrequency(1000)] < 50 && array[analyser.binIndexForFrequency(5000)] < 50 && array[analyser.binIndexForFrequency(10000)] < 50) .then(() => analyser.disconnect()); }).then(() => { info("Analysing audio output with copy constructed 5k stream"); var stream = new MediaStream(osc5k); + is(stream.active, osc5k.active, + "Copy constructed MediaStream should preserve active state"); var analyser = new AudioStreamAnalyser(audioContext, stream); return analyser.waitForAnalysisSuccess(array => array[analyser.binIndexForFrequency(1000)] < 50 && array[analyser.binIndexForFrequency(5000)] > 200 && array[analyser.binIndexForFrequency(10000)] < 50) .then(() => analyser.disconnect()); }).then(() => { info("Analysing audio output with empty-list constructed stream"); @@ -114,16 +146,18 @@ return analyser.waitForAnalysisSuccess(array => array[analyser.binIndexForFrequency(1000)] < 50 && array[analyser.binIndexForFrequency(5000)] < 50 && array[analyser.binIndexForFrequency(10000)] < 50) .then(() => analyser.disconnect()); }).then(() => { info("Analysing audio output with list constructed 1k, 5k and 10k tracks"); var stream = new MediaStream([audioTrack1k, audioTrack5k, audioTrack10k]); + ok(stream.active, + "List constructed MediaStream from WebAudio should be active"); var analyser = new AudioStreamAnalyser(audioContext, stream); return analyser.waitForAnalysisSuccess(array => array[analyser.binIndexForFrequency(50)] < 50 && array[analyser.binIndexForFrequency(1000)] > 200 && array[analyser.binIndexForFrequency(2500)] < 50 && array[analyser.binIndexForFrequency(5000)] > 200 && array[analyser.binIndexForFrequency(7500)] < 50 && array[analyser.binIndexForFrequency(10000)] > 200 &&
--- a/dom/media/tests/mochitest/test_getUserMedia_mediaStreamTrackClone.html +++ b/dom/media/tests/mochitest/test_getUserMedia_mediaStreamTrackClone.html @@ -89,54 +89,16 @@ var audioTrack1kOriginal = osc1kOriginal.getTracks()[0]; var audioTrack1kClone = audioTrack1kOriginal.clone(); var osc5kOriginal = createOscillatorStream(ac, 5000); var audioTrack5kOriginal = osc5kOriginal.getTracks()[0]; var audioTrack5kClone = audioTrack5kOriginal.clone(); return Promise.resolve().then(() => { - info("Analysing audio output of 1k original and 5k clone."); - var stream = new MediaStream(); - stream.addTrack(audioTrack1kOriginal); - stream.addTrack(audioTrack5kClone); - - var analyser = new AudioStreamAnalyser(ac, stream); - return analyser.waitForAnalysisSuccess(array => - array[analyser.binIndexForFrequency(50)] < 50 && - array[analyser.binIndexForFrequency(1000)] > 200 && - array[analyser.binIndexForFrequency(3000)] < 50 && - array[analyser.binIndexForFrequency(5000)] > 200 && - array[analyser.binIndexForFrequency(10000)] < 50) - .then(() => { - info("Waiting for original tracks to stop"); - stream.getTracks().forEach(t => t.stop()); - return analyser.waitForAnalysisSuccess(array => - array[analyser.binIndexForFrequency(50)] < 50 && - // WebAudioDestination streams do not handle stop() - // XXX Should they? Plan to resolve that in bug 1208384. - // array[analyser.binIndexForFrequency(1000)] < 50 && - array[analyser.binIndexForFrequency(3000)] < 50 && - // array[analyser.binIndexForFrequency(5000)] < 50 && - array[analyser.binIndexForFrequency(10000)] < 50); - }).then(() => analyser.disconnect()); - }).then(() => { - info("Analysing audio output of clones of clones (1kx2 + 5kx5)"); - var stream = new MediaStream([audioTrack1kClone.clone(), - audioTrack5kClone.clone().clone().clone().clone()]); - - var analyser = new AudioStreamAnalyser(ac, stream); - return analyser.waitForAnalysisSuccess(array => - array[analyser.binIndexForFrequency(50)] < 50 && - array[analyser.binIndexForFrequency(1000)] > 200 && - array[analyser.binIndexForFrequency(3000)] < 50 && - array[analyser.binIndexForFrequency(5000)] > 200 && - array[analyser.binIndexForFrequency(10000)] < 50) - .then(() => analyser.disconnect()); - }).then(() => { info("Analysing audio output enabled and disabled tracks that don't affect each other"); audioTrack1kOriginal.enabled = true; audioTrack5kOriginal.enabled = false; audioTrack1kClone.enabled = false; audioTrack5kClone.enabled = true; var analyser = @@ -159,14 +121,50 @@ array[cloneAnalyser.binIndexForFrequency(10000)] < 50) .then(() => cloneAnalyser.disconnect()); }) // Restore original tracks .then(() => [audioTrack1kOriginal, audioTrack5kOriginal, audioTrack1kClone, audioTrack5kClone].forEach(t => t.enabled = true)); + }).then(() => { + info("Analysing audio output of 1k original and 5k clone."); + var stream = new MediaStream(); + stream.addTrack(audioTrack1kOriginal); + stream.addTrack(audioTrack5kClone); + + var analyser = new AudioStreamAnalyser(ac, stream); + return analyser.waitForAnalysisSuccess(array => + array[analyser.binIndexForFrequency(50)] < 50 && + array[analyser.binIndexForFrequency(1000)] > 200 && + array[analyser.binIndexForFrequency(3000)] < 50 && + array[analyser.binIndexForFrequency(5000)] > 200 && + array[analyser.binIndexForFrequency(10000)] < 50) + .then(() => { + info("Waiting for tracks to stop"); + stream.getTracks().forEach(t => t.stop()); + return analyser.waitForAnalysisSuccess(array => + array[analyser.binIndexForFrequency(50)] < 50 && + array[analyser.binIndexForFrequency(1000)] < 50 && + array[analyser.binIndexForFrequency(3000)] < 50 && + array[analyser.binIndexForFrequency(5000)] < 50 && + array[analyser.binIndexForFrequency(10000)] < 50); + }).then(() => analyser.disconnect()); + }).then(() => { + info("Analysing audio output of clones of clones (1kx2 + 5kx4)"); + var stream = new MediaStream([audioTrack1kClone.clone(), + audioTrack5kOriginal.clone().clone().clone().clone()]); + + var analyser = new AudioStreamAnalyser(ac, stream); + return analyser.waitForAnalysisSuccess(array => + array[analyser.binIndexForFrequency(50)] < 50 && + array[analyser.binIndexForFrequency(1000)] > 200 && + array[analyser.binIndexForFrequency(3000)] < 50 && + array[analyser.binIndexForFrequency(5000)] > 200 && + array[analyser.binIndexForFrequency(10000)] < 50) + .then(() => analyser.disconnect()); }); })); </script> </pre> </body> </html>
--- a/dom/media/tests/mochitest/test_peerConnection_trackDisabling.html +++ b/dom/media/tests/mochitest/test_peerConnection_trackDisabling.html @@ -15,29 +15,31 @@ createHTML({ runNetworkTest(() => { var test = new PeerConnectionTest(); // Always use fake tracks since we depend on video to be somewhat green and // audio to have a large 1000Hz component (or 440Hz if using fake devices). test.setMediaConstraints([{audio: true, video: true, fake: true}], []); test.chain.append([ function CHECK_ASSUMPTIONS() { - is(test.pcLocal.localMediaElements.length, 1, + is(test.pcLocal.localMediaElements.length, 2, "pcLocal should have one media element"); - is(test.pcRemote.remoteMediaElements.length, 1, + is(test.pcRemote.remoteMediaElements.length, 2, "pcRemote should have one media element"); is(test.pcLocal._pc.getLocalStreams().length, 1, "pcLocal should have one stream"); is(test.pcRemote._pc.getRemoteStreams().length, 1, "pcRemote should have one stream"); }, function CHECK_VIDEO() { var h = new CaptureStreamTestHelper2D(); - var localVideo = test.pcLocal.localMediaElements[0]; - var remoteVideo = test.pcRemote.remoteMediaElements[0]; + var localVideo = test.pcLocal.localMediaElements + .find(e => e instanceof HTMLVideoElement); + var remoteVideo = test.pcRemote.remoteMediaElements + .find(e => e instanceof HTMLVideoElement); // We check a pixel somewhere away from the top left corner since // MediaEngineDefault puts semi-transparent time indicators there. const offsetX = 50; const offsetY = 50; const threshold = 128; // We're regarding black as disabled here, and we're setting the alpha // channel of the pixel to 255 to disregard alpha when testing.
--- a/dom/media/tests/mochitest/test_peerConnection_trackDisabling_clones.html +++ b/dom/media/tests/mochitest/test_peerConnection_trackDisabling_clones.html @@ -21,38 +21,40 @@ runNetworkTest(() => { // Always use fake tracks since we depend on audio to have a large 1000Hz // component. test.setMediaConstraints([{audio: true, video: true, fake: true}], []); test.chain.replace("PC_LOCAL_GUM", [ function PC_LOCAL_GUM_CLONE() { return getUserMedia(test.pcLocal.constraints[0]).then(stream => { originalStream = stream; localVideoOriginal = - createMediaElement("audiovideo", "local-original"); + createMediaElement("video", "local-original"); localVideoOriginal.srcObject = stream; test.pcLocal.attachLocalStream(originalStream.clone()); }); } ]); test.chain.append([ function CHECK_ASSUMPTIONS() { - is(test.pcLocal.localMediaElements.length, 1, + is(test.pcLocal.localMediaElements.length, 2, "pcLocal should have one media element"); - is(test.pcRemote.remoteMediaElements.length, 1, + is(test.pcRemote.remoteMediaElements.length, 2, "pcRemote should have one media element"); is(test.pcLocal._pc.getLocalStreams().length, 1, "pcLocal should have one stream"); is(test.pcRemote._pc.getRemoteStreams().length, 1, "pcRemote should have one stream"); }, function CHECK_VIDEO() { info("Checking video"); var h = new CaptureStreamTestHelper2D(); - var localVideoClone = test.pcLocal.localMediaElements[0]; - var remoteVideoClone = test.pcRemote.remoteMediaElements[0]; + var localVideoClone = test.pcLocal.localMediaElements + .find(e => e instanceof HTMLVideoElement); + var remoteVideoClone = test.pcRemote.remoteMediaElements + .find(e => e instanceof HTMLVideoElement); // We check a pixel somewhere away from the top left corner since // MediaEngineDefault puts semi-transparent time indicators there. const offsetX = 50; const offsetY = 50; const threshold = 128; const remoteDisabledColor = h.black;
--- a/dom/media/webaudio/MediaStreamAudioDestinationNode.cpp +++ b/dom/media/webaudio/MediaStreamAudioDestinationNode.cpp @@ -11,16 +11,65 @@ #include "AudioNodeStream.h" #include "DOMMediaStream.h" #include "MediaStreamTrack.h" #include "TrackUnionStream.h" namespace mozilla { namespace dom { +class AudioDestinationTrackSource : + public MediaStreamTrackSource +{ +public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(AudioDestinationTrackSource, + MediaStreamTrackSource) + + AudioDestinationTrackSource(MediaStreamAudioDestinationNode* aNode, + nsIPrincipal* aPrincipal) + : MediaStreamTrackSource(aPrincipal, nsString()) + , mNode(aNode) + { + } + + void Destroy() override + { + if (mNode) { + mNode->DestroyMediaStream(); + mNode = nullptr; + } + } + + MediaSourceEnum GetMediaSource() const override + { + return MediaSourceEnum::AudioCapture; + } + + void Stop() override + { + Destroy(); + } + +private: + virtual ~AudioDestinationTrackSource() {} + + RefPtr<MediaStreamAudioDestinationNode> mNode; +}; + +NS_IMPL_ADDREF_INHERITED(AudioDestinationTrackSource, + MediaStreamTrackSource) +NS_IMPL_RELEASE_INHERITED(AudioDestinationTrackSource, + MediaStreamTrackSource) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(AudioDestinationTrackSource) +NS_INTERFACE_MAP_END_INHERITING(MediaStreamTrackSource) +NS_IMPL_CYCLE_COLLECTION_INHERITED(AudioDestinationTrackSource, + MediaStreamTrackSource, + mNode) + NS_IMPL_CYCLE_COLLECTION_INHERITED(MediaStreamAudioDestinationNode, AudioNode, mDOMStream) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(MediaStreamAudioDestinationNode) NS_INTERFACE_MAP_END_INHERITING(AudioNode) NS_IMPL_ADDREF_INHERITED(MediaStreamAudioDestinationNode, AudioNode) NS_IMPL_RELEASE_INHERITED(MediaStreamAudioDestinationNode, AudioNode) @@ -32,18 +81,17 @@ MediaStreamAudioDestinationNode::MediaSt , mDOMStream( DOMAudioNodeMediaStream::CreateTrackUnionStreamAsInput(GetOwner(), this, aContext->Graph())) { // Ensure an audio track with the correct ID is exposed to JS nsIDocument* doc = aContext->GetParentObject()->GetExtantDoc(); RefPtr<MediaStreamTrackSource> source = - new BasicUnstoppableTrackSource(doc->NodePrincipal(), - MediaSourceEnum::AudioCapture); + new AudioDestinationTrackSource(this, doc->NodePrincipal()); RefPtr<MediaStreamTrack> track = mDOMStream->CreateDOMTrack(AudioNodeStream::AUDIO_TRACK, MediaSegment::AUDIO, source, MediaTrackConstraints()); mDOMStream->AddTrackInternal(track); ProcessedMediaStream* outputStream = mDOMStream->GetInputStream()->AsProcessedStream(); MOZ_ASSERT(!!outputStream);
--- a/dom/media/webaudio/test/test_mediaStreamAudioDestinationNode.html +++ b/dom/media/webaudio/test/test_mediaStreamAudioDestinationNode.html @@ -29,16 +29,22 @@ addLoadEvent(function() { var elem = document.getElementById('audioelem'); elem.srcObject = dest.stream; elem.onloadedmetadata = function() { ok(true, "got metadata event"); setTimeout(function() { is(elem.played.length, 1, "should have a played interval"); is(elem.played.start(0), 0, "should have played immediately"); isnot(elem.played.end(0), 0, "should have played for a non-zero interval"); - SimpleTest.finish(); + + // This will end the media element. + dest.stream.getTracks()[0].stop(); }, 2000); }; + elem.onended = function() { + ok(true, "media element ended after destination track.stop()"); + SimpleTest.finish(); + }; source.start(0); elem.play(); }); </script>
--- a/dom/smil/TimeEvent.cpp +++ b/dom/smil/TimeEvent.cpp @@ -51,16 +51,18 @@ TimeEvent::GetDetail(int32_t* aDetail) *aDetail = mDetail; return NS_OK; } void TimeEvent::InitTimeEvent(const nsAString& aType, nsGlobalWindow* aView, int32_t aDetail) { + NS_ENSURE_TRUE_VOID(!mEvent->mFlags.mIsBeingDispatched); + Event::InitEvent(aType, false /*doesn't bubble*/, false /*can't cancel*/); mDetail = aDetail; mView = aView ? aView->GetOuterWindow() : nullptr; } } // namespace dom } // namespace mozilla
deleted file mode 100644 --- a/dom/webidl/IDBEnvironment.webidl +++ /dev/null @@ -1,14 +0,0 @@ -/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* 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/. - * - * The origin of this IDL file is: - * https://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html - */ - -[Exposed=(Window,Worker), NoInterfaceObject] -interface IDBEnvironment { - //[Throws] readonly attribute IDBFactory indexedDB; - [Throws] readonly attribute IDBFactory? indexedDB; -};
--- a/dom/webidl/MediaStream.webidl +++ b/dom/webidl/MediaStream.webidl @@ -31,15 +31,13 @@ interface MediaStream : EventTarget { readonly attribute DOMString id; sequence<AudioStreamTrack> getAudioTracks (); sequence<VideoStreamTrack> getVideoTracks (); sequence<MediaStreamTrack> getTracks (); MediaStreamTrack? getTrackById (DOMString trackId); void addTrack (MediaStreamTrack track); void removeTrack (MediaStreamTrack track); MediaStream clone (); - // readonly attribute boolean active; - // attribute EventHandler onactive; - // attribute EventHandler oninactive; + readonly attribute boolean active; attribute EventHandler onaddtrack; // attribute EventHandler onremovetrack; readonly attribute double currentTime; };
--- a/dom/webidl/URL.webidl +++ b/dom/webidl/URL.webidl @@ -46,19 +46,19 @@ interface URL { attribute USVString search; readonly attribute URLSearchParams searchParams; [Throws] attribute USVString hash; }; partial interface URL { [Throws] - static DOMString? createObjectURL(Blob blob, optional objectURLOptions options); + static DOMString createObjectURL(Blob blob, optional objectURLOptions options); [Throws] - static DOMString? createObjectURL(MediaStream stream, optional objectURLOptions options); + static DOMString createObjectURL(MediaStream stream, optional objectURLOptions options); [Throws] static void revokeObjectURL(DOMString url); [ChromeOnly, Throws] static boolean isValidURL(DOMString url); }; dictionary objectURLOptions {
--- a/dom/webidl/Window.webidl +++ b/dom/webidl/Window.webidl @@ -118,19 +118,16 @@ partial interface Window { }; // https://dvcs.w3.org/hg/editing/raw-file/tip/editing.html partial interface Window { //[Throws] Selection getSelection(); [Throws] Selection? getSelection(); }; -// https://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html -Window implements IDBEnvironment; - // http://dev.w3.org/csswg/cssom/ partial interface Window { //[NewObject, Throws] CSSStyleDeclaration getComputedStyle(Element elt, optional DOMString pseudoElt = ""); [NewObject, Throws] CSSStyleDeclaration? getComputedStyle(Element elt, optional DOMString pseudoElt = ""); }; // http://dev.w3.org/csswg/cssom-view/ enum ScrollBehavior { "auto", "instant", "smooth" };
--- a/dom/webidl/WindowOrWorkerGlobalScope.webidl +++ b/dom/webidl/WindowOrWorkerGlobalScope.webidl @@ -48,16 +48,23 @@ partial interface WindowOrWorkerGlobalSc [NewObject] Promise<Response> fetch(RequestInfo input, optional RequestInit init); }; // https://w3c.github.io/webappsec-secure-contexts/#monkey-patching-global-object partial interface WindowOrWorkerGlobalScope { readonly attribute boolean isSecureContext; }; +// http://w3c.github.io/IndexedDB/#factory-interface +partial interface WindowOrWorkerGlobalScope { + // readonly attribute IDBFactory indexedDB; + [Throws] + readonly attribute IDBFactory? indexedDB; +}; + // Mozilla extensions partial interface WindowOrWorkerGlobalScope { // Extensions to ImageBitmap bits. // Bug 1141979 - [FoxEye] Extend ImageBitmap with interfaces to access its // underlying image data // // Note: // Overloaded functions cannot have different "extended attributes",
--- a/dom/webidl/WorkerGlobalScope.webidl +++ b/dom/webidl/WorkerGlobalScope.webidl @@ -37,17 +37,16 @@ partial interface WorkerGlobalScope { // https://slightlyoff.github.io/ServiceWorker/spec/service_worker/index.html#self-caches partial interface WorkerGlobalScope { [Throws, Func="mozilla::dom::cache::CacheStorage::PrefEnabled", SameObject] readonly attribute CacheStorage caches; }; WorkerGlobalScope implements GlobalCrypto; -WorkerGlobalScope implements IDBEnvironment; WorkerGlobalScope implements WindowOrWorkerGlobalScope; // Not implemented yet: bug 1072107. // WorkerGlobalScope implements FontFaceSource; // Mozilla extensions partial interface WorkerGlobalScope {
--- a/dom/webidl/moz.build +++ b/dom/webidl/moz.build @@ -252,17 +252,16 @@ WEBIDL_FILES = [ 'HTMLTimeElement.webidl', 'HTMLTitleElement.webidl', 'HTMLTrackElement.webidl', 'HTMLUListElement.webidl', 'HTMLVideoElement.webidl', 'IccCardLockError.webidl', 'IDBCursor.webidl', 'IDBDatabase.webidl', - 'IDBEnvironment.webidl', 'IDBFactory.webidl', 'IDBFileHandle.webidl', 'IDBFileRequest.webidl', 'IDBIndex.webidl', 'IDBKeyRange.webidl', 'IDBMutableFile.webidl', 'IDBObjectStore.webidl', 'IDBOpenDBRequest.webidl',
--- a/dom/workers/ServiceWorkerContainer.cpp +++ b/dom/workers/ServiceWorkerContainer.cpp @@ -188,18 +188,19 @@ ServiceWorkerContainer::Register(const n aRv.ThrowTypeError<MSG_INVALID_SCOPE>(defaultScope, wSpec); return nullptr; } } else { // Step 5. Parse against entry settings object's base URL. rv = NS_NewURI(getter_AddRefs(scopeURI), aOptions.mScope.Value(), nullptr, baseURI); if (NS_WARN_IF(NS_FAILED(rv))) { + nsIURI* uri = baseURI ? baseURI : scriptURI; nsAutoCString spec; - baseURI->GetSpec(spec); + uri->GetSpec(spec); NS_ConvertUTF8toUTF16 wSpec(spec); aRv.ThrowTypeError<MSG_INVALID_SCOPE>(aOptions.mScope.Value(), wSpec); return nullptr; } aRv = CheckForSlashEscapedCharsInPath(scopeURI); if (NS_WARN_IF(aRv.Failed())) { return nullptr;
--- a/dom/workers/ServiceWorkerManager.cpp +++ b/dom/workers/ServiceWorkerManager.cpp @@ -3105,17 +3105,17 @@ ServiceWorkerManager::RemoveRegistration // object may be held by content script. aRegistration->Clear(); RemoveScopeAndRegistration(aRegistration); } namespace { /** - * See browser/components/sessionstore/Utils.jsm function hasRootDomain(). + * See toolkit/modules/sessionstore/Utils.jsm function hasRootDomain(). * * Returns true if the |url| passed in is part of the given root |domain|. * For example, if |url| is "www.mozilla.org", and we pass in |domain| as * "mozilla.org", this will return true. It would return false the other way * around. */ bool HasRootDomain(nsIURI* aURI, const nsACString& aDomain)
--- a/editor/composer/test/chrome.ini +++ b/editor/composer/test/chrome.ini @@ -1,15 +1,5 @@ [DEFAULT] skip-if = buildapp == 'b2g' || os == 'android' -[test_async_UpdateCurrentDictionary.html] -[test_bug338427.html] [test_bug434998.xul] -[test_bug678842.html] -[test_bug697981.html] -[test_bug717433.html] -[test_bug1204147.html] -[test_bug1200533.html] -[test_bug1205983.html] -[test_bug1209414.html] -[test_bug1219928.html] [test_bug1266815.html]
--- a/editor/composer/test/mochitest.ini +++ b/editor/composer/test/mochitest.ini @@ -8,14 +8,34 @@ support-files = bug1204147_subframe2.html en-GB/en_GB.dic en-GB/en_GB.aff en-AU/en_AU.dic en-AU/en_AU.aff de-DE/de_DE.dic de-DE/de_DE.aff +[test_async_UpdateCurrentDictionary.html] +skip-if = os == 'android' +[test_bug338427.html] +skip-if = os == 'android' [test_bug348497.html] [test_bug384147.html] [test_bug389350.html] skip-if = toolkit == 'android' [test_bug519928.html] +[test_bug678842.html] +skip-if = os == 'android' +[test_bug697981.html] +skip-if = os == 'android' +[test_bug717433.html] +skip-if = os == 'android' [test_bug738440.html] +[test_bug1200533.html] +skip-if = os == 'android' +[test_bug1204147.html] +skip-if = os == 'android' +[test_bug1205983.html] +skip-if = os == 'android' +[test_bug1209414.html] +skip-if = os == 'android' +[test_bug1219928.html] +skip-if = e10s || os == 'android'
--- a/editor/composer/test/test_async_UpdateCurrentDictionary.html +++ b/editor/composer/test/test_async_UpdateCurrentDictionary.html @@ -1,37 +1,37 @@ <!DOCTYPE HTML> <html> <!-- https://bugzilla.mozilla.org/show_bug.cgi?id=856270 --> <head> <title>Test for Bug 856270 - Async UpdateCurrentDictionary</title> - <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> - <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" href="/tests/SimpleTest/test.css"> </head> <body> <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=856270">Mozilla Bug 856270</a> <p id="display"></p> <div id="content"> <textarea id="editor" spellcheck="true"></textarea> </div> <pre id="test"> <script class="testbody" type="text/javascript;version=1.8"> SimpleTest.waitForExplicitFinish(); addLoadEvent(start); function start() { - Components.utils.import("resource://gre/modules/AsyncSpellCheckTestHelper.jsm"); var textarea = document.getElementById("editor"); textarea.focus(); - onSpellCheck(textarea, function () { - var isc = textarea.editor.getInlineSpellChecker(false); + SpecialPowers.Cu.import("resource://gre/modules/AsyncSpellCheckTestHelper.jsm") + .onSpellCheck(textarea, function () { + var isc = SpecialPowers.wrap(textarea).editor.getInlineSpellChecker(false); ok(isc, "Inline spell checker should exist after focus and spell check"); var sc = isc.spellChecker; isnot(sc.GetCurrentDictionary(), lang, "Current dictionary should not be set yet."); // First, set the lang attribute on the textarea, call Update, and make // sure the spell checker's language was updated appropriately. var lang = "en-US";
--- a/editor/composer/test/test_bug1200533.html +++ b/editor/composer/test/test_bug1200533.html @@ -1,17 +1,17 @@ <!DOCTYPE HTML> <html> <!-- https://bugzilla.mozilla.org/show_bug.cgi?id=1200533 --> <head> <title>Test for Bug 1200533</title> - <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> - <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" href="/tests/SimpleTest/test.css"> </head> <body> <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1200533">Mozilla Bug 1200533</a> <p id="display"></p> <iframe id="content"></iframe> </div> <pre id="test"> @@ -43,61 +43,71 @@ var tests = [ // Result: Random en-*. [ "en-ZA-not-avail", "de-DE", "*" ], [ "en-generic", "de-DE", "*" ], // Result: Preference value. [ "ko-not-avail", "de-DE", "de-DE" ], ]; var loadCount = 0; -var en_GB; -var en_AU; -var en_DE; -var hunspell; +var script; var loadListener = function(evt) { - Components.utils.import("resource://gre/modules/AsyncSpellCheckTestHelper.jsm"); - Components.utils.import("resource://gre/modules/Services.jsm"); + if (loadCount == 0) { + script = SpecialPowers.loadChromeScript(function() { + var dir = Components.classes["@mozilla.org/file/directory_service;1"] + .getService(Components.interfaces.nsIProperties) + .get("CurWorkD", Components.interfaces.nsIFile); + dir.append("tests"); + dir.append("editor"); + dir.append("composer"); + dir.append("test"); - if (loadCount == 0) { - var dir = Components.classes["@mozilla.org/file/directory_service;1"] - .getService(Components.interfaces.nsIProperties) - .get("CurWorkD", Components.interfaces.nsIFile); - dir.append("tests"); - dir.append("editor"); - dir.append("composer"); - dir.append("test"); + var hunspell = Components.classes["@mozilla.org/spellchecker/engine;1"] + .getService(Components.interfaces.mozISpellCheckingEngine); - hunspell = Components.classes["@mozilla.org/spellchecker/engine;1"] - .getService(Components.interfaces.mozISpellCheckingEngine); + // Install en-GB, en-AU and de-DE dictionaries. + var en_GB = dir.clone(); + var en_AU = dir.clone(); + var de_DE = dir.clone(); + en_GB.append("en-GB"); + en_AU.append("en-AU"); + de_DE.append("de-DE"); + hunspell.addDirectory(en_GB); + hunspell.addDirectory(en_AU); + hunspell.addDirectory(de_DE); - // Install en-GB, en-AU and de-DE dictionaries. - en_GB = dir.clone(); - en_AU = dir.clone(); - de_DE = dir.clone(); - en_GB.append("en-GB"); - en_AU.append("en-AU"); - de_DE.append("de-DE"); - is(en_GB.exists(), true, "true expected (en-GB directory should exist)"); - is(en_AU.exists(), true, "true expected (en-AU directory should exist)"); - is(de_DE.exists(), true, "true expected (de-DE directory should exist)"); - hunspell.addDirectory(en_GB); - hunspell.addDirectory(en_AU); - hunspell.addDirectory(de_DE); + addMessageListener("check-existence", + () => [en_GB.exists(), en_AU.exists(), + de_DE.exists()]); + addMessageListener("destroy", () => { + hunspell.removeDirectory(en_GB); + hunspell.removeDirectory(en_AU); + hunspell.removeDirectory(de_DE); + }); + }); + var existenceChecks = script.sendSyncMessage("check-existence")[0][0]; + is(existenceChecks[0], true, "true expected (en-GB directory should exist)"); + is(existenceChecks[1], true, "true expected (en-AU directory should exist)"); + is(existenceChecks[2], true, "true expected (de-DE directory should exist)"); } - Services.prefs.setCharPref("spellchecker.dictionary", tests[loadCount][1]); + SpecialPowers.pushPrefEnv({set: [["spellchecker.dictionary", tests[loadCount][1]]]}, + function() { continueTest(evt) }); +} +function continueTest(evt) { var doc = evt.target.contentDocument; var elem = doc.getElementById(tests[loadCount][0]); - var editor = elem.QueryInterface(Components.interfaces.nsIDOMNSEditableElement).editor; + var editor = SpecialPowers.wrap(elem).QueryInterface(SpecialPowers.Ci.nsIDOMNSEditableElement).editor; editor.setSpellcheckUserOverride(true); var inlineSpellChecker = editor.getInlineSpellChecker(true); - onSpellCheck(elem, function () { + SpecialPowers.Cu.import("resource://gre/modules/AsyncSpellCheckTestHelper.jsm") + .onSpellCheck(elem, function () { var spellchecker = inlineSpellChecker.spellChecker; try { var dict = spellchecker.GetCurrentDictionary(); } catch(e) {} if (tests[loadCount][2] != "*") { is (dict, tests[loadCount][2], "expected " + tests[loadCount][2]); } else { @@ -106,22 +116,17 @@ var loadListener = function(evt) { } loadCount++; if (loadCount < tests.length) { // Load the iframe again. content.src = 'http://mochi.test:8888/tests/editor/composer/test/bug1200533_subframe.html?firstload=false'; } else { // Remove the fake dictionaries again, since it's otherwise picked up by later tests. - hunspell.removeDirectory(en_GB); - hunspell.removeDirectory(en_AU); - hunspell.removeDirectory(de_DE); - - // Reset the preference, so the last value we set doesn't collide with the next test. - Services.prefs.setCharPref("spellchecker.dictionary", ""); + script.sendSyncMessage("destroy"); SimpleTest.finish(); } }); } content.addEventListener('load', loadListener, false);
--- a/editor/composer/test/test_bug1204147.html +++ b/editor/composer/test/test_bug1204147.html @@ -1,17 +1,17 @@ <!DOCTYPE html> <html> <!-- https://bugzilla.mozilla.org/show_bug.cgi?id=1204147 --> <head> <title>Test for Bug 1204147</title> - <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> - <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" href="/tests/SimpleTest/test.css"> </head> <body> <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1204147">Mozilla Bug 1204147</a> <p id="display"></p> <iframe id="content"></iframe> </div> <pre id="test"> @@ -22,54 +22,58 @@ SimpleTest.waitForExplicitFinish(); var content = document.getElementById('content'); // Load a subframe containing an editor with using "en-GB". At first // load, it will set the dictionary to "en-GB". The bug was that a content preference // was also created. At second load, we check the dictionary for another element, // one that should use "en-US". With the bug corrected, we get "en-US", before // we got "en-GB" from the content preference. var firstLoad = true; -var en_GB; -var hunspell; +var script; var loadListener = function(evt) { - Components.utils.import("resource://gre/modules/AsyncSpellCheckTestHelper.jsm"); + if (firstLoad) { + script = SpecialPowers.loadChromeScript(function() { + var dir = Components.classes["@mozilla.org/file/directory_service;1"] + .getService(Components.interfaces.nsIProperties) + .get("CurWorkD", Components.interfaces.nsIFile); + dir.append("tests"); + dir.append("editor"); + dir.append("composer"); + dir.append("test"); - if (firstLoad) { - var dir = Components.classes["@mozilla.org/file/directory_service;1"] - .getService(Components.interfaces.nsIProperties) - .get("CurWorkD", Components.interfaces.nsIFile); - dir.append("tests"); - dir.append("editor"); - dir.append("composer"); - dir.append("test"); + var hunspell = Components.classes["@mozilla.org/spellchecker/engine;1"] + .getService(Components.interfaces.mozISpellCheckingEngine); - hunspell = Components.classes["@mozilla.org/spellchecker/engine;1"] - .getService(Components.interfaces.mozISpellCheckingEngine); + // Install en-GB dictionary. + en_GB = dir.clone(); + en_GB.append("en-GB"); + hunspell.addDirectory(en_GB); - // Install en-GB dictionary. - en_GB = dir.clone(); - en_GB.append("en-GB"); - is(en_GB.exists(), true, "true expected (en-GB directory should exist)"); - hunspell.addDirectory(en_GB); + addMessageListener("en_GB-exists", () => en_GB.exists()); + addMessageListener("destroy", () => hunspell.removeDirectory(en_GB)); + }); + is(script.sendSyncMessage("en_GB-exists")[0][0], true, + "true expected (en-GB directory should exist)"); } var doc = evt.target.contentDocument; var elem; if (firstLoad) { elem = doc.getElementById('en-GB'); } else { elem = doc.getElementById('en-US'); } - var editor = elem.QueryInterface(Components.interfaces.nsIDOMNSEditableElement).editor; + var editor = SpecialPowers.wrap(elem).QueryInterface(SpecialPowers.Ci.nsIDOMNSEditableElement).editor; editor.setSpellcheckUserOverride(true); var inlineSpellChecker = editor.getInlineSpellChecker(true); - onSpellCheck(elem, function () { + SpecialPowers.Cu.import("resource://gre/modules/AsyncSpellCheckTestHelper.jsm") + .onSpellCheck(elem, function () { var spellchecker = inlineSpellChecker.spellChecker; try { var currentDictonary = spellchecker.GetCurrentDictionary(); } catch(e) {} if (firstLoad) { firstL