diff --git a/browser/base/content/test/general/browser_accesskeys.js b/browser/base/content/test/general/browser_accesskeys.js index fa9faf575614..d220e59a0d25 100644 --- a/browser/base/content/test/general/browser_accesskeys.js +++ b/browser/base/content/test/general/browser_accesskeys.js @@ -47,18 +47,88 @@ add_task(async function() { focusedId = await performAccessKeyForChrome("z"); is(focusedId, "chromebutton", "chromebutton accesskey"); - newButton.remove(); - gBrowser.removeTab(tab1); gBrowser.removeTab(tab2); + + // Test whether access key for the newButton isn't available when content + // consumes the key event. + + // When content in the tab3 consumes all keydown events. + const gPageURL3 = "data:text/html," + + "" + + ""; + let tab3 = await BrowserTestUtils.openNewForegroundTab(gBrowser, gPageURL3); + tab3.linkedBrowser.messageManager.loadFrameScript("data:,(" + childHandleFocus.toString() + ")();", false); + + Services.focus.clearFocus(window); + + focusedId = await performAccessKey("y"); + is(focusedId, "tab3button", "button accesskey in tab3 should be focused"); + + newButton.onfocus = () => { + ok(false, "chromebutton shouldn't get focus during testing with tab3"); + } + + // Press the accesskey for the chrome element while the content document is focused. + focusedId = await performAccessKey("z"); + is(focusedId, "tab3body", "button accesskey in tab3 should keep having focus"); + + newButton.onfocus = null; + + gBrowser.removeTab(tab3); + + // When content in the tab4 consumes all keypress events. + const gPageURL4 = "data:text/html," + + "" + + ""; + let tab4 = await BrowserTestUtils.openNewForegroundTab(gBrowser, gPageURL4); + tab4.linkedBrowser.messageManager.loadFrameScript("data:,(" + childHandleFocus.toString() + ")();", false); + + Services.focus.clearFocus(window); + + focusedId = await performAccessKey("y"); + is(focusedId, "tab4button", "button accesskey in tab4 should be focused"); + + newButton.onfocus = () => { + // EventStateManager handles accesskey before dispatching keypress event + // into the DOM tree, therefore, chrome accesskey always wins focus from + // content. However, this is different from shortcut keys. + todo(false, "chromebutton shouldn't get focus during testing with tab4"); + } + + // Press the accesskey for the chrome element while the content document is focused. + focusedId = await performAccessKey("z"); + is(focusedId, "tab4body", "button accesskey in tab4 should keep having focus"); + + newButton.onfocus = null; + + gBrowser.removeTab(tab4); + + newButton.remove(); }); function childHandleFocus() { + var sent = false; content.document.body.firstChild.addEventListener("focus", function focused(event) { + sent = true; let focusedElement = content.document.activeElement; focusedElement.blur(); sendAsyncMessage("Test:FocusFromAccessKey", { focus: focusedElement.id }) }, true); + content.document.body.addEventListener("keydown", function keydown(event) { + sent = false; + }, true); + content.document.body.addEventListener("keyup", function keyup(event) { + if (!sent) { + sent = true; + let focusedElement = content.document.activeElement; + sendAsyncMessage("Test:FocusFromAccessKey", { focus: focusedElement.id }); + } + }); } function performAccessKey(key) { diff --git a/browser/components/extensions/ext-menus.js b/browser/components/extensions/ext-menus.js index ffd438ce9757..147acc41bb25 100644 --- a/browser/components/extensions/ext-menus.js +++ b/browser/components/extensions/ext-menus.js @@ -303,51 +303,29 @@ global.actionContextMenu = function(contextData) { gMenuBuilder.buildActionContextMenu(contextData); }; +const contextsMap = { + onAudio: "audio", + onEditableArea: "editable", + inFrame: "frame", + onImage: "image", + onLink: "link", + onPassword: "password", + isTextSelected: "selection", + onVideo: "video", + + onBrowserAction: "browser_action", + onPageAction: "page_action", + onTab: "tab", + inToolsMenu: "tools_menu", +}; + const getMenuContexts = contextData => { let contexts = new Set(); - if (contextData.inFrame) { - contexts.add("frame"); - } - - if (contextData.isTextSelected) { - contexts.add("selection"); - } - - if (contextData.onLink) { - contexts.add("link"); - } - - if (contextData.onEditableArea) { - contexts.add("editable"); - } - - if (contextData.onPassword) { - contexts.add("password"); - } - - if (contextData.onImage) { - contexts.add("image"); - } - - if (contextData.onVideo) { - contexts.add("video"); - } - - if (contextData.onAudio) { - contexts.add("audio"); - } - - if (contextData.onPageAction) { - contexts.add("page_action"); - } - - if (contextData.onBrowserAction) { - contexts.add("browser_action"); - } - - if (contextData.onTab) { - contexts.add("tab"); + for (const [key, value] of Object.entries(contextsMap)) { + if (contextData[key]) { + contexts.add(value); + } } if (contexts.size === 0) { @@ -355,7 +333,7 @@ const getMenuContexts = contextData => { } // New non-content contexts supported in Firefox are not part of "all". - if (!contextData.onTab) { + if (!contextData.onTab && !contextData.inToolsMenu) { contexts.add("all"); } @@ -582,8 +560,10 @@ MenuItem.prototype = { }; // While any extensions are active, this Tracker registers to observe/listen -// for contex-menu events from both content and chrome. +// for menu events from both Tools and context menus, both content and chrome. const menuTracker = { + menuIds: ["menu_ToolsPopup", "tabContextMenu"], + register() { Services.obs.addObserver(this, "on-build-contextmenu"); for (const window of windowTracker.browserWindows()) { @@ -595,8 +575,10 @@ const menuTracker = { unregister() { Services.obs.removeObserver(this, "on-build-contextmenu"); for (const window of windowTracker.browserWindows()) { - const menu = window.document.getElementById("tabContextMenu"); - menu.removeEventListener("popupshowing", this); + for (const id of this.menuIds) { + const menu = window.document.getElementById(id); + menu.removeEventListener("popupshowing", this); + } } windowTracker.removeOpenListener(this.onWindowOpen); }, @@ -607,12 +589,19 @@ const menuTracker = { }, onWindowOpen(window) { - const menu = window.document.getElementById("tabContextMenu"); - menu.addEventListener("popupshowing", menuTracker); + for (const id of this.menuIds) { + const menu = window.document.getElementById(id); + menu.addEventListener("popupshowing", menuTracker); + } }, handleEvent(event) { const menu = event.target; + if (menu.id === "menu_ToolsPopup") { + const tab = tabTracker.activeTab; + const pageUrl = tab.linkedBrowser.currentURI.spec; + gMenuBuilder.build({menu, tab, pageUrl, inToolsMenu: true}); + } if (menu.id === "tabContextMenu") { const trigger = menu.triggerNode; const tab = trigger.localName === "tab" ? trigger : tabTracker.activeTab; diff --git a/browser/components/extensions/schemas/menus.json b/browser/components/extensions/schemas/menus.json index b71ec9b7c183..6601a156f9c4 100644 --- a/browser/components/extensions/schemas/menus.json +++ b/browser/components/extensions/schemas/menus.json @@ -22,7 +22,15 @@ "namespace": "contextMenus", "permissions": ["contextMenus"], "description": "Use the browser.contextMenus API to add items to the browser's context menu. You can choose what types of objects your context menu additions apply to, such as images, hyperlinks, and pages.", - "$import": "menus" + "$import": "menus", + "types": [ + { + "id": "ContextType", + "type": "string", + "enum": ["all", "page", "frame", "selection", "link", "editable", "password", "image", "video", "audio", "launcher", "browser_action", "page_action", "tab"], + "description": "The different contexts a menu can appear in. Specifying 'all' is equivalent to the combination of all other contexts except for 'tab' and 'tools_menu'." + } + ] }, { "namespace": "menus", @@ -38,8 +46,8 @@ { "id": "ContextType", "type": "string", - "enum": ["all", "page", "frame", "selection", "link", "editable", "password", "image", "video", "audio", "launcher", "browser_action", "page_action", "tab"], - "description": "The different contexts a menu can appear in. Specifying 'all' is equivalent to the combination of all other contexts except for 'tab'." + "enum": ["all", "page", "frame", "selection", "link", "editable", "password", "image", "video", "audio", "launcher", "browser_action", "page_action", "tab", "tools_menu"], + "description": "The different contexts a menu can appear in. Specifying 'all' is equivalent to the combination of all other contexts except for 'tab' and 'tools_menu'." }, { "id": "ItemType", diff --git a/browser/components/extensions/test/browser/browser_ext_menus.js b/browser/components/extensions/test/browser/browser_ext_menus.js index 2993b666355c..a9bb0d5b039d 100644 --- a/browser/components/extensions/test/browser/browser_ext_menus.js +++ b/browser/components/extensions/test/browser/browser_ext_menus.js @@ -255,3 +255,59 @@ add_task(async function test_multiple_contexts_init() { await BrowserTestUtils.removeTab(tab); await extension.unload(); }); + +add_task(async function test_tools_menu() { + const first = ExtensionTestUtils.loadExtension({ + manifest: { + permissions: ["menus"], + }, + async background() { + await browser.menus.create({title: "alpha", contexts: ["tools_menu"]}); + await browser.menus.create({title: "beta", contexts: ["tools_menu"]}); + browser.test.sendMessage("ready"); + }, + }); + + const second = ExtensionTestUtils.loadExtension({ + manifest: { + permissions: ["menus"], + }, + async background() { + await browser.menus.create({title: "gamma", contexts: ["tools_menu"]}); + browser.menus.onClicked.addListener((info, tab) => { + browser.test.sendMessage("click", {info, tab}); + }); + + const [tab] = await browser.tabs.query({active: true}); + browser.test.sendMessage("ready", tab.id); + }, + }); + + const tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com/"); + await first.startup(); + await second.startup(); + + await first.awaitMessage("ready"); + const tabId = await second.awaitMessage("ready"); + const menu = await openToolsMenu(); + + const [separator, submenu, gamma] = Array.from(menu.children).slice(-3); + is(separator.tagName, "menuseparator", "Separator before first extension item"); + + is(submenu.tagName, "menu", "Correct submenu type"); + is(submenu.getAttribute("label"), "Generated extension", "Correct submenu title"); + is(submenu.firstChild.children.length, 2, "Correct number of submenu items"); + + is(gamma.tagName, "menuitem", "Third menu item type is correct"); + is(gamma.getAttribute("label"), "gamma", "Third menu item label is correct"); + + closeToolsMenu(gamma); + + const click = await second.awaitMessage("click"); + is(click.info.pageUrl, "http://example.com/", "Click info pageUrl is correct"); + is(click.tab.id, tabId, "Click event tab ID is correct"); + + await BrowserTestUtils.removeTab(tab); + await first.unload(); + await second.unload(); +}); diff --git a/browser/components/extensions/test/browser/head.js b/browser/components/extensions/test/browser/head.js index 31f6ef4bc18b..487a376c7297 100644 --- a/browser/components/extensions/test/browser/head.js +++ b/browser/components/extensions/test/browser/head.js @@ -13,6 +13,7 @@ * openExtensionContextMenu closeExtensionContextMenu * openActionContextMenu openSubmenu closeActionContextMenu * openTabContextMenu closeTabContextMenu + * openToolsMenu closeToolsMenu * imageBuffer imageBufferFromDataURI * getListStyleImage getPanelForNode * awaitExtensionPanel awaitPopupResize @@ -336,6 +337,35 @@ async function closeExtensionContextMenu(itemToSelect, modifiers = {}) { contentAreaContextMenu.hidePopup(); } +async function openToolsMenu(win = window) { + const node = win.document.getElementById("tools-menu"); + const menu = win.document.getElementById("menu_ToolsPopup"); + const shown = BrowserTestUtils.waitForEvent(menu, "popupshown"); + if (AppConstants.platform === "macosx") { + // We can't open menubar items on OSX, so mocking instead. + menu.dispatchEvent(new MouseEvent("popupshowing")); + menu.dispatchEvent(new MouseEvent("popupshown")); + } else { + node.open = true; + } + await shown; + return menu; +} + +function closeToolsMenu(itemToSelect, win = window) { + const menu = win.document.getElementById("menu_ToolsPopup"); + const hidden = BrowserTestUtils.waitForEvent(menu, "popuphidden"); + if (AppConstants.platform === "macosx") { + // Mocking on OSX, see above. + itemToSelect.doCommand(); + menu.dispatchEvent(new MouseEvent("popuphiding")); + menu.dispatchEvent(new MouseEvent("popuphidden")); + } else { + EventUtils.synthesizeMouseAtCenter(itemToSelect, {}, win); + } + return hidden; +} + async function openChromeContextMenu(menuId, target, win = window) { const node = win.document.querySelector(target); const menu = win.document.getElementById(menuId); diff --git a/browser/extensions/onboarding/content/onboarding.js b/browser/extensions/onboarding/content/onboarding.js index 862d853c618b..738f8313b4fb 100644 --- a/browser/extensions/onboarding/content/onboarding.js +++ b/browser/extensions/onboarding/content/onboarding.js @@ -461,6 +461,8 @@ class Onboarding { // Let's toggle the overlay. case "onboarding-overlay": this.toggleOverlay(); + let selectedTour = this._tours.find(tour => !this.isTourCompleted(tour.id)) || this._tours[0]; + this.gotoPage(selectedTour.id); break; case "onboarding-notification-close-btn": this.hideNotification(); @@ -833,15 +835,15 @@ class Onboarding { // Cache elements in arrays for later use to avoid cost of querying elements this._tourItems.push(li); this._tourPages.push(div); + + this.markTourCompletionState(tour.id); } - tours.forEach(tour => this.markTourCompletionState(tour.id)); let dialog = this._window.document.getElementById("onboarding-overlay-dialog"); let ul = this._window.document.getElementById("onboarding-tour-list"); ul.appendChild(itemsFrag); let footer = this._window.document.getElementById("onboarding-footer"); dialog.insertBefore(pagesFrag, footer); - this.gotoPage(tours[0].id); } _loadCSS() { diff --git a/browser/themes/shared/privatebrowsing/aboutPrivateBrowsing.css b/browser/themes/shared/privatebrowsing/aboutPrivateBrowsing.css index ae40bd82d7d0..883882bbe704 100644 --- a/browser/themes/shared/privatebrowsing/aboutPrivateBrowsing.css +++ b/browser/themes/shared/privatebrowsing/aboutPrivateBrowsing.css @@ -5,7 +5,7 @@ @import url("chrome://global/skin/in-content/info-pages.css"); :root { - --color-grey: #b1b1b1; + --color-grey: #505473; } html.private { @@ -122,18 +122,18 @@ a.button { .toggle + .toggle-btn { box-sizing: border-box; cursor: pointer; - min-width: 42px; - height: 26px; + min-width: 48px; + height: 27px; border-radius: 13px; background-color: var(--color-grey); - padding: 1px; + border: 1px solid #202340; } .toggle + .toggle-btn::after { position: relative; display: block; content: ""; - width: 24px; + width: 25px; height: 100%; left: 0; border-radius: 50%; @@ -149,10 +149,11 @@ a.button { .toggle:checked + .toggle-btn { background: #16da00; + border-color: #0CA700; } .toggle:checked + .toggle-btn::after { - left: 16px; + left: 21px; } .toggle:checked + .toggle-btn:dir(rtl)::after { diff --git a/devtools/client/commandline/test/helpers.js b/devtools/client/commandline/test/helpers.js index 94fd9002a214..83ee5c946d1d 100644 --- a/devtools/client/commandline/test/helpers.js +++ b/devtools/client/commandline/test/helpers.js @@ -24,6 +24,7 @@ var { helpers, assert } = (function () { var { require } = Cu.import("resource://devtools/shared/Loader.jsm", {}); var { TargetFactory } = require("devtools/client/framework/target"); + var { gDevToolsBrowser } = require("devtools/client/framework/devtools-browser"); var Services = require("Services"); var assert = { ok: ok, is: is, log: info }; @@ -211,8 +212,8 @@ var { helpers, assert } = (function () { options = options || {}; options.chromeWindow = options.chromeWindow || window; - return options.chromeWindow.DeveloperToolbar.show(true).then(function () { - var toolbar = options.chromeWindow.DeveloperToolbar; + var toolbar = gDevToolsBrowser.getDeveloperToolbar(options.chromeWindow); + return toolbar.show(true).then(function () { options.automator = createDeveloperToolbarAutomator(toolbar); options.requisition = toolbar.requisition; return options; @@ -243,7 +244,8 @@ var { helpers, assert } = (function () { * @return A promise resolved (with undefined) when the toolbar is closed */ helpers.closeToolbar = function (options) { - return options.chromeWindow.DeveloperToolbar.hide().then(function () { + var toolbar = gDevToolsBrowser.getDeveloperToolbar(options.chromeWindow).hide(); + return toolbar.then(function () { delete options.automator; delete options.requisition; }); @@ -323,8 +325,8 @@ var { helpers, assert } = (function () { return helpers.addTab(url, function (innerOptions) { var win = innerOptions.chromeWindow; - return win.DeveloperToolbar.show(true).then(function () { - var toolbar = win.DeveloperToolbar; + var toolbar = gDevToolsBrowser.getDeveloperToolbar(win); + return toolbar.show(true).then(function () { innerOptions.automator = createDeveloperToolbarAutomator(toolbar); innerOptions.requisition = toolbar.requisition; @@ -334,7 +336,7 @@ var { helpers, assert } = (function () { ok(false, error); console.error(error); }).then(function () { - win.DeveloperToolbar.hide().then(function () { + toolbar.hide().then(function () { delete innerOptions.automator; }); }); diff --git a/devtools/client/framework/devtools-browser.js b/devtools/client/framework/devtools-browser.js index 18505901bfc1..e3f271468a80 100644 --- a/devtools/client/framework/devtools-browser.js +++ b/devtools/client/framework/devtools-browser.js @@ -24,6 +24,7 @@ loader.lazyRequireGetter(this, "DebuggerServer", "devtools/server/main", true); loader.lazyRequireGetter(this, "DebuggerClient", "devtools/shared/client/main", true); loader.lazyRequireGetter(this, "BrowserMenus", "devtools/client/framework/browser-menus"); loader.lazyRequireGetter(this, "appendStyleSheet", "devtools/client/shared/stylesheet-utils", true); +loader.lazyRequireGetter(this, "DeveloperToolbar", "devtools/client/shared/developer-toolbar", true); loader.lazyImporter(this, "CustomizableUI", "resource:///modules/CustomizableUI.jsm"); loader.lazyImporter(this, "CustomizableWidgets", "resource:///modules/CustomizableWidgets.jsm"); @@ -55,6 +56,11 @@ var gDevToolsBrowser = exports.gDevToolsBrowser = { */ _browserStyleSheets: new WeakMap(), + /** + * WeakMap keeping track of DeveloperToolbar instances for each firefox window. + */ + _toolbars: new WeakMap(), + _tabStats: { peakOpen: 0, peakPinned: 0, @@ -108,7 +114,7 @@ var gDevToolsBrowser = exports.gDevToolsBrowser = { focusEl.setAttribute("disabled", "true"); } if (devToolbarEnabled && Services.prefs.getBoolPref("devtools.toolbar.visible")) { - win.DeveloperToolbar.show(false).catch(console.error); + this.getDeveloperToolbar(win).show(false).catch(console.error); } // Enable WebIDE? @@ -499,12 +505,6 @@ var gDevToolsBrowser = exports.gDevToolsBrowser = { // only once menus are registered as it depends on it. gDevToolsBrowser.installDeveloperWidget(); - // Inject lazily DeveloperToolbar on the chrome window - loader.lazyGetter(win, "DeveloperToolbar", function () { - let { DeveloperToolbar } = require("devtools/client/shared/developer-toolbar"); - return new DeveloperToolbar(win); - }); - this.updateCommandAvailability(win); this.updateDevtoolsThemeAttribute(win); this.ensurePrefObserver(); @@ -518,6 +518,22 @@ var gDevToolsBrowser = exports.gDevToolsBrowser = { tabContainer.addEventListener("TabUnpinned", this); }, + /** + * Create singleton instance of the developer toolbar for a given top level window. + * + * @param {Window} win + * The window to which the toolbar should be created. + */ + getDeveloperToolbar(win) { + let toolbar = this._toolbars.get(win); + if (toolbar) { + return toolbar; + } + toolbar = new DeveloperToolbar(win); + this._toolbars.set(win, toolbar); + return toolbar; + }, + /** * Hook the JS debugger tool to the "Debug Script" button of the slow script * dialog. @@ -727,11 +743,7 @@ var gDevToolsBrowser = exports.gDevToolsBrowser = { this._browserStyleSheets.delete(win); } - // Destroy the Developer toolbar if it has been accessed - let desc = Object.getOwnPropertyDescriptor(win, "DeveloperToolbar"); - if (desc && !desc.get) { - win.DeveloperToolbar.destroy(); - } + this._toolbars.delete(win); let tabContainer = win.gBrowser.tabContainer; tabContainer.removeEventListener("TabSelect", this); diff --git a/devtools/client/menus.js b/devtools/client/menus.js index 3b43cd5f2e7b..d19df02dc092 100644 --- a/devtools/client/menus.js +++ b/devtools/client/menus.js @@ -75,9 +75,9 @@ exports.menuitems = [ // or close the toolbar and when hitting the key shortcut where we just // focus the toolbar if it doesn't already has it. if (event.target.tagName.toLowerCase() == "menuitem") { - window.DeveloperToolbar.toggle(); + gDevToolsBrowser.getDeveloperToolbar(window).toggle(); } else { - window.DeveloperToolbar.focusToggle(); + gDevToolsBrowser.getDeveloperToolbar(window).focusToggle(); } }, key: { diff --git a/devtools/client/shared/developer-toolbar.js b/devtools/client/shared/developer-toolbar.js index e62f3be6de49..16268d609e59 100644 --- a/devtools/client/shared/developer-toolbar.js +++ b/devtools/client/shared/developer-toolbar.js @@ -230,7 +230,9 @@ DeveloperToolbar.prototype.createToolbar = function () { let close = this._doc.createElement("toolbarbutton"); close.setAttribute("id", "developer-toolbar-closebutton"); close.setAttribute("class", "close-icon"); - close.setAttribute("oncommand", "DeveloperToolbar.hide();"); + close.addEventListener("command", (event) => { + this.hide(); + }); let closeTooltip = L10N.getStr("toolbar.closeButton.tooltip"); close.setAttribute("tooltiptext", closeTooltip); diff --git a/devtools/client/shared/test/.eslintrc.js b/devtools/client/shared/test/.eslintrc.js index ed80d6d12187..14098553359f 100644 --- a/devtools/client/shared/test/.eslintrc.js +++ b/devtools/client/shared/test/.eslintrc.js @@ -3,7 +3,4 @@ module.exports = { // Extend from the shared list of defined globals for mochitests. "extends": "../../../.eslintrc.mochitests.js", - "globals": { - "DeveloperToolbar": true - } }; diff --git a/devtools/client/shared/test/browser_toolbar_basic.js b/devtools/client/shared/test/browser_toolbar_basic.js index 657304a3f9ef..1f27c13519fb 100644 --- a/devtools/client/shared/test/browser_toolbar_basic.js +++ b/devtools/client/shared/test/browser_toolbar_basic.js @@ -5,18 +5,21 @@ // Tests that the developer toolbar works properly +const {gDevToolsBrowser} = require("devtools/client/framework/devtools-browser"); + const TEST_URI = TEST_URI_ROOT + "doc_toolbar_basic.html"; add_task(function* () { info("Starting browser_toolbar_basic.js"); yield addTab(TEST_URI); - ok(!DeveloperToolbar.visible, "DeveloperToolbar is not visible in to start"); + let toolbar = gDevToolsBrowser.getDeveloperToolbar(window); + ok(!toolbar.visible, "DeveloperToolbar is not visible in to start"); - let shown = oneTimeObserve(DeveloperToolbar.NOTIFICATIONS.SHOW); + let shown = oneTimeObserve(toolbar.NOTIFICATIONS.SHOW); document.getElementById("menu_devToolbar").doCommand(); yield shown; - ok(DeveloperToolbar.visible, "DeveloperToolbar is visible in checkOpen"); + ok(toolbar.visible, "DeveloperToolbar is visible in checkOpen"); let close = document.getElementById("developer-toolbar-closebutton"); ok(close, "Close button exists"); @@ -36,23 +39,23 @@ add_task(function* () { gBrowser.removeCurrentTab(); - let hidden = oneTimeObserve(DeveloperToolbar.NOTIFICATIONS.HIDE); + let hidden = oneTimeObserve(toolbar.NOTIFICATIONS.HIDE); document.getElementById("menu_devToolbar").doCommand(); yield hidden; - ok(!DeveloperToolbar.visible, "DeveloperToolbar is not visible in hidden"); + ok(!toolbar.visible, "DeveloperToolbar is not visible in hidden"); - shown = oneTimeObserve(DeveloperToolbar.NOTIFICATIONS.SHOW); + shown = oneTimeObserve(toolbar.NOTIFICATIONS.SHOW); document.getElementById("menu_devToolbar").doCommand(); yield shown; - ok(DeveloperToolbar.visible, "DeveloperToolbar is visible in after open"); + ok(toolbar.visible, "DeveloperToolbar is visible in after open"); ok(isChecked(toggleToolbox), "toggle toolbox button is checked"); - hidden = oneTimeObserve(DeveloperToolbar.NOTIFICATIONS.HIDE); + hidden = oneTimeObserve(toolbar.NOTIFICATIONS.HIDE); document.getElementById("developer-toolbar-closebutton").doCommand(); yield hidden; - ok(!DeveloperToolbar.visible, "DeveloperToolbar is not visible after re-close"); + ok(!toolbar.visible, "DeveloperToolbar is not visible after re-close"); }); function isChecked(b) { diff --git a/devtools/client/shared/test/browser_toolbar_tooltip.js b/devtools/client/shared/test/browser_toolbar_tooltip.js index 301717939d8a..965f92573b30 100644 --- a/devtools/client/shared/test/browser_toolbar_tooltip.js +++ b/devtools/client/shared/test/browser_toolbar_tooltip.js @@ -5,6 +5,8 @@ // Tests that the developer toolbar works properly +const {gDevToolsBrowser} = require("devtools/client/framework/devtools-browser"); + const TEST_URI = "data:text/html;charset=utf-8,

Tooltip Tests

"; const PREF_DEVTOOLS_THEME = "devtools.theme"; @@ -13,82 +15,84 @@ registerCleanupFunction(() => { Services.prefs.clearUserPref(PREF_DEVTOOLS_THEME); }); +let toolbar = gDevToolsBrowser.getDeveloperToolbar(window); + add_task(function* showToolbar() { yield addTab(TEST_URI); info("Starting browser_toolbar_tooltip.js"); - ok(!DeveloperToolbar.visible, "DeveloperToolbar is not visible in runTest"); + ok(!toolbar.visible, "DeveloperToolbar is not visible in runTest"); - let showPromise = observeOnce(DeveloperToolbar.NOTIFICATIONS.SHOW); + let showPromise = observeOnce(toolbar.NOTIFICATIONS.SHOW); document.getElementById("menu_devToolbar").doCommand(); yield showPromise; }); add_task(function* testDimensions() { - let tooltipPanel = DeveloperToolbar.tooltipPanel; + let tooltipPanel = toolbar.tooltipPanel; - DeveloperToolbar.focusManager.helpRequest(); - yield DeveloperToolbar.inputter.setInput("help help"); + toolbar.focusManager.helpRequest(); + yield toolbar.inputter.setInput("help help"); - DeveloperToolbar.inputter.setCursor({ start: "help help".length }); + toolbar.inputter.setCursor({ start: "help help".length }); is(tooltipPanel._dimensions.start, "help ".length, "search param start, when cursor at end"); ok(getLeftMargin() > 30, "tooltip offset, when cursor at end"); - DeveloperToolbar.inputter.setCursor({ start: "help".length }); + toolbar.inputter.setCursor({ start: "help".length }); is(tooltipPanel._dimensions.start, 0, "search param start, when cursor at end of command"); ok(getLeftMargin() > 9, "tooltip offset, when cursor at end of command"); - DeveloperToolbar.inputter.setCursor({ start: "help help".length - 1 }); + toolbar.inputter.setCursor({ start: "help help".length - 1 }); is(tooltipPanel._dimensions.start, "help ".length, "search param start, when cursor at penultimate position"); ok(getLeftMargin() > 30, "tooltip offset, when cursor at penultimate position"); - DeveloperToolbar.inputter.setCursor({ start: 0 }); + toolbar.inputter.setCursor({ start: 0 }); is(tooltipPanel._dimensions.start, 0, "search param start, when cursor at start"); ok(getLeftMargin() > 9, "tooltip offset, when cursor at start"); }); add_task(function* testThemes() { - let tooltipPanel = DeveloperToolbar.tooltipPanel; + let tooltipPanel = toolbar.tooltipPanel; ok(tooltipPanel.document, "Tooltip panel is initialized"); Services.prefs.setCharPref(PREF_DEVTOOLS_THEME, "dark"); - yield DeveloperToolbar.inputter.setInput(""); - yield DeveloperToolbar.inputter.setInput("help help"); + yield toolbar.inputter.setInput(""); + yield toolbar.inputter.setInput("help help"); is(tooltipPanel.document.documentElement.getAttribute("devtoolstheme"), "dark", "Tooltip panel has correct theme"); Services.prefs.setCharPref(PREF_DEVTOOLS_THEME, "light"); - yield DeveloperToolbar.inputter.setInput(""); - yield DeveloperToolbar.inputter.setInput("help help"); + yield toolbar.inputter.setInput(""); + yield toolbar.inputter.setInput("help help"); is(tooltipPanel.document.documentElement.getAttribute("devtoolstheme"), "light", "Tooltip panel has correct theme"); }); add_task(function* hideToolbar() { info("Ending browser_toolbar_tooltip.js"); - yield DeveloperToolbar.inputter.setInput(""); + yield toolbar.inputter.setInput(""); - ok(DeveloperToolbar.visible, "DeveloperToolbar is visible in hideToolbar"); + ok(toolbar.visible, "DeveloperToolbar is visible in hideToolbar"); info("Hide toolbar"); - let hidePromise = observeOnce(DeveloperToolbar.NOTIFICATIONS.HIDE); + let hidePromise = observeOnce(toolbar.NOTIFICATIONS.HIDE); document.getElementById("menu_devToolbar").doCommand(); yield hidePromise; - ok(!DeveloperToolbar.visible, "DeveloperToolbar is not visible in hideToolbar"); + ok(!toolbar.visible, "DeveloperToolbar is not visible in hideToolbar"); info("Done test"); }); function getLeftMargin() { - let style = DeveloperToolbar.tooltipPanel._panel.style.marginLeft; + let style = toolbar.tooltipPanel._panel.style.marginLeft; return parseInt(style.slice(0, -2), 10); } diff --git a/devtools/client/shared/test/browser_toolbar_webconsole_errors_count.js b/devtools/client/shared/test/browser_toolbar_webconsole_errors_count.js index 7e8ce10f7ea8..c89fa028e1d9 100644 --- a/devtools/client/shared/test/browser_toolbar_webconsole_errors_count.js +++ b/devtools/client/shared/test/browser_toolbar_webconsole_errors_count.js @@ -7,6 +7,8 @@ // Tests that the developer toolbar errors count works properly. +const {gDevToolsBrowser} = require("devtools/client/framework/devtools-browser"); + // Use the old webconsole since this is directly accessing old DOM, and // the error count isn't reset when pressing the clear button in new one // See Bug 1304794. @@ -15,6 +17,8 @@ registerCleanupFunction(function* () { Services.prefs.clearUserPref("devtools.webconsole.new-frontend-enabled"); }); +let toolbar = gDevToolsBrowser.getDeveloperToolbar(window); + function test() { const TEST_URI = TEST_URI_ROOT + "doc_toolbar_webconsole_errors_count.html"; @@ -35,15 +39,15 @@ function test() { expectUncaughtException(); - if (!DeveloperToolbar.visible) { - DeveloperToolbar.show(true).then(onOpenToolbar); + if (!toolbar.visible) { + toolbar.show(true).then(onOpenToolbar); } else { onOpenToolbar(); } } function onOpenToolbar() { - ok(DeveloperToolbar.visible, "DeveloperToolbar is visible"); + ok(toolbar.visible, "DeveloperToolbar is visible"); webconsole = document.getElementById("developer-toolbar-toolbox-button"); waitForButtonUpdate({ @@ -240,9 +244,9 @@ function test() { if (!check()) { info("wait for: " + options.name); - DeveloperToolbar.on("errors-counter-updated", function onUpdate(event) { + toolbar.on("errors-counter-updated", function onUpdate(event) { if (check()) { - DeveloperToolbar.off(event, onUpdate); + toolbar.off(event, onUpdate); } }); } diff --git a/devtools/client/webconsole/hudservice.js b/devtools/client/webconsole/hudservice.js index 6ab220ff6bdb..5e2c7d5110f8 100644 --- a/devtools/client/webconsole/hudservice.js +++ b/devtools/client/webconsole/hudservice.js @@ -7,6 +7,7 @@ var WebConsoleUtils = require("devtools/client/webconsole/utils").Utils; const {extend} = require("devtools/shared/extend"); var {TargetFactory} = require("devtools/client/framework/target"); +var {gDevToolsBrowser} = require("devtools/client/framework/devtools-browser"); var {Tools} = require("devtools/client/definitions"); const { Task } = require("devtools/shared/task"); var promise = require("promise"); @@ -389,7 +390,8 @@ WebConsole.prototype = { _onClearButton: function WC__onClearButton() { if (this.target.isLocalTab) { - this.browserWindow.DeveloperToolbar.resetErrorsCount(this.target.tab); + gDevToolsBrowser.getDeveloperToolbar(this.browserWindow) + .resetErrorsCount(this.target.tab); } }, diff --git a/dom/animation/test/mozilla/file_hide_and_show.html b/dom/animation/test/mozilla/file_hide_and_show.html index 0771fcce1f73..8c8f543f988d 100644 --- a/dom/animation/test/mozilla/file_hide_and_show.html +++ b/dom/animation/test/mozilla/file_hide_and_show.html @@ -8,6 +8,11 @@ } } +div.pseudo::before { + animation: move 0.01s; + content: 'content'; +} + diff --git a/dom/events/EventStateManager.cpp b/dom/events/EventStateManager.cpp index 152a5246d2b4..1a7c434e8c71 100644 --- a/dom/events/EventStateManager.cpp +++ b/dom/events/EventStateManager.cpp @@ -186,13 +186,6 @@ PrintDocTreeAll(nsIDocShellTreeItem* aItem) } #endif -// mask values for ui.key.chromeAccess and ui.key.contentAccess -#define NS_MODIFIER_SHIFT 1 -#define NS_MODIFIER_CONTROL 2 -#define NS_MODIFIER_ALT 4 -#define NS_MODIFIER_META 8 -#define NS_MODIFIER_OS 16 - /******************************************************************/ /* mozilla::UITimerCallback */ /******************************************************************/ @@ -768,30 +761,34 @@ EventStateManager::PreHandleEvent(nsPresContext* aPresContext, case eKeyPress: { WidgetKeyboardEvent* keyEvent = aEvent->AsKeyboardEvent(); - - int32_t modifierMask = 0; - if (keyEvent->IsShift()) - modifierMask |= NS_MODIFIER_SHIFT; - if (keyEvent->IsControl()) - modifierMask |= NS_MODIFIER_CONTROL; - if (keyEvent->IsAlt()) - modifierMask |= NS_MODIFIER_ALT; - if (keyEvent->IsMeta()) - modifierMask |= NS_MODIFIER_META; - if (keyEvent->IsOS()) - modifierMask |= NS_MODIFIER_OS; - - // Prevent keyboard scrolling while an accesskey modifier is in use. - if (modifierMask) { - bool matchesContentAccessKey = (modifierMask == Prefs::ContentAccessModifierMask()); - - if (modifierMask == Prefs::ChromeAccessModifierMask() || - matchesContentAccessKey) { + if (keyEvent->ModifiersMatchWithAccessKey(AccessKeyType::eChrome) || + keyEvent->ModifiersMatchWithAccessKey(AccessKeyType::eContent)) { + // If the eKeyPress event will be sent to a remote process, this + // process needs to wait reply from the remote process for checking if + // preceding eKeyDown event is consumed. If preceding eKeyDown event + // is consumed in the remote process, TabChild won't send the event + // back to this process. So, only when this process receives a reply + // eKeyPress event in TabParent, we should handle accesskey in this + // process. + if (IsRemoteTarget(GetFocusedContent())) { + // However, if there is no accesskey target for the key combination, + // we don't need to wait reply from the remote process. Otherwise, + // Mark the event as waiting reply from remote process and stop + // propagation in this process. + if (CheckIfEventMatchesAccessKey(keyEvent, aPresContext)) { + keyEvent->StopPropagation(); + keyEvent->MarkAsWaitingReplyFromRemoteProcess(); + } + } + // If the event target is in this process, we can handle accesskey now + // since if preceding eKeyDown event was consumed, eKeyPress event + // won't be dispatched by widget. So, coming eKeyPress event means + // that the preceding eKeyDown event wasn't consumed in this case. + else { AutoTArray accessCharCodes; keyEvent->GetAccessKeyCandidates(accessCharCodes); - if (HandleAccessKey(keyEvent, aPresContext, accessCharCodes, - modifierMask, matchesContentAccessKey)) { + if (HandleAccessKey(keyEvent, aPresContext, accessCharCodes)) { *aStatus = nsEventStatus_eConsumeNoDefault; } } @@ -817,6 +814,18 @@ EventStateManager::PreHandleEvent(nsPresContext* aPresContext, RefPtr composition = IMEStateManager::GetTextCompositionFor(aPresContext); aEvent->AsKeyboardEvent()->mIsComposing = !!composition; + + // Widget may need to perform default action for specific keyboard + // event if it's not consumed. In this case, widget has already marked + // the event as "waiting reply from remote process". However, we need + // to reset it if the target (focused content) isn't in a remote process + // because PresShell needs to check if it's marked as so before + // dispatching events into the DOM tree. + if (aEvent->IsWaitingReplyFromRemoteProcess() && + !aEvent->PropagationStopped() && + !IsRemoteTarget(content)) { + aEvent->ResetWaitingReplyFromRemoteProcessState(); + } } break; case eWheel: @@ -924,23 +933,21 @@ EventStateManager::HandleQueryContentEvent(WidgetQueryContentEvent* aEvent) handler.HandleQueryContentEvent(aEvent); } -// static -int32_t -EventStateManager::GetAccessModifierMaskFor(nsISupports* aDocShell) +static AccessKeyType +GetAccessKeyTypeFor(nsISupports* aDocShell) { nsCOMPtr treeItem(do_QueryInterface(aDocShell)); - if (!treeItem) - return -1; // invalid modifier + if (!treeItem) { + return AccessKeyType::eNone; + } switch (treeItem->ItemType()) { - case nsIDocShellTreeItem::typeChrome: - return Prefs::ChromeAccessModifierMask(); - - case nsIDocShellTreeItem::typeContent: - return Prefs::ContentAccessModifierMask(); - - default: - return -1; // invalid modifier + case nsIDocShellTreeItem::typeChrome: + return AccessKeyType::eChrome; + case nsIDocShellTreeItem::typeContent: + return AccessKeyType::eContent; + default: + return AccessKeyType::eNone; } } @@ -992,8 +999,22 @@ IsAccessKeyTarget(nsIContent* aContent, nsIFrame* aFrame, nsAString& aKey) } bool -EventStateManager::ExecuteAccessKey(nsTArray& aAccessCharCodes, - bool aIsTrustedEvent) +EventStateManager::CheckIfEventMatchesAccessKey(WidgetKeyboardEvent* aEvent, + nsPresContext* aPresContext) +{ + AutoTArray accessCharCodes; + aEvent->GetAccessKeyCandidates(accessCharCodes); + return WalkESMTreeToHandleAccessKey(const_cast(aEvent), + aPresContext, accessCharCodes, + nullptr, eAccessKeyProcessingNormal, + false); +} + +bool +EventStateManager::LookForAccessKeyAndExecute( + nsTArray& aAccessCharCodes, + bool aIsTrustedEvent, + bool aExecute) { int32_t count, start = -1; nsIContent* focusedContent = GetFocusedContent(); @@ -1013,6 +1034,9 @@ EventStateManager::ExecuteAccessKey(nsTArray& aAccessCharCodes, content = mAccessKeys[(start + count) % length]; frame = content->GetPrimaryFrame(); if (IsAccessKeyTarget(content, frame, accessKey)) { + if (!aExecute) { + return true; + } bool shouldActivate = Prefs::KeyCausesActivation(); while (shouldActivate && ++count <= length) { nsIContent *oc = mAccessKeys[(start + count) % length]; @@ -1059,30 +1083,33 @@ EventStateManager::GetAccessKeyLabelPrefix(Element* aElement, nsAString& aPrefix nsAutoString separator, modifierText; nsContentUtils::GetModifierSeparatorText(separator); - nsCOMPtr container = aElement->OwnerDoc()->GetDocShell(); - int32_t modifierMask = GetAccessModifierMaskFor(container); - - if (modifierMask == -1) { + AccessKeyType accessKeyType = + GetAccessKeyTypeFor(aElement->OwnerDoc()->GetDocShell()); + if (accessKeyType == AccessKeyType::eNone) { + return; + } + Modifiers modifiers = WidgetKeyboardEvent::AccessKeyModifiers(accessKeyType); + if (modifiers == MODIFIER_NONE) { return; } - if (modifierMask & NS_MODIFIER_CONTROL) { + if (modifiers & MODIFIER_CONTROL) { nsContentUtils::GetControlText(modifierText); aPrefix.Append(modifierText + separator); } - if (modifierMask & NS_MODIFIER_META) { + if (modifiers & MODIFIER_META) { nsContentUtils::GetMetaText(modifierText); aPrefix.Append(modifierText + separator); } - if (modifierMask & NS_MODIFIER_OS) { + if (modifiers & MODIFIER_OS) { nsContentUtils::GetOSText(modifierText); aPrefix.Append(modifierText + separator); } - if (modifierMask & NS_MODIFIER_ALT) { + if (modifiers & MODIFIER_ALT) { nsContentUtils::GetAltText(modifierText); aPrefix.Append(modifierText + separator); } - if (modifierMask & NS_MODIFIER_SHIFT) { + if (modifiers & MODIFIER_SHIFT) { nsContentUtils::GetShiftText(modifierText); aPrefix.Append(modifierText + separator); } @@ -1092,12 +1119,11 @@ struct MOZ_STACK_CLASS AccessKeyInfo { WidgetKeyboardEvent* event; nsTArray& charCodes; - int32_t modifierMask; - AccessKeyInfo(WidgetKeyboardEvent* aEvent, nsTArray& aCharCodes, int32_t aModifierMask) + AccessKeyInfo(WidgetKeyboardEvent* aEvent, + nsTArray& aCharCodes) : event(aEvent) , charCodes(aCharCodes) - , modifierMask(aModifierMask) { } }; @@ -1111,10 +1137,15 @@ HandleAccessKeyInRemoteChild(TabParent* aTabParent, void* aArg) bool active; aTabParent->GetDocShellIsActive(&active); if (active) { - accessKeyInfo->event->mAccessKeyForwardedToChild = true; + // Even if there is no target for the accesskey in this process, + // the event may match with a content accesskey. If so, the keyboard + // event should be handled with reply event for preventing double action. + // (e.g., Alt+Shift+F on Windows may focus a content in remote and open + // "File" menu.) + accessKeyInfo->event->StopPropagation(); + accessKeyInfo->event->MarkAsWaitingReplyFromRemoteProcess(); aTabParent->HandleAccessKey(*accessKeyInfo->event, - accessKeyInfo->charCodes, - accessKeyInfo->modifierMask); + accessKeyInfo->charCodes); return true; } @@ -1122,25 +1153,29 @@ HandleAccessKeyInRemoteChild(TabParent* aTabParent, void* aArg) } bool -EventStateManager::HandleAccessKey(WidgetKeyboardEvent* aEvent, - nsPresContext* aPresContext, - nsTArray& aAccessCharCodes, - bool aMatchesContentAccessKey, - nsIDocShellTreeItem* aBubbledFrom, - ProcessingAccessKeyState aAccessKeyState, - int32_t aModifierMask) +EventStateManager::WalkESMTreeToHandleAccessKey( + WidgetKeyboardEvent* aEvent, + nsPresContext* aPresContext, + nsTArray& aAccessCharCodes, + nsIDocShellTreeItem* aBubbledFrom, + ProcessingAccessKeyState aAccessKeyState, + bool aExecute) { EnsureDocument(mPresContext); nsCOMPtr docShell = aPresContext->GetDocShell(); if (NS_WARN_IF(!docShell) || NS_WARN_IF(!mDocument)) { return false; } - + AccessKeyType accessKeyType = GetAccessKeyTypeFor(docShell); + if (accessKeyType == AccessKeyType::eNone) { + return false; + } // Alt or other accesskey modifier is down, we may need to do an accesskey. if (mAccessKeys.Count() > 0 && - aModifierMask == GetAccessModifierMaskFor(docShell)) { + aEvent->ModifiersMatchWithAccessKey(accessKeyType)) { // Someone registered an accesskey. Find and activate it. - if (ExecuteAccessKey(aAccessCharCodes, aEvent->IsTrusted())) { + if (LookForAccessKeyAndExecute(aAccessCharCodes, + aEvent->IsTrusted(), aExecute)) { return true; } } @@ -1173,9 +1208,9 @@ EventStateManager::HandleAccessKey(WidgetKeyboardEvent* aEvent, static_cast(subPC->EventStateManager()); if (esm && - esm->HandleAccessKey(aEvent, subPC, aAccessCharCodes, - aMatchesContentAccessKey, nullptr, - eAccessKeyProcessingDown, aModifierMask)) { + esm->WalkESMTreeToHandleAccessKey(aEvent, subPC, aAccessCharCodes, + nullptr, eAccessKeyProcessingDown, + aExecute)) { return true; } } @@ -1196,29 +1231,38 @@ EventStateManager::HandleAccessKey(WidgetKeyboardEvent* aEvent, EventStateManager* esm = static_cast(parentPC->EventStateManager()); if (esm && - esm->HandleAccessKey(aEvent, parentPC, aAccessCharCodes, - aMatchesContentAccessKey, docShell, - eAccessKeyProcessingDown, aModifierMask)) { + esm->WalkESMTreeToHandleAccessKey(aEvent, parentPC, aAccessCharCodes, + docShell, eAccessKeyProcessingDown, + aExecute)) { return true; } } }// if end. bubble up process // If the content access key modifier is pressed, try remote children - if (aMatchesContentAccessKey && mDocument && mDocument->GetWindow()) { - // If the focus is currently on a node with a TabParent, the key event will - // get forwarded to the child process and HandleAccessKey called from there. + if (aExecute && + aEvent->ModifiersMatchWithAccessKey(AccessKeyType::eContent) && + mDocument && mDocument->GetWindow()) { + // If the focus is currently on a node with a TabParent, the key event + // should've gotten forwarded to the child process and HandleAccessKey + // called from there. + if (TabParent::GetFrom(GetFocusedContent())) { + // If access key may be only in remote contents, this method won't handle + // access key synchronously. In this case, only reply event should reach + // here. + MOZ_ASSERT(aEvent->IsHandledInRemoteProcess() || + !aEvent->IsWaitingReplyFromRemoteProcess()); + } // If focus is somewhere else, then we need to check the remote children. - nsFocusManager* fm = nsFocusManager::GetFocusManager(); - nsIContent* focusedContent = fm ? fm->GetFocusedContent() : nullptr; - if (TabParent::GetFrom(focusedContent)) { - // A remote child process is focused. The key event should get sent to - // the child process. - aEvent->mAccessKeyForwardedToChild = true; - } else { - AccessKeyInfo accessKeyInfo(aEvent, aAccessCharCodes, aModifierMask); + // However, if the event has already been handled in a remote process, + // then, focus is moved from the remote process after posting the event. + // In such case, we shouldn't retry to handle access keys in remote + // processes. + else if (!aEvent->IsHandledInRemoteProcess()) { + AccessKeyInfo accessKeyInfo(aEvent, aAccessCharCodes); nsContentUtils::CallOnAllRemoteChildren(mDocument->GetWindow(), - HandleAccessKeyInRemoteChild, &accessKeyInfo); + HandleAccessKeyInRemoteChild, + &accessKeyInfo); } } @@ -5819,9 +5863,6 @@ EventStateManager::WheelPrefs::IsOverOnePageScrollAllowedY( bool EventStateManager::Prefs::sKeyCausesActivation = true; bool EventStateManager::Prefs::sClickHoldContextMenu = false; -int32_t EventStateManager::Prefs::sGenericAccessModifierKey = -1; -int32_t EventStateManager::Prefs::sChromeAccessModifierMask = 0; -int32_t EventStateManager::Prefs::sContentAccessModifierMask = 0; // static void @@ -5846,21 +5887,6 @@ EventStateManager::Prefs::Init() sClickHoldContextMenu); MOZ_ASSERT(NS_SUCCEEDED(rv), "Failed to observe \"ui.click_hold_context_menus\""); - rv = Preferences::AddIntVarCache(&sGenericAccessModifierKey, - "ui.key.generalAccessKey", - sGenericAccessModifierKey); - MOZ_ASSERT(NS_SUCCEEDED(rv), - "Failed to observe \"ui.key.generalAccessKey\""); - rv = Preferences::AddIntVarCache(&sChromeAccessModifierMask, - "ui.key.chromeAccess", - sChromeAccessModifierMask); - MOZ_ASSERT(NS_SUCCEEDED(rv), - "Failed to observe \"ui.key.chromeAccess\""); - rv = Preferences::AddIntVarCache(&sContentAccessModifierMask, - "ui.key.contentAccess", - sContentAccessModifierMask); - MOZ_ASSERT(NS_SUCCEEDED(rv), - "Failed to observe \"ui.key.contentAccess\""); sPrefsAlreadyCached = true; } @@ -5881,44 +5907,6 @@ EventStateManager::Prefs::Shutdown() Preferences::UnregisterCallback(OnChange, "dom.popup_allowed_events"); } -// static -int32_t -EventStateManager::Prefs::ChromeAccessModifierMask() -{ - return GetAccessModifierMask(nsIDocShellTreeItem::typeChrome); -} - -// static -int32_t -EventStateManager::Prefs::ContentAccessModifierMask() -{ - return GetAccessModifierMask(nsIDocShellTreeItem::typeContent); -} - -// static -int32_t -EventStateManager::Prefs::GetAccessModifierMask(int32_t aItemType) -{ - switch (sGenericAccessModifierKey) { - case -1: break; // use the individual prefs - case nsIDOMKeyEvent::DOM_VK_SHIFT: return NS_MODIFIER_SHIFT; - case nsIDOMKeyEvent::DOM_VK_CONTROL: return NS_MODIFIER_CONTROL; - case nsIDOMKeyEvent::DOM_VK_ALT: return NS_MODIFIER_ALT; - case nsIDOMKeyEvent::DOM_VK_META: return NS_MODIFIER_META; - case nsIDOMKeyEvent::DOM_VK_WIN: return NS_MODIFIER_OS; - default: return 0; - } - - switch (aItemType) { - case nsIDocShellTreeItem::typeChrome: - return sChromeAccessModifierMask; - case nsIDocShellTreeItem::typeContent: - return sContentAccessModifierMask; - default: - return 0; - } -} - /******************************************************************/ /* mozilla::AutoHandlingUserInputStatePusher */ /******************************************************************/ diff --git a/dom/events/EventStateManager.h b/dom/events/EventStateManager.h index 5c474a8fe45e..888e4aeda6d2 100644 --- a/dom/events/EventStateManager.h +++ b/dom/events/EventStateManager.h @@ -184,17 +184,47 @@ public: static void GetAccessKeyLabelPrefix(dom::Element* aElement, nsAString& aPrefix); + /** + * HandleAccessKey() looks for access keys which matches with aEvent and + * execute when it matches with a chrome access key or some content access + * keys. + * If the event may match chrome access keys, this handles the access key + * synchronously (if there are nested ESMs, their HandleAccessKey() are + * also called recursively). + * If the event may match content access keys and focused target is a remote + * process, this does nothing for the content because when this is called, + * it should already have been handled in the remote process. + * If the event may match content access keys and focused target is not in + * remote process but there are some remote children, this will post + * HandleAccessKey messages to all remote children. + * + * @return true if there is accesskey which aEvent and + * aAccessCharCodes match with. Otherwise, false. + * I.e., when this returns true, a target is executed + * or focused. + * Note that even if this returns false, a target in + * remote process may be executed or focused + * asynchronously. + */ bool HandleAccessKey(WidgetKeyboardEvent* aEvent, nsPresContext* aPresContext, - nsTArray& aAccessCharCodes, - int32_t aModifierMask, - bool aMatchesContentAccessKey) + nsTArray& aAccessCharCodes) { - return HandleAccessKey(aEvent, aPresContext, aAccessCharCodes, - aMatchesContentAccessKey, nullptr, - eAccessKeyProcessingNormal, aModifierMask); + return WalkESMTreeToHandleAccessKey(aEvent, aPresContext, aAccessCharCodes, + nullptr, eAccessKeyProcessingNormal, + true); } + /** + * CheckIfEventMatchesAccessKey() looks for access key which matches with + * aEvent in the process but won't execute it. + * + * @return true if there is accesskey which aEvent matches with + * in this process. Otherwise, false. + */ + bool CheckIfEventMatchesAccessKey(WidgetKeyboardEvent* aEvent, + nsPresContext* aPresContext); + nsresult SetCursor(int32_t aCursor, imgIContainer* aContainer, bool aHaveHotspot, float aHotspotX, float aHotspotY, nsIWidget* aWidget, bool aLockCursor); @@ -316,8 +346,6 @@ protected: public: static bool KeyCausesActivation() { return sKeyCausesActivation; } static bool ClickHoldContextMenu() { return sClickHoldContextMenu; } - static int32_t ChromeAccessModifierMask(); - static int32_t ContentAccessModifierMask(); static void Init(); static void OnChange(const char* aPrefName, void*); @@ -326,19 +354,10 @@ protected: private: static bool sKeyCausesActivation; static bool sClickHoldContextMenu; - static int32_t sGenericAccessModifierKey; - static int32_t sChromeAccessModifierMask; - static int32_t sContentAccessModifierMask; static int32_t GetAccessModifierMask(int32_t aItemType); }; - /** - * Get appropriate access modifier mask for the aDocShell. Returns -1 if - * access key isn't available. - */ - static int32_t GetAccessModifierMaskFor(nsISupports* aDocShell); - /* * If aTargetFrame's widget has a cached cursor value, resets the cursor * such that the next call to SetCursor on the widget will force an update @@ -431,7 +450,7 @@ protected: void FlushPendingEvents(nsPresContext* aPresContext); /** - * The phases of HandleAccessKey processing. See below. + * The phases of WalkESMTreeToHandleAccessKey processing. See below. */ typedef enum { eAccessKeyProcessingNormal = 0, @@ -440,35 +459,55 @@ protected: } ProcessingAccessKeyState; /** - * Access key handling. If there is registered content for the accesskey - * given by the key event and modifier mask then call - * content.PerformAccesskey(), otherwise call HandleAccessKey() recursively, - * on descendant docshells first, then on the ancestor (with |aBubbledFrom| - * set to the docshell associated with |this|), until something matches. + * Walk EMS to look for access key and execute found access key when aExecute + * is true. + * If there is registered content for the accesskey given by the key event + * and modifier mask then call content.PerformAccesskey(), otherwise call + * WalkESMTreeToHandleAccessKey() recursively, on descendant docshells first, + * then on the ancestor (with |aBubbledFrom| set to the docshell associated + * with |this|), until something matches. * * @param aEvent the keyboard event triggering the acccess key * @param aPresContext the presentation context * @param aAccessCharCodes list of charcode candidates - * @param aMatchesContentAccessKey true if the content accesskey modifier is pressed - * @param aBubbledFrom is used by an ancestor to avoid calling HandleAccessKey() - * on the child the call originally came from, i.e. this is the child - * that recursively called us in its Up phase. The initial caller - * passes |nullptr| here. This is to avoid an infinite loop. + * @param aBubbledFrom is used by an ancestor to avoid calling + * WalkESMTreeToHandleAccessKey() on the child the call originally + * came from, i.e. this is the child that recursively called us in + * its Up phase. The initial caller passes |nullptr| here. This is to + * avoid an infinite loop. * @param aAccessKeyState Normal, Down or Up processing phase (see enums * above). The initial event receiver uses 'normal', then 'down' when * processing children and Up when recursively calling its ancestor. - * @param aModifierMask modifier mask for the key event + * @param aExecute is true, execute an accesskey if it's found. Otherwise, + * found accesskey won't be executed. + * + * @return true if there is a target which aEvent and + * aAccessCharCodes match with in this process. + * Otherwise, false. I.e., when this returns true and + * aExecute is true, a target is executed or focused. + * Note that even if this returns false, a target in + * remote process may be executed or focused + * asynchronously. */ - bool HandleAccessKey(WidgetKeyboardEvent* aEvent, - nsPresContext* aPresContext, - nsTArray& aAccessCharCodes, - bool aMatchesContentAccessKey, - nsIDocShellTreeItem* aBubbledFrom, - ProcessingAccessKeyState aAccessKeyState, - int32_t aModifierMask); + bool WalkESMTreeToHandleAccessKey(WidgetKeyboardEvent* aEvent, + nsPresContext* aPresContext, + nsTArray& aAccessCharCodes, + nsIDocShellTreeItem* aBubbledFrom, + ProcessingAccessKeyState aAccessKeyState, + bool aExecute); - bool ExecuteAccessKey(nsTArray& aAccessCharCodes, - bool aIsTrustedEvent); + /** + * Look for access key and execute found access key if aExecute is true in + * the instance. + * + * @return true if there is a target which matches with + * aAccessCharCodes and aIsTrustedEvent. Otherwise, + * false. I.e., when this returns true and aExecute + * is true, a target is executed or focused. + */ + bool LookForAccessKeyAndExecute(nsTArray& aAccessCharCodes, + bool aIsTrustedEvent, + bool aExecute); //--------------------------------------------- // DocShell Focus Traversal Methods diff --git a/dom/html/HTMLVideoElement.cpp b/dom/html/HTMLVideoElement.cpp index 8d6f7a27f6a5..aea48a7270ac 100644 --- a/dom/html/HTMLVideoElement.cpp +++ b/dom/html/HTMLVideoElement.cpp @@ -23,6 +23,7 @@ #include "nsITimer.h" +#include "FrameStatistics.h" #include "MediaError.h" #include "MediaDecoder.h" #include "mozilla/Preferences.h" diff --git a/dom/ipc/PBrowser.ipdl b/dom/ipc/PBrowser.ipdl index 6740a0613ef5..7f5ba5f08eeb 100644 --- a/dom/ipc/PBrowser.ipdl +++ b/dom/ipc/PBrowser.ipdl @@ -806,10 +806,9 @@ child: * * @param event keyboard event * @param isTrusted true if triggered by a trusted key event - * @param modifierMask indicates which accesskey modifiers are pressed */ async HandleAccessKey(WidgetKeyboardEvent event, - uint32_t[] charCodes, int32_t modifierMask); + uint32_t[] charCodes); /** * Tells the root child docShell whether or not to use diff --git a/dom/ipc/TabChild.cpp b/dom/ipc/TabChild.cpp index 940d2625ea56..ce774c0650a4 100644 --- a/dom/ipc/TabChild.cpp +++ b/dom/ipc/TabChild.cpp @@ -1599,7 +1599,7 @@ TabChild::RecvRealMouseButtonEvent(const WidgetMouseEvent& aEvent, localEvent.mWidget = mPuppetWidget; APZCCallbackHelper::ApplyCallbackTransform(localEvent, aGuid, mPuppetWidget->GetDefaultScale()); - APZCCallbackHelper::DispatchWidgetEvent(localEvent); + DispatchWidgetEventViaAPZ(localEvent); if (aInputBlockId && aEvent.mFlags.mHandledByAPZ) { mAPZEventState->ProcessMouseEvent(aEvent, aGuid, aInputBlockId); @@ -1648,6 +1648,13 @@ TabChild::MaybeCoalesceWheelEvent(const WidgetWheelEvent& aEvent, return false; } +nsEventStatus +TabChild::DispatchWidgetEventViaAPZ(WidgetGUIEvent& aEvent) +{ + aEvent.ResetWaitingReplyFromRemoteProcessState(); + return APZCCallbackHelper::DispatchWidgetEvent(aEvent); +} + void TabChild::MaybeDispatchCoalescedWheelEvent() { @@ -1678,7 +1685,7 @@ TabChild::DispatchWheelEvent(const WidgetWheelEvent& aEvent, localEvent.mWidget = mPuppetWidget; APZCCallbackHelper::ApplyCallbackTransform(localEvent, aGuid, mPuppetWidget->GetDefaultScale()); - APZCCallbackHelper::DispatchWidgetEvent(localEvent); + DispatchWidgetEventViaAPZ(localEvent); if (localEvent.mCanTriggerSwipe) { SendRespondStartSwipeEvent(aInputBlockId, localEvent.TriggersSwipe()); @@ -1745,7 +1752,7 @@ TabChild::RecvRealTouchEvent(const WidgetTouchEvent& aEvent, } // Dispatch event to content (potentially a long-running operation) - nsEventStatus status = APZCCallbackHelper::DispatchWidgetEvent(localEvent); + nsEventStatus status = DispatchWidgetEventViaAPZ(localEvent); if (!AsyncPanZoomEnabled()) { // We shouldn't have any e10s platforms that have touch events enabled @@ -1805,7 +1812,7 @@ TabChild::RecvRealDragEvent(const WidgetDragEvent& aEvent, } } - APZCCallbackHelper::DispatchWidgetEvent(localEvent); + DispatchWidgetEventViaAPZ(localEvent); return IPC_OK(); } @@ -1814,7 +1821,7 @@ TabChild::RecvPluginEvent(const WidgetPluginEvent& aEvent) { WidgetPluginEvent localEvent(aEvent); localEvent.mWidget = mPuppetWidget; - nsEventStatus status = APZCCallbackHelper::DispatchWidgetEvent(localEvent); + nsEventStatus status = DispatchWidgetEventViaAPZ(localEvent); if (status != nsEventStatus_eConsumeNoDefault) { // If not consumed, we should call default action SendDefaultProcOfPluginEvent(aEvent); @@ -1916,7 +1923,7 @@ TabChild::RecvRealKeyEvent(const WidgetKeyboardEvent& aEvent) WidgetKeyboardEvent localEvent(aEvent); localEvent.mWidget = mPuppetWidget; localEvent.mUniqueId = aEvent.mUniqueId; - nsEventStatus status = APZCCallbackHelper::DispatchWidgetEvent(localEvent); + nsEventStatus status = DispatchWidgetEventViaAPZ(localEvent); // Update the end time of the possible repeated event so that we can skip // some incoming events in case event handling took long time. @@ -1931,16 +1938,21 @@ TabChild::RecvRealKeyEvent(const WidgetKeyboardEvent& aEvent) } // If a response is desired from the content process, resend the key event. - // If mAccessKeyForwardedToChild is set, then don't resend the key event yet - // as RecvHandleAccessKey will do this. - if (localEvent.WantReplyFromContentProcess()) { + if (aEvent.WantReplyFromContentProcess()) { + // If the event's default isn't prevented but the status is no default, + // That means that the event was consumed by EventStateManager or something + // which is not a usual event handler. In such case, prevent its default + // as a default handler. For example, when an eKeyPress event matches + // with a content accesskey, and it's executed, peventDefault() of the + // event won't be called but the status is set to "no default". Then, + // the event shouldn't be handled by nsMenuBarListener in the main process. + if (!localEvent.DefaultPrevented() && + status == nsEventStatus_eConsumeNoDefault) { + localEvent.PreventDefault(); + } SendReplyKeyEvent(localEvent); } - if (localEvent.mAccessKeyForwardedToChild) { - SendAccessKeyNotHandled(localEvent); - } - return IPC_OK(); } @@ -1962,7 +1974,7 @@ TabChild::RecvCompositionEvent(const WidgetCompositionEvent& aEvent) { WidgetCompositionEvent localEvent(aEvent); localEvent.mWidget = mPuppetWidget; - APZCCallbackHelper::DispatchWidgetEvent(localEvent); + DispatchWidgetEventViaAPZ(localEvent); Unused << SendOnEventNeedingAckHandled(aEvent.mMessage); return IPC_OK(); } @@ -1972,7 +1984,7 @@ TabChild::RecvSelectionEvent(const WidgetSelectionEvent& aEvent) { WidgetSelectionEvent localEvent(aEvent); localEvent.mWidget = mPuppetWidget; - APZCCallbackHelper::DispatchWidgetEvent(localEvent); + DispatchWidgetEventViaAPZ(localEvent); Unused << SendOnEventNeedingAckHandled(aEvent.mMessage); return IPC_OK(); } @@ -2244,8 +2256,7 @@ TabChild::RecvSwappedWithOtherRemoteLoader(const IPCTabContext& aContext) mozilla::ipc::IPCResult TabChild::RecvHandleAccessKey(const WidgetKeyboardEvent& aEvent, - nsTArray&& aCharCodes, - const int32_t& aModifierMask) + nsTArray&& aCharCodes) { nsCOMPtr document(GetDocument()); nsCOMPtr presShell = document->GetShell(); @@ -2254,8 +2265,7 @@ TabChild::RecvHandleAccessKey(const WidgetKeyboardEvent& aEvent, if (pc) { if (!pc->EventStateManager()-> HandleAccessKey(&(const_cast(aEvent)), - pc, aCharCodes, - aModifierMask, true)) { + pc, aCharCodes)) { // If no accesskey was found, inform the parent so that accesskeys on // menus can be handled. WidgetKeyboardEvent localEvent(aEvent); diff --git a/dom/ipc/TabChild.h b/dom/ipc/TabChild.h index a1de6824cc4f..438ca410b188 100644 --- a/dom/ipc/TabChild.h +++ b/dom/ipc/TabChild.h @@ -595,9 +595,9 @@ public: virtual mozilla::ipc::IPCResult RecvThemeChanged(nsTArray&& aLookAndFeelIntCache) override; - virtual mozilla::ipc::IPCResult RecvHandleAccessKey(const WidgetKeyboardEvent& aEvent, - nsTArray&& aCharCodes, - const int32_t& aModifierMask) override; + virtual mozilla::ipc::IPCResult + RecvHandleAccessKey(const WidgetKeyboardEvent& aEvent, + nsTArray&& aCharCodes) override; virtual mozilla::ipc::IPCResult RecvSetUseGlobalHistory(const bool& aUse) override; @@ -802,6 +802,11 @@ private: void MaybeDispatchCoalescedWheelEvent(); + /** + * Dispatch aEvent on aEvent.mWidget. + */ + nsEventStatus DispatchWidgetEventViaAPZ(WidgetGUIEvent& aEvent); + void DispatchWheelEvent(const WidgetWheelEvent& aEvent, const ScrollableLayerGuid& aGuid, const uint64_t& aInputBlockId); diff --git a/dom/ipc/TabParent.cpp b/dom/ipc/TabParent.cpp index 399bfe779935..18f546ff8a2f 100644 --- a/dom/ipc/TabParent.cpp +++ b/dom/ipc/TabParent.cpp @@ -817,15 +817,14 @@ TabParent::ThemeChanged() void TabParent::HandleAccessKey(const WidgetKeyboardEvent& aEvent, - nsTArray& aCharCodes, - const int32_t& aModifierMask) + nsTArray& aCharCodes) { if (!mIsDestroyed) { // Note that we don't need to mark aEvent is posted to a remote process // because the event may be dispatched to it as normal keyboard event. // Therefore, we should use local copy to send it. WidgetKeyboardEvent localEvent(aEvent); - Unused << SendHandleAccessKey(localEvent, aCharCodes, aModifierMask); + Unused << SendHandleAccessKey(localEvent, aCharCodes); } } @@ -2047,7 +2046,23 @@ TabParent::RecvReplyKeyEvent(const WidgetKeyboardEvent& aEvent) AutoHandlingUserInputStatePusher userInpStatePusher(localEvent.IsTrusted(), &localEvent, doc); - EventDispatcher::Dispatch(mFrameElement, presContext, &localEvent); + nsEventStatus status = nsEventStatus_eIgnore; + + // Handle access key in this process before dispatching reply event because + // ESM handles it before dispatching the event to the DOM tree. + if (localEvent.mMessage == eKeyPress && + (localEvent.ModifiersMatchWithAccessKey(AccessKeyType::eChrome) || + localEvent.ModifiersMatchWithAccessKey(AccessKeyType::eContent))) { + RefPtr esm = presContext->EventStateManager(); + AutoTArray accessCharCodes; + localEvent.GetAccessKeyCandidates(accessCharCodes); + if (esm->HandleAccessKey(&localEvent, presContext, accessCharCodes)) { + status = nsEventStatus_eConsumeNoDefault; + } + } + + EventDispatcher::Dispatch(mFrameElement, presContext, &localEvent, nullptr, + &status); if (!localEvent.DefaultPrevented() && !localEvent.mFlags.mIsSynthesizedForTests) { @@ -2066,10 +2081,16 @@ TabParent::RecvAccessKeyNotHandled(const WidgetKeyboardEvent& aEvent) { NS_ENSURE_TRUE(mFrameElement, IPC_OK()); + // This is called only when this process had focus and HandleAccessKey + // message was posted to all remote process and each remote process didn't + // execute any content access keys. + // XXX If there were two or more remote processes, this may be called + // twice or more for a keyboard event, that must be a bug. But how to + // detect if received event has already been handled? + WidgetKeyboardEvent localEvent(aEvent); localEvent.MarkAsHandledInRemoteProcess(); localEvent.mMessage = eAccessKeyNotFound; - localEvent.mAccessKeyForwardedToChild = false; // Here we convert the WidgetEvent that we received to an nsIDOMEvent // to be able to dispatch it to the element as the target element. diff --git a/dom/ipc/TabParent.h b/dom/ipc/TabParent.h index 0973df8d8601..c5f3324a8d90 100644 --- a/dom/ipc/TabParent.h +++ b/dom/ipc/TabParent.h @@ -361,8 +361,7 @@ public: void ThemeChanged(); void HandleAccessKey(const WidgetKeyboardEvent& aEvent, - nsTArray& aCharCodes, - const int32_t& aModifierMask); + nsTArray& aCharCodes); void Activate(); diff --git a/dom/media/ADTSDecoder.cpp b/dom/media/ADTSDecoder.cpp index ae8188e48e26..6ab145cb541f 100644 --- a/dom/media/ADTSDecoder.cpp +++ b/dom/media/ADTSDecoder.cpp @@ -25,8 +25,9 @@ ADTSDecoder::Clone(MediaDecoderInit& aInit) MediaDecoderStateMachine* ADTSDecoder::CreateStateMachine() { - MediaFormatReaderInit init(this); + MediaFormatReaderInit init; init.mCrashHelper = GetOwner()->CreateGMPCrashHelper(); + init.mFrameStats = mFrameStats; mReader = new MediaFormatReader(init, new ADTSDemuxer(mResource)); return new MediaDecoderStateMachine(this, mReader); } diff --git a/dom/media/AbstractMediaDecoder.h b/dom/media/AbstractMediaDecoder.h deleted file mode 100644 index 1edd3313c207..000000000000 --- a/dom/media/AbstractMediaDecoder.h +++ /dev/null @@ -1,95 +0,0 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim:set ts=2 sw=2 sts=2 et cindent: */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#ifndef AbstractMediaDecoder_h_ -#define AbstractMediaDecoder_h_ - -#include "mozilla/Attributes.h" -#include "mozilla/StateMirroring.h" - -#include "FrameStatistics.h" -#include "MediaEventSource.h" -#include "MediaInfo.h" -#include "nsISupports.h" -#include "nsDataHashtable.h" -#include "nsThreadUtils.h" - -namespace mozilla { - -namespace layers { -class ImageContainer; -class KnowsCompositor; -} // namespace layers - -class AbstractThread; -class MediaResource; -class ReentrantMonitor; -class VideoFrameContainer; -class MediaDecoderOwner; -class CDMProxy; -class GMPCrashHelper; - -/** - * The AbstractMediaDecoder class describes the public interface for a media decoder - * and is used by the MediaReader classes. - */ -class AbstractMediaDecoder : public nsIObserver -{ -public: - // Increments the parsed, decoded and dropped frame counters by the passed in - // counts. - // Can be called on any thread. - virtual void NotifyDecodedFrames(const FrameStatisticsData& aStats) = 0; - - // Return an abstract thread on which to run main thread runnables. - virtual AbstractThread* AbstractMainThread() const = 0; - virtual VideoFrameContainer* GetVideoFrameContainer() = 0; - virtual mozilla::layers::ImageContainer* GetImageContainer() = 0; - - // Returns the owner of this decoder or null when the decoder is shutting - // down. The owner should only be used on the main thread. - virtual MediaDecoderOwner* GetOwner() const = 0; - - // Set by Reader if the current audio track can be offloaded - virtual void SetPlatformCanOffloadAudio(bool aCanOffloadAudio) { } - - // Stack based class to assist in notifying the frame statistics of - // parsed and decoded frames. Use inside video demux & decode functions - // to ensure all parsed and decoded frames are reported on all return paths. - class AutoNotifyDecoded - { - public: - explicit AutoNotifyDecoded(AbstractMediaDecoder* aDecoder) - : mDecoder(aDecoder) - { - } - ~AutoNotifyDecoded() - { - if (mDecoder) { - mDecoder->NotifyDecodedFrames(mStats); - } - } - - FrameStatisticsData mStats; - - private: - AbstractMediaDecoder* mDecoder; - }; - - // Classes directly inheriting from AbstractMediaDecoder do not support - // Observe and it should never be called directly. - NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic, - const char16_t* aData) override - { - MOZ_CRASH("Forbidden method"); - return NS_OK; - } -}; - -} // namespace mozilla - -#endif - diff --git a/dom/media/DecoderTraits.cpp b/dom/media/DecoderTraits.cpp index 09f3cc3b87b7..1e9790f7f8f0 100644 --- a/dom/media/DecoderTraits.cpp +++ b/dom/media/DecoderTraits.cpp @@ -296,11 +296,6 @@ DecoderTraits::CreateReader(const MediaContainerType& aType, { MOZ_ASSERT(NS_IsMainThread()); MediaFormatReader* decoderReader = nullptr; - - if (!aInit.mDecoder) { - return decoderReader; - } - MediaResource* resource = aInit.mResource; #ifdef MOZ_FMP4 diff --git a/dom/media/DecoderTraits.h b/dom/media/DecoderTraits.h index ae2d594aef41..de7890f2d27a 100644 --- a/dom/media/DecoderTraits.h +++ b/dom/media/DecoderTraits.h @@ -14,7 +14,6 @@ class nsACString; namespace mozilla { -class AbstractMediaDecoder; class ChannelMediaDecoder; class DecoderDoctorDiagnostics; class MediaContainerType; diff --git a/dom/media/FrameStatistics.h b/dom/media/FrameStatistics.h index e7d38262935d..1ca580e3fbae 100644 --- a/dom/media/FrameStatistics.h +++ b/dom/media/FrameStatistics.h @@ -129,6 +129,29 @@ public: ++mFrameStatisticsData.mPresentedFrames; } + // Stack based class to assist in notifying the frame statistics of + // parsed and decoded frames. Use inside video demux & decode functions + // to ensure all parsed and decoded frames are reported on all return paths. + class AutoNotifyDecoded + { + public: + explicit AutoNotifyDecoded(FrameStatistics* aFrameStats) + : mFrameStats(aFrameStats) + { + } + ~AutoNotifyDecoded() + { + if (mFrameStats) { + mFrameStats->NotifyDecodedFrames(mStats); + } + } + + FrameStatisticsData mStats; + + private: + FrameStatistics* mFrameStats; + }; + private: ~FrameStatistics() {} diff --git a/dom/media/MediaDecoder.cpp b/dom/media/MediaDecoder.cpp index caafd5b9dbd1..b9d0fd99c04a 100644 --- a/dom/media/MediaDecoder.cpp +++ b/dom/media/MediaDecoder.cpp @@ -284,8 +284,6 @@ MediaDecoder::InitStatics() NS_IMPL_ISUPPORTS(MediaMemoryTracker, nsIMemoryReporter) -NS_IMPL_ISUPPORTS0(MediaDecoder) - void MediaDecoder::NotifyOwnerActivityChanged(bool aIsDocumentVisible, Visibility aElementVisibility, diff --git a/dom/media/MediaDecoder.h b/dom/media/MediaDecoder.h index a92b9f11af15..37e19858cc59 100644 --- a/dom/media/MediaDecoder.h +++ b/dom/media/MediaDecoder.h @@ -7,7 +7,6 @@ #if !defined(MediaDecoder_h_) #define MediaDecoder_h_ -#include "AbstractMediaDecoder.h" #include "DecoderDoctorDiagnostics.h" #include "MediaDecoderOwner.h" #include "MediaEventSource.h" @@ -36,6 +35,7 @@ class nsIPrincipal; namespace mozilla { class AbstractThread; +class FrameStatistics; class VideoFrameContainer; class MediaFormatReader; class MediaDecoderStateMachine; @@ -83,14 +83,14 @@ struct MOZ_STACK_CLASS MediaDecoderInit } }; -class MediaDecoder : public AbstractMediaDecoder +class MediaDecoder { public: typedef MozPromise SeekPromise; - NS_DECL_THREADSAFE_ISUPPORTS + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaDecoder) // Enumeration for the valid play states (see mPlayState) enum PlayState @@ -295,11 +295,12 @@ private: virtual void AddSizeOfResources(ResourceSizes* aSizes); - VideoFrameContainer* GetVideoFrameContainer() final override + VideoFrameContainer* GetVideoFrameContainer() { return mVideoFrameContainer; } - layers::ImageContainer* GetImageContainer() override; + + layers::ImageContainer* GetImageContainer(); // Fire timeupdate events if needed according to the time constraints // outlined in the specification. @@ -385,9 +386,9 @@ private: // Indicate whether the media is same-origin with the element. void UpdateSameOriginStatus(bool aSameOrigin); - MediaDecoderOwner* GetOwner() const override; + MediaDecoderOwner* GetOwner() const; - AbstractThread* AbstractMainThread() const final override + AbstractThread* AbstractMainThread() const { return mAbstractMainThread; } @@ -422,13 +423,6 @@ private: // Return the frame decode/paint related statistics. FrameStatistics& GetFrameStatistics() { return *mFrameStats; } - // Increments the parsed and decoded frame counters by the passed in counts. - // Can be called on any thread. - virtual void NotifyDecodedFrames(const FrameStatisticsData& aStats) override - { - GetFrameStatistics().NotifyDecodedFrames(aStats); - } - void UpdateReadyState() { MOZ_ASSERT(NS_IsMainThread()); diff --git a/dom/media/MediaDecoderOwner.h b/dom/media/MediaDecoderOwner.h index 1af240a05136..9bd554f5d9d6 100644 --- a/dom/media/MediaDecoderOwner.h +++ b/dom/media/MediaDecoderOwner.h @@ -5,7 +5,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef MediaDecoderOwner_h_ #define MediaDecoderOwner_h_ -#include "AbstractMediaDecoder.h" + +#include "MediaInfo.h" #include "nsAutoPtr.h" namespace mozilla { diff --git a/dom/media/MediaDecoderStateMachine.cpp b/dom/media/MediaDecoderStateMachine.cpp index eef76bb243fe..cbfae1095cc4 100644 --- a/dom/media/MediaDecoderStateMachine.cpp +++ b/dom/media/MediaDecoderStateMachine.cpp @@ -1957,15 +1957,12 @@ public: void Enter() { - // TODO : use more approriate way to decide whether need to release - // resource in bug1367983. -#ifndef MOZ_WIDGET_ANDROID if (!mMaster->mLooping) { // We've decoded all samples. // We don't need decoders anymore if not looping. Reader()->ReleaseResources(); } -#endif + bool hasNextFrame = (!mMaster->HasAudio() || !mMaster->mAudioCompleted) && (!mMaster->HasVideo() || !mMaster->mVideoCompleted); diff --git a/dom/media/MediaFormatReader.cpp b/dom/media/MediaFormatReader.cpp index 130bb0651635..b9b72a61d7e1 100644 --- a/dom/media/MediaFormatReader.cpp +++ b/dom/media/MediaFormatReader.cpp @@ -1113,10 +1113,10 @@ MediaFormatReader::MediaFormatReader(MediaFormatReaderInit& aInit, , mCrashHelper(aInit.mCrashHelper) , mDecoderFactory(new DecoderFactory(this)) , mShutdownPromisePool(new ShutdownPromisePool()) - , mDecoder(aInit.mDecoder) , mBuffered(mTaskQueue, TimeIntervals(), "MediaFormatReader::mBuffered (Canonical)") + , mFrameStats(aInit.mFrameStats) { MOZ_ASSERT(aDemuxer); MOZ_COUNT_CTOR(MediaFormatReader); @@ -1212,7 +1212,6 @@ MediaFormatReader::TearDownDecoders() ReleaseResources(); mBuffered.DisconnectAll(); - mDecoder = nullptr; return mTaskQueue->BeginShutdown(); } @@ -1924,7 +1923,7 @@ MediaFormatReader::DecodeDemuxedSamples(TrackType aTrack, void MediaFormatReader::HandleDemuxedSamples( - TrackType aTrack, AbstractMediaDecoder::AutoNotifyDecoded& aA) + TrackType aTrack, FrameStatistics::AutoNotifyDecoded& aA) { MOZ_ASSERT(OnTaskQueue()); @@ -2150,7 +2149,7 @@ MediaFormatReader::Update(TrackType aTrack) // Record number of frames decoded and parsed. Automatically update the // stats counters using the AutoNotifyDecoded stack-based class. - AbstractMediaDecoder::AutoNotifyDecoded a(mDecoder); + FrameStatistics::AutoNotifyDecoded a(mFrameStats); // Drop any frames found prior our internal seek target. while (decoder.mTimeThreshold && decoder.mOutput.Length()) { @@ -2520,8 +2519,8 @@ MediaFormatReader::DropDecodedSamples(TrackType aTrack) } decoder.mOutput.Clear(); decoder.mSizeOfQueue -= lengthDecodedQueue; - if (aTrack == TrackInfo::kVideoTrack && mDecoder) { - mDecoder->NotifyDecodedFrames({ 0, 0, lengthDecodedQueue }); + if (aTrack == TrackInfo::kVideoTrack && mFrameStats) { + mFrameStats->NotifyDecodedFrames({ 0, 0, lengthDecodedQueue }); } } @@ -2552,16 +2551,16 @@ MediaFormatReader::VideoSkipReset(uint32_t aSkipped) // videoskip process and we know they would be late. DropDecodedSamples(TrackInfo::kVideoTrack); // Report the pending frames as dropped. - if (mDecoder) { - mDecoder->NotifyDecodedFrames({ 0, 0, SizeOfVideoQueueInFrames() }); + if (mFrameStats) { + mFrameStats->NotifyDecodedFrames({ 0, 0, SizeOfVideoQueueInFrames() }); } // Cancel any pending demux request and pending demuxed samples. mVideo.mDemuxRequest.DisconnectIfExists(); Reset(TrackType::kVideoTrack); - if (mDecoder) { - mDecoder->NotifyDecodedFrames({ aSkipped, 0, aSkipped }); + if (mFrameStats) { + mFrameStats->NotifyDecodedFrames({ aSkipped, 0, aSkipped }); } mVideo.mNumSamplesSkippedTotal += aSkipped; diff --git a/dom/media/MediaFormatReader.h b/dom/media/MediaFormatReader.h index be942c2864f0..510c231607ba 100644 --- a/dom/media/MediaFormatReader.h +++ b/dom/media/MediaFormatReader.h @@ -9,9 +9,11 @@ #include "mozilla/Atomics.h" #include "mozilla/Maybe.h" +#include "mozilla/StateMirroring.h" #include "mozilla/TaskQueue.h" #include "mozilla/Mutex.h" +#include "FrameStatistics.h" #include "MediaEventSource.h" #include "MediaDataDemuxer.h" #include "MediaMetadataManager.h" @@ -22,7 +24,6 @@ namespace mozilla { -class AbstractMediaDecoder; class CDMProxy; class GMPCrashHelper; class MediaResource; @@ -74,16 +75,11 @@ struct MetadataHolder struct MOZ_STACK_CLASS MediaFormatReaderInit { - AbstractMediaDecoder* const mDecoder; MediaResource* mResource = nullptr; VideoFrameContainer* mVideoFrameContainer = nullptr; + FrameStatistics* mFrameStats = nullptr; already_AddRefed mKnowsCompositor; already_AddRefed mCrashHelper; - - explicit MediaFormatReaderInit(AbstractMediaDecoder* aDecoder) - : mDecoder(aDecoder) - { - } }; class MediaFormatReader final @@ -280,7 +276,7 @@ private: void RequestDemuxSamples(TrackType aTrack); // Handle demuxed samples by the input behavior. void HandleDemuxedSamples(TrackType aTrack, - AbstractMediaDecoder::AutoNotifyDecoded& aA); + FrameStatistics::AutoNotifyDecoded& aA); // Decode any pending already demuxed samples. void DecodeDemuxedSamples(TrackType aTrack, MediaRawData* aSample); @@ -745,9 +741,6 @@ private: void ShutdownDecoder(TrackType aTrack); RefPtr TearDownDecoders(); - // Reference to the owning decoder object. - AbstractMediaDecoder* mDecoder; - bool mShutdown = false; // Buffered range. @@ -767,6 +760,8 @@ private: MediaEventProducer mOnWaitingForKey; MediaEventProducer mOnDecodeWarning; + + RefPtr mFrameStats; }; } // namespace mozilla diff --git a/dom/media/MediaMetadataManager.h b/dom/media/MediaMetadataManager.h index f875353a6543..b15b9145b538 100644 --- a/dom/media/MediaMetadataManager.h +++ b/dom/media/MediaMetadataManager.h @@ -11,7 +11,6 @@ #include "mozilla/LinkedList.h" #include "nsAutoPtr.h" -#include "AbstractMediaDecoder.h" #include "MediaEventSource.h" #include "TimeUnits.h" #include "VideoUtils.h" diff --git a/dom/media/flac/FlacDecoder.cpp b/dom/media/flac/FlacDecoder.cpp index d4bb497a20dc..53fc3c9937f7 100644 --- a/dom/media/flac/FlacDecoder.cpp +++ b/dom/media/flac/FlacDecoder.cpp @@ -26,8 +26,9 @@ FlacDecoder::Clone(MediaDecoderInit& aInit) MediaDecoderStateMachine* FlacDecoder::CreateStateMachine() { - MediaFormatReaderInit init(this); + MediaFormatReaderInit init; init.mCrashHelper = GetOwner()->CreateGMPCrashHelper(); + init.mFrameStats = mFrameStats; mReader = new MediaFormatReader(init, new FlacDemuxer(mResource)); return new MediaDecoderStateMachine(this, mReader); } diff --git a/dom/media/fmp4/MP4Decoder.cpp b/dom/media/fmp4/MP4Decoder.cpp index e435e817f292..189ed165b883 100644 --- a/dom/media/fmp4/MP4Decoder.cpp +++ b/dom/media/fmp4/MP4Decoder.cpp @@ -32,10 +32,11 @@ MP4Decoder::MP4Decoder(MediaDecoderInit& aInit) MediaDecoderStateMachine* MP4Decoder::CreateStateMachine() { - MediaFormatReaderInit init(this); + MediaFormatReaderInit init; init.mVideoFrameContainer = GetVideoFrameContainer(); init.mKnowsCompositor = GetCompositor(); init.mCrashHelper = GetOwner()->CreateGMPCrashHelper(); + init.mFrameStats = mFrameStats; mReader = new MediaFormatReader(init, new MP4Demuxer(mResource)); return new MediaDecoderStateMachine(this, mReader); } diff --git a/dom/media/hls/HLSDecoder.cpp b/dom/media/hls/HLSDecoder.cpp index 5f6a42bb6840..906a7700632d 100644 --- a/dom/media/hls/HLSDecoder.cpp +++ b/dom/media/hls/HLSDecoder.cpp @@ -28,10 +28,11 @@ HLSDecoder::CreateStateMachine() MOZ_ASSERT(resource); auto resourceWrapper = static_cast(resource)->GetResourceWrapper(); MOZ_ASSERT(resourceWrapper); - MediaFormatReaderInit init(this); + MediaFormatReaderInit init; init.mVideoFrameContainer = GetVideoFrameContainer(); init.mKnowsCompositor = GetCompositor(); init.mCrashHelper = GetOwner()->CreateGMPCrashHelper(); + init.mFrameStats = mFrameStats; mReader = new MediaFormatReader(init, new HLSDemuxer(resourceWrapper->GetPlayerId())); diff --git a/dom/media/mediasource/MediaSourceDecoder.cpp b/dom/media/mediasource/MediaSourceDecoder.cpp index c61a1ae1110c..eb7f18b05cd1 100644 --- a/dom/media/mediasource/MediaSourceDecoder.cpp +++ b/dom/media/mediasource/MediaSourceDecoder.cpp @@ -39,10 +39,11 @@ MediaSourceDecoder::CreateStateMachine() { MOZ_ASSERT(NS_IsMainThread()); mDemuxer = new MediaSourceDemuxer(AbstractMainThread()); - MediaFormatReaderInit init(this); + MediaFormatReaderInit init; init.mVideoFrameContainer = GetVideoFrameContainer(); init.mKnowsCompositor = GetCompositor(); init.mCrashHelper = GetOwner()->CreateGMPCrashHelper(); + init.mFrameStats = mFrameStats; mReader = new MediaFormatReader(init, mDemuxer); return new MediaDecoderStateMachine(this, mReader); } diff --git a/dom/media/mediasource/TrackBuffersManager.cpp b/dom/media/mediasource/TrackBuffersManager.cpp index 92948e637320..868ffb9a39fb 100644 --- a/dom/media/mediasource/TrackBuffersManager.cpp +++ b/dom/media/mediasource/TrackBuffersManager.cpp @@ -62,7 +62,7 @@ static Atomic sStreamSourceID(0u); class DispatchKeyNeededEvent : public Runnable { public: - DispatchKeyNeededEvent(AbstractMediaDecoder* aDecoder, + DispatchKeyNeededEvent(MediaSourceDecoder* aDecoder, const nsTArray& aInitData, const nsString& aInitDataType) : Runnable("DispatchKeyNeededEvent") @@ -82,7 +82,7 @@ public: return NS_OK; } private: - RefPtr mDecoder; + RefPtr mDecoder; nsTArray mInitData; nsString mInitDataType; }; diff --git a/dom/media/moz.build b/dom/media/moz.build index 58c36cb1f0a5..30aeaa3b7162 100644 --- a/dom/media/moz.build +++ b/dom/media/moz.build @@ -84,7 +84,6 @@ XPIDL_SOURCES += [ XPIDL_MODULE = 'dom_media' EXPORTS += [ - 'AbstractMediaDecoder.h', 'ADTSDecoder.h', 'ADTSDemuxer.h', 'AudioBufferUtils.h', diff --git a/dom/media/mp3/MP3Decoder.cpp b/dom/media/mp3/MP3Decoder.cpp index bdf107607585..5df5091f00fb 100644 --- a/dom/media/mp3/MP3Decoder.cpp +++ b/dom/media/mp3/MP3Decoder.cpp @@ -26,8 +26,9 @@ MP3Decoder::Clone(MediaDecoderInit& aInit) MediaDecoderStateMachine* MP3Decoder::CreateStateMachine() { - MediaFormatReaderInit init(this); + MediaFormatReaderInit init; init.mCrashHelper = GetOwner()->CreateGMPCrashHelper(); + init.mFrameStats = mFrameStats; mReader = new MediaFormatReader(init, new MP3Demuxer(mResource)); return new MediaDecoderStateMachine(this, mReader); } diff --git a/dom/media/ogg/OggDecoder.cpp b/dom/media/ogg/OggDecoder.cpp index 390c03e7e2cb..1eb9bca46126 100644 --- a/dom/media/ogg/OggDecoder.cpp +++ b/dom/media/ogg/OggDecoder.cpp @@ -16,10 +16,11 @@ namespace mozilla { MediaDecoderStateMachine* OggDecoder::CreateStateMachine() { RefPtr demuxer = new OggDemuxer(mResource); - MediaFormatReaderInit init(this); + MediaFormatReaderInit init; init.mVideoFrameContainer = GetVideoFrameContainer(); init.mKnowsCompositor = GetCompositor(); init.mCrashHelper = GetOwner()->CreateGMPCrashHelper(); + init.mFrameStats = mFrameStats; mReader = new MediaFormatReader(init, demuxer); demuxer->SetChainingEvents(&mReader->TimedMetadataProducer(), &mReader->MediaNotSeekableProducer()); diff --git a/dom/media/ogg/OggDemuxer.cpp b/dom/media/ogg/OggDemuxer.cpp index 26cc97fda18d..8c75e2e06426 100644 --- a/dom/media/ogg/OggDemuxer.cpp +++ b/dom/media/ogg/OggDemuxer.cpp @@ -6,7 +6,6 @@ #include "nsError.h" #include "MediaDecoderStateMachine.h" -#include "AbstractMediaDecoder.h" #include "OggDemuxer.h" #include "OggCodecState.h" #include "mozilla/AbstractThread.h" diff --git a/dom/media/test/test_mediarecorder_principals.html b/dom/media/test/test_mediarecorder_principals.html index 3c09a7b10058..607910870933 100644 --- a/dom/media/test/test_mediarecorder_principals.html +++ b/dom/media/test/test_mediarecorder_principals.html @@ -15,25 +15,26 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=489415 Test for MediaRecorder Principal Handling - - + +
 
