diff --git a/.hgtags b/.hgtags index 6321d26c59a3..549ffebb494e 100644 --- a/.hgtags +++ b/.hgtags @@ -139,3 +139,4 @@ f7e9777221a34f9f23c2e4933307eb38b621b679 FIREFOX_NIGHTLY_57_END 40a14ca1cf04499f398e4cb8ba359b39eae4e216 FIREFOX_BETA_58_BASE 1f91961bb79ad06fd4caef9e5dfd546afd5bf42c FIREFOX_NIGHTLY_58_END 5faab9e619901b1513fd4ca137747231be550def FIREFOX_NIGHTLY_59_END +e33efdb3e1517d521deb949de3fcd6d9946ea440 FIREFOX_BETA_60_BASE diff --git a/accessible/base/XULMap.h b/accessible/base/XULMap.h index d624810b9467..d0190f2b9c2e 100644 --- a/accessible/base/XULMap.h +++ b/accessible/base/XULMap.h @@ -2,10 +2,14 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +XULMAP_TYPE(browser, OuterDocAccessible) +XULMAP_TYPE(button, XULButtonAccessible) XULMAP_TYPE(checkbox, XULCheckboxAccessible) XULMAP_TYPE(dropMarker, XULDropmarkerAccessible) +XULMAP_TYPE(editor, OuterDocAccessible) XULMAP_TYPE(findbar, XULToolbarAccessible) XULMAP_TYPE(groupbox, XULGroupboxAccessible) +XULMAP_TYPE(iframe, OuterDocAccessible) XULMAP_TYPE(listbox, XULListboxAccessibleWrap) XULMAP_TYPE(listhead, XULColumAccessible) XULMAP_TYPE(listheader, XULColumnItemAccessible) @@ -22,6 +26,7 @@ XULMAP_TYPE(radio, XULRadioButtonAccessible) XULMAP_TYPE(radiogroup, XULRadioGroupAccessible) XULMAP_TYPE(richlistbox, XULListboxAccessibleWrap) XULMAP_TYPE(richlistitem, XULListitemAccessible) +XULMAP_TYPE(scale, XULSliderAccessible) XULMAP_TYPE(statusbar, XULStatusBarAccessible) XULMAP_TYPE(tab, XULTabAccessible) XULMAP_TYPE(tabpanels, XULTabpanelsAccessible) @@ -33,6 +38,7 @@ XULMAP_TYPE(treecol, XULColumnItemAccessible) XULMAP_TYPE(treecolpicker, XULButtonAccessible) XULMAP_TYPE(treecols, XULTreeColumAccessible) XULMAP_TYPE(toolbar, XULToolbarAccessible) +XULMAP_TYPE(toolbarbutton, XULToolbarButtonAccessible) XULMAP_TYPE(tooltip, XULTooltipAccessible) XULMAP( @@ -73,6 +79,44 @@ XULMAP( } ) +XULMAP( + menupopup, + [](nsIContent* aContent, Accessible* aContext) { + return CreateMenupopupAccessible(aContent, aContext); + } +) + +XULMAP( + popup, + [](nsIContent* aContent, Accessible* aContext) { + return CreateMenupopupAccessible(aContent, aContext); + } +) + +XULMAP( + textbox, + [](nsIContent* aContent, Accessible* aContext) -> Accessible* { + if (aContent->IsElement() && + aContent->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type, + nsGkAtoms::autocomplete, eIgnoreCase)) { + return new XULComboboxAccessible(aContent, aContext->Document()); + } + + return new EnumRoleAccessible(aContent, aContext->Document()); + } +) + +XULMAP( + thumb, + [](nsIContent* aContent, Accessible* aContext) -> Accessible* { + if (aContent->IsElement() && + aContent->AsElement()->ClassList()->Contains(NS_LITERAL_STRING("scale-thumb"))) { + return new XULThumbAccessible(aContent, aContext->Document()); + } + return nullptr; + } +) + XULMAP( tree, [](nsIContent* aContent, Accessible* aContext) -> Accessible* { diff --git a/accessible/base/nsAccessibilityService.cpp b/accessible/base/nsAccessibilityService.cpp index 8a099dcecc56..415be1e297f0 100644 --- a/accessible/base/nsAccessibilityService.cpp +++ b/accessible/base/nsAccessibilityService.cpp @@ -23,6 +23,7 @@ #include "nsAccUtils.h" #include "nsArrayUtils.h" #include "nsAttrName.h" +#include "nsDOMTokenList.h" #include "nsEventShell.h" #include "nsIURI.h" #include "nsTextFormatter.h" @@ -143,6 +144,28 @@ MustBeAccessible(nsIContent* aContent, DocAccessible* aDocument) return false; } +/** + * Used by XULMap.h to map both menupopup and popup elements + */ +#ifdef MOZ_XUL +Accessible* +CreateMenupopupAccessible(nsIContent* aContent, Accessible* aContext) +{ +#ifdef MOZ_ACCESSIBILITY_ATK + // ATK considers this node to be redundant when within menubars, and it makes menu + // navigation with assistive technologies more difficult + // XXX In the future we will should this for consistency across the nsIAccessible + // implementations on each platform for a consistent scripting environment, but + // then strip out redundant accessibles in the AccessibleWrap class for each platform. + nsIContent *parent = aContent->GetParent(); + if (parent && parent->IsXULElement(nsGkAtoms::menu)) + return nullptr; +#endif + + return new XULMenupopupAccessible(aContent, aContext->Document()); +} +#endif + //////////////////////////////////////////////////////////////////////////////// // Accessible constructors @@ -1449,46 +1472,21 @@ nsAccessibilityService::CreateAccessibleByType(nsIContent* aContent, { nsAutoString role; nsCoreUtils::XBLBindingRole(aContent, role); - if (role.IsEmpty() || role.EqualsLiteral("none")) + if (role.IsEmpty()) return nullptr; - if (role.EqualsLiteral("outerdoc")) { - RefPtr accessible = new OuterDocAccessible(aContent, aDoc); - return accessible.forget(); - } - RefPtr accessible; #ifdef MOZ_XUL // XUL controls - if (role.EqualsLiteral("xul:button")) { - accessible = new XULButtonAccessible(aContent, aDoc); - - } else if (role.EqualsLiteral("xul:colorpicker")) { + if (role.EqualsLiteral("xul:colorpicker")) { accessible = new XULColorPickerAccessible(aContent, aDoc); } else if (role.EqualsLiteral("xul:colorpickertile")) { accessible = new XULColorPickerTileAccessible(aContent, aDoc); - } else if (role.EqualsLiteral("xul:combobox")) { - accessible = new XULComboboxAccessible(aContent, aDoc); - } else if (role.EqualsLiteral("xul:link")) { accessible = new XULLinkAccessible(aContent, aDoc); - } else if (role.EqualsLiteral("xul:menupopup")) { -#ifdef MOZ_ACCESSIBILITY_ATK - // ATK considers this node to be redundant when within menubars, and it makes menu - // navigation with assistive technologies more difficult - // XXX In the future we will should this for consistency across the nsIAccessible - // implementations on each platform for a consistent scripting environment, but - // then strip out redundant accessibles in the AccessibleWrap class for each platform. - nsIContent *parent = aContent->GetParent(); - if (parent && parent->IsXULElement(nsGkAtoms::menu)) - return nullptr; -#endif - - accessible = new XULMenupopupAccessible(aContent, aDoc); - } else if(role.EqualsLiteral("xul:pane")) { accessible = new EnumRoleAccessible(aContent, aDoc); @@ -1501,21 +1499,9 @@ nsAccessibilityService::CreateAccessibleByType(nsIContent* aContent, else accessible = new EnumRoleAccessible(aContent, aDoc); - } else if (role.EqualsLiteral("xul:scale")) { - accessible = new XULSliderAccessible(aContent, aDoc); - } else if (role.EqualsLiteral("xul:text")) { accessible = new XULLabelAccessible(aContent, aDoc); - } else if (role.EqualsLiteral("xul:textbox")) { - accessible = new EnumRoleAccessible(aContent, aDoc); - - } else if (role.EqualsLiteral("xul:thumb")) { - accessible = new XULThumbAccessible(aContent, aDoc); - - } else if (role.EqualsLiteral("xul:toolbarbutton")) { - accessible = new XULToolbarButtonAccessible(aContent, aDoc); - } #endif // MOZ_XUL diff --git a/accessible/ipc/win/HandlerProvider.cpp b/accessible/ipc/win/HandlerProvider.cpp index 8c9339347a49..488315f54b4a 100644 --- a/accessible/ipc/win/HandlerProvider.cpp +++ b/accessible/ipc/win/HandlerProvider.cpp @@ -464,6 +464,13 @@ HandlerProvider::MarshalAs(REFIID aIid) return aIid; } +HRESULT +HandlerProvider::DisconnectHandlerRemotes() +{ + IUnknown* unk = static_cast(this); + return ::CoDisconnectObject(unk, 0); +} + REFIID HandlerProvider::GetEffectiveOutParamIid(REFIID aCallIid, ULONG aCallMethod) diff --git a/accessible/ipc/win/HandlerProvider.h b/accessible/ipc/win/HandlerProvider.h index b608c5260207..110604e44718 100644 --- a/accessible/ipc/win/HandlerProvider.h +++ b/accessible/ipc/win/HandlerProvider.h @@ -47,6 +47,7 @@ public: STDMETHODIMP WriteHandlerPayload(NotNull aInterceptor, NotNull aStream) override; STDMETHODIMP_(REFIID) MarshalAs(REFIID aIid) override; + STDMETHODIMP DisconnectHandlerRemotes() override; STDMETHODIMP_(REFIID) GetEffectiveOutParamIid(REFIID aCallIid, ULONG aCallMethod) override; STDMETHODIMP NewInstance(REFIID aIid, diff --git a/accessible/windows/msaa/AccessibleWrap.cpp b/accessible/windows/msaa/AccessibleWrap.cpp index 6e14927ca802..702bbabbbc6c 100644 --- a/accessible/windows/msaa/AccessibleWrap.cpp +++ b/accessible/windows/msaa/AccessibleWrap.cpp @@ -46,6 +46,7 @@ #include "mozilla/ReverseIterator.h" #include "nsIXULRuntime.h" #include "mozilla/mscom/AsyncInvoker.h" +#include "mozilla/mscom/Interceptor.h" #include "oleacc.h" @@ -103,6 +104,24 @@ AccessibleWrap::Shutdown() } } + if (XRE_IsContentProcess()) { + // Bug 1434822: To improve performance for cross-process COM, we disable COM + // garbage collection. However, this means we never receive Release calls + // from clients, so defunct accessibles can never be deleted. Since we + // know when an accessible is shutting down, we can work around this by + // forcing COM to disconnect this object from all of its remote clients, + // which will cause associated references to be released. + IUnknown* unk = static_cast(this); + mscom::Interceptor::DisconnectRemotesForTarget(unk); + // If an accessible was retrieved via IAccessibleHypertext::hyperlink*, + // it will have a different Interceptor that won't be matched by the above + // call, even though it's the same object. Therefore, call it again with + // the IAccessibleHyperlink pointer. We can remove this horrible hack once + // bug 1440267 is fixed. + unk = static_cast(this); + mscom::Interceptor::DisconnectRemotesForTarget(unk); + } + Accessible::Shutdown(); } diff --git a/accessible/xul/XULFormControlAccessible.cpp b/accessible/xul/XULFormControlAccessible.cpp index e7fbc3eb37e9..50297bf723c3 100644 --- a/accessible/xul/XULFormControlAccessible.cpp +++ b/accessible/xul/XULFormControlAccessible.cpp @@ -169,20 +169,16 @@ XULButtonAccessible::IsAcceptableChild(nsIContent* aEl) const // buttons can have button (@type="menu-button") and popup accessibles // (@type="menu-button", @type="menu" or columnpicker. - // XXX: no children until the button is menu button. Probably it's not - // totally correct but in general AT wants to have leaf buttons. - nsAutoString role; - nsCoreUtils::XBLBindingRole(aEl, role); - - // Get an accessible for menupopup or panel elements. - if (role.EqualsLiteral("xul:menupopup")) { + // Get an accessible for menupopup or popup elements. + if (aEl->IsXULElement(nsGkAtoms::menupopup) || + aEl->IsXULElement(nsGkAtoms::popup)) { return true; } - // Button type="menu-button" contains a real button. Get an accessible + // Button and toolbarbutton are real buttons. Get an accessible // for it. Ignore dropmarker button which is placed as a last child. - if ((!role.EqualsLiteral("xul:button") && - !role.EqualsLiteral("xul:toolbarbutton")) || + if ((!aEl->IsXULElement(nsGkAtoms::button) && + !aEl->IsXULElement(nsGkAtoms::toolbarbutton)) || aEl->IsXULElement(nsGkAtoms::dropMarker)) { return false; } diff --git a/browser/base/content/browser-menubar.inc b/browser/base/content/browser-menubar.inc index a27a1d5aedc2..502f21983924 100644 --- a/browser/base/content/browser-menubar.inc +++ b/browser/base/content/browser-menubar.inc @@ -212,6 +212,7 @@ observes="viewHistorySidebar" label="&historyButton.label;"/> diff --git a/browser/base/content/browser.xul b/browser/base/content/browser.xul index 23765eef43ac..b84e36c6e95e 100644 --- a/browser/base/content/browser.xul +++ b/browser/base/content/browser.xul @@ -298,7 +298,7 @@ diff --git a/browser/base/content/tabbrowser.js b/browser/base/content/tabbrowser.js index 87eac0f126df..9eaa59d07a74 100644 --- a/browser/base/content/tabbrowser.js +++ b/browser/base/content/tabbrowser.js @@ -201,7 +201,7 @@ class TabBrowser { this.mCurrentBrowser.droppedLinkHandler = handleDroppedLink; // Hook up the event listeners to the first browser - var tabListener = this.mTabProgressListener(this.mCurrentTab, this.mCurrentBrowser, true, false); + var tabListener = new TabProgressListener(this.mCurrentTab, this.mCurrentBrowser, true, false); const nsIWebProgress = Ci.nsIWebProgress; const filter = Cc["@mozilla.org/appshell/component/browser-status-filter;1"] .createInstance(nsIWebProgress); @@ -764,437 +764,6 @@ class TabBrowser { } } - /** - * A web progress listener object definition for a given tab. - */ - mTabProgressListener(aTab, aBrowser, aStartsBlank, aWasPreloadedBrowser, aOrigStateFlags) { - let stateFlags = aOrigStateFlags || 0; - // Initialize mStateFlags to non-zero e.g. when creating a progress - // listener for preloaded browsers as there was no progress listener - // around when the content started loading. If the content didn't - // quite finish loading yet, mStateFlags will very soon be overridden - // with the correct value and end up at STATE_STOP again. - if (aWasPreloadedBrowser) { - stateFlags = Ci.nsIWebProgressListener.STATE_STOP | - Ci.nsIWebProgressListener.STATE_IS_REQUEST; - } - - return ({ - mTabBrowser: this, - mTab: aTab, - mBrowser: aBrowser, - mBlank: aStartsBlank, - - // cache flags for correct status UI update after tab switching - mStateFlags: stateFlags, - mStatus: 0, - mMessage: "", - mTotalProgress: 0, - - // count of open requests (should always be 0 or 1) - mRequestCount: 0, - - destroy() { - delete this.mTab; - delete this.mBrowser; - delete this.mTabBrowser; - }, - - _callProgressListeners() { - Array.unshift(arguments, this.mBrowser); - return this.mTabBrowser._callProgressListeners.apply(this.mTabBrowser, arguments); - }, - - _shouldShowProgress(aRequest) { - if (this.mBlank) - return false; - - // Don't show progress indicators in tabs for about: URIs - // pointing to local resources. - if ((aRequest instanceof Ci.nsIChannel) && - this.mTabBrowser._isLocalAboutURI(aRequest.originalURI, aRequest.URI)) { - return false; - } - - return true; - }, - - _isForInitialAboutBlank(aWebProgress, aStateFlags, aLocation) { - if (!this.mBlank || !aWebProgress.isTopLevel) { - return false; - } - - // If the state has STATE_STOP, and no requests were in flight, then this - // must be the initial "stop" for the initial about:blank document. - const nsIWebProgressListener = Ci.nsIWebProgressListener; - if (aStateFlags & nsIWebProgressListener.STATE_STOP && - this.mRequestCount == 0 && - !aLocation) { - return true; - } - - let location = aLocation ? aLocation.spec : ""; - return location == "about:blank"; - }, - - onProgressChange(aWebProgress, aRequest, - aCurSelfProgress, aMaxSelfProgress, - aCurTotalProgress, aMaxTotalProgress) { - this.mTotalProgress = aMaxTotalProgress ? aCurTotalProgress / aMaxTotalProgress : 0; - - if (!this._shouldShowProgress(aRequest)) - return; - - if (this.mTotalProgress && this.mTab.hasAttribute("busy")) - this.mTab.setAttribute("progress", "true"); - - this._callProgressListeners("onProgressChange", - [aWebProgress, aRequest, - aCurSelfProgress, aMaxSelfProgress, - aCurTotalProgress, aMaxTotalProgress]); - }, - - onProgressChange64(aWebProgress, aRequest, - aCurSelfProgress, aMaxSelfProgress, - aCurTotalProgress, aMaxTotalProgress) { - return this.onProgressChange(aWebProgress, aRequest, - aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress, - aMaxTotalProgress); - }, - - /* eslint-disable complexity */ - onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) { - if (!aRequest) - return; - - const nsIWebProgressListener = Ci.nsIWebProgressListener; - const nsIChannel = Ci.nsIChannel; - let location, originalLocation; - try { - aRequest.QueryInterface(nsIChannel); - location = aRequest.URI; - originalLocation = aRequest.originalURI; - } catch (ex) {} - - let ignoreBlank = this._isForInitialAboutBlank(aWebProgress, aStateFlags, - location); - - // If we were ignoring some messages about the initial about:blank, and we - // got the STATE_STOP for it, we'll want to pay attention to those messages - // from here forward. Similarly, if we conclude that this state change - // is one that we shouldn't be ignoring, then stop ignoring. - if ((ignoreBlank && - aStateFlags & nsIWebProgressListener.STATE_STOP && - aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK) || - !ignoreBlank && this.mBlank) { - this.mBlank = false; - } - - if (aStateFlags & nsIWebProgressListener.STATE_START) { - this.mRequestCount++; - } else if (aStateFlags & nsIWebProgressListener.STATE_STOP) { - const NS_ERROR_UNKNOWN_HOST = 2152398878; - if (--this.mRequestCount > 0 && aStatus == NS_ERROR_UNKNOWN_HOST) { - // to prevent bug 235825: wait for the request handled - // by the automatic keyword resolver - return; - } - // since we (try to) only handle STATE_STOP of the last request, - // the count of open requests should now be 0 - this.mRequestCount = 0; - } - - if (aStateFlags & nsIWebProgressListener.STATE_START && - aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK) { - if (aWebProgress.isTopLevel) { - // Need to use originalLocation rather than location because things - // like about:home and about:privatebrowsing arrive with nsIRequest - // pointing to their resolved jar: or file: URIs. - if (!(originalLocation && gInitialPages.includes(originalLocation.spec) && - originalLocation != "about:blank" && - this.mBrowser.initialPageLoadedFromURLBar != originalLocation.spec && - this.mBrowser.currentURI && this.mBrowser.currentURI.spec == "about:blank")) { - // Indicating that we started a load will allow the location - // bar to be cleared when the load finishes. - // In order to not overwrite user-typed content, we avoid it - // (see if condition above) in a very specific case: - // If the load is of an 'initial' page (e.g. about:privatebrowsing, - // about:newtab, etc.), was not explicitly typed in the location - // bar by the user, is not about:blank (because about:blank can be - // loaded by websites under their principal), and the current - // page in the browser is about:blank (indicating it is a newly - // created or re-created browser, e.g. because it just switched - // remoteness or is a new tab/window). - this.mBrowser.urlbarChangeTracker.startedLoad(); - } - delete this.mBrowser.initialPageLoadedFromURLBar; - // If the browser is loading it must not be crashed anymore - this.mTab.removeAttribute("crashed"); - } - - if (this._shouldShowProgress(aRequest)) { - if (!(aStateFlags & nsIWebProgressListener.STATE_RESTORING) && - aWebProgress && aWebProgress.isTopLevel) { - this.mTab.setAttribute("busy", "true"); - this.mTab._notselectedsinceload = !this.mTab.selected; - SchedulePressure.startMonitoring(window, { - highPressureFn() { - // Only switch back to the SVG loading indicator after getting - // three consecutive low pressure callbacks. Used to prevent - // switching quickly between the SVG and APNG loading indicators. - gBrowser.tabContainer._schedulePressureCount = gBrowser.schedulePressureDefaultCount; - gBrowser.tabContainer.setAttribute("schedulepressure", "true"); - }, - lowPressureFn() { - if (!gBrowser.tabContainer._schedulePressureCount || - --gBrowser.tabContainer._schedulePressureCount <= 0) { - gBrowser.tabContainer.removeAttribute("schedulepressure"); - } - - // If tabs are closed while they are loading we need to - // stop monitoring schedule pressure. We don't stop monitoring - // during high pressure times because we want to eventually - // return to the SVG tab loading animations. - let continueMonitoring = true; - if (!document.querySelector(".tabbrowser-tab[busy]")) { - SchedulePressure.stopMonitoring(window); - continueMonitoring = false; - } - return { continueMonitoring }; - }, - }); - this.mTabBrowser.syncThrobberAnimations(this.mTab); - } - - if (this.mTab.selected) { - this.mTabBrowser.mIsBusy = true; - } - } - } else if (aStateFlags & nsIWebProgressListener.STATE_STOP && - aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK) { - - if (this.mTab.hasAttribute("busy")) { - this.mTab.removeAttribute("busy"); - if (!document.querySelector(".tabbrowser-tab[busy]")) { - SchedulePressure.stopMonitoring(window); - this.mTabBrowser.tabContainer.removeAttribute("schedulepressure"); - } - - // Only animate the "burst" indicating the page has loaded if - // the top-level page is the one that finished loading. - if (aWebProgress.isTopLevel && !aWebProgress.isLoadingDocument && - Components.isSuccessCode(aStatus) && - !this.mTabBrowser.tabAnimationsInProgress && - Services.prefs.getBoolPref("toolkit.cosmeticAnimations.enabled")) { - if (this.mTab._notselectedsinceload) { - this.mTab.setAttribute("notselectedsinceload", "true"); - } else { - this.mTab.removeAttribute("notselectedsinceload"); - } - - this.mTab.setAttribute("bursting", "true"); - } - - this.mTabBrowser._tabAttrModified(this.mTab, ["busy"]); - if (!this.mTab.selected) - this.mTab.setAttribute("unread", "true"); - } - this.mTab.removeAttribute("progress"); - - if (aWebProgress.isTopLevel) { - let isSuccessful = Components.isSuccessCode(aStatus); - if (!isSuccessful && !isTabEmpty(this.mTab)) { - // Restore the current document's location in case the - // request was stopped (possibly from a content script) - // before the location changed. - - this.mBrowser.userTypedValue = null; - - let inLoadURI = this.mBrowser.inLoadURI; - if (this.mTab.selected && gURLBar && !inLoadURI) { - URLBarSetURI(); - } - } else if (isSuccessful) { - this.mBrowser.urlbarChangeTracker.finishedLoad(); - } - - // Ignore initial about:blank to prevent flickering. - if (!this.mBrowser.mIconURL && !ignoreBlank) { - // Don't switch to the default icon on about:home or about:newtab, - // since these pages get their favicon set in browser code to - // improve perceived performance. - let isNewTab = originalLocation && - (originalLocation.spec == "about:newtab" || - originalLocation.spec == "about:privatebrowsing" || - originalLocation.spec == "about:home"); - if (!isNewTab) { - this.mTabBrowser.useDefaultIcon(this.mTab); - } - } - } - - // For keyword URIs clear the user typed value since they will be changed into real URIs - if (location.scheme == "keyword") - this.mBrowser.userTypedValue = null; - - if (this.mTab.selected) - this.mTabBrowser.mIsBusy = false; - } - - if (ignoreBlank) { - this._callProgressListeners("onUpdateCurrentBrowser", - [aStateFlags, aStatus, "", 0], - true, false); - } else { - this._callProgressListeners("onStateChange", - [aWebProgress, aRequest, aStateFlags, aStatus], - true, false); - } - - this._callProgressListeners("onStateChange", - [aWebProgress, aRequest, aStateFlags, aStatus], - false); - - if (aStateFlags & (nsIWebProgressListener.STATE_START | - nsIWebProgressListener.STATE_STOP)) { - // reset cached temporary values at beginning and end - this.mMessage = ""; - this.mTotalProgress = 0; - } - this.mStateFlags = aStateFlags; - this.mStatus = aStatus; - }, - /* eslint-enable complexity */ - - onLocationChange(aWebProgress, aRequest, aLocation, - aFlags) { - // OnLocationChange is called for both the top-level content - // and the subframes. - let topLevel = aWebProgress.isTopLevel; - - if (topLevel) { - let isSameDocument = !!(aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT); - // We need to clear the typed value - // if the document failed to load, to make sure the urlbar reflects the - // failed URI (particularly for SSL errors). However, don't clear the value - // if the error page's URI is about:blank, because that causes complete - // loss of urlbar contents for invalid URI errors (see bug 867957). - // Another reason to clear the userTypedValue is if this was an anchor - // navigation initiated by the user. - if (this.mBrowser.didStartLoadSinceLastUserTyping() || - ((aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_ERROR_PAGE) && - aLocation.spec != "about:blank") || - (isSameDocument && this.mBrowser.inLoadURI)) { - this.mBrowser.userTypedValue = null; - } - - // If the tab has been set to "busy" outside the stateChange - // handler below (e.g. by sessionStore.navigateAndRestore), and - // the load results in an error page, it's possible that there - // isn't any (STATE_IS_NETWORK & STATE_STOP) state to cause busy - // attribute being removed. In this case we should remove the - // attribute here. - if ((aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_ERROR_PAGE) && - this.mTab.hasAttribute("busy")) { - this.mTab.removeAttribute("busy"); - this.mTabBrowser._tabAttrModified(this.mTab, ["busy"]); - } - - // If the browser was playing audio, we should remove the playing state. - if (this.mTab.hasAttribute("soundplaying") && !isSameDocument) { - clearTimeout(this.mTab._soundPlayingAttrRemovalTimer); - this.mTab._soundPlayingAttrRemovalTimer = 0; - this.mTab.removeAttribute("soundplaying"); - this.mTabBrowser._tabAttrModified(this.mTab, ["soundplaying"]); - } - - // If the browser was previously muted, we should restore the muted state. - if (this.mTab.hasAttribute("muted")) { - this.mTab.linkedBrowser.mute(); - } - - if (this.mTabBrowser.isFindBarInitialized(this.mTab)) { - let findBar = this.mTabBrowser.getFindBar(this.mTab); - - // Close the Find toolbar if we're in old-style TAF mode - if (findBar.findMode != findBar.FIND_NORMAL) { - findBar.close(); - } - } - - this.mTabBrowser.setTabTitle(this.mTab); - - // Don't clear the favicon if this tab is in the pending - // state, as SessionStore will have set the icon for us even - // though we're pointed at an about:blank. Also don't clear it - // if onLocationChange was triggered by a pushState or a - // replaceState (bug 550565) or a hash change (bug 408415). - if (!this.mTab.hasAttribute("pending") && - aWebProgress.isLoadingDocument && - !isSameDocument) { - this.mBrowser.mIconURL = null; - } - - let userContextId = this.mBrowser.getAttribute("usercontextid") || 0; - if (this.mBrowser.registeredOpenURI) { - this.mTabBrowser._unifiedComplete - .unregisterOpenPage(this.mBrowser.registeredOpenURI, - userContextId); - delete this.mBrowser.registeredOpenURI; - } - // Tabs in private windows aren't registered as "Open" so - // that they don't appear as switch-to-tab candidates. - if (!isBlankPageURL(aLocation.spec) && - (!PrivateBrowsingUtils.isWindowPrivate(window) || - PrivateBrowsingUtils.permanentPrivateBrowsing)) { - this.mTabBrowser._unifiedComplete - .registerOpenPage(aLocation, userContextId); - this.mBrowser.registeredOpenURI = aLocation; - } - } - - if (!this.mBlank) { - this._callProgressListeners("onLocationChange", - [aWebProgress, aRequest, aLocation, aFlags]); - } - - if (topLevel) { - this.mBrowser.lastURI = aLocation; - this.mBrowser.lastLocationChange = Date.now(); - } - }, - - onStatusChange(aWebProgress, aRequest, aStatus, aMessage) { - if (this.mBlank) - return; - - this._callProgressListeners("onStatusChange", - [aWebProgress, aRequest, aStatus, aMessage]); - - this.mMessage = aMessage; - }, - - onSecurityChange(aWebProgress, aRequest, aState) { - this._callProgressListeners("onSecurityChange", - [aWebProgress, aRequest, aState]); - }, - - onRefreshAttempted(aWebProgress, aURI, aDelay, aSameURI) { - return this._callProgressListeners("onRefreshAttempted", - [aWebProgress, aURI, aDelay, aSameURI]); - }, - - QueryInterface(aIID) { - if (aIID.equals(Ci.nsIWebProgressListener) || - aIID.equals(Ci.nsIWebProgressListener2) || - aIID.equals(Ci.nsISupportsWeakReference) || - aIID.equals(Ci.nsISupports)) - return this; - throw Cr.NS_NOINTERFACE; - } - }); - } - storeIcon(aBrowser, aURI, aLoadingPrincipal, aRequestContextID) { try { if (!(aURI instanceof Ci.nsIURI)) { @@ -2115,7 +1684,7 @@ class TabBrowser { // Create a new tab progress listener for the new browser we just injected, // since tab progress listeners have logic for handling the initial about:blank // load - listener = this.mTabProgressListener(tab, aBrowser, true, false); + listener = new TabProgressListener(tab, aBrowser, true, false); this._tabListeners.set(tab, listener); filter.addProgressListener(listener, Ci.nsIWebProgress.NOTIFY_ALL); @@ -2516,7 +2085,7 @@ class TabBrowser { } // wire up a progress listener for the new browser object. - let tabListener = this.mTabProgressListener(aTab, browser, uriIsAboutBlank, usingPreloadedContent); + let tabListener = new TabProgressListener(aTab, browser, uriIsAboutBlank, usingPreloadedContent); const filter = Cc["@mozilla.org/appshell/component/browser-status-filter;1"] .createInstance(Ci.nsIWebProgress); filter.addProgressListener(tabListener, Ci.nsIWebProgress.NOTIFY_ALL); @@ -3601,7 +3170,7 @@ class TabBrowser { this._swapBrowserDocShells(aOurTab, otherBrowser, aFlags); // Restore the listeners for the swapped in tab. - tabListener = otherTabBrowser.mTabProgressListener(aOtherTab, otherBrowser, false, false); + tabListener = new otherTabBrowser.ownerGlobal.TabProgressListener(aOtherTab, otherBrowser, false, false); otherTabBrowser._tabListeners.set(aOtherTab, tabListener); const notifyAll = Ci.nsIWebProgress.NOTIFY_ALL; @@ -3667,8 +3236,7 @@ class TabBrowser { } // Restore the progress listener - tabListener = this.mTabProgressListener(aOurTab, ourBrowser, false, false, - aStateFlags); + tabListener = new TabProgressListener(aOurTab, ourBrowser, false, false, aStateFlags); this._tabListeners.set(aOurTab, tabListener); const notifyAll = Ci.nsIWebProgress.NOTIFY_ALL; @@ -5735,3 +5303,428 @@ class TabBrowser { }); } } + +/** + * A web progress listener object definition for a given tab. + */ +class TabProgressListener { + constructor(aTab, aBrowser, aStartsBlank, aWasPreloadedBrowser, aOrigStateFlags) { + let stateFlags = aOrigStateFlags || 0; + // Initialize mStateFlags to non-zero e.g. when creating a progress + // listener for preloaded browsers as there was no progress listener + // around when the content started loading. If the content didn't + // quite finish loading yet, mStateFlags will very soon be overridden + // with the correct value and end up at STATE_STOP again. + if (aWasPreloadedBrowser) { + stateFlags = Ci.nsIWebProgressListener.STATE_STOP | + Ci.nsIWebProgressListener.STATE_IS_REQUEST; + } + + this.mTab = aTab; + this.mBrowser = aBrowser; + this.mBlank = aStartsBlank; + + // cache flags for correct status UI update after tab switching + this.mStateFlags = stateFlags; + this.mStatus = 0; + this.mMessage = ""; + this.mTotalProgress = 0; + + // count of open requests (should always be 0 or 1) + this.mRequestCount = 0; + } + + destroy() { + delete this.mTab; + delete this.mBrowser; + } + + _callProgressListeners() { + Array.unshift(arguments, this.mBrowser); + return gBrowser._callProgressListeners.apply(gBrowser, arguments); + } + + _shouldShowProgress(aRequest) { + if (this.mBlank) + return false; + + // Don't show progress indicators in tabs for about: URIs + // pointing to local resources. + if ((aRequest instanceof Ci.nsIChannel) && + gBrowser._isLocalAboutURI(aRequest.originalURI, aRequest.URI)) { + return false; + } + + return true; + } + + _isForInitialAboutBlank(aWebProgress, aStateFlags, aLocation) { + if (!this.mBlank || !aWebProgress.isTopLevel) { + return false; + } + + // If the state has STATE_STOP, and no requests were in flight, then this + // must be the initial "stop" for the initial about:blank document. + const nsIWebProgressListener = Ci.nsIWebProgressListener; + if (aStateFlags & nsIWebProgressListener.STATE_STOP && + this.mRequestCount == 0 && + !aLocation) { + return true; + } + + let location = aLocation ? aLocation.spec : ""; + return location == "about:blank"; + } + + onProgressChange(aWebProgress, aRequest, aCurSelfProgress, aMaxSelfProgress, + aCurTotalProgress, aMaxTotalProgress) { + this.mTotalProgress = aMaxTotalProgress ? aCurTotalProgress / aMaxTotalProgress : 0; + + if (!this._shouldShowProgress(aRequest)) + return; + + if (this.mTotalProgress && this.mTab.hasAttribute("busy")) + this.mTab.setAttribute("progress", "true"); + + this._callProgressListeners("onProgressChange", + [aWebProgress, aRequest, + aCurSelfProgress, aMaxSelfProgress, + aCurTotalProgress, aMaxTotalProgress]); + } + + onProgressChange64(aWebProgress, aRequest, aCurSelfProgress, + aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress) { + return this.onProgressChange(aWebProgress, aRequest, + aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress, + aMaxTotalProgress); + } + + /* eslint-disable complexity */ + onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) { + if (!aRequest) + return; + + const nsIWebProgressListener = Ci.nsIWebProgressListener; + const nsIChannel = Ci.nsIChannel; + let location, originalLocation; + try { + aRequest.QueryInterface(nsIChannel); + location = aRequest.URI; + originalLocation = aRequest.originalURI; + } catch (ex) {} + + let ignoreBlank = this._isForInitialAboutBlank(aWebProgress, aStateFlags, + location); + + // If we were ignoring some messages about the initial about:blank, and we + // got the STATE_STOP for it, we'll want to pay attention to those messages + // from here forward. Similarly, if we conclude that this state change + // is one that we shouldn't be ignoring, then stop ignoring. + if ((ignoreBlank && + aStateFlags & nsIWebProgressListener.STATE_STOP && + aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK) || + !ignoreBlank && this.mBlank) { + this.mBlank = false; + } + + if (aStateFlags & nsIWebProgressListener.STATE_START) { + this.mRequestCount++; + } else if (aStateFlags & nsIWebProgressListener.STATE_STOP) { + const NS_ERROR_UNKNOWN_HOST = 2152398878; + if (--this.mRequestCount > 0 && aStatus == NS_ERROR_UNKNOWN_HOST) { + // to prevent bug 235825: wait for the request handled + // by the automatic keyword resolver + return; + } + // since we (try to) only handle STATE_STOP of the last request, + // the count of open requests should now be 0 + this.mRequestCount = 0; + } + + if (aStateFlags & nsIWebProgressListener.STATE_START && + aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK) { + if (aWebProgress.isTopLevel) { + // Need to use originalLocation rather than location because things + // like about:home and about:privatebrowsing arrive with nsIRequest + // pointing to their resolved jar: or file: URIs. + if (!(originalLocation && gInitialPages.includes(originalLocation.spec) && + originalLocation != "about:blank" && + this.mBrowser.initialPageLoadedFromURLBar != originalLocation.spec && + this.mBrowser.currentURI && this.mBrowser.currentURI.spec == "about:blank")) { + // Indicating that we started a load will allow the location + // bar to be cleared when the load finishes. + // In order to not overwrite user-typed content, we avoid it + // (see if condition above) in a very specific case: + // If the load is of an 'initial' page (e.g. about:privatebrowsing, + // about:newtab, etc.), was not explicitly typed in the location + // bar by the user, is not about:blank (because about:blank can be + // loaded by websites under their principal), and the current + // page in the browser is about:blank (indicating it is a newly + // created or re-created browser, e.g. because it just switched + // remoteness or is a new tab/window). + this.mBrowser.urlbarChangeTracker.startedLoad(); + } + delete this.mBrowser.initialPageLoadedFromURLBar; + // If the browser is loading it must not be crashed anymore + this.mTab.removeAttribute("crashed"); + } + + if (this._shouldShowProgress(aRequest)) { + if (!(aStateFlags & nsIWebProgressListener.STATE_RESTORING) && + aWebProgress && aWebProgress.isTopLevel) { + this.mTab.setAttribute("busy", "true"); + this.mTab._notselectedsinceload = !this.mTab.selected; + SchedulePressure.startMonitoring(window, { + highPressureFn() { + // Only switch back to the SVG loading indicator after getting + // three consecutive low pressure callbacks. Used to prevent + // switching quickly between the SVG and APNG loading indicators. + gBrowser.tabContainer._schedulePressureCount = gBrowser.schedulePressureDefaultCount; + gBrowser.tabContainer.setAttribute("schedulepressure", "true"); + }, + lowPressureFn() { + if (!gBrowser.tabContainer._schedulePressureCount || + --gBrowser.tabContainer._schedulePressureCount <= 0) { + gBrowser.tabContainer.removeAttribute("schedulepressure"); + } + + // If tabs are closed while they are loading we need to + // stop monitoring schedule pressure. We don't stop monitoring + // during high pressure times because we want to eventually + // return to the SVG tab loading animations. + let continueMonitoring = true; + if (!document.querySelector(".tabbrowser-tab[busy]")) { + SchedulePressure.stopMonitoring(window); + continueMonitoring = false; + } + return { continueMonitoring }; + }, + }); + gBrowser.syncThrobberAnimations(this.mTab); + } + + if (this.mTab.selected) { + gBrowser.mIsBusy = true; + } + } + } else if (aStateFlags & nsIWebProgressListener.STATE_STOP && + aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK) { + + if (this.mTab.hasAttribute("busy")) { + this.mTab.removeAttribute("busy"); + if (!document.querySelector(".tabbrowser-tab[busy]")) { + SchedulePressure.stopMonitoring(window); + gBrowser.tabContainer.removeAttribute("schedulepressure"); + } + + // Only animate the "burst" indicating the page has loaded if + // the top-level page is the one that finished loading. + if (aWebProgress.isTopLevel && !aWebProgress.isLoadingDocument && + Components.isSuccessCode(aStatus) && + !gBrowser.tabAnimationsInProgress && + Services.prefs.getBoolPref("toolkit.cosmeticAnimations.enabled")) { + if (this.mTab._notselectedsinceload) { + this.mTab.setAttribute("notselectedsinceload", "true"); + } else { + this.mTab.removeAttribute("notselectedsinceload"); + } + + this.mTab.setAttribute("bursting", "true"); + } + + gBrowser._tabAttrModified(this.mTab, ["busy"]); + if (!this.mTab.selected) + this.mTab.setAttribute("unread", "true"); + } + this.mTab.removeAttribute("progress"); + + if (aWebProgress.isTopLevel) { + let isSuccessful = Components.isSuccessCode(aStatus); + if (!isSuccessful && !isTabEmpty(this.mTab)) { + // Restore the current document's location in case the + // request was stopped (possibly from a content script) + // before the location changed. + + this.mBrowser.userTypedValue = null; + + let inLoadURI = this.mBrowser.inLoadURI; + if (this.mTab.selected && gURLBar && !inLoadURI) { + URLBarSetURI(); + } + } else if (isSuccessful) { + this.mBrowser.urlbarChangeTracker.finishedLoad(); + } + + // Ignore initial about:blank to prevent flickering. + if (!this.mBrowser.mIconURL && !ignoreBlank) { + // Don't switch to the default icon on about:home or about:newtab, + // since these pages get their favicon set in browser code to + // improve perceived performance. + let isNewTab = originalLocation && + (originalLocation.spec == "about:newtab" || + originalLocation.spec == "about:privatebrowsing" || + originalLocation.spec == "about:home"); + if (!isNewTab) { + gBrowser.useDefaultIcon(this.mTab); + } + } + } + + // For keyword URIs clear the user typed value since they will be changed into real URIs + if (location.scheme == "keyword") + this.mBrowser.userTypedValue = null; + + if (this.mTab.selected) + gBrowser.mIsBusy = false; + } + + if (ignoreBlank) { + this._callProgressListeners("onUpdateCurrentBrowser", + [aStateFlags, aStatus, "", 0], + true, false); + } else { + this._callProgressListeners("onStateChange", + [aWebProgress, aRequest, aStateFlags, aStatus], + true, false); + } + + this._callProgressListeners("onStateChange", + [aWebProgress, aRequest, aStateFlags, aStatus], + false); + + if (aStateFlags & (nsIWebProgressListener.STATE_START | + nsIWebProgressListener.STATE_STOP)) { + // reset cached temporary values at beginning and end + this.mMessage = ""; + this.mTotalProgress = 0; + } + this.mStateFlags = aStateFlags; + this.mStatus = aStatus; + } + /* eslint-enable complexity */ + + onLocationChange(aWebProgress, aRequest, aLocation, aFlags) { + // OnLocationChange is called for both the top-level content + // and the subframes. + let topLevel = aWebProgress.isTopLevel; + + if (topLevel) { + let isSameDocument = !!(aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT); + // We need to clear the typed value + // if the document failed to load, to make sure the urlbar reflects the + // failed URI (particularly for SSL errors). However, don't clear the value + // if the error page's URI is about:blank, because that causes complete + // loss of urlbar contents for invalid URI errors (see bug 867957). + // Another reason to clear the userTypedValue is if this was an anchor + // navigation initiated by the user. + if (this.mBrowser.didStartLoadSinceLastUserTyping() || + ((aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_ERROR_PAGE) && + aLocation.spec != "about:blank") || + (isSameDocument && this.mBrowser.inLoadURI)) { + this.mBrowser.userTypedValue = null; + } + + // If the tab has been set to "busy" outside the stateChange + // handler below (e.g. by sessionStore.navigateAndRestore), and + // the load results in an error page, it's possible that there + // isn't any (STATE_IS_NETWORK & STATE_STOP) state to cause busy + // attribute being removed. In this case we should remove the + // attribute here. + if ((aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_ERROR_PAGE) && + this.mTab.hasAttribute("busy")) { + this.mTab.removeAttribute("busy"); + gBrowser._tabAttrModified(this.mTab, ["busy"]); + } + + // If the browser was playing audio, we should remove the playing state. + if (this.mTab.hasAttribute("soundplaying") && !isSameDocument) { + clearTimeout(this.mTab._soundPlayingAttrRemovalTimer); + this.mTab._soundPlayingAttrRemovalTimer = 0; + this.mTab.removeAttribute("soundplaying"); + gBrowser._tabAttrModified(this.mTab, ["soundplaying"]); + } + + // If the browser was previously muted, we should restore the muted state. + if (this.mTab.hasAttribute("muted")) { + this.mTab.linkedBrowser.mute(); + } + + if (gBrowser.isFindBarInitialized(this.mTab)) { + let findBar = gBrowser.getFindBar(this.mTab); + + // Close the Find toolbar if we're in old-style TAF mode + if (findBar.findMode != findBar.FIND_NORMAL) { + findBar.close(); + } + } + + gBrowser.setTabTitle(this.mTab); + + // Don't clear the favicon if this tab is in the pending + // state, as SessionStore will have set the icon for us even + // though we're pointed at an about:blank. Also don't clear it + // if onLocationChange was triggered by a pushState or a + // replaceState (bug 550565) or a hash change (bug 408415). + if (!this.mTab.hasAttribute("pending") && + aWebProgress.isLoadingDocument && + !isSameDocument) { + this.mBrowser.mIconURL = null; + } + + let userContextId = this.mBrowser.getAttribute("usercontextid") || 0; + if (this.mBrowser.registeredOpenURI) { + gBrowser._unifiedComplete + .unregisterOpenPage(this.mBrowser.registeredOpenURI, userContextId); + delete this.mBrowser.registeredOpenURI; + } + // Tabs in private windows aren't registered as "Open" so + // that they don't appear as switch-to-tab candidates. + if (!isBlankPageURL(aLocation.spec) && + (!PrivateBrowsingUtils.isWindowPrivate(window) || + PrivateBrowsingUtils.permanentPrivateBrowsing)) { + gBrowser._unifiedComplete.registerOpenPage(aLocation, userContextId); + this.mBrowser.registeredOpenURI = aLocation; + } + } + + if (!this.mBlank) { + this._callProgressListeners("onLocationChange", + [aWebProgress, aRequest, aLocation, aFlags]); + } + + if (topLevel) { + this.mBrowser.lastURI = aLocation; + this.mBrowser.lastLocationChange = Date.now(); + } + } + + onStatusChange(aWebProgress, aRequest, aStatus, aMessage) { + if (this.mBlank) + return; + + this._callProgressListeners("onStatusChange", + [aWebProgress, aRequest, aStatus, aMessage]); + + this.mMessage = aMessage; + } + + onSecurityChange(aWebProgress, aRequest, aState) { + this._callProgressListeners("onSecurityChange", + [aWebProgress, aRequest, aState]); + } + + onRefreshAttempted(aWebProgress, aURI, aDelay, aSameURI) { + return this._callProgressListeners("onRefreshAttempted", + [aWebProgress, aURI, aDelay, aSameURI]); + } + + QueryInterface(aIID) { + if (aIID.equals(Ci.nsIWebProgressListener) || + aIID.equals(Ci.nsIWebProgressListener2) || + aIID.equals(Ci.nsISupportsWeakReference) || + aIID.equals(Ci.nsISupports)) + return this; + throw Cr.NS_NOINTERFACE; + } +} + diff --git a/browser/components/places/tests/browser/browser.ini b/browser/components/places/tests/browser/browser.ini index 4e4b89fee39f..6aea5fbe34f0 100644 --- a/browser/components/places/tests/browser/browser.ini +++ b/browser/components/places/tests/browser/browser.ini @@ -19,6 +19,8 @@ skip-if = (os == 'win' && ccov) # Bug 1423667 skip-if = (os == 'win' && ccov) # Bug 1423667 [browser_bookmark_folder_moveability.js] skip-if = (os == 'win' && ccov) # Bug 1423667 +[browser_bookmark_load_in_sidebar.js] +skip-if = (os == 'win' && ccov) # Bug 1423667 [browser_bookmark_private_window.js] skip-if = (os == 'win' && ccov) # Bug 1423667 [browser_bookmark_remove_tags.js] diff --git a/browser/components/places/tests/browser/browser_bookmark_load_in_sidebar.js b/browser/components/places/tests/browser/browser_bookmark_load_in_sidebar.js new file mode 100644 index 000000000000..3123f7596763 --- /dev/null +++ b/browser/components/places/tests/browser/browser_bookmark_load_in_sidebar.js @@ -0,0 +1,72 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +/** + * Test that a bookmark can be loaded inside the Bookmarks sidebar. + */ +"use strict"; + +const TEST_URL = "about:buildconfig"; + +// Cleanup. +registerCleanupFunction(async () => { + await PlacesUtils.bookmarks.eraseEverything(); +}); + +add_task(async function test_load_in_sidebar() { + let bm = await PlacesUtils.bookmarks.insert({ + parentGuid: PlacesUtils.bookmarks.unfiledGuid, + url: TEST_URL, + title: TEST_URL, + }); + + await withSidebarTree("bookmarks", async function(tree) { + tree.selectItems([bm.guid]); + await withBookmarksDialog( + false, + function openPropertiesDialog() { + tree.controller.doCommand("placesCmd_show:info"); + }, + + // Check the "Load this bookmark in the sidebar" option. + async function test(dialogWin) { + let loadInSidebar = dialogWin.document.getElementById("editBMPanel_loadInSidebarCheckbox"); + let promiseCheckboxChanged = PlacesTestUtils.waitForNotification( + "onItemChanged", + (id, parentId, checked) => checked === true + ); + + loadInSidebar.click(); + + EventUtils.synthesizeKey("VK_RETURN", {}, dialogWin); + await promiseCheckboxChanged; + } + ); + + let sidebar = document.getElementById("sidebar"); + + let sidebarLoadedPromise = new Promise(resolve => { + sidebar.addEventListener("load", function() { + executeSoon(resolve); + }, {capture: true, once: true}); + }); + + // Select and open the bookmark in the sidebar. + tree.selectItems([bm.guid]); + tree.controller.doCommand("placesCmd_open"); + + await sidebarLoadedPromise; + + let sidebarTitle = document.getElementById("sidebar-title"); + let sidebarBrowser = sidebar.contentDocument.getElementById("web-panels-browser"); + + await BrowserTestUtils.browserLoaded(sidebarBrowser, false, TEST_URL); + + let h1Elem = sidebarBrowser.contentDocument.getElementsByTagName("h1")[0]; + + // Check that the title and the content of the page are loaded successfully. + Assert.equal(sidebarTitle.value, TEST_URL, "The sidebar title is successfully loaded."); + Assert.equal(h1Elem.textContent, TEST_URL, "The sidebar content is successfully loaded."); + }); +}); diff --git a/browser/components/preferences/connection.js b/browser/components/preferences/connection.js index 1ed238c2a13c..ea51f0a054f8 100644 --- a/browser/components/preferences/connection.js +++ b/browser/components/preferences/connection.js @@ -261,6 +261,9 @@ var gConnectionsDialog = { for (let element of gConnectionsDialog.getProxyControls()) { element.disabled = disabled; } + if (!isControlled) { + gConnectionsDialog.proxyTypeChanged(); + } } if (isLocked) { diff --git a/browser/components/preferences/in-content/preferences.xul b/browser/components/preferences/in-content/preferences.xul index 6544e401538d..a28c973db63f 100644 --- a/browser/components/preferences/in-content/preferences.xul +++ b/browser/components/preferences/in-content/preferences.xul @@ -91,6 +91,7 @@ data-l10n-attrs="title"> + diff --git a/browser/components/preferences/in-content/tests/browser_connection.js b/browser/components/preferences/in-content/tests/browser_connection.js index d3269d8294e2..955350cb4c04 100644 --- a/browser/components/preferences/in-content/tests/browser_connection.js +++ b/browser/components/preferences/in-content/tests/browser_connection.js @@ -57,6 +57,10 @@ function runConnectionTests(win) { is(networkProxyNone.getAttribute("rows"), "2", "networkProxyNone textbox has two rows"); + // make sure manual proxy controls are disabled when the window is opened + let networkProxyHTTP = doc.getElementById("networkProxyHTTP"); + is(networkProxyHTTP.disabled, true, "networkProxyHTTP textbox is disabled"); + // check if sanitizing the given input for the no_proxies_on pref results in // expected string function testSanitize(input, expected, errorMessage) { diff --git a/browser/components/preferences/in-content/tests/browser_extension_controlled.js b/browser/components/preferences/in-content/tests/browser_extension_controlled.js index 4ceeae8ac5cc..d71739489100 100644 --- a/browser/components/preferences/in-content/tests/browser_extension_controlled.js +++ b/browser/components/preferences/in-content/tests/browser_extension_controlled.js @@ -673,17 +673,30 @@ add_task(async function testExtensionControlledProxyConfig() { } function getProxyControls() { let controlGroup = doc.getElementById("networkProxyType"); - return [ - ...controlGroup.querySelectorAll(":scope > radio"), - ...controlGroup.querySelectorAll("label"), - ...controlGroup.querySelectorAll("textbox"), - ...controlGroup.querySelectorAll("checkbox"), - ...doc.querySelectorAll("#networkProxySOCKSVersion > radio"), - ...doc.querySelectorAll("#ConnectionsDialogPane > checkbox"), - ]; + let manualControlContainer = controlGroup.querySelector("grid"); + return { + manualControls: [ + ...manualControlContainer.querySelectorAll("label"), + ...manualControlContainer.querySelectorAll("textbox"), + ...manualControlContainer.querySelectorAll("checkbox"), + ...doc.querySelectorAll("#networkProxySOCKSVersion > radio")], + pacControls: [doc.getElementById("networkProxyAutoconfigURL")], + otherControls: [ + ...controlGroup.querySelectorAll(":scope > radio"), + ...doc.querySelectorAll("#ConnectionsDialogPane > checkbox")], + }; } let controlState = isControlled ? "disabled" : "enabled"; - for (let element of getProxyControls()) { + let controls = getProxyControls(); + for (let element of controls.manualControls) { + let disabled = isControlled || proxyType !== proxySvc.PROXYCONFIG_MANUAL; + is(element.disabled, disabled, `Proxy controls are ${controlState}.`); + } + for (let element of controls.pacControls) { + let disabled = isControlled || proxyType !== proxySvc.PROXYCONFIG_PAC; + is(element.disabled, disabled, `Proxy controls are ${controlState}.`); + } + for (let element of controls.otherControls) { is(element.disabled, isControlled, `Proxy controls are ${controlState}.`); } } else { @@ -750,7 +763,7 @@ add_task(async function testExtensionControlledProxyConfig() { verifyState(mainDoc, false); - // Install an extension that sets Tracking Protection. + // Install an extension that controls proxy settings. let extension = ExtensionTestUtils.loadExtension({ useAddonManager: "permanent", manifest: { diff --git a/browser/extensions/pocket/content/pktApi.jsm b/browser/extensions/pocket/content/pktApi.jsm index 0a8472db3b19..77a1b51fe64a 100644 --- a/browser/extensions/pocket/content/pktApi.jsm +++ b/browser/extensions/pocket/content/pktApi.jsm @@ -622,7 +622,7 @@ var pktApi = (function() { function retrieve(data = {}, options = {}) { const requestData = Object.assign({}, data, {access_token: getAccessToken()}); return apiRequest({ - path: "/get", + path: "/firefox/get", data: requestData, success: options.success, error: options.error diff --git a/browser/locales/en-US/chrome/browser/browser.properties b/browser/locales/en-US/chrome/browser/browser.properties index 83f29821716d..e42c1326f207 100644 --- a/browser/locales/en-US/chrome/browser/browser.properties +++ b/browser/locales/en-US/chrome/browser/browser.properties @@ -101,6 +101,7 @@ webextPerms.description.browsingData=Clear recent browsing history, cookies, and webextPerms.description.clipboardRead=Get data from the clipboard webextPerms.description.clipboardWrite=Input data to the clipboard webextPerms.description.devtools=Extend developer tools to access your data in open tabs +webextPerms.description.dns=Access IP address and hostname information webextPerms.description.downloads=Download files and read and modify the browser’s download history webextPerms.description.downloads.open=Open files downloaded to your computer webextPerms.description.find=Read the text of all open tabs diff --git a/browser/themes/windows/places/organizer.css b/browser/themes/windows/places/organizer.css index ce2c36e7ffff..20fec5d608e3 100644 --- a/browser/themes/windows/places/organizer.css +++ b/browser/themes/windows/places/organizer.css @@ -161,6 +161,12 @@ margin-inline-start: -3px; position: relative; } + + @media (-moz-os-version: windows-win7) { + #detailsDeck { + border-top-color: #A9B7C9; + } + } } @media (-moz-windows-glass) { @@ -168,25 +174,3 @@ border-top: none; } } - -@media (-moz-windows-default-theme) and (-moz-os-version: windows-win7) { - #placesView, - #infoPane, - #placesList, - #placeContent { - background-color: #EEF3FA; - } - - #detailsDeck { - border-top-color: #A9B7C9; - } - - #searchFilter { - -moz-appearance: none; - padding: 2px; - padding-inline-start: 4px; - background-clip: padding-box; - border: 1px solid rgba(0,0,0,.32); - border-radius: 2px; - } -} diff --git a/build/moz.configure/rust.configure b/build/moz.configure/rust.configure index 9e9de3c38027..749085744738 100644 --- a/build/moz.configure/rust.configure +++ b/build/moz.configure/rust.configure @@ -67,7 +67,7 @@ def rust_compiler(rustc_info, cargo_info): You can install rust by running './mach bootstrap' or by directly running the installer from https://rustup.rs/ ''')) - rustc_min_version = Version('1.23.0') + rustc_min_version = Version('1.24.0') cargo_min_version = Version('0.{}'.format(rustc_min_version.minor + 1)) version = rustc_info.version diff --git a/devtools/client/inspector/fonts/components/Font.js b/devtools/client/inspector/fonts/components/Font.js index f891d13ece1e..561eb35608df 100644 --- a/devtools/client/inspector/fonts/components/Font.js +++ b/devtools/client/inspector/fonts/components/Font.js @@ -26,11 +26,9 @@ class Font extends PureComponent { super(props); this.state = { - isFontExpanded: false, isFontFaceRuleExpanded: false, }; - this.onFontToggle = this.onFontToggle.bind(this); this.onFontFaceRuleToggle = this.onFontFaceRuleToggle.bind(this); } @@ -40,18 +38,10 @@ class Font extends PureComponent { } this.setState({ - isFontExpanded: false, isFontFaceRuleExpanded: false, }); } - onFontToggle(event) { - this.setState({ - isFontExpanded: !this.state.isFontExpanded - }); - event.stopPropagation(); - } - onFontFaceRuleToggle(event) { this.setState({ isFontFaceRuleExpanded: !this.state.isFontFaceRuleExpanded @@ -59,15 +49,6 @@ class Font extends PureComponent { event.stopPropagation(); } - renderFontCSS(cssFamilyName) { - return dom.p( - { - className: "font-css-name" - }, - `${getStr("fontinspector.usedAs")} "${cssFamilyName}"` - ); - } - renderFontCSSCode(rule, ruleText) { if (!rule) { return null; @@ -89,14 +70,13 @@ class Font extends PureComponent { this.renderFontCSSCodeTwisty(), leading, isFontFaceRuleExpanded ? - null - : + body : dom.span( { - className: "font-css-code-expander" + className: "font-css-code-expander", + onClick: this.onFontFaceRuleToggle, } ), - isFontFaceRuleExpanded ? body : null, trailing ); } @@ -129,29 +109,20 @@ class Font extends PureComponent { renderFontName(name) { return dom.h1( { - className: "font-name", - onClick: this.onFontToggle, + className: "font-name" }, name ); } - renderFontTwisty() { - let { isFontExpanded } = this.state; - return this.renderTwisty(isFontExpanded, this.onFontToggle); - } - renderFontCSSCodeTwisty() { let { isFontFaceRuleExpanded } = this.state; - return this.renderTwisty(isFontFaceRuleExpanded, this.onFontFaceRuleToggle); - } - renderTwisty(isExpanded, onClick) { let attributes = { className: "theme-twisty", - onClick, + onClick: this.onFontFaceRuleToggle, }; - if (isExpanded) { + if (isFontFaceRuleExpanded) { attributes.open = "true"; } @@ -168,7 +139,6 @@ class Font extends PureComponent { let { previewText } = fontOptions; let { - CSSFamilyName, format, name, previewUrl, @@ -177,23 +147,14 @@ class Font extends PureComponent { URI, } = font; - let { isFontExpanded } = this.state; - return dom.li( { - className: "font" + (isFontExpanded ? " expanded" : ""), + className: "font", }, - this.renderFontTwisty(), this.renderFontName(name), FontPreview({ previewText, previewUrl, onPreviewFonts }), - dom.div( - { - className: "font-details" - }, - this.renderFontTypeAndURL(URI, format), - this.renderFontCSSCode(rule, ruleText), - this.renderFontCSS(CSSFamilyName) - ) + this.renderFontTypeAndURL(URI, format), + this.renderFontCSSCode(rule, ruleText) ); } } diff --git a/devtools/client/inspector/fonts/components/FontPreview.js b/devtools/client/inspector/fonts/components/FontPreview.js index d43b47cd0a5e..00ab5011c53d 100644 --- a/devtools/client/inspector/fonts/components/FontPreview.js +++ b/devtools/client/inspector/fonts/components/FontPreview.js @@ -9,6 +9,7 @@ const dom = require("devtools/client/shared/vendor/react-dom-factories"); const PropTypes = require("devtools/client/shared/vendor/react-prop-types"); const Types = require("../types"); +const { getStr } = require("../utils/l10n"); class FontPreview extends PureComponent { static get propTypes() { @@ -85,6 +86,7 @@ class FontPreview extends PureComponent { className: "font-preview", src: previewUrl, onClick: this.onClick, + title: !isFocused ? getStr("fontinspector.editPreview") : "", } ) ); diff --git a/devtools/client/inspector/fonts/test/browser.ini b/devtools/client/inspector/fonts/test/browser.ini index 6f71fd709fd2..7b3696397d3b 100644 --- a/devtools/client/inspector/fonts/test/browser.ini +++ b/devtools/client/inspector/fonts/test/browser.ini @@ -17,7 +17,6 @@ support-files = [browser_fontinspector.js] [browser_fontinspector_edit-previews.js] [browser_fontinspector_expand-css-code.js] -[browser_fontinspector_expand-details.js] [browser_fontinspector_other-fonts.js] [browser_fontinspector_text-node.js] [browser_fontinspector_theme-change.js] diff --git a/devtools/client/inspector/fonts/test/browser_fontinspector.js b/devtools/client/inspector/fonts/test/browser_fontinspector.js index fb9dd4dd31cf..639d5c3bfe2a 100644 --- a/devtools/client/inspector/fonts/test/browser_fontinspector.js +++ b/devtools/client/inspector/fonts/test/browser_fontinspector.js @@ -55,12 +55,6 @@ function getFormat(fontLi) { return link.textContent; } -function getCSSName(fontLi) { - let text = fontLi.querySelector(".font-css-name").textContent; - - return text.substring(text.indexOf('"') + 1, text.lastIndexOf('"')); -} - function* testBodyFonts(inspector, viewDoc) { let lis = getUsedFontsEls(viewDoc); is(lis.length, 5, "Found 5 fonts"); @@ -73,7 +67,6 @@ function* testBodyFonts(inspector, viewDoc) { is(isRemote(li), font.remote, "font " + i + " remote value correct"); is(li.querySelector(".font-url").href, font.url, "font " + i + " url correct"); is(getFormat(li), font.format, "font " + i + " format correct"); - is(getCSSName(li), font.cssName, "font " + i + " css name correct"); } // test that the bold and regular fonts have different previews @@ -83,15 +76,12 @@ function* testBodyFonts(inspector, viewDoc) { // test system font let localFontName = getName(lis[4]); - let localFontCSSName = getCSSName(lis[4]); // On Linux test machines, the Arial font doesn't exist. // The fallback is "Liberation Sans" ok((localFontName == "Arial") || (localFontName == "Liberation Sans"), "local font right font name"); ok(!isRemote(lis[4]), "local font is local"); - ok((localFontCSSName == "Arial") || (localFontCSSName == "Liberation Sans"), - "Arial", "local font has right css name"); } function* testDivFonts(inspector, viewDoc) { diff --git a/devtools/client/inspector/fonts/test/browser_fontinspector_expand-css-code.js b/devtools/client/inspector/fonts/test/browser_fontinspector_expand-css-code.js index 4685b792670c..4eb1a23ac271 100644 --- a/devtools/client/inspector/fonts/test/browser_fontinspector_expand-css-code.js +++ b/devtools/client/inspector/fonts/test/browser_fontinspector_expand-css-code.js @@ -12,11 +12,8 @@ add_task(function* () { let { view } = yield openFontInspectorForURL(TEST_URI); let viewDoc = view.document; - info("Expanding the details section of the first font"); - let fontEl = getUsedFontsEls(viewDoc)[0]; - yield expandFontDetails(fontEl); - info("Checking that the css font-face rule is collapsed by default"); + let fontEl = getUsedFontsEls(viewDoc)[0]; let codeEl = fontEl.querySelector(".font-css-code"); is(codeEl.textContent, "@font-face {}", "The font-face rule is collapsed"); @@ -33,4 +30,22 @@ add_task(function* () { yield onExpanded; ok(true, "Font-face rule is now expanded"); + + info("Expanding another rule by clicking on the [...] icon instead"); + fontEl = getUsedFontsEls(viewDoc)[1]; + codeEl = fontEl.querySelector(".font-css-code"); + + onExpanded = BrowserTestUtils.waitForCondition(() => { + return codeEl.textContent === `@font-face { + font-family: "bar"; + font-weight: bold; + src: url("ostrich-black.ttf"); +}`; + }, "Waiting for the font-face rule"); + + expander = fontEl.querySelector(".font-css-code .font-css-code-expander"); + expander.click(); + yield onExpanded; + + ok(true, "Font-face rule is now expanded too"); }); diff --git a/devtools/client/inspector/fonts/test/browser_fontinspector_expand-details.js b/devtools/client/inspector/fonts/test/browser_fontinspector_expand-details.js deleted file mode 100644 index 39bbc015fe6f..000000000000 --- a/devtools/client/inspector/fonts/test/browser_fontinspector_expand-details.js +++ /dev/null @@ -1,38 +0,0 @@ -/* vim: set 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 that fonts are collapsed by default, and can be expanded. - -const TEST_URI = URL_ROOT + "browser_fontinspector.html"; - -add_task(function* () { - let { inspector, view } = yield openFontInspectorForURL(TEST_URI); - let viewDoc = view.document; - - info("Checking all font are collapsed by default"); - let fonts = getUsedFontsEls(viewDoc); - checkAllFontsCollapsed(fonts); - - info("Clicking on the first one to expand the font details"); - yield expandFontDetails(fonts[0]); - - ok(fonts[0].querySelector(".theme-twisty").hasAttribute("open"), `Twisty is open`); - ok(isFontDetailsVisible(fonts[0]), `Font details is shown`); - - info("Selecting a node with different fonts and checking that all fonts are collapsed"); - yield selectNode(".black-text", inspector); - fonts = getUsedFontsEls(viewDoc); - checkAllFontsCollapsed(fonts); -}); - -function checkAllFontsCollapsed(fonts) { - fonts.forEach((el, i) => { - let twisty = el.querySelector(".theme-twisty"); - ok(twisty, `Twisty ${i} exists`); - ok(!twisty.hasAttribute("open"), `Twisty ${i} is closed`); - ok(!isFontDetailsVisible(el), `Font details ${i} is hidden`); - }); -} diff --git a/devtools/client/inspector/fonts/test/head.js b/devtools/client/inspector/fonts/test/head.js index c2096fb84f36..974f8c737444 100644 --- a/devtools/client/inspector/fonts/test/head.js +++ b/devtools/client/inspector/fonts/test/head.js @@ -88,21 +88,6 @@ function* updatePreviewText(view, text) { is(input.value, text, "The input now contains the correct text."); } -async function expandFontDetails(fontEl) { - info("Expanding a font details section"); - - let onExpanded = BrowserTestUtils.waitForCondition(() => isFontDetailsVisible(fontEl), - "Waiting for font details"); - let twisty = fontEl.querySelector(".theme-twisty"); - twisty.click(); - await onExpanded; -} - -function isFontDetailsVisible(fontEl) { - return [...fontEl.querySelectorAll(".font-css-name, .font-css-code, .font-format-url")] - .every(el => el.getBoxQuads().length); -} - /** * Get all of the
  • elements for the fonts used on the currently selected element. * diff --git a/devtools/client/inspector/fonts/types.js b/devtools/client/inspector/fonts/types.js index a042cf164071..aff35b494a63 100644 --- a/devtools/client/inspector/fonts/types.js +++ b/devtools/client/inspector/fonts/types.js @@ -10,9 +10,6 @@ const PropTypes = require("devtools/client/shared/vendor/react-prop-types"); * A single font. */ const font = exports.font = { - // The name of the font family - CSSFamilyName: PropTypes.string, - // The format of the font format: PropTypes.string, diff --git a/devtools/client/inspector/inspector.js b/devtools/client/inspector/inspector.js index 80587ea6c2d6..a7b5e3d622bd 100644 --- a/devtools/client/inspector/inspector.js +++ b/devtools/client/inspector/inspector.js @@ -708,10 +708,14 @@ Inspector.prototype = { await this.addRuleView(defaultTab); - this.sidebar.addExistingTab( - "computedview", - INSPECTOR_L10N.getStr("inspector.sidebar.computedViewTitle"), - defaultTab == "computedview"); + // If the 3 Pane Inspector feature is disabled, use the old order: + // Rules, Computed, Layout, etc. + if (!this.showSplitSidebarToggle) { + this.sidebar.addExistingTab( + "computedview", + INSPECTOR_L10N.getStr("inspector.sidebar.computedViewTitle"), + defaultTab == "computedview"); + } // Inject a lazy loaded react tab by exposing a fake React object // with a lazy defined Tab thanks to `panel` being a function @@ -737,6 +741,15 @@ Inspector.prototype = { }, defaultTab == layoutId); + // If the 3 Pane Inspector feature is enabled, use the new order: + // Rules, Layout, Computed, etc. + if (this.showSplitSidebarToggle) { + this.sidebar.addExistingTab( + "computedview", + INSPECTOR_L10N.getStr("inspector.sidebar.computedViewTitle"), + defaultTab == "computedview"); + } + if (Services.prefs.getBoolPref("devtools.changesview.enabled")) { // Inject a lazy loaded react tab by exposing a fake React object // with a lazy defined Tab thanks to `panel` being a function diff --git a/devtools/client/locales/en-US/font-inspector.properties b/devtools/client/locales/en-US/font-inspector.properties index 40e6e933104e..46fe183dbb75 100644 --- a/devtools/client/locales/en-US/font-inspector.properties +++ b/devtools/client/locales/en-US/font-inspector.properties @@ -5,10 +5,6 @@ # LOCALIZATION NOTE This file contains the Font Inspector strings. # The Font Inspector is a panel accessible in the Inspector sidebar. -# LOCALIZATION NOTE (fontinspector.usedAs) This label introduces the name used to refer to -# the font in a stylesheet. -fontinspector.usedAs=Used as: - # LOCALIZATION NOTE (fontinspector.system) This label indicates that the font is a local # system font. fontinspector.system=system @@ -23,4 +19,9 @@ fontinspector.noFontsOnSelectedElement=No fonts were found for the current eleme # LOCALIZATION NOTE (fontinspector.otherFontsInPageHeader): This is the text for the # header of a collapsible section containing other fonts used in the page. -fontinspector.otherFontsInPageHeader=Other fonts in page \ No newline at end of file +fontinspector.otherFontsInPageHeader=Other fonts in page + +# LOCALIZATION NOTE (fontinspector.editPreview): This is the text that appears in a +# tooltip on hover of a font preview string. Clicking on the string opens a text input +# where users can type to change the preview text. +fontinspector.editPreview=Click to edit preview diff --git a/devtools/client/netmonitor/initializer.js b/devtools/client/netmonitor/initializer.js index e4edc958de51..c6b4d02815d8 100644 --- a/devtools/client/netmonitor/initializer.js +++ b/devtools/client/netmonitor/initializer.js @@ -22,7 +22,7 @@ const { render, unmountComponentAtNode } = require("devtools/client/shared/vendo const Provider = createFactory(require("devtools/client/shared/vendor/react-redux").Provider); const { bindActionCreators } = require("devtools/client/shared/vendor/redux"); const { Connector } = require("./src/connector/index"); -const { configureStore } = require("./src/utils/create-store"); +const { configureStore } = require("./src/create-store"); const App = createFactory(require("./src/components/App")); const { EVENTS } = require("./src/constants"); const { diff --git a/devtools/client/netmonitor/launchpad.js b/devtools/client/netmonitor/launchpad.js index 4ebe469b0ed1..4756767a484e 100644 --- a/devtools/client/netmonitor/launchpad.js +++ b/devtools/client/netmonitor/launchpad.js @@ -43,7 +43,7 @@ require("./src/assets/styles/netmonitor.css"); const EventEmitter = require("devtools-modules/src/utils/event-emitter"); EventEmitter.decorate(window); -const { configureStore } = require("./src/utils/create-store"); +const { configureStore } = require("./src/create-store"); const App = require("./src/components/App"); const { Connector } = require("./src/connector/index"); const connector = new Connector(); diff --git a/devtools/client/netmonitor/src/components/App.js b/devtools/client/netmonitor/src/components/App.js index 9616aaa6a2bb..6a97ac590819 100644 --- a/devtools/client/netmonitor/src/components/App.js +++ b/devtools/client/netmonitor/src/components/App.js @@ -7,7 +7,7 @@ const { Component, createFactory } = require("devtools/client/shared/vendor/react"); const PropTypes = require("devtools/client/shared/vendor/react-prop-types"); const dom = require("devtools/client/shared/vendor/react-dom-factories"); -const { connect } = require("devtools/client/shared/vendor/react-redux"); +const { connect } = require("../utils/redux-connect"); // Components loader.lazyGetter(this, "MonitorPanel", function () { diff --git a/devtools/client/netmonitor/src/components/CustomRequestPanel.js b/devtools/client/netmonitor/src/components/CustomRequestPanel.js index 4f88a1edc089..d111d1fc08c4 100644 --- a/devtools/client/netmonitor/src/components/CustomRequestPanel.js +++ b/devtools/client/netmonitor/src/components/CustomRequestPanel.js @@ -7,7 +7,7 @@ const { Component } = require("devtools/client/shared/vendor/react"); const PropTypes = require("devtools/client/shared/vendor/react-prop-types"); const dom = require("devtools/client/shared/vendor/react-dom-factories"); -const { connect } = require("devtools/client/shared/vendor/react-redux"); +const { connect } = require("../utils/redux-connect"); const { L10N } = require("../utils/l10n"); const { fetchNetworkUpdatePacket } = require("../utils/request-utils"); const Actions = require("../actions/index"); diff --git a/devtools/client/netmonitor/src/components/MonitorPanel.js b/devtools/client/netmonitor/src/components/MonitorPanel.js index 40728620c0d9..c314acd9d1a1 100644 --- a/devtools/client/netmonitor/src/components/MonitorPanel.js +++ b/devtools/client/netmonitor/src/components/MonitorPanel.js @@ -9,7 +9,7 @@ const { Component, createFactory } = require("devtools/client/shared/vendor/reac const dom = require("devtools/client/shared/vendor/react-dom-factories"); const { div } = dom; const PropTypes = require("devtools/client/shared/vendor/react-prop-types"); -const { connect } = require("devtools/client/shared/vendor/react-redux"); +const { connect } = require("../utils/redux-connect"); const { findDOMNode } = require("devtools/client/shared/vendor/react-dom"); const Actions = require("../actions/index"); const { updateFormDataSections } = require("../utils/request-utils"); diff --git a/devtools/client/netmonitor/src/components/NetworkDetailsPanel.js b/devtools/client/netmonitor/src/components/NetworkDetailsPanel.js index 979f65a3279c..f1eb57c4c453 100644 --- a/devtools/client/netmonitor/src/components/NetworkDetailsPanel.js +++ b/devtools/client/netmonitor/src/components/NetworkDetailsPanel.js @@ -7,7 +7,7 @@ const { createFactory } = require("devtools/client/shared/vendor/react"); const PropTypes = require("devtools/client/shared/vendor/react-prop-types"); const dom = require("devtools/client/shared/vendor/react-dom-factories"); -const { connect } = require("devtools/client/shared/vendor/react-redux"); +const { connect } = require("../utils/redux-connect"); const Actions = require("../actions/index"); const { getSelectedRequest } = require("../selectors/index"); diff --git a/devtools/client/netmonitor/src/components/ParamsPanel.js b/devtools/client/netmonitor/src/components/ParamsPanel.js index b822d7c8cd50..f93fe28c4fc2 100644 --- a/devtools/client/netmonitor/src/components/ParamsPanel.js +++ b/devtools/client/netmonitor/src/components/ParamsPanel.js @@ -7,7 +7,7 @@ const { Component, createFactory } = require("devtools/client/shared/vendor/react"); const dom = require("devtools/client/shared/vendor/react-dom-factories"); const PropTypes = require("devtools/client/shared/vendor/react-prop-types"); -const { connect } = require("devtools/client/shared/vendor/react-redux"); +const { connect } = require("../utils/redux-connect"); const { L10N } = require("../utils/l10n"); const { fetchNetworkUpdatePacket, diff --git a/devtools/client/netmonitor/src/components/RequestListContent.js b/devtools/client/netmonitor/src/components/RequestListContent.js index 7408262bc806..12d8c983bf7e 100644 --- a/devtools/client/netmonitor/src/components/RequestListContent.js +++ b/devtools/client/netmonitor/src/components/RequestListContent.js @@ -7,7 +7,7 @@ const { Component, createFactory } = require("devtools/client/shared/vendor/react"); const dom = require("devtools/client/shared/vendor/react-dom-factories"); const PropTypes = require("devtools/client/shared/vendor/react-prop-types"); -const { connect } = require("devtools/client/shared/vendor/react-redux"); +const { connect } = require("../utils/redux-connect"); const { HTMLTooltip } = require("devtools/client/shared/widgets/tooltip/HTMLTooltip"); const Actions = require("../actions/index"); diff --git a/devtools/client/netmonitor/src/components/RequestListEmptyNotice.js b/devtools/client/netmonitor/src/components/RequestListEmptyNotice.js index c43ba80f8e4c..fbe0c198345a 100644 --- a/devtools/client/netmonitor/src/components/RequestListEmptyNotice.js +++ b/devtools/client/netmonitor/src/components/RequestListEmptyNotice.js @@ -7,7 +7,7 @@ const { Component, createFactory } = require("devtools/client/shared/vendor/react"); const dom = require("devtools/client/shared/vendor/react-dom-factories"); const PropTypes = require("devtools/client/shared/vendor/react-prop-types"); -const { connect } = require("devtools/client/shared/vendor/react-redux"); +const { connect } = require("../utils/redux-connect"); const Actions = require("../actions/index"); const { ACTIVITY_TYPE } = require("../constants"); const { L10N } = require("../utils/l10n"); diff --git a/devtools/client/netmonitor/src/components/RequestListHeader.js b/devtools/client/netmonitor/src/components/RequestListHeader.js index 0aea8c84c290..3f970d357b36 100644 --- a/devtools/client/netmonitor/src/components/RequestListHeader.js +++ b/devtools/client/netmonitor/src/components/RequestListHeader.js @@ -7,7 +7,7 @@ const { Component } = require("devtools/client/shared/vendor/react"); const dom = require("devtools/client/shared/vendor/react-dom-factories"); const PropTypes = require("devtools/client/shared/vendor/react-prop-types"); -const { connect } = require("devtools/client/shared/vendor/react-redux"); +const { connect } = require("../utils/redux-connect"); const { getTheme, addThemeObserver, removeThemeObserver } = require("devtools/client/shared/theme"); const Actions = require("../actions/index"); diff --git a/devtools/client/netmonitor/src/components/StatisticsPanel.js b/devtools/client/netmonitor/src/components/StatisticsPanel.js index 822282ff1668..b4134c602f6b 100644 --- a/devtools/client/netmonitor/src/components/StatisticsPanel.js +++ b/devtools/client/netmonitor/src/components/StatisticsPanel.js @@ -9,7 +9,7 @@ const { FILTER_TAGS } = require("../constants"); const { Component, createFactory } = require("devtools/client/shared/vendor/react"); const dom = require("devtools/client/shared/vendor/react-dom-factories"); const PropTypes = require("devtools/client/shared/vendor/react-prop-types"); -const { connect } = require("devtools/client/shared/vendor/react-redux"); +const { connect } = require("../utils/redux-connect"); const { Chart } = require("devtools/client/shared/widgets/Chart"); const { PluralForm } = require("devtools/shared/plural-form"); const Actions = require("../actions/index"); diff --git a/devtools/client/netmonitor/src/components/StatusBar.js b/devtools/client/netmonitor/src/components/StatusBar.js index 01e38ca4db17..4e9b703fb5e2 100644 --- a/devtools/client/netmonitor/src/components/StatusBar.js +++ b/devtools/client/netmonitor/src/components/StatusBar.js @@ -6,7 +6,7 @@ const PropTypes = require("devtools/client/shared/vendor/react-prop-types"); const dom = require("devtools/client/shared/vendor/react-dom-factories"); -const { connect } = require("devtools/client/shared/vendor/react-redux"); +const { connect } = require("../utils/redux-connect"); const { PluralForm } = require("devtools/shared/plural-form"); const Actions = require("../actions/index"); const { diff --git a/devtools/client/netmonitor/src/components/Toolbar.js b/devtools/client/netmonitor/src/components/Toolbar.js index f150fc834a5c..f8d5938daa02 100644 --- a/devtools/client/netmonitor/src/components/Toolbar.js +++ b/devtools/client/netmonitor/src/components/Toolbar.js @@ -8,7 +8,7 @@ const Services = require("Services"); const { Component, createFactory } = require("devtools/client/shared/vendor/react"); const dom = require("devtools/client/shared/vendor/react-dom-factories"); const PropTypes = require("devtools/client/shared/vendor/react-prop-types"); -const { connect } = require("devtools/client/shared/vendor/react-redux"); +const { connect } = require("../utils/redux-connect"); const Actions = require("../actions/index"); const { FILTER_SEARCH_DELAY, FILTER_TAGS } = require("../constants"); diff --git a/devtools/client/netmonitor/src/utils/create-store.js b/devtools/client/netmonitor/src/create-store.js similarity index 76% rename from devtools/client/netmonitor/src/utils/create-store.js rename to devtools/client/netmonitor/src/create-store.js index af1941da1141..b30a6af8d7a7 100644 --- a/devtools/client/netmonitor/src/utils/create-store.js +++ b/devtools/client/netmonitor/src/create-store.js @@ -8,18 +8,18 @@ const Services = require("Services"); const { applyMiddleware, createStore } = require("devtools/client/shared/vendor/redux"); // Middleware -const batching = require("../middleware/batching"); -const prefs = require("../middleware/prefs"); -const thunk = require("../middleware/thunk"); -const recording = require("../middleware/recording"); +const batching = require("./middleware/batching"); +const prefs = require("./middleware/prefs"); +const thunk = require("./middleware/thunk"); +const recording = require("./middleware/recording"); // Reducers -const rootReducer = require("../reducers/index"); -const { FilterTypes, Filters } = require("../reducers/filters"); -const { Requests } = require("../reducers/requests"); -const { Sort } = require("../reducers/sort"); -const { TimingMarkers } = require("../reducers/timing-markers"); -const { UI, Columns } = require("../reducers/ui"); +const rootReducer = require("./reducers/index"); +const { FilterTypes, Filters } = require("./reducers/filters"); +const { Requests } = require("./reducers/requests"); +const { Sort } = require("./reducers/sort"); +const { TimingMarkers } = require("./reducers/timing-markers"); +const { UI, Columns } = require("./reducers/ui"); /** * Configure state and middleware for the Network monitor tool. diff --git a/devtools/client/netmonitor/src/moz.build b/devtools/client/netmonitor/src/moz.build index feff904c1e05..ddaa7a426c44 100644 --- a/devtools/client/netmonitor/src/moz.build +++ b/devtools/client/netmonitor/src/moz.build @@ -16,4 +16,5 @@ DIRS += [ DevToolsModules( 'constants.js', + 'create-store.js', ) diff --git a/devtools/client/netmonitor/src/utils/moz.build b/devtools/client/netmonitor/src/utils/moz.build index 55c260b43653..d0e412cf9eba 100644 --- a/devtools/client/netmonitor/src/utils/moz.build +++ b/devtools/client/netmonitor/src/utils/moz.build @@ -8,7 +8,6 @@ DIRS += [ ] DevToolsModules( - 'create-store.js', 'filter-autocomplete-provider.js', 'filter-predicates.js', 'filter-text-utils.js', @@ -19,6 +18,7 @@ DevToolsModules( 'menu.js', 'open-request-in-tab.js', 'prefs.js', + 'redux-connect.js', 'request-utils.js', 'sort-predicates.js', 'sort-utils.js' diff --git a/devtools/client/netmonitor/src/utils/redux-connect.js b/devtools/client/netmonitor/src/utils/redux-connect.js new file mode 100644 index 000000000000..0a1bf15005a0 --- /dev/null +++ b/devtools/client/netmonitor/src/utils/redux-connect.js @@ -0,0 +1,28 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const { createFactory, createElement } = require("devtools/client/shared/vendor/react"); +const VisibilityHandler = createFactory(require("devtools/client/shared/components/VisibilityHandler")); +const { connect } = require("devtools/client/shared/vendor/react-redux"); + +/** + * This helper is wrapping Redux's connect() method and applying + * HOC (VisibilityHandler component) on whatever component is + * originally passed in. The HOC is responsible for not causing + * rendering if the owner panel runs in the background. + */ +function connectWrapper() { + let args = [].slice.call(arguments); + return component => { + return connect(...args)(props => { + return VisibilityHandler(null, createElement(component, props)); + }); + }; +} + +module.exports = { + connect: connectWrapper +}; diff --git a/devtools/client/netmonitor/test/browser.ini b/devtools/client/netmonitor/test/browser.ini index 14ecefef3df3..928b167a9c56 100644 --- a/devtools/client/netmonitor/test/browser.ini +++ b/devtools/client/netmonitor/test/browser.ini @@ -60,6 +60,7 @@ support-files = [browser_net_accessibility-01.js] [browser_net_accessibility-02.js] [browser_net_api-calls.js] +[browser_net_background_update.js] [browser_net_autoscroll.js] [browser_net_cached-status.js] [browser_net_cause.js] diff --git a/devtools/client/netmonitor/test/browser_net_background_update.js b/devtools/client/netmonitor/test/browser_net_background_update.js new file mode 100644 index 000000000000..e143627d4682 --- /dev/null +++ b/devtools/client/netmonitor/test/browser_net_background_update.js @@ -0,0 +1,56 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * Check that network logs created when the Net panel is not visible + * are displayed when the user shows the panel again. + */ +add_task(async () => { + let { tab, monitor, toolbox } = await initNetMonitor(CUSTOM_GET_URL); + info("Starting test... "); + + let { document, store, windowRequire } = monitor.panelWin; + let Actions = windowRequire("devtools/client/netmonitor/src/actions/index"); + + store.dispatch(Actions.batchEnable(false)); + + // Execute two requests + await performRequests(monitor, tab, 2); + + // Wait for two logs + await waitUntil(() => document.querySelectorAll(".request-list-item").length == 2); + + info("Select the inspector"); + await toolbox.selectTool("inspector"); + + info("Wait for Net panel to be hidden"); + await waitUntil(() => (document.visibilityState == "hidden")); + + // Execute another two requests + await performRequests(monitor, tab, 2); + + // The number of rendered requests should be the same since + // requests shouldn't be rendered while the net panel is in + // background + is(document.querySelectorAll(".request-list-item").length, 2, + "There should be expected number of requests"); + + info("Select the Net panel again"); + await toolbox.selectTool("netmonitor"); + + // Wait for another two logs to be rendered since the panel + // is selected now. + await waitUntil(() => document.querySelectorAll(".request-list-item").length == 4); + + return teardown(monitor); +}); + +async function performRequests(monitor, tab, count) { + let wait = waitForNetworkEvents(monitor, count); + await ContentTask.spawn(tab.linkedBrowser, count, requestCount => { + content.wrappedJSObject.performRequests(requestCount); + }); + await wait; +} diff --git a/devtools/client/shared/browser-loader.js b/devtools/client/shared/browser-loader.js index c1e9e4fc74ef..1043e593717a 100644 --- a/devtools/client/shared/browser-loader.js +++ b/devtools/client/shared/browser-loader.js @@ -19,6 +19,7 @@ const BROWSER_BASED_DIRS = [ "resource://devtools/client/inspector/grids", "resource://devtools/client/inspector/layout", "resource://devtools/client/jsonview", + "resource://devtools/client/netmonitor/src/utils", "resource://devtools/client/shared/source-map", "resource://devtools/client/shared/redux", "resource://devtools/client/shared/vendor", diff --git a/devtools/client/shared/components/SidebarToggle.js b/devtools/client/shared/components/SidebarToggle.js index b9c27c1d114f..30c485cb2932 100644 --- a/devtools/client/shared/components/SidebarToggle.js +++ b/devtools/client/shared/components/SidebarToggle.js @@ -44,6 +44,7 @@ class SidebarToggle extends Component { // Events onClick(event) { + event.stopPropagation(); this.props.onClick(event); } diff --git a/devtools/client/sourceeditor/codemirror/old-debugger.css b/devtools/client/sourceeditor/codemirror/old-debugger.css index d17976e427e1..0bbc44e62d2b 100644 --- a/devtools/client/sourceeditor/codemirror/old-debugger.css +++ b/devtools/client/sourceeditor/codemirror/old-debugger.css @@ -1,12 +1,10 @@ :root { --breakpoint-background: url("chrome://devtools/skin/images/breakpoint.svg#light"); - --breakpoint-hover-background: url("chrome://devtools/skin/images/breakpoint.svg#light-hover"); --breakpoint-conditional-background: url("chrome://devtools/skin/images/breakpoint.svg#light-conditional"); } .theme-dark:root { --breakpoint-background: url("chrome://devtools/skin/images/breakpoint.svg#dark"); - --breakpoint-hover-background: url("chrome://devtools/skin/images/breakpoint.svg#dark-hover"); --breakpoint-conditional-background: url("chrome://devtools/skin/images/breakpoint.svg#dark-conditional"); } diff --git a/devtools/client/themes/fonts.css b/devtools/client/themes/fonts.css index d2c9c9071d87..9ad63c56f49e 100644 --- a/devtools/client/themes/fonts.css +++ b/devtools/client/themes/fonts.css @@ -25,22 +25,19 @@ border: 1px solid var(--theme-splitter-color); border-width: 0 1px 1px 0; display: grid; - grid-template-columns: 14px auto 1fr; - grid-template-rows: 50px; - grid-column-gap: 10px; - padding: 0 10px 0 5px; + grid-template-columns: 1fr auto; + padding: 10px 20px; } #font-container .theme-twisty { display: inline-block; cursor: pointer; - place-self: center; - vertical-align: text-top; + vertical-align: bottom; } .font-preview-container { - grid-column: 3 / -1; - grid-row: 1; + grid-column: 2; + grid-row: 1 / span 2; overflow: hidden; display: grid; place-items: center end; @@ -61,14 +58,16 @@ background-position-y: 45px; } -.font-preview-input { +#font-container .font-preview-input { position: absolute; - top: 0; + top: 5px; left: 0; width: calc(100% - 5px); - height: calc(100% - 2px); + height: calc(100% - 10px); background: transparent; color: transparent; + border-radius: 0; + padding: 0; } .font-preview-input::-moz-selection { @@ -78,27 +77,20 @@ .font-name { margin: 0; - font-size: 1em; + font-size: 1.2em; + font-weight: normal; white-space: nowrap; - grid-column: 2; - place-self: center start; -} - -.font-details { - grid-column: 2 / 4; - padding-inline-end: 14px; - width: 100%; } .font-css-code { direction: ltr; - padding: 5px; margin: 0; - border: 1px solid var(--theme-splitter-color); - border-radius: 3px; overflow: hidden; text-overflow: ellipsis; color: var(--theme-toolbar-color); + grid-column: span 2; + position: relative; + offset-inline-start: -4px; } .font-css-code-expander::before { @@ -118,12 +110,12 @@ .font-format-url { text-transform: capitalize; - margin-block-start: 0; + margin-top: .2em; + color: var(--grey-50); } .font-url { - margin-inline-start: 1em; - text-transform: uppercase; + margin-inline-start: .5em; text-decoration: underline; color: var(--theme-highlight-blue); background: transparent; @@ -145,9 +137,6 @@ fill: var(--blue-60); } - -.font:not(.expanded) .font-css-name, -.font:not(.expanded) .font-css-code, -.font:not(.expanded) .font-format-url { - display: none; +#font-container .devtools-sidepanel-no-result + .accordion { + border-block-start: 1px solid var(--theme-splitter-color); } diff --git a/devtools/client/themes/webconsole.css b/devtools/client/themes/webconsole.css index 13b55323d57b..6e5d208ff0ed 100644 --- a/devtools/client/themes/webconsole.css +++ b/devtools/client/themes/webconsole.css @@ -761,7 +761,6 @@ a.learn-more-link.webconsole-learn-more-link { height: 100%; -moz-user-focus: normal; color: var(--console-output-color); - --console-output-indent-width: 1rem; --console-output-indent-border-color: var(--theme-selection-background); --icon-top-margin: 3px; --object-inspector-hover-background: transparent; @@ -937,7 +936,7 @@ a.learn-more-link.webconsole-learn-more-link { } .theme-dark .webconsole-output-wrapper .message.error .tree.object-inspector, .theme-dark .webconsole-output-wrapper .message.warn .tree.object-inspector { - --tree-indent-border-color: var(--theme-body-color); + --console-output-indent-border-color: var(--theme-body-color); } .webconsole-output-wrapper .message-flex-body > .message-body { diff --git a/devtools/client/webconsole/new-console-output/test/mochitest/browser.ini b/devtools/client/webconsole/new-console-output/test/mochitest/browser.ini index 48d72b83dd73..f82dc81c3223 100644 --- a/devtools/client/webconsole/new-console-output/test/mochitest/browser.ini +++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser.ini @@ -176,7 +176,6 @@ skip-if = true # Bug 1437844 [browser_console_dead_objects.js] skip-if = true # Bug 1437845 [browser_console_error_source_click.js] -skip-if = true # Bug 1437847 [browser_console_filters.js] [browser_console_nsiconsolemessage.js] [browser_console_open_or_focus.js] diff --git a/devtools/client/webconsole/new-console-output/test/mochitest/browser_console_error_source_click.js b/devtools/client/webconsole/new-console-output/test/mochitest/browser_console_error_source_click.js index 5839f20d515a..3079ca6581c3 100644 --- a/devtools/client/webconsole/new-console-output/test/mochitest/browser_console_error_source_click.js +++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_console_error_source_click.js @@ -3,26 +3,27 @@ /* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ +/* import-globals-from head.js */ + // Check that JS errors and CSS warnings open view source when their source link -// is clicked in the Browser Console. See bug 877778. +// is clicked in the Browser Console. "use strict"; -const TEST_URI = "data:text/html;charset=utf8,

    hello world from bug 877778 " + +const TEST_URI = "data:text/html;charset=utf8,

    hello world" + ""; -add_task(function* () { - yield new Promise(resolve => { - SpecialPowers.pushPrefEnv({"set": [ - ["devtools.browserconsole.filter.cssparser", true] - ]}, resolve); - }); - - yield loadTab(TEST_URI); - let hud = yield HUDService.toggleBrowserConsole(); +add_task(async function () { + await addTab(TEST_URI); + let hud = await HUDService.toggleBrowserConsole(); ok(hud, "browser console opened"); + // Enable CSS warnings and errors. + await setFilterState(hud, { + css: true + }); + // On e10s, the exception is triggered in child process // and is ignored by test harness if (!Services.appinfo.browserTabsRemoteAutostart) { @@ -30,50 +31,28 @@ add_task(function* () { } info("generate exception and wait for the message"); - ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () { + ContentTask.spawn(gBrowser.selectedBrowser, {}, () => { let button = content.document.querySelector("button"); button.click(); }); - let results = yield waitForMessages({ - webconsole: hud, - messages: [ - { - text: "ReferenceError: foobar is not defined", - category: CATEGORY_JS, - severity: SEVERITY_ERROR, - }, - { - text: "Unknown property \u2018test-color\u2019", - category: CATEGORY_CSS, - severity: SEVERITY_WARNING, - }, - ], - }); - - let viewSourceCalled = false; - - let viewSource = hud.viewSource; - hud.viewSource = () => { - viewSourceCalled = true; - }; - - for (let result of results) { - viewSourceCalled = false; - - let msg = [...result.matched][0]; - ok(msg, "message element found for: " + result.text); - ok(!msg.classList.contains("filtered-by-type"), "message element is not filtered"); - let selector = ".message .message-location .frame-link-source"; - let locationNode = msg.querySelector(selector); - ok(locationNode, "message location element found"); - - locationNode.click(); - - ok(viewSourceCalled, "view source opened"); - } - - hud.viewSource = viewSource; - - yield finishTest(); + await waitForMessageAndViewSource(hud, + "ReferenceError: foobar is not defined"); + await waitForMessageAndViewSource(hud, + "Unknown property \u2018test-color\u2019."); + await resetFilters(hud); }); + +async function waitForMessageAndViewSource(hud, message) { + let msg = await waitFor(() => findMessage(hud, message)); + ok(msg, `Message found: "${message}"`); + + let locationNode = msg.querySelector(".message-location .frame-link-source"); + ok(locationNode, "Message location link element found"); + + let onTabOpen = BrowserTestUtils.waitForNewTab(gBrowser, null, true); + locationNode.click(); + let newTab = await onTabOpen; + ok(true, "The view source tab was opened in response to clicking the link"); + await BrowserTestUtils.removeTab(newTab); +} diff --git a/dom/base/ChildIterator.cpp b/dom/base/ChildIterator.cpp index 1929a05d1b3e..058e4b4aa450 100644 --- a/dom/base/ChildIterator.cpp +++ b/dom/base/ChildIterator.cpp @@ -124,6 +124,7 @@ void FlattenedChildIterator::Init(bool aIgnoreXBL) { if (aIgnoreXBL) { + mXBLInvolved = Some(false); return; } @@ -132,7 +133,7 @@ FlattenedChildIterator::Init(bool aIgnoreXBL) if (mParent->IsElement()) { if (ShadowRoot* shadow = mParent->AsElement()->GetShadowRoot()) { mParent = shadow; - mXBLInvolved = true; + mXBLInvolved = Some(true); return; } } @@ -143,25 +144,31 @@ FlattenedChildIterator::Init(bool aIgnoreXBL) if (binding) { MOZ_ASSERT(binding->GetAnonymousContent()); mParent = binding->GetAnonymousContent(); - mXBLInvolved = true; + mXBLInvolved = Some(true); + } +} + +bool +FlattenedChildIterator::ComputeWhetherXBLIsInvolved() const +{ + MOZ_ASSERT(mXBLInvolved.isNothing()); + // We set mXBLInvolved to true if either the node we're iterating has a + // binding with content attached to it (in which case it is handled in Init), + // or the node is generated XBL content and has an child. + if (!mParent->GetBindingParent()) { + return false; } - // We set mXBLInvolved to true if either: - // - The node we're iterating has a binding with content attached to it. - // - The node is generated XBL content and has an child. - // - // FIXME(emilio): This is very slow :( - if (!mXBLInvolved && mParent->GetBindingParent()) { - for (nsIContent* child = mParent->GetFirstChild(); - child; - child = child->GetNextSibling()) { - if (child->NodeInfo()->Equals(nsGkAtoms::children, kNameSpaceID_XBL)) { - MOZ_ASSERT(child->GetBindingParent()); - mXBLInvolved = true; - break; - } + for (nsIContent* child = mParent->GetFirstChild(); + child; + child = child->GetNextSibling()) { + if (child->NodeInfo()->Equals(nsGkAtoms::children, kNameSpaceID_XBL)) { + MOZ_ASSERT(child->GetBindingParent()); + return true; } } + + return false; } bool diff --git a/dom/base/ChildIterator.h b/dom/base/ChildIterator.h index 2b20b4e12e71..be95b8a54eee 100644 --- a/dom/base/ChildIterator.h +++ b/dom/base/ChildIterator.h @@ -134,7 +134,6 @@ public: explicit FlattenedChildIterator(const nsIContent* aParent, bool aStartAtBeginning = true) : ExplicitChildIterator(aParent, aStartAtBeginning) - , mXBLInvolved(false) , mOriginalContent(aParent) { Init(false); @@ -142,20 +141,30 @@ public: FlattenedChildIterator(FlattenedChildIterator&& aOther) : ExplicitChildIterator(Move(aOther)) - , mXBLInvolved(aOther.mXBLInvolved) , mOriginalContent(aOther.mOriginalContent) + , mXBLInvolved(aOther.mXBLInvolved) {} FlattenedChildIterator(const FlattenedChildIterator& aOther) : ExplicitChildIterator(aOther) - , mXBLInvolved(aOther.mXBLInvolved) , mOriginalContent(aOther.mOriginalContent) + , mXBLInvolved(aOther.mXBLInvolved) {} - bool XBLInvolved() { return mXBLInvolved; } + bool XBLInvolved() { + if (mXBLInvolved.isNothing()) { + mXBLInvolved = Some(ComputeWhetherXBLIsInvolved()); + } + return *mXBLInvolved; + } const nsIContent* Parent() const { return mOriginalContent; } +private: + bool ComputeWhetherXBLIsInvolved() const; + + void Init(bool aIgnoreXBL); + protected: /** * This constructor is a hack to help AllChildrenIterator which sometimes @@ -164,20 +173,20 @@ protected: FlattenedChildIterator(const nsIContent* aParent, uint32_t aFlags, bool aStartAtBeginning = true) : ExplicitChildIterator(aParent, aStartAtBeginning) - , mXBLInvolved(false) , mOriginalContent(aParent) { bool ignoreXBL = aFlags & nsIContent::eAllButXBL; Init(ignoreXBL); } - void Init(bool aIgnoreXBL); - - // For certain optimizations, nsCSSFrameConstructor needs to know if the - // child list of the element that we're iterating matches its .childNodes. - bool mXBLInvolved; - const nsIContent* mOriginalContent; + +private: + // For certain optimizations, nsCSSFrameConstructor needs to know if the child + // list of the element that we're iterating matches its .childNodes. + // + // This is lazily computed when asked for it. + Maybe mXBLInvolved; }; /** diff --git a/dom/base/nsDeprecatedOperationList.h b/dom/base/nsDeprecatedOperationList.h index 4ad858346e25..58a4e3c17e3a 100644 --- a/dom/base/nsDeprecatedOperationList.h +++ b/dom/base/nsDeprecatedOperationList.h @@ -42,3 +42,8 @@ DEPRECATED_OPERATION(URLCreateObjectURL_MediaStream) DEPRECATED_OPERATION(XMLBaseAttribute) DEPRECATED_OPERATION(WindowContentUntrusted) DEPRECATED_OPERATION(RegisterProtocolHandlerInsecure) +DEPRECATED_OPERATION(MixedDisplayObjectSubrequest) +DEPRECATED_OPERATION(MotionEvent) +DEPRECATED_OPERATION(OrientationEvent) +DEPRECATED_OPERATION(ProximityEvent) +DEPRECATED_OPERATION(AmbientLightEvent) diff --git a/dom/base/nsGenericDOMDataNode.cpp b/dom/base/nsGenericDOMDataNode.cpp index f27dcb39b01f..73bd2439e80d 100644 --- a/dom/base/nsGenericDOMDataNode.cpp +++ b/dom/base/nsGenericDOMDataNode.cpp @@ -911,6 +911,10 @@ nsGenericDOMDataNode::ThreadSafeTextIsOnlyWhitespace() const if (mText.Is2b()) { // The fragment contains non-8bit characters and such characters // are never considered whitespace. + // + // FIXME(emilio): This is not quite true in presence of the + // NS_MAYBE_MODIFIED_FREQUENTLY flag... But looks like we only set that on + // anonymous nodes, so should be fine... return false; } @@ -924,6 +928,8 @@ nsGenericDOMDataNode::ThreadSafeTextIsOnlyWhitespace() const while (cp < end) { char ch = *cp; + // NOTE(emilio): If you ever change the definition of "whitespace" here, you + // need to change it too in RestyleManager::CharacterDataChanged. if (!dom::IsSpaceCharacter(ch)) { return false; } diff --git a/dom/base/nsGlobalWindowInner.cpp b/dom/base/nsGlobalWindowInner.cpp index 57c0dce4c0dc..ee2fd4aabc7c 100644 --- a/dom/base/nsGlobalWindowInner.cpp +++ b/dom/base/nsGlobalWindowInner.cpp @@ -3032,6 +3032,12 @@ nsGlobalWindowInner::RegisterProtocolHandlerAllowedForContext(JSContext* aCx, JS Preferences::GetBool("dom.registerProtocolHandler.insecure.enabled"); } +/* static */ bool +nsGlobalWindowInner::DeviceSensorsEnabled(JSContext* aCx, JSObject* aObj) +{ + return Preferences::GetBool("device.sensors.enabled"); +} + nsIDOMOfflineResourceList* nsGlobalWindowInner::GetApplicationCache(ErrorResult& aError) { diff --git a/dom/base/nsGlobalWindowInner.h b/dom/base/nsGlobalWindowInner.h index e7943734427b..a23da2b94970 100644 --- a/dom/base/nsGlobalWindowInner.h +++ b/dom/base/nsGlobalWindowInner.h @@ -402,6 +402,8 @@ public: static bool RegisterProtocolHandlerAllowedForContext(JSContext* /* unused */, JSObject* aObj); + static bool DeviceSensorsEnabled(JSContext* /* unused */, JSObject* aObj); + bool DoResolve(JSContext* aCx, JS::Handle aObj, JS::Handle aId, JS::MutableHandle aDesc); diff --git a/dom/canvas/crashtests/1441613.html b/dom/canvas/crashtests/1441613.html new file mode 100644 index 000000000000..c6ca8f349282 --- /dev/null +++ b/dom/canvas/crashtests/1441613.html @@ -0,0 +1,19 @@ + + + + + diff --git a/dom/canvas/crashtests/crashtests.list b/dom/canvas/crashtests/crashtests.list index a0e6e6d63323..77973f0dde25 100644 --- a/dom/canvas/crashtests/crashtests.list +++ b/dom/canvas/crashtests/crashtests.list @@ -50,3 +50,4 @@ load 1334647-1.html load 1349067.html pref(gfx.offscreencanvas.enabled,true) load 1348976-1.html load 1357092.html +load 1441613.html diff --git a/dom/events/test/mochitest.ini b/dom/events/test/mochitest.ini index f14ec903217a..8c2adb57ad06 100644 --- a/dom/events/test/mochitest.ini +++ b/dom/events/test/mochitest.ini @@ -117,7 +117,7 @@ skip-if = toolkit == 'android' #CRASH_DUMP, RANDOM skip-if = toolkit == 'android' #CRASH_DUMP, RANDOM [test_bug704423.html] [test_bug741666.html] -[test_bug742376.html] +[test_deviceSensor.html] [test_bug812744.html] [test_bug822898.html] [test_bug855741.html] @@ -167,6 +167,8 @@ skip-if = toolkit == 'android' #CRASH_DUMP, RANDOM skip-if = toolkit == 'android' #TIMED_OUT [test_eventctors.html] skip-if = toolkit == 'android' #CRASH_DUMP, RANDOM +[test_eventctors_sensors.html] +[test_disabled_events.html] [test_eventhandler_scoping.html] [test_eventTimeStamp.html] [test_focus_disabled.html] diff --git a/dom/events/test/test_bug742376.html b/dom/events/test/test_bug742376.html deleted file mode 100644 index 20a075f6a5f7..000000000000 --- a/dom/events/test/test_bug742376.html +++ /dev/null @@ -1,74 +0,0 @@ - - - - - Test for Bug 742376 - - - - - - -Mozilla Bug 742376 - - - - - diff --git a/dom/events/test/test_deviceSensor.html b/dom/events/test/test_deviceSensor.html new file mode 100644 index 000000000000..8cf5fe0bc476 --- /dev/null +++ b/dom/events/test/test_deviceSensor.html @@ -0,0 +1,136 @@ + + + + + Test for Bug 742376 + + + + + + +Mozilla Bug 742376 + + + + + diff --git a/dom/events/test/test_disabled_events.html b/dom/events/test/test_disabled_events.html new file mode 100644 index 000000000000..c53459627dfa --- /dev/null +++ b/dom/events/test/test_disabled_events.html @@ -0,0 +1,40 @@ + + + + + Test for Bug 675884 + + + + +Mozilla Bug 1359076 +

    + +
    +
    +
    + + diff --git a/dom/events/test/test_eventctors.html b/dom/events/test/test_eventctors.html index 3ad26b20a6c9..774d9abd29c0 100644 --- a/dom/events/test/test_eventctors.html +++ b/dom/events/test/test_eventctors.html @@ -624,61 +624,6 @@ is(e.storageArea, localStorage, "Wrong value"); document.dispatchEvent(e); is(receivedEvent, e, "Wrong event!"); -// DeviceProximityEvent -e = new DeviceProximityEvent("hello", {min: 0, value: 1, max: 2}); -is(e.type, "hello", "Wrong event type!"); -ok(!e.isTrusted, "Event should not be trusted"); -is(e.value, 1, "value should be 1"); -is(e.min, 0, "min should be 0"); -is(e.max, 2, "max should be 2"); -document.dispatchEvent(e); -is(receivedEvent, e, "Wrong event!"); -e = new DeviceProximityEvent("hello"); -is(e.value, Infinity, "Uninitialized value should be infinity"); -is(e.min, -Infinity, "Uninitialized min should be -infinity"); -is(e.max, Infinity, "Uninitialized max should be infinity"); - -// UserProximityEvent -e = new UserProximityEvent("hello", {near: true}); -is(e.type, "hello", "Wrong event type!"); -ok(!e.isTrusted, "Event should not be trusted"); -is(e.near, true, "near should be true"); -document.dispatchEvent(e); -is(receivedEvent, e, "Wrong event!"); - -// DeviceLightEvent -e = new DeviceLightEvent("hello", {value: 1} ); -is(e.type, "hello", "Wrong event type!"); -ok(!e.isTrusted, "Event should not be trusted"); -is(e.value, 1, "value should be 1"); -document.dispatchEvent(e); -is(receivedEvent, e, "Wrong event!"); -e = new DeviceLightEvent("hello", {value: Infinity} ); -is(e.value, Infinity, "value should be positive infinity"); -e = new DeviceLightEvent("hello", {value: -Infinity} ); -is(e.value, -Infinity, "value should be negative infinity"); -e = new DeviceLightEvent("hello"); -is(e.value, Infinity, "Uninitialized value should be positive infinity"); - -// DeviceOrientationEvent -e = new DeviceOrientationEvent("hello"); -is(e.type, "hello", "Wrong event type!"); -ok(!e.isTrusted, "Event should not be trusted"); -is(e.alpha, null); -is(e.beta, null); -is(e.gamma, null); -is(e.absolute, false); - -e = new DeviceOrientationEvent("hello", { alpha: 1, beta: 2, gamma: 3, absolute: true } ); -is(e.type, "hello", "Wrong event type!"); -ok(!e.isTrusted, "Event should not be trusted"); -is(e.alpha, 1); -is(e.beta, 2); -is(e.gamma, 3); -is(e.absolute, true); -document.dispatchEvent(e); -is(receivedEvent, e, "Wrong event!"); - // MouseEvent try { diff --git a/dom/events/test/test_eventctors_sensors.html b/dom/events/test/test_eventctors_sensors.html new file mode 100644 index 000000000000..537557df6815 --- /dev/null +++ b/dom/events/test/test_eventctors_sensors.html @@ -0,0 +1,110 @@ + + + + + Test for Bug 675884 + + + + +Mozilla Bug 675884 +

    + +
    +
    +
    + + diff --git a/dom/html/HTMLMediaElement.cpp b/dom/html/HTMLMediaElement.cpp index 4462ef1d756c..f92121ae1086 100644 --- a/dom/html/HTMLMediaElement.cpp +++ b/dom/html/HTMLMediaElement.cpp @@ -1645,11 +1645,10 @@ HTMLMediaElement::MozDumpDebugInfo() void HTMLMediaElement::SetVisible(bool aVisible) { - if (!mDecoder) { - return; + mForcedHidden = !aVisible; + if (mDecoder) { + mDecoder->SetForcedHidden(!aVisible); } - - mDecoder->SetForcedHidden(!aVisible); } already_AddRefed @@ -3880,6 +3879,7 @@ HTMLMediaElement::HTMLMediaElement(already_AddRefed& aNo mFirstFrameLoaded(false), mDefaultPlaybackStartPosition(0.0), mHasSuspendTaint(false), + mForcedHidden(false), mMediaTracksConstructed(false), mVisibilityState(Visibility::UNTRACKED), mErrorSink(new ErrorSink(this)), @@ -7331,6 +7331,9 @@ HTMLMediaElement::SetDecoder(MediaDecoder* aDecoder) } mDecoder = aDecoder; DDLINKCHILD("decoder", mDecoder.get()); + if (mDecoder && mForcedHidden) { + mDecoder->SetForcedHidden(mForcedHidden); + } } float diff --git a/dom/html/HTMLMediaElement.h b/dom/html/HTMLMediaElement.h index 7f75aab11327..0cecc1243660 100644 --- a/dom/html/HTMLMediaElement.h +++ b/dom/html/HTMLMediaElement.h @@ -1821,6 +1821,10 @@ private: // participate in video decoder suspending. bool mHasSuspendTaint; + // True if media element has been forced into being considered 'hidden'. + // For use by mochitests. Enabling pref "media.test.video-suspend" + bool mForcedHidden; + // True if audio tracks and video tracks are constructed and added into the // track list, false if all tracks are removed from the track list. bool mMediaTracksConstructed; diff --git a/dom/html/nsGenericHTMLElement.cpp b/dom/html/nsGenericHTMLElement.cpp index 94feeb34e6ad..708a0a8c68a0 100644 --- a/dom/html/nsGenericHTMLElement.cpp +++ b/dom/html/nsGenericHTMLElement.cpp @@ -2969,11 +2969,11 @@ nsGenericHTMLElement::NewURIFromString(const nsAString& aURISpec, static bool IsOrHasAncestorWithDisplayNone(Element* aElement, nsIPresShell* aPresShell) { - // FIXME(emilio): In the servo case it should suffice to do something like: - // - // return !aElement->HasServoData() || Servo_Element_IsDisplayNone(aElement); - // - // at least in the case the element is part of the flattened tree... + if (aPresShell->StyleSet()->IsServo()) { + return !aElement->HasServoData() || Servo_Element_IsDisplayNone(aElement); + } + +#ifdef MOZ_OLD_STYLE AutoTArray elementsToCheck; // Style and layout work on the flattened tree, so this is what we need to // check in order to figure out whether we're in a display: none subtree. @@ -2990,26 +2990,22 @@ IsOrHasAncestorWithDisplayNone(Element* aElement, nsIPresShell* aPresShell) return false; } - StyleSetHandle styleSet = aPresShell->StyleSet(); - RefPtr sc; + nsStyleSet* styleSet = aPresShell->StyleSet()->AsGecko(); + RefPtr sc; for (auto* element : Reversed(elementsToCheck)) { if (sc) { - if (styleSet->IsGecko()) { - sc = styleSet->ResolveStyleFor(element, sc, - LazyComputeBehavior::Assert); - } else { - // Call ResolveStyleLazily to protect against stale element data in - // the tree when styled by Servo. - sc = styleSet->AsServo()->ResolveStyleLazily( - element, CSSPseudoElementType::NotPseudo); - } + sc = styleSet->ResolveStyleFor(element, sc, LazyComputeBehavior::Assert); } else { - sc = nsComputedDOMStyle::GetStyleContextNoFlush(element, nullptr); + sc = nsComputedDOMStyle::GetStyleContextNoFlush(element, nullptr) + .downcast(); } if (sc->StyleDisplay()->mDisplay == StyleDisplay::None) { return true; } } +#else + MOZ_CRASH("Old style system disabled"); +#endif return false; } diff --git a/dom/interfaces/payments/nsIPaymentRequest.idl b/dom/interfaces/payments/nsIPaymentRequest.idl index 4e39bd7beef7..af48daedf58d 100644 --- a/dom/interfaces/payments/nsIPaymentRequest.idl +++ b/dom/interfaces/payments/nsIPaymentRequest.idl @@ -30,6 +30,7 @@ interface nsIPaymentItem : nsISupports readonly attribute AString label; readonly attribute nsIPaymentCurrencyAmount amount; readonly attribute boolean pending; + readonly attribute AString type; }; [scriptable, builtinclass, uuid(74259861-c318-40e8-b3d5-518e701bed80)] diff --git a/dom/locales/en-US/chrome/dom/dom.properties b/dom/locales/en-US/chrome/dom/dom.properties index 6d353acff558..638a05d13ac9 100644 --- a/dom/locales/en-US/chrome/dom/dom.properties +++ b/dom/locales/en-US/chrome/dom/dom.properties @@ -354,3 +354,8 @@ InvalidKeyframePropertyValue=Keyframe property value “%1$S” is invalid accor ReadableStreamReadingFailed=Failed to read data from the ReadableStream: “%S”. # LOCALIZATION NOTE: Do not translate "registerProtocolHandler". RegisterProtocolHandlerInsecureWarning=Use of the registerProtocolHandler for insecure connections will be removed in version 62. +MixedDisplayObjectSubrequestWarning=Loading insecure content within a plugin embedded in a secure connection is going to be removed. +MotionEventWarning=Use of the motion sensor is deprecated. +OrientationEventWarning=Use of the orientation sensor is deprecated. +ProximityEventWarning=Use of the proximity sensor is deprecated. +AmbientLightEventWarning=Use of the ambient light sensor is deprecated. diff --git a/dom/media/CubebUtils.cpp b/dom/media/CubebUtils.cpp index 9c47c2f2ea55..4cda851197a7 100644 --- a/dom/media/CubebUtils.cpp +++ b/dom/media/CubebUtils.cpp @@ -25,6 +25,9 @@ #include "prdtoa.h" #include #include +#ifdef MOZ_WIDGET_ANDROID +#include "GeneratedJNIWrappers.h" +#endif #define PREF_VOLUME_SCALE "media.volume_scale" #define PREF_CUBEB_BACKEND "media.cubeb.backend" @@ -119,8 +122,8 @@ cubeb* sCubebContext; double sVolumeScale = 1.0; uint32_t sCubebPlaybackLatencyInMilliseconds = 100; uint32_t sCubebMSGLatencyInFrames = 512; -bool sCubebPlaybackLatencyPrefSet; -bool sCubebMSGLatencyPrefSet; +bool sCubebPlaybackLatencyPrefSet = false; +bool sCubebMSGLatencyPrefSet = false; bool sAudioStreamInitEverSucceeded = false; #ifdef MOZ_CUBEB_REMOTING bool sCubebSandbox; @@ -305,11 +308,15 @@ bool InitPreferredSampleRate() if (!context) { return false; } +#ifdef MOZ_WIDGET_ANDROID + sPreferredSampleRate = AndroidGetAudioOutputSampleRate(); +#else if (cubeb_get_preferred_sample_rate(context, &sPreferredSampleRate) != CUBEB_OK) { return false; } +#endif MOZ_ASSERT(sPreferredSampleRate); return true; } @@ -527,14 +534,28 @@ bool CubebMSGLatencyPrefSet() return sCubebMSGLatencyPrefSet; } -Maybe GetCubebMSGLatencyInFrames() +uint32_t GetCubebMSGLatencyInFrames(cubeb_stream_params * params) { StaticMutexAutoLock lock(sMutex); - if (!sCubebMSGLatencyPrefSet) { - return Maybe(); + if (sCubebMSGLatencyPrefSet) { + MOZ_ASSERT(sCubebMSGLatencyInFrames > 0); + return sCubebMSGLatencyInFrames; } - MOZ_ASSERT(sCubebMSGLatencyInFrames > 0); - return Some(sCubebMSGLatencyInFrames); + +#ifdef MOZ_WIDGET_ANDROID + return AndroidGetAudioOutputFramesPerBuffer(); +#else + cubeb* context = GetCubebContextUnlocked(); + if (!context) { + return sCubebMSGLatencyInFrames; // default 512 + } + uint32_t latency_frames = 0; + if (cubeb_get_min_latency(context, params, &latency_frames) != CUBEB_OK) { + NS_WARNING("Could not get minimal latency from cubeb."); + return sCubebMSGLatencyInFrames; // default 512 + } + return latency_frames; +#endif } void InitLibrary() @@ -741,5 +762,20 @@ void GetDeviceCollection(nsTArray>& aDeviceInfos, } } +#ifdef MOZ_WIDGET_ANDROID +uint32_t AndroidGetAudioOutputSampleRate() +{ + int32_t sample_rate = java::GeckoAppShell::GetAudioOutputSampleRate(); + MOZ_ASSERT(sample_rate > 0); + return sample_rate; +} +uint32_t AndroidGetAudioOutputFramesPerBuffer() +{ + int32_t frames = java::GeckoAppShell::GetAudioOutputFramesPerBuffer(); + MOZ_ASSERT(frames > 0); + return frames; +} +#endif + } // namespace CubebUtils } // namespace mozilla diff --git a/dom/media/CubebUtils.h b/dom/media/CubebUtils.h index 60e9eb1e477e..79f52d9714e6 100644 --- a/dom/media/CubebUtils.h +++ b/dom/media/CubebUtils.h @@ -44,7 +44,7 @@ cubeb* GetCubebContext(); void ReportCubebStreamInitFailure(bool aIsFirstStream); void ReportCubebBackendUsed(); uint32_t GetCubebPlaybackLatencyInMilliseconds(); -Maybe GetCubebMSGLatencyInFrames(); +uint32_t GetCubebMSGLatencyInFrames(cubeb_stream_params * params); bool CubebLatencyPrefSet(); cubeb_channel_layout ConvertChannelMapToCubebLayout(uint32_t aChannelMap); void GetCurrentBackend(nsAString& aBackend); @@ -52,6 +52,11 @@ void GetPreferredChannelLayout(nsAString& aLayout); void GetDeviceCollection(nsTArray>& aDeviceInfos, Side aSide); cubeb_channel_layout GetPreferredChannelLayoutOrSMPTE(cubeb* context, uint32_t aChannels); + +#ifdef MOZ_WIDGET_ANDROID +uint32_t AndroidGetAudioOutputSampleRate(); +uint32_t AndroidGetAudioOutputFramesPerBuffer(); +#endif } // namespace CubebUtils } // namespace mozilla diff --git a/dom/media/GraphDriver.cpp b/dom/media/GraphDriver.cpp index f3d8c05d6be5..62d10cce42c6 100644 --- a/dom/media/GraphDriver.cpp +++ b/dom/media/GraphDriver.cpp @@ -599,7 +599,6 @@ AudioCallbackDriver::Init() cubeb_stream_params output; cubeb_stream_params input; - uint32_t latency_frames; bool firstStream = CubebUtils::GetFirstStream(); MOZ_ASSERT(!NS_IsMainThread(), @@ -629,14 +628,7 @@ AudioCallbackDriver::Init() output.layout = CubebUtils::GetPreferredChannelLayoutOrSMPTE(cubebContext, mOutputChannels); output.prefs = CUBEB_STREAM_PREF_NONE; - Maybe latencyPref = CubebUtils::GetCubebMSGLatencyInFrames(); - if (latencyPref) { - latency_frames = latencyPref.value(); - } else { - if (cubeb_get_min_latency(cubebContext, &output, &latency_frames) != CUBEB_OK) { - NS_WARNING("Could not get minimal latency from cubeb."); - } - } + uint32_t latency_frames = CubebUtils::GetCubebMSGLatencyInFrames(&output); // Macbook and MacBook air don't have enough CPU to run very low latency // MediaStreamGraphs, cap the minimal latency to 512 frames int this case. diff --git a/dom/media/MediaDecoderStateMachine.cpp b/dom/media/MediaDecoderStateMachine.cpp index d73bfdbea90b..1ded2b283474 100644 --- a/dom/media/MediaDecoderStateMachine.cpp +++ b/dom/media/MediaDecoderStateMachine.cpp @@ -3080,6 +3080,11 @@ void MediaDecoderStateMachine::SetVideoDecodeModeInternal(VideoDecodeMode aMode) { MOZ_ASSERT(OnTaskQueue()); + LOG("SetVideoDecodeModeInternal(), VideoDecodeMode=(%s->%s), mVideoDecodeSuspended=%c", + mVideoDecodeMode == VideoDecodeMode::Normal ? "Normal" : "Suspend", + aMode == VideoDecodeMode::Normal ? "Normal" : "Suspend", + mVideoDecodeSuspended ? 'T' : 'F'); + // Should not suspend decoding if we don't turn on the pref. if (!MediaPrefs::MDSMSuspendBackgroundVideoEnabled() && aMode == VideoDecodeMode::Suspend) { @@ -3092,11 +3097,6 @@ void MediaDecoderStateMachine::SetVideoDecodeModeInternal(VideoDecodeMode aMode) return; } - LOG("SetVideoDecodeModeInternal(), VideoDecodeMode=(%s->%s), mVideoDecodeSuspended=%c", - mVideoDecodeMode == VideoDecodeMode::Normal ? "Normal" : "Suspend", - aMode == VideoDecodeMode::Normal ? "Normal" : "Suspend", - mVideoDecodeSuspended ? 'T' : 'F'); - // Set new video decode mode. mVideoDecodeMode = aMode; diff --git a/dom/media/test/background_video.js b/dom/media/test/background_video.js index bc62086b68fc..fc7b9215b8d7 100644 --- a/dom/media/test/background_video.js +++ b/dom/media/test/background_video.js @@ -2,7 +2,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /* jshint esversion: 6, -W097 */ -/* globals SimpleTest, SpecialPowers, info, is, ok */ +/* globals SimpleTest, SpecialPowers, document, info, is, manager, ok */ "use strict"; @@ -14,6 +14,38 @@ function startTest(test) { }); } +/** + * @param {HTMLMediaElement} video target of interest. + * @param {string} eventName the event to wait on. + * @returns {Promise} A promise that is resolved when event happens. + */ +function nextEvent(video, eventName) { + return new Promise(function (resolve, reject) { + let f = function (event) { + ok(true, `${video.token} ${eventName}.`); + video.removeEventListener(eventName, f, false); + resolve(event); + }; + video.addEventListener(eventName, f, false); + }); +} + +function nextVideoEnded(video) { + return nextEvent(video, 'ended'); +} + +function nextVideoPlaying(video) { + return nextEvent(video, 'playing'); +} + +function nextVideoResumes(video) { + return nextEvent(video, 'mozexitvideosuspend'); +} + +function nextVideoSuspends(video) { + return nextEvent(video, 'mozentervideosuspend'); +} + /** * @param {string} url video src. * @returns {HTMLMediaElement} The created video element. @@ -21,14 +53,14 @@ function startTest(test) { function appendVideoToDoc(url, token, width, height) { // Default size of (160, 120) is used by other media tests. if (width === undefined) { width = 160; } - if (height === undefined) { height = 3*width/4; } + if (height === undefined) { height = 3 * width / 4; } let v = document.createElement('video'); v.token = token; - document.body.appendChild(v); v.width = width; v.height = height; v.src = url; + document.body.appendChild(v); return v; } @@ -89,7 +121,7 @@ function testVideoSuspendsWhenHidden(video) { * @returns {Promise} Promise that is resolved when video decode resumes. */ function testVideoResumesWhenShown(video) { - var p = once(video, 'mozexitvideosuspend').then(() => { + var p = once(video, 'mozexitvideosuspend').then(() => { ok(true, `${video.token} resumes`); }); Log(video.token, "Set visible"); @@ -102,7 +134,7 @@ function testVideoResumesWhenShown(video) { * @returns {Promise} Promise that is resolved when video decode resumes. */ function testVideoOnlySeekCompletedWhenShown(video) { - var p = once(video, 'mozvideoonlyseekcompleted').then(() => { + var p = once(video, 'mozvideoonlyseekcompleted').then(() => { ok(true, `${video.token} resumes`); }); Log(video.token, "Set visible"); @@ -116,7 +148,7 @@ function testVideoOnlySeekCompletedWhenShown(video) { */ function checkVideoDoesntSuspend(video) { let p = Promise.race([ - waitUntilEnded(video).then(() => { ok(true, `${video.token} ended before decode was suspended`)}), + waitUntilEnded(video).then(() => { ok(true, `${video.token} ended before decode was suspended`) }), once(video, 'mozentervideosuspend', () => { Promise.reject(new Error(`${video.token} suspended`)) }) ]); Log(video.token, "Set hidden."); diff --git a/dom/media/test/test_background_video_suspend.html b/dom/media/test/test_background_video_suspend.html index 8a7f1c40a9d4..12fea9e01601 100644 --- a/dom/media/test/test_background_video_suspend.html +++ b/dom/media/test/test_background_video_suspend.html @@ -4,48 +4,71 @@ - + + + startTest({ + desc: 'Test Background Video Suspends', + prefs: [ + ["media.test.video-suspend", true], + ["media.suspend-bkgnd-video.enabled", true], + // Use a short delay to ensure video decode suspend happens before end + // of video. + ["media.suspend-bkgnd-video.delay-ms", MIN_DELAY], + ["privacy.reduceTimerPrecision", false] + ], + tests: gDecodeSuspendTests, + runTest: runTest + }); + \ No newline at end of file diff --git a/dom/media/test/test_background_video_suspend_ends.html b/dom/media/test/test_background_video_suspend_ends.html index e4478f78a426..2b703c4eb3ab 100644 --- a/dom/media/test/test_background_video_suspend_ends.html +++ b/dom/media/test/test_background_video_suspend_ends.html @@ -4,36 +4,50 @@ - + + + startTest({ + desc: "Test Background Suspended Video Fires 'ended' Event", + prefs: [ + ["media.test.video-suspend", true], + ["media.suspend-bkgnd-video.enabled", true], + // User a short delay to ensure video decode suspend happens before end + // of video. + ["media.suspend-bkgnd-video.delay-ms", 1000] + ], + tests: gDecodeSuspendTests, + runTest: runTest + }); + \ No newline at end of file diff --git a/dom/payments/PaymentRequestData.cpp b/dom/payments/PaymentRequestData.cpp index 8ccfd772bd01..59893d0a1865 100644 --- a/dom/payments/PaymentRequestData.cpp +++ b/dom/payments/PaymentRequestData.cpp @@ -108,10 +108,12 @@ NS_IMPL_ISUPPORTS(PaymentItem, PaymentItem::PaymentItem(const nsAString& aLabel, nsIPaymentCurrencyAmount* aAmount, - const bool aPending) + const bool aPending, + const nsAString& aType) : mLabel(aLabel) , mAmount(aAmount) , mPending(aPending) + , mType(aType) { } @@ -126,7 +128,7 @@ PaymentItem::Create(const IPCPaymentItem& aIPCItem, nsIPaymentItem** aItem) return rv; } nsCOMPtr item = - new PaymentItem(aIPCItem.label(), amount, aIPCItem.pending()); + new PaymentItem(aIPCItem.label(), amount, aIPCItem.pending(), aIPCItem.type()); item.forget(aItem); return NS_OK; } @@ -156,6 +158,13 @@ PaymentItem::GetPending(bool* aPending) return NS_OK; } +NS_IMETHODIMP +PaymentItem::GetType(nsAString& aType) +{ + aType = mType; + return NS_OK; +} + /* PaymentDetailsModifier */ NS_IMPL_ISUPPORTS(PaymentDetailsModifier, diff --git a/dom/payments/PaymentRequestData.h b/dom/payments/PaymentRequestData.h index e05d5c1e6ea5..5eb5298f2796 100644 --- a/dom/payments/PaymentRequestData.h +++ b/dom/payments/PaymentRequestData.h @@ -65,13 +65,15 @@ public: private: PaymentItem(const nsAString& aLabel, nsIPaymentCurrencyAmount* aAmount, - const bool aPending); + const bool aPending, + const nsAString& aType); ~PaymentItem() = default; nsString mLabel; nsCOMPtr mAmount; bool mPending; + nsString mType; }; class PaymentDetailsModifier final : public nsIPaymentDetailsModifier diff --git a/dom/payments/PaymentRequestManager.cpp b/dom/payments/PaymentRequestManager.cpp index 1bb30c377d01..54ddb7d1d030 100644 --- a/dom/payments/PaymentRequestManager.cpp +++ b/dom/payments/PaymentRequestManager.cpp @@ -51,9 +51,18 @@ ConvertCurrencyAmount(const PaymentCurrencyAmount& aAmount, void ConvertItem(const PaymentItem& aItem, IPCPaymentItem& aIPCItem) { + uint8_t typeIndex = UINT8_MAX; + if (aItem.mType.WasPassed()) { + typeIndex = static_cast(aItem.mType.Value()); + } + nsString type; + if (typeIndex < ArrayLength(PaymentItemTypeValues::strings)) { + type.AssignASCII( + PaymentItemTypeValues::strings[typeIndex].value); + } IPCPaymentCurrencyAmount amount; ConvertCurrencyAmount(aItem.mAmount, amount); - aIPCItem = IPCPaymentItem(aItem.mLabel, amount, aItem.mPending); + aIPCItem = IPCPaymentItem(aItem.mLabel, amount, aItem.mPending, type); } nsresult diff --git a/dom/payments/ipc/PPaymentRequest.ipdl b/dom/payments/ipc/PPaymentRequest.ipdl index 8b711b18c055..dc26793a90da 100644 --- a/dom/payments/ipc/PPaymentRequest.ipdl +++ b/dom/payments/ipc/PPaymentRequest.ipdl @@ -27,6 +27,7 @@ struct IPCPaymentItem nsString label; IPCPaymentCurrencyAmount amount; bool pending; + nsString type; }; struct IPCPaymentDetailsModifier diff --git a/dom/payments/test/ConstructorChromeScript.js b/dom/payments/test/ConstructorChromeScript.js index 08108b9946b3..42661351c598 100644 --- a/dom/payments/test/ConstructorChromeScript.js +++ b/dom/payments/test/ConstructorChromeScript.js @@ -141,8 +141,8 @@ function checkComplexRequest(payRequest) { if (!details.displayItems) { emitTestFail("details.displayItems should not be undefined."); } - if (displayItems.length != 2) { - emitTestFail("displayItems' length should be 2.") + if (displayItems.length != 3) { + emitTestFail("displayItems' length should be 3.") } let item = displayItems.queryElementAt(0, Ci.nsIPaymentItem); if (item.label != "First item") { @@ -154,6 +154,9 @@ function checkComplexRequest(payRequest) { if (item.amount.value != "60.00") { emitTestFail("1st display item's value should be '60.00'."); } + if (item.type != "") { + emitTestFail("1st display item's type should be ''."); + } item = displayItems.queryElementAt(1, Ci.nsIPaymentItem); if (item.label != "Second item") { emitTestFail("2nd display item's label should be 'Second item'."); @@ -164,6 +167,22 @@ function checkComplexRequest(payRequest) { if (item.amount.value != "40.00") { emitTestFail("2nd display item's value should be '40.00'."); } + if (item.type != "") { + emitTestFail("2nd display item's type should be ''."); + } + item = displayItems.queryElementAt(2, Ci.nsIPaymentItem); + if (item.label != "Tax") { + emitTestFail("3rd display item's label should be 'Tax'."); + } + if (item.amount.currency != "USD") { + emitTestFail("3rd display item's currency should be 'USD'."); + } + if (item.amount.value != "5.00") { + emitTestFail("3rd display item's value should be '5.00'."); + } + if (item.type != "tax") { + emitTestFail("3rd display item's type should be 'tax'."); + } const modifiers = details.modifiers; if (!modifiers) { diff --git a/dom/payments/test/test_constructor.html b/dom/payments/test/test_constructor.html index 1374bba47662..2fe0c1232203 100644 --- a/dom/payments/test/test_constructor.html +++ b/dom/payments/test/test_constructor.html @@ -75,6 +75,14 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1345361 currency: "USD", value: "40.00" } + }, + { + label: "Tax", + amount: { + currency: "USD", + value: "5.00" + }, + type: "tax" } ], modifiers: [ diff --git a/dom/security/nsMixedContentBlocker.cpp b/dom/security/nsMixedContentBlocker.cpp index 0361e744c800..adc0bfd80e88 100644 --- a/dom/security/nsMixedContentBlocker.cpp +++ b/dom/security/nsMixedContentBlocker.cpp @@ -936,6 +936,9 @@ nsMixedContentBlocker::ShouldLoad(bool aHadInsecureImageRedirect, // set hasMixedContentObjectSubrequest on this object if necessary if (aContentType == TYPE_OBJECT_SUBREQUEST) { + if (!sBlockMixedObjectSubrequest) { + rootDoc->WarnOnceAbout(nsIDocument::eMixedDisplayObjectSubrequest); + } rootDoc->SetHasMixedContentObjectSubrequest(true); } diff --git a/dom/system/nsDeviceSensors.cpp b/dom/system/nsDeviceSensors.cpp index 794c7c2d3ad1..ad909d0cdd45 100644 --- a/dom/system/nsDeviceSensors.cpp +++ b/dom/system/nsDeviceSensors.cpp @@ -37,6 +37,12 @@ using namespace hal; #define DEFAULT_SENSOR_POLL 100 +static bool gPrefSensorsEnabled = false; +static bool gPrefMotionSensorEnabled = false; +static bool gPrefOrientationSensorEnabled = false; +static bool gPrefProximitySensorEnabled = false; +static bool gPrefAmbientLightSensorEnabled = false; + static const nsTArray::index_type NoIndex = nsTArray::NoIndex; @@ -106,7 +112,21 @@ nsDeviceSensors::nsDeviceSensors() { mIsUserProximityNear = false; mLastDOMMotionEventTime = TimeStamp::Now(); - mEnabled = Preferences::GetBool("device.sensors.enabled", true); + Preferences::AddBoolVarCache(&gPrefSensorsEnabled, + "device.sensors.enabled", + true); + Preferences::AddBoolVarCache(&gPrefMotionSensorEnabled, + "device.sensors.motion.enabled", + true); + Preferences::AddBoolVarCache(&gPrefOrientationSensorEnabled, + "device.sensors.orientation.enabled", + true); + Preferences::AddBoolVarCache(&gPrefProximitySensorEnabled, + "device.sensors.proximity.enabled", + false); + Preferences::AddBoolVarCache(&gPrefAmbientLightSensorEnabled, + "device.sensors.ambientLight.enabled", + false); for (int i = 0; i < NUM_SENSOR_TYPE; i++) { nsTArray *windows = new nsTArray(); @@ -130,7 +150,7 @@ nsDeviceSensors::~nsDeviceSensors() NS_IMETHODIMP nsDeviceSensors::HasWindowListener(uint32_t aType, nsIDOMWindow *aWindow, bool *aRetVal) { - if (AreSensorEventsDisabled(aWindow)) + if (!IsSensorAllowedByPref(aType, aWindow)) *aRetVal = false; else *aRetVal = mWindowListeners[aType]->IndexOf(aWindow) != NoIndex; @@ -171,7 +191,7 @@ static bool sTestSensorEvents = false; NS_IMETHODIMP nsDeviceSensors::AddWindowListener(uint32_t aType, nsIDOMWindow *aWindow) { - if (AreSensorEventsDisabled(aWindow)) + if (!IsSensorAllowedByPref(aType, aWindow)) return NS_OK; if (mWindowListeners[aType]->IndexOf(aWindow) != NoIndex) @@ -587,17 +607,64 @@ nsDeviceSensors::FireDOMMotionEvent(nsIDocument *doc, } bool -nsDeviceSensors::AreSensorEventsDisabled(nsIDOMWindow* aWindow) +nsDeviceSensors::IsSensorAllowedByPref(uint32_t aType, nsIDOMWindow* aWindow) { - if (!mEnabled) { - return true; - } - - nsCOMPtr window = do_QueryInterface(aWindow); - - if (!window) { + // checks "device.sensors.enabled" master pref + if (!gPrefSensorsEnabled) { return false; } - return nsContentUtils::ShouldResistFingerprinting(window->GetDocShell()); + nsCOMPtr window = do_QueryInterface(aWindow); + nsCOMPtr doc; + if (window) { + doc = window->GetExtantDoc(); + } + + switch (aType) { + case nsIDeviceSensorData::TYPE_LINEAR_ACCELERATION: + case nsIDeviceSensorData::TYPE_ACCELERATION: + case nsIDeviceSensorData::TYPE_GYROSCOPE: + // checks "device.sensors.motion.enabled" pref + if (!gPrefMotionSensorEnabled) { + return false; + } else if (doc) { + doc->WarnOnceAbout(nsIDocument::eMotionEvent); + } + break; + case nsIDeviceSensorData::TYPE_GAME_ROTATION_VECTOR: + case nsIDeviceSensorData::TYPE_ORIENTATION: + case nsIDeviceSensorData::TYPE_ROTATION_VECTOR: + // checks "device.sensors.orientation.enabled" pref + if (!gPrefOrientationSensorEnabled) { + return false; + } else if (doc) { + doc->WarnOnceAbout(nsIDocument::eOrientationEvent); + } + break; + case nsIDeviceSensorData::TYPE_PROXIMITY: + // checks "device.sensors.proximity.enabled" pref + if (!gPrefProximitySensorEnabled) { + return false; + } else if (doc) { + doc->WarnOnceAbout(nsIDocument::eProximityEvent, true); + } + break; + case nsIDeviceSensorData::TYPE_LIGHT: + // checks "device.sensors.ambientLight.enabled" pref + if (!gPrefAmbientLightSensorEnabled) { + return false; + } else if (doc) { + doc->WarnOnceAbout(nsIDocument::eAmbientLightEvent, true); + } + break; + default: + MOZ_ASSERT_UNREACHABLE("Device sensor type not recognised"); + return false; + } + + if (!window) { + return true; + } + + return !nsContentUtils::ShouldResistFingerprinting(window->GetDocShell()); } diff --git a/dom/system/nsDeviceSensors.h b/dom/system/nsDeviceSensors.h index e1a3cc5e5320..e7195fa5887a 100644 --- a/dom/system/nsDeviceSensors.h +++ b/dom/system/nsDeviceSensors.h @@ -69,13 +69,11 @@ private: double y, double z); - bool mEnabled; - inline bool IsSensorEnabled(uint32_t aType) { return mWindowListeners[aType]->Length() > 0; } - bool AreSensorEventsDisabled(nsIDOMWindow* aWindow); + bool IsSensorAllowedByPref(uint32_t aType, nsIDOMWindow* aWindow); mozilla::TimeStamp mLastDOMMotionEventTime; bool mIsUserProximityNear; diff --git a/dom/tests/mochitest/general/test_interfaces.js b/dom/tests/mochitest/general/test_interfaces.js index 5ce5c6e4b715..918c0cf96d5c 100644 --- a/dom/tests/mochitest/general/test_interfaces.js +++ b/dom/tests/mochitest/general/test_interfaces.js @@ -295,13 +295,13 @@ var interfaceNamesInGlobalScope = // IMPORTANT: Do not change this list without review from a DOM peer! {name: "DelayNode", insecureContext: true}, // IMPORTANT: Do not change this list without review from a DOM peer! - {name: "DeviceLightEvent", insecureContext: true}, + {name: "DeviceLightEvent", insecureContext: true, release: true}, // IMPORTANT: Do not change this list without review from a DOM peer! {name: "DeviceMotionEvent", insecureContext: true}, // IMPORTANT: Do not change this list without review from a DOM peer! {name: "DeviceOrientationEvent", insecureContext: true}, // IMPORTANT: Do not change this list without review from a DOM peer! - {name: "DeviceProximityEvent", insecureContext: true}, + {name: "DeviceProximityEvent", insecureContext: true, release: true}, // IMPORTANT: Do not change this list without review from a DOM peer! {name: "Directory", insecureContext: true}, // IMPORTANT: Do not change this list without review from a DOM peer! @@ -1165,7 +1165,7 @@ var interfaceNamesInGlobalScope = // IMPORTANT: Do not change this list without review from a DOM peer! {name: "URLSearchParams", insecureContext: true}, // IMPORTANT: Do not change this list without review from a DOM peer! - {name: "UserProximityEvent", insecureContext: true}, + {name: "UserProximityEvent", insecureContext: true, release: true}, // IMPORTANT: Do not change this list without review from a DOM peer! {name: "ValidityState", insecureContext: true}, // IMPORTANT: Do not change this list without review from a DOM peer! diff --git a/dom/webidl/DeviceLightEvent.webidl b/dom/webidl/DeviceLightEvent.webidl index 453c67613b69..8b44d2f0157e 100644 --- a/dom/webidl/DeviceLightEvent.webidl +++ b/dom/webidl/DeviceLightEvent.webidl @@ -4,7 +4,7 @@ * You can obtain one at http://mozilla.org/MPL/2.0/. */ -[Constructor(DOMString type, optional DeviceLightEventInit eventInitDict)] +[Pref="device.sensors.ambientLight.enabled", Func="nsGlobalWindowInner::DeviceSensorsEnabled", Constructor(DOMString type, optional DeviceLightEventInit eventInitDict)] interface DeviceLightEvent : Event { readonly attribute unrestricted double value; diff --git a/dom/webidl/DeviceMotionEvent.webidl b/dom/webidl/DeviceMotionEvent.webidl index c26ab080c7b6..851b913ef15e 100644 --- a/dom/webidl/DeviceMotionEvent.webidl +++ b/dom/webidl/DeviceMotionEvent.webidl @@ -18,7 +18,7 @@ interface DeviceRotationRate { readonly attribute double? gamma; }; -[Constructor(DOMString type, optional DeviceMotionEventInit eventInitDict)] +[Pref="device.sensors.motion.enabled", Func="nsGlobalWindowInner::DeviceSensorsEnabled", Constructor(DOMString type, optional DeviceMotionEventInit eventInitDict)] interface DeviceMotionEvent : Event { readonly attribute DeviceAcceleration? acceleration; readonly attribute DeviceAcceleration? accelerationIncludingGravity; diff --git a/dom/webidl/DeviceOrientationEvent.webidl b/dom/webidl/DeviceOrientationEvent.webidl index 9802b36813b3..511add55dbdc 100644 --- a/dom/webidl/DeviceOrientationEvent.webidl +++ b/dom/webidl/DeviceOrientationEvent.webidl @@ -4,7 +4,7 @@ * You can obtain one at http://mozilla.org/MPL/2.0/. */ -[Constructor(DOMString type, optional DeviceOrientationEventInit eventInitDict), LegacyEventInit] +[Pref="device.sensors.orientation.enabled", Func="nsGlobalWindowInner::DeviceSensorsEnabled", Constructor(DOMString type, optional DeviceOrientationEventInit eventInitDict), LegacyEventInit] interface DeviceOrientationEvent : Event { readonly attribute double? alpha; diff --git a/dom/webidl/DeviceProximityEvent.webidl b/dom/webidl/DeviceProximityEvent.webidl index 0d3dc1275a69..70ee70b3364f 100644 --- a/dom/webidl/DeviceProximityEvent.webidl +++ b/dom/webidl/DeviceProximityEvent.webidl @@ -4,7 +4,7 @@ * You can obtain one at http://mozilla.org/MPL/2.0/. */ -[Constructor(DOMString type, optional DeviceProximityEventInit eventInitDict)] +[Pref="device.sensors.proximity.enabled", Func="nsGlobalWindowInner::DeviceSensorsEnabled", Constructor(DOMString type, optional DeviceProximityEventInit eventInitDict)] interface DeviceProximityEvent : Event { readonly attribute double value; diff --git a/dom/webidl/PaymentRequest.webidl b/dom/webidl/PaymentRequest.webidl index 9dc8fd7648d4..d49cbf7b30ad 100644 --- a/dom/webidl/PaymentRequest.webidl +++ b/dom/webidl/PaymentRequest.webidl @@ -18,10 +18,15 @@ dictionary PaymentCurrencyAmount { DOMString currencySystem = "urn:iso:std:iso:4217"; }; +enum PaymentItemType { + "tax" +}; + dictionary PaymentItem { required DOMString label; required PaymentCurrencyAmount amount; boolean pending = false; + PaymentItemType type; }; dictionary PaymentShippingOption { diff --git a/dom/webidl/UserProximityEvent.webidl b/dom/webidl/UserProximityEvent.webidl index 9c10947f78cd..671fc4625685 100644 --- a/dom/webidl/UserProximityEvent.webidl +++ b/dom/webidl/UserProximityEvent.webidl @@ -4,7 +4,7 @@ * You can obtain one at http://mozilla.org/MPL/2.0/. */ -[Constructor(DOMString type, optional UserProximityEventInit eventInitDict)] +[Pref="device.sensors.proximity.enabled", Func="nsGlobalWindowInner::DeviceSensorsEnabled", Constructor(DOMString type, optional UserProximityEventInit eventInitDict)] interface UserProximityEvent : Event { readonly attribute boolean near; diff --git a/dom/xul/XULDocument.cpp b/dom/xul/XULDocument.cpp index 4f31cbc05fb7..8e0d4fe0bb7d 100644 --- a/dom/xul/XULDocument.cpp +++ b/dom/xul/XULDocument.cpp @@ -1073,11 +1073,11 @@ XULDocument::GetElementsByAttribute(const nsAString& aAttribute, const nsAString& aValue) { RefPtr attrAtom(NS_Atomize(aAttribute)); - void* attrValue = new nsString(aValue); + nsAutoPtr attrValue(new nsString(aValue)); RefPtr list = new nsContentList(this, MatchAttribute, nsContentUtils::DestroyMatchString, - attrValue, + attrValue.forget(), true, attrAtom, kNameSpaceID_Unknown); @@ -1092,7 +1092,7 @@ XULDocument::GetElementsByAttributeNS(const nsAString& aNamespaceURI, ErrorResult& aRv) { RefPtr attrAtom(NS_Atomize(aAttribute)); - void* attrValue = new nsString(aValue); + nsAutoPtr attrValue(new nsString(aValue)); int32_t nameSpaceId = kNameSpaceID_Wildcard; if (!aNamespaceURI.EqualsLiteral("*")) { @@ -1108,7 +1108,7 @@ XULDocument::GetElementsByAttributeNS(const nsAString& aNamespaceURI, RefPtr list = new nsContentList(this, MatchAttribute, nsContentUtils::DestroyMatchString, - attrValue, + attrValue.forget(), true, attrAtom, nameSpaceId); diff --git a/ipc/mscom/FastMarshaler.cpp b/ipc/mscom/FastMarshaler.cpp index 52f3a935bf59..6660b42cd28d 100644 --- a/ipc/mscom/FastMarshaler.cpp +++ b/ipc/mscom/FastMarshaler.cpp @@ -103,7 +103,7 @@ FastMarshaler::GetMarshalFlags(DWORD aDestContext, DWORD aMshlFlags) return aMshlFlags; } - if (!IsCallerExternalProcess()) { + if (IsCallerExternalProcess()) { return aMshlFlags; } diff --git a/ipc/mscom/IHandlerProvider.h b/ipc/mscom/IHandlerProvider.h index 76f9242f4adf..dabd4d6aec74 100644 --- a/ipc/mscom/IHandlerProvider.h +++ b/ipc/mscom/IHandlerProvider.h @@ -23,6 +23,7 @@ struct HandlerProvider virtual STDMETHODIMP GetHandlerPayloadSize(NotNull aInterceptor, NotNull aOutPayloadSize) = 0; virtual STDMETHODIMP WriteHandlerPayload(NotNull aInterceptor, NotNull aStream) = 0; virtual STDMETHODIMP_(REFIID) MarshalAs(REFIID aIid) = 0; + virtual STDMETHODIMP DisconnectHandlerRemotes() = 0; }; struct IHandlerProvider : public IUnknown diff --git a/ipc/mscom/Interceptor.cpp b/ipc/mscom/Interceptor.cpp index ecb07a24b7e3..cf6d5eca4f96 100644 --- a/ipc/mscom/Interceptor.cpp +++ b/ipc/mscom/Interceptor.cpp @@ -428,6 +428,7 @@ Interceptor::ReleaseMarshalData(IStream* pStm) HRESULT Interceptor::DisconnectObject(DWORD dwReserved) { + mEventSink->DisconnectHandlerRemotes(); return mStdMarshal->DisconnectObject(dwReserved); } @@ -830,5 +831,30 @@ Interceptor::Release() return WeakReferenceSupport::Release(); } +/* static */ HRESULT +Interceptor::DisconnectRemotesForTarget(IUnknown* aTarget) +{ + MOZ_ASSERT(aTarget); + + detail::LiveSetAutoLock lock(GetLiveSet()); + + // It is not an error if the interceptor doesn't exist, so we return + // S_FALSE instead of an error in that case. + RefPtr existingWeak(Move(GetLiveSet().Get(aTarget))); + if (!existingWeak) { + return S_FALSE; + } + + RefPtr existingStrong; + if (FAILED(existingWeak->ToStrongRef(getter_AddRefs(existingStrong)))) { + return S_FALSE; + } + // Since we now hold a strong ref on the interceptor, we may now release the + // lock. + lock.Unlock(); + + return ::CoDisconnectObject(existingStrong, 0); +} + } // namespace mscom } // namespace mozilla diff --git a/ipc/mscom/Interceptor.h b/ipc/mscom/Interceptor.h index 70439ff7a2a6..a17d20365b4f 100644 --- a/ipc/mscom/Interceptor.h +++ b/ipc/mscom/Interceptor.h @@ -74,6 +74,26 @@ public: static HRESULT Create(STAUniquePtr aTarget, IInterceptorSink* aSink, REFIID aInitialIid, void** aOutInterface); + /** + * Disconnect all remote clients for a given target. + * Because Interceptors disable COM garbage collection to improve + * performance, they never receive Release calls from remote clients. If + * the object can be shut down while clients still hold a reference, this + * function can be used to force COM to disconnect all remote connections + * (using CoDisconnectObject) and thus release the associated references to + * the Interceptor, its target and any objects associated with the + * HandlerProvider. + * Note that the specified target must be the same IUnknown pointer used to + * create the Interceptor. Where there is multiple inheritance, querying for + * IID_IUnknown and calling this function with that pointer alone will not + * disconnect remotes for all interfaces. If you expect that the same object + * may be fetched with different initial interfaces, you should call this + * function once for each possible IUnknown pointer. + * @return S_OK if there was an Interceptor for the given target, + * S_FALSE if there was not. + */ + static HRESULT DisconnectRemotesForTarget(IUnknown* aTarget); + // IUnknown STDMETHODIMP QueryInterface(REFIID riid, void** ppv) override; STDMETHODIMP_(ULONG) AddRef() override; diff --git a/ipc/mscom/MainThreadHandoff.cpp b/ipc/mscom/MainThreadHandoff.cpp index a0e4a20a64bc..caf209112fc4 100644 --- a/ipc/mscom/MainThreadHandoff.cpp +++ b/ipc/mscom/MainThreadHandoff.cpp @@ -589,6 +589,16 @@ MainThreadHandoff::MarshalAs(REFIID aIid) return mHandlerProvider->MarshalAs(aIid); } +HRESULT +MainThreadHandoff::DisconnectHandlerRemotes() +{ + if (!mHandlerProvider) { + return E_NOTIMPL; + } + + return mHandlerProvider->DisconnectHandlerRemotes(); +} + HRESULT MainThreadHandoff::OnWalkInterface(REFIID aIid, PVOID* aInterface, BOOL aIsInParam, BOOL aIsOutParam) diff --git a/ipc/mscom/MainThreadHandoff.h b/ipc/mscom/MainThreadHandoff.h index 6e4ffc13f52d..deb295017822 100644 --- a/ipc/mscom/MainThreadHandoff.h +++ b/ipc/mscom/MainThreadHandoff.h @@ -66,6 +66,7 @@ public: STDMETHODIMP WriteHandlerPayload(NotNull aInterceptor, NotNull aStream) override; STDMETHODIMP_(REFIID) MarshalAs(REFIID aIid) override; + STDMETHODIMP DisconnectHandlerRemotes() override; // ICallFrameWalker STDMETHODIMP OnWalkInterface(REFIID aIid, PVOID* aInterface, BOOL aIsInParam, diff --git a/layout/base/PresShell.cpp b/layout/base/PresShell.cpp index e22741e67f9a..2be19c6f72a5 100644 --- a/layout/base/PresShell.cpp +++ b/layout/base/PresShell.cpp @@ -4300,20 +4300,7 @@ PresShell::CharacterDataChanged(nsIDocument* aDocument, nsAutoCauseReflowNotifier crNotifier(this); - // Call this here so it only happens for real content mutations and - // not cases when the frame constructor calls its own methods to force - // frame reconstruction. - nsIContent *container = aContent->GetParent(); - uint32_t selectorFlags = - container ? (container->GetFlags() & NODE_ALL_SELECTOR_FLAGS) : 0; - if (selectorFlags != 0 && !aContent->IsRootOfAnonymousSubtree()) { - Element* element = container->AsElement(); - if (aInfo.mAppend && !aContent->GetNextSibling()) - mPresContext->RestyleManager()->RestyleForAppend(element, aContent); - else - mPresContext->RestyleManager()->RestyleForInsertOrChange(element, aContent); - } - + mPresContext->RestyleManager()->CharacterDataChanged(aContent, aInfo); mFrameConstructor->CharacterDataChanged(aContent, aInfo); VERIFY_STYLE_TREE; } diff --git a/layout/base/RestyleManager.cpp b/layout/base/RestyleManager.cpp index 0acb44db4370..d66a1334ce95 100644 --- a/layout/base/RestyleManager.cpp +++ b/layout/base/RestyleManager.cpp @@ -21,6 +21,7 @@ #include "nsStyleUtil.h" #include "StickyScrollContainer.h" #include "mozilla/EffectSet.h" +#include "mozilla/IntegerRange.h" #include "mozilla/ViewportFrame.h" #include "SVGObserverUtils.h" #include "SVGTextFrame.h" @@ -52,27 +53,6 @@ RestyleManager::ContentInserted(nsINode* aContainer, nsIContent* aChild) void RestyleManager::ContentAppended(nsIContent* aContainer, nsIContent* aFirstNewContent) -{ - RestyleForAppend(aContainer, aFirstNewContent); -} - -void -RestyleManager::RestyleForEmptyChange(Element* aContainer) -{ - // In some cases (:empty + E, :empty ~ E), a change in the content of - // an element requires restyling its parent's siblings. - nsRestyleHint hint = eRestyle_Subtree; - nsIContent* grandparent = aContainer->GetParent(); - if (grandparent && - (grandparent->GetFlags() & NODE_HAS_SLOW_SELECTOR_LATER_SIBLINGS)) { - hint = nsRestyleHint(hint | eRestyle_LaterSiblings); - } - PostRestyleEvent(aContainer, hint, nsChangeHint(0)); -} - -void -RestyleManager::RestyleForAppend(nsIContent* aContainer, - nsIContent* aFirstNewContent) { // The container cannot be a document, but might be a ShadowRoot. if (!aContainer->IsElement()) { @@ -134,6 +114,62 @@ RestyleManager::RestyleForAppend(nsIContent* aContainer, } } +void +RestyleManager::RestyleForEmptyChange(Element* aContainer) +{ + // In some cases (:empty + E, :empty ~ E), a change in the content of + // an element requires restyling its parent's siblings. + nsRestyleHint hint = eRestyle_Subtree; + nsIContent* grandparent = aContainer->GetParent(); + if (grandparent && + (grandparent->GetFlags() & NODE_HAS_SLOW_SELECTOR_LATER_SIBLINGS)) { + hint = nsRestyleHint(hint | eRestyle_LaterSiblings); + } + PostRestyleEvent(aContainer, hint, nsChangeHint(0)); +} + +void +RestyleManager::MaybeRestyleForEdgeChildChange(Element* aContainer, + nsIContent* aChangedChild) +{ + MOZ_ASSERT(aContainer->GetFlags() & NODE_HAS_EDGE_CHILD_SELECTOR); + MOZ_ASSERT(aChangedChild->GetParent() == aContainer); + // restyle the previously-first element child if it is after this node + bool passedChild = false; + for (nsIContent* content = aContainer->GetFirstChild(); + content; + content = content->GetNextSibling()) { + if (content == aChangedChild) { + passedChild = true; + continue; + } + if (content->IsElement()) { + if (passedChild) { + PostRestyleEvent(content->AsElement(), eRestyle_Subtree, + nsChangeHint(0)); + } + break; + } + } + // restyle the previously-last element child if it is before this node + passedChild = false; + for (nsIContent* content = aContainer->GetLastChild(); + content; + content = content->GetPreviousSibling()) { + if (content == aChangedChild) { + passedChild = true; + continue; + } + if (content->IsElement()) { + if (passedChild) { + PostRestyleEvent(content->AsElement(), eRestyle_Subtree, + nsChangeHint(0)); + } + break; + } + } +} + // Needed since we can't use PostRestyleEvent on non-elements (with // eRestyle_LaterSiblings or nsRestyleHint(eRestyle_Subtree | // eRestyle_LaterSiblings) as appropriate). @@ -153,6 +189,132 @@ RestyleSiblingsStartingWith(RestyleManager* aRestyleManager, } } +template +bool +WhitespaceOnly(const CharT* aBuffer, size_t aUpTo) +{ + for (auto index : IntegerRange(aUpTo)) { + if (!dom::IsSpaceCharacter(aBuffer[index])) { + return false; + } + } + return true; +} + +template +bool +WhitespaceOnlyChangedOnAppend(const CharT* aBuffer, + size_t aOldLength, + size_t aNewLength) +{ + MOZ_ASSERT(aOldLength < aNewLength); + if (!WhitespaceOnly(aBuffer, aOldLength)) { + // The old text was already not whitespace-only. + return false; + } + + return !WhitespaceOnly(aBuffer + aOldLength, aNewLength - aOldLength); +} + +static bool +HasAnySignificantSibling(Element* aContainer, nsIContent* aChild) +{ + MOZ_ASSERT(aChild->GetParent() == aContainer); + for (nsIContent* child = aContainer->GetFirstChild(); + child; + child = child->GetNextSibling()) { + if (child == aChild) { + continue; + } + // We don't know whether we're testing :empty or :-moz-only-whitespace, + // so be conservative and assume :-moz-only-whitespace (i.e., make + // IsSignificantChild less likely to be true, and thus make us more + // likely to restyle). + if (nsStyleUtil::IsSignificantChild(child, true, false)) { + return true; + } + } + + return false; +} + +void +RestyleManager::CharacterDataChanged(nsIContent* aContent, + const CharacterDataChangeInfo& aInfo) +{ + nsINode* parent = aContent->GetParentNode(); + MOZ_ASSERT(parent, "How were we notified of a stray node?"); + + uint32_t slowSelectorFlags = parent->GetFlags() & NODE_ALL_SELECTOR_FLAGS; + if (!(slowSelectorFlags & (NODE_HAS_EMPTY_SELECTOR | + NODE_HAS_EDGE_CHILD_SELECTOR))) { + // Nothing to do, no other slow selector can change as a result of this. + return; + } + + if (!aContent->IsNodeOfType(nsINode::eTEXT)) { + // Doesn't matter to styling (could be a processing instruction or a + // comment), it can't change whether any selectors match or don't. + return; + } + + + if (MOZ_UNLIKELY(!parent->IsElement())) { + MOZ_ASSERT(parent->IsShadowRoot()); + return; + } + + if (MOZ_UNLIKELY(aContent->IsRootOfAnonymousSubtree())) { + // This is an anonymous node and thus isn't in child lists, so isn't taken + // into account for selector matching the relevant selectors here. + return; + } + + // Handle appends specially since they're common and we can know both the old + // and the new text exactly. + // + // TODO(emilio): This could be made much more general if :-moz-only-whitespace + // / :-moz-first-node and :-moz-last-node didn't exist. In that case we only + // need to know whether we went from empty to non-empty, and that's trivial to + // know, with CharacterDataChangeInfo... + if (!aInfo.mAppend) { + // FIXME(emilio): This restyles unnecessarily if the text node is the only + // child of the parent element. Fortunately, it's uncommon to have such + // nodes and this not being an append. + // + // See the testcase in bug 1427625 for a test-case that triggers this. + RestyleForInsertOrChange(parent->AsElement(), aContent); + return; + } + + const nsTextFragment* text = aContent->GetText(); + + const size_t oldLength = aInfo.mChangeStart; + const size_t newLength = text->GetLength(); + + const bool emptyChanged = !oldLength && newLength; + + const bool whitespaceOnlyChanged = text->Is2b() + ? WhitespaceOnlyChangedOnAppend(text->Get2b(), oldLength, newLength) + : WhitespaceOnlyChangedOnAppend(text->Get1b(), oldLength, newLength); + + if (!emptyChanged && !whitespaceOnlyChanged) { + return; + } + + if (slowSelectorFlags & NODE_HAS_EMPTY_SELECTOR) { + if (!HasAnySignificantSibling(parent->AsElement(), aContent)) { + // We used to be empty, restyle the parent. + RestyleForEmptyChange(parent->AsElement()); + return; + } + } + + if (slowSelectorFlags & NODE_HAS_EDGE_CHILD_SELECTOR) { + MaybeRestyleForEdgeChildChange(parent->AsElement(), aContent); + } +} + // Restyling for a ContentInserted or CharacterDataChanged notification. // This could be used for ContentRemoved as well if we got the // notification before the removal happened (and sometimes @@ -176,23 +338,13 @@ RestyleManager::RestyleForInsertOrChange(nsINode* aContainer, return; if (selectorFlags & NODE_HAS_EMPTY_SELECTOR) { - // see whether we need to restyle the container - bool wasEmpty = true; // :empty or :-moz-only-whitespace - for (nsIContent* child = container->GetFirstChild(); - child; - child = child->GetNextSibling()) { - if (child == aChild) - continue; - // We don't know whether we're testing :empty or :-moz-only-whitespace, - // so be conservative and assume :-moz-only-whitespace (i.e., make - // IsSignificantChild less likely to be true, and thus make us more - // likely to restyle). - if (nsStyleUtil::IsSignificantChild(child, true, false)) { - wasEmpty = false; - break; - } - } + // See whether we need to restyle the container due to :empty / + // :-moz-only-whitespace. + const bool wasEmpty = !HasAnySignificantSibling(container, aChild); if (wasEmpty) { + // FIXME(emilio): When coming from CharacterDataChanged this can restyle + // unnecessarily. Also can restyle unnecessarily if aChild is not + // significant anyway, though that's more unlikely. RestyleForEmptyChange(container); return; } @@ -210,40 +362,7 @@ RestyleManager::RestyleForInsertOrChange(nsINode* aContainer, } if (selectorFlags & NODE_HAS_EDGE_CHILD_SELECTOR) { - // restyle the previously-first element child if it is after this node - bool passedChild = false; - for (nsIContent* content = container->GetFirstChild(); - content; - content = content->GetNextSibling()) { - if (content == aChild) { - passedChild = true; - continue; - } - if (content->IsElement()) { - if (passedChild) { - PostRestyleEvent(content->AsElement(), eRestyle_Subtree, - nsChangeHint(0)); - } - break; - } - } - // restyle the previously-last element child if it is before this node - passedChild = false; - for (nsIContent* content = container->GetLastChild(); - content; - content = content->GetPreviousSibling()) { - if (content == aChild) { - passedChild = true; - continue; - } - if (content->IsElement()) { - if (passedChild) { - PostRestyleEvent(content->AsElement(), eRestyle_Subtree, - nsChangeHint(0)); - } - break; - } - } + MaybeRestyleForEdgeChildChange(container, aChild); } } diff --git a/layout/base/RestyleManager.h b/layout/base/RestyleManager.h index f90ca3ff7a43..d6d9075f9a65 100644 --- a/layout/base/RestyleManager.h +++ b/layout/base/RestyleManager.h @@ -159,14 +159,13 @@ public: nsIContent* aFollowingSibling); // Restyling for a ContentInserted (notification after insertion) or - // for a CharacterDataChanged. |aContainer| must be non-null; when + // for some CharacterDataChanged. |aContainer| must be non-null; when // the container is null, no work is needed. void RestyleForInsertOrChange(nsINode* aContainer, nsIContent* aChild); - // Restyling for a ContentAppended (notification after insertion) or - // for a CharacterDataChanged. |aContainer| must be non-null; when - // the container is null, no work is needed. - void RestyleForAppend(nsIContent* aContainer, nsIContent* aFirstNewContent); + // Restyle for a CharacterDataChanged notification. In practice this can only + // affect :empty / :-moz-only-whitespace / :-moz-first-node / :-moz-last-node. + void CharacterDataChanged(nsIContent*, const CharacterDataChangeInfo&); MOZ_DECL_STYLO_METHODS(GeckoRestyleManager, ServoRestyleManager) @@ -223,6 +222,7 @@ protected: } void RestyleForEmptyChange(Element* aContainer); + void MaybeRestyleForEdgeChildChange(Element* aContainer, nsIContent* aChangedChild); void ContentStateChangedInternal(Element* aElement, EventStates aStateMask, diff --git a/layout/base/nsCSSFrameConstructor.cpp b/layout/base/nsCSSFrameConstructor.cpp index 9dd53b946e19..e3eb2fb1b059 100644 --- a/layout/base/nsCSSFrameConstructor.cpp +++ b/layout/base/nsCSSFrameConstructor.cpp @@ -6837,7 +6837,7 @@ nsCSSFrameConstructor::IsValidSibling(nsIFrame* aSibling, template nsIFrame* nsCSSFrameConstructor::FindSiblingInternal( - FlattenedChildIterator aIter, + FlattenedChildIterator& aIter, nsIContent* aTargetContent, StyleDisplay& aTargetContentDisplay) { @@ -6949,8 +6949,9 @@ nsCSSFrameConstructor::FindSibling(const FlattenedChildIterator& aIter, StyleDisplay& aTargetContentDisplay) { nsIContent* targetContent = aIter.Get(); - nsIFrame* sibling = - FindSiblingInternal(aIter, targetContent, aTargetContentDisplay); + FlattenedChildIterator siblingIter = aIter; + nsIFrame* sibling = FindSiblingInternal( + siblingIter, targetContent, aTargetContentDisplay); if (sibling) { return sibling; } diff --git a/layout/base/nsCSSFrameConstructor.h b/layout/base/nsCSSFrameConstructor.h index 03ab6a237189..623d9f3796ad 100644 --- a/layout/base/nsCSSFrameConstructor.h +++ b/layout/base/nsCSSFrameConstructor.h @@ -2155,9 +2155,11 @@ private: mozilla::StyleDisplay& aTargetContentDisplay); // Helper for the implementation of FindSibling. + // + // Beware that this function does mutate the iterator. template nsIFrame* FindSiblingInternal( - mozilla::dom::FlattenedChildIterator, + mozilla::dom::FlattenedChildIterator&, nsIContent* aTargetContent, mozilla::StyleDisplay& aTargetContentDisplay); diff --git a/layout/generic/nsFlexContainerFrame.cpp b/layout/generic/nsFlexContainerFrame.cpp index 63f70f1e3214..dd488a5b2c67 100644 --- a/layout/generic/nsFlexContainerFrame.cpp +++ b/layout/generic/nsFlexContainerFrame.cpp @@ -254,21 +254,15 @@ PhysicalCoordFromFlexRelativeCoord(nscoord aFlexRelativeCoord, return aContainerSize - aFlexRelativeCoord; } -// Helper-macro to let us pick one of two expressions to evaluate -// (a width expression vs. a height expression), to get a main-axis or -// cross-axis component. -// For code that has e.g. a nsSize object, FlexboxAxisTracker::GetMainComponent -// and GetCrossComponent are cleaner; but in cases where we simply have -// two separate expressions for width and height (which may be expensive to -// evaluate), these macros will ensure that only the expression for the correct -// axis gets evaluated. -#define GET_MAIN_COMPONENT(axisTracker_, width_, height_) \ - (axisTracker_).IsMainAxisHorizontal() ? (width_) : (height_) - -#define GET_CROSS_COMPONENT(axisTracker_, width_, height_) \ - (axisTracker_).IsCrossAxisHorizontal() ? (width_) : (height_) - -// Logical versions of helper-macros above: +// Helper-macros to let us pick one of two expressions to evaluate +// (an inline-axis expression vs. a block-axis expression), to get a +// main-axis or cross-axis component. +// For code that has e.g. a LogicalSize object, the methods +// FlexboxAxisTracker::GetMainComponent and GetCrossComponent are cleaner +// than these macros. But in cases where we simply have two separate +// expressions for ISize and BSize (which may be expensive to evaluate), +// these macros can be used to ensure that only the needed expression is +// evaluated. #define GET_MAIN_COMPONENT_LOGICAL(axisTracker_, wm_, isize_, bsize_) \ wm_.IsOrthogonalTo(axisTracker_.GetWritingMode()) != \ (axisTracker_).IsRowOriented() ? (isize_) : (bsize_) @@ -300,16 +294,6 @@ public: // XXXdholbert [BEGIN DEPRECATED] AxisOrientationType GetMainAxis() const { return mMainAxis; } AxisOrientationType GetCrossAxis() const { return mCrossAxis; } - - bool IsMainAxisHorizontal() const { - // If we're row-oriented, and our writing mode is NOT vertical, - // or we're column-oriented and our writing mode IS vertical, - // then our main axis is horizontal. This handles all cases: - return mIsRowOriented != mWM.IsVertical(); - } - bool IsCrossAxisHorizontal() const { - return !IsMainAxisHorizontal(); - } // XXXdholbert [END DEPRECATED] // Returns the flex container's writing mode. @@ -329,40 +313,34 @@ public: bool IsRowOriented() const { return mIsRowOriented; } bool IsColumnOriented() const { return !mIsRowOriented; } - nscoord GetMainComponent(const nsSize& aSize) const { - return GET_MAIN_COMPONENT(*this, aSize.width, aSize.height); + // aSize is expected to match the flex container's WritingMode. + nscoord GetMainComponent(const LogicalSize& aSize) const { + return IsRowOriented() ? aSize.ISize(mWM) : aSize.BSize(mWM); } int32_t GetMainComponent(const LayoutDeviceIntSize& aIntSize) const { - return GET_MAIN_COMPONENT(*this, aIntSize.width, aIntSize.height); + return IsMainAxisHorizontal() ? aIntSize.width : aIntSize.height; } - nscoord GetCrossComponent(const nsSize& aSize) const { - return GET_CROSS_COMPONENT(*this, aSize.width, aSize.height); + // aSize is expected to match the flex container's WritingMode. + nscoord GetCrossComponent(const LogicalSize& aSize) const { + return IsRowOriented() ? aSize.BSize(mWM) : aSize.ISize(mWM); } int32_t GetCrossComponent(const LayoutDeviceIntSize& aIntSize) const { - return GET_CROSS_COMPONENT(*this, aIntSize.width, aIntSize.height); + return IsMainAxisHorizontal() ? aIntSize.height : aIntSize.width; } - nscoord GetMarginSizeInMainAxis(const nsMargin& aMargin) const { - return IsMainAxisHorizontal() ? - aMargin.LeftRight() : - aMargin.TopBottom(); + // NOTE: aMargin is expected to use the flex container's WritingMode. + nscoord GetMarginSizeInMainAxis(const LogicalMargin& aMargin) const { + // If we're row-oriented, our main axis is the inline axis. + return IsRowOriented() + ? aMargin.IStartEnd(mWM) + : aMargin.BStartEnd(mWM); } - nscoord GetMarginSizeInCrossAxis(const nsMargin& aMargin) const { - return IsCrossAxisHorizontal() ? - aMargin.LeftRight() : - aMargin.TopBottom(); - } - - // Returns aFrame's computed value for 'height' or 'width' -- whichever is in - // the cross-axis. (NOTE: This is cross-axis-specific for now. If we need a - // main-axis version as well, we could generalize or clone this function.) - const nsStyleCoord& ComputedCrossSize(const nsIFrame* aFrame) const { - const nsStylePosition* stylePos = aFrame->StylePosition(); - - return IsCrossAxisHorizontal() ? - stylePos->mWidth : - stylePos->mHeight; + nscoord GetMarginSizeInCrossAxis(const LogicalMargin& aMargin) const { + // If we're row-oriented, our cross axis is the block axis. + return IsRowOriented() + ? aMargin.BStartEnd(mWM) + : aMargin.IStartEnd(mWM); } /** @@ -424,6 +402,15 @@ private: FlexboxAxisTracker(const FlexboxAxisTracker&) = delete; FlexboxAxisTracker& operator=(const FlexboxAxisTracker&) = delete; + // Private because callers shouldn't need to care about physical axes + // (but we do internally, to provide one API). + bool IsMainAxisHorizontal() const { + // If we're row-oriented, and our writing mode is NOT vertical, + // or we're column-oriented and our writing mode IS vertical, + // then our main axis is horizontal. This handles all cases: + return mIsRowOriented != mWM.IsVertical(); + } + // Helpers for constructor which determine the orientation of our axes, based // on legacy box properties (-webkit-box-orient, -webkit-box-direction) or // modern flexbox properties (flex-direction, flex-wrap) depending on whether @@ -553,6 +540,9 @@ public: // before its actual reflow. bool HadMeasuringReflow() const { return mHadMeasuringReflow; } + // Indicates whether this item's computed cross-size property is 'auto'. + bool IsCrossSizeAuto() const; + // Indicates whether this item's cross-size has been stretched (from having // "align-self: stretch" with an auto cross-size and no auto margins in the // cross axis). @@ -567,14 +557,17 @@ public: // visibility:collapse. bool IsStrut() const { return mIsStrut; } - // Returns true if this item's inline axis is parallel (or antiparallel) - // to the container's main axis. Otherwise (i.e. if this item's inline axis - // is orthogonal to the container's main axis), this function returns false. - bool IsInlineAxisMainAxis() const { return mIsInlineAxisMainAxis; } - - // Same as above, but for cross axis. Equivalent to !IsInlineAxisMainAxis(). - // This just exists for convenience/readability at callsites. + // IsInlineAxisMainAxis() returns true if this item's inline axis is parallel + // (or antiparallel) to the container's main axis. Otherwise (i.e. if this + // item's inline axis is orthogonal to the container's main axis), this + // function returns false. The next 3 methods are all other ways of asking + // the same question, and only exist for readability at callsites (depending + // on which axes those callsites are reasoning about). + bool IsInlineAxisMainAxis() const { return mIsInlineAxisMainAxis; } bool IsInlineAxisCrossAxis() const { return !mIsInlineAxisMainAxis; } + bool IsBlockAxisMainAxis() const { return !mIsInlineAxisMainAxis; } + bool IsBlockAxisCrossAxis() const { return mIsInlineAxisMainAxis; } + WritingMode GetWritingMode() const { return mWM; } uint8_t GetAlignSelf() const { return mAlignSelf; } @@ -622,8 +615,10 @@ public: return mFlexShrink * mFlexBaseSize; } - const nsSize& IntrinsicRatio() const { return mIntrinsicRatio; } - bool HasIntrinsicRatio() const { return mIntrinsicRatio != nsSize(); } + // Returns a LogicalSize representing the flex item's logical intrinsic ratio + // (ISize:BSize), as expressed in the *flex container's* writing mode. + const LogicalSize& IntrinsicRatio() const { return mIntrinsicRatio; } + bool HasIntrinsicRatio() const { return !mIntrinsicRatio.IsAllZero(); } // Getters for margin: // =================== @@ -818,7 +813,7 @@ protected: nsIFrame* const mFrame; // The flex item's frame. const float mFlexGrow; const float mFlexShrink; - const nsSize mIntrinsicRatio; + const LogicalSize mIntrinsicRatio; const nsMargin mBorderPadding; nsMargin mMargin; // non-const because we need to resolve auto margins @@ -1202,13 +1197,6 @@ nsFlexContainerFrame::CSSAlignmentForAbsPosChild( return alignment; } -bool -nsFlexContainerFrame::IsHorizontal() -{ - const FlexboxAxisTracker axisTracker(this, GetWritingMode()); - return axisTracker.IsMainAxisHorizontal(); -} - UniquePtr nsFlexContainerFrame::GenerateFlexItemForChild( nsPresContext* aPresContext, @@ -1289,13 +1277,16 @@ nsFlexContainerFrame::GenerateFlexItemForChild( aPresContext->DevPixelsToAppUnits( aAxisTracker.GetCrossComponent(widgetMinSize)); - // GMWS() returns border-box. We need content-box, so subtract - // borderPadding (but don't let that push our min sizes below 0). - nsMargin& bp = childRI.ComputedPhysicalBorderPadding(); - widgetMainMinSize = std::max(widgetMainMinSize - - aAxisTracker.GetMarginSizeInMainAxis(bp), 0); - widgetCrossMinSize = std::max(widgetCrossMinSize - - aAxisTracker.GetMarginSizeInCrossAxis(bp), 0); + // GetMinimumWidgetSize() returns border-box. We need content-box, so + // subtract borderPadding. + const LogicalMargin bpInChildWM = childRI.ComputedLogicalBorderPadding(); + const LogicalMargin bpInFlexWM = + bpInChildWM.ConvertTo(aAxisTracker.GetWritingMode(), childWM); + widgetMainMinSize -= aAxisTracker.GetMarginSizeInMainAxis(bpInFlexWM); + widgetCrossMinSize -= aAxisTracker.GetMarginSizeInCrossAxis(bpInFlexWM); + // ... (but don't let that push these min sizes below 0). + widgetMainMinSize = std::max(0, widgetMainMinSize); + widgetCrossMinSize = std::max(0, widgetCrossMinSize); if (!canOverride) { // Fixed-size widget: freeze our main-size at the widget's mandated size. @@ -1408,22 +1399,19 @@ CrossSizeToUseWithRatio(const FlexItem& aFlexItem, } // Convenience function; returns a main-size, given a cross-size and an -// intrinsic ratio. The intrinsic ratio must not have 0 in its cross-axis -// component (or else we'll divide by 0). +// intrinsic ratio. The caller is responsible for ensuring that the passed-in +// intrinsic ratio must not have 0 in its cross-axis component (or else we'll +// divide by 0). static nscoord MainSizeFromAspectRatio(nscoord aCrossSize, - const nsSize& aIntrinsicRatio, + const LogicalSize& aIntrinsicRatio, const FlexboxAxisTracker& aAxisTracker) { MOZ_ASSERT(aAxisTracker.GetCrossComponent(aIntrinsicRatio) != 0, "Invalid ratio; will divide by 0! Caller should've checked..."); - - if (aAxisTracker.IsCrossAxisHorizontal()) { - // cross axis horiz --> aCrossSize is a width. Converting to height. - return NSCoordMulDiv(aCrossSize, aIntrinsicRatio.height, aIntrinsicRatio.width); - } - // cross axis vert --> aCrossSize is a height. Converting to width. - return NSCoordMulDiv(aCrossSize, aIntrinsicRatio.width, aIntrinsicRatio.height); + return NSCoordMulDiv(aCrossSize, + aAxisTracker.GetMainComponent(aIntrinsicRatio), + aAxisTracker.GetCrossComponent(aIntrinsicRatio)); } // Partially resolves "min-[width|height]:auto" and returns the resulting value. @@ -1805,7 +1793,8 @@ FlexItem::FlexItem(ReflowInput& aFlexItemReflowInput, : mFrame(aFlexItemReflowInput.mFrame), mFlexGrow(aFlexGrow), mFlexShrink(aFlexShrink), - mIntrinsicRatio(mFrame->GetIntrinsicRatio()), + // We store the intrinsic ratio in the *flex container's* WM: + mIntrinsicRatio(aAxisTracker.GetWritingMode(), mFrame->GetIntrinsicRatio()), mBorderPadding(aFlexItemReflowInput.ComputedPhysicalBorderPadding()), mMargin(aFlexItemReflowInput.ComputedPhysicalMargin()), mMainMinSize(aMainMinSize), @@ -1876,14 +1865,15 @@ FlexItem::FlexItem(ReflowInput& aFlexItemReflowInput, #endif // DEBUG // Map align-self 'baseline' value to 'start' when baseline alignment - // is not possible because the FlexItem's writing mode is orthogonal to - // the main axis of the container. If that's the case, we just directly + // is not possible because the FlexItem's block axis is orthogonal to + // the cross axis of the container. If that's the case, we just directly // convert our align-self value here, so that we don't have to handle this // with special cases elsewhere. // We are treating this case as one where it is appropriate to use the - // fallback values defined at https://www.w3.org/TR/css-align-3/#baseline - if (aAxisTracker.IsRowOriented() == - aAxisTracker.GetWritingMode().IsOrthogonalTo(mWM)) { + // fallback values defined at https://www.w3.org/TR/css-align/#baseline-values + // XXXdholbert That spec text actually says to fall back to 'start'/'end', + // not 'flex-start'/'flex-end'... Probably sort this out in bug 1207698. + if (!IsBlockAxisCrossAxis()) { if (mAlignSelf == NS_STYLE_ALIGN_BASELINE) { mAlignSelf = NS_STYLE_ALIGN_FLEX_START; } else if (mAlignSelf == NS_STYLE_ALIGN_LAST_BASELINE) { @@ -1900,7 +1890,7 @@ FlexItem::FlexItem(nsIFrame* aChildFrame, nscoord aCrossSize, : mFrame(aChildFrame), mFlexGrow(0.0f), mFlexShrink(0.0f), - mIntrinsicRatio(), + mIntrinsicRatio(aContainerWM), // mBorderPadding uses default constructor, // mMargin uses default constructor, mFlexBaseSize(0), @@ -1945,20 +1935,20 @@ FlexItem::CheckForMinSizeAuto(const ReflowInput& aFlexItemReflowInput, const nsStyleDisplay* disp = aFlexItemReflowInput.mStyleDisplay; // We'll need special behavior for "min-[width|height]:auto" (whichever is in - // the main axis) iff: + // the flex container's main axis) iff: // (a) its computed value is "auto" // (b) the "overflow" sub-property in the same axis (the main axis) has a // computed value of "visible" - const nsStyleCoord& minSize = GET_MAIN_COMPONENT(aAxisTracker, - pos->mMinWidth, - pos->mMinHeight); + const nsStyleCoord& mainMinSize = aAxisTracker.IsRowOriented() + ? pos->MinISize(aAxisTracker.GetWritingMode()) + : pos->MinBSize(aAxisTracker.GetWritingMode()); - const uint8_t overflowVal = GET_MAIN_COMPONENT(aAxisTracker, - disp->mOverflowX, - disp->mOverflowY); - - mNeedsMinSizeAutoResolution = (minSize.GetUnit() == eStyleUnit_Auto && - overflowVal == NS_STYLE_OVERFLOW_VISIBLE); + // NOTE: Technically we should be checking the 'overflow' subproperty in the + // main axis. But since we only care whether it's 'visible', we can check + // either subproperty -- because they must be BOTH 'visible' or BOTH + // non-'visible' due to the way the subproperties interact. + mNeedsMinSizeAutoResolution = (mainMinSize.GetUnit() == eStyleUnit_Auto && + disp->mOverflowX == NS_STYLE_OVERFLOW_VISIBLE); } nscoord @@ -1967,18 +1957,22 @@ FlexItem::GetBaselineOffsetFromOuterCrossEdge( const FlexboxAxisTracker& aAxisTracker, bool aUseFirstLineBaseline) const { - // NOTE: Currently, 'mAscent' (taken from reflow) is an inherently vertical - // measurement -- it's the distance from the border-top edge of this FlexItem - // to its baseline. So, we can really only do baseline alignment when the - // cross axis is vertical. (The FlexItem constructor enforces this when - // resolving the item's "mAlignSelf" value). - MOZ_ASSERT(!aAxisTracker.IsCrossAxisHorizontal(), + // NOTE: + // * We only use baselines for aligning in the flex container's cross axis. + // * Baselines are a measurement in the item's block axis. + // ...so we only expect to get here if the item's block axis is parallel (or + // antiparallel) to the container's cross axis. (Otherwise, the FlexItem + // constructor should've resolved mAlignSelf with a fallback value, which + // would prevent this function from being called.) + MOZ_ASSERT(IsBlockAxisCrossAxis(), "Only expecting to be doing baseline computations when the " - "cross axis is vertical"); + "cross axis is the block axis"); AxisOrientationType crossAxis = aAxisTracker.GetCrossAxis(); mozilla::Side sideToMeasureFrom = kAxisOrientationToSidesMap[crossAxis][aEdge]; + // XXXdholbert The "top"/"bottom" physical-axis dependencies below need to be + // logicalized -- see bug 1384266. nscoord marginTopToBaseline = ResolvedAscent(aUseFirstLineBaseline) + mMargin.top; @@ -1998,6 +1992,18 @@ FlexItem::GetBaselineOffsetFromOuterCrossEdge( return GetOuterCrossSize(crossAxis) - marginTopToBaseline; } +bool +FlexItem::IsCrossSizeAuto() const +{ + const nsStylePosition* stylePos = mFrame->StylePosition(); + // Check whichever component is in the flex container's cross axis. + // (IsInlineAxisCrossAxis() tells us whether that's our ISize or BSize, in + // terms of our own WritingMode, mWM.) + return eStyleUnit_Auto == (IsInlineAxisCrossAxis() + ? stylePos->ISize(mWM).GetUnit() + : stylePos->BSize(mWM).GetUnit()); +} + uint32_t FlexItem::GetNumAutoMarginsInAxis(AxisOrientationType aAxis) const { @@ -3278,7 +3284,7 @@ FlexItem::ResolveStretchedCrossSize(nscoord aLineCrossSize, // conditions don't hold up, we won't stretch. if (mAlignSelf != NS_STYLE_ALIGN_STRETCH || GetNumAutoMarginsInAxis(crossAxis) != 0 || - eStyleUnit_Auto != aAxisTracker.ComputedCrossSize(mFrame).GetUnit()) { + !IsCrossSizeAuto()) { return; } @@ -4521,10 +4527,10 @@ nsFlexContainerFrame::DoFlexLayout(nsPresContext* aPresContext, item->Frame(), availSize); if (!sizeOverride) { // Directly override the computed main-size, by tweaking reflow state: - if (aAxisTracker.IsMainAxisHorizontal()) { - childReflowInput.SetComputedWidth(item->GetMainSize()); + if (item->IsInlineAxisMainAxis()) { + childReflowInput.SetComputedISize(item->GetMainSize()); } else { - childReflowInput.SetComputedHeight(item->GetMainSize()); + childReflowInput.SetComputedBSize(item->GetMainSize()); } } @@ -4864,18 +4870,18 @@ nsFlexContainerFrame::ReflowFlexItem(nsPresContext* aPresContext, ReflowInput childReflowInput(aPresContext, aReflowInput, aItem.Frame(), availSize); - // Keep track of whether we've overriden the child's computed height - // and/or width, so we can set its resize flags accordingly. - bool didOverrideComputedWidth = false; - bool didOverrideComputedHeight = false; + // Keep track of whether we've overriden the child's computed ISize + // and/or BSize, so we can set its resize flags accordingly. + bool didOverrideComputedISize = false; + bool didOverrideComputedBSize = false; // Override computed main-size - if (aAxisTracker.IsMainAxisHorizontal()) { - childReflowInput.SetComputedWidth(aItem.GetMainSize()); - didOverrideComputedWidth = true; + if (aItem.IsInlineAxisMainAxis()) { + childReflowInput.SetComputedISize(aItem.GetMainSize()); + didOverrideComputedISize = true; } else { - childReflowInput.SetComputedHeight(aItem.GetMainSize()); - didOverrideComputedHeight = true; + childReflowInput.SetComputedBSize(aItem.GetMainSize()); + didOverrideComputedBSize = true; } // Override reflow state's computed cross-size if either: @@ -4889,16 +4895,24 @@ nsFlexContainerFrame::ReflowFlexItem(nsPresContext* aPresContext, // cheaper to just directly set it instead of setting a frame property.) if (aItem.IsStretched() || aItem.HasIntrinsicRatio()) { - if (aAxisTracker.IsCrossAxisHorizontal()) { - childReflowInput.SetComputedWidth(aItem.GetCrossSize()); - didOverrideComputedWidth = true; + if (aItem.IsInlineAxisCrossAxis()) { + childReflowInput.SetComputedISize(aItem.GetCrossSize()); + didOverrideComputedISize = true; } else { - childReflowInput.SetComputedHeight(aItem.GetCrossSize()); - didOverrideComputedHeight = true; + childReflowInput.SetComputedBSize(aItem.GetCrossSize()); + didOverrideComputedBSize = true; } } - if (aItem.IsStretched() && !aAxisTracker.IsCrossAxisHorizontal()) { - // If this item's height is stretched, it's a relative height. + if (aItem.IsStretched() && aItem.IsBlockAxisCrossAxis()) { + // This item is stretched (in the cross axis), and that axis is its block + // axis. That stretching effectively gives it a relative BSize. + // XXXdholbert This flag only makes a difference if we use the flex items' + // frame-state when deciding whether to reflow them -- and we don't, as of + // the changes in bug 851607. So this has no effect right now, but it might + // make a difference if we optimize to use dirty bits in the + // future. (Reftests flexbox-resizeviewport-1.xhtml and -2.xhtml are + // intended to catch any regressions here, if we end up relying on this bit + // & neglecting to set it.) aItem.Frame()->AddStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE); } @@ -4910,15 +4924,15 @@ nsFlexContainerFrame::ReflowFlexItem(nsPresContext* aPresContext, // earlier "measuring" reflow, then this upcoming reflow needs to be // treated as a resize. if (aItem.HadMeasuringReflow()) { - if (didOverrideComputedWidth) { - // (This is somewhat redundant, since the reflow state already - // sets mHResize whenever our computed width has changed since the - // previous reflow. Still, it's nice for symmetry, and it may become - // necessary once we support orthogonal flows.) - childReflowInput.SetHResize(true); + if (didOverrideComputedISize) { + // (This is somewhat redundant, since ReflowInput::InitResizeFlags() + // already calls SetIResize() whenever our computed ISize has changed + // since the previous reflow. Still, it's nice for symmetry, and it might + // be necessary for some edge cases.) + childReflowInput.SetIResize(true); } - if (didOverrideComputedHeight) { - childReflowInput.SetVResize(true); + if (didOverrideComputedBSize) { + childReflowInput.SetBResize(true); } } // NOTE: Be very careful about doing anything else with childReflowInput diff --git a/layout/generic/nsFlexContainerFrame.h b/layout/generic/nsFlexContainerFrame.h index 9fbd5ed038e1..07d65a275add 100644 --- a/layout/generic/nsFlexContainerFrame.h +++ b/layout/generic/nsFlexContainerFrame.h @@ -153,9 +153,6 @@ public: const ReflowInput& aChildRI, mozilla::LogicalAxis aLogicalAxis) const override; - // Flexbox-specific public methods - bool IsHorizontal(); - /** * Helper function to calculate packing space and initial offset of alignment * subjects in MainAxisPositionTracker() and CrossAxisPositionTracker() for diff --git a/layout/reftests/flexbox/flexbox-resizeviewport-2-helper.html b/layout/reftests/flexbox/flexbox-resizeviewport-2-helper.html new file mode 100644 index 000000000000..998bcbd527fc --- /dev/null +++ b/layout/reftests/flexbox/flexbox-resizeviewport-2-helper.html @@ -0,0 +1,32 @@ + + + + + + + +
    +
    +
    + + diff --git a/layout/reftests/flexbox/flexbox-resizeviewport-2-ref.xhtml b/layout/reftests/flexbox/flexbox-resizeviewport-2-ref.xhtml new file mode 100644 index 000000000000..9c76a32b8ddc --- /dev/null +++ b/layout/reftests/flexbox/flexbox-resizeviewport-2-ref.xhtml @@ -0,0 +1,22 @@ + + + + + + + +