diff --git a/.eslintignore b/.eslintignore index dd7b54f13ba5..02e51ca7655a 100644 --- a/.eslintignore +++ b/.eslintignore @@ -11,15 +11,6 @@ # Exclude expected objdirs. obj*/** -# We ignore all these directories by default, until we get them enabled. -# If you are enabling a directory, please add directory specific exclusions -# below. -layout/** -netwerk/cookie/test/browser/** -netwerk/test/browser/** -netwerk/test/mochitests/** -netwerk/test/unit*/** - # We currently have no js files in these directories, so we ignore them by # default to aid ESLint's performance. build/** diff --git a/.eslintrc.js b/.eslintrc.js index 2da37d8ab09c..509e7d9b5125 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -193,5 +193,149 @@ module.exports = { "space-before-function-paren": "off", "space-infix-ops": "off", } + }, { + "files": [ + "netwerk/cookie/test/browser/**", + "netwerk/test/browser/**", + "netwerk/test/mochitests/**", + "netwerk/test/unit*/**", + ], + "rules": { + "object-shorthand": "off", + "mozilla/consistent-if-bracing": "off", + "mozilla/reject-importGlobalProperties": "off", + "mozilla/no-arbitrary-setTimeout": "off", + "mozilla/no-define-cc-etc": "off", + "mozilla/no-useless-parameters": "off", + "mozilla/no-useless-run-test": "off", + "mozilla/use-chromeutils-generateqi": "off", + "mozilla/use-chromeutils-import": "off", + "mozilla/use-default-preference-values": "off", + "mozilla/use-services": "off", + "consistent-return": "off", + "no-array-constructor": "off", + "no-extra-boolean-cast": "off", + "no-eval": "off", + "no-else-return": "off", + "no-global-assign": "off", + "no-lonely-if": "off", + "no-nested-ternary": "off", + "no-new-wrappers": "off", + "no-redeclare": "off", + "no-return-await": "off", + "no-sequences": "off", + "no-shadow": "off", + "no-throw-literal": "off", + "no-undef": "off", + "no-unreachable": "off", + "no-unused-vars": "off", + "no-useless-return": "off", + + // Not enabling the rules below for now pending prettier roll-out. + "arrow-spacing": "off", + "block-spacing": "off", + "brace-style": "off", + "comma-dangle": "off", + "comma-spacing": "off", + "comma-style": "off", + "eol-last": "off", + "func-call-spacing": "off", + "generator-star-spacing": "off", + "key-spacing": "off", + "keyword-spacing": "off", + "no-extra-semi": "off", + "no-tabs": "off", + "no-mixed-spaces-and-tabs": "off", + "no-multi-spaces": "off", + "no-trailing-spaces": "off", + "no-whitespace-before-property": "off", + "padded-blocks": "off", + "quotes": "off", + "rest-spread-spacing": "off", + "semi": "off", + "space-before-blocks": "off", + "space-before-function-paren": "off", + "space-infix-ops": "off", + "space-unary-ops": "off", + "spaced-comment": "off", + } + }, { + "files": [ + "layout/**", + ], + "rules": { + "object-shorthand": "off", + "mozilla/avoid-removeChild": "off", + "mozilla/consistent-if-bracing": "off", + "mozilla/reject-importGlobalProperties": "off", + "mozilla/no-arbitrary-setTimeout": "off", + "mozilla/no-define-cc-etc": "off", + "mozilla/no-useless-parameters": "off", + "mozilla/no-useless-run-test": "off", + "mozilla/use-chromeutils-generateqi": "off", + "mozilla/use-chromeutils-import": "off", + "mozilla/use-default-preference-values": "off", + "mozilla/use-includes-instead-of-indexOf": "off", + "mozilla/use-services": "off", + "mozilla/use-ownerGlobal": "off", + "complexity": "off", + "consistent-return": "off", + "dot-notation": "off", + "no-array-constructor": "off", + "no-caller": "off", + "no-cond-assign": "off", + "no-extra-boolean-cast": "off", + "no-eval": "off", + "no-else-return": "off", + "no-func-assign": "off", + "no-global-assign": "off", + "no-implied-eval": "off", + "no-lonely-if": "off", + "no-nested-ternary": "off", + "no-new-wrappers": "off", + "no-redeclare": "off", + "no-restricted-globals": "off", + "no-return-await": "off", + "no-sequences": "off", + "no-throw-literal": "off", + "no-useless-concat": "off", + "no-undef": "off", + "no-unreachable": "off", + "no-unsanitized/method": "off", + "no-unsanitized/property": "off", + "no-unsafe-negation": "off", + "no-unused-vars": "off", + "no-useless-return": "off", + + // Not enabling the rules below for now pending prettier roll-out. + "arrow-spacing": "off", + "block-spacing": "off", + "brace-style": "off", + "comma-dangle": "off", + "comma-spacing": "off", + "comma-style": "off", + "eol-last": "off", + "func-call-spacing": "off", + "generator-star-spacing": "off", + "linebreak-style": "off", + "key-spacing": "off", + "keyword-spacing": "off", + "no-extra-semi": "off", + "no-tabs": "off", + "no-mixed-spaces-and-tabs": "off", + "no-multi-spaces": "off", + "no-trailing-spaces": "off", + "no-unexpected-multiline": "off", + "no-whitespace-before-property": "off", + "padded-blocks": "off", + "quotes": "off", + "rest-spread-spacing": "off", + "semi": "off", + "space-before-blocks": "off", + "space-before-function-paren": "off", + "space-infix-ops": "off", + "space-unary-ops": "off", + "spaced-comment": "off", + } }] }; diff --git a/accessible/generic/Accessible.cpp b/accessible/generic/Accessible.cpp index f0765af68c40..b4e4830cbf3d 100644 --- a/accessible/generic/Accessible.cpp +++ b/accessible/generic/Accessible.cpp @@ -742,8 +742,7 @@ void Accessible::TakeFocus() const { nsFocusManager* fm = nsFocusManager::GetFocusManager(); if (fm) { - AutoHandlingUserInputStatePusher inputStatePusher(true, nullptr, - focusContent->OwnerDoc()); + AutoHandlingUserInputStatePusher inputStatePusher(true); // XXXbz: Can we actually have a non-element content here? RefPtr element = focusContent->IsElement() ? focusContent->AsElement() : nullptr; diff --git a/accessible/generic/DocAccessible.cpp b/accessible/generic/DocAccessible.cpp index 991d11a549cd..4160eedd27ca 100644 --- a/accessible/generic/DocAccessible.cpp +++ b/accessible/generic/DocAccessible.cpp @@ -294,8 +294,7 @@ void DocAccessible::TakeFocus() const { // Focus the document. nsFocusManager* fm = nsFocusManager::GetFocusManager(); RefPtr newFocus; - AutoHandlingUserInputStatePusher inputStatePusher(true, nullptr, - mDocumentNode); + AutoHandlingUserInputStatePusher inputStatePusher(true); fm->MoveFocus(mDocumentNode->GetWindow(), nullptr, nsFocusManager::MOVEFOCUS_ROOT, 0, getter_AddRefs(newFocus)); } diff --git a/browser/app/profile/firefox.js b/browser/app/profile/firefox.js index 0e0f43361de8..18e273699e7c 100644 --- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -498,9 +498,16 @@ pref("browser.tabs.showAudioPlayingIcon", true); // This should match Chromium's audio indicator delay. pref("browser.tabs.delayHidingAudioPlayingIconMS", 3000); -// Pref to control whether we use separate privileged content processes. #if defined(NIGHTLY_BUILD) && !defined(MOZ_ASAN) +// Pref to control whether we use a separate privileged content process +// for about: pages. This pref name did not age well: we will have multiple +// types of privileged content processes, each with different privileges. +// types of privleged content processes, each with different privleges. pref("browser.tabs.remote.separatePrivilegedContentProcess", true); +// Pref to control whether we use a separate privileged content process +// for certain mozilla webpages (which are listed in the pref +// browser.tabs.remote.separatedMozillaDomains). +pref("browser.tabs.remote.separatePrivilegedMozillaWebContentProcess", true); #endif #ifdef NIGHTLY_BUILD diff --git a/browser/base/content/browser-siteIdentity.js b/browser/base/content/browser-siteIdentity.js index cbff499b9685..be9b71e6da85 100644 --- a/browser/base/content/browser-siteIdentity.js +++ b/browser/base/content/browser-siteIdentity.js @@ -977,7 +977,7 @@ var gIdentityHandler = { let ctx = canvas.getContext("2d"); ctx.font = `${14 * scale}px sans-serif`; ctx.fillText(`${value}`, 20 * scale, 14 * scale); - let tabIcon = document.getAnonymousElementByAttribute(gBrowser.selectedTab, "anonid", "tab-icon-image"); + let tabIcon = gBrowser.selectedTab.iconImage; let image = new Image(); image.src = tabIcon.src; ctx.drawImage(image, 0, 0, 16 * scale, 16 * scale); diff --git a/browser/base/content/browser.css b/browser/base/content/browser.css index 3fa715f8696f..8dd343eea717 100644 --- a/browser/base/content/browser.css +++ b/browser/base/content/browser.css @@ -158,10 +158,6 @@ panelview[mainview] > .panel-header { visibility: hidden; /* temporary space to keep a tab's close button under the cursor */ } -.tabbrowser-tab { - -moz-binding: url("chrome://browser/content/tabbrowser.xml#tabbrowser-tab"); -} - .tabbrowser-tab:not([pinned]) { -moz-box-flex: 100; max-width: 225px; diff --git a/browser/base/content/browser.xul b/browser/base/content/browser.xul index 54bb31b69582..890c0311e039 100644 --- a/browser/base/content/browser.xul +++ b/browser/base/content/browser.xul @@ -96,6 +96,7 @@ Services.scriptloader.loadSubScript("chrome://browser/content/browser-sidebar.js", this); Services.scriptloader.loadSubScript("chrome://browser/content/browser-tabsintitlebar.js", this); Services.scriptloader.loadSubScript("chrome://browser/content/tabbrowser.js", this); + Services.scriptloader.loadSubScript("chrome://browser/content/tabbrowser-tab.js", this); Services.scriptloader.loadSubScript("chrome://browser/content/search/autocomplete-popup.js", this); Services.scriptloader.loadSubScript("chrome://browser/content/search/searchbar.js", this); @@ -728,7 +729,7 @@ setfocus="false" tooltip="tabbrowser-tab-tooltip" stopwatchid="FX_TAB_CLICK_MS"> - + { + if (event.originalTarget.classList.contains("tab-close-button")) { + this.mOverCloseButton = true; + } + this._mouseenter(); + }); + + this.addEventListener("mouseout", (event) => { + if (event.originalTarget.classList.contains("tab-close-button")) { + this.mOverCloseButton = false; + } + this._mouseleave(); + }); + + this.addEventListener("dragstart", (event) => { + this.style.MozUserFocus = ""; + }, true); + + this.addEventListener("dragstart", (event) => { + if (this.mOverCloseButton) { + event.stopPropagation(); + } + }); + + this.addEventListener("mousedown", (event) => { + let tabContainer = this.parentNode; + + if (tabContainer._closeTabByDblclick && + event.button == 0 && + event.detail == 1) { + this._selectedOnFirstMouseDown = this.selected; + } + + if (this.selected) { + this.style.MozUserFocus = "ignore"; + } else if (event.originalTarget.classList.contains("tab-close-button") || + event.originalTarget.classList.contains("tab-icon-sound") || + event.originalTarget.classList.contains("tab-icon-overlay")) { + // Prevent tabbox.xml from selecting the tab. + event.stopPropagation(); + } + + if (event.button == 1) { + gBrowser.warmupTab(gBrowser._findTabToBlurTo(this)); + } + + if (event.button == 0 && tabContainer._multiselectEnabled) { + let shiftKey = event.shiftKey; + let accelKey = event.getModifierState("Accel"); + if (shiftKey) { + const lastSelectedTab = gBrowser.lastMultiSelectedTab; + if (!accelKey) { + gBrowser.selectedTab = lastSelectedTab; + + // Make sure selection is cleared when tab-switch doesn't happen. + gBrowser.clearMultiSelectedTabs(false); + } + gBrowser.addRangeToMultiSelectedTabs(lastSelectedTab, this); + + // Prevent tabbox.xml from selecting the tab. + event.stopPropagation(); + } else if (accelKey) { + // Ctrl (Cmd for mac) key is pressed + if (this.multiselected) { + gBrowser.removeFromMultiSelectedTabs(this, true); + } else if (this != gBrowser.selectedTab) { + gBrowser.addToMultiSelectedTabs(this, false); + gBrowser.lastMultiSelectedTab = this; + } + + // Prevent tabbox.xml from selecting the tab. + event.stopPropagation(); + } else if (!this.selected && this.multiselected) { + gBrowser.lockClearMultiSelectionOnce(); + } + } + }, true); + + this.addEventListener("mouseup", (event) => { + // Make sure that clear-selection is released. + // Otherwise selection using Shift key may be broken. + gBrowser.unlockClearMultiSelection(); + + this.style.MozUserFocus = ""; + }); + + this.addEventListener("click", (event) => { + if (event.button != 0) { + return; + } + + if (event.getModifierState("Accel") || event.shiftKey) { + return; + } + + if (gBrowser.multiSelectedTabsCount > 0 && + !event.originalTarget.classList.contains("tab-close-button") && + !event.originalTarget.classList.contains("tab-icon-sound") && + !event.originalTarget.classList.contains("tab-icon-overlay")) { + // Tabs were previously multi-selected and user clicks on a tab + // without holding Ctrl/Cmd Key + + // Force positional attributes to update when the + // target (of the click) is the "active" tab. + let updatePositionalAttr = gBrowser.selectedTab == this; + + gBrowser.clearMultiSelectedTabs(updatePositionalAttr); + } + + if (event.originalTarget.classList.contains("tab-icon-sound") || + (event.originalTarget.classList.contains("tab-icon-overlay") && + (event.originalTarget.hasAttribute("soundplaying") || + event.originalTarget.hasAttribute("muted") || + event.originalTarget.hasAttribute("activemedia-blocked")))) { + if (this.multiselected) { + gBrowser.toggleMuteAudioOnMultiSelectedTabs(this); + } else { + this.toggleMuteAudio(); + } + return; + } + + if (event.originalTarget.classList.contains("tab-close-button")) { + if (this.multiselected) { + gBrowser.removeMultiSelectedTabs(); + } else { + gBrowser.removeTab(this, { + animate: true, + byMouse: event.mozInputSource == MouseEvent.MOZ_SOURCE_MOUSE, + }); + } + // This enables double-click protection for the tab container + // (see tabbrowser-tabs 'click' handler). + gBrowser.tabContainer._blockDblClick = true; + } + }); + + this.addEventListener("dblclick", (event) => { + if (event.button != 0) { + return; + } + + // for the one-close-button case + if (event.originalTarget.classList.contains("tab-close-button")) { + event.stopPropagation(); + } + + let tabContainer = this.parentNode; + if (tabContainer._closeTabByDblclick && + this._selectedOnFirstMouseDown && + this.selected && + !(event.originalTarget.classList.contains("tab-icon-sound") || + event.originalTarget.classList.contains("tab-icon-overlay"))) { + gBrowser.removeTab(this, { + animate: true, + byMouse: event.mozInputSource == MouseEvent.MOZ_SOURCE_MOUSE, + }); + } + }, true); + + this.addEventListener("animationend", (event) => { + if (event.originalTarget.classList.contains("tab-loading-burst")) { + this.removeAttribute("bursting"); + } + }); + + this._selectedOnFirstMouseDown = false; + + /** + * Describes how the tab ended up in this mute state. May be any of: + * + * - undefined: The tabs mute state has never changed. + * - null: The mute state was last changed through the UI. + * - Any string: The ID was changed through an extension API. The string + * must be the ID of the extension which changed it. + */ + this.muteReason = undefined; + + this.mOverCloseButton = false; + + this.mCorrespondingMenuitem = null; + } + + static get inheritedAttributes() { + return { + ".tab-background": "selected=visuallyselected,fadein,multiselected", + ".tab-line": "selected=visuallyselected,multiselected,before-multiselected", + ".tab-loading-burst": "pinned,bursting,notselectedsinceload", + ".tab-content": "pinned,selected=visuallyselected,titlechanged,attention", + ".tab-throbber": "fadein,pinned,busy,progress,selected=visuallyselected", + ".tab-icon-pending": "fadein,pinned,busy,progress,selected=visuallyselected,pendingicon", + ".tab-icon-image": "src=image,triggeringprincipal=iconloadingprincipal,requestcontextid,fadein,pinned,selected=visuallyselected,busy,crashed,sharing", + ".tab-sharing-icon-overlay": "sharing,selected=visuallyselected,pinned", + ".tab-icon-overlay": "crashed,busy,soundplaying,soundplaying-scheduledremoval,pinned,muted,blocked,selected=visuallyselected,activemedia-blocked", + ".tab-label-container": "pinned,selected=visuallyselected,labeldirection", + ".tab-label": "text=label,accesskey,fadein,pinned,selected=visuallyselected,attention", + ".tab-icon-pip": "pictureinpicture", + ".tab-icon-sound": "soundplaying,soundplaying-scheduledremoval,pinned,muted,blocked,selected=visuallyselected,activemedia-blocked,pictureinpicture", + ".tab-close-button": "fadein,pinned,selected=visuallyselected", + }; + } + + get fragment() { + if (!this._fragment) { + this._fragment = MozXULElement.parseXULToFragment(` + + + + + + + + + + + + + + + + + + + + + + `); + } + return this.ownerDocument.importNode(this._fragment, true); + } + + connectedCallback() { + if (this._initialized) { + return; + } + + this.textContent = ""; + this.appendChild(this.fragment); + this.initializeAttributeInheritance(); + this.setAttribute("context", "tabContextMenu"); + this._initialized = true; + + if (!("_lastAccessed" in this)) { + this.updateLastAccessed(); + } + } + + set _visuallySelected(val) { + if (val == (this.getAttribute("visuallyselected") == "true")) { + return val; + } + + if (val) { + this.setAttribute("visuallyselected", "true"); + } else { + this.removeAttribute("visuallyselected"); + } + gBrowser._tabAttrModified(this, ["visuallyselected"]); + + return val; + } + + set _selected(val) { + // in e10s we want to only pseudo-select a tab before its rendering is done, so that + // the rest of the system knows that the tab is selected, but we don't want to update its + // visual status to selected until after we receive confirmation that its content has painted. + if (val) + this.setAttribute("selected", "true"); + else + this.removeAttribute("selected"); + + // If we're non-e10s we should update the visual selection as well at the same time, + // *or* if we're e10s and the visually selected tab isn't changing, in which case the + // tab switcher code won't run and update anything else (like the before- and after- + // selected attributes). + if (!gMultiProcessBrowser || (val && this.hasAttribute("visuallyselected"))) { + this._visuallySelected = val; + } + + return val; + } + + get pinned() { + return this.getAttribute("pinned") == "true"; + } + + get hidden() { + return this.getAttribute("hidden") == "true"; + } + + get muted() { + return this.getAttribute("muted") == "true"; + } + + get multiselected() { + return this.getAttribute("multiselected") == "true"; + } + + get beforeMultiselected() { + return this.getAttribute("before-multiselected") == "true"; + } + + get userContextId() { + return this.hasAttribute("usercontextid") ? + parseInt(this.getAttribute("usercontextid")) : + 0; + } + + get soundPlaying() { + return this.getAttribute("soundplaying") == "true"; + } + + get pictureinpicture() { + return this.getAttribute("pictureinpicture") == "true"; + } + + get activeMediaBlocked() { + return this.getAttribute("activemedia-blocked") == "true"; + } + + get isEmpty() { + // Determines if a tab is "empty", usually used in the context of determining + // if it's ok to close the tab. + if (this.hasAttribute("busy")) + return false; + + if (this.hasAttribute("customizemode")) + return false; + + let browser = this.linkedBrowser; + if (!isBlankPageURL(browser.currentURI.spec)) + return false; + + if (!checkEmptyPageOrigin(browser)) + return false; + + if (browser.canGoForward || browser.canGoBack) + return false; + + return true; + } + + get lastAccessed() { + return this._lastAccessed == Infinity ? Date.now() : this._lastAccessed; + } + + get _overPlayingIcon() { + let iconVisible = this.hasAttribute("soundplaying") || + this.hasAttribute("muted") || + this.hasAttribute("activemedia-blocked"); + + let soundPlayingIcon = this.soundPlayingIcon; + let overlayIcon = this.overlayIcon; + return soundPlayingIcon && soundPlayingIcon.matches(":hover") || + (overlayIcon && overlayIcon.matches(":hover") && iconVisible); + } + + get soundPlayingIcon() { + return this.querySelector(".tab-icon-sound"); + } + + get overlayIcon() { + return this.querySelector(".tab-icon-overlay"); + } + + get throbber() { + return this.querySelector(".tab-throbber"); + } + + get iconImage() { + return this.querySelector(".tab-icon-image"); + } + + get sharingIcon() { + return this.querySelector(".tab-sharing-icon-overlay"); + } + + get textLabel() { + return this.querySelector(".tab-label"); + } + + get closeButton() { + return this.querySelector(".tab-close-button"); + } + + updateLastAccessed(aDate) { + this._lastAccessed = this.selected ? Infinity : (aDate || Date.now()); + } + + /** + * While it would make sense to track this in a field, the field will get nuked + * once the node is gone from the DOM, which causes us to think the tab is not + * closed, which causes us to make wrong decisions. So we use an expando instead. + * false + */ + _mouseenter() { + if (this.hidden || this.closing) { + return; + } + + let tabContainer = this.parentNode; + let visibleTabs = tabContainer._getVisibleTabs(); + let tabIndex = visibleTabs.indexOf(this); + + if (this.selected) + tabContainer._handleTabSelect(); + + if (tabIndex == 0) { + tabContainer._beforeHoveredTab = null; + } else { + let candidate = visibleTabs[tabIndex - 1]; + let separatedByScrollButton = + tabContainer.getAttribute("overflow") == "true" && + candidate.pinned && !this.pinned; + if (!candidate.selected && !separatedByScrollButton) { + tabContainer._beforeHoveredTab = candidate; + candidate.setAttribute("beforehovered", "true"); + } + } + + if (tabIndex == visibleTabs.length - 1) { + tabContainer._afterHoveredTab = null; + } else { + let candidate = visibleTabs[tabIndex + 1]; + if (!candidate.selected) { + tabContainer._afterHoveredTab = candidate; + candidate.setAttribute("afterhovered", "true"); + } + } + + tabContainer._hoveredTab = this; + if (this.linkedPanel && !this.selected) { + this.linkedBrowser.unselectedTabHover(true); + this.startUnselectedTabHoverTimer(); + } + + // Prepare connection to host beforehand. + SessionStore.speculativeConnectOnTabHover(this); + + let tabToWarm = this; + if (this.mOverCloseButton) { + tabToWarm = gBrowser._findTabToBlurTo(this); + } + gBrowser.warmupTab(tabToWarm); + } + + _mouseleave() { + let tabContainer = this.parentNode; + if (tabContainer._beforeHoveredTab) { + tabContainer._beforeHoveredTab.removeAttribute("beforehovered"); + tabContainer._beforeHoveredTab = null; + } + if (tabContainer._afterHoveredTab) { + tabContainer._afterHoveredTab.removeAttribute("afterhovered"); + tabContainer._afterHoveredTab = null; + } + + tabContainer._hoveredTab = null; + if (this.linkedPanel && !this.selected) { + this.linkedBrowser.unselectedTabHover(false); + this.cancelUnselectedTabHoverTimer(); + } + } + + startUnselectedTabHoverTimer() { + // Only record data when we need to. + if (!this.linkedBrowser.shouldHandleUnselectedTabHover) { + return; + } + + if (!TelemetryStopwatch.running("HOVER_UNTIL_UNSELECTED_TAB_OPENED", this)) { + TelemetryStopwatch.start("HOVER_UNTIL_UNSELECTED_TAB_OPENED", this); + } + + if (this._hoverTabTimer) { + clearTimeout(this._hoverTabTimer); + this._hoverTabTimer = null; + } + } + + cancelUnselectedTabHoverTimer() { + // Since we're listening "mouseout" event, instead of "mouseleave". + // Every time the cursor is moving from the tab to its child node (icon), + // it would dispatch "mouseout"(for tab) first and then dispatch + // "mouseover" (for icon, eg: close button, speaker icon) soon. + // It causes we would cancel present TelemetryStopwatch immediately + // when cursor is moving on the icon, and then start a new one. + // In order to avoid this situation, we could delay cancellation and + // remove it if we get "mouseover" within very short period. + this._hoverTabTimer = setTimeout(() => { + if (TelemetryStopwatch.running("HOVER_UNTIL_UNSELECTED_TAB_OPENED", this)) { + TelemetryStopwatch.cancel("HOVER_UNTIL_UNSELECTED_TAB_OPENED", this); + } + }, 100); + } + + finishUnselectedTabHoverTimer() { + // Stop timer when the tab is opened. + if (TelemetryStopwatch.running("HOVER_UNTIL_UNSELECTED_TAB_OPENED", this)) { + TelemetryStopwatch.finish("HOVER_UNTIL_UNSELECTED_TAB_OPENED", this); + } + } + + toggleMuteAudio(aMuteReason) { + let browser = this.linkedBrowser; + let modifiedAttrs = []; + let hist = Services.telemetry.getHistogramById("TAB_AUDIO_INDICATOR_USED"); + + if (this.hasAttribute("activemedia-blocked")) { + this.removeAttribute("activemedia-blocked"); + modifiedAttrs.push("activemedia-blocked"); + + browser.resumeMedia(); + hist.add(3 /* unblockByClickingIcon */ ); + } else { + if (browser.audioMuted) { + if (this.linkedPanel) { + // "Lazy Browser" should not invoke its unmute method + browser.unmute(); + } + this.removeAttribute("muted"); + hist.add(1 /* unmute */ ); + } else { + if (this.linkedPanel) { + // "Lazy Browser" should not invoke its mute method + browser.mute(); + } + this.setAttribute("muted", "true"); + hist.add(0 /* mute */ ); + } + this.muteReason = aMuteReason || null; + modifiedAttrs.push("muted"); + } + gBrowser._tabAttrModified(this, modifiedAttrs); + } + + setUserContextId(aUserContextId) { + if (aUserContextId) { + if (this.linkedBrowser) { + this.linkedBrowser.setAttribute("usercontextid", aUserContextId); + } + this.setAttribute("usercontextid", aUserContextId); + } else { + if (this.linkedBrowser) { + this.linkedBrowser.removeAttribute("usercontextid"); + } + this.removeAttribute("usercontextid"); + } + + ContextualIdentityService.setTabStyle(this); + } +} + +customElements.define("tabbrowser-tab", MozTabbrowserTab, { + extends: "tab", +}); +} diff --git a/browser/base/content/tabbrowser.js b/browser/base/content/tabbrowser.js index b0e13020112b..68358b242fc4 100644 --- a/browser/base/content/tabbrowser.js +++ b/browser/base/content/tabbrowser.js @@ -655,8 +655,7 @@ window._gBrowser = { const animations = Array.from(aTab.parentNode.getElementsByTagName("tab")) .map(tab => { - const throbber = - document.getAnonymousElementByAttribute(tab, "anonid", "tab-throbber"); + const throbber = tab.throbber; return throbber ? throbber.getAnimations({ subtree: true }) : []; }) .reduce((a, b) => a.concat(b)) @@ -2319,8 +2318,7 @@ window._gBrowser = { let openerTab = ((openerBrowser && this.getTabForBrowser(openerBrowser)) || (relatedToCurrent && this.selectedTab)); - var t = document.createXULElement("tab"); - + var t = document.createXULElement("tab", { is: "tabbrowser-tab" }); t.openerTab = openerTab; aURI = aURI || "about:blank"; @@ -4228,8 +4226,8 @@ window._gBrowser = { createTooltip(event) { event.stopPropagation(); - var tab = document.tooltipNode; - if (!tab || tab.localName != "tab") { + let tab = document.tooltipNode ? document.tooltipNode.closest("tab") : null; + if (!tab) { event.preventDefault(); return; } @@ -5410,8 +5408,9 @@ var TabContextMenu = { }); }, updateContextMenu(aPopupMenu) { - this.contextTab = aPopupMenu.triggerNode.localName == "tab" ? - aPopupMenu.triggerNode : gBrowser.selectedTab; + let tab = aPopupMenu.triggerNode && aPopupMenu.triggerNode.closest("tab"); + this.contextTab = tab || gBrowser.selectedTab; + let disabled = gBrowser.tabs.length == 1; let multiselectionContext = this.contextTab.multiselected; diff --git a/browser/base/content/tabbrowser.xml b/browser/base/content/tabbrowser.xml index 0ee33ad8d3a5..6913f90c4595 100644 --- a/browser/base/content/tabbrowser.xml +++ b/browser/base/content/tabbrowser.xml @@ -991,7 +991,10 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - false - - - - return this.getAttribute("pinned") == "true"; - - - - - return this.getAttribute("hidden") == "true"; - - - - - return this.getAttribute("muted") == "true"; - - - - - return this.getAttribute("multiselected") == "true"; - - - - - return this.getAttribute("before-multiselected") == "true"; - - - - undefined - - - - return this.hasAttribute("usercontextid") - ? parseInt(this.getAttribute("usercontextid")) - : 0; - - - - - - return this.getAttribute("soundplaying") == "true"; - - - - - - return this.getAttribute("pictureinpicture") == "true"; - - - - - - return this.getAttribute("activemedia-blocked") == "true"; - - - - - - // Determines if a tab is "empty", usually used in the context of determining - // if it's ok to close the tab. - if (this.hasAttribute("busy")) - return false; - - if (this.hasAttribute("customizemode")) - return false; - - let browser = this.linkedBrowser; - if (!isBlankPageURL(browser.currentURI.spec)) - return false; - - if (!checkEmptyPageOrigin(browser)) - return false; - - if (browser.canGoForward || browser.canGoBack) - return false; - - return true; - - - - - - return this._lastAccessed == Infinity ? Date.now() : this._lastAccessed; - - - - - - - - false - - - - null - - - - - - - - - - - - - - - - - { - if (TelemetryStopwatch.running("HOVER_UNTIL_UNSELECTED_TAB_OPENED", this)) { - TelemetryStopwatch.cancel("HOVER_UNTIL_UNSELECTED_TAB_OPENED", this); - } - }, 100); - ]]> - - - - - - - - - - - - - - - - - - - - - - - - - - - this.style.MozUserFocus = ""; - - - - - - - - - // Make sure that clear-selection is released. - // Otherwise selection using Shift key may be broken. - gBrowser.unlockClearMultiSelection(); - - this.style.MozUserFocus = ""; - - - 0 && - !event.originalTarget.classList.contains("tab-close-button") && - !event.originalTarget.classList.contains("tab-icon-sound") && - !event.originalTarget.classList.contains("tab-icon-overlay")) { - // Tabs were previously multi-selected and user clicks on a tab - // without holding Ctrl/Cmd Key - - // Force positional attributes to update when the - // target (of the click) is the "active" tab. - let updatePositionalAttr = gBrowser.selectedTab == this; - - gBrowser.clearMultiSelectedTabs(updatePositionalAttr); - } - - if (event.originalTarget.classList.contains("tab-icon-sound") || - (event.originalTarget.classList.contains("tab-icon-overlay") && - (event.originalTarget.hasAttribute("soundplaying") || - event.originalTarget.hasAttribute("muted") || - event.originalTarget.hasAttribute("activemedia-blocked")))) { - if (this.multiselected) { - gBrowser.toggleMuteAudioOnMultiSelectedTabs(this); - } else { - this.toggleMuteAudio(); - } - return; - } - - if (event.originalTarget.getAttribute("anonid") == "close-button") { - if (this.multiselected) { - gBrowser.removeMultiSelectedTabs(); - } else { - gBrowser.removeTab(this, { - animate: true, - byMouse: event.mozInputSource == MouseEvent.MOZ_SOURCE_MOUSE, - }); - } - // This enables double-click protection for the tab container - // (see tabbrowser-tabs 'click' handler). - gBrowser.tabContainer._blockDblClick = true; - } - ]]> - - - - - - - - diff --git a/browser/base/content/test/favicons/browser_title_flicker.js b/browser/base/content/test/favicons/browser_title_flicker.js index 7599bdb3489b..40350b80dc96 100644 --- a/browser/base/content/test/favicons/browser_title_flicker.js +++ b/browser/base/content/test/favicons/browser_title_flicker.js @@ -38,7 +38,7 @@ add_task(async () => { await waitForAttributeChange(tab, "label"); ok(tab.hasAttribute("busy"), "Should have seen the busy attribute"); - let label = document.getAnonymousElementByAttribute(tab, "anonid", "tab-label"); + let label = tab.textLabel; let bounds = label.getBoundingClientRect(); await waitForAttributeChange(tab, "busy"); @@ -60,7 +60,7 @@ add_task(async () => { is(icon.iconURL, "http://example.com/favicon.ico"); let tab = gBrowser.getTabForBrowser(browser); - let label = document.getAnonymousElementByAttribute(tab, "anonid", "tab-label"); + let label = tab.textLabel; let bounds = label.getBoundingClientRect(); await ContentTask.spawn(browser, null, () => { diff --git a/browser/base/content/test/forms/browser_selectpopup.js b/browser/base/content/test/forms/browser_selectpopup.js index cd14ee10a01f..2267bda416e6 100644 --- a/browser/base/content/test/forms/browser_selectpopup.js +++ b/browser/base/content/test/forms/browser_selectpopup.js @@ -794,3 +794,47 @@ add_task(async function test_blur_hides_popup() { BrowserTestUtils.removeTab(tab); }); + +function getIsHandlingUserInput(browser, elementId, eventName) { + return ContentTask.spawn(browser, [elementId, eventName], + async function([contentElementId, contentEventName]) { + let element = content.document.getElementById(contentElementId); + let isHandlingUserInput = false; + await ContentTaskUtils.waitForEvent(element, contentEventName, false, e => { + isHandlingUserInput = content.window.windowUtils.isHandlingUserInput; + return true; + }); + + return isHandlingUserInput; + }); +} + +// This test checks if the change/click event is considered as user input event. +add_task(async function test_handling_user_input() { + const pageUrl = "data:text/html," + escape(PAGECONTENT_SMALL); + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, pageUrl); + + let menulist = document.getElementById("ContentSelectDropdown"); + let selectPopup = menulist.menupopup; + + // Test onchange event when changing value via keyboard. + await openSelectPopup(selectPopup, "click", "#one"); + let getPromise = getIsHandlingUserInput(tab.linkedBrowser, "one", "change"); + EventUtils.synthesizeKey("KEY_ArrowDown"); + await hideSelectPopup(selectPopup); + is(await getPromise, true, "isHandlingUserInput should be true"); + + // Test onchange event when changing value via mouse click + await openSelectPopup(selectPopup, "click", "#two"); + getPromise = getIsHandlingUserInput(tab.linkedBrowser, "two", "change"); + EventUtils.synthesizeMouseAtCenter(selectPopup.lastElementChild, {}); + is(await getPromise, true, "isHandlingUserInput should be true"); + + // Test onclick event fired from clicking select popup. + await openSelectPopup(selectPopup, "click", "#three"); + getPromise = getIsHandlingUserInput(tab.linkedBrowser, "three", "click"); + EventUtils.synthesizeMouseAtCenter(selectPopup.firstElementChild, {}); + is(await getPromise, true, "isHandlingUserInput should be true"); + + BrowserTestUtils.removeTab(tab); +}); diff --git a/browser/base/content/test/general/browser.ini b/browser/base/content/test/general/browser.ini index c97c0f337357..af7831be3d0d 100644 --- a/browser/base/content/test/general/browser.ini +++ b/browser/base/content/test/general/browser.ini @@ -47,7 +47,6 @@ support-files = test_bug462673.html test_bug628179.html test_bug839103.html - test_process_flags_chrome.html title_test.svg unknownContentType_file.pif unknownContentType_file.pif^headers^ @@ -414,21 +413,6 @@ skip-if = e10s || debug # Bug 1094240 - has findbar-related failures # DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. [browser_addCertException.js] # DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. -[browser_e10s_about_page_triggeringprincipal.js] -skip-if = verify -support-files = - file_about_child.html - file_about_parent.html -# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. -[browser_e10s_switchbrowser.js] -# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. -[browser_e10s_about_process.js] -# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. -[browser_e10s_chrome_process.js] -skip-if = debug # Bug 1444565, Bug 1457887 -# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. -[browser_e10s_javascript.js] -# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD. [browser_blockHPKP.js] skip-if = verify && !debug uses-unsafe-cpows = true diff --git a/browser/base/content/test/general/browser_beforeunload_duplicate_dialogs.js b/browser/base/content/test/general/browser_beforeunload_duplicate_dialogs.js index 5615f0e9cbad..c31f0e21b4a3 100644 --- a/browser/base/content/test/general/browser_beforeunload_duplicate_dialogs.js +++ b/browser/base/content/test/general/browser_beforeunload_duplicate_dialogs.js @@ -34,7 +34,7 @@ add_task(async function closeLastTabInWindow() { let windowClosedPromise = BrowserTestUtils.domWindowClosed(newWin); expectingDialog = true; // close tab: - document.getAnonymousElementByAttribute(firstTab, "anonid", "close-button").click(); + firstTab.closeButton.click(); await windowClosedPromise; ok(!expectingDialog, "There should have been a dialog."); ok(newWin.closed, "Window should be closed."); @@ -63,13 +63,13 @@ add_task(async function closeWindoWithSingleTabTwice() { expectingDialog = true; wantToClose = false; let firstDialogShownPromise = new Promise((resolve, reject) => { resolveDialogPromise = resolve; }); - document.getAnonymousElementByAttribute(firstTab, "anonid", "close-button").click(); + firstTab.closeButton.click(); await firstDialogShownPromise; info("Got initial dialog, now trying again"); expectingDialog = true; wantToClose = true; resolveDialogPromise = null; - document.getAnonymousElementByAttribute(firstTab, "anonid", "close-button").click(); + firstTab.closeButton.click(); await windowClosedPromise; ok(!expectingDialog, "There should have been a dialog."); ok(newWin.closed, "Window should be closed."); diff --git a/browser/base/content/test/general/browser_double_close_tab.js b/browser/base/content/test/general/browser_double_close_tab.js index 6d69a4fca424..1a1df5a6b53e 100644 --- a/browser/base/content/test/general/browser_double_close_tab.js +++ b/browser/base/content/test/general/browser_double_close_tab.js @@ -61,10 +61,10 @@ add_task(async function() { doCompletion(); }); // Click again: - document.getAnonymousElementByAttribute(testTab, "anonid", "close-button").click(); + testTab.closeButton.click(); }); // Click once: - document.getAnonymousElementByAttribute(testTab, "anonid", "close-button").click(); + testTab.closeButton.click(); }); await TestUtils.waitForCondition(() => !testTab.parentNode); ok(!testTab.parentNode, "Tab should be closed completely"); diff --git a/browser/base/content/test/general/browser_e10s_about_process.js b/browser/base/content/test/general/browser_e10s_about_process.js deleted file mode 100644 index abcd7a35dc1b..000000000000 --- a/browser/base/content/test/general/browser_e10s_about_process.js +++ /dev/null @@ -1,176 +0,0 @@ -const CHROME_PROCESS = E10SUtils.NOT_REMOTE; -const WEB_CONTENT_PROCESS = E10SUtils.WEB_REMOTE_TYPE; -const PRIVILEGED_CONTENT_PROCESS = E10SUtils.PRIVILEGED_REMOTE_TYPE; -const EXTENSION_PROCESS = E10SUtils.EXTENSION_REMOTE_TYPE; - -const CHROME = { - id: "cb34538a-d9da-40f3-b61a-069f0b2cb9fb", - path: "test-chrome", - flags: 0, -}; -const CANREMOTE = { - id: "2480d3e1-9ce4-4b84-8ae3-910b9a95cbb3", - path: "test-allowremote", - flags: Ci.nsIAboutModule.URI_CAN_LOAD_IN_CHILD, -}; -const MUSTREMOTE = { - id: "f849cee5-e13e-44d2-981d-0fb3884aaead", - path: "test-mustremote", - flags: Ci.nsIAboutModule.URI_MUST_LOAD_IN_CHILD, -}; -const CANPRIVILEGEDREMOTE = { - id: "a04ffafe-6c63-4266-acae-0f4b093165aa", - path: "test-canprivilegedremote", - flags: Ci.nsIAboutModule.URI_MUST_LOAD_IN_CHILD | - Ci.nsIAboutModule.URI_CAN_LOAD_IN_PRIVILEGED_CHILD, -}; -const MUSTEXTENSION = { - id: "f7a1798f-965b-49e9-be83-ec6ee4d7d675", - path: "test-mustextension", - flags: Ci.nsIAboutModule.URI_MUST_LOAD_IN_EXTENSION_PROCESS, -}; - - -const TEST_MODULES = [ - CHROME, - CANREMOTE, - MUSTREMOTE, - CANPRIVILEGEDREMOTE, - MUSTEXTENSION, -]; - -function AboutModule() { -} - -AboutModule.prototype = { - newChannel(aURI, aLoadInfo) { - throw Cr.NS_ERROR_NOT_IMPLEMENTED; - }, - - getURIFlags(aURI) { - for (let module of TEST_MODULES) { - if (aURI.pathQueryRef.startsWith(module.path)) { - return module.flags; - } - } - - ok(false, "Called getURIFlags for an unknown page " + aURI.spec); - return 0; - }, - - getIndexedDBOriginPostfix(aURI) { - return null; - }, - - QueryInterface: ChromeUtils.generateQI([Ci.nsIAboutModule]), -}; - -var AboutModuleFactory = { - createInstance(aOuter, aIID) { - if (aOuter) - throw Cr.NS_ERROR_NO_AGGREGATION; - return new AboutModule().QueryInterface(aIID); - }, - - lockFactory(aLock) { - throw Cr.NS_ERROR_NOT_IMPLEMENTED; - }, - - QueryInterface: ChromeUtils.generateQI([Ci.nsIFactory]), -}; - -add_task(async function init() { - let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar); - for (let module of TEST_MODULES) { - registrar.registerFactory(Components.ID(module.id), "", - "@mozilla.org/network/protocol/about;1?what=" + module.path, - AboutModuleFactory); - } -}); - -registerCleanupFunction(() => { - let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar); - for (let module of TEST_MODULES) { - registrar.unregisterFactory(Components.ID(module.id), AboutModuleFactory); - } -}); - -function test_url(url, chromeResult, webContentResult, privilegedContentResult, extensionProcessResult) { - is(E10SUtils.canLoadURIInRemoteType(url, /* fission */ false, CHROME_PROCESS), - chromeResult, "Check URL in chrome process."); - is(E10SUtils.canLoadURIInRemoteType(url, /* fission */ false, WEB_CONTENT_PROCESS), - webContentResult, "Check URL in web content process."); - is(E10SUtils.canLoadURIInRemoteType(url, /* fission */ false, PRIVILEGED_CONTENT_PROCESS), - privilegedContentResult, "Check URL in privileged content process."); - is(E10SUtils.canLoadURIInRemoteType(url, /* fission */ false, EXTENSION_PROCESS), - extensionProcessResult, "Check URL in extension process."); - - is(E10SUtils.canLoadURIInRemoteType(url + "#foo", /* fission */ false, CHROME_PROCESS), - chromeResult, "Check URL with ref in chrome process."); - is(E10SUtils.canLoadURIInRemoteType(url + "#foo", /* fission */ false, WEB_CONTENT_PROCESS), - webContentResult, "Check URL with ref in web content process."); - is(E10SUtils.canLoadURIInRemoteType(url + "#foo", /* fission */ false, PRIVILEGED_CONTENT_PROCESS), - privilegedContentResult, "Check URL with ref in privileged content process."); - is(E10SUtils.canLoadURIInRemoteType(url + "#foo", /* fission */ false, EXTENSION_PROCESS), - extensionProcessResult, "Check URL with ref in extension process."); - - is(E10SUtils.canLoadURIInRemoteType(url + "?foo", /* fission */ false, CHROME_PROCESS), - chromeResult, "Check URL with query in chrome process."); - is(E10SUtils.canLoadURIInRemoteType(url + "?foo", /* fission */ false, WEB_CONTENT_PROCESS), - webContentResult, "Check URL with query in web content process."); - is(E10SUtils.canLoadURIInRemoteType(url + "?foo", /* fission */ false, PRIVILEGED_CONTENT_PROCESS), - privilegedContentResult, "Check URL with query in privileged content process."); - is(E10SUtils.canLoadURIInRemoteType(url + "?foo", /* fission */ false, EXTENSION_PROCESS), - extensionProcessResult, "Check URL with query in extension process."); - - is(E10SUtils.canLoadURIInRemoteType(url + "?foo#bar", /* fission */ false, CHROME_PROCESS), - chromeResult, "Check URL with query and ref in chrome process."); - is(E10SUtils.canLoadURIInRemoteType(url + "?foo#bar", /* fission */ false, WEB_CONTENT_PROCESS), - webContentResult, "Check URL with query and ref in web content process."); - is(E10SUtils.canLoadURIInRemoteType(url + "?foo#bar", /* fission */ false, PRIVILEGED_CONTENT_PROCESS), - privilegedContentResult, "Check URL with query and ref in privileged content process."); - is(E10SUtils.canLoadURIInRemoteType(url + "?foo#bar", /* fission */ false, EXTENSION_PROCESS), - extensionProcessResult, "Check URL with query and ref in extension process."); -} - -add_task(async function test_chrome() { - test_url("about:" + CHROME.path, true, false, false, false); -}); - -add_task(async function test_any() { - test_url("about:" + CANREMOTE.path, true, true, false, false); -}); - -add_task(async function test_remote() { - test_url("about:" + MUSTREMOTE.path, false, true, false, false); -}); - -add_task(async function test_privileged_remote_true() { - await SpecialPowers.pushPrefEnv({ - set: [ - ["browser.tabs.remote.separatePrivilegedContentProcess", true], - ], - }); - - // This shouldn't be taken literally. We will always use the privileged - // content type if the URI_CAN_LOAD_IN_PRIVILEGED_CHILD flag is enabled and - // the pref is turned on. - test_url("about:" + CANPRIVILEGEDREMOTE.path, false, false, true, false); -}); - -add_task(async function test_privileged_remote_false() { - await SpecialPowers.pushPrefEnv({ - set: [ - ["browser.tabs.remote.separatePrivilegedContentProcess", false], - ], - }); - - // This shouldn't be taken literally. We will always use the privileged - // content type if the URI_CAN_LOAD_IN_PRIVILEGED_CHILD flag is enabled and - // the pref is turned on. - test_url("about:" + CANPRIVILEGEDREMOTE.path, false, true, false, false); -}); - -add_task(async function test_extension() { - test_url("about:" + MUSTEXTENSION.path, false, false, false, true); -}); diff --git a/browser/base/content/test/general/browser_tabs_close_beforeunload.js b/browser/base/content/test/general/browser_tabs_close_beforeunload.js index df24f5511a09..3e7e1bef6247 100644 --- a/browser/base/content/test/general/browser_tabs_close_beforeunload.js +++ b/browser/base/content/test/general/browser_tabs_close_beforeunload.js @@ -31,7 +31,7 @@ add_task(async function() { info("Waiting for the load in that tab to finish"); await secondTabLoadedPromise; - let closeBtn = document.getAnonymousElementByAttribute(secondTab, "anonid", "close-button"); + let closeBtn = secondTab.closeButton; info("closing second tab (which will self-close in beforeunload)"); closeBtn.click(); ok(secondTab.closing, "Second tab should be marked as closing synchronously."); diff --git a/browser/base/content/test/performance/browser_appmenu.js b/browser/base/content/test/performance/browser_appmenu.js index 67924d0742c8..86eaa6af095f 100644 --- a/browser/base/content/test/performance/browser_appmenu.js +++ b/browser/base/content/test/performance/browser_appmenu.js @@ -46,6 +46,7 @@ add_task(async function() { "anonid", "moz-input-box").getBoundingClientRect(); let menuButtonRect = document.getElementById("PanelUI-menu-button").getBoundingClientRect(); + let firstTabRect = gBrowser.selectedTab.getBoundingClientRect(); let frameExpectations = { filter: rects => rects.filter(r => !( // We expect the menu button to get into the active state. @@ -60,6 +61,11 @@ add_task(async function() { r.x1 >= textBoxRect.left && r.x2 <= textBoxRect.right && r.y1 >= textBoxRect.top && r.y2 <= textBoxRect.bottom, }, + {name: "bug 1547341 - a first tab gets drawn early", + condition: r => + r.x1 >= firstTabRect.left && r.x2 <= firstTabRect.right && + r.y1 >= firstTabRect.top && r.y2 <= firstTabRect.bottom, + }, ], }; diff --git a/browser/base/content/test/performance/browser_preferences_usage.js b/browser/base/content/test/performance/browser_preferences_usage.js index 63ec9efc814b..b0cf45c2fcd7 100644 --- a/browser/base/content/test/performance/browser_preferences_usage.js +++ b/browser/base/content/test/performance/browser_preferences_usage.js @@ -134,7 +134,7 @@ add_task(async function open_10_tabs() { max: 20, }, "browser.tabs.remote.logSwitchTiming": { - max: 25, + max: 35, }, "network.loadinfo.skip_type_assertion": { // This is accessed in debug only. diff --git a/browser/base/content/test/performance/browser_tabopen.js b/browser/base/content/test/performance/browser_tabopen.js index 2422a6be4dd1..c9ae6753b595 100644 --- a/browser/base/content/test/performance/browser_tabopen.js +++ b/browser/base/content/test/performance/browser_tabopen.js @@ -31,11 +31,12 @@ add_task(async function() { let tabStripRect = gBrowser.tabContainer.arrowScrollbox.getBoundingClientRect(); let firstTabRect = gBrowser.selectedTab.getBoundingClientRect(); - let firstTabLabelRect = - document.getAnonymousElementByAttribute(gBrowser.selectedTab, "anonid", "tab-label") - .getBoundingClientRect(); + let firstTabLabelRect = gBrowser.selectedTab.textLabel.getBoundingClientRect(); let textBoxRect = document.getAnonymousElementByAttribute(gURLBar.textbox, "anonid", "moz-input-box").getBoundingClientRect(); + let historyDropmarkerRect = document.getAnonymousElementByAttribute( + gURLBar.textbox, "anonid", "historydropmarker").getBoundingClientRect(); + let inRange = (val, min, max) => min <= val && val <= max; // Add a reflow observer and open a new tab. @@ -93,6 +94,12 @@ add_task(async function() { r.y1 >= firstTabLabelRect.y && r.y2 <= firstTabLabelRect.bottom, }, + {name: "bug 1547341 - addressbar history dropmarker is shown", + condition: r => r.x1 >= historyDropmarkerRect.x && + r.x2 <= historyDropmarkerRect.right && + r.y1 >= historyDropmarkerRect.y && + r.y2 <= historyDropmarkerRect.bottom, + }, ], }, }); diff --git a/browser/base/content/test/sync/browser_fxa_web_channel.js b/browser/base/content/test/sync/browser_fxa_web_channel.js index 878dff438a99..de3550011e87 100644 --- a/browser/base/content/test/sync/browser_fxa_web_channel.js +++ b/browser/base/content/test/sync/browser_fxa_web_channel.js @@ -191,8 +191,14 @@ function makeObserver(aObserveTopic, aObserveFunc) { return removeMe; } +registerCleanupFunction(function() { + Services.prefs.clearUserPref("browser.tabs.remote.separatePrivilegedMozillaWebContentProcess"); +}); + + function test() { waitForExplicitFinish(); + Services.prefs.setBoolPref("browser.tabs.remote.separatePrivilegedMozillaWebContentProcess", false); (async function() { for (let testCase of gTests) { diff --git a/browser/base/content/test/tabs/browser.ini b/browser/base/content/test/tabs/browser.ini index 726eea06fc63..1789d8f8b8ac 100644 --- a/browser/base/content/test/tabs/browser.ini +++ b/browser/base/content/test/tabs/browser.ini @@ -4,6 +4,7 @@ support-files = dummy_page.html ../general/audio.ogg file_mediaPlayback.html + test_process_flags_chrome.html [browser_accessibility_indicator.js] skip-if = (verify && debug && (os == 'linux')) || (os == 'win' && processor == 'aarch64') @@ -19,6 +20,17 @@ skip-if = (verify && debug && (os == 'linux')) support-files = test_bug1358314.html [browser_isLocalAboutURI.js] +[browser_e10s_about_page_triggeringprincipal.js] +skip-if = verify +support-files = + file_about_child.html + file_about_parent.html +[browser_e10s_switchbrowser.js] +[browser_e10s_about_process.js] +[browser_e10s_mozillaweb_process.js] +[browser_e10s_chrome_process.js] +skip-if = debug # Bug 1444565, Bug 1457887 +[browser_e10s_javascript.js] [browser_multiselect_tabs_active_tab_selected_by_default.js] [browser_multiselect_tabs_bookmark.js] [browser_multiselect_tabs_clear_selection_when_tab_switch.js] @@ -52,7 +64,9 @@ skip-if = !e10s # Test only relevant for e10s. [browser_new_tab_insert_position.js] skip-if = (debug && os == 'linux' && bits == 32) #Bug 1455882, disabled on Linux32 for almost permafailing support-files = file_new_tab_page.html -[browser_new_tab_in_privileged_process_pref.js] +[browser_new_tab_in_privilegedabout_process_pref.js] +skip-if = !e10s # Pref and test only relevant for e10s. +[browser_privilegedmozilla_process_pref.js] skip-if = !e10s # Pref and test only relevant for e10s. [browser_new_web_tab_in_file_process_pref.js] skip-if = !e10s # Pref and test only relevant for e10s. diff --git a/browser/base/content/test/tabs/browser_audioTabIcon.js b/browser/base/content/test/tabs/browser_audioTabIcon.js index 592dd9e05619..c3df357d9dd5 100644 --- a/browser/base/content/test/tabs/browser_audioTabIcon.js +++ b/browser/base/content/test/tabs/browser_audioTabIcon.js @@ -111,8 +111,7 @@ async function test_muting_using_menu(tab, expectMuted) { } async function test_playing_icon_on_tab(tab, browser, isPinned) { - let icon = document.getAnonymousElementByAttribute(tab, "anonid", - isPinned ? "overlay-icon" : "soundplaying-icon"); + let icon = isPinned ? tab.overlayIcon : tab.soundPlayingIcon; let isActiveTab = tab === gBrowser.selectedTab; await play(tab); @@ -241,9 +240,7 @@ async function test_swapped_browser_while_playing(oldTab, newBrowser) { is(newTab.muteReason, null, "Expected the correct muteReason property on the new tab"); ok(!newTab.hasAttribute("soundplaying"), "Expected the correct soundplaying attribute on the new tab"); - let icon = document.getAnonymousElementByAttribute(newTab, "anonid", - "soundplaying-icon"); - await test_tooltip(icon, "Unmute tab", true); + await test_tooltip(newTab.soundPlayingIcon, "Unmute tab", true); } async function test_swapped_browser_while_not_playing(oldTab, newBrowser) { @@ -281,18 +278,14 @@ async function test_swapped_browser_while_not_playing(oldTab, newBrowser) { is(newTab.muteReason, null, "Expected the correct muteReason property on the new tab"); ok(!newTab.hasAttribute("soundplaying"), "Expected the correct soundplaying attribute on the new tab"); - let icon = document.getAnonymousElementByAttribute(newTab, "anonid", - "soundplaying-icon"); - await test_tooltip(icon, "Unmute tab", true); + await test_tooltip(newTab.soundPlayingIcon, "Unmute tab", true); } async function test_browser_swapping(tab, browser) { // First, test swapping with a playing but muted tab. await play(tab); - let icon = document.getAnonymousElementByAttribute(tab, "anonid", - "soundplaying-icon"); - await test_mute_tab(tab, icon, true); + await test_mute_tab(tab, tab.soundPlayingIcon, true); await BrowserTestUtils.withNewTab({ gBrowser, @@ -327,7 +320,7 @@ async function test_click_on_pinned_tab_after_mute() { await play(tab); // Mute the tab. - let icon = document.getAnonymousElementByAttribute(tab, "anonid", "overlay-icon"); + let icon = tab.overlayIcon; await test_mute_tab(tab, icon, true); // Pause playback and wait for it to finish. @@ -337,8 +330,7 @@ async function test_click_on_pinned_tab_after_mute() { await test_mute_tab(tab, icon, false); // Now click on the tab. - let image = document.getAnonymousElementByAttribute(tab, "anonid", "tab-icon-image"); - EventUtils.synthesizeMouseAtCenter(image, {button: 0}); + EventUtils.synthesizeMouseAtCenter(tab.iconImage, {button: 0}); is(tab, gBrowser.selectedTab, "Tab switch should be successful"); diff --git a/browser/base/content/test/general/browser_e10s_about_page_triggeringprincipal.js b/browser/base/content/test/tabs/browser_e10s_about_page_triggeringprincipal.js similarity index 100% rename from browser/base/content/test/general/browser_e10s_about_page_triggeringprincipal.js rename to browser/base/content/test/tabs/browser_e10s_about_page_triggeringprincipal.js diff --git a/browser/base/content/test/tabs/browser_e10s_about_process.js b/browser/base/content/test/tabs/browser_e10s_about_process.js new file mode 100644 index 000000000000..2833cdf59221 --- /dev/null +++ b/browser/base/content/test/tabs/browser_e10s_about_process.js @@ -0,0 +1,135 @@ +const CHROME = { + id: "cb34538a-d9da-40f3-b61a-069f0b2cb9fb", + path: "test-chrome", + flags: 0, +}; +const CANREMOTE = { + id: "2480d3e1-9ce4-4b84-8ae3-910b9a95cbb3", + path: "test-allowremote", + flags: Ci.nsIAboutModule.URI_CAN_LOAD_IN_CHILD, +}; +const MUSTREMOTE = { + id: "f849cee5-e13e-44d2-981d-0fb3884aaead", + path: "test-mustremote", + flags: Ci.nsIAboutModule.URI_MUST_LOAD_IN_CHILD, +}; +const CANPRIVILEGEDREMOTE = { + id: "a04ffafe-6c63-4266-acae-0f4b093165aa", + path: "test-canprivilegedremote", + flags: Ci.nsIAboutModule.URI_MUST_LOAD_IN_CHILD | + Ci.nsIAboutModule.URI_CAN_LOAD_IN_PRIVILEGEDABOUT_PROCESS, +}; +const MUSTEXTENSION = { + id: "f7a1798f-965b-49e9-be83-ec6ee4d7d675", + path: "test-mustextension", + flags: Ci.nsIAboutModule.URI_MUST_LOAD_IN_EXTENSION_PROCESS, +}; + + +const TEST_MODULES = [ + CHROME, + CANREMOTE, + MUSTREMOTE, + CANPRIVILEGEDREMOTE, + MUSTEXTENSION, +]; + +function AboutModule() { +} + +AboutModule.prototype = { + newChannel(aURI, aLoadInfo) { + throw Cr.NS_ERROR_NOT_IMPLEMENTED; + }, + + getURIFlags(aURI) { + for (let module of TEST_MODULES) { + if (aURI.pathQueryRef.startsWith(module.path)) { + return module.flags; + } + } + + ok(false, "Called getURIFlags for an unknown page " + aURI.spec); + return 0; + }, + + getIndexedDBOriginPostfix(aURI) { + return null; + }, + + QueryInterface: ChromeUtils.generateQI([Ci.nsIAboutModule]), +}; + +var AboutModuleFactory = { + createInstance(aOuter, aIID) { + if (aOuter) + throw Cr.NS_ERROR_NO_AGGREGATION; + return new AboutModule().QueryInterface(aIID); + }, + + lockFactory(aLock) { + throw Cr.NS_ERROR_NOT_IMPLEMENTED; + }, + + QueryInterface: ChromeUtils.generateQI([Ci.nsIFactory]), +}; + +add_task(async function init() { + SpecialPowers.setBoolPref("browser.tabs.remote.separatePrivilegedMozillaWebContentProcess", true); + let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar); + for (let module of TEST_MODULES) { + registrar.registerFactory(Components.ID(module.id), "", + "@mozilla.org/network/protocol/about;1?what=" + module.path, + AboutModuleFactory); + } +}); + +registerCleanupFunction(() => { + SpecialPowers.clearUserPref("browser.tabs.remote.separatePrivilegedMozillaWebContentProcess"); + let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar); + for (let module of TEST_MODULES) { + registrar.unregisterFactory(Components.ID(module.id), AboutModuleFactory); + } +}); + +add_task(async function test_chrome() { + test_url_for_process_types("about:" + CHROME.path, true, false, false, false, false); +}); + +add_task(async function test_any() { + test_url_for_process_types("about:" + CANREMOTE.path, true, true, false, false, false); +}); + +add_task(async function test_remote() { + test_url_for_process_types("about:" + MUSTREMOTE.path, false, true, false, false, false); +}); + +add_task(async function test_privileged_remote_true() { + await SpecialPowers.pushPrefEnv({ + set: [ + ["browser.tabs.remote.separatePrivilegedContentProcess", true], + ], + }); + + // This shouldn't be taken literally. We will always use the privleged about + // content type if the URI_CAN_LOAD_IN_PRIVILEGEDABOUT_PROCESS flag is enabled and + // the pref is turned on. + test_url_for_process_types("about:" + CANPRIVILEGEDREMOTE.path, false, false, true, false, false); +}); + +add_task(async function test_privileged_remote_false() { + await SpecialPowers.pushPrefEnv({ + set: [ + ["browser.tabs.remote.separatePrivilegedContentProcess", false], + ], + }); + + // This shouldn't be taken literally. We will always use the privleged about + // content type if the URI_CAN_LOAD_IN_PRIVILEGEDABOUT_PROCESS flag is enabled and + // the pref is turned on. + test_url_for_process_types("about:" + CANPRIVILEGEDREMOTE.path, false, true, false, false, false); +}); + +add_task(async function test_extension() { + test_url_for_process_types("about:" + MUSTEXTENSION.path, false, false, false, false, true); +}); diff --git a/browser/base/content/test/general/browser_e10s_chrome_process.js b/browser/base/content/test/tabs/browser_e10s_chrome_process.js similarity index 69% rename from browser/base/content/test/general/browser_e10s_chrome_process.js rename to browser/base/content/test/tabs/browser_e10s_chrome_process.js index 296747da8223..af27416147aa 100644 --- a/browser/base/content/test/general/browser_e10s_chrome_process.js +++ b/browser/base/content/test/tabs/browser_e10s_chrome_process.js @@ -32,8 +32,6 @@ function makeTest(name, startURL, startProcessIsRemote, endURL, endProcessIsRemo }; } -const CHROME_PROCESS = E10SUtils.NOT_REMOTE; -const WEB_CONTENT_PROCESS = E10SUtils.WEB_REMOTE_TYPE; const PATH = (getRootDirectory(gTestPath) + "test_process_flags_chrome.html").replace("chrome://mochitests", ""); const CHROME = "chrome://mochitests" + PATH; @@ -48,38 +46,16 @@ registerCleanupFunction(() => { gBrowser.removeCurrentTab(); }); -function test_url(url, chromeResult, contentResult) { - is(E10SUtils.canLoadURIInRemoteType(url, /* fission */ false, CHROME_PROCESS), - chromeResult, "Check URL in chrome process."); - is(E10SUtils.canLoadURIInRemoteType(url, /* fission */ false, WEB_CONTENT_PROCESS), - contentResult, "Check URL in web content process."); - - is(E10SUtils.canLoadURIInRemoteType(url + "#foo", /* fission */ false, CHROME_PROCESS), - chromeResult, "Check URL with ref in chrome process."); - is(E10SUtils.canLoadURIInRemoteType(url + "#foo", /* fission */ false, WEB_CONTENT_PROCESS), - contentResult, "Check URL with ref in web content process."); - - is(E10SUtils.canLoadURIInRemoteType(url + "?foo", /* fission */ false, CHROME_PROCESS), - chromeResult, "Check URL with query in chrome process."); - is(E10SUtils.canLoadURIInRemoteType(url + "?foo", /* fission */ false, WEB_CONTENT_PROCESS), - contentResult, "Check URL with query in web content process."); - - is(E10SUtils.canLoadURIInRemoteType(url + "?foo#bar", /* fission */ false, CHROME_PROCESS), - chromeResult, "Check URL with query and ref in chrome process."); - is(E10SUtils.canLoadURIInRemoteType(url + "?foo#bar", /* fission */ false, WEB_CONTENT_PROCESS), - contentResult, "Check URL with query and ref in web content process."); -} - add_task(async function test_chrome() { - test_url(CHROME, true, false); + test_url_for_process_types(CHROME, true, false, false, false, false); }); add_task(async function test_any() { - test_url(CANREMOTE, true, true); + test_url_for_process_types(CANREMOTE, true, true, false, false, false); }); add_task(async function test_remote() { - test_url(MUSTREMOTE, false, true); + test_url_for_process_types(MUSTREMOTE, false, true, false, false, false); }); // The set of page transitions diff --git a/browser/base/content/test/general/browser_e10s_javascript.js b/browser/base/content/test/tabs/browser_e10s_javascript.js similarity index 100% rename from browser/base/content/test/general/browser_e10s_javascript.js rename to browser/base/content/test/tabs/browser_e10s_javascript.js diff --git a/browser/base/content/test/tabs/browser_e10s_mozillaweb_process.js b/browser/base/content/test/tabs/browser_e10s_mozillaweb_process.js new file mode 100644 index 000000000000..20574acf90f7 --- /dev/null +++ b/browser/base/content/test/tabs/browser_e10s_mozillaweb_process.js @@ -0,0 +1,24 @@ +add_task(async function test_privileged_remote_true() { + await SpecialPowers.pushPrefEnv({ + set: [ + ["browser.tabs.remote.separatePrivilegedContentProcess", true], + ["browser.tabs.remote.separatePrivilegedMozillaWebContentProcess", true], + ["browser.tabs.remote.separatedMozillaDomains", "example.org"], + ], + }); + + test_url_for_process_types("https://example.com", false, true, false, false, false); + test_url_for_process_types("https://example.org", false, false, false, true, false); +}); + +add_task(async function test_privileged_remote_false() { + await SpecialPowers.pushPrefEnv({ + set: [ + ["browser.tabs.remote.separatePrivilegedContentProcess", true], + ["browser.tabs.remote.separatePrivilegedMozillaWebContentProcess", false], + ], + }); + + test_url_for_process_types("https://example.com", false, true, false, false, false); + test_url_for_process_types("https://example.org", false, true, false, false, false); +}); diff --git a/browser/base/content/test/general/browser_e10s_switchbrowser.js b/browser/base/content/test/tabs/browser_e10s_switchbrowser.js similarity index 100% rename from browser/base/content/test/general/browser_e10s_switchbrowser.js rename to browser/base/content/test/tabs/browser_e10s_switchbrowser.js diff --git a/browser/base/content/test/tabs/browser_multiselect_tabs_close.js b/browser/base/content/test/tabs/browser_multiselect_tabs_close.js index a67d514bdb31..811e6e4c900f 100644 --- a/browser/base/content/test/tabs/browser_multiselect_tabs_close.js +++ b/browser/base/content/test/tabs/browser_multiselect_tabs_close.js @@ -29,7 +29,7 @@ add_task(async function usingTabCloseButton() { is(gBrowser.selectedTab, tab1, "Tab1 is active"); // Closing a tab which is not multiselected - let tab4CloseBtn = document.getAnonymousElementByAttribute(tab4, "anonid", "close-button"); + let tab4CloseBtn = tab4.closeButton; let tab4Closing = BrowserTestUtils.waitForTabClosing(tab4); tab4.mOverCloseButton = true; @@ -48,7 +48,7 @@ add_task(async function usingTabCloseButton() { is(gBrowser.multiSelectedTabsCount, 2, "Two multiselected tabs"); // Closing a selected tab - let tab2CloseBtn = document.getAnonymousElementByAttribute(tab2, "anonid", "close-button"); + let tab2CloseBtn = tab2.closeButton; tab2.mOverCloseButton = true; let tab1Closing = BrowserTestUtils.waitForTabClosing(tab1); let tab2Closing = BrowserTestUtils.waitForTabClosing(tab2); diff --git a/browser/base/content/test/tabs/browser_multiselect_tabs_mute_unmute.js b/browser/base/content/test/tabs/browser_multiselect_tabs_mute_unmute.js index 7d35d779da97..4ee585377943 100644 --- a/browser/base/content/test/tabs/browser_multiselect_tabs_mute_unmute.js +++ b/browser/base/content/test/tabs/browser_multiselect_tabs_mute_unmute.js @@ -55,7 +55,7 @@ add_task(async function muteTabs_usingButton() { } // Mute tab0 which is not multiselected, thus other tabs muted state should not be affected - let tab0MuteAudioBtn = document.getAnonymousElementByAttribute(tab0, "anonid", "soundplaying-icon"); + let tab0MuteAudioBtn = tab0.soundPlayingIcon; await test_mute_tab(tab0, tab0MuteAudioBtn, true); ok(muted(tab0), "Tab0 is muted"); @@ -85,7 +85,7 @@ add_task(async function muteTabs_usingButton() { // b) unmuted tabs (tab1, tab3) will become muted. // b) media-blocked tabs (tab2) will remain media-blocked. // However tab4 (unmuted) which is not multiselected should not be affected. - let tab1MuteAudioBtn = document.getAnonymousElementByAttribute(tab1, "anonid", "soundplaying-icon"); + let tab1MuteAudioBtn = tab1.soundPlayingIcon; await test_mute_tab(tab1, tab1MuteAudioBtn, true); // Check mute state @@ -141,7 +141,7 @@ add_task(async function unmuteTabs_usingButton() { // b) unmuted tabs (tab0) will remain unmuted. // b) media-blocked tabs (tab1, tab2) will get playing. (media not blocked anymore) // However tab4 (muted) which is not multiselected should not be affected. - let tab3MuteAudioBtn = document.getAnonymousElementByAttribute(tab3, "anonid", "soundplaying-icon"); + let tab3MuteAudioBtn = tab3.soundPlayingIcon; await test_mute_tab(tab3, tab3MuteAudioBtn, false); ok(!muted(tab0) && !activeMediaBlocked(tab0), "Tab0 is unmuted and not media-blocked"); @@ -249,7 +249,7 @@ add_task(async function playTabs_usingButton() { // b) unmuted tabs (tab3) will remain unmuted. // b) media-blocked tabs (tab1, tab2) will get playing. (media not blocked anymore) // However tab4 (muted) which is not multiselected should not be affected. - let tab2MuteAudioBtn = document.getAnonymousElementByAttribute(tab2, "anonid", "soundplaying-icon"); + let tab2MuteAudioBtn = tab2.soundPlayingIcon; await test_mute_tab(tab2, tab2MuteAudioBtn, false); ok(!muted(tab0) && !activeMediaBlocked(tab0), "Tab0 is unmuted and not activemedia-blocked"); diff --git a/browser/base/content/test/tabs/browser_new_tab_in_privileged_process_pref.js b/browser/base/content/test/tabs/browser_new_tab_in_privilegedabout_process_pref.js similarity index 69% rename from browser/base/content/test/tabs/browser_new_tab_in_privileged_process_pref.js rename to browser/base/content/test/tabs/browser_new_tab_in_privilegedabout_process_pref.js index 3c2f5e1da7d1..497e0179da52 100644 --- a/browser/base/content/test/tabs/browser_new_tab_in_privileged_process_pref.js +++ b/browser/base/content/test/tabs/browser_new_tab_in_privilegedabout_process_pref.js @@ -5,8 +5,9 @@ */ /** - * Tests to ensure that Activity Stream loads in the privileged content process. - * Normal http web pages should load in the web content process. + * Tests to ensure that Activity Stream loads in the privileged about: + * content process. Normal http web pages should load in the web content + * process. * Ref: Bug 1469072. */ @@ -16,54 +17,29 @@ const ABOUT_NEWTAB = "about:newtab"; const ABOUT_WELCOME = "about:welcome"; const TEST_HTTP = "http://example.org/"; -/** - * Takes a xul:browser and makes sure that the remoteTypes for the browser in - * both the parent and the child processes are the same. - * - * @param {xul:browser} browser - * A xul:browser. - * @param {string} expectedRemoteType - * The expected remoteType value for the browser in both the parent - * and child processes. - * @param {optional string} message - * If provided, shows this string as the message when remoteType values - * do not match. If not present, it uses the default message defined - * in the function parameters. - */ -function checkBrowserRemoteType( - browser, - expectedRemoteType, - message = `Ensures that tab runs in the ${expectedRemoteType} content process.` -) { - // Check both parent and child to ensure that they have the correct remoteType. - is(browser.remoteType, expectedRemoteType, message); - is(browser.messageManager.remoteType, expectedRemoteType, - "Parent and child process should agree on the remote type."); -} - add_task(async function setup() { await SpecialPowers.pushPrefEnv({ set: [ ["browser.newtab.preload", false], ["browser.tabs.remote.separatePrivilegedContentProcess", true], - ["dom.ipc.processCount.privileged", 1], - ["dom.ipc.keepProcessesAlive.privileged", 1], + ["dom.ipc.processCount.privilegedabout", 1], + ["dom.ipc.keepProcessesAlive.privilegedabout", 1], ], }); }); /* - * Test to ensure that the Activity Stream tabs open in privileged content + * Test to ensure that the Activity Stream tabs open in privileged about: content * process. We will first open an about:newtab page that acts as a reference to - * the privileged content process. With the reference, we can then open Activity - * Stream links in a new tab and ensure that the new tab opens in the same - * privileged content process as our reference. + * the privileged about: content process. With the reference, we can then open + * Activity Stream links in a new tab and ensure that the new tab opens in the same + * privileged about: content process as our reference. */ add_task(async function activity_stream_in_privileged_content_process() { Services.ppmm.releaseCachedProcesses(); await BrowserTestUtils.withNewTab(ABOUT_NEWTAB, async function(browser1) { - checkBrowserRemoteType(browser1, E10SUtils.PRIVILEGED_REMOTE_TYPE); + checkBrowserRemoteType(browser1, E10SUtils.PRIVILEGEDABOUT_REMOTE_TYPE); // Note the processID for about:newtab for comparison later. let privilegedPid = browser1.frameLoader.remoteTab.osPid; @@ -81,7 +57,7 @@ add_task(async function activity_stream_in_privileged_content_process() { ]) { await BrowserTestUtils.withNewTab(url, async function(browser2) { is(browser2.frameLoader.remoteTab.osPid, privilegedPid, - "Check that about:newtab tabs are in the same privileged content process."); + "Check that about:newtab tabs are in the same privileged about: content process."); }); } }); @@ -100,25 +76,25 @@ add_task(async function process_switching_through_loading_in_the_same_tab() { checkBrowserRemoteType(browser, E10SUtils.WEB_REMOTE_TYPE); for (let [url, remoteType] of [ - [ABOUT_NEWTAB, E10SUtils.PRIVILEGED_REMOTE_TYPE], - [ABOUT_BLANK, E10SUtils.PRIVILEGED_REMOTE_TYPE], + [ABOUT_NEWTAB, E10SUtils.PRIVILEGEDABOUT_REMOTE_TYPE], + [ABOUT_BLANK, E10SUtils.PRIVILEGEDABOUT_REMOTE_TYPE], [TEST_HTTP, E10SUtils.WEB_REMOTE_TYPE], - [ABOUT_HOME, E10SUtils.PRIVILEGED_REMOTE_TYPE], + [ABOUT_HOME, E10SUtils.PRIVILEGEDABOUT_REMOTE_TYPE], [TEST_HTTP, E10SUtils.WEB_REMOTE_TYPE], - [ABOUT_WELCOME, E10SUtils.PRIVILEGED_REMOTE_TYPE], + [ABOUT_WELCOME, E10SUtils.PRIVILEGEDABOUT_REMOTE_TYPE], [TEST_HTTP, E10SUtils.WEB_REMOTE_TYPE], [ABOUT_BLANK, E10SUtils.WEB_REMOTE_TYPE], - [`${ABOUT_NEWTAB}#foo`, E10SUtils.PRIVILEGED_REMOTE_TYPE], + [`${ABOUT_NEWTAB}#foo`, E10SUtils.PRIVILEGEDABOUT_REMOTE_TYPE], [TEST_HTTP, E10SUtils.WEB_REMOTE_TYPE], - [`${ABOUT_WELCOME}#bar`, E10SUtils.PRIVILEGED_REMOTE_TYPE], + [`${ABOUT_WELCOME}#bar`, E10SUtils.PRIVILEGEDABOUT_REMOTE_TYPE], [TEST_HTTP, E10SUtils.WEB_REMOTE_TYPE], - [`${ABOUT_HOME}#baz`, E10SUtils.PRIVILEGED_REMOTE_TYPE], + [`${ABOUT_HOME}#baz`, E10SUtils.PRIVILEGEDABOUT_REMOTE_TYPE], [TEST_HTTP, E10SUtils.WEB_REMOTE_TYPE], - [`${ABOUT_NEWTAB}?q=foo`, E10SUtils.PRIVILEGED_REMOTE_TYPE], + [`${ABOUT_NEWTAB}?q=foo`, E10SUtils.PRIVILEGEDABOUT_REMOTE_TYPE], [TEST_HTTP, E10SUtils.WEB_REMOTE_TYPE], - [`${ABOUT_WELCOME}?q=bar`, E10SUtils.PRIVILEGED_REMOTE_TYPE], + [`${ABOUT_WELCOME}?q=bar`, E10SUtils.PRIVILEGEDABOUT_REMOTE_TYPE], [TEST_HTTP, E10SUtils.WEB_REMOTE_TYPE], - [`${ABOUT_HOME}?q=baz`, E10SUtils.PRIVILEGED_REMOTE_TYPE], + [`${ABOUT_HOME}?q=baz`, E10SUtils.PRIVILEGEDABOUT_REMOTE_TYPE], [TEST_HTTP, E10SUtils.WEB_REMOTE_TYPE], ]) { BrowserTestUtils.loadURI(browser, url); @@ -139,7 +115,7 @@ add_task(async function process_switching_through_navigation_features() { Services.ppmm.releaseCachedProcesses(); await BrowserTestUtils.withNewTab(ABOUT_NEWTAB, async function(browser) { - checkBrowserRemoteType(browser, E10SUtils.PRIVILEGED_REMOTE_TYPE); + checkBrowserRemoteType(browser, E10SUtils.PRIVILEGEDABOUT_REMOTE_TYPE); // Note the processID for about:newtab for comparison later. let privilegedPid = browser.frameLoader.remoteTab.osPid; @@ -155,20 +131,20 @@ add_task(async function process_switching_through_navigation_features() { }); browser = newTab.linkedBrowser; is(browser.frameLoader.remoteTab.osPid, privilegedPid, - "Check that new tab opened from about:newtab is loaded in privileged content process."); + "Check that new tab opened from about:newtab is loaded in privileged about: content process."); - // Check that reload does not break the privileged content process affinity. + // Check that reload does not break the privileged about: content process affinity. BrowserReload(); await BrowserTestUtils.browserLoaded(browser, false, ABOUT_NEWTAB); is(browser.frameLoader.remoteTab.osPid, privilegedPid, - "Check that about:newtab is still in privileged content process after reload."); + "Check that about:newtab is still in privileged about: content process after reload."); // Load http webpage BrowserTestUtils.loadURI(browser, TEST_HTTP); await BrowserTestUtils.browserLoaded(browser, false, TEST_HTTP); checkBrowserRemoteType(browser, E10SUtils.WEB_REMOTE_TYPE); - // Check that using the history back feature switches back to privileged content process. + // Check that using the history back feature switches back to privileged about: content process. let promiseLocation = BrowserTestUtils.waitForLocationChange(gBrowser, ABOUT_NEWTAB); browser.goBack(); await promiseLocation; @@ -176,7 +152,7 @@ add_task(async function process_switching_through_navigation_features() { // the navigation history data will be available when we do browser.goForward(); await BrowserTestUtils.waitForEvent(newTab, "SSTabRestored"); is(browser.frameLoader.remoteTab.osPid, privilegedPid, - "Check that about:newtab is still in privileged content process after history goBack."); + "Check that about:newtab is still in privileged about: content process after history goBack."); // Check that using the history forward feature switches back to the web content process. promiseLocation = BrowserTestUtils.waitForLocationChange(gBrowser, TEST_HTTP); @@ -193,7 +169,7 @@ add_task(async function process_switching_through_navigation_features() { browser.gotoIndex(0); await promiseLocation; is(browser.frameLoader.remoteTab.osPid, privilegedPid, - "Check that about:newtab is in privileged content process after history gotoIndex."); + "Check that about:newtab is in privileged about: content process after history gotoIndex."); BrowserTestUtils.loadURI(browser, TEST_HTTP); await BrowserTestUtils.browserLoaded(browser, false, TEST_HTTP); @@ -205,7 +181,7 @@ add_task(async function process_switching_through_navigation_features() { }); await BrowserTestUtils.browserLoaded(browser, false, ABOUT_NEWTAB); is(browser.frameLoader.remoteTab.osPid, privilegedPid, - "Check that about:newtab is in privileged content process after location change."); + "Check that about:newtab is in privileged about: content process after location change."); }); Services.ppmm.releaseCachedProcesses(); diff --git a/browser/base/content/test/tabs/browser_privilegedmozilla_process_pref.js b/browser/base/content/test/tabs/browser_privilegedmozilla_process_pref.js new file mode 100644 index 000000000000..654dbb22c30c --- /dev/null +++ b/browser/base/content/test/tabs/browser_privilegedmozilla_process_pref.js @@ -0,0 +1,183 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +/** + * Tests to ensure that Mozilla Privileged Webpages load in the privileged + * mozilla web content process. Normal http web pages should load in the web + * content process. + * Ref: Bug 1539595. + */ + +// High and Low Privilege +const TEST_HIGH1 = "https://example.org/"; +const TEST_HIGH2 = "https://test1.example.org/"; +const TEST_LOW1 = "http://example.org/"; +const TEST_LOW2 = "https://example.com/"; + +add_task(async function setup() { + await SpecialPowers.pushPrefEnv({ + set: [ + ["browser.tabs.remote.separatePrivilegedMozillaWebContentProcess", true], + ["browser.tabs.remote.separatedMozillaDomains", "example.org"], + ["dom.ipc.processCount.privilegedmozilla", 1], + ], + }); +}); + +/* + * Test to ensure that the tabs open in privileged mozilla content process. We + * will first open a page that acts as a reference to the privileged mozilla web + * content process. With the reference, we can then open other links in a new tab + * and ensure that the new tab opens in the same privileged mozilla content process + * as our reference. + */ +add_task(async function webpages_in_privileged_content_process() { + Services.ppmm.releaseCachedProcesses(); + + await BrowserTestUtils.withNewTab(TEST_HIGH1, async function(browser1) { + checkBrowserRemoteType(browser1, E10SUtils.PRIVILEGEDMOZILLA_REMOTE_TYPE); + + // Note the processID for about:newtab for comparison later. + let privilegedPid = browser1.frameLoader.remoteTab.osPid; + + for (let url of [ + TEST_HIGH1, + `${TEST_HIGH1}#foo`, + `${TEST_HIGH1}?q=foo`, + TEST_HIGH2, + `${TEST_HIGH2}#foo`, + `${TEST_HIGH2}?q=foo`, + ]) { + await BrowserTestUtils.withNewTab(url, async function(browser2) { + is(browser2.frameLoader.remoteTab.osPid, privilegedPid, + "Check that privileged pages are in the same privileged mozilla content process."); + }); + } + }); + + Services.ppmm.releaseCachedProcesses(); +}); + +/* + * Test to ensure that a process switch occurs when navigating between normal + * web pages and unprivileged pages in the same tab. + */ +add_task(async function process_switching_through_loading_in_the_same_tab() { + Services.ppmm.releaseCachedProcesses(); + + await BrowserTestUtils.withNewTab(TEST_LOW1, async function(browser) { + checkBrowserRemoteType(browser, E10SUtils.WEB_REMOTE_TYPE); + + for (let [url, remoteType] of [ + [TEST_HIGH1, E10SUtils.PRIVILEGEDMOZILLA_REMOTE_TYPE], + [TEST_LOW1, E10SUtils.WEB_REMOTE_TYPE], + [TEST_HIGH1, E10SUtils.PRIVILEGEDMOZILLA_REMOTE_TYPE], + [TEST_LOW2, E10SUtils.WEB_REMOTE_TYPE], + [TEST_HIGH1, E10SUtils.PRIVILEGEDMOZILLA_REMOTE_TYPE], + [TEST_LOW1, E10SUtils.WEB_REMOTE_TYPE], + [TEST_LOW2, E10SUtils.WEB_REMOTE_TYPE], + [`${TEST_HIGH1}#foo`, E10SUtils.PRIVILEGEDMOZILLA_REMOTE_TYPE], + [TEST_LOW1, E10SUtils.WEB_REMOTE_TYPE], + [`${TEST_HIGH1}#bar`, E10SUtils.PRIVILEGEDMOZILLA_REMOTE_TYPE], + [TEST_LOW2, E10SUtils.WEB_REMOTE_TYPE], + [`${TEST_HIGH1}#baz`, E10SUtils.PRIVILEGEDMOZILLA_REMOTE_TYPE], + [TEST_LOW1, E10SUtils.WEB_REMOTE_TYPE], + [`${TEST_HIGH1}?q=foo`, E10SUtils.PRIVILEGEDMOZILLA_REMOTE_TYPE], + [TEST_LOW2, E10SUtils.WEB_REMOTE_TYPE], + [`${TEST_HIGH1}?q=bar`, E10SUtils.PRIVILEGEDMOZILLA_REMOTE_TYPE], + [TEST_LOW1, E10SUtils.WEB_REMOTE_TYPE], + [`${TEST_HIGH1}?q=baz`, E10SUtils.PRIVILEGEDMOZILLA_REMOTE_TYPE], + [TEST_LOW2, E10SUtils.WEB_REMOTE_TYPE], + ]) { + BrowserTestUtils.loadURI(browser, url); + await BrowserTestUtils.browserLoaded(browser, false, url); + checkBrowserRemoteType(browser, remoteType); + } + }); + + Services.ppmm.releaseCachedProcesses(); +}); + +/* + * Test to ensure that a process switch occurs when navigating between normal + * web pages and privileged pages using the browser's navigation features + * such as history and location change. + */ +add_task(async function process_switching_through_navigation_features() { + Services.ppmm.releaseCachedProcesses(); + + await BrowserTestUtils.withNewTab(TEST_HIGH1, async function(browser) { + checkBrowserRemoteType(browser, E10SUtils.PRIVILEGEDMOZILLA_REMOTE_TYPE); + + // Note the processID for about:newtab for comparison later. + let privilegedPid = browser.frameLoader.remoteTab.osPid; + + // Check that about:newtab opened from JS in about:newtab page is in the same process. + let promiseTabOpened = BrowserTestUtils.waitForNewTab(gBrowser, TEST_HIGH1, true); + await ContentTask.spawn(browser, TEST_HIGH1, uri => { + content.open(uri, "_blank"); + }); + let newTab = await promiseTabOpened; + registerCleanupFunction(async function() { + BrowserTestUtils.removeTab(newTab); + }); + browser = newTab.linkedBrowser; + is(browser.frameLoader.remoteTab.osPid, privilegedPid, + "Check that new tab opened from privileged page is loaded in privileged mozilla content process."); + + // Check that reload does not break the privileged mozilla content process affinity. + BrowserReload(); + await BrowserTestUtils.browserLoaded(browser, false, TEST_HIGH1); + is(browser.frameLoader.remoteTab.osPid, privilegedPid, + "Check that privileged page is still in privileged mozilla content process after reload."); + + // Load http webpage + BrowserTestUtils.loadURI(browser, TEST_LOW1); + await BrowserTestUtils.browserLoaded(browser, false, TEST_LOW1); + checkBrowserRemoteType(browser, E10SUtils.WEB_REMOTE_TYPE); + + // Check that using the history back feature switches back to privileged mozilla content process. + let promiseLocation = BrowserTestUtils.waitForLocationChange(gBrowser, TEST_HIGH1); + browser.goBack(); + await promiseLocation; + // We will need to ensure that the process flip has fully completed so that + // the navigation history data will be available when we do browser.goForward(); + await BrowserTestUtils.waitForEvent(newTab, "SSTabRestored"); + is(browser.frameLoader.remoteTab.osPid, privilegedPid, + "Check that privileged page is still in privileged mozilla content process after history goBack."); + + // Check that using the history forward feature switches back to the web content process. + promiseLocation = BrowserTestUtils.waitForLocationChange(gBrowser, TEST_LOW1); + browser.goForward(); + await promiseLocation; + // We will need to ensure that the process flip has fully completed so that + // the navigation history data will be available when we do browser.gotoIndex(0); + await BrowserTestUtils.waitForEvent(newTab, "SSTabRestored"); + checkBrowserRemoteType(browser, E10SUtils.WEB_REMOTE_TYPE, + "Check that tab runs in the web content process after using history goForward."); + + // Check that goto history index does not break the affinity. + promiseLocation = BrowserTestUtils.waitForLocationChange(gBrowser, TEST_HIGH1); + browser.gotoIndex(0); + await promiseLocation; + is(browser.frameLoader.remoteTab.osPid, privilegedPid, + "Check that privileged page is in privileged mozilla content process after history gotoIndex."); + + BrowserTestUtils.loadURI(browser, TEST_LOW2); + await BrowserTestUtils.browserLoaded(browser, false, TEST_LOW2); + checkBrowserRemoteType(browser, E10SUtils.WEB_REMOTE_TYPE); + + // Check that location change causes a change in process type as well. + await ContentTask.spawn(browser, TEST_HIGH1, uri => { + content.location = uri; + }); + await BrowserTestUtils.browserLoaded(browser, false, TEST_HIGH1); + is(browser.frameLoader.remoteTab.osPid, privilegedPid, + "Check that privileged page is in privileged mozilla content process after location change."); + }); + + Services.ppmm.releaseCachedProcesses(); +}); diff --git a/browser/base/content/test/tabs/browser_tabCloseSpacer.js b/browser/base/content/test/tabs/browser_tabCloseSpacer.js index dbfb4eab7479..4142f8ef80fa 100644 --- a/browser/base/content/test/tabs/browser_tabCloseSpacer.js +++ b/browser/base/content/test/tabs/browser_tabCloseSpacer.js @@ -53,7 +53,7 @@ async function overflowTabs() { function getLastCloseButton() { let lastTab = gBrowser.tabs[gBrowser.tabs.length - 1]; - return document.getAnonymousElementByAttribute(lastTab, "anonid", "close-button"); + return lastTab.closeButton; } function getLastCloseButtonLocation() { diff --git a/browser/base/content/test/general/file_about_child.html b/browser/base/content/test/tabs/file_about_child.html similarity index 100% rename from browser/base/content/test/general/file_about_child.html rename to browser/base/content/test/tabs/file_about_child.html diff --git a/browser/base/content/test/general/file_about_parent.html b/browser/base/content/test/tabs/file_about_parent.html similarity index 100% rename from browser/base/content/test/general/file_about_parent.html rename to browser/base/content/test/tabs/file_about_parent.html diff --git a/browser/base/content/test/tabs/head.js b/browser/base/content/test/tabs/head.js index b51a00072bf1..122f0fe7912a 100644 --- a/browser/base/content/test/tabs/head.js +++ b/browser/base/content/test/tabs/head.js @@ -189,3 +189,80 @@ async function dragAndDrop(tab1, tab2, copy, destWindow = window) { function getUrl(tab) { return tab.linkedBrowser.currentURI.spec; } + +/** + * Takes a xul:browser and makes sure that the remoteTypes for the browser in + * both the parent and the child processes are the same. + * + * @param {xul:browser} browser + * A xul:browser. + * @param {string} expectedRemoteType + * The expected remoteType value for the browser in both the parent + * and child processes. + * @param {optional string} message + * If provided, shows this string as the message when remoteType values + * do not match. If not present, it uses the default message defined + * in the function parameters. + */ +function checkBrowserRemoteType( + browser, + expectedRemoteType, + message = `Ensures that tab runs in the ${expectedRemoteType} content process.` +) { + // Check both parent and child to ensure that they have the correct remoteType. + is(browser.remoteType, expectedRemoteType, message); + is(browser.messageManager.remoteType, expectedRemoteType, + "Parent and child process should agree on the remote type."); +} + +function test_url_for_process_types(url, chromeResult, webContentResult, privilegedAboutContentResult, privilegedMozillaContentResult, extensionProcessResult) { + const CHROME_PROCESS = E10SUtils.NOT_REMOTE; + const WEB_CONTENT_PROCESS = E10SUtils.WEB_REMOTE_TYPE; + const PRIVILEGEDABOUT_CONTENT_PROCESS = E10SUtils.PRIVILEGEDABOUT_REMOTE_TYPE; + const PRIVILEGEDMOZILLA_CONTENT_PROCESS = E10SUtils.PRIVILEGEDMOZILLA_REMOTE_TYPE; + const EXTENSION_PROCESS = E10SUtils.EXTENSION_REMOTE_TYPE; + + is(E10SUtils.canLoadURIInRemoteType(url, /* fission */ false, CHROME_PROCESS), + chromeResult, "Check URL in chrome process."); + is(E10SUtils.canLoadURIInRemoteType(url, /* fission */ false, WEB_CONTENT_PROCESS), + webContentResult, "Check URL in web content process."); + is(E10SUtils.canLoadURIInRemoteType(url, /* fission */ false, PRIVILEGEDABOUT_CONTENT_PROCESS), + privilegedAboutContentResult, "Check URL in privileged about content process."); + is(E10SUtils.canLoadURIInRemoteType(url, /* fission */ false, PRIVILEGEDMOZILLA_CONTENT_PROCESS), + privilegedMozillaContentResult, "Check URL in privileged mozilla content process."); + is(E10SUtils.canLoadURIInRemoteType(url, /* fission */ false, EXTENSION_PROCESS), + extensionProcessResult, "Check URL in extension process."); + + is(E10SUtils.canLoadURIInRemoteType(url + "#foo", /* fission */ false, CHROME_PROCESS), + chromeResult, "Check URL with ref in chrome process."); + is(E10SUtils.canLoadURIInRemoteType(url + "#foo", /* fission */ false, WEB_CONTENT_PROCESS), + webContentResult, "Check URL with ref in web content process."); + is(E10SUtils.canLoadURIInRemoteType(url + "#foo", /* fission */ false, PRIVILEGEDABOUT_CONTENT_PROCESS), + privilegedAboutContentResult, "Check URL with ref in privileged about content process."); + is(E10SUtils.canLoadURIInRemoteType(url + "#foo", /* fission */ false, PRIVILEGEDMOZILLA_CONTENT_PROCESS), + privilegedMozillaContentResult, "Check URL with ref in privileged mozilla content process."); + is(E10SUtils.canLoadURIInRemoteType(url + "#foo", /* fission */ false, EXTENSION_PROCESS), + extensionProcessResult, "Check URL with ref in extension process."); + + is(E10SUtils.canLoadURIInRemoteType(url + "?foo", /* fission */ false, CHROME_PROCESS), + chromeResult, "Check URL with query in chrome process."); + is(E10SUtils.canLoadURIInRemoteType(url + "?foo", /* fission */ false, WEB_CONTENT_PROCESS), + webContentResult, "Check URL with query in web content process."); + is(E10SUtils.canLoadURIInRemoteType(url + "?foo", /* fission */ false, PRIVILEGEDABOUT_CONTENT_PROCESS), + privilegedAboutContentResult, "Check URL with query in privileged about content process."); + is(E10SUtils.canLoadURIInRemoteType(url + "?foo", /* fission */ false, PRIVILEGEDMOZILLA_CONTENT_PROCESS), + privilegedMozillaContentResult, "Check URL with query in privileged mozilla content process."); + is(E10SUtils.canLoadURIInRemoteType(url + "?foo", /* fission */ false, EXTENSION_PROCESS), + extensionProcessResult, "Check URL with query in extension process."); + + is(E10SUtils.canLoadURIInRemoteType(url + "?foo#bar", /* fission */ false, CHROME_PROCESS), + chromeResult, "Check URL with query and ref in chrome process."); + is(E10SUtils.canLoadURIInRemoteType(url + "?foo#bar", /* fission */ false, WEB_CONTENT_PROCESS), + webContentResult, "Check URL with query and ref in web content process."); + is(E10SUtils.canLoadURIInRemoteType(url + "?foo#bar", /* fission */ false, PRIVILEGEDABOUT_CONTENT_PROCESS), + privilegedAboutContentResult, "Check URL with query and ref in privileged about content process."); + is(E10SUtils.canLoadURIInRemoteType(url + "?foo#bar", /* fission */ false, PRIVILEGEDMOZILLA_CONTENT_PROCESS), + privilegedMozillaContentResult, "Check URL with query and ref in privileged mozilla content process."); + is(E10SUtils.canLoadURIInRemoteType(url + "?foo#bar", /* fission */ false, EXTENSION_PROCESS), + extensionProcessResult, "Check URL with query and ref in extension process."); +} diff --git a/browser/base/content/test/general/test_process_flags_chrome.html b/browser/base/content/test/tabs/test_process_flags_chrome.html similarity index 55% rename from browser/base/content/test/general/test_process_flags_chrome.html rename to browser/base/content/test/tabs/test_process_flags_chrome.html index adcbf03403f1..c447d7ffb056 100644 --- a/browser/base/content/test/general/test_process_flags_chrome.html +++ b/browser/base/content/test/tabs/test_process_flags_chrome.html @@ -3,8 +3,8 @@