diff --git a/dom/media/wave/WaveDecoder.cpp b/dom/media/wave/WaveDecoder.cpp
index 45dfaf84319c..0541f5a2cee5 100644
--- a/dom/media/wave/WaveDecoder.cpp
+++ b/dom/media/wave/WaveDecoder.cpp
@@ -22,8 +22,9 @@ WaveDecoder::Clone(MediaDecoderInit& aInit)
 MediaDecoderStateMachine*
 WaveDecoder::CreateStateMachine()
 {
-  MediaFormatReaderInit init(this);
+  MediaFormatReaderInit init;
   init.mCrashHelper = GetOwner()->CreateGMPCrashHelper();
+  init.mFrameStats = mFrameStats;
   mReader = new MediaFormatReader(init, new WAVDemuxer(mResource));
   return new MediaDecoderStateMachine(this, mReader);
 }
diff --git a/dom/media/webaudio/BufferDecoder.cpp b/dom/media/webaudio/BufferDecoder.cpp
deleted file mode 100644
index 9a9f4a6287da..000000000000
--- a/dom/media/webaudio/BufferDecoder.cpp
+++ /dev/null
@@ -1,69 +0,0 @@
-/* -*- 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 "BufferDecoder.h"
-
-#include "nsISupports.h"
-#include "MediaResource.h"
-
-namespace mozilla {
-
-NS_IMPL_ISUPPORTS0(BufferDecoder)
-
-BufferDecoder::BufferDecoder(MediaResource* aResource,
-                             AbstractThread* aMainThread)
-  : mResource(aResource)
-  , mAbstractMainThread(aMainThread)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-}
-
-BufferDecoder::~BufferDecoder()
-{
-  // The dtor may run on any thread, we cannot be sure.
-}
-
-void
-BufferDecoder::BeginDecoding(TaskQueue* aTaskQueueIdentity)
-{
-  MOZ_ASSERT(!mTaskQueueIdentity && aTaskQueueIdentity);
-  mTaskQueueIdentity = aTaskQueueIdentity;
-}
-
-void
-BufferDecoder::NotifyDecodedFrames(const FrameStatisticsData& aStats)
-{
-  // ignore
-}
-
-VideoFrameContainer*
-BufferDecoder::GetVideoFrameContainer()
-{
-  // no video frame
-  return nullptr;
-}
-
-layers::ImageContainer*
-BufferDecoder::GetImageContainer()
-{
-  // no image container
-  return nullptr;
-}
-
-MediaDecoderOwner*
-BufferDecoder::GetOwner() const
-{
-  // unknown
-  return nullptr;
-}
-
-AbstractThread*
-BufferDecoder::AbstractMainThread() const
-{
-  return mAbstractMainThread;
-}
-
-} // namespace mozilla
diff --git a/dom/media/webaudio/BufferDecoder.h b/dom/media/webaudio/BufferDecoder.h
deleted file mode 100644
index ea47b9bebefc..000000000000
--- a/dom/media/webaudio/BufferDecoder.h
+++ /dev/null
@@ -1,53 +0,0 @@
-/* -*- 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/. */
-
-#ifndef BUFFER_DECODER_H_
-#define BUFFER_DECODER_H_
-
-#include "mozilla/Attributes.h"
-#include "mozilla/ReentrantMonitor.h"
-#include "mozilla/TaskQueue.h"
-
-#include "AbstractMediaDecoder.h"
-
-namespace mozilla {
-
-/**
- * This class provides a decoder object which decodes a media file that lives in
- * a memory buffer.
- */
-class BufferDecoder final : public AbstractMediaDecoder
-{
-public:
-  // This class holds a weak pointer to MediaResource.  It's the responsibility
-  // of the caller to manage the memory of the MediaResource object.
-  explicit BufferDecoder(MediaResource* aResource,
-                         AbstractThread* aMainThread);
-
-  NS_DECL_THREADSAFE_ISUPPORTS
-
-  // This has to be called before decoding begins
-  void BeginDecoding(TaskQueue* aTaskQueueIdentity);
-
-  void NotifyDecodedFrames(const FrameStatisticsData& aStats) final override;
-
-  VideoFrameContainer* GetVideoFrameContainer() final override;
-  layers::ImageContainer* GetImageContainer() final override;
-
-  MediaDecoderOwner* GetOwner() const final override;
-
-  AbstractThread* AbstractMainThread() const final override;
-
-private:
-  virtual ~BufferDecoder();
-  RefPtr mTaskQueueIdentity;
-  RefPtr mResource;
-  const RefPtr mAbstractMainThread;
-};
-
-} // namespace mozilla
-
-#endif /* BUFFER_DECODER_H_ */
diff --git a/dom/media/webaudio/MediaBufferDecoder.cpp b/dom/media/webaudio/MediaBufferDecoder.cpp
index 6b75883cbd14..4ccee6caf344 100644
--- a/dom/media/webaudio/MediaBufferDecoder.cpp
+++ b/dom/media/webaudio/MediaBufferDecoder.cpp
@@ -5,7 +5,6 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "MediaBufferDecoder.h"
-#include "BufferDecoder.h"
 #include "mozilla/dom/AudioContextBinding.h"
 #include "mozilla/dom/BaseAudioContextBinding.h"
 #include "mozilla/dom/DOMException.h"