chrome: test page

-

chrome

-

canremote

-

mustremote

+

chrome

+

canremote

+

mustremote

diff --git a/browser/base/content/test/webrtc/browser_devices_get_user_media_anim.js b/browser/base/content/test/webrtc/browser_devices_get_user_media_anim.js index f966f290576c..a9dbae99a44b 100644 --- a/browser/base/content/test/webrtc/browser_devices_get_user_media_anim.js +++ b/browser/base/content/test/webrtc/browser_devices_get_user_media_anim.js @@ -33,8 +33,7 @@ var gTests = [ let tab = gBrowser.selectedTab; is(tab.getAttribute("sharing"), aSharing, "the tab has the attribute to show the " + aSharing + " icon"); - let icon = - document.getAnonymousElementByAttribute(tab, "anonid", "sharing-icon"); + let icon = tab.sharingIcon; is(window.getComputedStyle(icon).display, "none", "the animated sharing icon of the tab is hidden"); diff --git a/browser/base/jar.mn b/browser/base/jar.mn index 12e6de2aba53..a0217ad91f80 100644 --- a/browser/base/jar.mn +++ b/browser/base/jar.mn @@ -95,6 +95,7 @@ browser.jar: content/browser/contentSearchUI.css (content/contentSearchUI.css) content/browser/tabbrowser.css (content/tabbrowser.css) content/browser/tabbrowser.js (content/tabbrowser.js) + content/browser/tabbrowser-tab.js (content/tabbrowser-tab.js) content/browser/tabbrowser.xml (content/tabbrowser.xml) * content/browser/urlbarBindings.xml (content/urlbarBindings.xml) content/browser/utilityOverlay.js (content/utilityOverlay.js) diff --git a/browser/components/about/AboutRedirector.cpp b/browser/components/about/AboutRedirector.cpp index 82ffd2434d9f..3abd3d91b9c9 100644 --- a/browser/components/about/AboutRedirector.cpp +++ b/browser/components/about/AboutRedirector.cpp @@ -27,7 +27,7 @@ bool AboutRedirector::sAboutLoginsEnabled = false; static const uint32_t ACTIVITY_STREAM_FLAGS = nsIAboutModule::ALLOW_SCRIPT | nsIAboutModule::ENABLE_INDEXED_DB | nsIAboutModule::URI_MUST_LOAD_IN_CHILD | - nsIAboutModule::URI_CAN_LOAD_IN_PRIVILEGED_CHILD | + nsIAboutModule::URI_CAN_LOAD_IN_PRIVILEGEDABOUT_PROCESS | nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT; struct RedirEntry { @@ -59,7 +59,7 @@ static const RedirEntry kRedirMap[] = { nsIAboutModule::HIDE_FROM_ABOUTABOUT}, {"logins", "chrome://browser/content/aboutlogins/aboutLogins.html", nsIAboutModule::ALLOW_SCRIPT | nsIAboutModule::URI_MUST_LOAD_IN_CHILD | - nsIAboutModule::URI_CAN_LOAD_IN_PRIVILEGED_CHILD | + nsIAboutModule::URI_CAN_LOAD_IN_PRIVILEGEDABOUT_PROCESS | nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT}, {"tabcrashed", "chrome://browser/content/aboutTabCrashed.xhtml", nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT | @@ -85,7 +85,7 @@ static const RedirEntry kRedirMap[] = { {"newtab", "about:blank", ACTIVITY_STREAM_FLAGS}, {"welcome", "about:blank", nsIAboutModule::URI_MUST_LOAD_IN_CHILD | - nsIAboutModule::URI_CAN_LOAD_IN_PRIVILEGED_CHILD | + nsIAboutModule::URI_CAN_LOAD_IN_PRIVILEGEDABOUT_PROCESS | nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT | nsIAboutModule::ALLOW_SCRIPT}, {"library", "chrome://browser/content/aboutLibrary.xhtml", @@ -118,7 +118,7 @@ static const RedirEntry kRedirMap[] = { {"protections", "chrome://browser/content/protections.html", nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT | nsIAboutModule::URI_MUST_LOAD_IN_CHILD | nsIAboutModule::ALLOW_SCRIPT | - nsIAboutModule::URI_CAN_LOAD_IN_PRIVILEGED_CHILD}, + nsIAboutModule::URI_CAN_LOAD_IN_PRIVILEGEDABOUT_PROCESS}, }; static nsAutoCString GetAboutModuleName(nsIURI* aURI) { diff --git a/browser/components/aboutlogins/AboutLoginsParent.jsm b/browser/components/aboutlogins/AboutLoginsParent.jsm index 733b7d16cb6c..b73defc322bd 100644 --- a/browser/components/aboutlogins/AboutLoginsParent.jsm +++ b/browser/components/aboutlogins/AboutLoginsParent.jsm @@ -23,16 +23,16 @@ XPCOMUtils.defineLazyGetter(this, "log", () => { const ABOUT_LOGINS_ORIGIN = "about:logins"; const MASTER_PASSWORD_NOTIFICATION_ID = "master-password-login-required"; -const PRIVILEGED_PROCESS_PREF = +const PRIVILEGEDABOUT_PROCESS_PREF = "browser.tabs.remote.separatePrivilegedContentProcess"; -const PRIVILEGED_PROCESS_ENABLED = - Services.prefs.getBoolPref(PRIVILEGED_PROCESS_PREF, false); +const PRIVILEGEDABOUT_PROCESS_ENABLED = + Services.prefs.getBoolPref(PRIVILEGEDABOUT_PROCESS_PREF, false); // When the privileged content process is enabled, we expect about:logins // to load in it. Otherwise, it's in a normal web content process. const EXPECTED_ABOUTLOGINS_REMOTE_TYPE = - PRIVILEGED_PROCESS_ENABLED ? E10SUtils.PRIVILEGED_REMOTE_TYPE - : E10SUtils.DEFAULT_REMOTE_TYPE; + PRIVILEGEDABOUT_PROCESS_ENABLED ? E10SUtils.PRIVILEGEDABOUT_REMOTE_TYPE + : E10SUtils.DEFAULT_REMOTE_TYPE; const isValidLogin = login => { return !(login.hostname || "").startsWith("chrome://"); diff --git a/browser/components/extensions/parent/ext-menus.js b/browser/components/extensions/parent/ext-menus.js index 3366a85cd050..cf514818a357 100644 --- a/browser/components/extensions/parent/ext-menus.js +++ b/browser/components/extensions/parent/ext-menus.js @@ -1065,8 +1065,11 @@ const menuTracker = { gMenuBuilder.build({menu, tab, pageUrl, inToolsMenu: true}); } if (menu.id === "tabContextMenu") { - const trigger = menu.triggerNode; - const tab = trigger.localName === "tab" ? trigger : tabTracker.activeTab; + let trigger = menu.triggerNode; + while (trigger && trigger.localName != "tab") { + trigger = trigger.parentNode; + } + const tab = trigger || tabTracker.activeTab; const pageUrl = tab.linkedBrowser.currentURI.spec; gMenuBuilder.build({menu, tab, pageUrl, onTab: true}); } diff --git a/browser/components/newtab/AboutNewTabService.jsm b/browser/components/newtab/AboutNewTabService.jsm index 8995d82d543b..015954112cc9 100644 --- a/browser/components/newtab/AboutNewTabService.jsm +++ b/browser/components/newtab/AboutNewTabService.jsm @@ -26,18 +26,18 @@ const BASE_URL = "resource://activity-stream/"; const ACTIVITY_STREAM_PAGES = new Set(["home", "newtab", "welcome"]); const IS_MAIN_PROCESS = Services.appinfo.processType === Services.appinfo.PROCESS_TYPE_DEFAULT; -const IS_PRIVILEGED_PROCESS = Services.appinfo.remoteType === E10SUtils.PRIVILEGED_REMOTE_TYPE; +const IS_PRIVILEGED_PROCESS = Services.appinfo.remoteType === E10SUtils.PRIVILEGEDABOUT_REMOTE_TYPE; const IS_RELEASE_OR_BETA = AppConstants.RELEASE_OR_BETA; -const PREF_SEPARATE_PRIVILEGED_CONTENT_PROCESS = "browser.tabs.remote.separatePrivilegedContentProcess"; +const PREF_SEPARATE_PRIVILEGEDABOUT_CONTENT_PROCESS = "browser.tabs.remote.separatePrivilegedContentProcess"; const PREF_ACTIVITY_STREAM_PRERENDER_ENABLED = "browser.newtabpage.activity-stream.prerender"; const PREF_ACTIVITY_STREAM_DEBUG = "browser.newtabpage.activity-stream.debug"; function AboutNewTabService() { Services.obs.addObserver(this, TOPIC_APP_QUIT); Services.obs.addObserver(this, TOPIC_LOCALES_CHANGE); - Services.prefs.addObserver(PREF_SEPARATE_PRIVILEGED_CONTENT_PROCESS, this); + Services.prefs.addObserver(PREF_SEPARATE_PRIVILEGEDABOUT_CONTENT_PROCESS, this); Services.prefs.addObserver(PREF_ACTIVITY_STREAM_PRERENDER_ENABLED, this); if (!IS_RELEASE_OR_BETA) { Services.prefs.addObserver(PREF_ACTIVITY_STREAM_DEBUG, this); @@ -93,7 +93,7 @@ AboutNewTabService.prototype = { _activityStreamPrerender: false, _activityStreamPath: "", _activityStreamDebug: false, - _privilegedContentProcess: false, + _privilegedAboutContentProcess: false, _overridden: false, willNotifyUser: false, @@ -106,8 +106,8 @@ AboutNewTabService.prototype = { observe(subject, topic, data) { switch (topic) { case "nsPref:changed": - if (data === PREF_SEPARATE_PRIVILEGED_CONTENT_PROCESS) { - this._privilegedContentProcess = Services.prefs.getBoolPref(PREF_SEPARATE_PRIVILEGED_CONTENT_PROCESS); + if (data === PREF_SEPARATE_PRIVILEGEDABOUT_CONTENT_PROCESS) { + this._privilegedAboutContentProcess = Services.prefs.getBoolPref(PREF_SEPARATE_PRIVILEGEDABOUT_CONTENT_PROCESS); this.updatePrerenderedPath(); this.notifyChange(); } else if (data === PREF_ACTIVITY_STREAM_PRERENDER_ENABLED) { @@ -216,7 +216,7 @@ AboutNewTabService.prototype = { } else { this._activityStreamEnabled = false; } - this._privilegedContentProcess = Services.prefs.getBoolPref(PREF_SEPARATE_PRIVILEGED_CONTENT_PROCESS); + this._privilegedAboutContentProcess = Services.prefs.getBoolPref(PREF_SEPARATE_PRIVILEGEDABOUT_CONTENT_PROCESS); this._activityStreamPrerender = Services.prefs.getBoolPref(PREF_ACTIVITY_STREAM_PRERENDER_ENABLED); if (!IS_RELEASE_OR_BETA) { this._activityStreamDebug = Services.prefs.getBoolPref(PREF_ACTIVITY_STREAM_DEBUG, false); @@ -233,7 +233,7 @@ AboutNewTabService.prototype = { // Debug files are specially packaged in a non-localized directory, but with // dynamic script loading, localized debug is supported. this._activityStreamPath = `${this._activityStreamDebug && - !this._privilegedContentProcess ? "static" : this.activityStreamLocale}/`; + !this._privilegedAboutContentProcess ? "static" : this.activityStreamLocale}/`; }, /* @@ -253,8 +253,8 @@ AboutNewTabService.prototype = { "activity-stream", this._activityStreamPrerender ? "-prerendered" : "", // Debug version loads dev scripts but noscripts separately loads scripts - this._activityStreamDebug && !this._privilegedContentProcess ? "-debug" : "", - this._privilegedContentProcess ? "-noscripts" : "", + this._activityStreamDebug && !this._privilegedAboutContentProcess ? "-debug" : "", + this._privilegedAboutContentProcess ? "-noscripts" : "", ".html", ].join(""); }, @@ -351,7 +351,7 @@ AboutNewTabService.prototype = { } Services.obs.removeObserver(this, TOPIC_APP_QUIT); Services.obs.removeObserver(this, TOPIC_LOCALES_CHANGE); - Services.prefs.removeObserver(PREF_SEPARATE_PRIVILEGED_CONTENT_PROCESS, this); + Services.prefs.removeObserver(PREF_SEPARATE_PRIVILEGEDABOUT_CONTENT_PROCESS, this); Services.prefs.removeObserver(PREF_ACTIVITY_STREAM_PRERENDER_ENABLED, this); if (!IS_RELEASE_OR_BETA) { Services.prefs.removeObserver(PREF_ACTIVITY_STREAM_DEBUG, this); diff --git a/browser/components/sessionstore/TabState.jsm b/browser/components/sessionstore/TabState.jsm index 054bf7a68f9c..294514c223fb 100644 --- a/browser/components/sessionstore/TabState.jsm +++ b/browser/components/sessionstore/TabState.jsm @@ -192,5 +192,15 @@ var TabStateInternal = { tabData[key] = value; } } + + // [Bug 1554512] + // If the latest scroll position is on the top, we will delete scroll entry. + // When scroll entry is deleted in TabStateCache, it cannot be updated. + // To prevent losing the scroll position, we need to add a handing here. + if (tabData.scroll) { + if (!data.scroll) { + delete tabData.scroll; + } + } }, }; diff --git a/browser/components/tests/browser/browser_initial_tab_remoteType.js b/browser/components/tests/browser/browser_initial_tab_remoteType.js index b9e1a1bea82d..820f0a6c63ba 100644 --- a/browser/components/tests/browser/browser_initial_tab_remoteType.js +++ b/browser/components/tests/browser/browser_initial_tab_remoteType.js @@ -9,17 +9,17 @@ "use strict"; -const PRIVILEGED_PROCESS_PREF = +const PRIVILEGEDABOUT_PROCESS_PREF = "browser.tabs.remote.separatePrivilegedContentProcess"; -const PRIVILEGED_PROCESS_ENABLED = - Services.prefs.getBoolPref(PRIVILEGED_PROCESS_PREF); +const PRIVILEGEDABOUT_PROCESS_ENABLED = + Services.prefs.getBoolPref(PRIVILEGEDABOUT_PROCESS_PREF); const REMOTE_BROWSER_SHOWN = "remote-browser-shown"; // When the privileged content process is enabled, we expect about:home // to load in it. Otherwise, it's in a normal web content process. const EXPECTED_ABOUTHOME_REMOTE_TYPE = - PRIVILEGED_PROCESS_ENABLED ? E10SUtils.PRIVILEGED_REMOTE_TYPE + PRIVILEGEDABOUT_PROCESS_ENABLED ? E10SUtils.PRIVILEGEDABOUT_REMOTE_TYPE : E10SUtils.DEFAULT_REMOTE_TYPE; /** diff --git a/browser/components/uitour/UITour.jsm b/browser/components/uitour/UITour.jsm index b23d07115408..e26ec2e81dcd 100644 --- a/browser/components/uitour/UITour.jsm +++ b/browser/components/uitour/UITour.jsm @@ -172,9 +172,7 @@ var UITour = { ["selectedTabIcon", { query: (aDocument) => { let selectedtab = aDocument.defaultView.gBrowser.selectedTab; - let element = aDocument.getAnonymousElementByAttribute(selectedtab, - "anonid", - "tab-icon-image"); + let element = selectedtab.iconImage; if (!element || !UITour.isElementVisible(element)) { return null; } diff --git a/build/mach_bootstrap.py b/build/mach_bootstrap.py index 6bf0d9963d55..ca19d7b7a884 100644 --- a/build/mach_bootstrap.py +++ b/build/mach_bootstrap.py @@ -388,6 +388,9 @@ class ImportHook(object): def __call__(self, name, globals=None, locals=None, fromlist=None, level=-1): + if sys.version_info[0] >= 3 and level < 0: + level = 0 + # name might be a relative import. Instead of figuring out what that # resolves to, which is complex, just rely on the real import. # Since we don't know the full module name, we can't check sys.modules, diff --git a/devtools/client/inspector/rules/models/element-style.js b/devtools/client/inspector/rules/models/element-style.js index cc46102df3b6..3fec3e820d99 100644 --- a/devtools/client/inspector/rules/models/element-style.js +++ b/devtools/client/inspector/rules/models/element-style.js @@ -288,55 +288,10 @@ class ElementStyle { * Optional pseudo-element for which to restrict marking CSS declarations as * overridden. */ - /* eslint-disable complexity */ updateDeclarations(pseudo = "") { - // Gather all the text properties applied by these rules, ordered - // from more- to less-specific. Text properties from keyframes rule are - // excluded from being marked as overridden since a number of criteria such - // as time, and animation overlay are required to be check in order to - // determine if the property is overridden. - const textProps = []; - - for (const rule of this.rules) { - // Skip @keyframes rules - if (rule.keyframes) { - continue; - } - - // Style rules must be considered only when they have selectors that match the node. - // When renaming a selector, the unmatched rule lingers in the Rule view, but it no - // longer matches the node. This strict check avoids accidentally causing - // declarations to be overridden in the remaining matching rules. - const isStyleRule = rule.pseudoElement === "" && rule.matchedSelectors.length > 0; - - // Style rules for pseudo-elements must always be considered, regardless if their - // selector matches the node. As a convenience, declarations in rules for - // pseudo-elements show up in a separate Pseudo-elements accordion when selecting - // the host node (instead of the pseudo-element node directly, which is sometimes - // impossible, for example with ::selection or ::first-line). - // Loosening the strict check on matched selectors ensures these declarations - // participate in the algorithm below to mark them as overridden. - const isPseudoElementRule = rule.pseudoElement !== "" && - rule.pseudoElement === pseudo; - - const isElementStyle = rule.domRule.type === ELEMENT_STYLE; - - const filterCondition = pseudo === "" - ? (isStyleRule || isElementStyle) - : isPseudoElementRule; - - // First, gather all relevant CSS declarations (aka TextProperty instances). - if (filterCondition) { - for (const textProp of rule.textProps.slice(0).reverse()) { - if (textProp.enabled) { - textProps.push(textProp); - } - } - } - } - - // Gather all the computed properties applied by those text - // properties. + // Gather all text properties applicable to the selected element or pseudo-element. + const textProps = this._getDeclarations(pseudo); + // Gather all the computed properties applied by those text properties. let computedProps = []; for (const textProp of textProps) { computedProps = computedProps.concat(textProp.computed); @@ -418,7 +373,71 @@ class ElementStyle { } } } - /* eslint-enable complexity */ + + /** + * Helper for |this.updateDeclarations()| to mark CSS declarations as overridden. + * + * Returns an array of CSS declarations (aka TextProperty instances) from all rules + * applicable to the selected element ordered from more- to less-specific. + * + * If a pseudo-element type is given, restrict the result only to declarations + * applicable to that pseudo-element. + * + * NOTE: this method skips CSS declarations in @keyframes rules because a number of + * criteria such as time and animation delay need to be checked in order to determine + * if the property is overridden at runtime. + * + * @param {String} pseudo + * Optional pseudo-element for which to restrict marking CSS declarations as + * overridden. If omitted, only declarations for regular style rules are + * returned (no pseudo-element style rules). + * + * @return {Array} + * Array of TextProperty instances. + */ + _getDeclarations(pseudo = "") { + const textProps = []; + + for (const rule of this.rules) { + // Skip @keyframes rules + if (rule.keyframes) { + continue; + } + + // Style rules must be considered only when they have selectors that match the node. + // When renaming a selector, the unmatched rule lingers in the Rule view, but it no + // longer matches the node. This strict check avoids accidentally causing + // declarations to be overridden in the remaining matching rules. + const isStyleRule = rule.pseudoElement === "" && rule.matchedSelectors.length > 0; + + // Style rules for pseudo-elements must always be considered, regardless if their + // selector matches the node. As a convenience, declarations in rules for + // pseudo-elements show up in a separate Pseudo-elements accordion when selecting + // the host node (instead of the pseudo-element node directly, which is sometimes + // impossible, for example with ::selection or ::first-line). + // Loosening the strict check on matched selectors ensures these declarations + // participate in the algorithm below to mark them as overridden. + const isPseudoElementRule = rule.pseudoElement !== "" && + rule.pseudoElement === pseudo; + + const isElementStyle = rule.domRule.type === ELEMENT_STYLE; + + const filterCondition = pseudo === "" + ? (isStyleRule || isElementStyle) + : isPseudoElementRule; + + // Collect all relevant CSS declarations (aka TextProperty instances). + if (filterCondition) { + for (const textProp of rule.textProps.slice(0).reverse()) { + if (textProp.enabled) { + textProps.push(textProp); + } + } + } + } + + return textProps; + } /** * Adds a new declaration to the rule. diff --git a/devtools/client/inspector/rules/test/browser.ini b/devtools/client/inspector/rules/test/browser.ini index e0fd5578d321..cfbe6e233dbb 100644 --- a/devtools/client/inspector/rules/test/browser.ini +++ b/devtools/client/inspector/rules/test/browser.ini @@ -199,6 +199,7 @@ skip-if = os == 'linux' # focusEditableField times out consistently on linux. [browser_rules_highlight-used-fonts.js] [browser_rules_inactive_css_flexbox.js] [browser_rules_inactive_css_grid.js] +[browser_rules_inactive_css_inline.js] [browser_rules_inherited-properties_01.js] [browser_rules_inherited-properties_02.js] [browser_rules_inherited-properties_03.js] diff --git a/devtools/client/inspector/rules/test/browser_rules_inactive_css_inline.js b/devtools/client/inspector/rules/test/browser_rules_inactive_css_inline.js new file mode 100644 index 000000000000..7abbea3816e5 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_inactive_css_inline.js @@ -0,0 +1,76 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test css properties that are inactive on block-level elements. + +const TEST_URI = ` + +

browser_rules_inactive_css_inline.js

+
Block
+ + +
A table cell
+
Inline flex element
+`; + +const TEST_DATA = [ + { + selector: "h1", + inactiveDeclarations: [ + { + declaration: { "vertical-align": "text-bottom" }, + ruleIndex: 0, + }, + ], + }, + { + selector: "#block", + inactiveDeclarations: [ + { + declaration: { "vertical-align": "sub" }, + ruleIndex: 1, + }, + ], + }, + { + selector: "td", + activeDeclarations: [ + { + declarations: { "vertical-align": "super" }, + ruleIndex: 1, + }, + ], + }, + { + selector: "#flex", + activeDeclarations: [ + { + declarations: { "vertical-align": "text-bottom" }, + ruleIndex: 1, + }, + ], + }, +]; + +add_task(async function() { + await pushPref("devtools.inspector.inactive.css.enabled", true); + await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + const { inspector, view } = await openRuleView(); + + await runInactiveCSSTests(view, inspector, TEST_DATA); +}); diff --git a/devtools/client/inspector/shared/tooltips-overlay.js b/devtools/client/inspector/shared/tooltips-overlay.js index 4b965179757f..6b518619585d 100644 --- a/devtools/client/inspector/shared/tooltips-overlay.js +++ b/devtools/client/inspector/shared/tooltips-overlay.js @@ -35,6 +35,7 @@ loader.lazyRequireGetter(this, "setVariableTooltip", "devtools/client/shared/widgets/tooltip/VariableTooltipHelper", true); loader.lazyRequireGetter(this, "InactiveCssTooltipHelper", "devtools/client/shared/widgets/tooltip/inactive-css-tooltip-helper", false); +loader.lazyRequireGetter(this, "Telemetry", "devtools/client/shared/telemetry", false); const PREF_IMAGE_TOOLTIP_SIZE = "devtools.inspector.imagePreviewTooltipSize"; @@ -44,6 +45,9 @@ const TOOLTIP_FONTFAMILY_TYPE = "font-family"; const TOOLTIP_INACTIVE_CSS = "inactive-css"; const TOOLTIP_VARIABLE_TYPE = "variable"; +// Telemetry +const TOOLTIP_SHOWN_SCALAR = "devtools.tooltip.shown"; + /** * Manages all tooltips in the style-inspector. * @@ -53,6 +57,7 @@ const TOOLTIP_VARIABLE_TYPE = "variable"; function TooltipsOverlay(view) { this.view = view; this._instances = new Map(); + this.telemetry = new Telemetry(); this._onNewSelection = this._onNewSelection.bind(this); this.view.inspector.selection.on("new-node-front", this._onNewSelection); @@ -253,6 +258,9 @@ TooltipsOverlay.prototype = { await setBrokenImageTooltip(this.getTooltip("previewTooltip"), this.view.inspector.panelDoc); } + + this.sendOpenScalarToTelemetry(type); + return true; } @@ -261,6 +269,8 @@ TooltipsOverlay.prototype = { const nodeFront = inspector.selection.nodeFront; await this._setFontPreviewTooltip(font, nodeFront); + this.sendOpenScalarToTelemetry(type); + if (nodeInfo.type === VIEW_NODE_FONT_TYPE) { // If the hovered element is on the font family span, anchor // the tooltip on the whole property value instead. @@ -272,6 +282,9 @@ TooltipsOverlay.prototype = { if (type === TOOLTIP_VARIABLE_TYPE && nodeInfo.value.value.startsWith("--")) { const variable = nodeInfo.value.variable; await this._setVariablePreviewTooltip(variable); + + this.sendOpenScalarToTelemetry(type); + return true; } @@ -320,12 +333,25 @@ TooltipsOverlay.prototype = { await this.inactiveCssTooltipHelper.setContent( nodeInfo.value, this.getTooltip("interactiveTooltip")); + + this.sendOpenScalarToTelemetry(type); + return true; } return false; }, + /** + * Send a telemetry Scalar showing that a tooltip of `type` has been opened. + * + * @param {String} type + * The node type from `devtools/client/inspector/shared/node-types`. + */ + sendOpenScalarToTelemetry(type) { + this.telemetry.keyedScalarAdd(TOOLTIP_SHOWN_SCALAR, type, 1); + }, + /** * Set the content of the preview tooltip to display an image preview. The image URL can * be relative, a call will be made to the debuggee to retrieve the image content as an @@ -414,6 +440,7 @@ TooltipsOverlay.prototype = { this.view.inspector.selection.off("new-node-front", this._onNewSelection); this.view = null; + this.telemetry = null; this._isDestroyed = true; }, diff --git a/devtools/client/responsive.html/browser/swap.js b/devtools/client/responsive.html/browser/swap.js index 74eccd2ea289..ee4c608dadfd 100644 --- a/devtools/client/responsive.html/browser/swap.js +++ b/devtools/client/responsive.html/browser/swap.js @@ -141,7 +141,7 @@ function swapToInnerBrowser({ tab, containerURL, getInnerBrowser }) { // Bug 1510806 has been filed to fix this properly, by making RDM resilient // to process flips. if (mustChangeProcess && - tab.linkedBrowser.remoteType == "privileged") { + tab.linkedBrowser.remoteType == "privilegedabout") { debug(`Tab must flip away from the privileged content process ` + `on navigation`); gBrowser.updateBrowserRemoteness(tab.linkedBrowser, { diff --git a/devtools/client/shared/widgets/tooltip/inactive-css-tooltip-helper.js b/devtools/client/shared/widgets/tooltip/inactive-css-tooltip-helper.js index 8a1e689ea5c1..1284e4cea507 100644 --- a/devtools/client/shared/widgets/tooltip/inactive-css-tooltip-helper.js +++ b/devtools/client/shared/widgets/tooltip/inactive-css-tooltip-helper.js @@ -43,43 +43,45 @@ class InactiveCssTooltipHelper { // Size the content. tooltip.setContentSize({width: 275, height: Infinity}); } -/** - * Get the template that the Fluent string will be merged with. This template - * looks something like this but there is a variable amount of properties in the - * fix section: - * - *
- *

- * - *

- *

- * - * - * - *

- *
- * - * @param {Object} data - * An object in the following format: { - * fixId: "inactive-css-not-grid-item-fix", // Fluent id containing the - * // Inactive CSS fix. - * msgId: "inactive-css-not-grid-item", // Fluent id containing the - * // Inactive CSS message. - * numFixProps: 2, // Number of properties in the fix section of the - * // tooltip. - * property: "color", // Property name - * } - * @param {HTMLTooltip} tooltip - * The tooltip we are targetting. - */ + + /** + * Get the template that the Fluent string will be merged with. This template + * looks something like this but there is a variable amount of properties in the + * fix section: + * + *
+ *

+ * + *

+ *

+ * + * + * + *

+ *
+ * + * @param {Object} data + * An object in the following format: { + * fixId: "inactive-css-not-grid-item-fix", // Fluent id containing the + * // Inactive CSS fix. + * msgId: "inactive-css-not-grid-item", // Fluent id containing the + * // Inactive CSS message. + * numFixProps: 2, // Number of properties in the fix section of the + * // tooltip. + * property: "color", // Property name + * } + * @param {HTMLTooltip} tooltip + * The tooltip we are targetting. + */ getTemplate(data, tooltip) { const XHTML_NS = "http://www.w3.org/1999/xhtml"; const { fixId, msgId, numFixProps, property } = data; const { doc } = tooltip; this._currentTooltip = tooltip; - this._currentUrl = `https://developer.mozilla.org/docs/Web/CSS/${property}`; + this._currentUrl = `https://developer.mozilla.org/docs/Web/CSS/${property}` + + `?utm_source=devtools&utm_medium=inspector-inactive-css`; const templateNode = doc.createElementNS(XHTML_NS, "template"); diff --git a/devtools/server/actors/utils/inactive-property-helper.js b/devtools/server/actors/utils/inactive-property-helper.js index 386feefe3ba1..86d7e7aa5764 100644 --- a/devtools/server/actors/utils/inactive-property-helper.js +++ b/devtools/server/actors/utils/inactive-property-helper.js @@ -143,9 +143,7 @@ class InactivePropertyHelper { const isFirstLetter = selectorText && selectorText.includes("::first-letter"); const isFirstLine = selectorText && selectorText.includes("::first-line"); - const isInlineLevel = this.checkStyle("display", ["inline", "table-cell"]); - - return !isInlineLevel && !isFirstLetter && !isFirstLine; + return !this.isInlineLevel() && !isFirstLetter && !isFirstLine; }, fixId: "inactive-css-not-inline-or-tablecell-fix", msgId: "inactive-css-not-inline-or-tablecell", @@ -299,6 +297,24 @@ class InactivePropertyHelper { return values.some(value => this.style[propName] === value); } + /** + * Check if the current node is an inline-level box. + */ + isInlineLevel() { + return this.checkStyle("display", [ + "inline", + "inline-block", + "inline-table", + "inline-flex", + "inline-grid", + "table-cell", + "table-row", + "table-row-group", + "table-header-group", + "table-footer-group", + ]); + } + /** * Check if the current node is a flex container i.e. a node that has a style * of `display:flex` or `display:inline-flex`. diff --git a/docshell/base/nsAboutRedirector.cpp b/docshell/base/nsAboutRedirector.cpp index d8ce14908566..8eb06b5e9922 100644 --- a/docshell/base/nsAboutRedirector.cpp +++ b/docshell/base/nsAboutRedirector.cpp @@ -91,7 +91,7 @@ static const RedirEntry kRedirMap[] = { nsIAboutModule::ALLOW_SCRIPT | nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT | nsIAboutModule::URI_MUST_LOAD_IN_CHILD | - nsIAboutModule::URI_CAN_LOAD_IN_PRIVILEGED_CHILD}, + nsIAboutModule::URI_CAN_LOAD_IN_PRIVILEGEDABOUT_PROCESS}, {"mozilla", "chrome://global/content/mozilla.xhtml", nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT}, {"neterror", "chrome://global/content/netError.xhtml", diff --git a/dom/base/ChromeUtils.cpp b/dom/base/ChromeUtils.cpp index dede365a1d64..ad4b6ca4f961 100644 --- a/dom/base/ChromeUtils.cpp +++ b/dom/base/ChromeUtils.cpp @@ -710,8 +710,9 @@ already_AddRefed ChromeUtils::RequestProcInfo(GlobalObject& aGlobal, type = mozilla::ProcType::File; } else if (processType.EqualsLiteral(EXTENSION_REMOTE_TYPE)) { type = mozilla::ProcType::Extension; - } else if (processType.EqualsLiteral(PRIVILEGED_REMOTE_TYPE)) { - type = mozilla::ProcType::Privileged; + } else if (processType.EqualsLiteral( + PRIVILEGEDABOUT_REMOTE_TYPE)) { + type = mozilla::ProcType::PrivilegedAbout; } else if (processType.EqualsLiteral( LARGE_ALLOCATION_REMOTE_TYPE)) { type = mozilla::ProcType::WebLargeAllocation; diff --git a/dom/base/PopupBlocker.cpp b/dom/base/PopupBlocker.cpp index 562812a10fad..b8e2b90db2b0 100644 --- a/dom/base/PopupBlocker.cpp +++ b/dom/base/PopupBlocker.cpp @@ -185,7 +185,7 @@ PopupBlocker::PopupControlState PopupBlocker::GetEventPopupControlState( case eBasicEventClass: // For these following events only allow popups if they're // triggered while handling user input. See - // PresShell::EventHandler::PrepareToDispatchEvent() for details. + // EventStateManager::IsUserInteractionEvent() for details. if (EventStateManager::IsHandlingUserInput()) { abuse = PopupBlocker::openBlocked; switch (aEvent->mMessage) { @@ -207,7 +207,7 @@ PopupBlocker::PopupControlState PopupBlocker::GetEventPopupControlState( case eEditorInputEventClass: // For this following event only allow popups if it's triggered // while handling user input. See - // PresShell::EventHandler::PrepareToDispatchEvent() for details. + // EventStateManager::IsUserInteractionEvent() for details. if (EventStateManager::IsHandlingUserInput()) { abuse = PopupBlocker::openBlocked; switch (aEvent->mMessage) { @@ -224,7 +224,7 @@ PopupBlocker::PopupControlState PopupBlocker::GetEventPopupControlState( case eInputEventClass: // For this following event only allow popups if it's triggered // while handling user input. See - // PresShell::EventHandler::PrepareToDispatchEvent() for details. + // EventStateManager::IsUserInteractionEvent() for details. if (EventStateManager::IsHandlingUserInput()) { abuse = PopupBlocker::openBlocked; switch (aEvent->mMessage) { @@ -370,7 +370,7 @@ PopupBlocker::PopupControlState PopupBlocker::GetEventPopupControlState( case eFormEventClass: // For these following events only allow popups if they're // triggered while handling user input. See - // PresShell::EventHandler::PrepareToDispatchEvent() for details. + // EventStateManager::IsUserInteractionEvent() for details. if (EventStateManager::IsHandlingUserInput()) { abuse = PopupBlocker::openBlocked; switch (aEvent->mMessage) { diff --git a/dom/chrome-webidl/ChromeUtils.webidl b/dom/chrome-webidl/ChromeUtils.webidl index de9f5103676d..0b44dfe46179 100644 --- a/dom/chrome-webidl/ChromeUtils.webidl +++ b/dom/chrome-webidl/ChromeUtils.webidl @@ -415,7 +415,7 @@ enum ProcType { "web", "file", "extension", - "privileged", + "privilegedabout", "webLargeAllocation", "gpu", "rdd", diff --git a/dom/events/EventListenerManager.cpp b/dom/events/EventListenerManager.cpp index 27c9aa1fd1a1..840556c6eb3b 100644 --- a/dom/events/EventListenerManager.cpp +++ b/dom/events/EventListenerManager.cpp @@ -1141,8 +1141,11 @@ void EventListenerManager::HandleEventInternal(nsPresContext* aPresContext, aEvent->PreventDefault(); } + Maybe userInputStatePusher; Maybe popupStatePusher; if (mIsMainThreadELM) { + userInputStatePusher.emplace( + EventStateManager::IsUserInteractionEvent(aEvent), aEvent); popupStatePusher.emplace( PopupBlocker::GetEventPopupControlState(aEvent, *aDOMEvent)); } diff --git a/dom/events/EventStateManager.cpp b/dom/events/EventStateManager.cpp index eb202fab8270..0248985a4091 100644 --- a/dom/events/EventStateManager.cpp +++ b/dom/events/EventStateManager.cpp @@ -1558,8 +1558,7 @@ void EventStateManager::FireContextClick() { } } - Document* doc = mGestureDownContent->GetComposedDoc(); - AutoHandlingUserInputStatePusher userInpStatePusher(true, &event, doc); + AutoHandlingUserInputStatePusher userInpStatePusher(true, &event); // dispatch to DOM EventDispatcher::Dispatch(mGestureDownContent, mPresContext, &event, @@ -4042,6 +4041,39 @@ class MOZ_STACK_CLASS ESMEventCB : public EventDispatchingCallback { nsCOMPtr mTarget; }; +/*static*/ +bool EventStateManager::IsUserInteractionEvent(const WidgetEvent* aEvent) { + if (!aEvent->IsTrusted()) { + return false; + } + + switch (aEvent->mMessage) { + // eKeyboardEventClass + case eKeyPress: + case eKeyDown: + case eKeyUp: + // Not all keyboard events are treated as user input, so that popups + // can't be opened, fullscreen mode can't be started, etc at + // unexpected time. + return aEvent->AsKeyboardEvent()->CanTreatAsUserInput(); + // eBasicEventClass + case eFormChange: + // eMouseEventClass + case eMouseClick: + case eMouseDown: + case eMouseUp: + // ePointerEventClass + case ePointerDown: + case ePointerUp: + // eTouchEventClass + case eTouchStart: + case eTouchEnd: + return true; + default: + return false; + } +} + /*static*/ bool EventStateManager::IsHandlingUserInput() { return sUserInputEventDepth > 0; @@ -6257,36 +6289,13 @@ void EventStateManager::Prefs::Init() { /******************************************************************/ AutoHandlingUserInputStatePusher::AutoHandlingUserInputStatePusher( - bool aIsHandlingUserInput, WidgetEvent* aEvent, Document* aDocument) + bool aIsHandlingUserInput, WidgetEvent* aEvent) : mMessage(aEvent ? aEvent->mMessage : eVoidEvent), mIsHandlingUserInput(aIsHandlingUserInput) { if (!aIsHandlingUserInput) { return; } EventStateManager::StartHandlingUserInput(mMessage); - if (mMessage == eMouseDown) { - PresShell::ReleaseCapturingContent(); - PresShell::AllowMouseCapture(true); - } - if (!aDocument || !aEvent || !aEvent->IsTrusted()) { - return; - } - if (NeedsToResetFocusManagerMouseButtonHandlingState()) { - nsFocusManager* fm = nsFocusManager::GetFocusManager(); - NS_ENSURE_TRUE_VOID(fm); - // If it's in modal state, mouse button event handling may be nested. - // E.g., a modal dialog is opened at mousedown or mouseup event handler - // and the dialog is clicked. Therefore, we should store current - // mouse button event handling document if nsFocusManager already has it. - mMouseButtonEventHandlingDocument = - fm->SetMouseButtonHandlingDocument(aDocument); - } - if (NeedsToUpdateCurrentMouseBtnState()) { - WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent(); - if (mouseEvent) { - EventStateManager::sCurrentMouseBtn = mouseEvent->mButton; - } - } } AutoHandlingUserInputStatePusher::~AutoHandlingUserInputStatePusher() { @@ -6294,18 +6303,6 @@ AutoHandlingUserInputStatePusher::~AutoHandlingUserInputStatePusher() { return; } EventStateManager::StopHandlingUserInput(mMessage); - if (mMessage == eMouseDown) { - PresShell::AllowMouseCapture(false); - } - if (NeedsToResetFocusManagerMouseButtonHandlingState()) { - nsFocusManager* fm = nsFocusManager::GetFocusManager(); - NS_ENSURE_TRUE_VOID(fm); - nsCOMPtr handlingDocument = - fm->SetMouseButtonHandlingDocument(mMouseButtonEventHandlingDocument); - } - if (NeedsToUpdateCurrentMouseBtnState()) { - EventStateManager::sCurrentMouseBtn = MouseButton::eNotPressed; - } } } // namespace mozilla diff --git a/dom/events/EventStateManager.h b/dom/events/EventStateManager.h index fe24441a16f8..51bdd754eafe 100644 --- a/dom/events/EventStateManager.h +++ b/dom/events/EventStateManager.h @@ -243,6 +243,12 @@ class EventStateManager : public nsSupportsWeakReference, public nsIObserver { const Maybe& aHotspot, nsIWidget* aWidget, bool aLockCursor); + /** + * Returns true if the event is considered as user interaction event. I.e., + * enough obvious input to allow to open popup, etc. Otherwise, returns false. + */ + static bool IsUserInteractionEvent(const WidgetEvent* aEvent); + /** * StartHandlingUserInput() is called when we start to handle a user input. * StopHandlingUserInput() is called when we finish handling a user input. @@ -1286,24 +1292,13 @@ class EventStateManager : public nsSupportsWeakReference, public nsIObserver { */ class MOZ_RAII AutoHandlingUserInputStatePusher final { public: - AutoHandlingUserInputStatePusher(bool aIsHandlingUserInput, - WidgetEvent* aEvent, - dom::Document* aDocument); + explicit AutoHandlingUserInputStatePusher(bool aIsHandlingUserInput, + WidgetEvent* aEvent = nullptr); ~AutoHandlingUserInputStatePusher(); protected: - RefPtr mMouseButtonEventHandlingDocument; EventMessage mMessage; bool mIsHandlingUserInput; - - bool NeedsToResetFocusManagerMouseButtonHandlingState() const { - return mMessage == eMouseDown || mMessage == eMouseUp; - } - - bool NeedsToUpdateCurrentMouseBtnState() const { - return mMessage == eMouseDown || mMessage == eMouseUp || - mMessage == ePointerDown || mMessage == ePointerUp; - } }; } // namespace mozilla diff --git a/dom/html/HTMLFormElement.cpp b/dom/html/HTMLFormElement.cpp index 8688cbc10985..1b9b8f214384 100644 --- a/dom/html/HTMLFormElement.cpp +++ b/dom/html/HTMLFormElement.cpp @@ -698,7 +698,7 @@ nsresult HTMLFormElement::SubmitSubmission( AutoPopupStatePusher popupStatePusher(mSubmitPopupState); AutoHandlingUserInputStatePusher userInpStatePusher( - aFormSubmission->IsInitiatedFromUserInput(), nullptr, doc); + aFormSubmission->IsInitiatedFromUserInput()); nsCOMPtr postDataStream; rv = aFormSubmission->GetEncodedSubmission( diff --git a/dom/ipc/BrowserParent.cpp b/dom/ipc/BrowserParent.cpp index 1b6c09a1c34c..52959bc1d13a 100644 --- a/dom/ipc/BrowserParent.cpp +++ b/dom/ipc/BrowserParent.cpp @@ -2285,7 +2285,7 @@ mozilla::ipc::IPCResult BrowserParent::RecvReplyKeyEvent( NS_ENSURE_TRUE(presContext, IPC_OK()); AutoHandlingUserInputStatePusher userInpStatePusher(localEvent.IsTrusted(), - &localEvent, doc); + &localEvent); nsEventStatus status = nsEventStatus_eIgnore; diff --git a/dom/ipc/ContentChild.cpp b/dom/ipc/ContentChild.cpp index aa76d065f91c..88dcd2e3f047 100644 --- a/dom/ipc/ContentChild.cpp +++ b/dom/ipc/ContentChild.cpp @@ -672,6 +672,11 @@ bool ContentChild::Init(MessageLoop* aIOLoop, base::ProcessId aParentPid, // their own children. if (recordreplay::IsMiddleman()) { SetMiddlemanIPCChannel(recordreplay::parent::ChannelToUIProcess()); + + // Eagerly mark this child as connected, as using another IPC channel will + // cause that channel's protocol to be marked as connected instead and + // prevent this one from being able to send IPDL messages. + ActorConnected(); } if (!Open(aChannel, aParentPid, aIOLoop)) { @@ -2723,7 +2728,7 @@ mozilla::ipc::IPCResult ContentChild::RecvRemoteType( SetProcessName(NS_LITERAL_STRING("file:// Content")); } else if (aRemoteType.EqualsLiteral(EXTENSION_REMOTE_TYPE)) { SetProcessName(NS_LITERAL_STRING("WebExtensions")); - } else if (aRemoteType.EqualsLiteral(PRIVILEGED_REMOTE_TYPE)) { + } else if (aRemoteType.EqualsLiteral(PRIVILEGEDABOUT_REMOTE_TYPE)) { SetProcessName(NS_LITERAL_STRING("Privileged Content")); } else if (aRemoteType.EqualsLiteral(LARGE_ALLOCATION_REMOTE_TYPE)) { SetProcessName(NS_LITERAL_STRING("Large Allocation Web Content")); diff --git a/dom/ipc/ContentParent.h b/dom/ipc/ContentParent.h index 97b182f05ed3..33bb3f1eb739 100644 --- a/dom/ipc/ContentParent.h +++ b/dom/ipc/ContentParent.h @@ -52,7 +52,8 @@ #define DEFAULT_REMOTE_TYPE "web" #define FILE_REMOTE_TYPE "file" #define EXTENSION_REMOTE_TYPE "extension" -#define PRIVILEGED_REMOTE_TYPE "privileged" +#define PRIVILEGEDABOUT_REMOTE_TYPE "privilegedabout" +#define PRIVILEGEDMOZILLA_REMOTE_TYPE "privilegedmozilla" // This must start with the DEFAULT_REMOTE_TYPE above. #define LARGE_ALLOCATION_REMOTE_TYPE "webLargeAllocation" diff --git a/dom/ipc/tests/JSWindowActor/browser_getActor_filter.js b/dom/ipc/tests/JSWindowActor/browser_getActor_filter.js index fc09d7629a91..679aff450880 100644 --- a/dom/ipc/tests/JSWindowActor/browser_getActor_filter.js +++ b/dom/ipc/tests/JSWindowActor/browser_getActor_filter.js @@ -92,7 +92,7 @@ declTest("getActor with remoteType match", { }); declTest("getActor with remoteType mismatch", { - remoteTypes: ["privileged"], + remoteTypes: ["privilegedabout"], url: TEST_URL, async test(browser) { diff --git a/dom/security/nsContentSecurityManager.cpp b/dom/security/nsContentSecurityManager.cpp index 55e3f331c433..579aeaa84ef7 100644 --- a/dom/security/nsContentSecurityManager.cpp +++ b/dom/security/nsContentSecurityManager.cpp @@ -800,7 +800,7 @@ static void DebugDoContentSecurityCheck(nsIChannel* aChannel, } } -#if defined(DEBUG) || defined(FUZZING) +#ifdef EARLY_BETA_OR_EARLIER // Assert that we never use the SystemPrincipal to load remote documents // i.e., HTTP, HTTPS, FTP URLs static void AssertSystemPrincipalMustNotLoadRemoteDocuments( @@ -860,7 +860,7 @@ static void AssertSystemPrincipalMustNotLoadRemoteDocuments( // but other mochitest are exempt from this return; } - MOZ_ASSERT(false, "SystemPrincipal must not load remote documents."); + MOZ_RELEASE_ASSERT(false, "SystemPrincipal must not load remote documents."); } #endif @@ -890,7 +890,7 @@ nsresult nsContentSecurityManager::doContentSecurityCheck( DebugDoContentSecurityCheck(aChannel, loadInfo); } -#if defined(DEBUG) || defined(FUZZING) +#ifdef EARLY_BETA_OR_EARLIER AssertSystemPrincipalMustNotLoadRemoteDocuments(aChannel); #endif diff --git a/dom/security/test/general/browser.ini b/dom/security/test/general/browser.ini index 619f5d7d99aa..e063df2a9d6e 100644 --- a/dom/security/test/general/browser.ini +++ b/dom/security/test/general/browser.ini @@ -17,7 +17,7 @@ support-files = support-files = file_FTP_console_warning.html [browser_test_assert_systemprincipal_documents.js] -skip-if = !debug && !fuzzing +skip-if = !nightly_build support-files = file_assert_systemprincipal_documents.html file_assert_systemprincipal_documents_iframe.html diff --git a/js/src/jit/shared/IonAssemblerBufferWithConstantPools.h b/js/src/jit/shared/IonAssemblerBufferWithConstantPools.h index 1221426fbd4f..f21aaaf088f4 100644 --- a/js/src/jit/shared/IonAssemblerBufferWithConstantPools.h +++ b/js/src/jit/shared/IonAssemblerBufferWithConstantPools.h @@ -7,6 +7,7 @@ #ifndef jit_shared_IonAssemblerBufferWithConstantPools_h #define jit_shared_IonAssemblerBufferWithConstantPools_h +#include "mozilla/CheckedInt.h" #include "mozilla/MathAlgorithms.h" #include @@ -783,7 +784,7 @@ struct AssemblerBufferWithConstantPools id, sizeExcludingCurrentPool()); } - finishPool(); + finishPool(numInst * InstSize); if (this->oom()) { return OOM_FAIL; } @@ -813,7 +814,7 @@ struct AssemblerBufferWithConstantPools JitSpew(JitSpew_Pools, "[%d] nextInstrOffset @ %d caused a constant pool spill", id, this->nextOffset().getOffset()); - finishPool(); + finishPool(ShortRangeBranchHysteresis); } return this->nextOffset(); } @@ -944,27 +945,38 @@ struct AssemblerBufferWithConstantPools private: // Are any short-range branches about to expire? - bool hasExpirableShortRangeBranches() const { + bool hasExpirableShortRangeBranches(size_t reservedBytes) const { if (branchDeadlines_.empty()) { return false; } - // Include branches that would expire in the next N bytes. - // The hysteresis avoids the needless creation of many tiny constant - // pools. - return this->nextOffset().getOffset() + ShortRangeBranchHysteresis > - size_t(branchDeadlines_.earliestDeadline().getOffset()); + // Include branches that would expire in the next N bytes. The reservedBytes + // argument avoids the needless creation of many tiny constant pools. + // + // As the reservedBytes could be of any sizes such as SIZE_MAX, in the case + // of flushPool, we have to check for overflow when comparing the deadline + // with our expected reserved bytes. + size_t deadline = branchDeadlines_.earliestDeadline().getOffset(); + using CheckedSize = mozilla::CheckedInt; + CheckedSize current(this->nextOffset().getOffset()); + CheckedSize poolFreeSpace(reservedBytes); + auto future = current + poolFreeSpace; + return !future.isValid() || deadline < future.value(); } - bool isPoolEmpty() const { - return pool_.numEntries() == 0 && !hasExpirableShortRangeBranches(); + bool isPoolEmptyFor(size_t bytes) const { + return pool_.numEntries() == 0 && !hasExpirableShortRangeBranches(bytes); } - void finishPool() { + void finishPool(size_t reservedBytes) { JitSpew(JitSpew_Pools, "[%d] Attempting to finish pool %zu with %u entries.", id, poolInfo_.length(), pool_.numEntries()); - if (isPoolEmpty()) { + if (reservedBytes < ShortRangeBranchHysteresis) { + reservedBytes = ShortRangeBranchHysteresis; + } + + if (isPoolEmptyFor(reservedBytes)) { // If there is no data in the pool being dumped, don't dump anything. JitSpew(JitSpew_Pools, "[%d] Aborting because the pool is empty", id); return; @@ -984,7 +996,7 @@ struct AssemblerBufferWithConstantPools // Now generate branch veneers for any short-range branches that are // about to expire. - while (hasExpirableShortRangeBranches()) { + while (hasExpirableShortRangeBranches(reservedBytes)) { unsigned rangeIdx = branchDeadlines_.earliestDeadlineRange(); BufferOffset deadline = branchDeadlines_.earliestDeadline(); @@ -1052,7 +1064,7 @@ struct AssemblerBufferWithConstantPools return; } JitSpew(JitSpew_Pools, "[%d] Requesting a pool flush", id); - finishPool(); + finishPool(SIZE_MAX); } void enterNoPool(size_t maxInst) { @@ -1070,7 +1082,8 @@ struct AssemblerBufferWithConstantPools if (!hasSpaceForInsts(maxInst, 0)) { JitSpew(JitSpew_Pools, "[%d] No-Pool instruction(%zu) caused a spill.", id, sizeExcludingCurrentPool()); - finishPool(); + finishPool(maxInst * InstSize); + MOZ_ASSERT(hasSpaceForInsts(maxInst, 0)); } #ifdef DEBUG @@ -1105,7 +1118,7 @@ struct AssemblerBufferWithConstantPools inhibitNops_ = false; } void assertNoPoolAndNoNops() { - MOZ_ASSERT(inhibitNops_ && (isPoolEmpty() || canNotPlacePool_)); + MOZ_ASSERT(inhibitNops_ && (isPoolEmptyFor(InstSize) || canNotPlacePool_)); } void align(unsigned alignment) { align(alignment, alignFillInst_); } @@ -1130,7 +1143,7 @@ struct AssemblerBufferWithConstantPools // Alignment would cause a pool dump, so dump the pool now. JitSpew(JitSpew_Pools, "[%d] Alignment of %d at %zu caused a spill.", id, alignment, sizeExcludingCurrentPool()); - finishPool(); + finishPool(requiredFill); } bool prevInhibitNops = inhibitNops_; diff --git a/js/src/js.msg b/js/src/js.msg index 089501621c20..11adcbaf5034 100644 --- a/js/src/js.msg +++ b/js/src/js.msg @@ -435,12 +435,14 @@ MSG_DEF(JSMSG_PROXY_DELETE_RETURNED_FALSE, 1, JSEXN_TYPEERR, "can't delete prope MSG_DEF(JSMSG_PROXY_PREVENTEXTENSIONS_RETURNED_FALSE, 0, JSEXN_TYPEERR, "proxy preventExtensions handler returned false") MSG_DEF(JSMSG_PROXY_SET_RETURNED_FALSE, 1, JSEXN_TYPEERR, "proxy set handler returned false for property '{0}'") MSG_DEF(JSMSG_CANT_REPORT_AS_NON_EXTENSIBLE, 0, JSEXN_TYPEERR, "proxy can't report an extensible object as non-extensible") +MSG_DEF(JSMSG_CANT_DELETE_NON_EXTENSIBLE, 1, JSEXN_TYPEERR, "proxy can't delete property '{0}' on a non-extensible object") MSG_DEF(JSMSG_CANT_REPORT_C_AS_NC, 1, JSEXN_TYPEERR, "proxy can't report existing configurable property '{0}' as non-configurable") MSG_DEF(JSMSG_CANT_REPORT_E_AS_NE, 1, JSEXN_TYPEERR, "proxy can't report an existing own property '{0}' as non-existent on a non-extensible object") MSG_DEF(JSMSG_CANT_REPORT_INVALID, 2, JSEXN_TYPEERR, "proxy can't report an incompatible property descriptor ('{0}', {1})") MSG_DEF(JSMSG_CANT_REPORT_NC_AS_NE, 1, JSEXN_TYPEERR, "proxy can't report a non-configurable own property '{0}' as non-existent") MSG_DEF(JSMSG_CANT_REPORT_NEW, 1, JSEXN_TYPEERR, "proxy can't report a new property '{0}' on a non-extensible object") MSG_DEF(JSMSG_CANT_REPORT_NE_AS_NC, 1, JSEXN_TYPEERR, "proxy can't report a non-existent property '{0}' as non-configurable") +MSG_DEF(JSMSG_CANT_REPORT_W_AS_NW, 1, JSEXN_TYPEERR, "proxy can't report existing writable property '{0}' as non-writable") MSG_DEF(JSMSG_CANT_SET_NW_NC, 1, JSEXN_TYPEERR, "proxy can't successfully set a non-writable, non-configurable property '{0}'") MSG_DEF(JSMSG_CANT_SET_WO_SETTER, 1, JSEXN_TYPEERR, "proxy can't succesfully set an accessor property '{0}' without a setter") MSG_DEF(JSMSG_CANT_SKIP_NC, 1, JSEXN_TYPEERR, "proxy can't skip a non-configurable property '{0}'") diff --git a/js/src/proxy/ScriptedProxyHandler.cpp b/js/src/proxy/ScriptedProxyHandler.cpp index 455b97f47353..c17431a17c08 100644 --- a/js/src/proxy/ScriptedProxyHandler.cpp +++ b/js/src/proxy/ScriptedProxyHandler.cpp @@ -620,6 +620,12 @@ bool ScriptedProxyHandler::getOwnPropertyDescriptor( if (targetDesc.configurable()) { return js::Throw(cx, id, JSMSG_CANT_REPORT_C_AS_NC); } + + if (resultDesc.hasWritable() && !resultDesc.writable()) { + if (targetDesc.writable()) { + return js::Throw(cx, id, JSMSG_CANT_REPORT_W_AS_NW); + } + } } // Step 18. @@ -733,6 +739,17 @@ bool ScriptedProxyHandler::defineProperty(JSContext* cx, HandleObject proxy, return js::Throw(cx, id, JSMSG_CANT_DEFINE_INVALID, DETAILS_CANT_REPORT_C_AS_NC); } + + if (targetDesc.isDataDescriptor() && !targetDesc.configurable() && + targetDesc.writable()) { + if (desc.hasWritable() && !desc.writable()) { + static const char DETAILS_CANT_DEFINE_NW[] = + "proxy can't define an existing non-configurable writable property " + "as non-writable"; + return js::Throw(cx, id, JSMSG_CANT_DEFINE_INVALID, + DETAILS_CANT_DEFINE_NW); + } + } } // Step 17. @@ -992,12 +1009,26 @@ bool ScriptedProxyHandler::delete_(JSContext* cx, HandleObject proxy, return false; } + // Step 11. + if (!desc.object()) { + return result.succeed(); + } + // Step 12. - if (desc.object() && !desc.configurable()) { + if (!desc.configurable()) { return Throw(cx, id, JSMSG_CANT_DELETE); } - // Steps 11,13. + bool extensible; + if (!IsExtensible(cx, target, &extensible)) { + return false; + } + + if (!extensible) { + return Throw(cx, id, JSMSG_CANT_DELETE_NON_EXTENSIBLE); + } + + // Step 13. return result.succeed(); } diff --git a/js/src/tests/non262/Proxy/define-writable-as-non-writable.js b/js/src/tests/non262/Proxy/define-writable-as-non-writable.js new file mode 100644 index 000000000000..f764d278f0bc --- /dev/null +++ b/js/src/tests/non262/Proxy/define-writable-as-non-writable.js @@ -0,0 +1,19 @@ +"use strict"; + +var target = {}; +Object.defineProperty(target, "test", {configurable: false, writable: true, value: 5}); + +var proxy = new Proxy(target, { + defineProperty(target, property) { + assertEq(property, "test"); + return true; + } +}); + +assertThrowsInstanceOf( + () => Object.defineProperty(proxy, "test", {writable: false}), TypeError); + +assertThrowsInstanceOf( + () => Reflect.defineProperty(proxy, "test", {writable: false}), TypeError); + +reportCompare(0, 0); diff --git a/js/src/tests/non262/Proxy/delete-non-extensible.js b/js/src/tests/non262/Proxy/delete-non-extensible.js new file mode 100644 index 000000000000..b31216bb659a --- /dev/null +++ b/js/src/tests/non262/Proxy/delete-non-extensible.js @@ -0,0 +1,18 @@ +"use strict"; + +var target = { test: true }; +Object.preventExtensions(target); + +var proxy = new Proxy(target, { + deleteProperty(target, property) { + return true; + } +}); + +assertEq(delete proxy.missing, true); +assertEq(Reflect.deleteProperty(proxy, "missing"), true); + +assertThrowsInstanceOf(() => { delete proxy.test; }, TypeError); +assertThrowsInstanceOf(() => Reflect.deleteProperty(proxy, "test"), TypeError); + +reportCompare(0, 0); diff --git a/js/src/tests/non262/Proxy/report-writable-as-non-writable.js b/js/src/tests/non262/Proxy/report-writable-as-non-writable.js new file mode 100644 index 000000000000..c0facd10574a --- /dev/null +++ b/js/src/tests/non262/Proxy/report-writable-as-non-writable.js @@ -0,0 +1,20 @@ +"use strict"; + +var target = {}; +Object.defineProperty(target, "test", + {configurable: false, writable: true, value: 1}); + +var proxy = new Proxy(target, { + getOwnPropertyDescriptor(target, property) { + assertEq(property, "test"); + return {configurable: false, writable: false, value: 1}; + } +}); + +assertThrowsInstanceOf(() => Object.getOwnPropertyDescriptor(proxy, "test"), + TypeError); + +assertThrowsInstanceOf(() => Reflect.getOwnPropertyDescriptor(proxy, "test"), + TypeError); + +reportCompare(0, 0); diff --git a/js/xpconnect/loader/ScriptPreloader.cpp b/js/xpconnect/loader/ScriptPreloader.cpp index 745b71a0c805..4442e7a905a4 100644 --- a/js/xpconnect/loader/ScriptPreloader.cpp +++ b/js/xpconnect/loader/ScriptPreloader.cpp @@ -188,8 +188,8 @@ ProcessType ScriptPreloader::GetChildProcessType(const nsAString& remoteType) { if (remoteType.EqualsLiteral(EXTENSION_REMOTE_TYPE)) { return ProcessType::Extension; } - if (remoteType.EqualsLiteral(PRIVILEGED_REMOTE_TYPE)) { - return ProcessType::Privileged; + if (remoteType.EqualsLiteral(PRIVILEGEDABOUT_REMOTE_TYPE)) { + return ProcessType::PrivilegedAbout; } return ProcessType::Web; } @@ -346,9 +346,9 @@ void ScriptPreloader::FinishContentStartup() { #ifdef DEBUG if (mContentStartupFinishedTopic.Equals(CONTENT_DOCUMENT_LOADED_TOPIC)) { - MOZ_ASSERT(sProcessType == ProcessType::Privileged); + MOZ_ASSERT(sProcessType == ProcessType::PrivilegedAbout); } else { - MOZ_ASSERT(sProcessType != ProcessType::Privileged); + MOZ_ASSERT(sProcessType != ProcessType::PrivilegedAbout); } #endif /* DEBUG */ @@ -457,7 +457,7 @@ Result ScriptPreloader::InitCache( nsCOMPtr obs = services::GetObserverService(); MOZ_RELEASE_ASSERT(obs); - if (sProcessType == ProcessType::Privileged) { + if (sProcessType == ProcessType::PrivilegedAbout) { // Since we control all of the documents loaded in the privileged // content process, we can increase the window of active time for the // ScriptPreloader to include the scripts that are loaded until the diff --git a/js/xpconnect/loader/ScriptPreloader.h b/js/xpconnect/loader/ScriptPreloader.h index fe5b59320cf8..47835417a5e7 100644 --- a/js/xpconnect/loader/ScriptPreloader.h +++ b/js/xpconnect/loader/ScriptPreloader.h @@ -46,7 +46,7 @@ enum class ProcessType : uint8_t { Parent, Web, Extension, - Privileged, + PrivilegedAbout, }; template diff --git a/layout/base/PresShell.cpp b/layout/base/PresShell.cpp index f76edc908449..8833e817e44a 100644 --- a/layout/base/PresShell.cpp +++ b/layout/base/PresShell.cpp @@ -7730,6 +7730,67 @@ nsresult PresShell::EventHandler::HandleEventWithTarget( return rv; } +namespace { + +class MOZ_RAII AutoEventHandler final { + public: + AutoEventHandler(WidgetEvent* aEvent, Document* aDocument) : mEvent(aEvent) { + MOZ_ASSERT(mEvent); + MOZ_ASSERT(mEvent->IsTrusted()); + + if (mEvent->mMessage == eMouseDown) { + PresShell::ReleaseCapturingContent(); + PresShell::AllowMouseCapture(true); + } + if (aDocument && NeedsToResetFocusManagerMouseButtonHandlingState()) { + nsFocusManager* fm = nsFocusManager::GetFocusManager(); + NS_ENSURE_TRUE_VOID(fm); + // If it's in modal state, mouse button event handling may be nested. + // E.g., a modal dialog is opened at mousedown or mouseup event handler + // and the dialog is clicked. Therefore, we should store current + // mouse button event handling document if nsFocusManager already has it. + mMouseButtonEventHandlingDocument = + fm->SetMouseButtonHandlingDocument(aDocument); + } + if (NeedsToUpdateCurrentMouseBtnState()) { + WidgetMouseEvent* mouseEvent = mEvent->AsMouseEvent(); + if (mouseEvent) { + EventStateManager::sCurrentMouseBtn = mouseEvent->mButton; + } + } + } + + ~AutoEventHandler() { + if (mEvent->mMessage == eMouseDown) { + PresShell::AllowMouseCapture(false); + } + if (NeedsToResetFocusManagerMouseButtonHandlingState()) { + nsFocusManager* fm = nsFocusManager::GetFocusManager(); + NS_ENSURE_TRUE_VOID(fm); + RefPtr document = + fm->SetMouseButtonHandlingDocument(mMouseButtonEventHandlingDocument); + } + if (NeedsToUpdateCurrentMouseBtnState()) { + EventStateManager::sCurrentMouseBtn = MouseButton::eNotPressed; + } + } + + protected: + bool NeedsToResetFocusManagerMouseButtonHandlingState() const { + return mEvent->mMessage == eMouseDown || mEvent->mMessage == eMouseUp; + } + + bool NeedsToUpdateCurrentMouseBtnState() const { + return mEvent->mMessage == eMouseDown || mEvent->mMessage == eMouseUp || + mEvent->mMessage == ePointerDown || mEvent->mMessage == ePointerUp; + } + + RefPtr mMouseButtonEventHandlingDocument; + WidgetEvent* mEvent; +}; + +} // anonymous namespace + nsresult PresShell::EventHandler::HandleEventWithCurrentEventInfo( WidgetEvent* aEvent, nsEventStatus* aEventStatus, bool aIsHandlingNativeEvent, nsIContent* aOverrideClickTarget) { @@ -7756,10 +7817,8 @@ nsresult PresShell::EventHandler::HandleEventWithCurrentEventInfo( } } - bool isHandlingUserInput = false; bool touchIsNew = false; - if (!PrepareToDispatchEvent(aEvent, aEventStatus, &isHandlingUserInput, - &touchIsNew)) { + if (!PrepareToDispatchEvent(aEvent, aEventStatus, &touchIsNew)) { return NS_OK; } @@ -7767,9 +7826,9 @@ nsresult PresShell::EventHandler::HandleEventWithCurrentEventInfo( // performance. RecordEventPreparationPerformance(aEvent); - AutoHandlingUserInputStatePusher userInpStatePusher(isHandlingUserInput, - aEvent, GetDocument()); - + AutoHandlingUserInputStatePusher userInpStatePusher( + EventStateManager::IsUserInteractionEvent(aEvent), aEvent); + AutoEventHandler eventHandler(aEvent, GetDocument()); AutoPopupStatePusher popupStatePusher( PopupBlocker::GetEventPopupControlState(aEvent)); @@ -7877,11 +7936,9 @@ nsresult PresShell::EventHandler::DispatchEvent( } bool PresShell::EventHandler::PrepareToDispatchEvent( - WidgetEvent* aEvent, nsEventStatus* aEventStatus, bool* aIsUserInteraction, - bool* aTouchIsNew) { + WidgetEvent* aEvent, nsEventStatus* aEventStatus, bool* aTouchIsNew) { MOZ_ASSERT(aEvent->IsTrusted()); MOZ_ASSERT(aEventStatus); - MOZ_ASSERT(aIsUserInteraction); MOZ_ASSERT(aTouchIsNew); *aTouchIsNew = false; @@ -7895,26 +7952,14 @@ bool PresShell::EventHandler::PrepareToDispatchEvent( case eKeyUp: { WidgetKeyboardEvent* keyboardEvent = aEvent->AsKeyboardEvent(); MaybeHandleKeyboardEventBeforeDispatch(keyboardEvent); - // Not all keyboard events are treated as user input, so that popups - // can't be opened, fullscreen mode can't be started, etc at unexpected - // time. - *aIsUserInteraction = keyboardEvent->CanTreatAsUserInput(); return true; } - case eMouseDown: - case eMouseUp: - case ePointerDown: - case ePointerUp: - *aIsUserInteraction = true; - return true; - case eMouseMove: { bool allowCapture = EventStateManager::GetActiveEventStateManager() && GetPresContext() && GetPresContext()->EventStateManager() == EventStateManager::GetActiveEventStateManager(); PresShell::AllowMouseCapture(allowCapture); - *aIsUserInteraction = false; return true; } case eDrop: { @@ -7926,12 +7971,9 @@ bool PresShell::EventHandler::PrepareToDispatchEvent( aEvent->mFlags.mOnlyChromeDispatch = true; } } - *aIsUserInteraction = false; return true; } case eContextMenu: { - *aIsUserInteraction = false; - // If we cannot open context menu even though eContextMenu is fired, we // should stop dispatching it into the DOM. WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent(); @@ -7956,10 +7998,8 @@ bool PresShell::EventHandler::PrepareToDispatchEvent( case eTouchCancel: case eTouchPointerCancel: return mPresShell->mTouchManager.PreHandleEvent( - aEvent, aEventStatus, *aTouchIsNew, *aIsUserInteraction, - mPresShell->mCurrentEventContent); + aEvent, aEventStatus, *aTouchIsNew, mPresShell->mCurrentEventContent); default: - *aIsUserInteraction = false; return true; } } diff --git a/layout/base/PresShell.h b/layout/base/PresShell.h index f972d827029a..aba7d6a02b06 100644 --- a/layout/base/PresShell.h +++ b/layout/base/PresShell.h @@ -2563,10 +2563,6 @@ class PresShell final : public nsStubDocumentObserver, * * @param aEvent The handling event. * @param aEventStatus [in/out] The status of aEvent. - * @param aIsUserInteraction [out] Set to true if the event is user - * interaction. I.e., enough obvious input - * to allow to open popup, etc. Otherwise, - * set to false. * @param aTouchIsNew [out] Set to true if the event is an * eTouchMove event and it represents new * touch. Otherwise, set to false. @@ -2575,8 +2571,7 @@ class PresShell final : public nsStubDocumentObserver, */ MOZ_CAN_RUN_SCRIPT bool PrepareToDispatchEvent(WidgetEvent* aEvent, - nsEventStatus* aEventStatus, - bool* aIsUserInteraction, bool* aTouchIsNew); + nsEventStatus* aEventStatus, bool* aTouchIsNew); /** * MaybeHandleKeyboardEventBeforeDispatch() may handle aKeyboardEvent diff --git a/layout/base/TouchManager.cpp b/layout/base/TouchManager.cpp index 2b79b9537848..4e8ddce764ed 100644 --- a/layout/base/TouchManager.cpp +++ b/layout/base/TouchManager.cpp @@ -220,7 +220,7 @@ nsIFrame* TouchManager::SuppressInvalidPointsAndGetTargetedFrame( } bool TouchManager::PreHandleEvent(WidgetEvent* aEvent, nsEventStatus* aStatus, - bool& aTouchIsNew, bool& aIsHandlingUserInput, + bool& aTouchIsNew, nsCOMPtr& aCurrentEventContent) { MOZ_DIAGNOSTIC_ASSERT(aEvent->IsTrusted()); @@ -228,7 +228,6 @@ bool TouchManager::PreHandleEvent(WidgetEvent* aEvent, nsEventStatus* aStatus, // cases in PresShell::EventHandler::PrepareToDispatchEvent(). switch (aEvent->mMessage) { case eTouchStart: { - aIsHandlingUserInput = true; WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent(); // if there is only one touch in this touchstart event, assume that it is // the start of a new touch session and evict any old touches in the @@ -335,9 +334,6 @@ bool TouchManager::PreHandleEvent(WidgetEvent* aEvent, nsEventStatus* aStatus, break; } case eTouchEnd: - aIsHandlingUserInput = true; - // Fall through to touchcancel code - MOZ_FALLTHROUGH; case eTouchCancel: { // Remove the changed touches // need to make sure we only remove touches that are ending here diff --git a/layout/base/TouchManager.h b/layout/base/TouchManager.h index cb4d19630927..42844a6b75db 100644 --- a/layout/base/TouchManager.h +++ b/layout/base/TouchManager.h @@ -49,7 +49,7 @@ class TouchManager { WidgetTouchEvent* aEvent); bool PreHandleEvent(mozilla::WidgetEvent* aEvent, nsEventStatus* aStatus, - bool& aTouchIsNew, bool& aIsHandlingUserInput, + bool& aTouchIsNew, nsCOMPtr& aCurrentEventContent); static already_AddRefed GetAnyCapturedTouchTarget(); diff --git a/layout/base/tests/transformed_scrolling_repaints_3_window.html b/layout/base/tests/transformed_scrolling_repaints_3_window.html index e946a9f2b0a7..1b345a25d082 100644 --- a/layout/base/tests/transformed_scrolling_repaints_3_window.html +++ b/layout/base/tests/transformed_scrolling_repaints_3_window.html @@ -14,7 +14,6 @@ var SimpleTest = window.opener.SimpleTest; var SpecialPowers = window.opener.SpecialPowers; var is = window.opener.is; -var t, e, utils, iterations; var smoothScrollPref = "general.smoothScroll"; function startTest() { diff --git a/layout/forms/test/test_bug536567_perwindowpb.html b/layout/forms/test/test_bug536567_perwindowpb.html index c4ea329b8dd0..dc2088e20c29 100644 --- a/layout/forms/test/test_bug536567_perwindowpb.html +++ b/layout/forms/test/test_bug536567_perwindowpb.html @@ -30,12 +30,12 @@ function newDir() { var dir = tmpDir.clone(); dir.append("testdir" + Math.floor(Math.random() * 10000)); dir.QueryInterface(Ci.nsIFile); - dir.createUnique(Ci.nsIFile.DIRECTORY_TYPE, 0700); + dir.createUnique(Ci.nsIFile.DIRECTORY_TYPE, 0o700); return dir; } var dirs = []; -for(var i = 0; i < 6; i++) { +for(let i = 0; i < 6; i++) { dirs.push(newDir()); } dirs.push(homeDir); @@ -119,7 +119,7 @@ function runTest() { } function endTest() { - for(var i = 0; i < dirs.length - 1; i++) { + for(let i = 0; i < dirs.length - 1; i++) { dirs[i].remove(true); } @@ -168,7 +168,7 @@ function testOnWindow(aIsPrivate, aCallback) { MockFilePicker.showCallback = function(filepicker) { var test = tests[testIndex]; var returned = -1; - for (var i = 0; i < dirs.length; i++) { + for (let i = 0; i < dirs.length; i++) { var dir = MockFilePicker.displayDirectory ? MockFilePicker.displayDirectory : Services.dirsvc.get(MockFilePicker.displaySpecialDirectory, Ci.nsIFile); diff --git a/layout/forms/test/test_bug903715.html b/layout/forms/test/test_bug903715.html index 9b9db48b75cf..b887b2cd0144 100644 --- a/layout/forms/test/test_bug903715.html +++ b/layout/forms/test/test_bug903715.html @@ -59,7 +59,7 @@ function runTests() select.addEventListener("popupshowing", function (aEvent) { setTimeout(function () { synthesizeKey("KEY_ArrowDown"); - select.addEventListener("popuphiding", function (aEvent) { + select.addEventListener("popuphiding", function (aEventInner) { setTimeout(function () { // Enter key should cause closing the dropdown of the select element // and keypress event shouldn't be fired on the input element because diff --git a/layout/generic/test/plugin_clipping_lib.js b/layout/generic/test/plugin_clipping_lib.js index 287c1574eadc..ee6deedb9187 100644 --- a/layout/generic/test/plugin_clipping_lib.js +++ b/layout/generic/test/plugin_clipping_lib.js @@ -114,17 +114,17 @@ function checkClipRegionWithDoc(doc, offsetX, offsetY, id, rects, checkBounds) { "': expected " + dumpRegion(rects) + ", got " + dumpRegion(clipRects)); } -checkClipRegion = function checkClipRegion(id, rects) { +checkClipRegion = function(id, rects) { checkClipRegionWithDoc(document, 0, 0, id, rects, true); } -checkClipRegionForFrame = function checkClipRegionForFrame(fid, id, rects) { +checkClipRegionForFrame = function(fid, id, rects) { var f = document.getElementById(fid); var bounds = f.getBoundingClientRect(); checkClipRegionWithDoc(f.contentDocument, bounds.left, bounds.top, id, rects, true); } -checkClipRegionNoBounds = function checkClipRegionNoBounds(id, rects) { +checkClipRegionNoBounds = function(id, rects) { checkClipRegionWithDoc(document, 0, 0, id, rects, false); } diff --git a/layout/generic/test/test_bug1499961.html b/layout/generic/test/test_bug1499961.html index a2f4c0ba1f1c..12b217f39e1f 100644 --- a/layout/generic/test/test_bug1499961.html +++ b/layout/generic/test/test_bug1499961.html @@ -29,6 +29,7 @@ limitations under the License.

         
+  
   
 
 
@@ -60,7 +60,7 @@ var canvasNames = [ "brokenIconTest",  "brokenIconReference",
                     "loadingIconTest", "loadingIconReference",
                     "loadedTest",      "loadedReference" ];
 var windowElem = document.documentElement;
-for (var i in canvasNames) {
+for (let i in canvasNames) {
   var can = document.createElement("canvas");
   can.setAttribute("width", windowElem.getAttribute("width"));
   can.setAttribute("height", windowElem.getAttribute("height"));
@@ -81,7 +81,7 @@ for (var i in canvasNames) {
   // with the image, but not the bottom and right borders.
 
   if ((i > 1) && (i < 4)) {
-    var ctx = can.getContext("2d");
+    let ctx = can.getContext("2d");
     ctx.beginPath();
     ctx.rect(0,0, 30, 30);
     ctx.clip();
@@ -327,7 +327,7 @@ function resetImage() {
 // debugging.
 //
 function makeCanvasesVisible() {
-  for (var i = 0; i < canvasNames.length - 1; i += 2) {
+  for (let i = 0; i < canvasNames.length - 1; i += 2) {
     var title = document.createElement("h3");
     title.innerHTML = canvasNames[i] + ", " + canvasNames[i+1] + ":";
     document.body.appendChild(title);
@@ -359,7 +359,7 @@ function disableBorderAndPad() {
 
 function drawWindowToCanvas(canvasName) {
   var win = testFrameElem.contentWindow;
-  var ctx = canvases[canvasName].getContext("2d");
+  let ctx = canvases[canvasName].getContext("2d");
   // drawWindow always draws one canvas pixel for each CSS pixel in the source
   // window, so scale the drawing to show the zoom (making each canvas pixel be one
   // device pixel instead)
diff --git a/layout/style/test/test_acid3_test46.html b/layout/style/test/test_acid3_test46.html
index 8845feddf4f2..4ec50cfddcea 100644
--- a/layout/style/test/test_acid3_test46.html
+++ b/layout/style/test/test_acid3_test46.html
@@ -23,7 +23,7 @@ extracted from the test framework there and put into Mochitest.
 
 Mozilla Bug 156716
 
 
 
diff --git a/toolkit/components/extensions/test/browser/browser_ext_themes_tab_line.js b/toolkit/components/extensions/test/browser/browser_ext_themes_tab_line.js
index 432773043450..01d63ca50617 100644
--- a/toolkit/components/extensions/test/browser/browser_ext_themes_tab_line.js
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_tab_line.js
@@ -21,7 +21,7 @@ add_task(async function test_support_tab_line() {
 
   info("Checking selected tab line color");
   let selectedTab = document.querySelector(".tabbrowser-tab[selected]");
-  let line = document.getAnonymousElementByAttribute(selectedTab, "class", "tab-line");
+  let line = selectedTab.querySelector(".tab-line");
   Assert.equal(window.getComputedStyle(line).backgroundColor,
                `rgb(${hexToRGB(TAB_LINE_COLOR).join(", ")})`,
                "Tab line should have theme color");
diff --git a/toolkit/components/extensions/test/browser/browser_ext_themes_tab_loading.js b/toolkit/components/extensions/test/browser/browser_ext_themes_tab_loading.js
index a094aa70a47f..3a22477cb18e 100644
--- a/toolkit/components/extensions/test/browser/browser_ext_themes_tab_loading.js
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_tab_loading.js
@@ -33,7 +33,7 @@ add_task(async function test_support_tab_loading_filling() {
   selectedTab.setAttribute("busy", "true");
   selectedTab.setAttribute("progress", "true");
 
-  let throbber = document.getAnonymousElementByAttribute(selectedTab, "class", "tab-throbber");
+  let throbber = selectedTab.throbber;
   Assert.equal(window.getComputedStyle(throbber, "::before").fill,
                `rgb(${hexToRGB(TAB_LOADING_COLOR).join(", ")})`,
                "Throbber is filled with theme color");
diff --git a/toolkit/components/extensions/test/browser/browser_ext_themes_tab_selected.js b/toolkit/components/extensions/test/browser/browser_ext_themes_tab_selected.js
index cf89fa834e4e..fd4c2594dc5f 100644
--- a/toolkit/components/extensions/test/browser/browser_ext_themes_tab_selected.js
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_tab_selected.js
@@ -22,10 +22,10 @@ add_task(async function test_tab_background_color_property() {
   info("Checking selected tab color");
 
   let openTab = document.querySelector(".tabbrowser-tab[visuallyselected=true]");
-  let openTabBackground = document.getAnonymousElementByAttribute(openTab, "class", "tab-background");
+  let openTabBackground = openTab.querySelector(".tab-background");
 
   let selectedTab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank");
-  let selectedTabBackground = document.getAnonymousElementByAttribute(selectedTab, "class", "tab-background");
+  let selectedTabBackground = selectedTab.querySelector(".tab-background");
 
   let openTabGradient = window.getComputedStyle(openTabBackground).getPropertyValue("background-image");
   let selectedTabGradient = window.getComputedStyle(selectedTabBackground).getPropertyValue("background-image");
diff --git a/toolkit/components/passwordmgr/LoginAutoCompleteResult.jsm b/toolkit/components/passwordmgr/LoginAutoCompleteResult.jsm
index 753fb0319eb0..8c164cd9f1cd 100644
--- a/toolkit/components/passwordmgr/LoginAutoCompleteResult.jsm
+++ b/toolkit/components/passwordmgr/LoginAutoCompleteResult.jsm
@@ -99,7 +99,7 @@ class InsecureLoginFormAutocompleteItem extends AutocompleteItem {
 class LoginAutocompleteItem extends AutocompleteItem {
   constructor(login, isPasswordField, dateAndTimeFormatter, duplicateUsernames, messageManager) {
     super(SHOULD_SHOW_ORIGIN ? "loginWithOrigin" : "login");
-    this._login = login;
+    this._login = login.QueryInterface(Ci.nsILoginMetaInfo);
     this._messageManager = messageManager;
 
     XPCOMUtils.defineLazyGetter(this, "label", () => {
@@ -109,8 +109,7 @@ class LoginAutocompleteItem extends AutocompleteItem {
         if (!username) {
           username = getLocalizedString("noUsername");
         }
-        let meta = login.QueryInterface(Ci.nsILoginMetaInfo);
-        let time = dateAndTimeFormatter.format(new Date(meta.timePasswordChanged));
+        let time = dateAndTimeFormatter.format(new Date(login.timePasswordChanged));
         username = getLocalizedString("loginHostAge", [username, time]);
       }
 
@@ -122,14 +121,19 @@ class LoginAutocompleteItem extends AutocompleteItem {
     });
 
     XPCOMUtils.defineLazyGetter(this, "comment", () => {
+      let comment = login.hostname;
       try {
         let uri = Services.io.newURI(login.hostname);
         // Fallback to handle file: URIs
-        return uri.displayHostPort || login.hostname;
+        comment = uri.displayHostPort || login.hostname;
       } catch (ex) {
-        // Fallback to origin below
+        // Fallback to login.hostname set above.
       }
-      return login.hostname;
+
+      return JSON.stringify({
+        guid: login.guid,
+        comment,
+      });
     });
   }
 
diff --git a/toolkit/components/passwordmgr/LoginManagerContent.jsm b/toolkit/components/passwordmgr/LoginManagerContent.jsm
index d4336b6ab06e..53748902d913 100644
--- a/toolkit/components/passwordmgr/LoginManagerContent.jsm
+++ b/toolkit/components/passwordmgr/LoginManagerContent.jsm
@@ -103,6 +103,33 @@ const observer = {
     LoginManagerContent._onNavigation(aWebProgress.DOMWindow.document);
   },
 
+  // nsIObserver
+  observe(subject, topic, data) {
+    switch (topic) {
+      case "autocomplete-did-enter-text": {
+        let input = subject.QueryInterface(Ci.nsIAutoCompleteInput);
+        let {selectedIndex} = input.popup;
+        if (selectedIndex < 0) {
+          break;
+        }
+        let style = input.controller.getStyleAt(selectedIndex);
+        if (style != "login" && style != "loginWithOrigin") {
+          break;
+        }
+        let {focusedInput} = LoginManagerContent._formFillService;
+        if (focusedInput.nodePrincipal.isNullPrincipal) {
+          // If we have a null principal then prevent any more password manager code from running and
+          // incorrectly using the document `location`.
+          return;
+        }
+        let details = JSON.parse(input.controller.getCommentAt(selectedIndex));
+        LoginManagerContent.onFieldAutoComplete(focusedInput, details.guid);
+        break;
+      }
+    }
+  },
+
+  // nsIDOMEventListener
   handleEvent(aEvent) {
     if (!aEvent.isTrusted) {
       return;
@@ -116,7 +143,7 @@ const observer = {
       case "keydown": {
         if (aEvent.keyCode == aEvent.DOM_VK_TAB ||
             aEvent.keyCode == aEvent.DOM_VK_RETURN) {
-          LoginManagerContent.onUsernameInput(aEvent);
+          LoginManagerContent.onUsernameAutocompleted(aEvent.target);
         }
         break;
       }
@@ -144,6 +171,8 @@ const observer = {
   },
 };
 
+// Add this observer once for the process.
+Services.obs.addObserver(observer, "autocomplete-did-enter-text");
 
 // This object maps to the "child" process (even in the single-process case).
 this.LoginManagerContent = {
@@ -323,6 +352,7 @@ this.LoginManagerContent = {
    *
    * @param {HTMLFormElement} form - form to get login data for
    * @param {Object} options
+   * @param {boolean} options.guid - guid of a login to retrieve
    * @param {boolean} options.showMasterPassword - whether to show a master password prompt
    */
   _getLoginDataFromParent(form, options) {
@@ -733,19 +763,13 @@ this.LoginManagerContent = {
   },
 
   /**
-   * Listens for DOMAutoComplete event on login form.
+   * A username or password was autocompleted into a field.
    */
-  onDOMAutoComplete(event) {
-    if (!event.isTrusted) {
-      return;
-    }
-
+  onFieldAutoComplete(acInputField, loginGUID) {
     if (!LoginHelper.enabled) {
       return;
     }
 
-    let acInputField = event.target;
-
     // This is probably a bit over-conservatative.
     if (ChromeUtils.getClassName(acInputField.ownerDocument) != "HTMLDocument") {
       return;
@@ -756,26 +780,17 @@ this.LoginManagerContent = {
     }
 
     if (LoginHelper.isUsernameFieldType(acInputField)) {
-      this.onUsernameInput(event);
+      this.onUsernameAutocompleted(acInputField, loginGUID);
     } else if (acInputField.hasBeenTypePassword) {
-      this._highlightFilledField(event.target);
+      this._highlightFilledField(acInputField);
     }
   },
 
   /**
-   * Calls fill form on the username field.
+   * A username field was filled so try fill in the associated password in the password field.
    */
-  onUsernameInput(event) {
-    let acInputField = event.target;
-
-    // If the username is blank, bail out now -- we don't want
-    // fillForm() to try filling in a login without a username
-    // to filter on (bug 471906).
-    if (!acInputField.value) {
-      return;
-    }
-
-    log("onUsernameInput from", event.type);
+  onUsernameAutocompleted(acInputField, loginGUID = null) {
+    log("onUsernameAutocompleted:", acInputField);
 
     let acForm = LoginFormFactory.createFromField(acInputField);
     let doc = acForm.ownerDocument;
@@ -787,7 +802,10 @@ this.LoginManagerContent = {
     let [usernameField, passwordField, ignored] =
         this._getFormFields(acForm, false, recipes);
     if (usernameField == acInputField && passwordField) {
-      this._getLoginDataFromParent(acForm, { showMasterPassword: false })
+      this._getLoginDataFromParent(acForm, {
+        guid: loginGUID,
+        showMasterPassword: false,
+      })
           .then(({ form, loginsFound, recipes }) => {
             this._fillForm(form, loginsFound, recipes, {
               autofillForm: true,
diff --git a/toolkit/components/passwordmgr/LoginManagerParent.jsm b/toolkit/components/passwordmgr/LoginManagerParent.jsm
index c556492a079b..d17ca284c63b 100644
--- a/toolkit/components/passwordmgr/LoginManagerParent.jsm
+++ b/toolkit/components/passwordmgr/LoginManagerParent.jsm
@@ -107,11 +107,11 @@ this.LoginManagerParent = {
     switch (msg.name) {
       case "PasswordManager:findLogins": {
         // TODO Verify msg.target's principals against the formOrigin?
-        this.sendLoginDataToChild(data.options.showMasterPassword,
-                                  data.formOrigin,
+        this.sendLoginDataToChild(data.formOrigin,
                                   data.actionOrigin,
                                   data.requestId,
-                                  msg.target.messageManager);
+                                  msg.target.messageManager,
+                                  data.options);
         break;
       }
 
@@ -194,8 +194,10 @@ this.LoginManagerParent = {
   /**
    * Send relevant data (e.g. logins and recipes) to the child process (LoginManagerContent).
    */
-  async sendLoginDataToChild(showMasterPassword, formOrigin, actionOrigin,
-                             requestId, target) {
+  async sendLoginDataToChild(formOrigin, actionOrigin, requestId, target, {
+    guid,
+    showMasterPassword,
+  }) {
     let recipes = [];
     if (formOrigin) {
       let formHost;
@@ -244,8 +246,9 @@ this.LoginManagerParent = {
             return;
           }
 
-          self.sendLoginDataToChild(showMasterPassword, formOrigin, actionOrigin,
-                                    requestId, target);
+          self.sendLoginDataToChild(formOrigin, actionOrigin, requestId, target, {
+            showMasterPassword,
+          });
         },
       };
 
@@ -260,12 +263,19 @@ this.LoginManagerParent = {
     }
 
     // Autocomplete results do not need to match actionOrigin or exact hostname.
-    let logins = this._searchAndDedupeLogins(formOrigin,
-                                             actionOrigin,
-                                             {
-                                               ignoreActionAndRealm: true,
-                                               acceptDifferentSubdomains: INCLUDE_OTHER_SUBDOMAINS_IN_LOOKUP,
-                                             });
+    let logins = null;
+    if (guid) {
+      logins = LoginHelper.searchLoginsWithObject({
+        guid,
+      });
+    } else {
+      logins = this._searchAndDedupeLogins(formOrigin,
+                                           actionOrigin,
+                                           {
+                                             ignoreActionAndRealm: true,
+                                             acceptDifferentSubdomains: INCLUDE_OTHER_SUBDOMAINS_IN_LOOKUP, // TODO: for TAB case
+                                           });
+    }
 
     log("sendLoginDataToChild:", logins.length, "deduped logins");
     // Convert the array of nsILoginInfo to vanilla JS objects since nsILoginInfo
diff --git a/toolkit/components/passwordmgr/test/mochitest/test_basic_form_autocomplete_subdomain.html b/toolkit/components/passwordmgr/test/mochitest/test_basic_form_autocomplete_subdomain.html
index 6a0a29370a0f..fa9bf3bb3e5d 100644
--- a/toolkit/components/passwordmgr/test/mochitest/test_basic_form_autocomplete_subdomain.html
+++ b/toolkit/components/passwordmgr/test/mochitest/test_basic_form_autocomplete_subdomain.html
@@ -117,13 +117,8 @@ add_task(async function test_form1_menu_shows_two_logins_same_usernames_for_diff
 
   synthesizeKey("KEY_Enter");
   await promiseFormsProcessed();
-  todo_is(pword.value, "dsdp1",
-          `Bug 1166113: The password should match the login that was selected.
-           From bug 499649 when there are two logins with the same case we just
-           blindly select the last one always. We should instead remember which
-           login was selected from the dropdown and use that login's password
-           when possible`);
-  checkLoginForm(uname, "dsdu1", pword, "dsdp1prime");
+  is(pword.value, "dsdp1", "password should match the login that was selected");
+  checkLoginForm(uname, "dsdu1", pword, "dsdp1");
 
   restoreForm();
 
@@ -137,12 +132,7 @@ add_task(async function test_form1_menu_shows_two_logins_same_usernames_for_diff
 
   synthesizeKey("KEY_Enter");
   await promiseFormsProcessed();
-  todo_is(pword.value, "dsdp1",
-          `Bug 1166113: The password should match the login that was selected.
-           From bug 499649 when there are two logins with the same case we just
-           blindly select the last one always. We should instead remember which
-           login was selected from the dropdown and use that login's password
-           when possible`);
+  is(pword.value, "dsdp1prime", "Password should match the login that was selected");
   checkLoginForm(uname, "dsdu1", pword, "dsdp1prime");
 });
 
diff --git a/toolkit/components/passwordmgr/test/unit/test_login_autocomplete_result.js b/toolkit/components/passwordmgr/test/unit/test_login_autocomplete_result.js
index ad4a8a94506b..dd160d7ccd62 100644
--- a/toolkit/components/passwordmgr/test/unit/test_login_autocomplete_result.js
+++ b/toolkit/components/passwordmgr/test/unit/test_login_autocomplete_result.js
@@ -20,717 +20,723 @@ matchingLogins.push(new nsLoginInfo("http://mochi.test:8888", "http://autocomple
 matchingLogins.push(new nsLoginInfo("http://mochi.test:8888", "http://autocomplete:8888", null,
                                     "zzzuser4", "zzzpass4", "uname", "pword"));
 
-let meta = matchingLogins[0].QueryInterface(Ci.nsILoginMetaInfo);
-let dateAndTimeFormatter = new Services.intl.DateTimeFormat(undefined,
-                                                            { dateStyle: "medium" });
-let time = dateAndTimeFormatter.format(new Date(meta.timePasswordChanged));
-const LABEL_NO_USERNAME = "No username (" + time + ")";
-
-let expectedResults = [
-  {
-    insecureFieldWarningEnabled: true,
-    isSecure: true,
-    isPasswordField: false,
-    matchingLogins,
-    items: [{
-      value: "",
-      label: LABEL_NO_USERNAME,
-      style: "loginWithOrigin",
-      comment: "mochi.test:8888",
-    }, {
-      value: "tempuser1",
-      label: "tempuser1",
-      style: "loginWithOrigin",
-      comment: "mochi.test:8888",
-    }, {
-      value: "testuser2",
-      label: "testuser2",
-      style: "loginWithOrigin",
-      comment: "mochi.test:8888",
-    }, {
-      value: "testuser3",
-      label: "testuser3",
-      style: "loginWithOrigin",
-      comment: "mochi.test:8888",
-    }, {
-      value: "zzzuser4",
-      label: "zzzuser4",
-      style: "loginWithOrigin",
-      comment: "mochi.test:8888",
-    }, {
-      value: "",
-      label: "View Saved Logins",
-      style: "loginsFooter",
-      comment: "mochi.test",
-    }],
-  },
-  {
-    insecureFieldWarningEnabled: true,
-    isSecure: false,
-    isPasswordField: false,
-    matchingLogins: [],
-    items: [{
-      value: "",
-      label: "This connection is not secure. Logins entered here could be compromised. Learn More",
-      style: "insecureWarning",
-      comment: "",
-    }, {
-      value: "",
-      label: "View Saved Logins",
-      style: "loginsFooter",
-      comment: "mochi.test",
-    }],
-  },
-  {
-    insecureFieldWarningEnabled: true,
-    isSecure: false,
-    isPasswordField: false,
-    matchingLogins,
-    items: [{
-      value: "",
-      label: "This connection is not secure. Logins entered here could be compromised. Learn More",
-      style: "insecureWarning",
-      comment: "",
-    }, {
-      value: "",
-      label: LABEL_NO_USERNAME,
-      style: "loginWithOrigin",
-      comment: "mochi.test:8888",
-    }, {
-      value: "tempuser1",
-      label: "tempuser1",
-      style: "loginWithOrigin",
-      comment: "mochi.test:8888",
-    }, {
-      value: "testuser2",
-      label: "testuser2",
-      style: "loginWithOrigin",
-      comment: "mochi.test:8888",
-    }, {
-      value: "testuser3",
-      label: "testuser3",
-      style: "loginWithOrigin",
-      comment: "mochi.test:8888",
-    }, {
-      value: "zzzuser4",
-      label: "zzzuser4",
-      style: "loginWithOrigin",
-      comment: "mochi.test:8888",
-    }, {
-      value: "",
-      label: "View Saved Logins",
-      style: "loginsFooter",
-      comment: "mochi.test",
-    }],
-  },
-  {
-    insecureFieldWarningEnabled: true,
-    isSecure: true,
-    isPasswordField: true,
-    matchingLogins,
-    items: [{
-      value: "emptypass1",
-      label: LABEL_NO_USERNAME,
-      style: "loginWithOrigin",
-      comment: "mochi.test:8888",
-    }, {
-      value: "temppass1",
-      label: "tempuser1",
-      style: "loginWithOrigin",
-      comment: "mochi.test:8888",
-    }, {
-      value: "testpass2",
-      label: "testuser2",
-      style: "loginWithOrigin",
-      comment: "mochi.test:8888",
-    }, {
-      value: "testpass3",
-      label: "testuser3",
-      style: "loginWithOrigin",
-      comment: "mochi.test:8888",
-    }, {
-      value: "zzzpass4",
-      label: "zzzuser4",
-      style: "loginWithOrigin",
-      comment: "mochi.test:8888",
-    }, {
-      value: "",
-      label: "View Saved Logins",
-      style: "loginsFooter",
-      comment: "mochi.test",
-    }],
-  },
-  {
-    insecureFieldWarningEnabled: true,
-    isSecure: false,
-    isPasswordField: true,
-    matchingLogins,
-    items: [{
-      value: "",
-      label: "This connection is not secure. Logins entered here could be compromised. Learn More",
-      style: "insecureWarning",
-      comment: "",
-    }, {
-      value: "emptypass1",
-      label: LABEL_NO_USERNAME,
-      style: "loginWithOrigin",
-      comment: "mochi.test:8888",
-    }, {
-      value: "temppass1",
-      label: "tempuser1",
-      style: "loginWithOrigin",
-      comment: "mochi.test:8888",
-    }, {
-      value: "testpass2",
-      label: "testuser2",
-      style: "loginWithOrigin",
-      comment: "mochi.test:8888",
-    }, {
-      value: "testpass3",
-      label: "testuser3",
-      style: "loginWithOrigin",
-      comment: "mochi.test:8888",
-    }, {
-      value: "zzzpass4",
-      label: "zzzuser4",
-      style: "loginWithOrigin",
-      comment: "mochi.test:8888",
-    }, {
-      value: "",
-      label: "View Saved Logins",
-      style: "loginsFooter",
-      comment: "mochi.test",
-    }],
-  },
-  {
-    insecureFieldWarningEnabled: false,
-    isSecure: true,
-    isPasswordField: false,
-    matchingLogins,
-    items: [{
-      value: "",
-      label: LABEL_NO_USERNAME,
-      style: "loginWithOrigin",
-      comment: "mochi.test:8888",
-    }, {
-      value: "tempuser1",
-      label: "tempuser1",
-      style: "loginWithOrigin",
-      comment: "mochi.test:8888",
-    }, {
-      value: "testuser2",
-      label: "testuser2",
-      style: "loginWithOrigin",
-      comment: "mochi.test:8888",
-    }, {
-      value: "testuser3",
-      label: "testuser3",
-      style: "loginWithOrigin",
-      comment: "mochi.test:8888",
-    }, {
-      value: "zzzuser4",
-      label: "zzzuser4",
-      style: "loginWithOrigin",
-      comment: "mochi.test:8888",
-    }, {
-      value: "",
-      label: "View Saved Logins",
-      style: "loginsFooter",
-      comment: "mochi.test",
-    }],
-  },
-  {
-    insecureFieldWarningEnabled: false,
-    isSecure: false,
-    isPasswordField: false,
-    matchingLogins,
-    items: [{
-      value: "",
-      label: LABEL_NO_USERNAME,
-      style: "loginWithOrigin",
-      comment: "mochi.test:8888",
-    }, {
-      value: "tempuser1",
-      label: "tempuser1",
-      style: "loginWithOrigin",
-      comment: "mochi.test:8888",
-    }, {
-      value: "testuser2",
-      label: "testuser2",
-      style: "loginWithOrigin",
-      comment: "mochi.test:8888",
-    }, {
-      value: "testuser3",
-      label: "testuser3",
-      style: "loginWithOrigin",
-      comment: "mochi.test:8888",
-    }, {
-      value: "zzzuser4",
-      label: "zzzuser4",
-      style: "loginWithOrigin",
-      comment: "mochi.test:8888",
-    }, {
-      value: "",
-      label: "View Saved Logins",
-      style: "loginsFooter",
-      comment: "mochi.test",
-    }],
-  },
-  {
-    insecureFieldWarningEnabled: false,
-    isSecure: true,
-    isPasswordField: true,
-    matchingLogins,
-    items: [{
-      value: "emptypass1",
-      label: LABEL_NO_USERNAME,
-      style: "loginWithOrigin",
-      comment: "mochi.test:8888",
-    }, {
-      value: "temppass1",
-      label: "tempuser1",
-      style: "loginWithOrigin",
-      comment: "mochi.test:8888",
-    }, {
-      value: "testpass2",
-      label: "testuser2",
-      style: "loginWithOrigin",
-      comment: "mochi.test:8888",
-    }, {
-      value: "testpass3",
-      label: "testuser3",
-      style: "loginWithOrigin",
-      comment: "mochi.test:8888",
-    }, {
-      value: "zzzpass4",
-      label: "zzzuser4",
-      style: "loginWithOrigin",
-      comment: "mochi.test:8888",
-    }, {
-      value: "",
-      label: "View Saved Logins",
-      style: "loginsFooter",
-      comment: "mochi.test",
-    }],
-  },
-  {
-    insecureFieldWarningEnabled: false,
-    isSecure: false,
-    isPasswordField: true,
-    matchingLogins,
-    items: [{
-      value: "emptypass1",
-      label: LABEL_NO_USERNAME,
-      style: "loginWithOrigin",
-      comment: "mochi.test:8888",
-    }, {
-      value: "temppass1",
-      label: "tempuser1",
-      style: "loginWithOrigin",
-      comment: "mochi.test:8888",
-    }, {
-      value: "testpass2",
-      label: "testuser2",
-      style: "loginWithOrigin",
-      comment: "mochi.test:8888",
-    }, {
-      value: "testpass3",
-      label: "testuser3",
-      style: "loginWithOrigin",
-      comment: "mochi.test:8888",
-    }, {
-      value: "zzzpass4",
-      label: "zzzuser4",
-      style: "loginWithOrigin",
-      comment: "mochi.test:8888",
-    }, {
-      value: "",
-      label: "View Saved Logins",
-      style: "loginsFooter",
-      comment: "mochi.test",
-    }],
-  },
-  {
-    insecureFieldWarningEnabled: true,
-    isSecure: true,
-    isPasswordField: false,
-    matchingLogins,
-    items: [{
-      value: "",
-      label: LABEL_NO_USERNAME,
-      style: "loginWithOrigin",
-      comment: "mochi.test:8888",
-    }, {
-      value: "tempuser1",
-      label: "tempuser1",
-      style: "loginWithOrigin",
-      comment: "mochi.test:8888",
-    }, {
-      value: "testuser2",
-      label: "testuser2",
-      style: "loginWithOrigin",
-      comment: "mochi.test:8888",
-    }, {
-      value: "testuser3",
-      label: "testuser3",
-      style: "loginWithOrigin",
-      comment: "mochi.test:8888",
-    }, {
-      value: "zzzuser4",
-      label: "zzzuser4",
-      style: "loginWithOrigin",
-      comment: "mochi.test:8888",
-    }, {
-      value: "",
-      label: "View Saved Logins",
-      style: "loginsFooter",
-      comment: "mochi.test",
-    }],
-  },
-  {
-    insecureFieldWarningEnabled: true,
-    isSecure: false,
-    isPasswordField: false,
-    matchingLogins,
-    items: [{
-      value: "",
-      label: "This connection is not secure. Logins entered here could be compromised. Learn More",
-      style: "insecureWarning",
-      comment: "",
-    }, {
-      value: "",
-      label: LABEL_NO_USERNAME,
-      style: "loginWithOrigin",
-      comment: "mochi.test:8888",
-    }, {
-      value: "tempuser1",
-      label: "tempuser1",
-      style: "loginWithOrigin",
-      comment: "mochi.test:8888",
-    }, {
-      value: "testuser2",
-      label: "testuser2",
-      style: "loginWithOrigin",
-      comment: "mochi.test:8888",
-    }, {
-      value: "testuser3",
-      label: "testuser3",
-      style: "loginWithOrigin",
-      comment: "mochi.test:8888",
-    }, {
-      value: "zzzuser4",
-      label: "zzzuser4",
-      style: "loginWithOrigin",
-      comment: "mochi.test:8888",
-    }, {
-      value: "",
-      label: "View Saved Logins",
-      style: "loginsFooter",
-      comment: "mochi.test",
-    }],
-  },
-  {
-    insecureFieldWarningEnabled: true,
-    isSecure: true,
-    isPasswordField: true,
-    matchingLogins,
-    items: [{
-      value: "emptypass1",
-      label: LABEL_NO_USERNAME,
-      style: "loginWithOrigin",
-      comment: "mochi.test:8888",
-    }, {
-      value: "temppass1",
-      label: "tempuser1",
-      style: "loginWithOrigin",
-      comment: "mochi.test:8888",
-    }, {
-      value: "testpass2",
-      label: "testuser2",
-      style: "loginWithOrigin",
-      comment: "mochi.test:8888",
-    }, {
-      value: "testpass3",
-      label: "testuser3",
-      style: "loginWithOrigin",
-      comment: "mochi.test:8888",
-    }, {
-      value: "zzzpass4",
-      label: "zzzuser4",
-      style: "loginWithOrigin",
-      comment: "mochi.test:8888",
-    }, {
-      value: "",
-      label: "View Saved Logins",
-      style: "loginsFooter",
-      comment: "mochi.test",
-    }],
-  },
-  {
-    insecureFieldWarningEnabled: true,
-    isSecure: false,
-    isPasswordField: true,
-    matchingLogins,
-    items: [{
-      value: "",
-      label: "This connection is not secure. Logins entered here could be compromised. Learn More",
-      style: "insecureWarning",
-      comment: "",
-    }, {
-      value: "emptypass1",
-      label: LABEL_NO_USERNAME,
-      style: "loginWithOrigin",
-      comment: "mochi.test:8888",
-    }, {
-      value: "temppass1",
-      label: "tempuser1",
-      style: "loginWithOrigin",
-      comment: "mochi.test:8888",
-    }, {
-      value: "testpass2",
-      label: "testuser2",
-      style: "loginWithOrigin",
-      comment: "mochi.test:8888",
-    }, {
-      value: "testpass3",
-      label: "testuser3",
-      style: "loginWithOrigin",
-      comment: "mochi.test:8888",
-    }, {
-      value: "zzzpass4",
-      label: "zzzuser4",
-      style: "loginWithOrigin",
-      comment: "mochi.test:8888",
-    }, {
-      value: "",
-      label: "View Saved Logins",
-      style: "loginsFooter",
-      comment: "mochi.test",
-    }],
-  },
-  {
-    insecureFieldWarningEnabled: false,
-    isSecure: true,
-    isPasswordField: false,
-    matchingLogins,
-    items: [{
-      value: "",
-      label: LABEL_NO_USERNAME,
-      style: "loginWithOrigin",
-      comment: "mochi.test:8888",
-    }, {
-      value: "tempuser1",
-      label: "tempuser1",
-      style: "loginWithOrigin",
-      comment: "mochi.test:8888",
-    }, {
-      value: "testuser2",
-      label: "testuser2",
-      style: "loginWithOrigin",
-      comment: "mochi.test:8888",
-    }, {
-      value: "testuser3",
-      label: "testuser3",
-      style: "loginWithOrigin",
-      comment: "mochi.test:8888",
-    }, {
-      value: "zzzuser4",
-      label: "zzzuser4",
-      style: "loginWithOrigin",
-      comment: "mochi.test:8888",
-    }, {
-      value: "",
-      label: "View Saved Logins",
-      style: "loginsFooter",
-      comment: "mochi.test",
-    }],
-  },
-  {
-    insecureFieldWarningEnabled: false,
-    isSecure: false,
-    isPasswordField: false,
-    matchingLogins: [],
-    items: [{
-      value: "",
-      label: "View Saved Logins",
-      style: "loginsFooter",
-      comment: "mochi.test",
-    }],
-  },
-  {
-    insecureFieldWarningEnabled: false,
-    isSecure: false,
-    isPasswordField: false,
-    matchingLogins: [],
-    searchString: "foo",
-    items: [{
-      value: "",
-      label: "View Saved Logins",
-      style: "loginsFooter",
-      comment: "mochi.test",
-    }],
-  },
-  {
-    insecureFieldWarningEnabled: false,
-    isSecure: false,
-    isPasswordField: false,
-    matchingLogins,
-    items: [{
-      value: "",
-      label: LABEL_NO_USERNAME,
-      style: "loginWithOrigin",
-      comment: "mochi.test:8888",
-    }, {
-      value: "tempuser1",
-      label: "tempuser1",
-      style: "loginWithOrigin",
-      comment: "mochi.test:8888",
-    }, {
-      value: "testuser2",
-      label: "testuser2",
-      style: "loginWithOrigin",
-      comment: "mochi.test:8888",
-    }, {
-      value: "testuser3",
-      label: "testuser3",
-      style: "loginWithOrigin",
-      comment: "mochi.test:8888",
-    }, {
-      value: "zzzuser4",
-      label: "zzzuser4",
-      style: "loginWithOrigin",
-      comment: "mochi.test:8888",
-    }, {
-      value: "",
-      label: "View Saved Logins",
-      style: "loginsFooter",
-      comment: "mochi.test",
-    }],
-  },
-  {
-    insecureFieldWarningEnabled: false,
-    isSecure: true,
-    isPasswordField: true,
-    matchingLogins,
-    items: [{
-      value: "emptypass1",
-      label: LABEL_NO_USERNAME,
-      style: "loginWithOrigin",
-      comment: "mochi.test:8888",
-    }, {
-      value: "temppass1",
-      label: "tempuser1",
-      style: "loginWithOrigin",
-      comment: "mochi.test:8888",
-    }, {
-      value: "testpass2",
-      label: "testuser2",
-      style: "loginWithOrigin",
-      comment: "mochi.test:8888",
-    }, {
-      value: "testpass3",
-      label: "testuser3",
-      style: "loginWithOrigin",
-      comment: "mochi.test:8888",
-    }, {
-      value: "zzzpass4",
-      label: "zzzuser4",
-      style: "loginWithOrigin",
-      comment: "mochi.test:8888",
-    }, {
-      value: "",
-      label: "View Saved Logins",
-      style: "loginsFooter",
-      comment: "mochi.test",
-    }],
-  },
-  {
-    insecureFieldWarningEnabled: false,
-    isSecure: false,
-    isPasswordField: true,
-    matchingLogins,
-    items: [{
-      value: "emptypass1",
-      label: LABEL_NO_USERNAME,
-      style: "loginWithOrigin",
-      comment: "mochi.test:8888",
-    }, {
-      value: "temppass1",
-      label: "tempuser1",
-      style: "loginWithOrigin",
-      comment: "mochi.test:8888",
-    }, {
-      value: "testpass2",
-      label: "testuser2",
-      style: "loginWithOrigin",
-      comment: "mochi.test:8888",
-    }, {
-      value: "testpass3",
-      label: "testuser3",
-      style: "loginWithOrigin",
-      comment: "mochi.test:8888",
-    }, {
-      value: "zzzpass4",
-      label: "zzzuser4",
-      style: "loginWithOrigin",
-      comment: "mochi.test:8888",
-    }, {
-      value: "",
-      label: "View Saved Logins",
-      style: "loginsFooter",
-      comment: "mochi.test",
-    }],
-  },
-  {
-    insecureFieldWarningEnabled: true,
-    isSecure: true,
-    isPasswordField: true,
-    matchingLogins: [],
-    items: [{
-      value: "",
-      label: "View Saved Logins",
-      style: "loginsFooter",
-      comment: "mochi.test",
-    }],
-  },
-  {
-    insecureFieldWarningEnabled: true,
-    isSecure: true,
-    isPasswordField: true,
-    matchingLogins: [],
-    searchString: "foo",
-    items: [],
-  },
-  {
-    generatedPassword: "9ljgfd4shyktb45",
-    insecureFieldWarningEnabled: true,
-    isSecure: true,
-    isPasswordField: true,
-    matchingLogins: [],
-    items: [{
-      value: "9ljgfd4shyktb45",
-      label: "Use Generated Password",
-      style: "generatedPassword",
-      comment: "9ljgfd4shyktb45",
-    }, {
-      value: "",
-      label: "View Saved Logins",
-      style: "loginsFooter",
-      comment: "mochi.test",
-    }],
-  },
-  {
-    generatedPassword: "9ljgfd4shyktb45",
-    insecureFieldWarningEnabled: true,
-    isSecure: true,
-    isPasswordField: true,
-    matchingLogins: [],
-    searchString: "9ljgfd4shyktb45",
-    items: [],
-  },
-];
+add_task(async function setup() {
+  // Get a profile so we have storage access and insert the logins to get unique GUIDs.
+  do_get_profile();
+  matchingLogins = await Services.logins.addLogins(matchingLogins);
+});
 
 add_task(async function test_all_patterns() {
+  let meta = matchingLogins[0].QueryInterface(Ci.nsILoginMetaInfo);
+  let dateAndTimeFormatter = new Services.intl.DateTimeFormat(undefined,
+                                                              { dateStyle: "medium" });
+  let time = dateAndTimeFormatter.format(new Date(meta.timePasswordChanged));
+  const LABEL_NO_USERNAME = "No username (" + time + ")";
+
+  let expectedResults = [
+    {
+      insecureFieldWarningEnabled: true,
+      isSecure: true,
+      isPasswordField: false,
+      matchingLogins,
+      items: [{
+        value: "",
+        label: LABEL_NO_USERNAME,
+        style: "loginWithOrigin",
+        comment: { comment: "mochi.test:8888" },
+      }, {
+        value: "tempuser1",
+        label: "tempuser1",
+        style: "loginWithOrigin",
+        comment: { comment: "mochi.test:8888" },
+      }, {
+        value: "testuser2",
+        label: "testuser2",
+        style: "loginWithOrigin",
+        comment: { comment: "mochi.test:8888" },
+      }, {
+        value: "testuser3",
+        label: "testuser3",
+        style: "loginWithOrigin",
+        comment: { comment: "mochi.test:8888" },
+      }, {
+        value: "zzzuser4",
+        label: "zzzuser4",
+        style: "loginWithOrigin",
+        comment: { comment: "mochi.test:8888" },
+      }, {
+        value: "",
+        label: "View Saved Logins",
+        style: "loginsFooter",
+        comment: "mochi.test",
+      }],
+    },
+    {
+      insecureFieldWarningEnabled: true,
+      isSecure: false,
+      isPasswordField: false,
+      matchingLogins: [],
+      items: [{
+        value: "",
+        label: "This connection is not secure. Logins entered here could be compromised. Learn More",
+        style: "insecureWarning",
+        comment: "",
+      }, {
+        value: "",
+        label: "View Saved Logins",
+        style: "loginsFooter",
+        comment: "mochi.test",
+      }],
+    },
+    {
+      insecureFieldWarningEnabled: true,
+      isSecure: false,
+      isPasswordField: false,
+      matchingLogins,
+      items: [{
+        value: "",
+        label: "This connection is not secure. Logins entered here could be compromised. Learn More",
+        style: "insecureWarning",
+        comment: "",
+      }, {
+        value: "",
+        label: LABEL_NO_USERNAME,
+        style: "loginWithOrigin",
+        comment: { comment: "mochi.test:8888" },
+      }, {
+        value: "tempuser1",
+        label: "tempuser1",
+        style: "loginWithOrigin",
+        comment: { comment: "mochi.test:8888" },
+      }, {
+        value: "testuser2",
+        label: "testuser2",
+        style: "loginWithOrigin",
+        comment: { comment: "mochi.test:8888" },
+      }, {
+        value: "testuser3",
+        label: "testuser3",
+        style: "loginWithOrigin",
+        comment: { comment: "mochi.test:8888" },
+      }, {
+        value: "zzzuser4",
+        label: "zzzuser4",
+        style: "loginWithOrigin",
+        comment: { comment: "mochi.test:8888" },
+      }, {
+        value: "",
+        label: "View Saved Logins",
+        style: "loginsFooter",
+        comment: "mochi.test",
+      }],
+    },
+    {
+      insecureFieldWarningEnabled: true,
+      isSecure: true,
+      isPasswordField: true,
+      matchingLogins,
+      items: [{
+        value: "emptypass1",
+        label: LABEL_NO_USERNAME,
+        style: "loginWithOrigin",
+        comment: { comment: "mochi.test:8888" },
+      }, {
+        value: "temppass1",
+        label: "tempuser1",
+        style: "loginWithOrigin",
+        comment: { comment: "mochi.test:8888" },
+      }, {
+        value: "testpass2",
+        label: "testuser2",
+        style: "loginWithOrigin",
+        comment: { comment: "mochi.test:8888" },
+      }, {
+        value: "testpass3",
+        label: "testuser3",
+        style: "loginWithOrigin",
+        comment: { comment: "mochi.test:8888" },
+      }, {
+        value: "zzzpass4",
+        label: "zzzuser4",
+        style: "loginWithOrigin",
+        comment: { comment: "mochi.test:8888" },
+      }, {
+        value: "",
+        label: "View Saved Logins",
+        style: "loginsFooter",
+        comment: "mochi.test",
+      }],
+    },
+    {
+      insecureFieldWarningEnabled: true,
+      isSecure: false,
+      isPasswordField: true,
+      matchingLogins,
+      items: [{
+        value: "",
+        label: "This connection is not secure. Logins entered here could be compromised. Learn More",
+        style: "insecureWarning",
+        comment: "",
+      }, {
+        value: "emptypass1",
+        label: LABEL_NO_USERNAME,
+        style: "loginWithOrigin",
+        comment: { comment: "mochi.test:8888" },
+      }, {
+        value: "temppass1",
+        label: "tempuser1",
+        style: "loginWithOrigin",
+        comment: { comment: "mochi.test:8888" },
+      }, {
+        value: "testpass2",
+        label: "testuser2",
+        style: "loginWithOrigin",
+        comment: { comment: "mochi.test:8888" },
+      }, {
+        value: "testpass3",
+        label: "testuser3",
+        style: "loginWithOrigin",
+        comment: { comment: "mochi.test:8888" },
+      }, {
+        value: "zzzpass4",
+        label: "zzzuser4",
+        style: "loginWithOrigin",
+        comment: { comment: "mochi.test:8888" },
+      }, {
+        value: "",
+        label: "View Saved Logins",
+        style: "loginsFooter",
+        comment: "mochi.test",
+      }],
+    },
+    {
+      insecureFieldWarningEnabled: false,
+      isSecure: true,
+      isPasswordField: false,
+      matchingLogins,
+      items: [{
+        value: "",
+        label: LABEL_NO_USERNAME,
+        style: "loginWithOrigin",
+        comment: { comment: "mochi.test:8888" },
+      }, {
+        value: "tempuser1",
+        label: "tempuser1",
+        style: "loginWithOrigin",
+        comment: { comment: "mochi.test:8888" },
+      }, {
+        value: "testuser2",
+        label: "testuser2",
+        style: "loginWithOrigin",
+        comment: { comment: "mochi.test:8888" },
+      }, {
+        value: "testuser3",
+        label: "testuser3",
+        style: "loginWithOrigin",
+        comment: { comment: "mochi.test:8888" },
+      }, {
+        value: "zzzuser4",
+        label: "zzzuser4",
+        style: "loginWithOrigin",
+        comment: { comment: "mochi.test:8888" },
+      }, {
+        value: "",
+        label: "View Saved Logins",
+        style: "loginsFooter",
+        comment: "mochi.test",
+      }],
+    },
+    {
+      insecureFieldWarningEnabled: false,
+      isSecure: false,
+      isPasswordField: false,
+      matchingLogins,
+      items: [{
+        value: "",
+        label: LABEL_NO_USERNAME,
+        style: "loginWithOrigin",
+        comment: { comment: "mochi.test:8888" },
+      }, {
+        value: "tempuser1",
+        label: "tempuser1",
+        style: "loginWithOrigin",
+        comment: { comment: "mochi.test:8888" },
+      }, {
+        value: "testuser2",
+        label: "testuser2",
+        style: "loginWithOrigin",
+        comment: { comment: "mochi.test:8888" },
+      }, {
+        value: "testuser3",
+        label: "testuser3",
+        style: "loginWithOrigin",
+        comment: { comment: "mochi.test:8888" },
+      }, {
+        value: "zzzuser4",
+        label: "zzzuser4",
+        style: "loginWithOrigin",
+        comment: { comment: "mochi.test:8888" },
+      }, {
+        value: "",
+        label: "View Saved Logins",
+        style: "loginsFooter",
+        comment: "mochi.test",
+      }],
+    },
+    {
+      insecureFieldWarningEnabled: false,
+      isSecure: true,
+      isPasswordField: true,
+      matchingLogins,
+      items: [{
+        value: "emptypass1",
+        label: LABEL_NO_USERNAME,
+        style: "loginWithOrigin",
+        comment: { comment: "mochi.test:8888" },
+      }, {
+        value: "temppass1",
+        label: "tempuser1",
+        style: "loginWithOrigin",
+        comment: { comment: "mochi.test:8888" },
+      }, {
+        value: "testpass2",
+        label: "testuser2",
+        style: "loginWithOrigin",
+        comment: { comment: "mochi.test:8888" },
+      }, {
+        value: "testpass3",
+        label: "testuser3",
+        style: "loginWithOrigin",
+        comment: { comment: "mochi.test:8888" },
+      }, {
+        value: "zzzpass4",
+        label: "zzzuser4",
+        style: "loginWithOrigin",
+        comment: { comment: "mochi.test:8888" },
+      }, {
+        value: "",
+        label: "View Saved Logins",
+        style: "loginsFooter",
+        comment: "mochi.test",
+      }],
+    },
+    {
+      insecureFieldWarningEnabled: false,
+      isSecure: false,
+      isPasswordField: true,
+      matchingLogins,
+      items: [{
+        value: "emptypass1",
+        label: LABEL_NO_USERNAME,
+        style: "loginWithOrigin",
+        comment: { comment: "mochi.test:8888" },
+      }, {
+        value: "temppass1",
+        label: "tempuser1",
+        style: "loginWithOrigin",
+        comment: { comment: "mochi.test:8888" },
+      }, {
+        value: "testpass2",
+        label: "testuser2",
+        style: "loginWithOrigin",
+        comment: { comment: "mochi.test:8888" },
+      }, {
+        value: "testpass3",
+        label: "testuser3",
+        style: "loginWithOrigin",
+        comment: { comment: "mochi.test:8888" },
+      }, {
+        value: "zzzpass4",
+        label: "zzzuser4",
+        style: "loginWithOrigin",
+        comment: { comment: "mochi.test:8888" },
+      }, {
+        value: "",
+        label: "View Saved Logins",
+        style: "loginsFooter",
+        comment: "mochi.test",
+      }],
+    },
+    {
+      insecureFieldWarningEnabled: true,
+      isSecure: true,
+      isPasswordField: false,
+      matchingLogins,
+      items: [{
+        value: "",
+        label: LABEL_NO_USERNAME,
+        style: "loginWithOrigin",
+        comment: { comment: "mochi.test:8888" },
+      }, {
+        value: "tempuser1",
+        label: "tempuser1",
+        style: "loginWithOrigin",
+        comment: { comment: "mochi.test:8888" },
+      }, {
+        value: "testuser2",
+        label: "testuser2",
+        style: "loginWithOrigin",
+        comment: { comment: "mochi.test:8888" },
+      }, {
+        value: "testuser3",
+        label: "testuser3",
+        style: "loginWithOrigin",
+        comment: { comment: "mochi.test:8888" },
+      }, {
+        value: "zzzuser4",
+        label: "zzzuser4",
+        style: "loginWithOrigin",
+        comment: { comment: "mochi.test:8888" },
+      }, {
+        value: "",
+        label: "View Saved Logins",
+        style: "loginsFooter",
+        comment: "mochi.test",
+      }],
+    },
+    {
+      insecureFieldWarningEnabled: true,
+      isSecure: false,
+      isPasswordField: false,
+      matchingLogins,
+      items: [{
+        value: "",
+        label: "This connection is not secure. Logins entered here could be compromised. Learn More",
+        style: "insecureWarning",
+        comment: "",
+      }, {
+        value: "",
+        label: LABEL_NO_USERNAME,
+        style: "loginWithOrigin",
+        comment: { comment: "mochi.test:8888" },
+      }, {
+        value: "tempuser1",
+        label: "tempuser1",
+        style: "loginWithOrigin",
+        comment: { comment: "mochi.test:8888" },
+      }, {
+        value: "testuser2",
+        label: "testuser2",
+        style: "loginWithOrigin",
+        comment: { comment: "mochi.test:8888" },
+      }, {
+        value: "testuser3",
+        label: "testuser3",
+        style: "loginWithOrigin",
+        comment: { comment: "mochi.test:8888" },
+      }, {
+        value: "zzzuser4",
+        label: "zzzuser4",
+        style: "loginWithOrigin",
+        comment: { comment: "mochi.test:8888" },
+      }, {
+        value: "",
+        label: "View Saved Logins",
+        style: "loginsFooter",
+        comment: "mochi.test",
+      }],
+    },
+    {
+      insecureFieldWarningEnabled: true,
+      isSecure: true,
+      isPasswordField: true,
+      matchingLogins,
+      items: [{
+        value: "emptypass1",
+        label: LABEL_NO_USERNAME,
+        style: "loginWithOrigin",
+        comment: { comment: "mochi.test:8888" },
+      }, {
+        value: "temppass1",
+        label: "tempuser1",
+        style: "loginWithOrigin",
+        comment: { comment: "mochi.test:8888" },
+      }, {
+        value: "testpass2",
+        label: "testuser2",
+        style: "loginWithOrigin",
+        comment: { comment: "mochi.test:8888" },
+      }, {
+        value: "testpass3",
+        label: "testuser3",
+        style: "loginWithOrigin",
+        comment: { comment: "mochi.test:8888" },
+      }, {
+        value: "zzzpass4",
+        label: "zzzuser4",
+        style: "loginWithOrigin",
+        comment: { comment: "mochi.test:8888" },
+      }, {
+        value: "",
+        label: "View Saved Logins",
+        style: "loginsFooter",
+        comment: "mochi.test",
+      }],
+    },
+    {
+      insecureFieldWarningEnabled: true,
+      isSecure: false,
+      isPasswordField: true,
+      matchingLogins,
+      items: [{
+        value: "",
+        label: "This connection is not secure. Logins entered here could be compromised. Learn More",
+        style: "insecureWarning",
+        comment: "",
+      }, {
+        value: "emptypass1",
+        label: LABEL_NO_USERNAME,
+        style: "loginWithOrigin",
+        comment: { comment: "mochi.test:8888" },
+      }, {
+        value: "temppass1",
+        label: "tempuser1",
+        style: "loginWithOrigin",
+        comment: { comment: "mochi.test:8888" },
+      }, {
+        value: "testpass2",
+        label: "testuser2",
+        style: "loginWithOrigin",
+        comment: { comment: "mochi.test:8888" },
+      }, {
+        value: "testpass3",
+        label: "testuser3",
+        style: "loginWithOrigin",
+        comment: { comment: "mochi.test:8888" },
+      }, {
+        value: "zzzpass4",
+        label: "zzzuser4",
+        style: "loginWithOrigin",
+        comment: { comment: "mochi.test:8888" },
+      }, {
+        value: "",
+        label: "View Saved Logins",
+        style: "loginsFooter",
+        comment: "mochi.test",
+      }],
+    },
+    {
+      insecureFieldWarningEnabled: false,
+      isSecure: true,
+      isPasswordField: false,
+      matchingLogins,
+      items: [{
+        value: "",
+        label: LABEL_NO_USERNAME,
+        style: "loginWithOrigin",
+        comment: { comment: "mochi.test:8888" },
+      }, {
+        value: "tempuser1",
+        label: "tempuser1",
+        style: "loginWithOrigin",
+        comment: { comment: "mochi.test:8888" },
+      }, {
+        value: "testuser2",
+        label: "testuser2",
+        style: "loginWithOrigin",
+        comment: { comment: "mochi.test:8888" },
+      }, {
+        value: "testuser3",
+        label: "testuser3",
+        style: "loginWithOrigin",
+        comment: { comment: "mochi.test:8888" },
+      }, {
+        value: "zzzuser4",
+        label: "zzzuser4",
+        style: "loginWithOrigin",
+        comment: { comment: "mochi.test:8888" },
+      }, {
+        value: "",
+        label: "View Saved Logins",
+        style: "loginsFooter",
+        comment: "mochi.test",
+      }],
+    },
+    {
+      insecureFieldWarningEnabled: false,
+      isSecure: false,
+      isPasswordField: false,
+      matchingLogins: [],
+      items: [{
+        value: "",
+        label: "View Saved Logins",
+        style: "loginsFooter",
+        comment: "mochi.test",
+      }],
+    },
+    {
+      insecureFieldWarningEnabled: false,
+      isSecure: false,
+      isPasswordField: false,
+      matchingLogins: [],
+      searchString: "foo",
+      items: [{
+        value: "",
+        label: "View Saved Logins",
+        style: "loginsFooter",
+        comment: "mochi.test",
+      }],
+    },
+    {
+      insecureFieldWarningEnabled: false,
+      isSecure: false,
+      isPasswordField: false,
+      matchingLogins,
+      items: [{
+        value: "",
+        label: LABEL_NO_USERNAME,
+        style: "loginWithOrigin",
+        comment: { comment: "mochi.test:8888" },
+      }, {
+        value: "tempuser1",
+        label: "tempuser1",
+        style: "loginWithOrigin",
+        comment: { comment: "mochi.test:8888" },
+      }, {
+        value: "testuser2",
+        label: "testuser2",
+        style: "loginWithOrigin",
+        comment: { comment: "mochi.test:8888" },
+      }, {
+        value: "testuser3",
+        label: "testuser3",
+        style: "loginWithOrigin",
+        comment: { comment: "mochi.test:8888" },
+      }, {
+        value: "zzzuser4",
+        label: "zzzuser4",
+        style: "loginWithOrigin",
+        comment: { comment: "mochi.test:8888" },
+      }, {
+        value: "",
+        label: "View Saved Logins",
+        style: "loginsFooter",
+        comment: "mochi.test",
+      }],
+    },
+    {
+      insecureFieldWarningEnabled: false,
+      isSecure: true,
+      isPasswordField: true,
+      matchingLogins,
+      items: [{
+        value: "emptypass1",
+        label: LABEL_NO_USERNAME,
+        style: "loginWithOrigin",
+        comment: { comment: "mochi.test:8888" },
+      }, {
+        value: "temppass1",
+        label: "tempuser1",
+        style: "loginWithOrigin",
+        comment: { comment: "mochi.test:8888" },
+      }, {
+        value: "testpass2",
+        label: "testuser2",
+        style: "loginWithOrigin",
+        comment: { comment: "mochi.test:8888" },
+      }, {
+        value: "testpass3",
+        label: "testuser3",
+        style: "loginWithOrigin",
+        comment: { comment: "mochi.test:8888" },
+      }, {
+        value: "zzzpass4",
+        label: "zzzuser4",
+        style: "loginWithOrigin",
+        comment: { comment: "mochi.test:8888" },
+      }, {
+        value: "",
+        label: "View Saved Logins",
+        style: "loginsFooter",
+        comment: "mochi.test",
+      }],
+    },
+    {
+      insecureFieldWarningEnabled: false,
+      isSecure: false,
+      isPasswordField: true,
+      matchingLogins,
+      items: [{
+        value: "emptypass1",
+        label: LABEL_NO_USERNAME,
+        style: "loginWithOrigin",
+        comment: { comment: "mochi.test:8888" },
+      }, {
+        value: "temppass1",
+        label: "tempuser1",
+        style: "loginWithOrigin",
+        comment: { comment: "mochi.test:8888" },
+      }, {
+        value: "testpass2",
+        label: "testuser2",
+        style: "loginWithOrigin",
+        comment: { comment: "mochi.test:8888" },
+      }, {
+        value: "testpass3",
+        label: "testuser3",
+        style: "loginWithOrigin",
+        comment: { comment: "mochi.test:8888" },
+      }, {
+        value: "zzzpass4",
+        label: "zzzuser4",
+        style: "loginWithOrigin",
+        comment: { comment: "mochi.test:8888" },
+      }, {
+        value: "",
+        label: "View Saved Logins",
+        style: "loginsFooter",
+        comment: "mochi.test",
+      }],
+    },
+    {
+      insecureFieldWarningEnabled: true,
+      isSecure: true,
+      isPasswordField: true,
+      matchingLogins: [],
+      items: [{
+        value: "",
+        label: "View Saved Logins",
+        style: "loginsFooter",
+        comment: "mochi.test",
+      }],
+    },
+    {
+      insecureFieldWarningEnabled: true,
+      isSecure: true,
+      isPasswordField: true,
+      matchingLogins: [],
+      searchString: "foo",
+      items: [],
+    },
+    {
+      generatedPassword: "9ljgfd4shyktb45",
+      insecureFieldWarningEnabled: true,
+      isSecure: true,
+      isPasswordField: true,
+      matchingLogins: [],
+      items: [{
+        value: "9ljgfd4shyktb45",
+        label: "Use Generated Password",
+        style: "generatedPassword",
+        comment: "9ljgfd4shyktb45",
+      }, {
+        value: "",
+        label: "View Saved Logins",
+        style: "loginsFooter",
+        comment: "mochi.test",
+      }],
+    },
+    {
+      generatedPassword: "9ljgfd4shyktb45",
+      insecureFieldWarningEnabled: true,
+      isSecure: true,
+      isPasswordField: true,
+      matchingLogins: [],
+      searchString: "9ljgfd4shyktb45",
+      items: [],
+    },
+  ];
+
   LoginHelper.createLogger("LoginAutoCompleteResult");
   Services.prefs.setBoolPref("signon.showAutoCompleteFooter", true);
   Services.prefs.setBoolPref("signon.showAutoCompleteOrigins", true);
@@ -750,7 +756,13 @@ add_task(async function test_all_patterns() {
       equal(actual.getValueAt(index), item.value, `Value ${index}`);
       equal(actual.getLabelAt(index), item.label, `Label ${index}`);
       equal(actual.getStyleAt(index), item.style, `Style ${index}`);
-      equal(actual.getCommentAt(index), item.comment, `Comment ${index}`);
+      let actualComment = actual.getCommentAt(index);
+      if (typeof(item.comment) == "object") {
+        let parsedComment = JSON.parse(actualComment);
+        equal(parsedComment.comment, item.comment.comment, `Comment.comment ${index}`);
+      } else {
+        equal(actualComment, item.comment, `Comment ${index}`);
+      }
     });
 
     if (pattern.items.length != 0) {
diff --git a/toolkit/components/reader/test/browser_readerMode.js b/toolkit/components/reader/test/browser_readerMode.js
index 05a0ed5286f8..a13ba1943a14 100644
--- a/toolkit/components/reader/test/browser_readerMode.js
+++ b/toolkit/components/reader/test/browser_readerMode.js
@@ -62,7 +62,7 @@ add_task(async function test_reader_button() {
   let readerUrl = gBrowser.selectedBrowser.currentURI.spec;
   ok(readerUrl.startsWith("about:reader"), "about:reader loaded after clicking reader mode button");
   is_element_visible(readerButton, "Reader mode button is present on about:reader");
-  let iconEl = document.getAnonymousElementByAttribute(tab, "anonid", "tab-icon-image");
+  let iconEl = tab.iconImage;
   await TestUtils.waitForCondition(() => iconEl.getBoundingClientRect().width != 0);
   is_element_visible(iconEl, "Favicon should be visible");
   is(iconEl.src, favicon, "Correct favicon should be loaded");
diff --git a/toolkit/components/telemetry/Scalars.yaml b/toolkit/components/telemetry/Scalars.yaml
index 41063d28f3a3..669fe49289dd 100644
--- a/toolkit/components/telemetry/Scalars.yaml
+++ b/toolkit/components/telemetry/Scalars.yaml
@@ -1507,6 +1507,21 @@ devtools:
     record_in_processes:
       - 'main'
 
+devtools.tooltip:
+  shown:
+    bug_numbers:
+      - 1553471
+    description: >
+      Number of times a tooltip was shown, keyed by tooltip type. Currently supported types are "image", "font-family", "inactive-css" and "variable."
+    expires: never
+    kind: uint
+    keyed: true
+    notification_emails:
+      - dev-developer-tools@lists.mozilla.org
+    release_channel_collection: opt-out
+    record_in_processes:
+      - 'main'
+
 devtools.aboutdevtools:
   opened:
     bug_numbers:
diff --git a/toolkit/content/tests/browser/browser_block_silentAudioTrack_media.js b/toolkit/content/tests/browser/browser_block_silentAudioTrack_media.js
index a6c229c9d80e..6379f5461195 100644
--- a/toolkit/content/tests/browser/browser_block_silentAudioTrack_media.js
+++ b/toolkit/content/tests/browser/browser_block_silentAudioTrack_media.js
@@ -8,7 +8,7 @@ var SuspendedType = {
 };
 
 async function click_unblock_icon(tab) {
-  let icon = document.getAnonymousElementByAttribute(tab, "anonid", "soundplaying-icon");
+  let icon = tab.soundPlayingIcon;
 
   await hover_icon(icon, document.getElementById("tabbrowser-tab-tooltip"));
   EventUtils.synthesizeMouseAtCenter(icon, {button: 0});
diff --git a/toolkit/content/tests/browser/browser_mute_plugIn.js b/toolkit/content/tests/browser/browser_mute_plugIn.js
index 1520bea783d4..9bf81b2b528e 100644
--- a/toolkit/content/tests/browser/browser_mute_plugIn.js
+++ b/toolkit/content/tests/browser/browser_mute_plugIn.js
@@ -1,7 +1,7 @@
 const PAGE = "https://example.com/browser/toolkit/content/tests/browser/file_plugIn.html";
 
 async function click_icon(tab) {
-  let icon = document.getAnonymousElementByAttribute(tab, "anonid", "soundplaying-icon");
+  let icon = tab.soundPlayingIcon;
 
   await hover_icon(icon, document.getElementById("tabbrowser-tab-tooltip"));
   EventUtils.synthesizeMouseAtCenter(icon, {button: 0});
diff --git a/toolkit/content/tests/browser/browser_mute_webAudio.js b/toolkit/content/tests/browser/browser_mute_webAudio.js
index 19ff95bc8ee6..00cfb8339b44 100644
--- a/toolkit/content/tests/browser/browser_mute_webAudio.js
+++ b/toolkit/content/tests/browser/browser_mute_webAudio.js
@@ -8,7 +8,7 @@ if (!gMultiProcessBrowser) {
 const PAGE = "https://example.com/browser/toolkit/content/tests/browser/file_webAudio.html";
 
 async function click_icon(tab) {
-  let icon = document.getAnonymousElementByAttribute(tab, "anonid", "soundplaying-icon");
+  let icon = tab.soundPlayingIcon;
 
   await hover_icon(icon, document.getElementById("tabbrowser-tab-tooltip"));
   EventUtils.synthesizeMouseAtCenter(icon, {button: 0});
diff --git a/toolkit/content/widgets/autocomplete-popup.js b/toolkit/content/widgets/autocomplete-popup.js
index f4ff338a55be..c1df31b6977b 100644
--- a/toolkit/content/widgets/autocomplete-popup.js
+++ b/toolkit/content/widgets/autocomplete-popup.js
@@ -404,15 +404,17 @@ MozElements.MozAutocompleteRichlistboxPopup = class MozAutocompleteRichlistboxPo
           case "autofill-insecureWarning":
             options = { is: "autocomplete-creditcard-insecure-field" };
             break;
+          case "generatedPassword":
+            options = { is: "autocomplete-two-line-richlistitem" };
+            break;
           case "insecureWarning":
             options = { is: "autocomplete-richlistitem-insecure-warning" };
             break;
           case "loginsFooter":
             options = { is: "autocomplete-richlistitem-logins-footer" };
             break;
-          case "generatedPassword":
           case "loginWithOrigin":
-            options = { is: "autocomplete-two-line-richlistitem" };
+            options = { is: "autocomplete-login-richlistitem" };
             break;
           default:
             options = { is: "autocomplete-richlistitem" };
diff --git a/toolkit/content/widgets/autocomplete-richlistitem.js b/toolkit/content/widgets/autocomplete-richlistitem.js
index 34c6dbf10661..a15c8da21a31 100644
--- a/toolkit/content/widgets/autocomplete-richlistitem.js
+++ b/toolkit/content/widgets/autocomplete-richlistitem.js
@@ -1052,6 +1052,23 @@ class MozAutocompleteTwoLineRichlistitem extends MozElements.MozRichlistitem {
   handleOverUnderflow() {}
 }
 
+class MozAutocompleteLoginRichlistitem extends MozAutocompleteTwoLineRichlistitem {
+  static get inheritedAttributes() {
+    return {
+      // getLabelAt:
+      ".line1-label": "text=ac-value",
+      // Don't inherit ac-label with getCommentAt since the label is JSON.
+    };
+  }
+
+  _adjustAcItem() {
+    super._adjustAcItem();
+
+    let details = JSON.parse(this.getAttribute("ac-label"));
+    this.querySelector(".line2-label").textContent = details.comment;
+  }
+}
+
 customElements.define("autocomplete-richlistitem", MozElements.MozAutocompleteRichlistitem, {
   extends: "richlistitem",
 });
@@ -1067,4 +1084,8 @@ customElements.define("autocomplete-richlistitem-logins-footer", MozAutocomplete
 customElements.define("autocomplete-two-line-richlistitem", MozAutocompleteTwoLineRichlistitem, {
   extends: "richlistitem",
 });
+
+customElements.define("autocomplete-login-richlistitem", MozAutocompleteLoginRichlistitem, {
+  extends: "richlistitem",
+});
 }
diff --git a/toolkit/content/widgets/autocomplete.xml b/toolkit/content/widgets/autocomplete.xml
index 8f8094f57e14..84881c559837 100644
--- a/toolkit/content/widgets/autocomplete.xml
+++ b/toolkit/content/widgets/autocomplete.xml
@@ -1042,15 +1042,17 @@
                 case "autofill-insecureWarning":
                   options = { is: "autocomplete-creditcard-insecure-field" };
                   break;
+                case "generatedPassword":
+                  options = { is: "autocomplete-two-line-richlistitem" };
+                  break;
                 case "insecureWarning":
                   options = { is: "autocomplete-richlistitem-insecure-warning" };
                   break;
                 case "loginsFooter":
                   options = { is: "autocomplete-richlistitem-logins-footer" };
                   break;
-                case "generatedPassword":
                 case "loginWithOrigin":
-                  options = { is: "autocomplete-two-line-richlistitem" };
+                  options = { is: "autocomplete-login-richlistitem" };
                   break;
                 default:
                   options = { is: "autocomplete-richlistitem" };
diff --git a/toolkit/content/widgets/tabbox.js b/toolkit/content/widgets/tabbox.js
index d1ea8046015c..4956204021ce 100644
--- a/toolkit/content/widgets/tabbox.js
+++ b/toolkit/content/widgets/tabbox.js
@@ -250,4 +250,164 @@ class MozTabpanels extends MozXULElement {
 
 MozXULElement.implementCustomInterface(MozTabpanels, [Ci.nsIDOMXULRelatedElement]);
 customElements.define("tabpanels", MozTabpanels);
+
+MozElements.MozTab = class MozTab extends MozElements.BaseText {
+  constructor() {
+    super();
+
+    this.addEventListener("mousedown", (event) => {
+      if (event.button != 0 || this.disabled) {
+        return;
+      }
+
+      this.parentNode.ariaFocusedItem = null;
+
+      if (this != this.parentNode.selectedItem) { // Not selected yet
+        let stopwatchid = this.parentNode.getAttribute("stopwatchid");
+        if (stopwatchid) {
+          TelemetryStopwatch.start(stopwatchid);
+        }
+
+        // Call this before setting the 'ignorefocus' attribute because this
+        // will pass on focus if the formerly selected tab was focused as well.
+        this.parentNode._selectNewTab(this);
+
+        var isTabFocused = false;
+        try {
+          isTabFocused = (document.commandDispatcher.focusedElement == this);
+        } catch (e) {}
+
+        // Set '-moz-user-focus' to 'ignore' so that PostHandleEvent() can't
+        // focus the tab; we only want tabs to be focusable by the mouse if
+        // they are already focused. After a short timeout we'll reset
+        // '-moz-user-focus' so that tabs can be focused by keyboard again.
+        if (!isTabFocused) {
+          this.setAttribute("ignorefocus", "true");
+          setTimeout(tab => tab.removeAttribute("ignorefocus"), 0, this);
+        }
+
+        if (stopwatchid) {
+          TelemetryStopwatch.finish(stopwatchid);
+        }
+      }
+      // Otherwise this tab is already selected and we will fall
+      // through to mousedown behavior which sets focus on the current tab,
+      // Only a click on an already selected tab should focus the tab itself.
+    });
+
+    this.addEventListener("keydown", (event) => {
+      if (event.ctrlKey || event.altKey || event.metaKey || event.shiftKey) {
+        return;
+      }
+      switch (event.keyCode) {
+        case KeyEvent.DOM_VK_LEFT: {
+          let direction = window.getComputedStyle(this.parentNode).direction;
+          this.parentNode.advanceSelectedTab(direction == "ltr" ? -1 : 1,
+                                             this.arrowKeysShouldWrap);
+          event.preventDefault();
+        } break;
+
+        case KeyEvent.DOM_VK_RIGHT: {
+          let direction = window.getComputedStyle(this.parentNode).direction;
+          this.parentNode.advanceSelectedTab(direction == "ltr" ? 1 : -1,
+                                             this.arrowKeysShouldWrap);
+          event.preventDefault();
+        } break;
+
+        case KeyEvent.DOM_VK_UP:
+          this.parentNode.advanceSelectedTab(-1, this.arrowKeysShouldWrap);
+          event.preventDefault();
+        break;
+
+        case KeyEvent.DOM_VK_DOWN:
+          this.parentNode.advanceSelectedTab(1, this.arrowKeysShouldWrap);
+          event.preventDefault();
+        break;
+
+        case KeyEvent.DOM_VK_HOME:
+          this.parentNode._selectNewTab(this.parentNode.children[0]);
+          event.preventDefault();
+        break;
+
+        case KeyEvent.DOM_VK_END:
+          let tabs = this.parentNode.children;
+          this.parentNode._selectNewTab(tabs[tabs.length - 1], -1);
+          event.preventDefault();
+        break;
+      }
+    });
+
+    this.arrowKeysShouldWrap = /Mac/.test(navigator.platform);
+  }
+
+  static get inheritedAttributes() {
+    return {
+      ".tab-middle": "align,dir,pack,orient,selected,visuallyselected",
+      ".tab-icon": "validate,src=image",
+      ".tab-text": "value=label,accesskey,crop,disabled",
+    };
+  }
+
+  get fragment() {
+    if (!this._fragment) {
+      this._fragment = MozXULElement.parseXULToFragment(`
+        
+          
+          
+        
+    `);
+    }
+    return this.ownerDocument.importNode(this._fragment, true);
+  }
+
+  connectedCallback() {
+    if (!this._initialized) {
+      this.textContent = "";
+      this.appendChild(this.fragment);
+      this.initializeAttributeInheritance();
+      this._initialized = true;
+    }
+  }
+
+  set value(val) {
+    this.setAttribute("value", val);
+    return val;
+  }
+
+  get value() {
+    return this.getAttribute("value");
+  }
+
+  get control() {
+    var parent = this.parentNode;
+    return (parent.localName == "tabs") ? parent : null;
+  }
+
+  get selected() {
+    return this.getAttribute("selected") == "true";
+  }
+
+  set _selected(val) {
+    if (val) {
+      this.setAttribute("selected", "true");
+      this.setAttribute("visuallyselected", "true");
+    } else {
+      this.removeAttribute("selected");
+      this.removeAttribute("visuallyselected");
+    }
+
+    return val;
+  }
+
+  set linkedPanel(val) {
+    this.setAttribute("linkedpanel", val);
+  }
+
+  get linkedPanel() {
+    return this.getAttribute("linkedpanel");
+  }
+};
+
+MozXULElement.implementCustomInterface(MozElements.MozTab, [Ci.nsIDOMXULSelectControlItemElement]);
+customElements.define("tab", MozElements.MozTab);
 }
diff --git a/toolkit/content/widgets/tabbox.xml b/toolkit/content/widgets/tabbox.xml
index ffa3d0e23011..015ddacf1439 100644
--- a/toolkit/content/widgets/tabbox.xml
+++ b/toolkit/content/widgets/tabbox.xml
@@ -374,139 +374,4 @@
       
     
   
-
-  
-    
-      
-        
-        
-      
-    
-
-    
-      
-      
-        
-          
-        
-      
-
-      
-
-      
-        
-      
-
-      
-
-      
-        /Mac/.test(navigator.platform)
-      
-    
-
-    
-      
-       tab.removeAttribute("ignorefocus"), 0, this);
-          }
-
-          if (stopwatchid) {
-            TelemetryStopwatch.finish(stopwatchid);
-          }
-        }
-        // Otherwise this tab is already selected and we will fall
-        // through to mousedown behavior which sets focus on the current tab,
-        // Only a click on an already selected tab should focus the tab itself.
-      ]]>
-      
-
-      
-      
-      
-
-      
-      
-      
-
-      
-      
-      
-
-      
-      
-      
-
-      
-      
-      
-
-      
-      
-      
-    
-  
-
 
diff --git a/toolkit/content/xul.css b/toolkit/content/xul.css
index 6c3e8b1414d9..6ac017cdcf0e 100644
--- a/toolkit/content/xul.css
+++ b/toolkit/content/xul.css
@@ -411,7 +411,6 @@ tabs {
 }
 
 tab {
-  -moz-binding: url("chrome://global/content/bindings/tabbox.xml#tab");
   -moz-box-align: center;
   -moz-box-pack: center;
 }
diff --git a/toolkit/locales/en-US/toolkit/global/processTypes.ftl b/toolkit/locales/en-US/toolkit/global/processTypes.ftl
index 635484d4720b..885a77e0b38b 100644
--- a/toolkit/locales/en-US/toolkit/global/processTypes.ftl
+++ b/toolkit/locales/en-US/toolkit/global/processTypes.ftl
@@ -4,9 +4,13 @@
 
 process-type-web = Web Content
 
-# process used to run privileged pages,
+# process used to run privileged about pages,
 # such as about:home
-process-type-privileged = Privileged Content
+process-type-privilegedabout = Privileged About
+
+# process used to run privileged mozilla pages,
+# such as accounts.firefox.com
+process-type-privilegedmozilla = Privileged Mozilla Content
 
 process-type-extension = Extension
 
diff --git a/toolkit/modules/E10SUtils.jsm b/toolkit/modules/E10SUtils.jsm
index 5eef0a2c3da2..6ae2290890c7 100644
--- a/toolkit/modules/E10SUtils.jsm
+++ b/toolkit/modules/E10SUtils.jsm
@@ -13,8 +13,13 @@ XPCOMUtils.defineLazyPreferenceGetter(this, "useSeparateFileUriProcess",
                                       "browser.tabs.remote.separateFileUriProcess", false);
 XPCOMUtils.defineLazyPreferenceGetter(this, "allowLinkedWebInFileUriProcess",
                                       "browser.tabs.remote.allowLinkedWebInFileUriProcess", false);
-XPCOMUtils.defineLazyPreferenceGetter(this, "useSeparatePrivilegedContentProcess",
+XPCOMUtils.defineLazyPreferenceGetter(this, "useSeparatePrivilegedAboutContentProcess",
                                       "browser.tabs.remote.separatePrivilegedContentProcess", false);
+XPCOMUtils.defineLazyPreferenceGetter(this, "separatePrivilegedMozillaWebContentProcess",
+                                      "browser.tabs.remote.separatePrivilegedMozillaWebContentProcess", false);
+XPCOMUtils.defineLazyPreferenceGetter(this, "separatedMozillaDomains",
+                                      "browser.tabs.remote.separatedMozillaDomains", false,
+                                      false, val => val.split(","));
 XPCOMUtils.defineLazyPreferenceGetter(this, "useHttpResponseProcessSelection",
                                       "browser.tabs.remote.useHTTPResponseProcessSelection", false);
 XPCOMUtils.defineLazyPreferenceGetter(this, "useCrossOriginOpenerPolicy",
@@ -46,13 +51,24 @@ const NOT_REMOTE = null;
 const WEB_REMOTE_TYPE = "web";
 const FILE_REMOTE_TYPE = "file";
 const EXTENSION_REMOTE_TYPE = "extension";
-const PRIVILEGED_REMOTE_TYPE = "privileged";
+const PRIVILEGEDABOUT_REMOTE_TYPE = "privilegedabout";
+const PRIVILEGEDMOZILLA_REMOTE_TYPE = "privilegedmozilla";
 
 // This must start with the WEB_REMOTE_TYPE above.
 const LARGE_ALLOCATION_REMOTE_TYPE = "webLargeAllocation";
 const DEFAULT_REMOTE_TYPE = WEB_REMOTE_TYPE;
 
 function validatedWebRemoteType(aPreferredRemoteType, aTargetUri, aCurrentUri, aRemoteSubframes) {
+  // To load into the Privileged Mozilla Content Process you must be https,
+  // and be an exact match or a subdomain of an allowlisted domain.
+  if (separatePrivilegedMozillaWebContentProcess &&
+      aTargetUri.asciiHost && aTargetUri.scheme == "https" &&
+      separatedMozillaDomains.some(function(val) {
+        return aTargetUri.asciiHost == val || aTargetUri.asciiHost.endsWith("." + val);
+      })) {
+    return PRIVILEGEDMOZILLA_REMOTE_TYPE;
+  }
+
   // If the domain is whitelisted to allow it to use file:// URIs, then we have
   // to run it in a file content process, in case it uses file:// sub-resources.
   const sm = Services.scriptSecurityManager;
@@ -103,7 +119,8 @@ var E10SUtils = {
   WEB_REMOTE_TYPE,
   FILE_REMOTE_TYPE,
   EXTENSION_REMOTE_TYPE,
-  PRIVILEGED_REMOTE_TYPE,
+  PRIVILEGEDABOUT_REMOTE_TYPE,
+  PRIVILEGEDMOZILLA_REMOTE_TYPE,
   LARGE_ALLOCATION_REMOTE_TYPE,
 
   useHttpResponseProcessSelection() {
@@ -235,9 +252,9 @@ var E10SUtils = {
         }
 
         if (flags & Ci.nsIAboutModule.URI_MUST_LOAD_IN_CHILD) {
-          if ((flags & Ci.nsIAboutModule.URI_CAN_LOAD_IN_PRIVILEGED_CHILD) &&
-              useSeparatePrivilegedContentProcess) {
-            return PRIVILEGED_REMOTE_TYPE;
+          if ((flags & Ci.nsIAboutModule.URI_CAN_LOAD_IN_PRIVILEGEDABOUT_PROCESS) &&
+              useSeparatePrivilegedAboutContentProcess) {
+            return PRIVILEGEDABOUT_REMOTE_TYPE;
           }
           return DEFAULT_REMOTE_TYPE;
         }
diff --git a/toolkit/mozapps/extensions/addonManager.js b/toolkit/mozapps/extensions/addonManager.js
index caafb4dd1f05..4d50d7a40cae 100644
--- a/toolkit/mozapps/extensions/addonManager.js
+++ b/toolkit/mozapps/extensions/addonManager.js
@@ -13,6 +13,12 @@
 ChromeUtils.defineModuleGetter(this, "AppConstants",
                                "resource://gre/modules/AppConstants.jsm");
 
+const {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
+XPCOMUtils.defineLazyPreferenceGetter(this, "separatePrivilegedMozillaWebContentProcess",
+  "browser.tabs.remote.separatePrivilegedMozillaWebContentProcess", false);
+XPCOMUtils.defineLazyPreferenceGetter(this, "extensionsWebAPITesting",
+  "extensions.webapi.testing", false);
+
 // The old XPInstall error codes
 const EXECUTION_ERROR   = -203;
 const CANT_READ_ARCHIVE = -207;
@@ -216,6 +222,12 @@ amManager.prototype = {
       }
 
       case MSG_PROMISE_REQUEST: {
+        if (!extensionsWebAPITesting &&
+            separatePrivilegedMozillaWebContentProcess && aMessage.target &&
+            aMessage.target.remoteType != null && aMessage.target.remoteType !== "privilegedmozilla") {
+          return undefined;
+        }
+
         let mm = aMessage.target.messageManager;
         let resolve = (value) => {
           mm.sendAsyncMessage(MSG_PROMISE_RESULT, {
@@ -240,11 +252,23 @@ amManager.prototype = {
       }
 
       case MSG_INSTALL_CLEANUP: {
+        if (!extensionsWebAPITesting &&
+            separatePrivilegedMozillaWebContentProcess && aMessage.target &&
+            aMessage.target.remoteType != null && aMessage.target.remoteType !== "privilegedmozilla") {
+          return undefined;
+        }
+
         AddonManager.webAPI.clearInstalls(payload.ids);
         break;
       }
 
       case MSG_ADDON_EVENT_REQ: {
+        if (!extensionsWebAPITesting &&
+            separatePrivilegedMozillaWebContentProcess && aMessage.target &&
+            aMessage.target.remoteType != null && aMessage.target.remoteType !== "privilegedmozilla") {
+          return undefined;
+        }
+
         let target = aMessage.target.messageManager;
         if (payload.enabled) {
           this._addAddonListener(target);
diff --git a/widget/ProcInfo.h b/widget/ProcInfo.h
index aff8612db534..b12bf2ad538b 100644
--- a/widget/ProcInfo.h
+++ b/widget/ProcInfo.h
@@ -18,7 +18,7 @@ enum class ProcType {
   Web,
   File,
   Extension,
-  Privileged,
+  PrivilegedAbout,
   WebLargeAllocation,
   // GPU process (only on Windows)
   Gpu,
diff --git a/widget/PuppetWidget.cpp b/widget/PuppetWidget.cpp
index 5b2456a64b63..ba9188f6abc8 100644
--- a/widget/PuppetWidget.cpp
+++ b/widget/PuppetWidget.cpp
@@ -789,6 +789,10 @@ nsresult PuppetWidget::NotifyIMEOfFocusChange(
           mBrowserChild->TabGroup()->EventTargetFor(TaskCategory::UI), __func__,
           [self](IMENotificationRequests&& aRequests) {
             self->mIMENotificationRequestsOfParent = aRequests;
+            if (TextEventDispatcher* dispatcher =
+                    self->GetTextEventDispatcher()) {
+              dispatcher->OnWidgetChangeIMENotificationRequests(self);
+            }
           },
           [self](mozilla::ipc::ResponseRejectReason&& aReason) {
             NS_WARNING("SendNotifyIMEFocus got rejected.");
diff --git a/widget/TextEventDispatcher.h b/widget/TextEventDispatcher.h
index e016413d0196..395ef78923a3 100644
--- a/widget/TextEventDispatcher.h
+++ b/widget/TextEventDispatcher.h
@@ -84,6 +84,18 @@ class TextEventDispatcher final {
     return mIMENotificationRequests;
   }
 
+  /**
+   * OnWidgetChangeIMENotificationRequests() is called when aWidget's
+   * IMENotificationRequest is maybe modified by unusual path.  E.g.,
+   * modified in an async path.
+   */
+  void OnWidgetChangeIMENotificationRequests(nsIWidget* aWidget) {
+    MOZ_ASSERT(aWidget);
+    if (mWidget == aWidget) {
+      UpdateNotificationRequests();
+    }
+  }
+
   /**
    * GetState() returns current state of this class.
    *
diff --git a/widget/gtk/WindowSurfaceWayland.cpp b/widget/gtk/WindowSurfaceWayland.cpp
index a890bd46dafb..6067d65cd8e2 100644
--- a/widget/gtk/WindowSurfaceWayland.cpp
+++ b/widget/gtk/WindowSurfaceWayland.cpp
@@ -35,6 +35,9 @@ extern mozilla::LazyLogModule gWidgetWaylandLog;
 namespace mozilla {
 namespace widget {
 
+bool WindowSurfaceWayland::mUseDMABuf = false;
+bool WindowSurfaceWayland::mUseDMABufInitialized = false;
+
 /*
   Wayland multi-thread rendering scheme
 
@@ -59,7 +62,7 @@ namespace widget {
         |       | WindowSurfaceWayland          |<------>| nsWindow       |
         |       |                               |        ------------------
         |       |  -----------------------      |
-        |       |  | WindowBackBuffer    |      |
+        |       |  | WindowBackBufferShm |      |
         |       |  |                     |      |
         |       |  | ------------------- |      |
         |       |  | |  WaylandShmPool | |      |
@@ -67,7 +70,7 @@ namespace widget {
         |       |  -----------------------      |
         |       |                               |
         |       |  -----------------------      |
-        |       |  | WindowBackBuffer    |      |
+        |       |  | WindowBackBufferShm |      |
         |       |  |                     |      |
         |       |  | ------------------- |      |
         |       |  | |  WaylandShmPool | |      |
@@ -80,7 +83,7 @@ namespace widget {
   | WindowSurfaceWayland          |<------>| nsWindow       |
   |                               |        ------------------
   |  -----------------------      |
-  |  | WindowBackBuffer    |      |
+  |  | WindowBackBufferShm |      |
   |  |                     |      |
   |  | ------------------- |      |
   |  | |  WaylandShmPool | |      |
@@ -88,7 +91,7 @@ namespace widget {
   |  -----------------------      |
   |                               |
   |  -----------------------      |
-  |  | WindowBackBuffer    |      |
+  |  | WindowBackBufferShm |      |
   |  |                     |      |
   |  | ------------------- |      |
   |  | |  WaylandShmPool | |      |
@@ -96,6 +99,34 @@ namespace widget {
   |  -----------------------      |
   ---------------------------------
 
+----------------------------------------------------------------
+When WindowBackBufferDMABuf is used it's similar to
+WindowBackBufferShm scheme:
+
+    |
+    |
+    |
+  -----------------------------------         ------------------
+  | WindowSurfaceWayland             |<------>| nsWindow       |
+  |                                  |        ------------------
+  |  --------------------------      |
+  |  |WindowBackBufferDMABuf  |      |
+  |  |                        |      |
+  |  | ---------------------- |      |
+  |  | |WaylandDMABufSurface  |      |
+  |  | ---------------------- |      |
+  |  --------------------------      |
+  |                                  |
+  |  --------------------------      |
+  |  |WindowBackBufferDMABuf  |      |
+  |  |                        |      |
+  |  | ---------------------- |      |
+  |  | |WaylandDMABufSurface  |      |
+  |  | ---------------------- |      |
+  |  --------------------------      |
+  -----------------------------------
+
+
 nsWaylandDisplay
 
 Is our connection to Wayland display server,
@@ -120,12 +151,14 @@ wl_surface and two wl_buffer objects (owned by WindowBackBuffer)
 as we use double buffering. When nsWindow drawing is finished to wl_buffer,
 the wl_buffer is attached to wl_surface and it's sent to Wayland compositor.
 
+When there's no wl_buffer available for drawing (all wl_buffers are locked in
+compositor for instance) we store the drawing to WindowImageSurface object
+and draw later when wl_buffer becomes availabe or discard the
+WindowImageSurface cache when whole screen is invalidated.
 
 WindowBackBuffer
 
-Manages one wl_buffer. It owns wl_buffer object, owns WaylandShmPool
-(which provides shared memory) and ties them together.
-
+Is an abstraction class which provides a wl_buffer for drawing.
 Wl_buffer is a main Wayland object with actual graphics data.
 Wl_buffer basically represent one complete window screen.
 When double buffering is involved every window (GdkWindow for instance)
@@ -133,6 +166,12 @@ utilises two wl_buffers which are cycled. One is filed with data by application
 and one is rendered by compositor.
 
 
+WindowBackBufferShm
+
+It's WindowBackBuffer implementation by shared memory (shm).
+It owns wl_buffer object, owns WaylandShmPool
+(which provides the shared memory) and ties them together.
+
 WaylandShmPool
 
 WaylandShmPool acts as a manager of shared memory for WindowBackBuffer.
@@ -142,6 +181,16 @@ We allocate shared memory (shm) by mmap(..., MAP_SHARED,...) as an interface
 between us and wayland compositor. We draw our graphics data to the shm and
 handle to wayland compositor by WindowBackBuffer/WindowSurfaceWayland
 (wl_buffer/wl_surface).
+
+WindowBackBufferDMABuf
+
+It's WindowBackBuffer implementation based on DMA Buffer.
+It owns wl_buffer object, owns WaylandDMABufSurface
+(which provides the DMA Buffer) and ties them together.
+
+WindowBackBufferDMABuf backend is used only when WaylandDMABufSurface is
+available and gfx.wayland_dmabuf_backend.enabled preference is set.
+
 */
 
 #define EVENT_LOOP_DELAY (1000 / 240)
@@ -260,7 +309,7 @@ static void buffer_release(void* data, wl_buffer* buffer) {
 
 static const struct wl_buffer_listener buffer_listener = {buffer_release};
 
-void WindowBackBuffer::Create(int aWidth, int aHeight) {
+void WindowBackBufferShm::Create(int aWidth, int aHeight) {
   MOZ_ASSERT(!IsAttached(), "We can't resize attached buffers.");
 
   int newBufferSize = aWidth * aHeight * BUFFER_BPP;
@@ -270,7 +319,7 @@ void WindowBackBuffer::Create(int aWidth, int aHeight) {
       wl_shm_pool_create_buffer(mShmPool.GetShmPool(), 0, aWidth, aHeight,
                                 aWidth * BUFFER_BPP, WL_SHM_FORMAT_ARGB8888);
   wl_proxy_set_queue((struct wl_proxy*)mWaylandBuffer,
-                     mWaylandDisplay->GetEventQueue());
+                     GetWaylandDisplay()->GetEventQueue());
   wl_buffer_add_listener(mWaylandBuffer, &buffer_listener, this);
 
   mWidth = aWidth;
@@ -282,31 +331,31 @@ void WindowBackBuffer::Create(int aWidth, int aHeight) {
       mWaylandBuffer ? wl_proxy_get_id((struct wl_proxy*)mWaylandBuffer) : -1));
 }
 
-void WindowBackBuffer::Release() {
+void WindowBackBufferShm::Release() {
   LOGWAYLAND(("%s [%p]\n", __PRETTY_FUNCTION__, (void*)this));
 
   wl_buffer_destroy(mWaylandBuffer);
   mWidth = mHeight = 0;
 }
 
-void WindowBackBuffer::Clear() {
+void WindowBackBufferShm::Clear() {
   memset(mShmPool.GetImageData(), 0, mHeight * mWidth * BUFFER_BPP);
 }
 
-WindowBackBuffer::WindowBackBuffer(nsWaylandDisplay* aWaylandDisplay,
-                                   int aWidth, int aHeight)
-    : mShmPool(aWaylandDisplay, aWidth * aHeight * BUFFER_BPP),
+WindowBackBufferShm::WindowBackBufferShm(nsWaylandDisplay* aWaylandDisplay,
+                                         int aWidth, int aHeight)
+    : WindowBackBuffer(aWaylandDisplay),
+      mShmPool(aWaylandDisplay, aWidth * aHeight * BUFFER_BPP),
       mWaylandBuffer(nullptr),
       mWidth(aWidth),
       mHeight(aHeight),
-      mAttached(false),
-      mWaylandDisplay(aWaylandDisplay) {
+      mAttached(false) {
   Create(aWidth, aHeight);
 }
 
-WindowBackBuffer::~WindowBackBuffer() { Release(); }
+WindowBackBufferShm::~WindowBackBufferShm() { Release(); }
 
-bool WindowBackBuffer::Resize(int aWidth, int aHeight) {
+bool WindowBackBufferShm::Resize(int aWidth, int aHeight) {
   if (aWidth == mWidth && aHeight == mHeight) return true;
 
   LOGWAYLAND(
@@ -319,20 +368,20 @@ bool WindowBackBuffer::Resize(int aWidth, int aHeight) {
 }
 
 void WindowBackBuffer::Attach(wl_surface* aSurface) {
-  LOGWAYLAND((
-      "%s [%p] wl_surface %p ID %d wl_buffer %p ID %d\n", __PRETTY_FUNCTION__,
-      (void*)this, (void*)aSurface,
-      aSurface ? wl_proxy_get_id((struct wl_proxy*)aSurface) : -1,
-      (void*)mWaylandBuffer,
-      mWaylandBuffer ? wl_proxy_get_id((struct wl_proxy*)mWaylandBuffer) : -1));
+  LOGWAYLAND(
+      ("%s [%p] wl_surface %p ID %d wl_buffer %p ID %d\n", __PRETTY_FUNCTION__,
+       (void*)this, (void*)aSurface,
+       aSurface ? wl_proxy_get_id((struct wl_proxy*)aSurface) : -1,
+       (void*)GetWlBuffer(),
+       GetWlBuffer() ? wl_proxy_get_id((struct wl_proxy*)GetWlBuffer()) : -1));
 
-  wl_surface_attach(aSurface, mWaylandBuffer, 0, 0);
+  wl_surface_attach(aSurface, GetWlBuffer(), 0, 0);
   wl_surface_commit(aSurface);
-  wl_display_flush(mWaylandDisplay->GetDisplay());
-  mAttached = true;
+  wl_display_flush(GetWaylandDisplay()->GetDisplay());
+  SetAttached();
 }
 
-void WindowBackBuffer::Detach(wl_buffer* aBuffer) {
+void WindowBackBufferShm::Detach(wl_buffer* aBuffer) {
   LOGWAYLAND(("%s [%p] wl_buffer %p ID %d\n", __PRETTY_FUNCTION__, (void*)this,
               (void*)aBuffer,
               aBuffer ? wl_proxy_get_id((struct wl_proxy*)aBuffer) : -1));
@@ -340,19 +389,20 @@ void WindowBackBuffer::Detach(wl_buffer* aBuffer) {
   mAttached = false;
 }
 
-bool WindowBackBuffer::SetImageDataFromBuffer(
+bool WindowBackBufferShm::SetImageDataFromBuffer(
     class WindowBackBuffer* aSourceBuffer) {
-  if (!IsMatchingSize(aSourceBuffer)) {
-    Resize(aSourceBuffer->mWidth, aSourceBuffer->mHeight);
+  auto sourceBuffer = static_cast(aSourceBuffer);
+  if (!IsMatchingSize(sourceBuffer)) {
+    Resize(sourceBuffer->mWidth, sourceBuffer->mHeight);
   }
 
   mShmPool.SetImageDataFromPool(
-      &aSourceBuffer->mShmPool,
-      aSourceBuffer->mWidth * aSourceBuffer->mHeight * BUFFER_BPP);
+      &sourceBuffer->mShmPool,
+      sourceBuffer->mWidth * sourceBuffer->mHeight * BUFFER_BPP);
   return true;
 }
 
-already_AddRefed WindowBackBuffer::Lock() {
+already_AddRefed WindowBackBufferShm::Lock() {
   LOGWAYLAND((
       "%s [%p] [%d x %d] wl_buffer %p ID %d\n", __PRETTY_FUNCTION__,
       (void*)this, mWidth, mHeight, (void*)mWaylandBuffer,
@@ -361,9 +411,71 @@ already_AddRefed WindowBackBuffer::Lock() {
   gfx::IntSize lockSize(mWidth, mHeight);
   return gfxPlatform::CreateDrawTargetForData(
       static_cast(mShmPool.GetImageData()), lockSize,
-      BUFFER_BPP * mWidth, mFormat);
+      BUFFER_BPP * mWidth, GetSurfaceFormat());
 }
 
+#ifdef HAVE_LIBDRM
+WindowBackBufferDMABuf::WindowBackBufferDMABuf(
+    nsWaylandDisplay* aWaylandDisplay, int aWidth, int aHeight)
+    : WindowBackBuffer(aWaylandDisplay) {
+  mDMAbufSurface.Create(aWidth, aHeight);
+}
+
+WindowBackBufferDMABuf::~WindowBackBufferDMABuf() { mDMAbufSurface.Release(); }
+
+already_AddRefed WindowBackBufferDMABuf::Lock() {
+  LOGWAYLAND(
+      ("%s [%p] [%d x %d] wl_buffer %p ID %d\n", __PRETTY_FUNCTION__,
+       (void*)this, GetWidth(), GetHeight(), (void*)GetWlBuffer(),
+       GetWlBuffer() ? wl_proxy_get_id((struct wl_proxy*)GetWlBuffer()) : -1));
+
+  uint32_t stride;
+  void* pixels = mDMAbufSurface.Map(&stride);
+  gfx::IntSize lockSize(GetWidth(), GetHeight());
+  return gfxPlatform::CreateDrawTargetForData(
+      static_cast(pixels), lockSize, stride,
+      GetSurfaceFormat());
+}
+
+void WindowBackBufferDMABuf::Unlock() { mDMAbufSurface.Unmap(); }
+
+bool WindowBackBufferDMABuf::IsAttached() {
+  return mDMAbufSurface.WLBufferIsAttached();
+}
+
+void WindowBackBufferDMABuf::SetAttached() {
+  return mDMAbufSurface.WLBufferSetAttached();
+}
+
+int WindowBackBufferDMABuf::GetWidth() { return mDMAbufSurface.GetWidth(); }
+
+int WindowBackBufferDMABuf::GetHeight() { return mDMAbufSurface.GetHeight(); }
+
+wl_buffer* WindowBackBufferDMABuf::GetWlBuffer() {
+  return mDMAbufSurface.GetWLBuffer();
+}
+
+bool WindowBackBufferDMABuf::IsLocked() { return mDMAbufSurface.IsMapped(); }
+
+bool WindowBackBufferDMABuf::Resize(int aWidth, int aHeight) {
+  return mDMAbufSurface.Resize(aWidth, aHeight);
+}
+
+bool WindowBackBufferDMABuf::SetImageDataFromBuffer(
+    class WindowBackBuffer* aSourceBuffer) {
+  WindowBackBufferDMABuf* source =
+      static_cast(aSourceBuffer);
+  mDMAbufSurface.CopyFrom(&source->mDMAbufSurface);
+  return true;
+}
+
+void WindowBackBufferDMABuf::Detach(wl_buffer* aBuffer) {
+  mDMAbufSurface.WLBufferDetach();
+}
+
+void WindowBackBufferDMABuf::Clear() { mDMAbufSurface.Clear(); }
+#endif
+
 static void frame_callback_handler(void* data, struct wl_callback* callback,
                                    uint32_t time) {
   auto surface = reinterpret_cast(data);
@@ -417,13 +529,53 @@ WindowSurfaceWayland::~WindowSurfaceWayland() {
   }
 }
 
-WindowBackBuffer* WindowSurfaceWayland::GetWaylandBufferToDraw(int aWidth,
-                                                               int aHeight) {
+bool WindowSurfaceWayland::UseDMABufBackend() {
+  if (!mUseDMABufInitialized) {
+#ifdef HAVE_LIBDRM
+    if (WaylandDMABufSurface::IsAvailable()) {
+      mUseDMABuf =
+          Preferences::GetBool("gfx.wayland_dmabuf_backend.enabled", false);
+    }
+#endif
+    mUseDMABufInitialized = true;
+  }
+  return mUseDMABuf;
+}
+
+WindowBackBuffer* WindowSurfaceWayland::CreateWaylandBuffer(int aWidth,
+                                                            int aHeight) {
+#ifdef HAVE_LIBDRM
+  if (UseDMABufBackend()) {
+    static bool sDMABufBufferCreated = false;
+    WindowBackBuffer* buffer =
+        new WindowBackBufferDMABuf(mWaylandDisplay, aWidth, aHeight);
+    if (buffer) {
+      sDMABufBufferCreated = true;
+      return buffer;
+    }
+    // If this is the first failure and there's no dmabuf already active
+    // we can safely fallback to Shm. Otherwise we can't mix DMAbuf and
+    // SHM buffers so just fails now.
+    if (sDMABufBufferCreated) {
+      NS_WARNING("Failed to allocate DMABuf buffer!");
+      return nullptr;
+    } else {
+      NS_WARNING("Wayland DMABuf failed, switched back to Shm backend!");
+      mUseDMABuf = false;
+    }
+  }
+#endif
+
+  return new WindowBackBufferShm(mWaylandDisplay, aWidth, aHeight);
+}
+
+WindowBackBuffer* WindowSurfaceWayland::GetWaylandBufferToDraw(
+    int aWidth, int aHeight, bool aFullScreenUpdate, bool aNoBackBufferCopy) {
   if (!mWaylandBuffer) {
     LOGWAYLAND(("%s [%p] Create [%d x %d]\n", __PRETTY_FUNCTION__, (void*)this,
                 aWidth, aHeight));
 
-    mWaylandBuffer = new WindowBackBuffer(mWaylandDisplay, aWidth, aHeight);
+    mWaylandBuffer = CreateWaylandBuffer(aWidth, aHeight);
     mWaitToFullScreenUpdate = true;
     return mWaylandBuffer;
   }
@@ -449,8 +601,7 @@ WindowBackBuffer* WindowSurfaceWayland::GetWaylandBufferToDraw(int aWidth,
   for (availableBuffer = 0; availableBuffer < BACK_BUFFER_NUM;
        availableBuffer++) {
     if (!mBackupBuffer[availableBuffer]) {
-      mBackupBuffer[availableBuffer] =
-          new WindowBackBuffer(mWaylandDisplay, aWidth, aHeight);
+      mBackupBuffer[availableBuffer] = CreateWaylandBuffer(aWidth, aHeight);
       break;
     }
 
@@ -466,17 +617,26 @@ WindowBackBuffer* WindowSurfaceWayland::GetWaylandBufferToDraw(int aWidth,
     return nullptr;
   }
 
+  bool bufferFlip = mWaylandBuffer->IsMatchingSize(aWidth, aHeight);
+  if (bufferFlip && aNoBackBufferCopy && !aFullScreenUpdate) {
+    LOGWAYLAND(("%s [%p] Delayed hard copy from old buffer [%d x %d]\n",
+                __PRETTY_FUNCTION__, (void*)this, aWidth, aHeight));
+    return nullptr;
+  }
+
   WindowBackBuffer* lastWaylandBuffer = mWaylandBuffer;
   mWaylandBuffer = mBackupBuffer[availableBuffer];
   mBackupBuffer[availableBuffer] = lastWaylandBuffer;
 
-  if (lastWaylandBuffer->IsMatchingSize(aWidth, aHeight)) {
-    LOGWAYLAND(("%s [%p] Copy from old buffer [%d x %d]\n", __PRETTY_FUNCTION__,
-                (void*)this, aWidth, aHeight));
+  if (bufferFlip) {
     // Former front buffer has the same size as a requested one.
     // Gecko may expect a content already drawn on screen so copy
-    // existing data to the new buffer.
-    mWaylandBuffer->SetImageDataFromBuffer(lastWaylandBuffer);
+    // existing data to the new buffer if we don't do fullscreen redraw.
+    if (!aFullScreenUpdate) {
+      LOGWAYLAND(("%s [%p] Copy from old buffer [%d x %d]\n",
+                  __PRETTY_FUNCTION__, (void*)this, aWidth, aHeight));
+      mWaylandBuffer->SetImageDataFromBuffer(lastWaylandBuffer);
+    }
     // When buffer switches we need to damage whole screen
     // (https://bugzilla.redhat.com/show_bug.cgi?id=1418260)
     mWaylandBufferFullScreenDamage = true;
@@ -493,11 +653,15 @@ WindowBackBuffer* WindowSurfaceWayland::GetWaylandBufferToDraw(int aWidth,
 }
 
 already_AddRefed WindowSurfaceWayland::LockWaylandBuffer(
-    int aWidth, int aHeight, bool aClearBuffer) {
-  WindowBackBuffer* buffer = GetWaylandBufferToDraw(aWidth, aHeight);
+    int aWidth, int aHeight, bool aClearBuffer, bool aFullScreenUpdate,
+    bool aNoBackBufferCopy) {
+  WindowBackBuffer* buffer = GetWaylandBufferToDraw(
+      aWidth, aHeight, aFullScreenUpdate, aNoBackBufferCopy);
   if (!buffer) {
-    NS_WARNING(
-        "WindowSurfaceWayland::LockWaylandBuffer(): No buffer available");
+    if (!aNoBackBufferCopy) {
+      NS_WARNING(
+          "WindowSurfaceWayland::LockWaylandBuffer(): No buffer available");
+    }
     return nullptr;
   }
 
@@ -508,6 +672,8 @@ already_AddRefed WindowSurfaceWayland::LockWaylandBuffer(
   return buffer->Lock();
 }
 
+void WindowSurfaceWayland::UnlockWaylandBuffer() { mWaylandBuffer->Unlock(); }
+
 already_AddRefed WindowSurfaceWayland::LockImageSurface(
     const gfx::IntSize& aLockSize) {
   if (!mImageSurface || mImageSurface->CairoStatus() ||
@@ -557,9 +723,14 @@ already_AddRefed WindowSurfaceWayland::Lock(
        lockSize.height == screenRect.height);
 
   if (mDrawToWaylandBufferDirectly) {
-    RefPtr dt =
-        LockWaylandBuffer(screenRect.width, screenRect.height,
-                          mWindow->WaylandSurfaceNeedsClear());
+    // If there's any pending image commit scratch them as we're going
+    // to redraw the whole sceen anyway.
+    mDelayedImageCommits.Clear();
+
+    RefPtr dt = LockWaylandBuffer(
+        screenRect.width, screenRect.height,
+        mWindow->WaylandSurfaceNeedsClear(),
+        /* aFullScreenUpdate */ true, /* aNoBackBufferCopy */ true);
     if (dt) {
       // When we have a request to update whole screen at once
       // (surface was created, resized or changed somehow)
@@ -579,8 +750,49 @@ already_AddRefed WindowSurfaceWayland::Lock(
   return LockImageSurface(lockSize);
 }
 
+void WindowImageSurface::Draw(gfx::SourceSurface* aSurface,
+                              gfx::DrawTarget* aDest,
+                              const LayoutDeviceIntRegion& aRegion) {
+  uint32_t numRects = aRegion.GetNumRects();
+  if (numRects != 1) {
+    AutoTArray rects;
+    rects.SetCapacity(numRects);
+    for (auto iter = aRegion.RectIter(); !iter.Done(); iter.Next()) {
+      rects.AppendElement(iter.Get().ToUnknownRect());
+    }
+    aDest->PushDeviceSpaceClipRects(rects.Elements(), rects.Length());
+  }
+
+  gfx::IntRect bounds = aRegion.GetBounds().ToUnknownRect();
+  gfx::Rect rect(bounds);
+  aDest->DrawSurface(aSurface, rect, rect);
+
+  if (numRects != 1) {
+    aDest->PopClip();
+  }
+}
+
+void WindowImageSurface::Draw(gfx::DrawTarget* aDest,
+                              LayoutDeviceIntRegion& aWaylandBufferDamage) {
+  Draw(mSurface.get(), aDest, mUpdateRegion);
+  aWaylandBufferDamage.OrWith(mUpdateRegion);
+}
+
+WindowImageSurface::WindowImageSurface(
+    gfx::SourceSurface* aSurface, const LayoutDeviceIntRegion& aUpdateRegion)
+    : mSurface(aSurface), mUpdateRegion(aUpdateRegion){};
+
+void WindowSurfaceWayland::DrawDelayedImageCommits(
+    gfx::DrawTarget* aDrawTarget, LayoutDeviceIntRegion& aWaylandBufferDamage) {
+  for (unsigned int i = 0; i < mDelayedImageCommits.Length(); i++) {
+    mDelayedImageCommits[i].Draw(aDrawTarget, aWaylandBufferDamage);
+  }
+  mDelayedImageCommits.Clear();
+}
+
 bool WindowSurfaceWayland::CommitImageSurfaceToWaylandBuffer(
-    const LayoutDeviceIntRegion& aRegion) {
+    const LayoutDeviceIntRegion& aRegion,
+    LayoutDeviceIntRegion& aWaylandBufferDamage) {
   MOZ_ASSERT(!mDrawToWaylandBufferDirectly);
 
   LayoutDeviceIntRect screenRect = mWindow->GetBounds();
@@ -591,30 +803,27 @@ bool WindowSurfaceWayland::CommitImageSurfaceToWaylandBuffer(
     return false;
   }
 
-  RefPtr dt = LockWaylandBuffer(
-      screenRect.width, screenRect.height, mWindow->WaylandSurfaceNeedsClear());
   RefPtr surf =
       gfx::Factory::CreateSourceSurfaceForCairoSurface(
           mImageSurface->CairoSurface(), mImageSurface->GetSize(),
           mImageSurface->Format());
-  if (!dt || !surf) {
+  if (!surf) {
     return false;
   }
 
-  uint32_t numRects = aRegion.GetNumRects();
-  if (numRects != 1) {
-    AutoTArray rects;
-    rects.SetCapacity(numRects);
-    for (auto iter = aRegion.RectIter(); !iter.Done(); iter.Next()) {
-      rects.AppendElement(iter.Get().ToUnknownRect());
-    }
-    dt->PushDeviceSpaceClipRects(rects.Elements(), rects.Length());
-  }
-
-  dt->DrawSurface(surf, rect, rect);
-
-  if (numRects != 1) {
-    dt->PopClip();
+  RefPtr dt = LockWaylandBuffer(
+      screenRect.width, screenRect.height, /* needs clear*/ false,
+      /* fullscreenDrawing */ false, /* aNoBackBufferCopy */ true);
+  if (dt) {
+    // Draw any delayed image commits first
+    DrawDelayedImageCommits(dt, aWaylandBufferDamage);
+    WindowImageSurface::Draw(surf, dt, aRegion);
+    // Submit all drawing to final Wayland buffer upload
+    aWaylandBufferDamage.OrWith(aRegion);
+    UnlockWaylandBuffer();
+  } else {
+    mDelayedImageCommits.AppendElement(WindowImageSurface(surf, aRegion));
+    return false;
   }
 
   return true;
@@ -648,6 +857,21 @@ void WindowSurfaceWayland::CommitWaylandBuffer() {
     return;
   }
 
+  if (!mDrawToWaylandBufferDirectly) {
+    // There's some cached drawings - try to flush them now.
+    LayoutDeviceIntRect screenRect = mWindow->GetBounds();
+    RefPtr dt =
+        LockWaylandBuffer(screenRect.width, screenRect.height,
+                          /* needsClear */ false,
+                          /* fullscreenInvalidate */ false,
+                          /* aNoBackBufferCopy */ true);
+    if (dt) {
+      DrawDelayedImageCommits(dt, mWaylandBufferDamage);
+      UnlockWaylandBuffer();
+      mDrawToWaylandBufferDirectly = true;
+    }
+  }
+
   wl_surface* waylandSurface = mWindow->GetWaylandSurface();
   if (!waylandSurface) {
     // Target window is not created yet - delay the commit. This can happen only
@@ -740,14 +964,21 @@ void WindowSurfaceWayland::Commit(const LayoutDeviceIntRegion& aInvalidRegion) {
   }
 #endif
 
-  // We have new content at mImageSurface - copy data to mWaylandBuffer first.
-  if (!mDrawToWaylandBufferDirectly) {
-    CommitImageSurfaceToWaylandBuffer(aInvalidRegion);
-  }
-
-  // If we're not at fullscreen damage add drawing area from aInvalidRegion
-  if (!mWaylandBufferFullScreenDamage) {
-    mWaylandBufferDamage.OrWith(aInvalidRegion);
+  if (mDrawToWaylandBufferDirectly) {
+    MOZ_ASSERT(mWaylandBuffer->IsLocked());
+    // If we're not at fullscreen damage add drawing area from aInvalidRegion
+    if (!mWaylandBufferFullScreenDamage) {
+      mWaylandBufferDamage.OrWith(aInvalidRegion);
+    }
+    UnlockWaylandBuffer();
+  } else {
+    MOZ_ASSERT(!mWaylandBuffer->IsLocked(),
+               "Drawing to already locked buffer?");
+    if (CommitImageSurfaceToWaylandBuffer(aInvalidRegion,
+                                          mWaylandBufferDamage)) {
+      // Our cached drawing is flushed, we can draw fullscreen again.
+      mDrawToWaylandBufferDirectly = true;
+    }
   }
 
   // We're ready to commit.
diff --git a/widget/gtk/WindowSurfaceWayland.h b/widget/gtk/WindowSurfaceWayland.h
index d5ab3eee3b79..e565dc0c1858 100644
--- a/widget/gtk/WindowSurfaceWayland.h
+++ b/widget/gtk/WindowSurfaceWayland.h
@@ -10,6 +10,9 @@
 #include 
 #include "mozilla/gfx/Types.h"
 #include "nsWaylandDisplay.h"
+#ifdef HAVE_LIBDRM
+#  include "mozilla/gfx/WaylandDMABufSurface.h"
+#endif
 
 #define BACK_BUFFER_NUM 2
 
@@ -40,12 +43,53 @@ class WaylandShmPool {
 // Holds actual graphics data for wl_surface
 class WindowBackBuffer {
  public:
-  WindowBackBuffer(nsWaylandDisplay* aDisplay, int aWidth, int aHeight);
-  ~WindowBackBuffer();
+  virtual already_AddRefed Lock() = 0;
+  virtual void Unlock(){};
+  virtual bool IsLocked() { return false; };
+
+  void Attach(wl_surface* aSurface);
+  virtual void Detach(wl_buffer* aBuffer) = 0;
+  virtual bool IsAttached() = 0;
+
+  virtual void Clear() = 0;
+  virtual bool Resize(int aWidth, int aHeight) = 0;
+
+  virtual int GetWidth() = 0;
+  virtual int GetHeight() = 0;
+  virtual wl_buffer* GetWlBuffer() = 0;
+  virtual void SetAttached() = 0;
+
+  virtual bool SetImageDataFromBuffer(
+      class WindowBackBuffer* aSourceBuffer) = 0;
+
+  bool IsMatchingSize(int aWidth, int aHeight) {
+    return aWidth == GetWidth() && aHeight == GetHeight();
+  }
+  bool IsMatchingSize(class WindowBackBuffer* aBuffer) {
+    return aBuffer->IsMatchingSize(GetWidth(), GetHeight());
+  }
+
+  static gfx::SurfaceFormat GetSurfaceFormat() { return mFormat; }
+
+  nsWaylandDisplay* GetWaylandDisplay() { return mWaylandDisplay; };
+
+  WindowBackBuffer(nsWaylandDisplay* aWaylandDisplay)
+      : mWaylandDisplay(aWaylandDisplay){};
+  virtual ~WindowBackBuffer(){};
+
+ private:
+  static gfx::SurfaceFormat mFormat;
+  nsWaylandDisplay* mWaylandDisplay;
+};
+
+class WindowBackBufferShm : public WindowBackBuffer {
+ public:
+  WindowBackBufferShm(nsWaylandDisplay* aWaylandDisplay, int aWidth,
+                      int aHeight);
+  ~WindowBackBufferShm();
 
   already_AddRefed Lock();
 
-  void Attach(wl_surface* aSurface);
   void Detach(wl_buffer* aBuffer);
   bool IsAttached() { return mAttached; }
 
@@ -53,14 +97,11 @@ class WindowBackBuffer {
   bool Resize(int aWidth, int aHeight);
   bool SetImageDataFromBuffer(class WindowBackBuffer* aSourceBuffer);
 
-  bool IsMatchingSize(int aWidth, int aHeight) {
-    return aWidth == mWidth && aHeight == mHeight;
-  }
-  bool IsMatchingSize(class WindowBackBuffer* aBuffer) {
-    return aBuffer->mWidth == mWidth && aBuffer->mHeight == mHeight;
-  }
+  int GetWidth() { return mWidth; };
+  int GetHeight() { return mHeight; };
 
-  static gfx::SurfaceFormat GetSurfaceFormat() { return mFormat; }
+  wl_buffer* GetWlBuffer() { return mWaylandBuffer; };
+  void SetAttached() { mAttached = true; };
 
  private:
   void Create(int aWidth, int aHeight);
@@ -75,8 +116,51 @@ class WindowBackBuffer {
   int mWidth;
   int mHeight;
   bool mAttached;
-  nsWaylandDisplay* mWaylandDisplay;
-  static gfx::SurfaceFormat mFormat;
+};
+
+#ifdef HAVE_LIBDRM
+class WindowBackBufferDMABuf : public WindowBackBuffer {
+ public:
+  WindowBackBufferDMABuf(nsWaylandDisplay* aWaylandDisplay, int aWidth,
+                         int aHeight);
+  ~WindowBackBufferDMABuf();
+
+  bool IsAttached();
+  void SetAttached();
+
+  int GetWidth();
+  int GetHeight();
+  wl_buffer* GetWlBuffer();
+
+  bool SetImageDataFromBuffer(class WindowBackBuffer* aSourceBuffer);
+
+  already_AddRefed Lock();
+  bool IsLocked();
+  void Unlock();
+
+  void Clear();
+  void Detach(wl_buffer* aBuffer);
+  bool Resize(int aWidth, int aHeight);
+
+ private:
+  WaylandDMABufSurface mDMAbufSurface;
+};
+#endif
+
+class WindowImageSurface {
+ public:
+  static void Draw(gfx::SourceSurface* aSurface, gfx::DrawTarget* aDest,
+                   const LayoutDeviceIntRegion& aRegion);
+
+  void Draw(gfx::DrawTarget* aDest,
+            LayoutDeviceIntRegion& aWaylandBufferDamage);
+
+  WindowImageSurface(gfx::SourceSurface* aSurface,
+                     const LayoutDeviceIntRegion& aUpdateRegion);
+
+ private:
+  RefPtr mSurface;
+  const LayoutDeviceIntRegion mUpdateRegion;
 };
 
 // WindowSurfaceWayland is an abstraction for wl_surface
@@ -93,33 +177,50 @@ class WindowSurfaceWayland : public WindowSurface {
   void DelayedCommitHandler();
 
  private:
-  WindowBackBuffer* GetWaylandBufferToDraw(int aWidth, int aHeight);
+  WindowBackBuffer* CreateWaylandBuffer(int aWidth, int aHeight);
+  WindowBackBuffer* GetWaylandBufferToDraw(int aWidth, int aHeight,
+                                           bool aFullScreenUpdate,
+                                           bool aNoBackBufferCopy);
 
   already_AddRefed LockWaylandBuffer(int aWidth, int aHeight,
-                                                      bool aClearBuffer);
+                                                      bool aClearBuffer,
+                                                      bool aFullScreenUpdate,
+                                                      bool aNoBackBufferCopy);
+  void UnlockWaylandBuffer();
+
   already_AddRefed LockImageSurface(
       const gfx::IntSize& aLockSize);
-  bool CommitImageSurfaceToWaylandBuffer(const LayoutDeviceIntRegion& aRegion);
+  bool CommitImageSurfaceToWaylandBuffer(
+      const LayoutDeviceIntRegion& aRegion,
+      LayoutDeviceIntRegion& aWaylandBufferDamage);
   void CommitWaylandBuffer();
   void CalcRectScale(LayoutDeviceIntRect& aRect, int scale);
 
+  void DrawDelayedImageCommits(gfx::DrawTarget* aDrawTarget,
+                               LayoutDeviceIntRegion& aWaylandBufferDamage);
+
   // TODO: Do we need to hold a reference to nsWindow object?
   nsWindow* mWindow;
   nsWaylandDisplay* mWaylandDisplay;
   WindowBackBuffer* mWaylandBuffer;
   LayoutDeviceIntRegion mWaylandBufferDamage;
   WindowBackBuffer* mBackupBuffer[BACK_BUFFER_NUM];
-  RefPtr mImageSurface;
   wl_callback* mFrameCallback;
   wl_surface* mLastCommittedSurface;
   MessageLoop* mDisplayThreadMessageLoop;
   WindowSurfaceWayland** mDelayedCommitHandle;
+  RefPtr mImageSurface;
+  AutoTArray mDelayedImageCommits;
   bool mDrawToWaylandBufferDirectly;
   bool mPendingCommit;
   bool mWaylandBufferFullScreenDamage;
   bool mIsMainThread;
   bool mNeedScaleFactorUpdate;
   bool mWaitToFullScreenUpdate;
+
+  static bool UseDMABufBackend();
+  static bool mUseDMABufInitialized;
+  static bool mUseDMABuf;
 };
 
 }  // namespace widget
diff --git a/widget/gtk/moz.build b/widget/gtk/moz.build
index 210a8a5fe78b..6616cc7a541f 100644
--- a/widget/gtk/moz.build
+++ b/widget/gtk/moz.build
@@ -102,7 +102,8 @@ if CONFIG['MOZ_WAYLAND']:
         'WindowSurfaceWayland.cpp',
     ]
     EXPORTS.mozilla.widget += [
-        'nsWaylandDisplayShutdown.h'
+        'nsWaylandDisplay.h',
+        'nsWaylandDisplayShutdown.h',
     ]
 
 if CONFIG['ACCESSIBILITY']:
diff --git a/widget/gtk/mozcontainer.cpp b/widget/gtk/mozcontainer.cpp
index 7bc7651699e3..2a6f071933ba 100644
--- a/widget/gtk/mozcontainer.cpp
+++ b/widget/gtk/mozcontainer.cpp
@@ -646,9 +646,9 @@ gboolean moz_container_has_wl_egl_window(MozContainer* container) {
 }
 
 gboolean moz_container_surface_needs_clear(MozContainer* container) {
-  gboolean state = container->surface_needs_clear;
+  int ret = container->surface_needs_clear;
   container->surface_needs_clear = false;
-  return state;
+  return ret;
 }
 #endif
 
diff --git a/widget/gtk/nsWindow.cpp b/widget/gtk/nsWindow.cpp
index c98919a9b290..795a922eb004 100644
--- a/widget/gtk/nsWindow.cpp
+++ b/widget/gtk/nsWindow.cpp
@@ -6866,9 +6866,6 @@ bool nsWindow::WaylandSurfaceNeedsClear() {
   if (mContainer) {
     return moz_container_surface_needs_clear(MOZ_CONTAINER(mContainer));
   }
-
-  NS_WARNING(
-      "nsWindow::WaylandSurfaceNeedsClear(): We don't have any mContainer!");
   return false;
 }
 #endif
diff --git a/xpcom/base/CycleCollectedJSContext.cpp b/xpcom/base/CycleCollectedJSContext.cpp
index e2d318e939a8..66c8b9494d4f 100644
--- a/xpcom/base/CycleCollectedJSContext.cpp
+++ b/xpcom/base/CycleCollectedJSContext.cpp
@@ -233,7 +233,7 @@ class PromiseJobRunnable final : public MicroTaskRunnable {
         doc = win->GetExtantDoc();
       }
       AutoHandlingUserInputStatePusher userInpStatePusher(
-          mPropagateUserInputEventHandling, nullptr, doc);
+          mPropagateUserInputEventHandling);
 
       mCallback->Call("promise callback");
       aAso.CheckForInterrupt();
diff --git a/xpcom/base/nsAutoRef.h b/xpcom/base/nsAutoRef.h
index 5ee49183b9bc..33c949af0856 100644
--- a/xpcom/base/nsAutoRef.h
+++ b/xpcom/base/nsAutoRef.h
@@ -154,6 +154,8 @@ class nsAutoRef : public nsAutoRefBase {
   // like a raw reference.
   operator typename SimpleRef::RawRef() const { return this->get(); }
 
+  explicit operator bool() const { return this->HaveResource(); }
+
   // Transfer ownership from another smart reference.
   void steal(ThisClass& aOtherRef) { BaseClass::steal(aOtherRef); }
 
diff --git a/xpcom/glue/FileUtils.cpp b/xpcom/glue/FileUtils.cpp
index 9ab37eaf4e28..e491f8b664a9 100644
--- a/xpcom/glue/FileUtils.cpp
+++ b/xpcom/glue/FileUtils.cpp
@@ -35,6 +35,7 @@
 #  include 
 #elif defined(XP_WIN)
 #  include 
+#  include 
 #  include 
 #endif
 
@@ -368,27 +369,25 @@ void mozilla::ReadAheadLib(mozilla::pathstr_t aFilePath) {
   if (!fd) {
     return;
   }
-  LARGE_INTEGER fileSize = {};
-  BOOL success = GetFileSizeEx(fd, &fileSize);
-  if (!success || !fileSize.QuadPart ||
-      fileSize.QuadPart > std::numeric_limits::max()) {
-    return;
-  }
 
   nsAutoHandle mapping(
       CreateFileMapping(fd, nullptr, SEC_IMAGE | PAGE_READONLY, 0, 0, nullptr));
-
   if (!mapping) {
     return;
   }
 
-  PVOID data =
-      MapViewOfFile(mapping, FILE_MAP_READ, 0, 0, (size_t)fileSize.QuadPart);
-  auto guard = MakeScopeExit([=]() { UnmapViewOfFile(data); });
-
-  if (data) {
-    PrefetchMemory((uint8_t*)data, (size_t)fileSize.QuadPart);
+  PVOID data = MapViewOfFile(mapping, FILE_MAP_READ, 0, 0, 0);
+  if (!data) {
+    return;
   }
+  auto guard = MakeScopeExit([=]() { UnmapViewOfFile(data); });
+  mozilla::nt::PEHeaders headers(data);
+  Maybe> bounds = headers.GetBounds();
+  if (!bounds) {
+    return;
+  }
+
+  PrefetchMemory((uint8_t*)data, bounds->Length());
 
 #elif defined(LINUX) && !defined(ANDROID)
   int fd = open(aFilePath, O_RDONLY);