@@ -133,10 +132,7 @@ private:
   void Cleanup()
   {
     MOZ_ASSERT(NS_IsMainThread());
-    // MediaFormatReader expects that BufferDecoder is alive.
-    // Destruct MediaFormatReader first.
     mDecoderReader = nullptr;
-    mBufferDecoder = nullptr;
     JS_free(nullptr, mBuffer);
   }
 
@@ -146,7 +142,6 @@ private:
   uint32_t mLength;
   WebAudioDecodeJob& mDecodeJob;
   PhaseEnum mPhase;
-  RefPtr mBufferDecoder;
   RefPtr mDecoderReader;
   MediaInfo mMediaInfo;
   MediaQueue mAudioQueue;
@@ -157,7 +152,6 @@ private:
 NS_IMETHODIMP
 MediaDecodeTask::Run()
 {
-  MOZ_ASSERT(mBufferDecoder);
   MOZ_ASSERT(mDecoderReader);
   switch (mPhase) {
   case PhaseEnum::Decode:
@@ -190,15 +184,13 @@ MediaDecodeTask::CreateReader()
   RefPtr resource =
     new BufferMediaResource(static_cast(mBuffer), mLength, principal);
 
-  MOZ_ASSERT(!mBufferDecoder);
   mMainThread =
     mDecodeJob.mContext->GetOwnerGlobal()->AbstractMainThreadFor(TaskCategory::Other);
-  mBufferDecoder = new BufferDecoder(resource, mMainThread);
 
   // If you change this list to add support for new decoders, please consider
   // updating HTMLMediaElement::CreateDecoder as well.
 
-  MediaFormatReaderInit init(mBufferDecoder);
+  MediaFormatReaderInit init;
   init.mResource = resource;
   mDecoderReader = DecoderTraits::CreateReader(mContainerType, init);
 
@@ -245,7 +237,6 @@ MediaDecodeTask::Decode()
 {
   MOZ_ASSERT(!NS_IsMainThread());
 
-  mBufferDecoder->BeginDecoding(mDecoderReader->OwnerThread());
 
   mDecoderReader->AsyncReadMetadata()->Then(mDecoderReader->OwnerThread(), __func__, this,
                                        &MediaDecodeTask::OnMetadataRead,
diff --git a/dom/media/webaudio/moz.build b/dom/media/webaudio/moz.build
index b0f85629647f..fbea53e8432c 100644
--- a/dom/media/webaudio/moz.build
+++ b/dom/media/webaudio/moz.build
@@ -88,7 +88,6 @@ UNIFIED_SOURCES += [
     'AudioProcessingEvent.cpp',
     'AudioScheduledSourceNode.cpp',
     'BiquadFilterNode.cpp',
-    'BufferDecoder.cpp',
     'ChannelMergerNode.cpp',
     'ChannelSplitterNode.cpp',
     'ConstantSourceNode.cpp',
diff --git a/dom/media/webm/WebMDecoder.cpp b/dom/media/webm/WebMDecoder.cpp
index 9475ef23dce5..ba9b70f8fcfd 100644
--- a/dom/media/webm/WebMDecoder.cpp
+++ b/dom/media/webm/WebMDecoder.cpp
@@ -18,10 +18,11 @@ namespace mozilla {
 
 MediaDecoderStateMachine* WebMDecoder::CreateStateMachine()
 {
-  MediaFormatReaderInit init(this);
+  MediaFormatReaderInit init;
   init.mVideoFrameContainer = GetVideoFrameContainer();
   init.mKnowsCompositor = GetCompositor();
   init.mCrashHelper = GetOwner()->CreateGMPCrashHelper();
+  init.mFrameStats = mFrameStats;
   mReader = new MediaFormatReader(init, new WebMDemuxer(mResource));
   return new MediaDecoderStateMachine(this, mReader);
 }
diff --git a/dom/media/webm/WebMDemuxer.cpp b/dom/media/webm/WebMDemuxer.cpp
index a6bda95f43a2..6a98fb97c435 100644
--- a/dom/media/webm/WebMDemuxer.cpp
+++ b/dom/media/webm/WebMDemuxer.cpp
@@ -6,7 +6,6 @@
 
 #include "nsError.h"
 #include "MediaDecoderStateMachine.h"
-#include "AbstractMediaDecoder.h"
 #include "MediaResource.h"
 #ifdef MOZ_AV1
 #include "AOMDecoder.h"
diff --git a/dom/xbl/nsXBLWindowKeyHandler.cpp b/dom/xbl/nsXBLWindowKeyHandler.cpp
index f202e768313c..56c4d4f8d23c 100644
--- a/dom/xbl/nsXBLWindowKeyHandler.cpp
+++ b/dom/xbl/nsXBLWindowKeyHandler.cpp
@@ -533,14 +533,10 @@ nsXBLWindowKeyHandler::HandleEventOnCaptureInSystemEventGroup(
   WidgetKeyboardEvent* widgetEvent =
     aEvent->AsEvent()->WidgetEventPtr()->AsKeyboardEvent();
 
-  if (widgetEvent->IsCrossProcessForwardingStopped() ||
-      widgetEvent->mFlags.mOnlySystemGroupDispatchInContent) {
-    return;
-  }
-
-  nsCOMPtr originalTarget =
-    do_QueryInterface(aEvent->AsEvent()->WidgetEventPtr()->mOriginalTarget);
-  if (!EventStateManager::IsRemoteTarget(originalTarget)) {
+  // If the event won't be sent to remote process, this listener needs to do
+  // nothing.
+  if (widgetEvent->mFlags.mOnlySystemGroupDispatchInContent ||
+      !widgetEvent->WillBeSentToRemoteProcess()) {
     return;
   }
 
diff --git a/js/src/devtools/rootAnalysis/analyzeHeapWrites.js b/js/src/devtools/rootAnalysis/analyzeHeapWrites.js
index ae1f61edeb87..45226debec4d 100644
--- a/js/src/devtools/rootAnalysis/analyzeHeapWrites.js
+++ b/js/src/devtools/rootAnalysis/analyzeHeapWrites.js
@@ -419,7 +419,6 @@ function ignoreContents(entry)
 
         // Needs main thread assertions or other fixes.
         /UndisplayedMap::GetEntryFor/,
-        /nsStyleContext::CalcStyleDifferenceInternal/,
         /EffectCompositor::GetServoAnimationRule/,
         /LookAndFeel::GetColor/,
         "Gecko_CopyStyleContentsFrom",
diff --git a/layout/base/PresShell.cpp b/layout/base/PresShell.cpp
index 468f18320dfe..e0b90218085a 100644
--- a/layout/base/PresShell.cpp
+++ b/layout/base/PresShell.cpp
@@ -8161,7 +8161,19 @@ PresShell::HandleEventInternal(WidgetEvent* aEvent,
       if (aEvent->mClass == eKeyboardEventClass) {
         nsContentUtils::SetIsHandlingKeyBoardEvent(true);
       }
-      if (aEvent->IsAllowedToDispatchDOMEvent()) {
+      // If EventStateManager or something wants reply from remote process and
+      // needs to win any other event listeners in chrome, the event is both
+      // stopped its propagation and marked as "waiting reply from remote
+      // process".  In this case, PresShell shouldn't dispatch the event into
+      // the DOM tree because they don't have a chance to stop propagation in
+      // the system event group.  On the other hand, if its propagation is not
+      // stopped, that means that the event may be reserved by chrome.  If it's
+      // reserved by chrome, the event shouldn't be sent to any remote
+      // processes.  In this case, PresShell needs to dispatch the event to
+      // the DOM tree for checking if it's reserved.
+      if (aEvent->IsAllowedToDispatchDOMEvent() &&
+          !(aEvent->PropagationStopped() &&
+            aEvent->IsWaitingReplyFromRemoteProcess())) {
         MOZ_ASSERT(nsContentUtils::IsSafeToRunScript(),
           "Somebody changed aEvent to cause a DOM event!");
         nsPresShellEventCB eventCB(this);
diff --git a/layout/base/RestyleManager.cpp b/layout/base/RestyleManager.cpp
index b3ec0d635d5e..ffe7de732daf 100644
--- a/layout/base/RestyleManager.cpp
+++ b/layout/base/RestyleManager.cpp
@@ -1814,8 +1814,18 @@ RestyleManager::AnimationsWithDestroyedFrame
   nsTransitionManager* transitionManager =
     mRestyleManager->PresContext()->TransitionManager();
   for (nsIContent* content : aArray) {
-    if (content->GetPrimaryFrame()) {
-      continue;
+    if (aPseudoType == CSSPseudoElementType::NotPseudo) {
+      if (content->GetPrimaryFrame()) {
+        continue;
+      }
+    } else if (aPseudoType == CSSPseudoElementType::before) {
+      if (nsLayoutUtils::GetBeforeFrame(content)) {
+        continue;
+      }
+    } else if (aPseudoType == CSSPseudoElementType::after) {
+      if (nsLayoutUtils::GetAfterFrame(content)) {
+        continue;
+      }
     }
     dom::Element* element = content->AsElement();
 
diff --git a/layout/base/ServoRestyleManager.cpp b/layout/base/ServoRestyleManager.cpp
index 6f63a2dcb3a9..1959dfb17cee 100644
--- a/layout/base/ServoRestyleManager.cpp
+++ b/layout/base/ServoRestyleManager.cpp
@@ -603,12 +603,9 @@ ServoRestyleManager::ProcessPostTraversal(
   RefPtr newContext = nullptr;
   if (wasRestyled && oldStyleContext) {
     MOZ_ASSERT(styleFrame || displayContentsNode);
-    RefPtr currentContext =
+    newContext =
       aRestyleState.StyleSet().ResolveServoStyle(aElement, aRestyleBehavior);
-    MOZ_ASSERT(oldStyleContext->ComputedData() != currentContext->ComputedData());
-
-    newContext = currentContext;
-    newContext->UpdateWithElementState(aElement);
+    MOZ_ASSERT(oldStyleContext->ComputedData() != newContext->ComputedData());
 
     newContext->ResolveSameStructsAs(oldStyleContext);
 
diff --git a/layout/reftests/css-animations/reftest.list b/layout/reftests/css-animations/reftest.list
index 27dd4eed256f..90c7ed42c49a 100644
--- a/layout/reftests/css-animations/reftest.list
+++ b/layout/reftests/css-animations/reftest.list
@@ -51,4 +51,6 @@ fails == background-position-important.html background-position-ref.html # This
 == mask-size-in-delay-1a.html mask-anim-ref.html
 == mask-size-in-delay-1b.html mask-anim-ref.html
 
+== stop-animation-on-discarded-pseudo-element.html about:blank
+
 == updating-animation-on-pseudo-element.html updating-animation-on-pseudo-element-ref.html
diff --git a/layout/reftests/css-animations/stop-animation-on-discarded-pseudo-element.html b/layout/reftests/css-animations/stop-animation-on-discarded-pseudo-element.html
new file mode 100644
index 000000000000..fccfade1cc09
--- /dev/null
+++ b/layout/reftests/css-animations/stop-animation-on-discarded-pseudo-element.html
@@ -0,0 +1,36 @@
+
+
+
+
+ diff --git a/layout/style/ServoBindings.cpp b/layout/style/ServoBindings.cpp index d0fb61ba8b76..f9a2622656a2 100644 --- a/layout/style/ServoBindings.cpp +++ b/layout/style/ServoBindings.cpp @@ -26,6 +26,7 @@ #include "nsIContentInlines.h" #include "nsIDOMNode.h" #include "nsIDocumentInlines.h" +#include "nsILoadContext.h" #include "nsIFrame.h" #include "nsINode.h" #include "nsIPresShell.h" @@ -282,6 +283,22 @@ Gecko_GetNextStyleChild(RawGeckoStyleChildrenIteratorBorrowedMut aIterator) return aIterator->GetNextChild(); } +bool +Gecko_IsPrivateBrowsingEnabled(const nsIDocument* aDoc) +{ + MOZ_ASSERT(aDoc); + MOZ_ASSERT(NS_IsMainThread()); + + nsILoadContext* loadContext = aDoc->GetLoadContext(); + return loadContext && loadContext->UsePrivateBrowsing(); +} + +bool +Gecko_AreVisitedLinksEnabled() +{ + return nsCSSRuleProcessor::VisitedLinksEnabled(); +} + EventStates::ServoType Gecko_ElementState(RawGeckoElementBorrowed aElement) { diff --git a/layout/style/ServoBindings.h b/layout/style/ServoBindings.h index f8ea60574fe7..32e4df539d8b 100644 --- a/layout/style/ServoBindings.h +++ b/layout/style/ServoBindings.h @@ -229,6 +229,13 @@ Gecko_GetVisitedLinkAttrDeclarationBlock(RawGeckoElementBorrowed element); RawServoDeclarationBlockStrongBorrowedOrNull Gecko_GetActiveLinkAttrDeclarationBlock(RawGeckoElementBorrowed element); +// Visited handling. + +// Returns whether private browsing is enabled for a given element. +bool Gecko_IsPrivateBrowsingEnabled(const nsIDocument* aDoc); +// Returns whether visited links are enabled. +bool Gecko_AreVisitedLinksEnabled(); + // Animations bool Gecko_GetAnimationRule(RawGeckoElementBorrowed aElementOrPseudo, diff --git a/layout/style/ServoStyleContext.cpp b/layout/style/ServoStyleContext.cpp index 51f48db181ea..698b64623f43 100644 --- a/layout/style/ServoStyleContext.cpp +++ b/layout/style/ServoStyleContext.cpp @@ -22,53 +22,14 @@ ServoStyleContext::ServoStyleContext( CSSPseudoElementType aPseudoType, ServoComputedDataForgotten aComputedValues) : nsStyleContext(aParent, aPseudoTag, aPseudoType) + , mPresContext(aPresContext) , mSource(aComputedValues) { - mPresContext = aPresContext; AddStyleBit(Servo_ComputedValues_GetStyleBits(this)); - FinishConstruction(); // No need to call ApplyStyleFixups here, since fixups are handled by Servo when // producing the ServoComputedData. } -void -ServoStyleContext::UpdateWithElementState(Element* aElementForAnimation) -{ - bool isLink = false; - bool isVisitedLink = false; - // If we need visited styles for callers where `aElementForAnimation` is null, - // we can precompute these and pass them as flags, similar to nsStyleSet.cpp. - if (aElementForAnimation) { - isLink = nsCSSRuleProcessor::IsLink(aElementForAnimation); - isVisitedLink = nsCSSRuleProcessor::GetContentState(aElementForAnimation) - .HasState(NS_EVENT_STATE_VISITED); - } - - - auto parent = GetParentAllowServo(); - - // The true visited state of the relevant link is used to decided whether - // visited styles should be consulted for all visited dependent properties. - bool relevantLinkVisited = isLink ? isVisitedLink : - (parent && parent->RelevantLinkVisited()); - - if (relevantLinkVisited && GetStyleIfVisited()) { - AddStyleBit(NS_STYLE_RELEVANT_LINK_VISITED); - } - - // Set the body color on the pres context. See nsStyleSet::GetContext - if (aElementForAnimation && - aElementForAnimation->IsHTMLElement(nsGkAtoms::body) && - GetPseudoType() == CSSPseudoElementType::NotPseudo && - mPresContext->CompatibilityMode() == eCompatibility_NavQuirks) { - nsIDocument* doc = aElementForAnimation->GetUncomposedDoc(); - if (doc && doc->GetBodyElement() == aElementForAnimation) { - // Update the prescontext's body color - mPresContext->SetBodyTextColor(StyleColor()->mColor); - } - } -} - } // namespace mozilla diff --git a/layout/style/ServoStyleContext.h b/layout/style/ServoStyleContext.h index e17c377e056a..df7af30e8584 100644 --- a/layout/style/ServoStyleContext.h +++ b/layout/style/ServoStyleContext.h @@ -35,10 +35,6 @@ public: return ComputedData()->visited_style.mPtr; } - // Update visited state for a given element, and set the prescontext's - // body text color if applicable. - void UpdateWithElementState(dom::Element* aElement); - /** * Makes this context match |aOther| in terms of which style structs have * been resolved. diff --git a/layout/style/ServoStyleSet.cpp b/layout/style/ServoStyleSet.cpp index 30b45d384dfd..0a83ffd52482 100644 --- a/layout/style/ServoStyleSet.cpp +++ b/layout/style/ServoStyleSet.cpp @@ -175,33 +175,14 @@ ServoStyleSet::ResolveStyleFor(Element* aElement, ServoStyleContext* aParentContext, LazyComputeBehavior aMayCompute) { - return GetContext(aElement, aParentContext, nullptr, - CSSPseudoElementType::NotPseudo, aMayCompute); -} - -already_AddRefed -ServoStyleSet::GetContext(nsIContent* aContent, - ServoStyleContext* aParentContext, - nsIAtom* aPseudoTag, - CSSPseudoElementType aPseudoType, - LazyComputeBehavior aMayCompute) -{ - MOZ_ASSERT(aContent->IsElement()); - Element* element = aContent->AsElement(); - RefPtr computedValues; if (aMayCompute == LazyComputeBehavior::Allow) { PreTraverseSync(); - computedValues = - ResolveStyleLazily(element, CSSPseudoElementType::NotPseudo, aPseudoTag, aParentContext); - computedValues->UpdateWithElementState(element); - } else { - computedValues = ResolveServoStyle(element, - TraversalRestyleBehavior::Normal); + return ResolveStyleLazily( + aElement, CSSPseudoElementType::NotPseudo, nullptr, aParentContext); } - MOZ_ASSERT(computedValues); - return computedValues.forget(); + return ResolveServoStyle(aElement, TraversalRestyleBehavior::Normal); } @@ -384,7 +365,6 @@ ServoStyleSet::ResolveStyleForText(nsIContent* aTextNode, nsCSSAnonBoxes::mozText, aParentContext, InheritTarget::Text).Consume(); - computedValues->UpdateWithElementState(nullptr); return computedValues.forget(); } @@ -398,8 +378,6 @@ ServoStyleSet::ResolveStyleForFirstLetterContinuation(ServoStyleContext* aParent InheritTarget::FirstLetterContinuation) .Consume(); MOZ_ASSERT(computedValues); - - computedValues->UpdateWithElementState(nullptr); return computedValues.forget(); } @@ -421,7 +399,6 @@ ServoStyleSet::ResolveStyleForPlaceholder() .Consume(); MOZ_ASSERT(computedValues); - computedValues->UpdateWithElementState(nullptr); cache = computedValues; return computedValues.forget(); } @@ -454,10 +431,6 @@ ServoStyleSet::ResolvePseudoElementStyle(Element* aOriginatingElement, } MOZ_ASSERT(computedValues); - - bool isBeforeOrAfter = aType == CSSPseudoElementType::before || - aType == CSSPseudoElementType::after; - computedValues->UpdateWithElementState(isBeforeOrAfter ? aOriginatingElement : nullptr); return computedValues.forget(); } @@ -469,7 +442,6 @@ ServoStyleSet::ResolveTransientStyle(Element* aElement, { RefPtr result = ResolveTransientServoStyle(aElement, aPseudoType, aPseudoTag, aRuleInclusion); - result->UpdateWithElementState(nullptr); return result.forget(); } @@ -507,7 +479,6 @@ ServoStyleSet::ResolveInheritingAnonymousBoxStyle(nsIAtom* aPseudoTag, } #endif - computedValues->UpdateWithElementState(nullptr); return computedValues.forget(); } @@ -552,7 +523,6 @@ ServoStyleSet::ResolveNonInheritingAnonymousBoxStyle(nsIAtom* aPseudoTag) } #endif - computedValues->UpdateWithElementState(nullptr); cache = computedValues; return computedValues.forget(); } @@ -784,14 +754,12 @@ ServoStyleSet::ProbePseudoElementStyle(Element* aOriginatingElement, if (isBeforeOrAfter) { const nsStyleDisplay* display = computedValues->ComputedData()->GetStyleDisplay(); const nsStyleContent* content = computedValues->ComputedData()->GetStyleContent(); - // XXXldb What is contentCount for |content: ""|? if (display->mDisplay == StyleDisplay::None || content->ContentCount() == 0) { return nullptr; } } - computedValues->UpdateWithElementState(isBeforeOrAfter ? aOriginatingElement : nullptr); return computedValues.forget(); } @@ -806,8 +774,8 @@ ServoStyleSet::HasStateDependentStyle(dom::Element* aElement, nsRestyleHint ServoStyleSet::HasStateDependentStyle(dom::Element* aElement, CSSPseudoElementType aPseudoType, - dom::Element* aPseudoElement, - EventStates aStateMask) + dom::Element* aPseudoElement, + EventStates aStateMask) { NS_WARNING("stylo: HasStateDependentStyle always returns zero!"); return nsRestyleHint(0); @@ -1088,14 +1056,45 @@ ServoStyleSet::CompatibilityModeChanged() Servo_StyleSet_CompatModeChanged(mRawSet.get()); } +inline static void +UpdateBodyTextColorIfNeeded( + const Element& aElement, + ServoStyleContext& aStyleContext, + nsPresContext& aPresContext) +{ + if (aPresContext.CompatibilityMode() != eCompatibility_NavQuirks) { + return; + } + + if (!aElement.IsHTMLElement(nsGkAtoms::body)) { + return; + } + + nsIDocument* doc = aElement.GetUncomposedDoc(); + if (!doc || doc->GetBodyElement() != &aElement) { + return; + } + + MOZ_ASSERT(!aStyleContext.GetPseudo()); + + // NOTE(emilio): We do the ComputedData() dance to avoid triggering the + // IsInServoTraversal() assertion in StyleColor(), which seems useful enough + // in the general case, I guess... + aPresContext.SetBodyTextColor( + aStyleContext.ComputedData()->GetStyleColor()->mColor); +} + already_AddRefed ServoStyleSet::ResolveServoStyle(Element* aElement, TraversalRestyleBehavior aRestyleBehavior) { UpdateStylistIfNeeded(); - return Servo_ResolveStyle(aElement, - mRawSet.get(), - aRestyleBehavior).Consume(); + RefPtr result = + Servo_ResolveStyle(aElement, + mRawSet.get(), + aRestyleBehavior).Consume(); + UpdateBodyTextColorIfNeeded(*aElement, *result, *mPresContext); + return result.forget(); } void @@ -1159,6 +1158,10 @@ ServoStyleSet::ResolveStyleLazily(Element* aElement, mRawSet.get()).Consume(); } + if (aPseudoType == CSSPseudoElementType::NotPseudo) { + UpdateBodyTextColorIfNeeded(*aElement, *computedValues, *mPresContext); + } + return computedValues.forget(); } diff --git a/layout/style/ServoStyleSet.h b/layout/style/ServoStyleSet.h index 062621a7d172..341770041342 100644 --- a/layout/style/ServoStyleSet.h +++ b/layout/style/ServoStyleSet.h @@ -481,12 +481,6 @@ private: ServoStyleSet* mSet; }; - already_AddRefed GetContext(nsIContent* aContent, - ServoStyleContext* aParentContext, - nsIAtom* aPseudoTag, - CSSPseudoElementType aPseudoType, - LazyComputeBehavior aMayCompute); - /** * Rebuild the style data. This will force a stylesheet flush, and also * recompute the default computed styles. diff --git a/layout/style/nsCSSRuleProcessor.cpp b/layout/style/nsCSSRuleProcessor.cpp index e38c6994c7eb..cbfb4d0f60aa 100644 --- a/layout/style/nsCSSRuleProcessor.cpp +++ b/layout/style/nsCSSRuleProcessor.cpp @@ -1068,6 +1068,12 @@ nsCSSRuleProcessor::Startup() true); } +/* static */ bool +nsCSSRuleProcessor::VisitedLinksEnabled() +{ + return gSupportVisitedPseudo; +} + /* static */ void nsCSSRuleProcessor::InitSystemMetrics() { @@ -1240,7 +1246,8 @@ nsCSSRuleProcessor::GetWindowsThemeIdentifier() /* static */ EventStates -nsCSSRuleProcessor::GetContentState(Element* aElement, bool aUsingPrivateBrowsing) +nsCSSRuleProcessor::GetContentState(const Element* aElement, + bool aUsingPrivateBrowsing) { EventStates state = aElement->StyleState(); @@ -1260,7 +1267,8 @@ nsCSSRuleProcessor::GetContentState(Element* aElement, bool aUsingPrivateBrowsin /* static */ EventStates -nsCSSRuleProcessor::GetContentState(Element* aElement, const TreeMatchContext& aTreeMatchContext) +nsCSSRuleProcessor::GetContentState(const Element* aElement, + const TreeMatchContext& aTreeMatchContext) { return nsCSSRuleProcessor::GetContentState( aElement, @@ -1270,7 +1278,7 @@ nsCSSRuleProcessor::GetContentState(Element* aElement, const TreeMatchContext& a /* static */ EventStates -nsCSSRuleProcessor::GetContentState(Element* aElement) +nsCSSRuleProcessor::GetContentState(const Element* aElement) { nsILoadContext* loadContext = aElement->OwnerDoc()->GetLoadContext(); bool usingPrivateBrowsing = loadContext && loadContext->UsePrivateBrowsing(); @@ -1288,7 +1296,7 @@ nsCSSRuleProcessor::IsLink(const Element* aElement) /* static */ EventStates nsCSSRuleProcessor::GetContentStateForVisitedHandling( - Element* aElement, + const Element* aElement, nsRuleWalker::VisitedHandlingType aVisitedHandling, bool aIsRelevantLink) { diff --git a/layout/style/nsCSSRuleProcessor.h b/layout/style/nsCSSRuleProcessor.h index cc1c61c5176f..f54f16bcc2a8 100644 --- a/layout/style/nsCSSRuleProcessor.h +++ b/layout/style/nsCSSRuleProcessor.h @@ -81,6 +81,7 @@ public: public: nsresult ClearRuleCascades(); + static bool VisitedLinksEnabled(); static void Startup(); static void InitSystemMetrics(); static void Shutdown(); @@ -103,19 +104,19 @@ public: * slightly adjusted from IntrinsicState(). */ static mozilla::EventStates GetContentState( - mozilla::dom::Element* aElement, + const mozilla::dom::Element* aElement, bool aUsingPrivateBrowsing); static mozilla::EventStates GetContentState( - mozilla::dom::Element* aElement, + const mozilla::dom::Element* aElement, const TreeMatchContext& aTreeMatchContext); static mozilla::EventStates GetContentState( - mozilla::dom::Element* aElement); + const mozilla::dom::Element* aElement); /* * Helper to get the content state for :visited handling for an element */ static mozilla::EventStates GetContentStateForVisitedHandling( - mozilla::dom::Element* aElement, + const mozilla::dom::Element* aElement, nsRuleWalker::VisitedHandlingType aVisitedHandling, bool aIsRelevantLink); diff --git a/layout/style/nsCSSValue.cpp b/layout/style/nsCSSValue.cpp index 4445f6d7f465..935e6eb9b9ca 100644 --- a/layout/style/nsCSSValue.cpp +++ b/layout/style/nsCSSValue.cpp @@ -2892,31 +2892,35 @@ css::URLValueData::IsLocalRef() const bool css::URLValueData::HasRef() const { + bool result = false; + if (IsLocalRef()) { - return true; + result = true; + } else { + if (nsIURI* uri = GetURI()) { + nsAutoCString ref; + nsresult rv = uri->GetRef(ref); + if (NS_SUCCEEDED(rv) && !ref.IsEmpty()) { + result = true; + } + } } - nsIURI* uri = GetURI(); - if (!uri) { - return false; - } + mMightHaveRef = Some(result); - nsAutoCString ref; - nsresult rv = uri->GetRef(ref); - if (NS_SUCCEEDED(rv) && !ref.IsEmpty()) { - return true; - } - - return false; + return result; } bool css::URLValueData::MightHaveRef() const { if (mMightHaveRef.isNothing()) { - // ::MightHaveRef is O(N), use it only use it only when MightHaveRef is - // called. - mMightHaveRef.emplace(::MightHaveRef(mString)); + bool result = ::MightHaveRef(mString); + if (!ServoStyleSet::IsInServoTraversal()) { + // Can only cache the result if we're not on a style worker thread. + mMightHaveRef.emplace(result); + } + return result; } return mMightHaveRef.value(); diff --git a/layout/style/nsCSSValue.h b/layout/style/nsCSSValue.h index f5a93fcfcdba..be968d502e2b 100644 --- a/layout/style/nsCSSValue.h +++ b/layout/style/nsCSSValue.h @@ -1755,6 +1755,9 @@ private: public: bool operator==(const nsCSSValueTokenStream& aOther) const { + // This is not safe to call OMT, due to the URI/Principal Equals calls. + MOZ_ASSERT(NS_IsMainThread()); + bool eq; return mPropertyID == aOther.mPropertyID && mShorthandPropertyID == aOther.mShorthandPropertyID && diff --git a/layout/style/nsStyleContext.cpp b/layout/style/nsStyleContext.cpp index 7e7264d0cf96..6e75cac92833 100644 --- a/layout/style/nsStyleContext.cpp +++ b/layout/style/nsStyleContext.cpp @@ -172,6 +172,10 @@ nsStyleContext::CalcStyleDifference(nsStyleContext* aNewContext, DebugOnly structsFound = 0; if (IsGecko()) { + // CalcStyleDifference is always called on the main thread for Gecko + // style contexts. This assertion helps the heap write static analysis. + MOZ_ASSERT(NS_IsMainThread()); + // FIXME(heycam): We should just do the comparison in // nsStyleVariables::CalcDifference, returning NeutralChange if there are // any Variables differences. diff --git a/layout/xul/nsMenuBarListener.cpp b/layout/xul/nsMenuBarListener.cpp index e9211c15b95b..54005a7c2e6d 100644 --- a/layout/xul/nsMenuBarListener.cpp +++ b/layout/xul/nsMenuBarListener.cpp @@ -271,8 +271,7 @@ nsMenuBarListener::KeyPress(nsIDOMEvent* aKeyEvent) // the mozaccesskeynotfound event before handling accesskeys. WidgetKeyboardEvent* nativeKeyEvent = aKeyEvent->WidgetEventPtr()->AsKeyboardEvent(); - if (!nativeKeyEvent || - (nativeKeyEvent && nativeKeyEvent->mAccessKeyForwardedToChild)) { + if (!nativeKeyEvent) { return NS_OK; } @@ -301,6 +300,17 @@ nsMenuBarListener::KeyPress(nsIDOMEvent* aKeyEvent) // so, we'll know the menu got activated. nsMenuFrame* result = mMenuBarFrame->FindMenuWithShortcut(keyEvent); if (result) { + // If the keyboard event matches with a menu item's accesskey and + // will be sent to a remote process, it should be executed with + // reply event from the focused remote process. Note that if the + // menubar is active, the event is already marked as "stop cross + // process dispatching". So, in that case, this won't wait + // reply from the remote content. + if (nativeKeyEvent->WillBeSentToRemoteProcess()) { + nativeKeyEvent->StopImmediatePropagation(); + nativeKeyEvent->MarkAsWaitingReplyFromRemoteProcess(); + return NS_OK; + } mMenuBarFrame->SetActiveByKeyboard(); mMenuBarFrame->SetActive(true); result->OpenMenu(true); @@ -317,6 +327,17 @@ nsMenuBarListener::KeyPress(nsIDOMEvent* aKeyEvent) // Also need to handle F10 specially on Non-Mac platform. else if (nativeKeyEvent->mMessage == eKeyPress && keyCode == NS_VK_F10) { if ((GetModifiersForAccessKey(keyEvent) & ~MODIFIER_CONTROL) == 0) { + // If the keyboard event should activate the menubar and will be + // sent to a remote process, it should be executed with reply + // event from the focused remote process. Note that if the menubar + // is active, the event is already marked as "stop cross + // process dispatching". So, in that case, this won't wait + // reply from the remote content. + if (nativeKeyEvent->WillBeSentToRemoteProcess()) { + nativeKeyEvent->StopImmediatePropagation(); + nativeKeyEvent->MarkAsWaitingReplyFromRemoteProcess(); + return NS_OK; + } // The F10 key just went down by itself or with ctrl pressed. // In Windows, both of these activate the menu bar. mMenuBarFrame->SetActiveByKeyboard(); diff --git a/mobile/android/base/java/org/mozilla/gecko/db/BrowserProvider.java b/mobile/android/base/java/org/mozilla/gecko/db/BrowserProvider.java index 37ef3d99100c..f8a775085ea4 100644 --- a/mobile/android/base/java/org/mozilla/gecko/db/BrowserProvider.java +++ b/mobile/android/base/java/org/mozilla/gecko/db/BrowserProvider.java @@ -2245,6 +2245,9 @@ public class BrowserProvider extends SharedBrowserDatabaseProvider { values.putNull(Bookmarks.TAGS); values.putNull(Bookmarks.FAVICON_ID); + // Bump the lastModified timestamp for sync to know to update bookmark records. + values.put(Bookmarks.DATE_MODIFIED, System.currentTimeMillis()); + // Leave space for variables in values. final int maxVariableNumber = DBUtils.SQLITE_MAX_VARIABLE_NUMBER - values.size(); diff --git a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/db/BrowserProviderBookmarksTest.java b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/db/BrowserProviderBookmarksTest.java index e08699626068..8bb5afec146b 100644 --- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/db/BrowserProviderBookmarksTest.java +++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/db/BrowserProviderBookmarksTest.java @@ -36,6 +36,7 @@ import static org.junit.Assert.assertTrue; public class BrowserProviderBookmarksTest { private static final long INVALID_ID = -1; + private static final long INVALID_TIMESTAMP = -1; private ContentProviderClient bookmarksClient; private Uri bookmarksTestUri; @@ -141,6 +142,50 @@ public class BrowserProviderBookmarksTest { assertEquals(4, changed); } + @Test + public void testBookmarkFolderLastModifiedOnDeletion() throws RemoteException { + final long rootId = getBookmarkIdFromGuid(BrowserContract.Bookmarks.MOBILE_FOLDER_GUID); + + // root + // -> sibling + // -> parent -- timestamp must change + // ---> child-1 + // ---> child-2 -- delete this one + + final Uri parentUri = insertBookmark("parent", null, "parent", rootId, + BrowserContract.Bookmarks.TYPE_FOLDER); + final Uri siblingUri = insertBookmark("sibling", null, "sibling", rootId, + BrowserContract.Bookmarks.TYPE_FOLDER); + + final long parentId = Long.parseLong(parentUri.getLastPathSegment()); + final Uri child1Uri = insertBookmark("child-1", null, "child-1", parentId, + BrowserContract.Bookmarks.TYPE_FOLDER); + final Uri child2Uri = insertBookmark("child-2", null, "child-2", parentId, + BrowserContract.Bookmarks.TYPE_FOLDER); + + final long parentLastModifiedBeforeDeletion = getLastModified(parentUri); + final long siblingLastModifiedBeforeDeletion = getLastModified(siblingUri); + + final long child1LastModifiedBeforeDeletion = getLastModified(child1Uri); + final long child2LastModifiedBeforeDeletion = getLastModified(child2Uri); + + bookmarksClient.delete(child2Uri, null, null); + + final long parentLastModifiedAfterDeletion = getLastModified(parentUri); + final long siblingLastModifiedAfterDeletion = getLastModified(siblingUri); + + final long child1LastModifiedAfterDeletion = getLastModified(child1Uri); + final long child2LastModifiedAfterDeletion = getLastModified(withDeleted(child2Uri)); + + // Check last modified timestamp of parent and child-2 is increased. + assertTrue(parentLastModifiedAfterDeletion > parentLastModifiedBeforeDeletion); + assertTrue(child2LastModifiedAfterDeletion > child2LastModifiedBeforeDeletion); + + // Check last modified timestamp of sibling and child-1 is not changed. + assertTrue(siblingLastModifiedBeforeDeletion == siblingLastModifiedAfterDeletion); + assertTrue(child1LastModifiedBeforeDeletion == child1LastModifiedAfterDeletion); + } + @Test public void testDeleteBookmarkFolder() throws RemoteException { final long rootId = getBookmarkIdFromGuid(BrowserContract.Bookmarks.MOBILE_FOLDER_GUID); @@ -240,6 +285,26 @@ public class BrowserProviderBookmarksTest { return baseUri.buildUpon().appendQueryParameter(BrowserContract.PARAM_SHOW_DELETED, "true").build(); } + private long getLastModified(final Uri uri) throws RemoteException { + final Cursor cursor = bookmarksClient.query(uri, + new String[] { BrowserContract.Bookmarks.DATE_MODIFIED }, + null, + null, + null); + assertNotNull(cursor); + + long lastModified = INVALID_TIMESTAMP; + try { + assertTrue(cursor.moveToFirst()); + assertEquals(1, cursor.getCount()); + lastModified = cursor.getLong(cursor.getColumnIndexOrThrow(BrowserContract.Bookmarks.DATE_MODIFIED)); + } finally { + cursor.close(); + } + assertNotEquals(lastModified, INVALID_TIMESTAMP); + return lastModified; + } + private long getBookmarkIdFromGuid(String guid) throws RemoteException { Cursor cursor = bookmarksClient.query(BrowserContract.Bookmarks.CONTENT_URI, new String[] { BrowserContract.Bookmarks._ID }, diff --git a/servo/Cargo.lock b/servo/Cargo.lock index b5f6aaa139eb..16fbec91af0a 100644 --- a/servo/Cargo.lock +++ b/servo/Cargo.lock @@ -3465,7 +3465,7 @@ dependencies = [ [[package]] name = "webrender" version = "0.48.0" -source = "git+https://github.com/servo/webrender#b83c200c657f6b6fb17d09f329ba77803420b46a" +source = "git+https://github.com/servo/webrender#8fd634882111415a65da67e947f26eb170234f2f" dependencies = [ "app_units 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "bincode 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -3494,7 +3494,7 @@ dependencies = [ [[package]] name = "webrender_api" version = "0.48.0" -source = "git+https://github.com/servo/webrender#b83c200c657f6b6fb17d09f329ba77803420b46a" +source = "git+https://github.com/servo/webrender#8fd634882111415a65da67e947f26eb170234f2f" dependencies = [ "app_units 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "bincode 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/servo/components/gfx/display_list/mod.rs b/servo/components/gfx/display_list/mod.rs index 69074e89a128..22d37d05cc31 100644 --- a/servo/components/gfx/display_list/mod.rs +++ b/servo/components/gfx/display_list/mod.rs @@ -1146,7 +1146,8 @@ pub struct LineDisplayItem { pub color: ColorF, /// The line segment style. - pub style: border_style::T + #[ignore_heap_size_of = "enum type in webrender"] + pub style: webrender_api::LineStyle, } /// Paints a box shadow per CSS-BACKGROUNDS. diff --git a/servo/components/layout/display_list_builder.rs b/servo/components/layout/display_list_builder.rs index d61d3bfbf6e7..f585d32eb8e6 100644 --- a/servo/components/layout/display_list_builder.rs +++ b/servo/components/layout/display_list_builder.rs @@ -71,7 +71,7 @@ use style_traits::CSSPixel; use style_traits::cursor::Cursor; use table_cell::CollapsedBordersForCell; use webrender_api::{ClipId, ColorF, ComplexClipRegion, GradientStop, LocalClip, RepeatMode}; -use webrender_api::{ScrollPolicy, TransformStyle}; +use webrender_api::{LineStyle, ScrollPolicy, TransformStyle}; use webrender_helpers::{ToBorderRadius, ToMixBlendMode, ToRectF, ToTransformStyle}; trait ResolvePercentage { @@ -1651,7 +1651,7 @@ impl FragmentDisplayListBuilding for Fragment { state.add_display_item(DisplayItem::Line(box LineDisplayItem { base: base, color: ColorF::rgb(0, 200, 0), - style: border_style::T::dashed, + style: LineStyle::Dashed, })); } @@ -2217,9 +2217,10 @@ impl FragmentDisplayListBuilding for Fragment { self.style.get_cursor(Cursor::Default), DisplayListSection::Content); - state.add_display_item(DisplayItem::SolidColor(box SolidColorDisplayItem { + state.add_display_item(DisplayItem::Line(box LineDisplayItem { base: base, color: color.to_gfx_color(), + style: LineStyle::Solid, })); } diff --git a/servo/components/layout/webrender_helpers.rs b/servo/components/layout/webrender_helpers.rs index 29236533f806..b2521ff159d7 100644 --- a/servo/components/layout/webrender_helpers.rs +++ b/servo/components/layout/webrender_helpers.rs @@ -432,8 +432,16 @@ impl WebRenderDisplayItemConverter for DisplayItem { rect.size, webrender_api::LayoutSize::zero()); } - DisplayItem::Line(..) => { - println!("TODO DisplayItem::Line"); + DisplayItem::Line(ref item) => { + let box_bounds = item.base.bounds.to_rectf(); + builder.push_line(Some(item.base.local_clip), + box_bounds.origin.y + box_bounds.size.height, + box_bounds.origin.x, + box_bounds.origin.x + box_bounds.size.width, + webrender_api::LineOrientation::Horizontal, + box_bounds.size.height, + item.color, + item.style); } DisplayItem::BoxShadow(ref item) => { let rect = item.base.bounds.to_rectf(); diff --git a/servo/components/layout_thread/lib.rs b/servo/components/layout_thread/lib.rs index 2f0fa57e5a30..576588abda88 100644 --- a/servo/components/layout_thread/lib.rs +++ b/servo/components/layout_thread/lib.rs @@ -594,6 +594,7 @@ impl LayoutThread { stylist: &self.stylist, options: StyleSystemOptions::default(), guards: guards, + visited_styles_enabled: false, running_animations: self.running_animations.clone(), expired_animations: self.expired_animations.clone(), local_context_creation_data: Mutex::new(thread_local_style_context_creation_data), diff --git a/servo/components/script/dom/htmlscriptelement.rs b/servo/components/script/dom/htmlscriptelement.rs index 0a5e9b74a0d3..ea0cc4c25e8e 100644 --- a/servo/components/script/dom/htmlscriptelement.rs +++ b/servo/components/script/dom/htmlscriptelement.rs @@ -342,9 +342,11 @@ impl HTMLScriptElement { return; } - // TODO(#4577): Step 11: CSP. + // TODO: Step 11: nomodule content attribute - // Step 12. + // TODO(#4577): Step 12: CSP. + + // Step 13. let for_attribute = element.get_attribute(&ns!(), &local_name!("for")); let event_attribute = element.get_attribute(&ns!(), &local_name!("event")); match (for_attribute.r(), event_attribute.r()) { @@ -364,19 +366,19 @@ impl HTMLScriptElement { (_, _) => (), } - // Step 13. + // Step 14. let encoding = element.get_attribute(&ns!(), &local_name!("charset")) .and_then(|charset| encoding_from_whatwg_label(&charset.value())) .unwrap_or_else(|| doc.encoding()); - // Step 14. + // Step 15. let cors_setting = cors_setting_for_element(element); - // TODO: Step 15: Module script credentials mode. + // TODO: Step 16: Module script credentials mode. - // TODO: Step 16: Nonce. + // TODO: Step 17: Nonce. - // Step 17: Integrity metadata. + // Step 18: Integrity metadata. let im_attribute = element.get_attribute(&ns!(), &local_name!("integrity")); let integrity_val = im_attribute.r().map(|a| a.value()); let integrity_metadata = match integrity_val { @@ -384,26 +386,26 @@ impl HTMLScriptElement { None => "", }; - // TODO: Step 18: parser state. + // TODO: Step 19: parser state. - // TODO: Step 19: environment settings object. + // TODO: Step 20: environment settings object. let base_url = doc.base_url(); if let Some(src) = element.get_attribute(&ns!(), &local_name!("src")) { - // Step 20. + // Step 21. - // Step 20.1. + // Step 21.1. let src = src.value(); - // Step 20.2. + // Step 21.2. if src.is_empty() { self.queue_error_event(); return; } - // Step 20.3: The "from an external file"" flag is stored in ClassicScript. + // Step 21.3: The "from an external file"" flag is stored in ClassicScript. - // Step 20.4-20.5. + // Step 21.4-21.5. let url = match base_url.join(&src) { Ok(url) => url, Err(_) => { @@ -413,25 +415,25 @@ impl HTMLScriptElement { }, }; - // Preparation for step 22. + // Preparation for step 23. let kind = if element.has_attribute(&local_name!("defer")) && was_parser_inserted && !async { - // Step 22.a: classic, has src, has defer, was parser-inserted, is not async. + // Step 23.a: classic, has src, has defer, was parser-inserted, is not async. ExternalScriptKind::Deferred } else if was_parser_inserted && !async { - // Step 22.b: classic, has src, was parser-inserted, is not async. + // Step 23.c: classic, has src, was parser-inserted, is not async. ExternalScriptKind::ParsingBlocking } else if !async && !self.non_blocking.get() { - // Step 22.c: classic, has src, is not async, is not non-blocking. + // Step 23.d: classic, has src, is not async, is not non-blocking. ExternalScriptKind::AsapInOrder } else { - // Step 22.d: classic, has src. + // Step 23.f: classic, has src. ExternalScriptKind::Asap }; - // Step 20.6. + // Step 21.6. fetch_a_classic_script(self, kind, url, cors_setting, integrity_metadata.to_owned(), encoding); - // Step 22. + // Step 23. match kind { ExternalScriptKind::Deferred => doc.add_deferred_script(self), ExternalScriptKind::ParsingBlocking => doc.set_pending_parsing_blocking_script(self, None), @@ -439,18 +441,18 @@ impl HTMLScriptElement { ExternalScriptKind::Asap => doc.add_asap_script(self), } } else { - // Step 21. + // Step 22. assert!(!text.is_empty()); let result = Ok(ClassicScript::internal(text, base_url)); - // Step 22. + // Step 23. if was_parser_inserted && doc.get_current_parser().map_or(false, |parser| parser.script_nesting_level() <= 1) && doc.get_script_blocking_stylesheets_count() > 0 { - // Step 22.e: classic, has no src, was parser-inserted, is blocked on stylesheet. + // Step 23.h: classic, has no src, was parser-inserted, is blocked on stylesheet. doc.set_pending_parsing_blocking_script(self, Some(result)); } else { - // Step 22.f: otherwise. + // Step 23.i: otherwise. self.execute(result); } } diff --git a/servo/components/style/context.rs b/servo/components/style/context.rs index e9733cb0985d..5abad6c5e03a 100644 --- a/servo/components/style/context.rs +++ b/servo/components/style/context.rs @@ -117,6 +117,12 @@ pub struct SharedStyleContext<'a> { /// The CSS selector stylist. pub stylist: &'a Stylist, + /// Whether visited styles are enabled. + /// + /// They may be disabled when Gecko's pref layout.css.visited_links_enabled + /// is false, or when in private browsing mode. + pub visited_styles_enabled: bool, + /// Configuration options. pub options: StyleSystemOptions, diff --git a/servo/components/style/dom.rs b/servo/components/style/dom.rs index 12eff9c7c125..a7f68a965415 100644 --- a/servo/components/style/dom.rs +++ b/servo/components/style/dom.rs @@ -550,6 +550,11 @@ pub trait TElement : Eq + PartialEq + Debug + Hash + Sized + Copy + Clone + unsafe fn unset_animation_only_dirty_descendants(&self) { } + /// Returns true if this element is a visited link. + /// + /// Servo doesn't support visited styles yet. + fn is_visited_link(&self) -> bool { false } + /// Returns true if this element is native anonymous (only Gecko has native /// anonymous content). fn is_native_anonymous(&self) -> bool { false } diff --git a/servo/components/style/gecko/data.rs b/servo/components/style/gecko/data.rs index 08cb11a4fe35..f845ec421c2f 100644 --- a/servo/components/style/gecko/data.rs +++ b/servo/components/style/gecko/data.rs @@ -187,6 +187,13 @@ impl PerDocumentStyleDataImpl { ); } + /// Returns whether private browsing is enabled. + pub fn is_private_browsing_enabled(&self) -> bool { + let doc = + self.stylist.device().pres_context().mDocument.raw::(); + unsafe { bindings::Gecko_IsPrivateBrowsingEnabled(doc) } + } + /// Get the default computed values for this document. pub fn default_computed_values(&self) -> &Arc { self.stylist.device().default_computed_values_arc() diff --git a/servo/components/style/gecko/generated/bindings.rs b/servo/components/style/gecko/generated/bindings.rs index 3226c67b1d03..fb3d1a35c898 100644 --- a/servo/components/style/gecko/generated/bindings.rs +++ b/servo/components/style/gecko/generated/bindings.rs @@ -746,6 +746,12 @@ extern "C" { RawGeckoElementBorrowed) -> RawServoDeclarationBlockStrongBorrowedOrNull; } +extern "C" { + pub fn Gecko_IsPrivateBrowsingEnabled(aDoc: *const nsIDocument) -> bool; +} +extern "C" { + pub fn Gecko_AreVisitedLinksEnabled() -> bool; +} extern "C" { pub fn Gecko_GetAnimationRule(aElementOrPseudo: RawGeckoElementBorrowed, aCascadeLevel: diff --git a/servo/components/style/gecko/wrapper.rs b/servo/components/style/gecko/wrapper.rs index 59f128086ed1..c8028acaee20 100644 --- a/servo/components/style/gecko/wrapper.rs +++ b/servo/components/style/gecko/wrapper.rs @@ -1011,6 +1011,11 @@ impl<'le> TElement for GeckoElement<'le> { self.unset_flags(ELEMENT_HAS_ANIMATION_ONLY_DIRTY_DESCENDANTS_FOR_SERVO as u32) } + fn is_visited_link(&self) -> bool { + use element_state::IN_VISITED_STATE; + self.get_state().intersects(IN_VISITED_STATE) + } + fn is_native_anonymous(&self) -> bool { self.flags() & (NODE_IS_NATIVE_ANONYMOUS as u32) != 0 } diff --git a/servo/components/style/properties/computed_value_flags.rs b/servo/components/style/properties/computed_value_flags.rs index 2dc3d91e1f68..3363fd768697 100644 --- a/servo/components/style/properties/computed_value_flags.rs +++ b/servo/components/style/properties/computed_value_flags.rs @@ -33,5 +33,9 @@ bitflags! { /// /// This is used from Gecko's layout engine. const IS_TEXT_COMBINED = 1 << 2, + + /// A flag used to mark styles under a relevant link that is also + /// visited. + const IS_RELEVANT_LINK_VISITED = 1 << 3, } } diff --git a/servo/components/style/properties/properties.mako.rs b/servo/components/style/properties/properties.mako.rs index 6e1bf5e74776..b2445f53dfb0 100644 --- a/servo/components/style/properties/properties.mako.rs +++ b/servo/components/style/properties/properties.mako.rs @@ -2650,6 +2650,11 @@ impl<'a> StyleBuilder<'a> { ) } + /// Returns whether we have a visited style. + pub fn has_visited_style(&self) -> bool { + self.visited_style.is_some() + } + /// Returns the style we're inheriting from. pub fn inherited_style(&self) -> &'a ComputedValues { self.inherited_style @@ -2805,14 +2810,14 @@ bitflags! { pub flags CascadeFlags: u8 { /// Whether to inherit all styles from the parent. If this flag is not /// present, non-inherited styles are reset to their initial values. - const INHERIT_ALL = 0x01, + const INHERIT_ALL = 1, /// Whether to skip any display style fixup for root element, flex/grid /// item, and ruby descendants. - const SKIP_ROOT_AND_ITEM_BASED_DISPLAY_FIXUP = 0x02, + const SKIP_ROOT_AND_ITEM_BASED_DISPLAY_FIXUP = 1 << 1, /// Whether to only cascade properties that are visited dependent. - const VISITED_DEPENDENT_ONLY = 0x04, + const VISITED_DEPENDENT_ONLY = 1 << 2, /// Whether the given element we're styling is the document element, /// that is, matches :root. @@ -2822,15 +2827,23 @@ bitflags! { /// /// This affects some style adjustments, like blockification, and means /// that it may affect global state, like the Device's root font-size. - const IS_ROOT_ELEMENT = 0x08, + const IS_ROOT_ELEMENT = 1 << 3, /// Whether to convert display:contents into display:inline. This /// is used by Gecko to prevent display:contents on generated /// content. - const PROHIBIT_DISPLAY_CONTENTS = 0x10, + const PROHIBIT_DISPLAY_CONTENTS = 1 << 4, /// Whether we're styling the ::-moz-fieldset-content anonymous box. - const IS_FIELDSET_CONTENT = 0x20, + const IS_FIELDSET_CONTENT = 1 << 5, + + /// Whether we're computing the style of a link, either visited or + /// unvisited. + const IS_LINK = 1 << 6, + + /// Whether we're computing the style of a link element that happens to + /// be visited. + const IS_VISITED_LINK = 1 << 7, } } diff --git a/servo/components/style/style_adjuster.rs b/servo/components/style/style_adjuster.rs index b7eeb5602170..8222634317e3 100644 --- a/servo/components/style/style_adjuster.rs +++ b/servo/components/style/style_adjuster.rs @@ -426,14 +426,44 @@ impl<'a, 'b: 'a> StyleAdjuster<'a, 'b> { } } + /// Computes the RELEVANT_LINK_VISITED flag based on the parent style and on + /// whether we're a relevant link. + /// + /// NOTE(emilio): We don't do this for text styles, which is... dubious, but + /// Gecko doesn't seem to do it either. It's extremely easy to do if needed + /// though. + /// + /// FIXME(emilio): This isn't technically a style adjustment thingie, could + /// it move somewhere else? + fn adjust_for_visited(&mut self, flags: CascadeFlags) { + use properties::{IS_LINK, IS_VISITED_LINK}; + use properties::computed_value_flags::IS_RELEVANT_LINK_VISITED; + + if !self.style.has_visited_style() { + return; + } + + let relevant_link_visited = if flags.contains(IS_LINK) { + flags.contains(IS_VISITED_LINK) + } else { + self.style.inherited_style().flags.contains(IS_RELEVANT_LINK_VISITED) + }; + + if relevant_link_visited { + self.style.flags.insert(IS_RELEVANT_LINK_VISITED); + } + } + /// Adjusts the style to account for various fixups that don't fit naturally /// into the cascade. /// /// When comparing to Gecko, this is similar to the work done by - /// `nsStyleContext::ApplyStyleFixups`. + /// `nsStyleContext::ApplyStyleFixups`, plus some parts of + /// `nsStyleSet::GetContext`. pub fn adjust(&mut self, layout_parent_style: &ComputedValues, flags: CascadeFlags) { + self.adjust_for_visited(flags); #[cfg(feature = "gecko")] { self.adjust_for_prohibited_display_contents(flags); diff --git a/servo/components/style/style_resolver.rs b/servo/components/style/style_resolver.rs index 71598ab14d2a..204f60219074 100644 --- a/servo/components/style/style_resolver.rs +++ b/servo/components/style/style_resolver.rs @@ -12,7 +12,8 @@ use dom::TElement; use log::LogLevel::Trace; use matching::{CascadeVisitedMode, MatchMethods}; use properties::{AnimationRules, CascadeFlags, ComputedValues}; -use properties::{IS_ROOT_ELEMENT, PROHIBIT_DISPLAY_CONTENTS, SKIP_ROOT_AND_ITEM_BASED_DISPLAY_FIXUP}; +use properties::{IS_LINK, IS_ROOT_ELEMENT, IS_VISITED_LINK}; +use properties::{PROHIBIT_DISPLAY_CONTENTS, SKIP_ROOT_AND_ITEM_BASED_DISPLAY_FIXUP}; use properties::{VISITED_DEPENDENT_ONLY, cascade}; use rule_tree::StrongRuleNode; use selector_parser::{PseudoElement, SelectorImpl}; @@ -473,6 +474,15 @@ where if self.element.skip_root_and_item_based_display_fixup() { cascade_flags.insert(SKIP_ROOT_AND_ITEM_BASED_DISPLAY_FIXUP); } + + if pseudo.is_none() && self.element.is_link() { + cascade_flags.insert(IS_LINK); + if self.element.is_visited_link() && + self.context.shared.visited_styles_enabled { + cascade_flags.insert(IS_VISITED_LINK); + } + } + if cascade_visited.visited_dependent_only() { // If this element is a link, we want its visited style to inherit // from the regular style of its parent, because only the diff --git a/servo/ports/geckolib/glue.rs b/servo/ports/geckolib/glue.rs index c9c104bdd2c0..028f8ee8d3fc 100644 --- a/servo/ports/geckolib/glue.rs +++ b/servo/ports/geckolib/glue.rs @@ -182,8 +182,13 @@ fn create_shared_context<'a>(global_style_data: &GlobalStyleData, traversal_flags: TraversalFlags, snapshot_map: &'a ServoElementSnapshotTable) -> SharedStyleContext<'a> { + let visited_styles_enabled = + unsafe { bindings::Gecko_AreVisitedLinksEnabled() } && + !per_doc_data.is_private_browsing_enabled(); + SharedStyleContext { stylist: &per_doc_data.stylist, + visited_styles_enabled: visited_styles_enabled, options: global_style_data.options.clone(), guards: StylesheetGuards::same(guard), timer: Timer::new(), @@ -1736,6 +1741,9 @@ pub extern "C" fn Servo_ComputedValues_GetStyleBits(values: ServoStyleContextBor use style::properties::computed_value_flags::*; let flags = values.flags; let mut result = 0; + if flags.contains(IS_RELEVANT_LINK_VISITED) { + result |= structs::NS_STYLE_RELEVANT_LINK_VISITED as u64; + } if flags.contains(HAS_TEXT_DECORATION_LINES) { result |= structs::NS_STYLE_HAS_TEXT_DECORATION_LINES as u64; } diff --git a/taskcluster/scripts/builder/hazard-analysis.sh b/taskcluster/scripts/builder/hazard-analysis.sh index 307aa5d4f44e..e1941e2aff71 100755 --- a/taskcluster/scripts/builder/hazard-analysis.sh +++ b/taskcluster/scripts/builder/hazard-analysis.sh @@ -160,7 +160,7 @@ function check_hazards () { exit 1 fi - NUM_ALLOWED_WRITE_HAZARDS=4 + NUM_ALLOWED_WRITE_HAZARDS=3 if [ $NUM_WRITE_HAZARDS -gt $NUM_ALLOWED_WRITE_HAZARDS ]; then echo "TEST-UNEXPECTED-FAIL $NUM_WRITE_HAZARDS heap write hazards detected out of $NUM_ALLOWED_WRITE_HAZARDS allowed" >&2 echo "TinderboxPrint: documentation
heap write hazard analysis failures, visit \"Inspect Task\" link for hazard details" diff --git a/widget/BasicEvents.h b/widget/BasicEvents.h index 95c18ee1f574..b5322c70097a 100644 --- a/widget/BasicEvents.h +++ b/widget/BasicEvents.h @@ -240,18 +240,34 @@ public: } /** * Mark the event as waiting reply from remote process. + * If the caller needs to win other keyboard event handlers in chrome, + * the caller should call StopPropagation() too. + * Otherwise, if the caller just needs to know if the event is consumed by + * either content or chrome, it should just call this because the event + * may be reserved by chrome and it needs to be dispatched into the DOM + * tree in chrome for checking if it's reserved before being sent to any + * remote processes. */ inline void MarkAsWaitingReplyFromRemoteProcess() { MOZ_ASSERT(!mPostedToRemoteProcess); - // When this is called, it means that event handlers in this process need - // a reply from content in a remote process. So, callers should stop - // propagation in this process first. - NS_ASSERTION(PropagationStopped(), - "Why didn't you stop propagation in this process?"); mNoRemoteProcessDispatch = false; mWantReplyFromContentProcess = true; } + /** + * Reset "waiting reply from remote process" state. This is useful when + * you dispatch a copy of an event coming from different process. + */ + inline void ResetWaitingReplyFromRemoteProcessState() + { + if (IsWaitingReplyFromRemoteProcess()) { + // FYI: mWantReplyFromContentProcess is also used for indicating + // "handled in remote process" state. Therefore, only when + // IsWaitingReplyFromRemoteProcess() returns true, this should + // reset the flag. + mWantReplyFromContentProcess = false; + } + } /** * Return true if the event handler should wait reply event. I.e., if this * returns true, any event handler should do nothing with the event. @@ -301,6 +317,12 @@ public: { MOZ_ASSERT(!IsCrossProcessForwardingStopped()); mPostedToRemoteProcess = false; + // Ignore propagation state in the different process if it's marked as + // "waiting reply from remote process" because the process needs to + // stop propagation in the process until receiving a reply event. + if (IsWaitingReplyFromRemoteProcess()) { + mPropagationStopped = mImmediatePropagationStopped = false; + } } /** * Return true if the event has been posted to a remote process. @@ -629,6 +651,14 @@ public: { mFlags.MarkAsWaitingReplyFromRemoteProcess(); } + /** + * Reset "waiting reply from remote process" state. This is useful when + * you dispatch a copy of an event coming from different process. + */ + inline void ResetWaitingReplyFromRemoteProcessState() + { + mFlags.ResetWaitingReplyFromRemoteProcessState(); + } /** * Return true if the event handler should wait reply event. I.e., if this * returns true, any event handler should do nothing with the event. @@ -760,6 +790,11 @@ public: * Returns true if the event can be sent to remote process. */ bool CanBeSentToRemoteProcess() const; + /** + * Returns true if the original target is a remote process and the event + * will be posted to the remote process later. + */ + bool WillBeSentToRemoteProcess() const; /** * Returns true if the event is native event deliverer event for plugin and * it should be retarted to focused document. diff --git a/widget/EventForwards.h b/widget/EventForwards.h index fc9395ff3fa7..58644d68e383 100644 --- a/widget/EventForwards.h +++ b/widget/EventForwards.h @@ -150,6 +150,8 @@ class WidgetEventTime; class NativeEventData; // TextEvents.h +enum class AccessKeyType; + struct AlternativeCharCode; struct ShortcutKeyCandidate; diff --git a/widget/TextEvents.h b/widget/TextEvents.h index 5e1f815d7c4e..c1f5178fe025 100644 --- a/widget/TextEvents.h +++ b/widget/TextEvents.h @@ -59,6 +59,16 @@ namespace plugins { class PPluginInstanceChild; } // namespace plugins +enum class AccessKeyType +{ + // Handle access key for chrome. + eChrome, + // Handle access key for content. + eContent, + // Don't handle access key. + eNone +}; + /****************************************************************************** * mozilla::AlternativeCharCode * @@ -144,7 +154,6 @@ protected: , mCharCode(0) , mPseudoCharCode(0) , mLocation(eKeyLocationStandard) - , mAccessKeyForwardedToChild(false) , mUniqueId(0) #ifdef XP_MACOSX , mNativeModifierFlags(0) @@ -173,7 +182,6 @@ public: , mCharCode(0) , mPseudoCharCode(0) , mLocation(eKeyLocationStandard) - , mAccessKeyForwardedToChild(false) , mUniqueId(0) #ifdef XP_MACOSX , mNativeModifierFlags(0) @@ -269,11 +277,6 @@ public: uint32_t mPseudoCharCode; // One of eKeyLocation* uint32_t mLocation; - // True if accesskey handling was forwarded to the child via - // TabParent::HandleAccessKey. In this case, parent process menu access key - // handling should be delayed until it is determined that there exists no - // overriding access key in the content process. - bool mAccessKeyForwardedToChild; // Unique id associated with a keydown / keypress event. It's ok if this wraps // over long periods. uint32_t mUniqueId; @@ -431,6 +434,24 @@ public: */ void GetAccessKeyCandidates(nsTArray& aCandidates) const; + /** + * Check whether the modifiers match with chrome access key or + * content access key. + */ + bool ModifiersMatchWithAccessKey(AccessKeyType aType) const; + + /** + * Return active modifiers which may match with access key. + * For example, even if Alt is access key modifier, then, when Control, + * CapseLock and NumLock are active, this returns only MODIFIER_CONTROL. + */ + Modifiers ModifiersForAccessKeyMatching() const; + + /** + * Return access key modifiers. + */ + static Modifiers AccessKeyModifiers(AccessKeyType aType); + static void Shutdown(); /** @@ -481,7 +502,6 @@ public: mAlternativeCharCodes = aEvent.mAlternativeCharCodes; mIsRepeat = aEvent.mIsRepeat; mIsComposing = aEvent.mIsComposing; - mAccessKeyForwardedToChild = aEvent.mAccessKeyForwardedToChild; mKeyNameIndex = aEvent.mKeyNameIndex; mCodeNameIndex = aEvent.mCodeNameIndex; mKeyValue = aEvent.mKeyValue; @@ -565,6 +585,10 @@ private: "Invalid native key binding type"); } } + + static int32_t GenericAccessModifierKeyPref(); + static int32_t ChromeAccessModifierMaskPref(); + static int32_t ContentAccessModifierMaskPref(); }; /****************************************************************************** diff --git a/widget/WidgetEventImpl.cpp b/widget/WidgetEventImpl.cpp index c701f3a1dbc1..03d15f5e90e2 100644 --- a/widget/WidgetEventImpl.cpp +++ b/widget/WidgetEventImpl.cpp @@ -6,12 +6,14 @@ #include "gfxPrefs.h" #include "mozilla/BasicEvents.h" #include "mozilla/ContentEvents.h" +#include "mozilla/EventStateManager.h" #include "mozilla/InternalMutationEvent.h" #include "mozilla/MiscEvents.h" #include "mozilla/MouseEvents.h" #include "mozilla/Preferences.h" #include "mozilla/TextEvents.h" #include "mozilla/TouchEvents.h" +#include "nsIContent.h" #include "nsIDOMEventTarget.h" #include "nsPrintfCString.h" @@ -349,6 +351,24 @@ WidgetEvent::CanBeSentToRemoteProcess() const } } +bool +WidgetEvent::WillBeSentToRemoteProcess() const +{ + // This event won't be posted to remote process if it's already explicitly + // stopped. + if (IsCrossProcessForwardingStopped()) { + return false; + } + + // When mOriginalTarget is nullptr, this method shouldn't be used. + if (NS_WARN_IF(!mOriginalTarget)) { + return false; + } + + nsCOMPtr originalTarget = do_QueryInterface(mOriginalTarget); + return EventStateManager::IsRemoteTarget(originalTarget); +} + bool WidgetEvent::IsRetargetedNativeEventDelivererForPlugin() const { @@ -869,6 +889,128 @@ WidgetKeyboardEvent::GetAccessKeyCandidates(nsTArray& aCandidates) con } } +// mask values for ui.key.chromeAccess and ui.key.contentAccess +#define NS_MODIFIER_SHIFT 1 +#define NS_MODIFIER_CONTROL 2 +#define NS_MODIFIER_ALT 4 +#define NS_MODIFIER_META 8 +#define NS_MODIFIER_OS 16 + +static Modifiers PrefFlagsToModifiers(int32_t aPrefFlags) +{ + Modifiers result = 0; + if (aPrefFlags & NS_MODIFIER_SHIFT) { + result |= MODIFIER_SHIFT; + } + if (aPrefFlags & NS_MODIFIER_CONTROL) { + result |= MODIFIER_CONTROL; + } + if (aPrefFlags & NS_MODIFIER_ALT) { + result |= MODIFIER_ALT; + } + if (aPrefFlags & NS_MODIFIER_META) { + result |= MODIFIER_META; + } + if (aPrefFlags & NS_MODIFIER_OS) { + result |= MODIFIER_OS; + } + return result; +} + +bool +WidgetKeyboardEvent::ModifiersMatchWithAccessKey(AccessKeyType aType) const +{ + if (!ModifiersForAccessKeyMatching()) { + return false; + } + return ModifiersForAccessKeyMatching() == AccessKeyModifiers(aType); +} + +Modifiers +WidgetKeyboardEvent::ModifiersForAccessKeyMatching() const +{ + static const Modifiers kModifierMask = + MODIFIER_SHIFT | MODIFIER_CONTROL | + MODIFIER_ALT | MODIFIER_META | MODIFIER_OS; + return mModifiers & kModifierMask; +} + +/* static */ +Modifiers +WidgetKeyboardEvent::AccessKeyModifiers(AccessKeyType aType) +{ + switch (GenericAccessModifierKeyPref()) { + case -1: + break; // use the individual prefs + case NS_VK_SHIFT: + return MODIFIER_SHIFT; + case NS_VK_CONTROL: + return MODIFIER_CONTROL; + case NS_VK_ALT: + return MODIFIER_ALT; + case NS_VK_META: + return MODIFIER_META; + case NS_VK_WIN: + return MODIFIER_OS; + default: + return MODIFIER_NONE; + } + + switch (aType) { + case AccessKeyType::eChrome: + return PrefFlagsToModifiers(ChromeAccessModifierMaskPref()); + case AccessKeyType::eContent: + return PrefFlagsToModifiers(ContentAccessModifierMaskPref()); + default: + return MODIFIER_NONE; + } +} + +/* static */ +int32_t +WidgetKeyboardEvent::GenericAccessModifierKeyPref() +{ + static bool sInitialized = false; + static int32_t sValue = -1; + if (!sInitialized) { + nsresult rv = + Preferences::AddIntVarCache(&sValue, "ui.key.generalAccessKey", sValue); + sInitialized = NS_SUCCEEDED(rv); + MOZ_ASSERT(sInitialized); + } + return sValue; +} + +/* static */ +int32_t +WidgetKeyboardEvent::ChromeAccessModifierMaskPref() +{ + static bool sInitialized = false; + static int32_t sValue = 0; + if (!sInitialized) { + nsresult rv = + Preferences::AddIntVarCache(&sValue, "ui.key.chromeAccess", sValue); + sInitialized = NS_SUCCEEDED(rv); + MOZ_ASSERT(sInitialized); + } + return sValue; +} + +/* static */ +int32_t +WidgetKeyboardEvent::ContentAccessModifierMaskPref() +{ + static bool sInitialized = false; + static int32_t sValue = 0; + if (!sInitialized) { + nsresult rv = + Preferences::AddIntVarCache(&sValue, "ui.key.contentAccess", sValue); + sInitialized = NS_SUCCEEDED(rv); + MOZ_ASSERT(sInitialized); + } + return sValue; +} + /* static */ void WidgetKeyboardEvent::Shutdown() { diff --git a/widget/cocoa/TextInputHandler.mm b/widget/cocoa/TextInputHandler.mm index 62a088dada34..e53e64ea9afd 100644 --- a/widget/cocoa/TextInputHandler.mm +++ b/widget/cocoa/TextInputHandler.mm @@ -2815,7 +2815,12 @@ IMEInputHandler::WillDispatchKeyboardEvent( (!insertString || insertString->IsEmpty())) { // Inform the child process that this is an event that we want a reply // from. - aKeyboardEvent.mFlags.mWantReplyFromContentProcess = true; + // XXX This should be called only when the target is a remote process. + // However, it's difficult to check it under widget/. + // So, let's do this here for now, then, + // EventStateManager::PreHandleEvent() will reset the flags if + // the event target isn't in remote process. + aKeyboardEvent.MarkAsWaitingReplyFromRemoteProcess(); } if (KeyboardLayoutOverrideRef().mOverrideEnabled) { TISInputSourceWrapper tis; diff --git a/widget/nsGUIEventIPC.h b/widget/nsGUIEventIPC.h index df254be9681e..7ca55b7048d4 100644 --- a/widget/nsGUIEventIPC.h +++ b/widget/nsGUIEventIPC.h @@ -449,7 +449,6 @@ struct ParamTraits WriteParam(aMsg, aParam.mPseudoCharCode); WriteParam(aMsg, aParam.mAlternativeCharCodes); WriteParam(aMsg, aParam.mIsRepeat); - WriteParam(aMsg, aParam.mAccessKeyForwardedToChild); WriteParam(aMsg, aParam.mLocation); WriteParam(aMsg, aParam.mUniqueId); WriteParam(aMsg, aParam.mIsSynthesizedByTIP); @@ -487,7 +486,6 @@ struct ParamTraits ReadParam(aMsg, aIter, &aResult->mPseudoCharCode) && ReadParam(aMsg, aIter, &aResult->mAlternativeCharCodes) && ReadParam(aMsg, aIter, &aResult->mIsRepeat) && - ReadParam(aMsg, aIter, &aResult->mAccessKeyForwardedToChild) && ReadParam(aMsg, aIter, &aResult->mLocation) && ReadParam(aMsg, aIter, &aResult->mUniqueId) && ReadParam(aMsg, aIter, &aResult->mIsSynthesizedByTIP) &&