Merge autoland to mozilla-central. a=merge

This commit is contained in:
Gurzau Raul 2018-07-04 19:27:44 +03:00
Родитель 05c61df014 d898125afb
Коммит c275df0863
141 изменённых файлов: 3016 добавлений и 1384 удалений

Просмотреть файл

@ -0,0 +1,155 @@
// Error url MUST be formatted like this:
// about:blocked?e=error_code&u=url(&o=1)?
// (o=1 when user overrides are allowed)
// Note that this file uses document.documentURI to get
// the URL (with the format from above). This is because
// document.location.href gets the current URI off the docshell,
// which is the URL displayed in the location bar, i.e.
// the URI that the user attempted to load.
function getErrorCode() {
var url = document.documentURI;
var error = url.search(/e\=/);
var duffUrl = url.search(/\&u\=/);
return decodeURIComponent(url.slice(error + 2, duffUrl));
}
function getURL() {
var url = document.documentURI;
var match = url.match(/&u=([^&]+)&/);
// match == null if not found; if so, return an empty string
// instead of what would turn out to be portions of the URI
if (!match)
return "";
url = decodeURIComponent(match[1]);
// If this is a view-source page, then get then real URI of the page
if (url.startsWith("view-source:"))
url = url.slice(12);
return url;
}
/**
* Check whether this warning page is overridable or not, in which case
* the "ignore the risk" suggestion in the error description
* should not be shown.
*/
function getOverride() {
var url = document.documentURI;
var match = url.match(/&o=1&/);
return !!match;
}
/**
* Attempt to get the hostname via document.location. Fail back
* to getURL so that we always return something meaningful.
*/
function getHostString() {
try {
return document.location.hostname;
} catch (e) {
return getURL();
}
}
function onClickSeeDetails() {
let details = document.getElementById("errorDescriptionContainer");
if (details.hidden) {
details.removeAttribute("hidden");
} else {
details.setAttribute("hidden", "true");
}
}
function initPage() {
var error = "";
switch (getErrorCode()) {
case "malwareBlocked" :
error = "malware";
break;
case "deceptiveBlocked" :
error = "phishing";
break;
case "unwantedBlocked" :
error = "unwanted";
break;
case "harmfulBlocked" :
error = "harmful";
break;
default:
return;
}
var el;
if (error !== "malware") {
el = document.getElementById("errorTitleText_malware");
el.remove();
el = document.getElementById("errorShortDescText_malware");
el.remove();
el = document.getElementById("errorLongDesc_malware");
el.remove();
}
if (error !== "phishing") {
el = document.getElementById("errorTitleText_phishing");
el.remove();
el = document.getElementById("errorShortDescText_phishing");
el.remove();
el = document.getElementById("errorLongDesc_phishing");
el.remove();
}
if (error !== "unwanted") {
el = document.getElementById("errorTitleText_unwanted");
el.remove();
el = document.getElementById("errorShortDescText_unwanted");
el.remove();
el = document.getElementById("errorLongDesc_unwanted");
el.remove();
}
if (error !== "harmful") {
el = document.getElementById("errorTitleText_harmful");
el.remove();
el = document.getElementById("errorShortDescText_harmful");
el.remove();
el = document.getElementById("errorLongDesc_harmful");
el.remove();
}
// Decide which version of the string should be visible in the error description.
if (getOverride()) {
document.getElementById(error + "_error_desc_no_override").remove();
} else {
document.getElementById(error + "_error_desc_override").remove();
}
// Set sitename in error details.
let sitenameElem = document.getElementById(error + "_sitename");
sitenameElem.setAttribute("class", "sitename");
sitenameElem.textContent = getHostString();
document.title = document.getElementById("errorTitleText_" + error).textContent;
// Inform the test harness that we're done loading the page.
var event = new CustomEvent("AboutBlockedLoaded",
{
bubbles: true,
detail: {
url: this.getURL(),
err: error
}
});
document.dispatchEvent(event);
}
let seeDetailsButton = document.getElementById("seeDetailsButton");
seeDetailsButton.addEventListener("click", onClickSeeDetails);
// Note: It is important to run the script this way, instead of using
// an onload handler. This is because error pages are loaded as
// LOAD_BACKGROUND, which means that onload handlers will not be executed.
initPage();

Просмотреть файл

@ -17,161 +17,10 @@
<html xmlns="http://www.w3.org/1999/xhtml" class="blacklist">
<head>
<meta http-equiv="Content-Security-Policy" content="default-src chrome:" />
<link rel="stylesheet" href="chrome://browser/skin/blockedSite.css" type="text/css" media="all" />
<link rel="icon" type="image/png" id="favicon" href="chrome://global/skin/icons/blacklist_favicon.png"/>
<script type="application/javascript"><![CDATA[
// Error url MUST be formatted like this:
// about:blocked?e=error_code&u=url(&o=1)?
// (o=1 when user overrides are allowed)
// Note that this file uses document.documentURI to get
// the URL (with the format from above). This is because
// document.location.href gets the current URI off the docshell,
// which is the URL displayed in the location bar, i.e.
// the URI that the user attempted to load.
function getErrorCode() {
var url = document.documentURI;
var error = url.search(/e\=/);
var duffUrl = url.search(/\&u\=/);
return decodeURIComponent(url.slice(error + 2, duffUrl));
}
function getURL() {
var url = document.documentURI;
var match = url.match(/&u=([^&]+)&/);
// match == null if not found; if so, return an empty string
// instead of what would turn out to be portions of the URI
if (!match)
return "";
url = decodeURIComponent(match[1]);
// If this is a view-source page, then get then real URI of the page
if (url.startsWith("view-source:"))
url = url.slice(12);
return url;
}
/**
* Check whether this warning page is overridable or not, in which case
* the "ignore the risk" suggestion in the error description
* should not be shown.
*/
function getOverride() {
var url = document.documentURI;
var match = url.match(/&o=1&/);
return !!match;
}
/**
* Attempt to get the hostname via document.location. Fail back
* to getURL so that we always return something meaningful.
*/
function getHostString() {
try {
return document.location.hostname;
} catch (e) {
return getURL();
}
}
function onClickSeeDetails() {
let details = document.getElementById("errorDescriptionContainer");
if (details.hidden) {
details.removeAttribute("hidden");
} else {
details.setAttribute("hidden", "true");
}
}
function initPage() {
var error = "";
switch (getErrorCode()) {
case "malwareBlocked" :
error = "malware";
break;
case "deceptiveBlocked" :
error = "phishing";
break;
case "unwantedBlocked" :
error = "unwanted";
break;
case "harmfulBlocked" :
error = "harmful";
break;
default:
return;
}
var el;
if (error !== "malware") {
el = document.getElementById("errorTitleText_malware");
el.remove();
el = document.getElementById("errorShortDescText_malware");
el.remove();
el = document.getElementById("errorLongDesc_malware");
el.remove();
}
if (error !== "phishing") {
el = document.getElementById("errorTitleText_phishing");
el.remove();
el = document.getElementById("errorShortDescText_phishing");
el.remove();
el = document.getElementById("errorLongDesc_phishing");
el.remove();
}
if (error !== "unwanted") {
el = document.getElementById("errorTitleText_unwanted");
el.remove();
el = document.getElementById("errorShortDescText_unwanted");
el.remove();
el = document.getElementById("errorLongDesc_unwanted");
el.remove();
}
if (error !== "harmful") {
el = document.getElementById("errorTitleText_harmful");
el.remove();
el = document.getElementById("errorShortDescText_harmful");
el.remove();
el = document.getElementById("errorLongDesc_harmful");
el.remove();
}
// Decide which version of the string should be visible in the error description.
if (getOverride()) {
document.getElementById(error + "_error_desc_no_override").remove();
} else {
document.getElementById(error + "_error_desc_override").remove();
}
// Set sitename in error details.
let sitenameElem = document.getElementById(error + "_sitename");
sitenameElem.setAttribute("class", "sitename");
sitenameElem.textContent = getHostString();
document.title = document.getElementById("errorTitleText_" + error).textContent;
// Inform the test harness that we're done loading the page.
var event = new CustomEvent("AboutBlockedLoaded",
{
bubbles: true,
detail: {
url: this.getURL(),
err: error
}
});
document.dispatchEvent(event);
}
]]></script>
</head>
<body dir="&locale.dir;">
<div id="errorPageContainer" class="container">
@ -202,7 +51,7 @@
<div id="buttons" class="button-container">
<!-- Commands handled in browser.js -->
<button id="goBackButton">&safeb.palm.accept.label2;</button>
<button id="seeDetailsButton" onclick="onClickSeeDetails();">&safeb.palm.seedetails.label;</button>
<button id="seeDetailsButton">&safeb.palm.seedetails.label;</button>
</div>
</div>
<div id="errorDescriptionContainer" hidden="true">
@ -228,13 +77,6 @@
</div>
</div>
</div>
<!--
- Note: It is important to run the script this way, instead of using
- an onload handler. This is because error pages are loaded as
- LOAD_BACKGROUND, which means that onload handlers will not be executed.
-->
<script type="application/javascript">
initPage();
</script>
</body>
<script type="application/javascript" src="chrome://browser/content/blockedSite.js"/>
</html>

Просмотреть файл

@ -387,21 +387,20 @@ var StarUI = {
var PlacesCommandHook = {
/**
* Adds a bookmark to the page loaded in the given browser.
* Adds a bookmark to the page loaded in the current browser.
*
* @param aBrowser
* a <browser> element.
* @param [optional] aUrl
* Option to provide a URL to bookmark rather than the current page
* @param [optional] aTitle
* Option to provide a title for a bookmark to use rather than the
* getting the current page's title
*/
async bookmarkPage(aBrowser, aUrl = null, aTitle = null) {
async bookmarkPage(aUrl = null, aTitle = null) {
let browser = gBrowser.selectedBrowser;
// If aUrl is provided, we want to bookmark that url rather than the
// the current page
let isCurrentBrowser = !aUrl;
let url = aUrl ? new URL(aUrl) : new URL(aBrowser.currentURI.spec);
let url = aUrl ? new URL(aUrl) : new URL(browser.currentURI.spec);
let info = await PlacesUtils.bookmarks.fetch({ url });
let isNewBookmark = !info;
let showEditUI = !isNewBookmark || StarUI.showForNewBookmarks;
@ -412,21 +411,21 @@ var PlacesCommandHook = {
let charset = null;
let isErrorPage = false;
if (!aUrl && aBrowser.documentURI) {
isErrorPage = /^about:(neterror|certerror|blocked)/.test(aBrowser.documentURI.spec);
if (!aUrl && browser.documentURI) {
isErrorPage = /^about:(neterror|certerror|blocked)/.test(browser.documentURI.spec);
}
try {
if (isErrorPage) {
let entry = await PlacesUtils.history.fetch(aBrowser.currentURI);
let entry = await PlacesUtils.history.fetch(browser.currentURI);
if (entry) {
info.title = entry.title;
}
} else {
info.title = aTitle || aBrowser.contentTitle;
info.title = aTitle || browser.contentTitle;
}
info.title = info.title || url.href;
charset = aUrl ? null : aBrowser.characterSet;
charset = aUrl ? null : browser.characterSet;
} catch (e) {
Cu.reportError(e);
}
@ -441,7 +440,7 @@ var PlacesCommandHook = {
info.guid = await PlacesTransactions.NewBookmark(info).transact();
// Set the character-set
if (charset && !PrivateBrowsingUtils.isBrowserPrivate(aBrowser))
if (charset && !PrivateBrowsingUtils.isBrowserPrivate(browser))
PlacesUtils.setCharsetForURI(makeURI(url.href), charset);
}
@ -1601,7 +1600,7 @@ var BookmarkingUI = {
}, { once: true });
this.star.setAttribute("animate", "true");
}
PlacesCommandHook.bookmarkPage(gBrowser.selectedBrowser);
PlacesCommandHook.bookmarkPage();
}
},

Просмотреть файл

@ -57,7 +57,7 @@
#endif
<!-- work-around bug 392512 -->
<command id="Browser:AddBookmarkAs"
oncommand="PlacesCommandHook.bookmarkPage(gBrowser.selectedBrowser);"/>
oncommand="PlacesCommandHook.bookmarkPage();"/>
<!-- The command disabled state must be manually updated through
PlacesCommandHook.updateBookmarkAllTabsCommand() -->
<command id="Browser:BookmarkAllTabs"

Просмотреть файл

@ -1432,7 +1432,7 @@ nsContextMenu.prototype = {
bookmarkThisPage: function CM_bookmarkThisPage() {
window.top.PlacesCommandHook
.bookmarkPage(this.browser)
.bookmarkPage()
.catch(Cu.reportError);
},

Просмотреть файл

@ -98,7 +98,7 @@ async function checkBookmarkedPageTitle(url, default_title, overridden_title) {
// Here we test that if we provide a url and a title to bookmark, it will use the
// title provided rather than the one provided by the current page
PlacesCommandHook.bookmarkPage(gBrowser.selectedBrowser, url, overridden_title);
PlacesCommandHook.bookmarkPage(url, overridden_title);
await promiseBookmark;
let bookmark = await PlacesUtils.bookmarks.fetch({url});
@ -121,7 +121,7 @@ async function checkBookmark(url, expected_title) {
let promiseBookmark = PlacesTestUtils.waitForNotification("onItemAdded",
(id, parentId, index, type, itemUrl) => itemUrl.equals(gBrowser.selectedBrowser.currentURI));
PlacesCommandHook.bookmarkPage(gBrowser.selectedBrowser);
PlacesCommandHook.bookmarkPage();
await promiseBookmark;
let bookmark = await PlacesUtils.bookmarks.fetch({url});

Просмотреть файл

@ -112,6 +112,7 @@ browser.jar:
* content/browser/license.html (/toolkit/content/license.html)
% override chrome://global/content/license.html chrome://browser/content/license.html
content/browser/blockedSite.xhtml (content/blockedSite.xhtml)
content/browser/blockedSite.js (content/blockedSite.js)
% override chrome://global/content/netError.xhtml chrome://browser/content/aboutNetError.xhtml

Просмотреть файл

@ -154,37 +154,39 @@ class FaviconLoad {
// This stuff isn't available after onStopRequest returns (so don't start
// any async operations before this!).
if (this.channel instanceof Ci.nsICacheInfoChannel) {
expiration = Math.min(this.channel.cacheTokenExpirationTime * 1000, expiration);
}
let type = this.channel.contentType;
let blob = new Blob(this.buffers, { type });
if (type != "image/svg+xml") {
let octets = await promiseBlobAsOctets(blob);
let sniffer = Cc["@mozilla.org/image/loader;1"].
createInstance(Ci.nsIContentSniffer);
try {
type = sniffer.getMIMETypeFromContent(this.channel, octets, octets.length);
expiration = Math.min(this.channel.cacheTokenExpirationTime * 1000, expiration);
} catch (e) {
this._deferred.reject(e);
return;
// Ignore failures to get the expiration time.
}
if (!type) {
this._deferred.reject(Components.Exception(`Favicon at "${this.icon.iconUri.spec}" did not match a known mimetype.`, Cr.NS_ERROR_FAILURE));
return;
}
blob = blob.slice(0, blob.size, type);
}
let dataURL = await promiseBlobAsDataURL(blob);
try {
let type = this.channel.contentType;
let blob = new Blob(this.buffers, { type });
this._deferred.resolve({
expiration,
dataURL,
});
if (type != "image/svg+xml") {
let octets = await promiseBlobAsOctets(blob);
let sniffer = Cc["@mozilla.org/image/loader;1"].
createInstance(Ci.nsIContentSniffer);
type = sniffer.getMIMETypeFromContent(this.channel, octets, octets.length);
if (!type) {
throw Components.Exception(`Favicon at "${this.icon.iconUri.spec}" did not match a known mimetype.`, Cr.NS_ERROR_FAILURE);
}
blob = blob.slice(0, blob.size, type);
}
let dataURL = await promiseBlobAsDataURL(blob);
this._deferred.resolve({
expiration,
dataURL,
});
} catch (e) {
this._deferred.reject(e);
}
}
getInterface(iid) {

Просмотреть файл

@ -267,5 +267,6 @@ AC_SUBST(HAVE_ARM_NEON)
AC_SUBST(BUILD_ARM_NEON)
AC_SUBST(ARM_ARCH)
AC_SUBST_LIST(NEON_FLAGS)
AC_SUBST(MOZ_FPU)
])

Просмотреть файл

@ -847,7 +847,12 @@ ifdef CARGO_INCREMENTAL
cargo_incremental := CARGO_INCREMENTAL=$(CARGO_INCREMENTAL)
endif
rustflags_override = RUSTFLAGS='$(MOZ_RUST_DEFAULT_FLAGS) $(RUSTFLAGS)'
rustflags_neon =
ifeq (neon,$(MOZ_FPU))
rustflags_neon += -C target_feature=+neon
endif
rustflags_override = RUSTFLAGS='$(MOZ_RUST_DEFAULT_FLAGS) $(RUSTFLAGS) $(rustflags_neon)'
ifdef MOZ_MSVCBITS
# If we are building a MozillaBuild shell, we want to clear out the

Просмотреть файл

@ -0,0 +1,197 @@
/* 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 { PureComponent } = require("devtools/client/shared/vendor/react");
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
const { createFactory } = require("devtools/client/shared/vendor/react");
const MenuItem = createFactory(
require("devtools/client/shared/components/menu/MenuItem")
);
const MenuList = createFactory(
require("devtools/client/shared/components/menu/MenuList")
);
const dom = require("devtools/client/shared/vendor/react-dom-factories");
const { hr } = dom;
const { openDocLink } = require("devtools/client/shared/link");
class MeatballMenu extends PureComponent {
static get propTypes() {
return {
// The id of the currently selected tool, e.g. "inspector"
currentToolId: PropTypes.string,
// List of possible docking options.
hostTypes: PropTypes.arrayOf(
PropTypes.shape({
position: PropTypes.string.isRequired,
switchHost: PropTypes.func.isRequired,
})
),
// Current docking type. Typically one of the position values in
// |hostTypes| but this is not always the case (e.g. when it is "custom").
currentHostType: PropTypes.string,
// Is the split console currently visible?
isSplitConsoleActive: PropTypes.bool,
// Are we disabling the behavior where pop-ups are automatically closed
// when clicking outside them?
//
// This is a tri-state value that may be true/false or undefined where
// undefined means that the option is not relevant in this context
// (i.e. we're not in a browser toolbox).
disableAutohide: PropTypes.bool,
// Function to turn the options panel on / off.
toggleOptions: PropTypes.func.isRequired,
// Function to turn the split console on / off.
toggleSplitConsole: PropTypes.func,
// Function to turn the disable pop-up autohide behavior on / off.
toggleNoAutohide: PropTypes.func,
// Localization interface.
L10N: PropTypes.object.isRequired,
// Callback function that will be invoked any time the component contents
// update in such a way that its bounding box might change.
onResize: PropTypes.func,
};
}
componentDidUpdate(prevProps) {
if (!this.props.onResize) {
return;
}
// We are only expecting the following kinds of dynamic changes when a popup
// is showing:
//
// - The "Disable pop-up autohide" menu item being added after the Browser
// Toolbox is connected.
// - The split console label changing between "Show split console" and "Hide
// split console".
// - The "Show/Hide split console" entry being added removed or removed.
//
// The latter two cases are only likely to be noticed when "Disable pop-up
// autohide" is active, but for completeness we handle them here.
const didChange =
typeof this.props.disableAutohide !== typeof prevProps.disableAutohide ||
this.props.currentToolId !== prevProps.currentToolId ||
this.props.isSplitConsoleActive !== prevProps.isSplitConsoleActive;
if (didChange) {
this.props.onResize();
}
}
render() {
const items = [];
// Dock options
for (const hostType of this.props.hostTypes) {
const l10nkey =
hostType.position === "window" ? "separateWindow" : hostType.position;
items.push(
MenuItem({
id: `toolbox-meatball-menu-dock-${hostType.position}`,
label: this.props.L10N.getStr(
`toolbox.meatballMenu.dock.${l10nkey}.label`
),
onClick: () => hostType.switchHost(),
checked: hostType.position === this.props.currentHostType,
className: "iconic",
})
);
}
if (items.length) {
items.push(hr());
}
// Split console
if (this.props.currentToolId !== "webconsole") {
items.push(
MenuItem({
id: "toolbox-meatball-menu-splitconsole",
label: this.props.L10N.getStr(
`toolbox.meatballMenu.${
this.props.isSplitConsoleActive ? "hideconsole" : "splitconsole"
}.label`
),
accelerator: "Esc",
onClick: this.props.toggleSplitConsole,
className: "iconic",
})
);
}
// Disable pop-up autohide
//
// If |disableAutohide| is undefined, it means this feature is not available
// in this context.
if (typeof this.props.disableAutohide !== "undefined") {
items.push(
MenuItem({
id: "toolbox-meatball-menu-noautohide",
label: this.props.L10N.getStr(
"toolbox.meatballMenu.noautohide.label"
),
type: "checkbox",
checked: this.props.disableAutohide,
onClick: this.props.toggleNoAutohide,
className: "iconic",
})
);
}
// Settings
items.push(
MenuItem({
id: "toolbox-meatball-menu-settings",
label: this.props.L10N.getStr("toolbox.meatballMenu.settings.label"),
accelerator: this.props.L10N.getStr("toolbox.help.key"),
onClick: () => this.props.toggleOptions(),
className: "iconic",
})
);
items.push(hr());
// Getting started
items.push(
MenuItem({
id: "toolbox-meatball-menu-documentation",
label: this.props.L10N.getStr(
"toolbox.meatballMenu.documentation.label"
),
onClick: () => {
openDocLink(
"https://developer.mozilla.org/docs/Tools?utm_source=devtools&utm_medium=tabbar-menu"
);
},
})
);
// Give feedback
items.push(
MenuItem({
id: "toolbox-meatball-menu-community",
label: this.props.L10N.getStr("toolbox.meatballMenu.community.label"),
onClick: () => {
openDocLink(
"https://discourse.mozilla.org/c/devtools?utm_source=devtools&utm_medium=tabbar-menu"
);
},
})
);
return MenuList({ id: "toolbox-meatball-menu" }, items);
}
}
module.exports = MeatballMenu;

Просмотреть файл

@ -7,10 +7,9 @@ const { Component, createFactory } = require("devtools/client/shared/vendor/reac
const dom = require("devtools/client/shared/vendor/react-dom-factories");
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
const {div, button} = dom;
const {openDocLink} = require("devtools/client/shared/link");
const Menu = require("devtools/client/framework/menu");
const MenuItem = require("devtools/client/framework/menu-item");
const MeatballMenu = createFactory(require("devtools/client/framework/components/MeatballMenu"));
const MenuButton = createFactory(require("devtools/client/shared/components/menu/MenuButton"));
const ToolboxTabs = createFactory(require("devtools/client/framework/components/ToolboxTabs"));
/**
@ -42,6 +41,9 @@ class ToolboxToolbar extends Component {
// Current docking type. Typically one of the position values in
// |hostTypes| but this is not always the case (e.g. when it is "custom").
currentHostType: PropTypes.string,
// Are docking options enabled? They are not enabled in certain situations
// like when they are in the WebIDE.
areDockOptionsEnabled: PropTypes.bool,
// Do we need to add UI for closing the toolbox? We don't when the
// toolbox is undocked, for example.
canCloseToolbox: PropTypes.bool,
@ -54,8 +56,6 @@ class ToolboxToolbar extends Component {
// undefined means that the option is not relevant in this context
// (i.e. we're not in a browser toolbox).
disableAutohide: PropTypes.bool,
// Function to select a tool based on its id.
selectTool: PropTypes.func,
// Function to turn the options panel on / off.
toggleOptions: PropTypes.func.isRequired,
// Function to turn the split console on / off.
@ -75,10 +75,10 @@ class ToolboxToolbar extends Component {
toolbox: PropTypes.object,
// Call back function to detect tabs order updated.
onTabsOrderUpdated: PropTypes.func.isRequired,
// Count of visible toolbox buttons which is used by ToolboxTabs component to
// recognize that the visibility of toolbox buttons were changed. Because in the
// component we cannot compare the visibility since the button definition instance
// in toolboxButtons will be unchanged.
// Count of visible toolbox buttons which is used by ToolboxTabs component
// to recognize that the visibility of toolbox buttons were changed.
// Because in the component we cannot compare the visibility since the
// button definition instance in toolboxButtons will be unchanged.
visibleToolboxButtonCount: PropTypes.number,
};
}
@ -108,7 +108,7 @@ class ToolboxToolbar extends Component {
startButtons,
ToolboxTabs(this.props),
endButtons,
renderToolboxControls(this.props)
renderToolboxControls(this.props, this.refs)
)
)
: div({ className: classnames.join(" ") });
@ -223,70 +223,84 @@ function renderSeparator() {
/**
* Render the toolbox control buttons. The following props are expected:
*
* @param {string} focusedButton
* @param {string} props.focusedButton
* The id of the focused button.
* @param {Object[]} hostTypes
* @param {string} props.currentToolId
* The id of the currently selected tool, e.g. "inspector".
* @param {Object[]} props.hostTypes
* Array of host type objects.
* @param {string} hostTypes[].position
* @param {string} props.hostTypes[].position
* Position name.
* @param {Function} hostTypes[].switchHost
* @param {Function} props.hostTypes[].switchHost
* Function to switch the host.
* @param {string} currentHostType
* @param {string} props.currentHostType
* The current docking configuration.
* @param {boolean} areDockOptionsEnabled
* @param {boolean} props.areDockOptionsEnabled
* They are not enabled in certain situations like when they are in the
* WebIDE.
* @param {boolean} canCloseToolbox
* @param {boolean} props.canCloseToolbox
* Do we need to add UI for closing the toolbox? We don't when the
* toolbox is undocked, for example.
* @param {boolean} isSplitConsoleActive
* @param {boolean} props.isSplitConsoleActive
* Is the split console currently visible?
* toolbox is undocked, for example.
* @param {boolean|undefined} disableAutohide
* @param {boolean|undefined} props.disableAutohide
* Are we disabling the behavior where pop-ups are automatically
* closed when clicking outside them?
* (Only defined for the browser toolbox.)
* @param {Function} selectTool
* @param {Function} props.selectTool
* Function to select a tool based on its id.
* @param {Function} toggleOptions
* @param {Function} props.toggleOptions
* Function to turn the options panel on / off.
* @param {Function} toggleSplitConsole
* @param {Function} props.toggleSplitConsole
* Function to turn the split console on / off.
* @param {Function} toggleNoAutohide
* @param {Function} props.toggleNoAutohide
* Function to turn the disable pop-up autohide behavior on / off.
* @param {Function} closeToolbox
* @param {Function} props.closeToolbox
* Completely close the toolbox.
* @param {Function} focusButton
* @param {Function} props.focusButton
* Keep a record of the currently focused button.
* @param {Object} L10N
* @param {Object} props.L10N
* Localization interface.
* @param {Object} props.toolbox
* The devtools toolbox. Used by the MenuButton component to display
* the menu popup.
* @param {Object} refs
* The components refs object. Used to keep a reference to the MenuButton
* for the meatball menu so that we can tell it to resize its contents
* when they change.
*/
function renderToolboxControls(props) {
function renderToolboxControls(props, refs) {
const {
focusedButton,
canCloseToolbox,
closeToolbox,
hostTypes,
focusButton,
L10N,
areDockOptionsEnabled,
canCloseToolbox,
toolbox,
} = props;
const meatballMenuButtonId = "toolbox-meatball-menu-button";
const meatballMenuButton = button({
id: meatballMenuButtonId,
onFocus: () => focusButton(meatballMenuButtonId),
className: "devtools-button",
title: L10N.getStr("toolbox.meatballMenu.button.tooltip"),
onClick: evt => {
showMeatballMenu(evt.target, {
...props,
hostTypes: areDockOptionsEnabled ? hostTypes : [],
});
const meatballMenuButton = MenuButton(
{
id: meatballMenuButtonId,
menuId: meatballMenuButtonId + "-panel",
doc: toolbox.doc,
onFocus: () => focusButton(meatballMenuButtonId),
className: "devtools-button",
title: L10N.getStr("toolbox.meatballMenu.button.tooltip"),
tabIndex: focusedButton === meatballMenuButtonId ? "0" : "-1",
ref: "meatballMenuButton",
},
tabIndex: focusedButton === meatballMenuButtonId ? "0" : "-1",
});
MeatballMenu({
...props,
hostTypes: props.areDockOptionsEnabled ? props.hostTypes : [],
onResize: () => {
refs.meatballMenuButton.resizeContent();
},
})
);
const closeButtonId = "toolbox-close";
@ -308,149 +322,3 @@ function renderToolboxControls(props) {
closeButton
);
}
/**
* Display the "..." menu (affectionately known as the meatball menu).
*
* @param {Object} menuButton
* The <button> element from which the menu should pop out. The geometry
* of this element is used to position the menu.
* @param {Object} props
* Properties as described below.
* @param {string} props.currentToolId
* The id of the currently selected tool.
* @param {Object[]} props.hostTypes
* Array of host type objects.
* This array will be empty if we shouldn't shouldn't show any dock
* options.
* @param {string} props.hostTypes[].position
* Position name.
* @param {Function} props.hostTypes[].switchHost
* Function to switch the host.
* @param {string} props.currentHostType
* The current docking configuration.
* @param {boolean} isSplitConsoleActive
* Is the split console currently visible?
* @param {boolean|undefined} disableAutohide
* Are we disabling the behavior where pop-ups are automatically
* closed when clicking outside them.
* (Only defined for the browser toolbox.)
* @param {Function} selectTool
* Function to select a tool based on its id.
* @param {Function} toggleOptions
* Function to turn the options panel on / off.
* @param {Function} toggleSplitConsole
* Function to turn the split console on / off.
* @param {Function} toggleNoAutohide
* Function to turn the disable pop-up autohide behavior on / off.
* @param {Object} props.L10N
* Localization interface.
* @param {Object} props.toolbox
* The devtools toolbox. Used by the Menu component to determine which
* document to use.
*/
function showMeatballMenu(
menuButton,
{
currentToolId,
hostTypes,
currentHostType,
isSplitConsoleActive,
disableAutohide,
toggleOptions,
toggleSplitConsole,
toggleNoAutohide,
L10N,
toolbox,
}
) {
const menu = new Menu({ id: "toolbox-meatball-menu" });
// Dock options
for (const hostType of hostTypes) {
const l10nkey =
hostType.position === "window"
? "separateWindow"
: hostType.position;
menu.append(
new MenuItem({
id: `toolbox-meatball-menu-dock-${hostType.position}`,
label: L10N.getStr(`toolbox.meatballMenu.dock.${l10nkey}.label`),
click: () => hostType.switchHost(),
type: "checkbox",
checked: hostType.position === currentHostType,
})
);
}
if (menu.items.length) {
menu.append(new MenuItem({ type: "separator" }));
}
// Split console
if (currentToolId !== "webconsole") {
menu.append(new MenuItem({
id: "toolbox-meatball-menu-splitconsole",
label: L10N.getStr(
`toolbox.meatballMenu.${
isSplitConsoleActive ? "hideconsole" : "splitconsole"
}.label`
),
accelerator: "Esc",
click: toggleSplitConsole,
}));
}
// Disable pop-up autohide
//
// If |disableAutohide| is undefined, it means this feature is not available
// in this context.
if (typeof disableAutohide !== "undefined") {
menu.append(new MenuItem({
id: "toolbox-meatball-menu-noautohide",
label: L10N.getStr("toolbox.meatballMenu.noautohide.label"),
type: "checkbox",
checked: disableAutohide,
click: toggleNoAutohide,
}));
}
// Settings
menu.append(new MenuItem({
id: "toolbox-meatball-menu-settings",
label: L10N.getStr("toolbox.meatballMenu.settings.label"),
accelerator: L10N.getStr("toolbox.help.key"),
click: () => toggleOptions(),
}));
if (menu.items.length) {
menu.append(new MenuItem({ type: "separator" }));
}
// Getting started
menu.append(new MenuItem({
id: "toolbox-meatball-menu-documentation",
label: L10N.getStr("toolbox.meatballMenu.documentation.label"),
click: () => {
openDocLink(
"https://developer.mozilla.org/docs/Tools?utm_source=devtools&utm_medium=tabbar-menu");
},
}));
// Give feedback
menu.append(new MenuItem({
id: "toolbox-meatball-menu-community",
label: L10N.getStr("toolbox.meatballMenu.community.label"),
click: () => {
openDocLink(
"https://discourse.mozilla.org/c/devtools?utm_source=devtools&utm_medium=tabbar-menu");
},
}));
const rect = menuButton.getBoundingClientRect();
const screenX = menuButton.ownerDocument.defaultView.mozInnerScreenX;
const screenY = menuButton.ownerDocument.defaultView.mozInnerScreenY;
// Display the popup below the button.
menu.popupWithZoom(rect.left + screenX, rect.bottom + screenY, toolbox);
}

Просмотреть файл

@ -6,6 +6,7 @@
DevToolsModules(
'MeatballMenu.js',
'ToolboxController.js',
'ToolboxTab.js',
'ToolboxTabs.js',

Просмотреть файл

@ -6,8 +6,8 @@
"use strict";
const Services = require("Services");
const EventEmitter = require("devtools/shared/event-emitter");
const { getCurrentZoom } = require("devtools/shared/layout/utils");
/**
* A partial implementation of the Menu API provided by electron:
@ -62,10 +62,7 @@ Menu.prototype.insert = function(pos, menuItem) {
* @param Toolbox toolbox
*/
Menu.prototype.popupWithZoom = function(x, y, toolbox) {
let zoom = parseFloat(Services.prefs.getCharPref("devtools.toolbox.zoomValue"));
if (!zoom || isNaN(zoom)) {
zoom = 1.0;
}
const zoom = getCurrentZoom(toolbox.doc);
this.popup(x * zoom, y * zoom, toolbox);
};

Просмотреть файл

@ -7,17 +7,18 @@
const {Toolbox} = require("devtools/client/framework/toolbox");
// Use simple URL in order to prevent displacing the left positon of frames menu.
// Use a simple URL in order to prevent displacing the left position of the
// frames menu.
const TEST_URL = "data:text/html;charset=utf-8,<iframe/>";
add_task(async function() {
registerCleanupFunction(async function() {
Services.prefs.clearUserPref("devtools.toolbox.zoomValue");
});
const zoom = 1.5;
const zoom = 1.4;
Services.prefs.setCharPref("devtools.toolbox.zoomValue", zoom.toString(10));
info("Load iframe page for checking the frame menu with x1.5 zoom.");
info("Load iframe page for checking the frame menu with x1.4 zoom.");
await addTab(TEST_URL);
const target = TargetFactory.forTab(gBrowser.selectedTab);
const toolbox = await gDevTools.showToolbox(target,
@ -30,16 +31,18 @@ add_task(async function() {
const windowUtils = toolbox.win.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
info("Waiting for the toolbox window will to be rendered with zoom x1.5");
info("Waiting for the toolbox window will to be rendered with zoom x1.4");
await waitUntil(() => {
return parseFloat(windowUtils.fullZoom.toFixed(1)) === parseFloat(zoom);
return parseFloat(windowUtils.fullZoom.toFixed(1)) === zoom;
});
info("Resizing and moving the toolbox window in order to display the chevron menu.");
// If window is displayed bottom of screen, menu might be displayed above of button.
// If the window is displayed bottom of screen, the menu might be displayed
// above the button so move it to the top of the screen first.
hostWindow.moveTo(0, 0);
// This size will display inspector's tabs menu button and chevron menu button of toolbox.
// Shrink the width of the window such that the inspector's tab menu button
// and chevron button are visible.
const prevTabs = toolbox.doc.querySelectorAll(".devtools-tab").length;
hostWindow.resizeTo(400, hostWindow.outerHeight);
await waitUntil(() => {
@ -58,15 +61,35 @@ add_task(async function() {
inspector.panelDoc.querySelector(".all-tabs-menu")];
for (const menu of menuList) {
const [btnRect, menuRect] = await getButtonAndMenuRects(toolbox, menu);
const { buttonBounds, menuType, menuBounds, arrowBounds } =
await getButtonAndMenuInfo(toolbox.doc, menu);
// Allow rounded error and platform offset value.
// horizontal : eIntID_ContextMenuOffsetHorizontal of GTK and Windows uses 2.
// vertical: eIntID_ContextMenuOffsetVertical of macOS uses -6.
const xDelta = Math.abs(menuRect.left - btnRect.left);
const yDelta = Math.abs(menuRect.top - btnRect.bottom);
ok(xDelta < 2, "xDelta is lower than 2: " + xDelta + ". #" + menu.id);
ok(yDelta < 6, "yDelta is lower than 6: " + yDelta + ". #" + menu.id);
switch (menuType) {
case "native":
{
// Allow rounded error and platform offset value.
// horizontal : eIntID_ContextMenuOffsetHorizontal of GTK and Windows
// uses 2.
// vertical: eIntID_ContextMenuOffsetVertical of macOS uses -6.
const xDelta = Math.abs(menuBounds.left - buttonBounds.left);
const yDelta = Math.abs(menuBounds.top - buttonBounds.bottom);
ok(xDelta < 2, "xDelta is lower than 2: " + xDelta + ". #" + menu.id);
ok(yDelta < 6, "yDelta is lower than 6: " + yDelta + ". #" + menu.id);
}
break;
case "doorhanger":
{
// Calculate the center of the button and center of the arrow and
// check they align.
const buttonCenter = buttonBounds.left + buttonBounds.width / 2;
const arrowCenter = arrowBounds.left + arrowBounds.width / 2;
const delta = Math.abs(arrowCenter - buttonCenter);
ok(delta < 1, "Center of arrow is within 1px of button center" +
` (delta: ${delta})`);
}
break;
}
}
const onResize = once(hostWindow, "resize");
@ -78,30 +101,62 @@ add_task(async function() {
});
/**
* Getting the rectangle of the menu button and popup menu.
* - The menu button rectangle will be calculated from specified button.
* - The popup menu rectangle will be calculated from displayed popup menu
* which clicking the specified button.
* Get the bounds of a menu button and its popup panel. The popup panel is
* measured by clicking the menu button and looking for its panel (and then
* hiding it again).
*
* @param {Object} doc
* The toolbox document to query.
* @param {Object} menuButton
* The button whose size and popup size we should measure.
* @return {Object}
* An object with the following properties:
* - buttonBounds {DOMRect} Bounds of the button.
* - menuType {string} Type of the menu, "native" or "doorhanger".
* - menuBounds {DOMRect} Bounds of the menu panel.
* - arrowBounds {DOMRect|null} Bounds of the arrow. Only set when
* menuType is "doorhanger", null otherwise.
*/
async function getButtonAndMenuRects(toolbox, menuButton) {
async function getButtonAndMenuInfo(doc, menuButton) {
info("Show popup menu with click event.");
menuButton.click();
const popupset = toolbox.doc.querySelector("popupset");
let menuPopup;
await waitUntil(() => {
menuPopup = popupset.querySelector("menupopup[menu-api=\"true\"]");
return !!menuPopup && menuPopup.state === "open";
});
let menuType;
let arrowBounds = null;
if (menuButton.hasAttribute("aria-controls")) {
menuType = "doorhanger";
menuPopup = doc.getElementById(menuButton.getAttribute("aria-controls"));
await waitUntil(() => menuPopup.classList.contains("tooltip-visible"));
} else {
menuType = "native";
const popupset = doc.querySelector("popupset");
await waitUntil(() => {
menuPopup = popupset.querySelector("menupopup[menu-api=\"true\"]");
return !!menuPopup && menuPopup.state === "open";
});
}
ok(menuPopup, "Menu popup is displayed.");
const btnRect = menuButton.getBoxQuads({relativeTo: toolbox.doc})[0].getBounds();
const menuRect = menuPopup.getBoxQuads({relativeTo: toolbox.doc})[0].getBounds();
const buttonBounds = menuButton
.getBoxQuads({ relativeTo: doc })[0]
.getBounds();
const menuBounds = menuPopup.getBoxQuads({ relativeTo: doc })[0].getBounds();
if (menuType === "doorhanger") {
const arrow = menuPopup.querySelector(".tooltip-arrow");
arrowBounds = arrow.getBoxQuads({ relativeTo: doc })[0].getBounds();
}
info("Hide popup menu.");
const onPopupHidden = once(menuPopup, "popuphidden");
menuPopup.hidePopup();
await onPopupHidden;
if (menuType === "doorhanger") {
EventUtils.sendKey("Escape", doc.defaultView);
await waitUntil(() => !menuPopup.classList.contains("tooltip-visible"));
} else {
const popupHidden = once(menuPopup, "popuphidden");
menuPopup.hidePopup();
await popupHidden;
}
return [btnRect, menuRect];
return { buttonBounds, menuType, menuBounds, arrowBounds };
}

Просмотреть файл

@ -1094,7 +1094,7 @@ MarkupView.prototype = {
|| type === "events" || type === "pseudoClassLock") {
container.update();
} else if (type === "childList" || type === "nativeAnonymousChildList"
|| type === "slotchange") {
|| type === "slotchange" || type === "shadowRootAttached") {
container.childrenDirty = true;
// Update the children to take care of changes in the markup view DOM
// and update container (and its subtree) DOM tree depth level for

Просмотреть файл

@ -168,6 +168,7 @@ skip-if = e10s # Bug 1036409 - The last selected node isn't reselected
[browser_markup_shadowdom_clickreveal.js]
[browser_markup_shadowdom_clickreveal_scroll.js]
[browser_markup_shadowdom_delete.js]
[browser_markup_shadowdom_dynamic.js]
[browser_markup_shadowdom_maxchildren.js]
[browser_markup_shadowdom_mutations_shadow.js]
[browser_markup_shadowdom_navigation.js]

Просмотреть файл

@ -0,0 +1,154 @@
/* 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 the inspector is correctly updated when shadow roots are attached to
// components after displaying them in the markup view.
const TEST_URL = `data:text/html;charset=utf-8,` + encodeURIComponent(`
<div id="root">
<test-component>
<div slot="slot1" id="el1">slot1-1</div>
<div slot="slot1" id="el2">slot1-2</div>
</test-component>
<inline-component>inline text</inline-component>
</div>
<script>
'use strict';
window.attachTestComponent = function () {
customElements.define('test-component', class extends HTMLElement {
constructor() {
super();
let shadowRoot = this.attachShadow({mode: 'open'});
shadowRoot.innerHTML = \`<div id="slot1-container">
<slot name="slot1"></slot>
</div>
<other-component>
<div slot="slot2">slot2-1</div>
</other-component>\`;
}
});
}
window.attachOtherComponent = function () {
customElements.define('other-component', class extends HTMLElement {
constructor() {
super();
let shadowRoot = this.attachShadow({mode: 'open'});
shadowRoot.innerHTML = \`<div id="slot2-container">
<slot name="slot2"></slot>
<div>some-other-node</div>
</div>\`;
}
});
}
window.attachInlineComponent = function () {
customElements.define('inline-component', class extends HTMLElement {
constructor() {
super();
let shadowRoot = this.attachShadow({mode: 'open'});
shadowRoot.innerHTML = \`<div id="inline-component-content">
<div>some-inline-content</div>
</div>\`;
}
});
}
</script>`);
add_task(async function() {
await enableWebComponents();
const {inspector} = await openInspectorForURL(TEST_URL);
const tree = `
div
test-component
slot1-1
slot1-2
inline text`;
await assertMarkupViewAsTree(tree, "#root", inspector);
info("Attach a shadow root to test-component");
let mutated = waitForMutation(inspector, "shadowRootAttached");
ContentTask.spawn(gBrowser.selectedBrowser, {}, function() {
content.wrappedJSObject.attachTestComponent();
});
await mutated;
const treeAfterTestAttach = `
div
test-component
#shadow-root
slot1-container
slot
div!slotted
div!slotted
other-component
slot2-1
slot1-1
slot1-2
inline text`;
await assertMarkupViewAsTree(treeAfterTestAttach, "#root", inspector);
info("Attach a shadow root to other-component, nested in test-component");
mutated = waitForMutation(inspector, "shadowRootAttached");
ContentTask.spawn(gBrowser.selectedBrowser, {}, function() {
content.wrappedJSObject.attachOtherComponent();
});
await mutated;
const treeAfterOtherAttach = `
div
test-component
#shadow-root
slot1-container
slot
div!slotted
div!slotted
other-component
#shadow-root
slot2-container
slot
div!slotted
some-other-node
slot2-1
slot1-1
slot1-2
inline text`;
await assertMarkupViewAsTree(treeAfterOtherAttach, "#root", inspector);
info("Attach a shadow root to inline-component, check the inline text child.");
mutated = waitForMutation(inspector, "shadowRootAttached");
ContentTask.spawn(gBrowser.selectedBrowser, {}, function() {
content.wrappedJSObject.attachInlineComponent();
});
await mutated;
const treeAfterInlineAttach = `
div
test-component
#shadow-root
slot1-container
slot
div!slotted
div!slotted
other-component
#shadow-root
slot2-container
slot
div!slotted
some-other-node
slot2-1
slot1-1
slot1-2
inline-component
#shadow-root
inline-component-content
some-inline-content
inline text`;
await assertMarkupViewAsTree(treeAfterInlineAttach, "#root", inspector);
});

Просмотреть файл

@ -134,9 +134,11 @@ devtools.jar:
skin/images/command-pick.svg (themes/images/command-pick.svg)
skin/images/command-pick-accessibility.svg (themes/images/command-pick-accessibility.svg)
skin/images/command-frames.svg (themes/images/command-frames.svg)
skin/images/command-console.svg (themes/images/command-console.svg)
skin/images/command-eyedropper.svg (themes/images/command-eyedropper.svg)
skin/images/command-rulers.svg (themes/images/command-rulers.svg)
skin/images/command-measure.svg (themes/images/command-measure.svg)
skin/images/command-noautohide.svg (themes/images/command-noautohide.svg)
skin/images/command-chevron.svg (themes/images/command-chevron.svg)
skin/markup.css (themes/markup.css)
skin/images/editor-error.png (themes/images/editor-error.png)
@ -178,6 +180,10 @@ devtools.jar:
skin/images/debugger-step-out.svg (themes/images/debugger-step-out.svg)
skin/images/debugger-step-over.svg (themes/images/debugger-step-over.svg)
skin/images/debugger-toggleBreakpoints.svg (themes/images/debugger-toggleBreakpoints.svg)
skin/images/dock-bottom.svg (themes/images/dock-bottom.svg)
skin/images/dock-side-left.svg (themes/images/dock-side-left.svg)
skin/images/dock-side-right.svg (themes/images/dock-side-right.svg)
skin/images/dock-undock.svg (themes/images/dock-undock.svg)
skin/images/jump-definition.svg (themes/images/jump-definition.svg)
skin/images/tracer-icon.png (themes/images/tracer-icon.png)
skin/images/tracer-icon@2x.png (themes/images/tracer-icon@2x.png)
@ -192,6 +198,7 @@ devtools.jar:
skin/images/sad-face.svg (themes/images/sad-face.svg)
skin/images/shape-swatch.svg (themes/images/shape-swatch.svg)
skin/images/tool-options.svg (themes/images/tool-options.svg)
skin/images/tool-options-photon.svg (themes/images/tool-options-photon.svg)
skin/images/tool-webconsole.svg (themes/images/tool-webconsole.svg)
skin/images/tool-canvas.svg (themes/images/tool-canvas.svg)
skin/images/tool-debugger.svg (themes/images/tool-debugger.svg)

Просмотреть файл

@ -0,0 +1,259 @@
/* 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/. */
/* eslint-env browser */
"use strict";
// A button that toggles a doorhanger menu.
const { PureComponent } = require("devtools/client/shared/vendor/react");
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
const ReactDOM = require("devtools/client/shared/vendor/react-dom");
const dom = require("devtools/client/shared/vendor/react-dom-factories");
const { button } = dom;
const {
HTMLTooltip,
} = require("devtools/client/shared/widgets/tooltip/HTMLTooltip");
class MenuButton extends PureComponent {
static get propTypes() {
return {
// The document to be used for rendering the menu popup.
doc: PropTypes.object.isRequired,
// An optional ID to assign to the menu's container tooltip object.
menuId: PropTypes.string,
// The preferred side of the anchor element to display the menu.
// Defaults to "bottom".
menuPosition: PropTypes.string.isRequired,
// The offset of the menu from the anchor element.
// Defaults to -5.
menuOffset: PropTypes.number.isRequired,
// The menu content.
children: PropTypes.any,
// Callback function to be invoked when the button is clicked.
onClick: PropTypes.func,
};
}
static get defaultProps() {
return {
menuPosition: "bottom",
menuOffset: -5,
};
}
constructor(props) {
super(props);
this.showMenu = this.showMenu.bind(this);
this.hideMenu = this.hideMenu.bind(this);
this.toggleMenu = this.toggleMenu.bind(this);
this.onHidden = this.onHidden.bind(this);
this.onClick = this.onClick.bind(this);
this.onKeyDown = this.onKeyDown.bind(this);
this.tooltip = null;
this.buttonRef = null;
this.setButtonRef = element => {
this.buttonRef = element;
};
this.state = {
expanded: false,
win: props.doc.defaultView.top,
};
}
componentWillMount() {
this.initializeTooltip();
}
componentWillReceiveProps(nextProps) {
// If the window changes, we need to regenerate the HTMLTooltip or else the
// XUL wrapper element will appear above (in terms of z-index) the old
// window, and not the new.
const win = nextProps.doc.defaultView.top;
if (
nextProps.doc !== this.props.doc ||
this.state.win !== win ||
nextProps.menuId !== this.props.menuId
) {
this.setState({ win });
this.resetTooltip();
this.initializeTooltip();
}
}
componentWillUnmount() {
this.resetTooltip();
}
initializeTooltip() {
const tooltipProps = {
type: "doorhanger",
useXulWrapper: true,
};
if (this.props.menuId) {
tooltipProps.id = this.props.menuId;
}
this.tooltip = new HTMLTooltip(this.props.doc, tooltipProps);
this.tooltip.on("hidden", this.onHidden);
}
async resetTooltip() {
if (!this.tooltip) {
return;
}
// Mark the menu as closed since the onHidden callback may not be called in
// this case.
this.setState({ expanded: false });
this.tooltip.destroy();
this.tooltip.off("hidden", this.onHidden);
this.tooltip = null;
}
async showMenu(anchor) {
this.setState({
expanded: true
});
if (!this.tooltip) {
return;
}
await this.tooltip.show(anchor, {
position: this.props.menuPosition,
y: this.props.menuOffset,
});
}
async hideMenu() {
this.setState({
expanded: false
});
if (!this.tooltip) {
return;
}
await this.tooltip.hide();
}
async toggleMenu(anchor) {
return this.state.expanded ? this.hideMenu() : this.showMenu(anchor);
}
// Used by the call site to indicate that the menu content has changed so
// its container should be updated.
resizeContent() {
if (!this.state.expanded || !this.tooltip || !this.buttonRef) {
return;
}
this.tooltip.updateContainerBounds(this.buttonRef, {
position: this.props.menuPosition,
y: this.props.menuOffset,
});
}
onHidden() {
this.setState({ expanded: false });
}
async onClick(e) {
if (e.target === this.buttonRef) {
if (this.props.onClick) {
this.props.onClick(e);
}
if (!e.defaultPrevented) {
const wasKeyboardEvent = e.screenX === 0 && e.screenY === 0;
await this.toggleMenu(e.target);
// If the menu was activated by keyboard, focus the first item.
if (wasKeyboardEvent && this.tooltip) {
this.tooltip.focus();
}
}
// If we clicked one of the menu items, then, by default, we should
// auto-collapse the menu.
//
// We check for the defaultPrevented state, however, so that menu items can
// turn this behavior off (e.g. a menu item with an embedded button).
} else if (this.state.expanded && !e.defaultPrevented) {
this.hideMenu();
}
}
onKeyDown(e) {
if (!this.state.expanded) {
return;
}
const isButtonFocussed =
this.props.doc && this.props.doc.activeElement === this.buttonRef;
switch (e.key) {
case "Escape":
this.hideMenu();
e.preventDefault();
break;
case "Tab":
case "ArrowDown":
if (isButtonFocussed && this.tooltip) {
if (this.tooltip.focus()) {
e.preventDefault();
}
}
break;
case "ArrowUp":
if (isButtonFocussed && this.tooltip) {
if (this.tooltip.focusEnd()) {
e.preventDefault();
}
}
break;
}
}
render() {
// We bypass the call to HTMLTooltip. setContent and set the panel contents
// directly here.
//
// Bug 1472942: Do this for all users of HTMLTooltip.
const menu = ReactDOM.createPortal(
this.props.children,
this.tooltip.panel
);
const buttonProps = {
...this.props,
onClick: this.onClick,
"aria-expanded": this.state.expanded,
"aria-haspopup": "menu",
ref: this.setButtonRef,
};
if (this.state.expanded) {
buttonProps.onKeyDown = this.onKeyDown;
}
if (this.props.menuId) {
buttonProps["aria-controls"] = this.props.menuId;
}
return button(buttonProps, menu);
}
}
module.exports = MenuButton;

Просмотреть файл

@ -0,0 +1,77 @@
/* 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/. */
/* eslint-env browser */
"use strict";
// A command in a menu.
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
const dom = require("devtools/client/shared/vendor/react-dom-factories");
const { button, li, span } = dom;
const MenuItem = props => {
const attr = {
className: "command"
};
if (props.id) {
attr.id = props.id;
}
if (props.className) {
attr.className += " " + props.className;
}
if (props.onClick) {
attr.onClick = props.onClick;
}
if (typeof props.checked !== "undefined") {
attr.role = "menuitemcheckbox";
if (props.checked) {
attr["aria-checked"] = true;
}
} else {
attr.role = "menuitem";
}
const textLabel = span({ className: "label" }, props.label);
const children = [textLabel];
if (typeof props.accelerator !== "undefined") {
const acceleratorLabel = span(
{ className: "accelerator" },
props.accelerator
);
children.push(acceleratorLabel);
}
return li({ className: "menuitem" }, button(attr, children));
};
MenuItem.propTypes = {
// An optional keyboard shortcut to display next to the item.
// (This does not actually register the event listener for the key.)
accelerator: PropTypes.string,
// A tri-state value that may be true/false if item should be checkable, and
// undefined otherwise.
checked: PropTypes.bool,
// Any additional classes to assign to the button specified as
// a space-separated string.
className: PropTypes.string,
// An optional ID to be assigned to the item.
id: PropTypes.string,
// The item label.
label: PropTypes.string.isRequired,
// An optional callback to be invoked when the item is selected.
onClick: PropTypes.func,
};
module.exports = MenuItem;

Просмотреть файл

@ -0,0 +1,116 @@
/* 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/. */
/* eslint-env browser */
"use strict";
// A list of menu items.
//
// This component provides keyboard navigation amongst any focusable
// children.
const { PureComponent } = 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 { div } = dom;
const { focusableSelector } = require("devtools/client/shared/focus");
class MenuList extends PureComponent {
static get propTypes() {
return {
// ID to assign to the list container.
id: PropTypes.string,
// Children of the list.
children: PropTypes.any,
};
}
constructor(props) {
super(props);
this.onKeyDown = this.onKeyDown.bind(this);
this.setWrapperRef = element => {
this.wrapperRef = element;
};
}
onKeyDown(e) {
// Check if the focus is in the list.
if (
!this.wrapperRef ||
!this.wrapperRef.contains(e.target.ownerDocument.activeElement)
) {
return;
}
const getTabList = () => Array.from(
this.wrapperRef.querySelectorAll(focusableSelector)
);
switch (e.key) {
case "ArrowUp":
case "ArrowDown":
{
const tabList = getTabList();
const currentElement = e.target.ownerDocument.activeElement;
const currentIndex = tabList.indexOf(currentElement);
if (currentIndex !== -1) {
let nextIndex;
if (e.key === "ArrowDown") {
nextIndex =
currentIndex === tabList.length - 1
? 0
: currentIndex + 1;
} else {
nextIndex =
currentIndex === 0
? tabList.length - 1
: currentIndex - 1;
}
tabList[nextIndex].focus();
e.preventDefault();
}
}
break;
case "Home":
{
const firstItem = this.wrapperRef.querySelector(focusableSelector);
if (firstItem) {
firstItem.focus();
e.preventDefault();
}
}
break;
case "End":
{
const tabList = getTabList();
if (tabList.length) {
tabList[tabList.length - 1].focus();
e.preventDefault();
}
}
break;
}
}
render() {
const attr = {
role: "menu",
ref: this.setWrapperRef,
onKeyDown: this.onKeyDown,
};
if (this.props.id) {
attr.id = this.props.id;
}
return div(attr, this.props.children);
}
}
module.exports = MenuList;

Просмотреть файл

@ -0,0 +1,11 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.
DevToolsModules(
'MenuButton.js',
'MenuItem.js',
'MenuList.js',
)

Просмотреть файл

@ -5,6 +5,7 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
DIRS += [
'menu',
'reps',
'splitter',
'tabs',

Просмотреть файл

@ -0,0 +1,19 @@
/* 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";
// Simplied selector targetting elements that can receive the focus, full
// version at
// http://stackoverflow.com/questions/1599660/which-html-elements-can-receive-focus
// .
exports.focusableSelector = [
"a[href]:not([tabindex='-1'])",
"button:not([disabled]):not([tabindex='-1'])",
"iframe:not([tabindex='-1'])",
"input:not([disabled]):not([tabindex='-1'])",
"select:not([disabled]):not([tabindex='-1'])",
"textarea:not([disabled]):not([tabindex='-1'])",
"[tabindex]:not([tabindex='-1'])",
].join(", ");

Просмотреть файл

@ -30,6 +30,7 @@ DevToolsModules(
'DOMHelpers.jsm',
'enum.js',
'file-saver.js',
'focus.js',
'getjson.js',
'inplace-editor.js',
'key-shortcuts.js',

Просмотреть файл

@ -13,6 +13,8 @@ support-files =
doc_html_tooltip.xul
doc_html_tooltip_arrow-01.xul
doc_html_tooltip_arrow-02.xul
doc_html_tooltip_doorhanger-01.xul
doc_html_tooltip_doorhanger-02.xul
doc_html_tooltip_hover.xul
doc_html_tooltip_rtl.xul
doc_inplace-editor_autocomplete_offset.xul
@ -138,8 +140,12 @@ skip-if = e10s # Bug 1221911, bug 1222289, frequent e10s timeouts
[browser_html_tooltip_arrow-01.js]
[browser_html_tooltip_arrow-02.js]
[browser_html_tooltip_consecutive-show.js]
[browser_html_tooltip_doorhanger-01.js]
[browser_html_tooltip_doorhanger-02.js]
[browser_html_tooltip_height-auto.js]
[browser_html_tooltip_hover.js]
[browser_html_tooltip_offset.js]
[browser_html_tooltip_resize.js]
[browser_html_tooltip_rtl.js]
[browser_html_tooltip_variable-height.js]
[browser_html_tooltip_width-auto.js]

Просмотреть файл

@ -0,0 +1,91 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/* import-globals-from helper_html_tooltip.js */
"use strict";
/**
* Test the HTMLTooltip "doorhanger" type's hang direction. It should hang
* towards the middle of the screen.
*/
const HTML_NS = "http://www.w3.org/1999/xhtml";
const TEST_URI = CHROME_URL_ROOT + "doc_html_tooltip_doorhanger-01.xul";
const {HTMLTooltip} =
require("devtools/client/shared/widgets/tooltip/HTMLTooltip");
loadHelperScript("helper_html_tooltip.js");
let useXulWrapper;
add_task(async function() {
// Force the toolbox to be 200px high;
await pushPref("devtools.toolbox.footer.height", 200);
await addTab("about:blank");
const [,, doc] = await createHost("bottom", TEST_URI);
info("Run tests for a Tooltip without using a XUL panel");
useXulWrapper = false;
await runTests(doc);
info("Run tests for a Tooltip with a XUL panel");
useXulWrapper = true;
await runTests(doc);
});
async function runTests(doc) {
info("Create HTML tooltip");
const tooltip = new HTMLTooltip(doc, {type: "doorhanger", useXulWrapper});
const div = doc.createElementNS(HTML_NS, "div");
div.style.width = "200px";
div.style.height = "35px";
tooltip.setContent(div);
const docBounds = doc.documentElement.getBoundingClientRect();
const elements = [...doc.querySelectorAll(".anchor")];
for (const el of elements) {
info("Display the tooltip on an anchor.");
await showTooltip(tooltip, el);
const arrow = tooltip.arrow;
ok(arrow, "Tooltip has an arrow");
// Get the geometry of the anchor, the tooltip panel & arrow.
const anchorBounds = el.getBoxQuads({ relativeTo: doc })[0].getBounds();
const panelBounds =
tooltip.panel.getBoxQuads({ relativeTo: doc })[0].getBounds();
const arrowBounds = arrow.getBoxQuads({ relativeTo: doc })[0].getBounds();
// Work out which side of the view the anchor is on.
const center = bounds => bounds.left + bounds.width / 2;
const anchorSide =
center(anchorBounds) < center(docBounds)
? "left"
: "right";
// Work out which direction the doorhanger hangs.
//
// We can do that just by checking which edge of the panel the center of the
// arrow is closer to.
const panelDirection =
center(arrowBounds) - panelBounds.left <
panelBounds.right - center(arrowBounds)
? "right"
: "left";
const params =
`document: ${docBounds.left}<->${docBounds.right}, ` +
`anchor: ${anchorBounds.left}<->${anchorBounds.right}, ` +
`panel: ${panelBounds.left}<->${panelBounds.right}, ` +
`anchor side: ${anchorSide}, ` +
`panel direction: ${panelDirection}`;
ok(anchorSide !== panelDirection,
`Doorhanger hangs towards center (${params})`);
await hideTooltip(tooltip);
}
tooltip.destroy();
}

Просмотреть файл

@ -0,0 +1,72 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/* import-globals-from helper_html_tooltip.js */
"use strict";
/**
* Test the HTMLTooltip "doorhanger" type's arrow tip is precisely centered on
* the anchor when the anchor is small.
*/
const HTML_NS = "http://www.w3.org/1999/xhtml";
const TEST_URI = CHROME_URL_ROOT + "doc_html_tooltip_doorhanger-02.xul";
const {HTMLTooltip} =
require("devtools/client/shared/widgets/tooltip/HTMLTooltip");
loadHelperScript("helper_html_tooltip.js");
let useXulWrapper;
add_task(async function() {
// Force the toolbox to be 200px high;
await pushPref("devtools.toolbox.footer.height", 200);
await addTab("about:blank");
const [,, doc] = await createHost("bottom", TEST_URI);
info("Run tests for a Tooltip without using a XUL panel");
useXulWrapper = false;
await runTests(doc);
info("Run tests for a Tooltip with a XUL panel");
useXulWrapper = true;
await runTests(doc);
});
async function runTests(doc) {
info("Create HTML tooltip");
const tooltip = new HTMLTooltip(doc, {type: "doorhanger", useXulWrapper});
const div = doc.createElementNS(HTML_NS, "div");
div.style.width = "200px";
div.style.height = "35px";
tooltip.setContent(div);
const elements = [...doc.querySelectorAll(".anchor")];
for (const el of elements) {
info("Display the tooltip on an anchor.");
await showTooltip(tooltip, el);
const arrow = tooltip.arrow;
ok(arrow, "Tooltip has an arrow");
// Get the geometry of the anchor and arrow.
const anchorBounds = el.getBoxQuads({ relativeTo: doc })[0].getBounds();
const arrowBounds = arrow.getBoxQuads({ relativeTo: doc })[0].getBounds();
// Compare the centers
const center = bounds => bounds.left + bounds.width / 2;
const delta = Math.abs(center(anchorBounds) - center(arrowBounds));
const describeBounds = bounds =>
`${bounds.left}<--[${center(bounds)}]-->${bounds.right}`;
const params =
`anchor: ${describeBounds(anchorBounds)}, ` +
`arrow: ${describeBounds(arrowBounds)}`;
ok(delta < 1,
`Arrow center is roughly aligned with anchor center (${params})`);
await hideTooltip(tooltip);
}
tooltip.destroy();
}

Просмотреть файл

@ -0,0 +1,67 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/* import-globals-from helper_html_tooltip.js */
"use strict";
/**
* Test the HTMLTooltip content can automatically calculate its height based on
* content.
*/
const HTML_NS = "http://www.w3.org/1999/xhtml";
const TEST_URI = CHROME_URL_ROOT + "doc_html_tooltip.xul";
const {HTMLTooltip} =
require("devtools/client/shared/widgets/tooltip/HTMLTooltip");
loadHelperScript("helper_html_tooltip.js");
let useXulWrapper;
add_task(async function() {
await addTab("about:blank");
const [,, doc] = await createHost("bottom", TEST_URI);
info("Run tests for a Tooltip without using a XUL panel");
useXulWrapper = false;
await runTests(doc);
info("Run tests for a Tooltip with a XUL panel");
useXulWrapper = true;
await runTests(doc);
});
async function runTests(doc) {
const tooltip = new HTMLTooltip(doc, {useXulWrapper});
info("Create tooltip content height to 150px");
const tooltipContent = doc.createElementNS(HTML_NS, "div");
tooltipContent.style.cssText =
"width: 300px; height: 150px; background: red;";
info("Set tooltip content using width:auto and height:auto");
tooltip.setContent(tooltipContent);
info("Show the tooltip and check the tooltip panel dimensions.");
await showTooltip(tooltip, doc.getElementById("box1"));
let panelRect = tooltip.panel.getBoundingClientRect();
is(panelRect.width, 300, "Tooltip panel has the expected width.");
is(panelRect.height, 150, "Tooltip panel has the expected width.");
await hideTooltip(tooltip);
info("Set tooltip content using fixed width and height:auto");
tooltipContent.style.cssText =
"width: auto; height: 200px; background: red;";
tooltip.setContent(tooltipContent, { width: 400 });
info("Show the tooltip and check the tooltip panel height.");
await showTooltip(tooltip, doc.getElementById("box1"));
panelRect = tooltip.panel.getBoundingClientRect();
is(panelRect.height, 200, "Tooltip panel has the expected width.");
await hideTooltip(tooltip);
tooltip.destroy();
}

Просмотреть файл

@ -0,0 +1,77 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/* import-globals-from helper_html_tooltip.js */
"use strict";
/**
* Test the HTMLTooltip can be resized.
*/
const HTML_NS = "http://www.w3.org/1999/xhtml";
const TEST_URI = CHROME_URL_ROOT + "doc_html_tooltip.xul";
const {HTMLTooltip} = require("devtools/client/shared/widgets/tooltip/HTMLTooltip");
loadHelperScript("helper_html_tooltip.js");
const TOOLBOX_WIDTH = 500;
add_task(async function() {
await pushPref("devtools.toolbox.sidebar.width", TOOLBOX_WIDTH);
// Open the host on the right so that the doorhangers hang right.
const [,, doc] = await createHost("right", TEST_URI);
info("Test resizing of a tooltip");
const tooltip =
new HTMLTooltip(doc, { useXulWrapper: true, type: "doorhanger" });
const div = doc.createElementNS(HTML_NS, "div");
div.textContent = "tooltip";
div.style.cssText = "width: 100px; height: 40px";
tooltip.setContent(div);
const box1 = doc.getElementById("box1");
await showTooltip(tooltip, box1, { position: "top" });
// Get the original position of the panel and arrow.
const originalPanelBounds =
tooltip.panel.getBoxQuads({ relativeTo: doc })[0].getBounds();
const originalArrowBounds =
tooltip.arrow.getBoxQuads({ relativeTo: doc })[0].getBounds();
// Resize the content
div.style.cssText = "width: 200px; height: 30px";
tooltip.updateContainerBounds(box1, { position: "top" });
// The panel should have moved 100px to the left and 10px down
const updatedPanelBounds =
tooltip.panel.getBoxQuads({ relativeTo: doc })[0].getBounds();
const panelXMovement = `panel left: ${originalPanelBounds.left}->` +
updatedPanelBounds.left;
ok(Math.round(updatedPanelBounds.left - originalPanelBounds.left) === -100,
`Panel should have moved 100px to the left (actual: ${panelXMovement})`);
const panelYMovement = `panel top: ${originalPanelBounds.top}->` +
updatedPanelBounds.top;
ok(Math.round(updatedPanelBounds.top - originalPanelBounds.top) === 10,
`Panel should have moved 10px down (actual: ${panelYMovement})`);
// The arrow should be in the same position
const updatedArrowBounds =
tooltip.arrow.getBoxQuads({ relativeTo: doc })[0].getBounds();
const arrowXMovement = `arrow left: ${originalArrowBounds.left}->` +
updatedArrowBounds.left;
ok(Math.round(updatedArrowBounds.left - originalArrowBounds.left) === 0,
`Arrow should not have moved (actual: ${arrowXMovement})`);
const arrowYMovement = `arrow top: ${originalArrowBounds.top}->` +
updatedArrowBounds.top;
ok(Math.round(updatedArrowBounds.top - originalArrowBounds.top) === 0,
`Arrow should not have moved (actual: ${arrowYMovement})`);
await hideTooltip(tooltip);
tooltip.destroy();
});

Просмотреть файл

@ -24,7 +24,7 @@ add_task(async function() {
const [,, doc] = await createHost("right", TEST_URI);
info("Test a tooltip is not closed when clicking inside itself");
info("Test the positioning of tooltips in RTL and LTR directions");
const tooltip = new HTMLTooltip(doc, {useXulWrapper: false});
const div = doc.createElementNS(HTML_NS, "div");
@ -37,6 +37,8 @@ add_task(async function() {
await hideTooltip(tooltip);
tooltip.destroy();
await testRtlArrow(doc);
});
async function testRtlAnchors(doc, tooltip) {
@ -124,3 +126,43 @@ async function testLtrAnchors(doc, tooltip) {
is(panelRect.top, anchorRect.bottom, "Tooltip aligned with the anchor bottom edge");
is(panelRect.height, TOOLTIP_HEIGHT, "Tooltip height is at 100px as expected");
}
async function testRtlArrow(doc) {
// Set up the arrow-style tooltip
const arrowTooltip =
new HTMLTooltip(doc, { type: "arrow", useXulWrapper: false });
const div = doc.createElementNS(HTML_NS, "div");
div.textContent = "tooltip";
div.style.cssText = "box-sizing: border-box; border: 1px solid black";
arrowTooltip.setContent(div, {
width: TOOLTIP_WIDTH,
height: TOOLTIP_HEIGHT,
});
// box2 uses RTL direction and is far enough from the edge that the arrow
// should not be squashed in the wrong direction.
const box2 = doc.getElementById("box2");
info("Display the arrow tooltip on box2.");
await showTooltip(arrowTooltip, box2, { position: "top" });
const arrow = arrowTooltip.arrow;
ok(arrow, "Tooltip has an arrow");
const panelRect = arrowTooltip.container.getBoundingClientRect();
const arrowRect = arrow.getBoundingClientRect();
// The arrow should be offset from the right edge, but still closer to the
// right edge than the left edge.
ok(arrowRect.right < panelRect.right, "Right edge of the arrow " +
`(${arrowRect.right}) is less than the right edge of the panel ` +
`(${panelRect.right})`);
const rightMargin = panelRect.right - arrowRect.right;
const leftMargin = arrowRect.left - panelRect.right;
ok(rightMargin > leftMargin, "Arrow should be closer to the right side of " +
` the panel (margin: ${rightMargin}) than the left side ` +
` (margin: ${leftMargin})`);
await hideTooltip(arrowTooltip);
arrowTooltip.destroy();
}

Просмотреть файл

@ -0,0 +1,48 @@
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin/global.css"?>
<?xml-stylesheet href="chrome://devtools/skin/light-theme.css"?>
<window class="theme-light"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:html="http://www.w3.org/1999/xhtml"
title="Tooltip test">
<vbox flex="1" style="position: relative">
<!-- Left edge -->
<html:div class="anchor"
style="width:10px; height: 10px; position: absolute; background: red;
top: 0; left: 0;">
</html:div>
<!-- Not left edge but still left of center plus RTL direction (which should
no affect the hang direction) -->
<html:div class="anchor"
style="width:10px; height: 10px; position: absolute; background: red;
top: 0; left: 25px; direction: rtl">
</html:div>
<!-- Wide but still left of center -->
<html:div class="anchor"
style="width:80%; height: 10px; position: absolute; background: red;
top: 0; left: 50px;">
</html:div>
<!-- Right edge -->
<html:div class="anchor"
style="width:10px; height: 10px; position: absolute; background: red;
bottom: 0; right: 0;">
</html:div>
<!-- Not right edge but still right of center plus RTL direction (which should
no affect the hang direction) -->
<html:div class="anchor"
style="width:10px; height: 10px; position: absolute; background: red;
bottom: 0; right: 25px; direction: rtl">
</html:div>
<!-- Wide but still right of center -->
<html:div class="anchor"
style="width:80%; height: 10px; position: absolute; background: red;
bottom: 0; right: 50px;">
</html:div>
</vbox>
</window>

Просмотреть файл

@ -0,0 +1,34 @@
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin/global.css"?>
<?xml-stylesheet href="chrome://devtools/skin/light-theme.css"?>
<window class="theme-light"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:html="http://www.w3.org/1999/xhtml"
title="Tooltip test">
<vbox flex="1" style="position: relative">
<!-- Towards the left -->
<html:div class="anchor"
style="width:10px; height: 10px; position: absolute; background: red;
top: 0; left: 25px;">
</html:div>
<!-- Towards the left with RTL direction -->
<html:div class="anchor"
style="width:10px; height: 10px; position: absolute; background: red;
top: 0; left: 50px; direction: rtl;">
</html:div>
<!-- Towards the right -->
<html:div class="anchor"
style="width:10px; height: 10px; position: absolute; background: red;
bottom: 0; right: 25px;">
</html:div>
<!-- Towards the right with RTL direction -->
<html:div class="anchor"
style="width:10px; height: 10px; position: absolute; background: red;
bottom: 0; right: 50px; direction: rtl;">
</html:div>
</vbox>
</window>

Просмотреть файл

@ -197,7 +197,10 @@ EventTooltip.prototype = {
this._addContentListeners(header);
}
this._tooltip.setContent(this.container, {width: CONTAINER_WIDTH});
this._tooltip.setContent(
this.container,
{width: CONTAINER_WIDTH, height: Infinity}
);
this._tooltip.on("hidden", this.destroy);
},

Просмотреть файл

@ -8,7 +8,10 @@
const EventEmitter = require("devtools/shared/event-emitter");
const {TooltipToggle} = require("devtools/client/shared/widgets/tooltip/TooltipToggle");
const {focusableSelector} = require("devtools/client/shared/focus");
const {getCurrentZoom} = require("devtools/shared/layout/utils");
const {listenOnce} = require("devtools/shared/async-utils");
const Services = require("Services");
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
@ -24,24 +27,41 @@ module.exports.POSITION = POSITION;
const TYPE = {
NORMAL: "normal",
ARROW: "arrow",
DOORHANGER: "doorhanger",
};
module.exports.TYPE = TYPE;
const ARROW_WIDTH = 32;
const ARROW_WIDTH = {
"normal": 0,
"arrow": 32,
// This is the value calculated for the .tooltip-arrow element in tooltip.css
// which includes the arrow width (20px) plus the extra margin added so that
// the drop shadow is not cropped (2px each side).
"doorhanger": 24,
};
// Default offset between the tooltip's left edge and the tooltip arrow.
const ARROW_OFFSET = 20;
const ARROW_OFFSET = {
"normal": 0,
// Default offset between the tooltip's edge and the tooltip arrow.
"arrow": 20,
// Match other Firefox menus which use 10px from edge (but subtract the 2px
// margin included in the ARROW_WIDTH above).
"doorhanger": 8,
};
const EXTRA_HEIGHT = {
"normal": 0,
// The arrow is 16px tall, but merges on 3px with the panel border
"arrow": 13,
// The doorhanger arrow is 10px tall, but merges on 1px with the panel border
"doorhanger": 9,
};
const EXTRA_BORDER = {
"normal": 0,
"arrow": 3,
"doorhanger": 0,
};
/**
@ -58,14 +78,21 @@ const EXTRA_BORDER = {
* Preferred height for the tooltip.
* @param {String} pos
* Preferred position for the tooltip. Possible values: "top" or "bottom".
* @param {Number} offset
* Offset between the top of the anchor and the tooltip.
* @return {Object}
* - {Number} top: the top offset for the tooltip.
* - {Number} height: the height to use for the tooltip container.
* - {String} computedPosition: Can differ from the preferred position depending
* on the available height). "top" or "bottom"
*/
const calculateVerticalPosition =
function(anchorRect, viewportRect, height, pos, offset) {
const calculateVerticalPosition = (
anchorRect,
viewportRect,
height,
pos,
offset
) => {
const {TOP, BOTTOM} = POSITION;
let {top: anchorTop, height: anchorHeight} = anchorRect;
@ -103,21 +130,30 @@ function(anchorRect, viewportRect, height, pos, offset) {
};
/**
* Calculate the vertical position & offsets to use for the tooltip. Will attempt to
* respect the provided height and position preferences, unless the available height
* prevents this.
* Calculate the horizontal position & offsets to use for the tooltip. Will
* attempt to respect the provided width and position preferences, unless the
* available width prevents this.
*
* @param {DOMRect} anchorRect
* Bounding rectangle for the anchor, relative to the tooltip document.
* @param {DOMRect} viewportRect
* Bounding rectangle for the viewport. top/left can be different from 0 if some
* space should not be used by tooltips (for instance OS toolbars, taskbars etc.).
* Bounding rectangle for the viewport. top/left can be different from
* 0 if some space should not be used by tooltips (for instance OS
* toolbars, taskbars etc.).
* @param {DOMRect} windowRect
* Bounding rectangle for the window. Used to determine which direction
* doorhangers should hang.
* @param {Number} width
* Preferred width for the tooltip.
* @param {String} type
* The tooltip type (e.g. "arrow").
* @param {Number} offset
* Horizontal offset in pixels.
* @param {Number} borderRadius
* The border radius of the panel. This is added to ARROW_OFFSET to
* calculate the distance from the edge of the tooltip to the start
* of arrow. It is separate from ARROW_OFFSET since it will vary by
* platform.
* @param {Boolean} isRtl
* If the anchor is in RTL, the tooltip should be aligned to the right.
* @return {Object}
@ -125,47 +161,94 @@ function(anchorRect, viewportRect, height, pos, offset) {
* - {Number} width: the width to use for the tooltip container.
* - {Number} arrowLeft: the left offset to use for the arrow element.
*/
const calculateHorizontalPosition =
function(anchorRect, viewportRect, width, type, offset, isRtl) {
const anchorWidth = anchorRect.width;
let anchorStart = isRtl ? anchorRect.right : anchorRect.left;
// Translate to the available viewport space before calculating dimensions and position.
anchorStart -= viewportRect.left;
// Calculate WIDTH.
width = Math.min(width, viewportRect.width);
// Calculate LEFT.
// By default the tooltip is aligned with the anchor left edge. Unless this
// makes it overflow the viewport, in which case is shifts to the left.
let left = anchorStart + offset - (isRtl ? width : 0);
left = Math.min(left, viewportRect.width - width);
left = Math.max(0, left);
// Calculate ARROW LEFT (tooltip's LEFT might be updated)
let arrowLeft;
// Arrow style tooltips may need to be shifted to the left
if (type === TYPE.ARROW) {
const arrowCenter = left + ARROW_OFFSET + ARROW_WIDTH / 2;
const anchorCenter = anchorStart + anchorWidth / 2;
// If the anchor is too narrow, align the arrow and the anchor center.
if (arrowCenter > anchorCenter) {
left = Math.max(0, left - (arrowCenter - anchorCenter));
}
// Arrow's left offset relative to the anchor.
arrowLeft = Math.min(ARROW_OFFSET, (anchorWidth - ARROW_WIDTH) / 2) | 0;
// Translate the coordinate to tooltip container
arrowLeft += anchorStart - left;
// Make sure the arrow remains in the tooltip container.
arrowLeft = Math.min(arrowLeft, width - ARROW_WIDTH);
arrowLeft = Math.max(arrowLeft, 0);
const calculateHorizontalPosition = (
anchorRect,
viewportRect,
windowRect,
width,
type,
offset,
borderRadius,
isRtl
) => {
// Which direction should the tooltip go?
//
// For tooltips we follow the writing direction but for doorhangers the
// guidelines[1] say that,
//
// "Doorhangers opening on the right side of the view show the directional
// arrow on the right.
//
// Doorhangers opening on the left side of the view show the directional
// arrow on the left.
//
// Never place the directional arrow at the center of doorhangers."
//
// [1] https://design.firefox.com/photon/components/doorhangers.html#directional-arrow
//
// So for those we need to check if the anchor is more right or left.
let hangDirection;
if (type === TYPE.DOORHANGER) {
const anchorCenter = anchorRect.left + anchorRect.width / 2;
const viewCenter = windowRect.left + windowRect.width / 2;
hangDirection = anchorCenter >= viewCenter ? "left" : "right";
} else {
hangDirection = isRtl ? "left" : "right";
}
// Translate back to absolute coordinates by re-including viewport left margin.
left += viewportRect.left;
const anchorWidth = anchorRect.width;
return {left, width, arrowLeft};
// Calculate logical start of anchor relative to the viewport.
const anchorStart =
hangDirection === "right"
? anchorRect.left - viewportRect.left
: viewportRect.right - anchorRect.right;
// Calculate tooltip width.
const tooltipWidth = Math.min(width, viewportRect.width);
// Calculate tooltip start.
let tooltipStart = anchorStart + offset;
tooltipStart = Math.min(tooltipStart, viewportRect.width - tooltipWidth);
tooltipStart = Math.max(0, tooltipStart);
// Calculate arrow start (tooltip's start might be updated)
const arrowWidth = ARROW_WIDTH[type];
let arrowStart;
// Arrow and doorhanger style tooltips may need to be shifted
if (type === TYPE.ARROW || type === TYPE.DOORHANGER) {
const arrowOffset = ARROW_OFFSET[type] + borderRadius;
// Where will the point of the arrow be if we apply the standard offset?
const arrowCenter = tooltipStart + arrowOffset + arrowWidth / 2;
// How does that compare to the center of the anchor?
const anchorCenter = anchorStart + anchorWidth / 2;
// If the anchor is too narrow, align the arrow and the anchor center.
if (arrowCenter > anchorCenter) {
tooltipStart = Math.max(0, tooltipStart - (arrowCenter - anchorCenter));
}
// Arrow's start offset relative to the anchor.
arrowStart = Math.min(arrowOffset, (anchorWidth - arrowWidth) / 2) | 0;
// Translate the coordinate to tooltip container
arrowStart += anchorStart - tooltipStart;
// Make sure the arrow remains in the tooltip container.
arrowStart = Math.min(arrowStart, tooltipWidth - arrowWidth - borderRadius);
arrowStart = Math.max(arrowStart, borderRadius);
}
// Convert from logical coordinates to physical
const left =
hangDirection === "right"
? viewportRect.left + tooltipStart
: viewportRect.right - tooltipStart - tooltipWidth;
const arrowLeft =
hangDirection === "right"
? arrowStart
: tooltipWidth - arrowWidth - arrowStart;
return { left, width: tooltipWidth, arrowLeft };
};
/**
@ -203,8 +286,14 @@ const getRelativeRect = function(node, relativeTo) {
* @param {Document} toolboxDoc
* The toolbox document to attach the HTMLTooltip popup.
* @param {Object}
* - {String} id
* The ID to assign to the tooltip container elment.
* - {String} className
* A string separated list of classes to add to the tooltip container
* element.
* - {String} type
* Display type of the tooltip. Possible values: "normal", "arrow"
* Display type of the tooltip. Possible values: "normal", "arrow", and
* "doorhanger".
* - {Boolean} autofocus
* Defaults to false. Should the tooltip be focused when opening it.
* - {Boolean} consumeOutsideClicks
@ -215,6 +304,8 @@ const getRelativeRect = function(node, relativeTo) {
* in order to use all the screen viewport available.
*/
function HTMLTooltip(toolboxDoc, {
id = "",
className = "",
type = "normal",
autofocus = false,
consumeOutsideClicks = true,
@ -223,10 +314,14 @@ function HTMLTooltip(toolboxDoc, {
EventEmitter.decorate(this);
this.doc = toolboxDoc;
this.id = id;
this.className = className;
this.type = type;
this.autofocus = autofocus;
this.consumeOutsideClicks = consumeOutsideClicks;
this.useXulWrapper = this._isXUL() && useXulWrapper;
this.preferredWidth = "auto";
this.preferredHeight = "auto";
// The top window is used to attach click event listeners to close the tooltip if the
// user clicks on the content page.
@ -298,11 +393,21 @@ HTMLTooltip.prototype = {
* @param {Object}
* - {Number} width: preferred width for the tooltip container. If not specified
* the tooltip container will be measured before being displayed, and the
* measured width will be used as preferred width.
* - {Number} height: optional, preferred height for the tooltip container. If
* not specified, the tooltip will be able to use all the height available.
* measured width will be used as the preferred width.
* - {Number} height: preferred height for the tooltip container. If
* not specified the tooltip container will be measured before being
* displayed, and the measured height will be used as the preferred
* height.
*
* For tooltips whose content height may change while being
* displayed, the special value Infinity may be used to produce
* a flexible container that accommodates resizing content. Note,
* however, that when used in combination with the XUL wrapper the
* unfilled part of this container will consume all mouse events
* making content behind this area inaccessible until the tooltip is
* dismissed.
*/
setContent: function(content, {width = "auto", height = Infinity} = {}) {
setContent: function(content, {width = "auto", height = "auto"} = {}) {
this.preferredWidth = width;
this.preferredHeight = height;
@ -316,61 +421,20 @@ HTMLTooltip.prototype = {
*
* @param {Element} anchor
* The reference element with which the tooltip should be aligned
* @param {Object}
* - {String} position: optional, possible values: top|bottom
* If layout permits, the tooltip will be displayed on top/bottom
* of the anchor. If ommitted, the tooltip will be displayed where
* more space is available.
* - {Number} x: optional, horizontal offset between the anchor and the tooltip
* - {Number} y: optional, vertical offset between the anchor and the tooltip
* @param {Object} options
* Optional settings for positioning the tooltip.
* @param {String} options.position
* Optional, possible values: top|bottom
* If layout permits, the tooltip will be displayed on top/bottom
* of the anchor. If omitted, the tooltip will be displayed where
* more space is available.
* @param {Number} options.x
* Optional, horizontal offset between the anchor and the tooltip.
* @param {Number} options.y
* Optional, vertical offset between the anchor and the tooltip.
*/
async show(anchor, {position, x = 0, y = 0} = {}) {
// Get anchor geometry
let anchorRect = getRelativeRect(anchor, this.doc);
if (this.useXulWrapper) {
anchorRect = this._convertToScreenRect(anchorRect);
}
// Get viewport size
const viewportRect = this._getViewportRect();
const themeHeight = EXTRA_HEIGHT[this.type] + 2 * EXTRA_BORDER[this.type];
const preferredHeight = this.preferredHeight + themeHeight;
const {top, height, computedPosition} =
calculateVerticalPosition(anchorRect, viewportRect, preferredHeight, position, y);
this._position = computedPosition;
// Apply height before measuring the content width (if width="auto").
const isTop = computedPosition === POSITION.TOP;
this.container.classList.toggle("tooltip-top", isTop);
this.container.classList.toggle("tooltip-bottom", !isTop);
// If the preferred height is set to Infinity, the tooltip container should grow based
// on its content's height and use as much height as possible.
this.container.classList.toggle("tooltip-flexible-height",
this.preferredHeight === Infinity);
this.container.style.height = height + "px";
let preferredWidth;
if (this.preferredWidth === "auto") {
preferredWidth = this._measureContainerWidth();
} else {
const themeWidth = 2 * EXTRA_BORDER[this.type];
preferredWidth = this.preferredWidth + themeWidth;
}
const anchorWin = anchor.ownerDocument.defaultView;
const isRtl = anchorWin.getComputedStyle(anchor).direction === "rtl";
const {left, width, arrowLeft} = calculateHorizontalPosition(
anchorRect, viewportRect, preferredWidth, this.type, x, isRtl);
this.container.style.width = width + "px";
if (this.type === TYPE.ARROW) {
this.arrow.style.left = arrowLeft + "px";
}
async show(anchor, options) {
const { left, top } = this._updateContainerBounds(anchor, options);
if (this.useXulWrapper) {
await this._showXulWrapperAt(left, top);
@ -386,8 +450,10 @@ HTMLTooltip.prototype = {
this.doc.defaultView.clearTimeout(this.attachEventsTimer);
this.attachEventsTimer = this.doc.defaultView.setTimeout(() => {
this._maybeFocusTooltip();
// Updated the top window reference each time in case the host changes.
if (this.autofocus) {
this.focus();
}
// Update the top window reference each time in case the host changes.
this.topWindow = this._getTopWindow();
this.topWindow.addEventListener("click", this._onClick, true);
this.emit("shown");
@ -395,22 +461,182 @@ HTMLTooltip.prototype = {
},
/**
* Calculate the rect of the viewport that limits the tooltip dimensions. When using a
* XUL panel wrapper, the viewport will be able to use the whole screen (excluding space
* reserved by the OS for toolbars etc.). Otherwise, the viewport is limited to the
* tooltip's document.
* Recalculate the dimensions and position of the tooltip in response to
* changes to its content.
*
* @return {Object} DOMRect-like object with the Number properties: top, right, bottom,
* left, width, height
* Parameters are identical to show().
*/
_getViewportRect: function() {
updateContainerBounds(anchor, options) {
if (!this.isVisible()) {
return;
}
const { left, top } = this._updateContainerBounds(anchor, options);
if (this.useXulWrapper) {
// availLeft/Top are the coordinates first pixel available on the screen for
// applications (excluding space dedicated for OS toolbars, menus etc...)
// availWidth/Height are the dimensions available to applications excluding all
// the OS reserved space
const {availLeft, availTop, availHeight, availWidth} = this.doc.defaultView.screen;
return {
this._moveXulWrapperTo(left, top);
} else {
this.container.style.left = left + "px";
this.container.style.top = top + "px";
}
},
_updateContainerBounds(anchor, {position, x = 0, y = 0} = {}) {
// Get anchor geometry
let anchorRect = getRelativeRect(anchor, this.doc);
if (this.useXulWrapper) {
anchorRect = this._convertToScreenRect(anchorRect);
}
const { viewportRect, windowRect } = this._getBoundingRects();
// Calculate the horizonal position and width
let preferredWidth;
// Record the height too since it might save us from having to look it up
// later.
let measuredHeight;
if (this.preferredWidth === "auto") {
// Reset any styles that constrain the dimensions we want to calculate.
this.container.style.width = "auto";
if (this.preferredHeight === "auto") {
this.container.style.height = "auto";
}
({
width: preferredWidth,
height: measuredHeight,
} = this._measureContainerSize());
} else {
const themeWidth = 2 * EXTRA_BORDER[this.type];
preferredWidth = this.preferredWidth + themeWidth;
}
const anchorWin = anchor.ownerDocument.defaultView;
const anchorCS = anchorWin.getComputedStyle(anchor);
const isRtl = anchorCS.direction === "rtl";
let borderRadius = 0;
if (this.type === TYPE.DOORHANGER) {
borderRadius = parseFloat(
anchorCS.getPropertyValue("--theme-arrowpanel-border-radius")
);
if (Number.isNaN(borderRadius)) {
borderRadius = 0;
}
}
const {left, width, arrowLeft} = calculateHorizontalPosition(
anchorRect,
viewportRect,
windowRect,
preferredWidth,
this.type,
x,
borderRadius,
isRtl
);
// If we constrained the width, then any measured height we have is no
// longer valid.
if (measuredHeight && width !== preferredWidth) {
measuredHeight = undefined;
}
// Apply width and arrow positioning
this.container.style.width = width + "px";
if (this.type === TYPE.ARROW || this.type === TYPE.DOORHANGER) {
this.arrow.style.left = arrowLeft + "px";
}
// Work out how much vertical margin we have.
//
// This relies on us having set either .tooltip-top or .tooltip-bottom
// and on the margins for both being symmetrical. Fortunately the call to
// _measureContainerSize above will set .tooltip-top for us and it also
// assumes these styles are symmetrical so this should be ok.
const panelWindow = this.panel.ownerDocument.defaultView;
const panelComputedStyle = panelWindow.getComputedStyle(this.panel);
const verticalMargin =
parseFloat(panelComputedStyle.marginTop) +
parseFloat(panelComputedStyle.marginBottom);
// Calculate the vertical position and height
let preferredHeight;
if (this.preferredHeight === "auto") {
if (measuredHeight) {
this.container.style.height = "auto";
preferredHeight = measuredHeight;
} else {
({ height: preferredHeight } = this._measureContainerSize());
}
preferredHeight += verticalMargin;
} else {
const themeHeight =
EXTRA_HEIGHT[this.type] +
verticalMargin +
2 * EXTRA_BORDER[this.type];
preferredHeight = this.preferredHeight + themeHeight;
}
const {top, height, computedPosition} =
calculateVerticalPosition(anchorRect, viewportRect, preferredHeight, position, y);
this._position = computedPosition;
const isTop = computedPosition === POSITION.TOP;
this.container.classList.toggle("tooltip-top", isTop);
this.container.classList.toggle("tooltip-bottom", !isTop);
// If the preferred height is set to Infinity, the tooltip container should grow based
// on its content's height and use as much height as possible.
this.container.classList.toggle("tooltip-flexible-height",
this.preferredHeight === Infinity);
this.container.style.height = height + "px";
return { left, top };
},
/**
* Calculate the following boundary rectangles:
*
* - Viewport rect: This is the region that limits the tooltip dimensions.
* When using a XUL panel wrapper, the tooltip will be able to use the whole
* screen (excluding space reserved by the OS for toolbars etc.) and hence
* the result will be in screen coordinates.
* Otherwise, the tooltip is limited to the tooltip's document.
*
* - Window rect: This is the bounds of the view in which the tooltip is
* presented. It is reported in the same coordinates as the viewport
* rect and is used for determining in which direction a doorhanger-type
* tooltip should "hang".
* When using the XUL panel wrapper this will be the dimensions of the
* window in screen coordinates. Otherwise it will be the same as the
* viewport rect.
*
* @return {Object} An object with the following properties
* viewportRect {Object} DOMRect-like object with the Number
* properties: top, right, bottom, left, width, height
* representing the viewport rect.
* windowRect {Object} DOMRect-like object with the Number
* properties: top, right, bottom, left, width, height
* representing the viewport rect.
*/
_getBoundingRects: function() {
let viewportRect;
let windowRect;
if (this.useXulWrapper) {
// availLeft/Top are the coordinates first pixel available on the screen
// for applications (excluding space dedicated for OS toolbars, menus
// etc...)
// availWidth/Height are the dimensions available to applications
// excluding all the OS reserved space
const {
availLeft,
availTop,
availHeight,
availWidth,
} = this.doc.defaultView.screen;
viewportRect = {
top: availTop,
right: availLeft + availWidth,
bottom: availTop + availHeight,
@ -418,12 +644,30 @@ HTMLTooltip.prototype = {
width: availWidth,
height: availHeight,
};
const {
screenX,
screenY,
outerWidth,
outerHeight,
} = this.doc.defaultView;
windowRect = {
top: screenY,
right: screenX + outerWidth,
bottom: screenY + outerHeight,
left: screenX,
width: outerWidth,
height: outerHeight,
};
} else {
viewportRect = windowRect =
this.doc.documentElement.getBoundingClientRect();
}
return this.doc.documentElement.getBoundingClientRect();
return { viewportRect, windowRect };
},
_measureContainerWidth: function() {
_measureContainerSize: function() {
const xulParent = this.container.parentNode;
if (this.useXulWrapper && !this.isVisible()) {
// Move the container out of the XUL Panel to measure it.
@ -431,15 +675,19 @@ HTMLTooltip.prototype = {
}
this.container.classList.add("tooltip-hidden");
this.container.style.width = "auto";
const width = this.container.getBoundingClientRect().width;
// Set either of the tooltip-top or tooltip-bottom styles so that we get an
// accurate height. We're assuming that the two styles will be symmetrical
// and that we will clear this as necessary later.
this.container.classList.add("tooltip-top");
this.container.classList.remove("tooltip-bottom");
const { width, height } = this.container.getBoundingClientRect();
this.container.classList.remove("tooltip-hidden");
if (this.useXulWrapper && !this.isVisible()) {
xulParent.appendChild(this.container);
}
return width;
return { width, height };
},
/**
@ -492,12 +740,20 @@ HTMLTooltip.prototype = {
_createContainer: function() {
const container = this.doc.createElementNS(XHTML_NS, "div");
container.setAttribute("type", this.type);
if (this.id) {
container.setAttribute("id", this.id);
}
container.classList.add("tooltip-container");
if (this.className) {
container.classList.add(...this.className.split(" "));
}
let html = '<div class="tooltip-filler"></div>';
html += '<div class="tooltip-panel"></div>';
if (this.type === TYPE.ARROW) {
if (this.type === TYPE.ARROW || this.type === TYPE.DOORHANGER) {
html += '<div class="tooltip-arrow"></div>';
}
// eslint-disable-next-line no-unsanitized/property
@ -510,6 +766,11 @@ HTMLTooltip.prototype = {
return;
}
// If the disable autohide setting is in effect, ignore.
if (Services.prefs.getBoolPref("ui.popup.disable_autohide", false)) {
return;
}
this.hide();
if (this.consumeOutsideClicks && e.button === 0) {
// Consume only left click events (button === 0).
@ -552,17 +813,29 @@ HTMLTooltip.prototype = {
},
/**
* If the tootlip is configured to autofocus and a focusable element can be found,
* focus it.
* Focus on the first focusable item in the tooltip.
*
* Returns true if we found something to focus on, false otherwise.
*/
_maybeFocusTooltip: function() {
// Simplied selector targetting elements that can receive the focus, full version at
// http://stackoverflow.com/questions/1599660/which-html-elements-can-receive-focus .
const focusableSelector = "a, button, iframe, input, select, textarea";
focus: function() {
const focusableElement = this.panel.querySelector(focusableSelector);
if (this.autofocus && focusableElement) {
if (focusableElement) {
focusableElement.focus();
}
return !!focusableElement;
},
/**
* Focus on the last focusable item in the tooltip.
*
* Returns true if we found something to focus on, false otherwise.
*/
focusEnd: function() {
const focusableElements = this.panel.querySelectorAll(focusableSelector);
if (focusableElements.length) {
focusableElements[focusableElements.length - 1].focus();
}
return focusableElements.length !== 0;
},
_getTopWindow: function() {
@ -599,14 +872,16 @@ HTMLTooltip.prototype = {
_showXulWrapperAt: function(left, top) {
this.xulPanelWrapper.addEventListener("popuphidden", this._onXulPanelHidden);
const onPanelShown = listenOnce(this.xulPanelWrapper, "popupshown");
let zoom = parseFloat(Services.prefs.getCharPref("devtools.toolbox.zoomValue"));
if (!zoom || isNaN(zoom)) {
zoom = 1.0;
}
const zoom = getCurrentZoom(this.xulPanelWrapper);
this.xulPanelWrapper.openPopupAtScreen(left * zoom, top * zoom, false);
return onPanelShown;
},
_moveXulWrapperTo: function(left, top) {
const zoom = getCurrentZoom(this.xulPanelWrapper);
this.xulPanelWrapper.moveTo(left * zoom, top * zoom);
},
_hideXulWrapper: function() {
this.xulPanelWrapper.removeEventListener("popuphidden", this._onXulPanelHidden);

Просмотреть файл

@ -358,7 +358,7 @@ checkbox:-moz-focusring {
}
.devtools-toolbarbutton:not([label]):hover,
.devtools-button:empty:not(:disabled):hover {
.devtools-button:empty:not(:disabled):not([aria-expanded="true"]):hover {
background: var(--toolbarbutton-background);
}
@ -371,7 +371,8 @@ checkbox:-moz-focusring {
.devtools-button:not(:empty):not(:disabled):not(.checked):hover,
.devtools-toolbarbutton[label]:not(:-moz-any([checked=true],[disabled])):hover,
.devtools-button:empty:not(:disabled):-moz-any(:hover:active,.checked),
.devtools-toolbarbutton:not([label]):-moz-any([checked],[open],:hover:active) {
.devtools-toolbarbutton:not([label]):-moz-any([checked],[open],:hover:active),
.devtools-button[aria-haspopup="menu"][aria-expanded="true"] {
background: var(--toolbarbutton-hover-background);
border-color: var(--toolbarbutton-hover-border-color);
}

Просмотреть файл

@ -0,0 +1,11 @@
<!-- 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/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="context-fill #0b0b0b">
<path d="
M13 1a3 3 0 0 1 3 3v8a3 3 0 0 1-3 3h-10a3 3 0 0 1-3-3v-8a3 3 0 0 1 3-3h10z
M13 3h-10a1 1 0 0 0-1 1v1h12v-1a1 1 0 0 0-1-1z
M14 6h-12v6a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1z"/>
<path d="M4.5 7.5l2 2l-2 2" stroke="context-fill" stroke-width="1"
stroke-linecap="round" stroke-linejoin="round" fill="none"/>
</svg>

После

Ширина:  |  Высота:  |  Размер: 635 B

Просмотреть файл

@ -0,0 +1,9 @@
<!-- 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/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="context-fill #0b0b0b">
<path d="M1 5a2 2 0 0 1 2-2h1l2-2 2 2h5a2 2 0 0 1 2 2v8
a2 2 0 0 1-2 2h-10a2 2 0 0 1-2-2z"
stroke-width="2" stroke="context-fill"
stroke-linejoin="round" stroke-linecap="round" fill="none"/>
</svg>

После

Ширина:  |  Высота:  |  Размер: 513 B

Просмотреть файл

@ -0,0 +1,10 @@
<!-- 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/. -->
<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"
fill="context-fill #0b0b0b">
<path d="
M12 0a3 3 0 0 1 3 3v10a3 3 0 0 1-3 3h-8a3 3 0 0 1-3-3v-10a3 3 0 0 1 3-3h8z
M12 2h-8a1 1 0 0 0-1 1v7h10v-7a1 1 0 0 0-1-1z
M13 11h-10v2a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1z"/>
</svg>

После

Ширина:  |  Высота:  |  Размер: 502 B

Просмотреть файл

@ -0,0 +1,10 @@
<!-- 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/. -->
<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"
fill="context-fill #0b0b0b">
<path d="
M0 4a3 3 0 0 1 3-3h10a3 3 0 0 1 3 3v8a3 3 0 0 1-3 3h-10a3 3 0 0 1-3-3z
M2 4v8a1 1 0 0 0 1 1h2v-10h-2a1 1 0 0 0-1 1z
M6 3v10h7a1 1 0 0 0 1-1v-8a1 1 0 0 0-1-1z"/>
</svg>

После

Ширина:  |  Высота:  |  Размер: 495 B

Просмотреть файл

@ -0,0 +1,10 @@
<!-- 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/. -->
<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"
fill="context-fill #0b0b0b">
<path d="
M0 4a3 3 0 0 1 3-3h10a3 3 0 0 1 3 3v8a3 3 0 0 1-3 3h-10a3 3 0 0 1-3-3z
M2 4v8a1 1 0 0 0 1 1h7v-10h-7a1 1 0 0 0-1 1z
M11 3v10h2a1 1 0 0 0 1-1v-8a1 1 0 0 0-1-1z"/>
</svg>

После

Ширина:  |  Высота:  |  Размер: 496 B

Просмотреть файл

@ -0,0 +1,12 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"
fill="context-fill #0b0b0b">
<path d="
M13 0a3 3 0 0 1 3 3v5a3 3 0 0 1-3 3h-5a3 3 0 0 1-3-3v-5a3 3 0 0 1 3-3z
M13 2h-5a1 1 0 0 0-1 1v5a1 1 0 0 0 1 1h5a1 1 0 0 0 1-1v-5a1 1 0 0 0-1-1z"/>
<path d="M9 13v-1h2v1a3 3 0 0 1-3 3
h-5a3 3 0 0 1-3-3v-5a3 3 0 0 1 3-3h1v2h-1a1 1 0 0 0-1 1v5
a1 1 0 0 0 1 1h5a1 1 0 0 0 1-1z"/>
</svg>

После

Ширина:  |  Высота:  |  Размер: 616 B

Просмотреть файл

@ -0,0 +1,9 @@
<!-- 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/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"
fill="context-fill #0b0b0b">
<path d="M7.45 15.95a1.86 1.86 0 0 1-1.86-1.86v-.89c-.3-.14-.6-.3-.89-.5l-.77.43a1.87 1.87 0 0 1-2.54-.68l-.55-.96c-.51-.88-.21-2.02.68-2.54l.77-.44a5.8 5.8 0 0 1 0-1.02l-.77-.45A1.87 1.87 0 0 1 .84 4.5l.55-.95a1.87 1.87 0 0 1 2.54-.69l.77.45c.28-.2.58-.37.89-.52v-.88C5.58.88 6.42.05 7.45.05h1.1c1.03 0 1.86.83 1.86 1.86v.89c.3.14.6.3.89.5l.77-.44a1.86 1.86 0 0 1 2.54.69l.55.95c.52.9.21 2.03-.68 2.55l-.77.44c.03.34.03.68 0 1.02l.77.44c.89.52 1.2 1.66.68 2.55l-.55.95a1.87 1.87 0 0 1-2.54.69l-.77-.45c-.29.2-.58.38-.88.52v.88c0 1.03-.84 1.86-1.87 1.86h-1.1zM5.49 11.4c.26.2.53.35.82.48l.78.34v1.87c0 .2.17.35.36.35h1.1c.2 0 .36-.16.36-.35v-1.87l.78-.34c.3-.13.57-.29.82-.48l.69-.5 1.62.93a.36.36 0 0 0 .48-.13l.56-.96a.36.36 0 0 0-.13-.48l-1.62-.94.1-.85c.03-.31.03-.63 0-.95l-.1-.85 1.62-.93c.17-.1.22-.32.13-.49l-.56-.95a.36.36 0 0 0-.48-.13l-1.62.94-.69-.51a4.27 4.27 0 0 0-.82-.48l-.78-.34V1.91c0-.2-.16-.35-.36-.35h-1.1c-.2 0-.36.16-.36.35v1.87l-.78.34c-.3.13-.57.29-.82.48l-.69.5-1.62-.93a.36.36 0 0 0-.48.13l-.56.96c-.1.16-.03.38.13.48l1.62.94-.1.85c-.03.31-.03.63 0 .95l.1.84-1.62.94a.36.36 0 0 0-.13.48l.56.96c.06.1.18.18.3.18.07 0 .13-.02.18-.05l1.62-.94.69.51z"/>
<circle cx="8" cy="8" r="1.5" stroke-width="1"
stroke="context-fill" fill="none"/>
</svg>

После

Ширина:  |  Высота:  |  Размер: 1.5 KiB

Просмотреть файл

@ -5,8 +5,15 @@
:root {
--close-button-image: url(chrome://devtools/skin/images/close.svg);
--dock-bottom-image: url(chrome://devtools/skin/images/dock-bottom.svg);
--dock-side-right-image: url(chrome://devtools/skin/images/dock-side-right.svg);
--dock-side-left-image: url(chrome://devtools/skin/images/dock-side-left.svg);
--dock-undock-image: url(chrome://devtools/skin/images/dock-undock.svg);
--more-button-image: url(chrome://devtools/skin/images/more.svg);
--settings-image: url(chrome://devtools/skin/images/tool-options-photon.svg);
--command-noautohide-image: url(images/command-noautohide.svg);
--command-console-image: url(images/command-console.svg);
--command-paintflashing-image: url(images/command-paintflashing.svg);
--command-screenshot-image: url(images/command-screenshot.svg);
--command-responsive-image: url(images/command-responsivemode.svg);
@ -219,6 +226,28 @@
background-image: var(--more-button-image);
}
#toolbox-meatball-menu-dock-bottom > .label::before {
background-image: var(--dock-bottom-image);
}
#toolbox-meatball-menu-dock-left > .label::before {
background-image: var(--dock-side-left-image);
}
#toolbox-meatball-menu-dock-right > .label::before {
background-image: var(--dock-side-right-image);
}
#toolbox-meatball-menu-dock-window > .label::before {
background-image: var(--dock-undock-image);
}
#toolbox-meatball-menu-splitconsole > .label::before {
background-image: var(--command-console-image);
}
#toolbox-meatball-menu-noautohide > .label::before {
background-image: var(--command-noautohide-image);
}
#toolbox-meatball-menu-settings > .label::before {
background-image: var(--settings-image);
}
/* Command buttons */
.command-button,

Просмотреть файл

@ -262,6 +262,212 @@
transform: rotate(45deg);
}
/* Tooltip : doorhanger style */
:root {
--theme-arrowpanel-border-radius: 0px;
}
:root[platform="mac"] {
--theme-arrowpanel-border-radius: 3.5px;
}
.tooltip-container[type="doorhanger"] > .tooltip-panel {
padding: 4px 0;
color: var(--theme-arrowpanel-color);
margin: 4px;
max-width: 320px;
}
.tooltip-container[type="doorhanger"] > .tooltip-panel,
.tooltip-container[type="doorhanger"] > .tooltip-arrow::before {
background: var(--theme-arrowpanel-background);
border: 1px solid var(--theme-arrowpanel-border-color);
border-radius: var(--theme-arrowpanel-border-radius);
box-shadow: 0 0 4px hsla(210,4%,10%,.2);
}
:root[platform="mac"] .tooltip-container[type="doorhanger"] > .tooltip-panel,
:root[platform="mac"] .tooltip-container[type="doorhanger"] > .tooltip-arrow::before {
box-shadow: none;
/*
* The above should be:
*
* box-shadow: 0 0 0 1px var(--theme-arrowpanel-border-color);
*
* but although that gives the right emphasis to the border it makes the
* platform shadow much too dark.
*/
}
:root[platform="mac"].theme-light .tooltip-container[type="doorhanger"] > .tooltip-panel,
:root[platform="mac"].theme-light .tooltip-container[type="doorhanger"] > .tooltip-arrow::before {
border: none;
}
.tooltip-container[type="doorhanger"] > .tooltip-arrow {
/* Desired width of the arrow */
--arrow-width: 20px;
/* Amount of room to allow for the shadow. Should be about half the radius. */
--shadow-radius: 4px;
--shadow-margin: calc(var(--shadow-radius) / 2);
/*
* Crop the arrow region to show half the arrow plus allow room for margins.
*
* The ARROW_WIDTH in HTMLTooltip.js needs to match the following value.
*/
width: calc(var(--arrow-width) + 2 * var(--shadow-margin));
height: calc(var(--arrow-width) / 2 + var(--shadow-margin));
}
.tooltip-container[type="doorhanger"] > .tooltip-arrow::before {
/* Make sure the border is included in the size */
box-sizing: border-box;
/* Don't inherit any rounded corners. */
border-radius: 0;
/*
* When the box is rotated, it should have width <arrow-width>.
* That makes the length of one side of the box equal to:
*
* (<arrow-width> / 2) / sin 45
*/
--sin-45: 0.707106781;
--square-side: calc(var(--arrow-width) / 2 / var(--sin-45));
width: var(--square-side);
height: var(--square-side);
/*
* The rotated square will overshoot the left side
* and need to be shifted in by:
*
* (<arrow-width> - square side) / 2
*
* But we also want to shift it in so that the box-shadow
* is not clipped when we clip the parent so we add
* a suitable margin for that.
*/
--overhang: calc((var(--arrow-width) - var(--square-side)) / 2);
margin-left: calc(var(--overhang) + var(--shadow-margin));
}
.tooltip-container[type="doorhanger"].tooltip-top > .tooltip-panel {
/* Drop the margin between the doorhanger and the arrow. */
margin-bottom: 0;
}
.tooltip-container[type="doorhanger"].tooltip-bottom > .tooltip-panel {
/* Drop the margin between the doorhanger and the arrow. */
margin-top: 0;
}
.tooltip-container[type="doorhanger"].tooltip-top > .tooltip-arrow {
/* Overlap the arrow with the 1px border of the doorhanger */
margin-top: -1px;
}
.tooltip-container[type="doorhanger"].tooltip-bottom > .tooltip-arrow {
/* Overlap the arrow with the 1px border of the doorhanger */
margin-bottom: -1px;
}
.tooltip-container[type="doorhanger"].tooltip-top > .tooltip-arrow::before {
/* Show only the bottom half of the box */
margin-top: calc(var(--square-side) / -2);
}
.tooltip-container[type="doorhanger"].tooltip-bottom > .tooltip-arrow::before {
/* Shift the rotated box in so that it is not clipped */
margin-top: calc(var(--overhang) + var(--shadow-margin));
}
.tooltip-container[type="doorhanger"] .tooltip-panel ul {
/* Override the display: -moz-box declaration in minimal-xul.css
* or else menu items won't stack. */
display: block;
}
.tooltip-container[type="doorhanger"] .menuitem > .command {
display: flex;
align-items: baseline;
margin: 0;
padding: 4px 12px;
outline: none;
}
.tooltip-container[type="doorhanger"] .menuitem > button.command:-moz-any([role="menuitem"],[role="menuitemcheckbox"]) {
-moz-appearance: none;
border: none;
color: var(--theme-arrowpanel-color);
background-color: transparent;
text-align: start;
width: 100%;
}
.tooltip-container[type="doorhanger"] .menuitem > .command:not(:-moz-any([disabled],[open],:active)):-moz-any(:hover,:focus) {
background-color: var(--theme-arrowpanel-dimmed);
}
.tooltip-container[type="doorhanger"] .menuitem > .command:-moz-focusring::-moz-focus-inner {
border-color: transparent;
}
.tooltip-container[type="doorhanger"] .menuitem > .command:not([disabled]):-moz-any([open],:hover:active) {
background-color: var(--theme-arrowpanel-dimmed-further);
box-shadow: 0 1px 0 hsla(210,4%,10%,.03) inset;
}
.tooltip-container[type="doorhanger"] .menuitem > .command[aria-checked="true"] {
list-style-image: none;
-moz-context-properties: fill;
fill: currentColor;
background: url(chrome://browser/skin/check.svg) no-repeat transparent;
background-size: 11px 11px;
background-position: center left 7px;
}
.tooltip-container[type="doorhanger"] .menuitem > .command[aria-checked="true"]:-moz-locale-dir(rtl) {
background-position: center right 7px;
}
.tooltip-container[type="doorhanger"] .menuitem > .command > .label {
flex: 1;
padding-inline-start: 16px;
font: menu;
}
.tooltip-container[type="doorhanger"] .menuitem > .command.iconic > .label::before {
content: " ";
display: inline-block;
margin-inline-end: 8px;
width: 16px;
height: 16px;
vertical-align: top;
-moz-context-properties: fill;
fill: currentColor;
/*
* The icons in the sidebar menu have opacity: 0.8 here, but those in the
* hamburger menu don't. For now we match the hamburger menu styling,
* especially because the 80% opacity makes the icons look dull in dark mode.
*/
}
.tooltip-container[type="doorhanger"] .menuitem > .command > .accelerator {
margin-inline-start: 10px;
color: var(--theme-arrowpanel-disabled-color);
font: message-box;
}
.tooltip-container[type="doorhanger"] hr {
display: block;
border: none;
border-top: 1px solid var(--theme-arrowpanel-separator);
margin: 6px 0;
padding: 0;
}
/* Tooltip: Events */
.event-header {

Просмотреть файл

@ -93,6 +93,17 @@
--theme-tooltip-background: rgba(255, 255, 255, .9);
--theme-tooltip-shadow: rgba(155, 155, 155, 0.26);
/* Doorhangers */
/* These colors are based on the colors used for doorhangers elsewhere in
* Firefox. */
--theme-arrowpanel-background: white;
--theme-arrowpanel-color: -moz-fieldText;
--theme-arrowpanel-border-color: var(--grey-90-a20);
--theme-arrowpanel-separator: var(--grey-90-a20);
--theme-arrowpanel-dimmed: hsla(0,0%,80%,.3);
--theme-arrowpanel-dimmed-further: hsla(0,0%,80%,.45);
--theme-arrowpanel-disabled-color: GrayText;
/* Command line */
--theme-command-line-image: url(chrome://devtools/skin/images/commandline-icon.svg#light-theme);
--theme-command-line-image-focus: url(chrome://devtools/skin/images/commandline-icon.svg#light-theme-focus);
@ -101,6 +112,16 @@
--theme-messageCloseButtonFilter: invert(0);
}
/*
* For doorhangers elsewhere in Fireflox, Mac uses a fixed color different to
* -moz-fieldText and a slightly lighter border color (presumably since it
* combines with the platform shadow).
*/
:root[platform="mac"].theme-light {
--theme-arrowpanel-color: rgb(26,26,26);
--theme-arrowpanel-border-color: hsla(210,4%,10%,.05);
}
:root.theme-dark {
--theme-body-background: var(--grey-80);
--theme-sidebar-background: #1B1B1D;
@ -180,6 +201,17 @@
--theme-tooltip-background: rgba(19, 28, 38, .9);
--theme-tooltip-shadow: rgba(25, 25, 25, 0.76);
/* Doorhangers */
/* These colors are based on the colors used for doorhangers elsewhere in
* Firefox. */
--theme-arrowpanel-background: var(--grey-60);
--theme-arrowpanel-color: rgb(249,249,250);
--theme-arrowpanel-border-color: #27272b;
--theme-arrowpanel-separator: rgba(249,249,250,.1);
--theme-arrowpanel-dimmed: rgba(249,249,250,.1);
--theme-arrowpanel-dimmed-further: rgba(249,249,250,.15);
--theme-arrowpanel-disabled-color: rgba(249,249,250,.5);
/* Command line */
--theme-command-line-image: url(chrome://devtools/skin/images/commandline-icon.svg#dark-theme);
--theme-command-line-image-focus: url(chrome://devtools/skin/images/commandline-icon.svg#dark-theme-focus);
@ -252,5 +284,6 @@
--grey-80: #2a2a2e;
--grey-90: #0c0c0d;
--grey-90-a10: rgba(12, 12, 13, 0.1);
--grey-90-a20: rgba(12, 12, 13, 0.2);
--grey-90-a80: rgba(12, 12, 13, 0.8);
}

Просмотреть файл

@ -104,9 +104,9 @@ add_task(async function() {
// Return undefined if the menu item is not available
let label;
if (menuItem) {
if (menuItem && menuItem.querySelector(".label")) {
label =
menuItem.label ===
menuItem.querySelector(".label").textContent ===
L10N.getStr("toolbox.meatballMenu.hideconsole.label")
? "hide"
: "split";

Просмотреть файл

@ -97,7 +97,8 @@ function doesMenuSayHide(toolbox) {
const result =
menuItem &&
menuItem.label ===
menuItem.querySelector(".label") &&
menuItem.querySelector(".label").textContent ===
L10N.getStr("toolbox.meatballMenu.hideconsole.label");
toolbox.doc.addEventListener("popuphidden", () => {

Просмотреть файл

@ -137,6 +137,7 @@ var WalkerActor = protocol.ActorClassWithSpec(walkerSpec, {
this.onMutations = this.onMutations.bind(this);
this.onSlotchange = this.onSlotchange.bind(this);
this.onShadowrootattached = this.onShadowrootattached.bind(this);
this.onFrameLoad = this.onFrameLoad.bind(this);
this.onFrameUnload = this.onFrameUnload.bind(this);
this._throttledEmitNewMutations = throttle(this._emitNewMutations.bind(this),
@ -145,6 +146,13 @@ var WalkerActor = protocol.ActorClassWithSpec(walkerSpec, {
targetActor.on("will-navigate", this.onFrameUnload);
targetActor.on("window-ready", this.onFrameLoad);
// Keep a reference to the chromeEventHandler for the current targetActor, to make
// sure we will be able to remove the listener during the WalkerActor destroy().
this.chromeEventHandler = targetActor.chromeEventHandler;
// shadowrootattached is a chrome-only event.
this.chromeEventHandler.addEventListener("shadowrootattached",
this.onShadowrootattached);
// Ensure that the root document node actor is ready and
// managed.
this.rootNode = this.document();
@ -233,6 +241,8 @@ var WalkerActor = protocol.ActorClassWithSpec(walkerSpec, {
this.targetActor.off("will-navigate", this.onFrameUnload);
this.targetActor.off("window-ready", this.onFrameLoad);
this.chromeEventHandler.removeEventListener("shadowrootattached",
this.onShadowrootattached);
this.onFrameLoad = null;
this.onFrameUnload = null;
@ -251,6 +261,7 @@ var WalkerActor = protocol.ActorClassWithSpec(walkerSpec, {
this.layoutActor = null;
this.targetActor = null;
this.chromeEventHandler = null;
this.emit("destroyed");
} catch (e) {
@ -1735,6 +1746,19 @@ var WalkerActor = protocol.ActorClassWithSpec(walkerSpec, {
});
},
onShadowrootattached: function(event) {
const actor = this.getNode(event.target);
if (!actor) {
return;
}
const mutation = {
type: "shadowRootAttached",
target: actor.actorID,
};
this.queueMutation(mutation);
},
onFrameLoad: function({ window, isTopLevel }) {
const { readyState } = window.document;
if (readyState != "interactive" && readyState != "complete") {

Просмотреть файл

@ -381,6 +381,8 @@ const WalkerFront = FrontClassWithSpec(walkerSpec, {
// Release the document node and all of its children, even retained.
this._releaseFront(targetFront, true);
} else if (change.type === "shadowRootAttached") {
targetFront._form.isShadowHost = true;
} else if (change.type === "unretained") {
// Retained orphans were force-released without the intervention of
// client (probably a navigated frame).
@ -397,6 +399,7 @@ const WalkerFront = FrontClassWithSpec(walkerSpec, {
// mutation types.
if (change.type === "inlineTextChild" ||
change.type === "childList" ||
change.type === "shadowRootAttached" ||
change.type === "nativeAnonymousChildList") {
if (change.inlineTextChild) {
targetFront.inlineTextChild =

Просмотреть файл

@ -46,7 +46,6 @@ XPIDL_SOURCES += [
'nsIDocShellTreeOwner.idl',
'nsIDocumentLoaderFactory.idl',
'nsIDownloadHistory.idl',
'nsIGlobalHistory2.idl',
'nsILoadContext.idl',
'nsIPrivacyTransitionObserver.idl',
'nsIReflowObserver.idl',
@ -94,9 +93,6 @@ UNIFIED_SOURCES += [
'SerializedLoadContext.cpp',
]
if not CONFIG['MOZ_PLACES']:
UNIFIED_SOURCES += ['nsDownloadHistory.cpp']
include('/ipc/chromium/chromium-config.mozbuild')
FINAL_LIBRARY = 'xul'

Просмотреть файл

@ -86,7 +86,6 @@
#include "nsIExternalProtocolService.h"
#include "nsIFormPOSTActionChannel.h"
#include "nsIFrame.h"
#include "nsIGlobalHistory2.h"
#include "nsIGlobalObject.h"
#include "nsIHttpChannel.h"
#include "nsIHttpChannelInternal.h"
@ -3878,27 +3877,13 @@ nsDocShell::AddChildSHEntryToParent(nsISHEntry* aNewEntry, int32_t aChildOffset,
NS_IMETHODIMP
nsDocShell::SetUseGlobalHistory(bool aUseGlobalHistory)
{
nsresult rv;
mUseGlobalHistory = aUseGlobalHistory;
if (!aUseGlobalHistory) {
mGlobalHistory = nullptr;
return NS_OK;
}
// No need to initialize mGlobalHistory if IHistory is available.
nsCOMPtr<IHistory> history = services::GetHistoryService();
if (history) {
return NS_OK;
}
if (mGlobalHistory) {
return NS_OK;
}
mGlobalHistory = do_GetService(NS_GLOBALHISTORY2_CONTRACTID, &rv);
return rv;
return history ? NS_OK : NS_ERROR_FAILURE;
}
NS_IMETHODIMP
@ -12534,12 +12519,6 @@ nsDocShell::AddURIVisit(nsIURI* aURI,
}
(void)history->VisitURI(aURI, aPreviousURI, visitURIFlags);
} else if (mGlobalHistory) {
// Falls back to sync global history interface.
(void)mGlobalHistory->AddURI(aURI,
!!aChannelRedirectFlags,
!IsFrame(),
aReferrerURI);
}
}
@ -13925,8 +13904,6 @@ nsDocShell::UpdateGlobalHistoryTitle(nsIURI* aURI)
nsCOMPtr<IHistory> history = services::GetHistoryService();
if (history) {
history->SetURITitle(aURI, mTitle);
} else if (mGlobalHistory) {
mGlobalHistory->SetPageTitle(aURI, nsString(mTitle));
}
}
}

Просмотреть файл

@ -80,7 +80,6 @@ class nsIContentViewer;
class nsIController;
class nsIDocShellTreeOwner;
class nsIDocument;
class nsIGlobalHistory2;
class nsIHttpChannel;
class nsIMutableArray;
class nsIPrompt;
@ -660,8 +659,7 @@ private: // member functions
uint32_t aChannelRedirectFlags);
/**
* Helper function for adding a URI visit using IHistory. If IHistory is
* not available, the method tries nsIGlobalHistory2.
* Helper function for adding a URI visit using IHistory.
*
* The IHistory API maintains chains of visits, tracking both HTTP referrers
* and redirects for a user session. VisitURI requires the current URI and
@ -928,7 +926,6 @@ private: // data members
nsCOMPtr<nsIContentViewer> mContentViewer;
nsCOMPtr<nsIWidget> mParentWidget;
RefPtr<mozilla::dom::ChildSHistory> mSessionHistory;
nsCOMPtr<nsIGlobalHistory2> mGlobalHistory;
nsCOMPtr<nsIWebBrowserFind> mFind;
nsCOMPtr<nsICommandManager> mCommandManager;

Просмотреть файл

@ -1,52 +0,0 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsDownloadHistory.h"
#include "nsCOMPtr.h"
#include "nsServiceManagerUtils.h"
#include "nsIGlobalHistory2.h"
#include "nsIObserverService.h"
#include "nsIURI.h"
#include "mozilla/Services.h"
NS_IMPL_ISUPPORTS(nsDownloadHistory, nsIDownloadHistory)
NS_IMETHODIMP
nsDownloadHistory::AddDownload(nsIURI* aSource,
nsIURI* aReferrer,
PRTime aStartTime,
nsIURI* aDestination)
{
NS_ENSURE_ARG_POINTER(aSource);
nsCOMPtr<nsIGlobalHistory2> history =
do_GetService("@mozilla.org/browser/global-history;2");
if (!history) {
return NS_ERROR_NOT_AVAILABLE;
}
bool visited;
nsresult rv = history->IsVisited(aSource, &visited);
NS_ENSURE_SUCCESS(rv, rv);
rv = history->AddURI(aSource, false, true, aReferrer);
NS_ENSURE_SUCCESS(rv, rv);
if (!visited) {
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
if (os) {
os->NotifyObservers(aSource, NS_LINK_VISITED_EVENT_TOPIC, nullptr);
}
}
return NS_OK;
}
NS_IMETHODIMP
nsDownloadHistory::RemoveAllDownloads()
{
return NS_ERROR_NOT_IMPLEMENTED;
}

Просмотреть файл

@ -1,28 +0,0 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef __nsDownloadHistory_h__
#define __nsDownloadHistory_h__
#include "nsIDownloadHistory.h"
#include "mozilla/Attributes.h"
#define NS_DOWNLOADHISTORY_CID \
{0x2ee83680, 0x2af0, 0x4bcb, {0xbf, 0xa0, 0xc9, 0x70, 0x5f, 0x65, 0x54, 0xf1}}
class nsDownloadHistory final : public nsIDownloadHistory
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIDOWNLOADHISTORY
NS_DEFINE_STATIC_CID_ACCESSOR(NS_DOWNLOADHISTORY_CID)
private:
~nsDownloadHistory() {}
};
#endif // __nsDownloadHistory_h__

Просмотреть файл

@ -1,59 +0,0 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* 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/. */
/**
* Provides information about global history to gecko.
*
* @note This interface replaces and deprecates nsIGlobalHistory.
*/
#include "nsISupports.idl"
interface nsIURI;
%{ C++
// nsIObserver topic to fire when you add new visited URIs to the history;
// the nsIURI is the subject
#define NS_LINK_VISITED_EVENT_TOPIC "link-visited"
%}
[scriptable, uuid(cf777d42-1270-4b34-be7b-2931c93feda5)]
interface nsIGlobalHistory2 : nsISupports
{
/**
* Add a URI to global history
*
* @param aURI the URI of the page
* @param aRedirect whether the URI was redirected to another location;
* this is 'true' for the original URI which is
* redirected.
* @param aToplevel whether the URI is loaded in a top-level window
* @param aReferrer the URI of the referring page
*
* @note Docshell will not filter out URI schemes like chrome: data:
* about: and view-source:. Embedders should consider filtering out
* these schemes and others, e.g. mailbox: for the main URI and the
* referrer.
*/
void addURI(in nsIURI aURI, in boolean aRedirect, in boolean aToplevel, in nsIURI aReferrer);
/**
* Checks to see whether the given URI is in history.
*
* @param aURI the uri to the page
* @return true if a URI has been visited
*/
boolean isVisited(in nsIURI aURI);
/**
* Set the page title for the given uri. URIs that are not already in
* global history will not be added.
*
* @param aURI the URI for which to set to the title
* @param aTitle the page title
*/
void setPageTitle(in nsIURI aURI, in AString aTitle);
};

Просмотреть файл

@ -36,12 +36,6 @@
#include "nsSHistory.h"
#include "nsSHTransaction.h"
#ifndef MOZ_PLACES
// download history
#include "nsDownloadHistory.h"
#endif
// LoadContexts (used for testing)
#include "LoadContext.h"
@ -101,11 +95,6 @@ NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(ContentHandlerService, Init)
NS_GENERIC_FACTORY_CONSTRUCTOR(nsSHEntry)
NS_GENERIC_FACTORY_CONSTRUCTOR(nsSHTransaction)
#ifndef MOZ_PLACES
// download history
NS_GENERIC_FACTORY_CONSTRUCTOR(nsDownloadHistory)
#endif
NS_DEFINE_NAMED_CID(NS_DOCSHELL_CID);
NS_DEFINE_NAMED_CID(NS_DEFAULTURIFIXUP_CID);
NS_DEFINE_NAMED_CID(NS_WEBNAVIGATION_INFO_CID);
@ -127,9 +116,6 @@ NS_DEFINE_NAMED_CID(NS_EXTERNALURLHANDLERSERVICE_CID);
#endif
NS_DEFINE_NAMED_CID(NS_SHENTRY_CID);
NS_DEFINE_NAMED_CID(NS_SHTRANSACTION_CID);
#ifndef MOZ_PLACES
NS_DEFINE_NAMED_CID(NS_DOWNLOADHISTORY_CID);
#endif
NS_DEFINE_NAMED_CID(NS_CONTENTHANDLERSERVICE_CID);
NS_DEFINE_NAMED_CID(NS_LOADCONTEXT_CID);
NS_DEFINE_NAMED_CID(NS_PRIVATELOADCONTEXT_CID);
@ -158,9 +144,6 @@ const mozilla::Module::CIDEntry kDocShellCIDs[] = {
#endif
{ &kNS_SHENTRY_CID, false, nullptr, nsSHEntryConstructor },
{ &kNS_SHTRANSACTION_CID, false, nullptr, nsSHTransactionConstructor },
#ifndef MOZ_PLACES
{ &kNS_DOWNLOADHISTORY_CID, false, nullptr, nsDownloadHistoryConstructor },
#endif
{ &kNS_LOADCONTEXT_CID, false, nullptr, mozilla::CreateTestLoadContext },
{ &kNS_PRIVATELOADCONTEXT_CID, false, nullptr, mozilla::CreatePrivateTestLoadContext },
{ nullptr }
@ -219,9 +202,6 @@ const mozilla::Module::ContractIDEntry kDocShellContracts[] = {
#endif
{ NS_SHENTRY_CONTRACTID, &kNS_SHENTRY_CID },
{ NS_SHTRANSACTION_CONTRACTID, &kNS_SHTRANSACTION_CID },
#ifndef MOZ_PLACES
{ NS_DOWNLOADHISTORY_CONTRACTID, &kNS_DOWNLOADHISTORY_CID },
#endif
{ NS_LOADCONTEXT_CONTRACTID, &kNS_LOADCONTEXT_CID },
{ NS_PRIVATELOADCONTEXT_CONTRACTID, &kNS_PRIVATELOADCONTEXT_CID },
{ nullptr }

Просмотреть файл

@ -126,7 +126,7 @@
});
yield undefined;
// And the nodes get notified after the "link-visited" topic, so
// And the nodes get notified after the "uri-visit-saved" topic, so
// we need to execute soon...
SimpleTest.executeSoon(nextTest);
yield undefined;

Просмотреть файл

@ -1,68 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const NS_DOWNLOADHISTORY_CID = "{2ee83680-2af0-4bcb-bfa0-c9705f6554f1}";
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyGetter(this, "Services", function() {
ChromeUtils.import("resource://gre/modules/Services.jsm");
return Services;
});
function testLinkVistedObserver()
{
const NS_LINK_VISITED_EVENT_TOPIC = "link-visited";
var ios = Cc["@mozilla.org/network/io-service;1"].
getService(Ci.nsIIOService);
var testURI = ios.newURI("http://google.com/");
var gh = Cc["@mozilla.org/browser/global-history;2"].
getService(Ci.nsIGlobalHistory2);
Assert.ok(!gh.isVisited(testURI));
var topicReceived = false;
var obs = {
observe: function tlvo_observe(aSubject, aTopic, aData)
{
if (NS_LINK_VISITED_EVENT_TOPIC == aTopic) {
Assert.equal(testURI, aSubject);
topicReceived = true;
}
}
};
var os = Cc["@mozilla.org/observer-service;1"].
getService(Ci.nsIObserverService);
os.addObserver(obs, NS_LINK_VISITED_EVENT_TOPIC);
var dh = Components.classesByID[NS_DOWNLOADHISTORY_CID].
getService(Ci.nsIDownloadHistory);
dh.addDownload(testURI);
Assert.ok(topicReceived);
Assert.ok(gh.isVisited(testURI));
}
var tests = [testLinkVistedObserver];
function run_test()
{
// Not everyone uses/defines an nsGlobalHistory* service. Especially if
// MOZ_PLACES is not defined. If getService fails, then abort gracefully.
try {
Cc["@mozilla.org/browser/global-history;2"].
getService(Ci.nsIGlobalHistory2);
}
catch (ex) {
return;
}
// Needed to properly setup and shutdown the profile.
do_get_profile();
for (var i = 0; i < tests.length; i++)
tests[i]();
cleanup();
}

Просмотреть файл

@ -7,7 +7,6 @@ head = head_docshell.js
skip-if = os == 'android'
[test_nsDefaultURIFixup_info.js]
skip-if = os == 'android'
[test_nsIDownloadHistory.js]
[test_pb_notification.js]
# Bug 751575: unrelated JS changes cause timeouts on random platforms
skip-if = true

Просмотреть файл

@ -1350,8 +1350,8 @@ HTMLEditRules::WillInsertText(EditSubAction aEditSubAction,
// tags, because we're hopefully going to insert text (bug 787432).
if (!SelectionRef().IsCollapsed()) {
nsresult rv =
HTMLEditorRef().DeleteSelectionAsAction(nsIEditor::eNone,
nsIEditor::eNoStrip);
HTMLEditorRef().DeleteSelectionAsSubAction(nsIEditor::eNone,
nsIEditor::eNoStrip);
if (NS_WARN_IF(!CanHandleEditAction())) {
return NS_ERROR_EDITOR_DESTROYED;
}
@ -1707,8 +1707,8 @@ HTMLEditRules::WillInsertBreak(bool* aCancel,
// If the selection isn't collapsed, delete it.
if (!SelectionRef().IsCollapsed()) {
nsresult rv =
HTMLEditorRef().DeleteSelectionAsAction(nsIEditor::eNone,
nsIEditor::eStrip);
HTMLEditorRef().DeleteSelectionAsSubAction(nsIEditor::eNone,
nsIEditor::eStrip);
if (NS_WARN_IF(!CanHandleEditAction())) {
return NS_ERROR_EDITOR_DESTROYED;
}

Просмотреть файл

@ -1163,7 +1163,7 @@ HTMLEditor::InsertBrElementAtSelectionWithTransaction()
NS_ENSURE_STATE(selection);
if (!selection->IsCollapsed()) {
nsresult rv = DeleteSelectionAsAction(eNone, eStrip);
nsresult rv = DeleteSelectionAsSubAction(eNone, eStrip);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
@ -1587,7 +1587,7 @@ HTMLEditor::InsertElementAtSelection(Element* aElement,
// inline wrappers before we do the insertion. Otherwise we let
// DeleteSelectionAndPrepareToCreateNode do the deletion for us, which
// calls DeleteSelection with aStripWrappers = eStrip.
rv = DeleteSelectionAsAction(eNone, eNoStrip);
rv = DeleteSelectionAsSubAction(eNone, eNoStrip);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}

Просмотреть файл

@ -117,7 +117,7 @@ HTMLEditor::LoadHTML(const nsAString& aInputString)
if (!handled) {
// Delete Selection, but only if it isn't collapsed, see bug #106269
if (!selection->IsCollapsed()) {
rv = DeleteSelectionAsAction(eNone, eStrip);
rv = DeleteSelectionAsSubAction(eNone, eStrip);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
@ -241,7 +241,7 @@ HTMLEditor::DoInsertHTMLWithContext(const nsAString& aInputString,
// Use an auto tracker so that our drop point is correctly
// positioned after the delete.
AutoTrackDOMPoint tracker(mRangeUpdater, &targetPoint);
rv = DeleteSelectionAsAction(eNone, eStrip);
rv = DeleteSelectionAsSubAction(eNone, eStrip);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
@ -270,7 +270,7 @@ HTMLEditor::DoInsertHTMLWithContext(const nsAString& aInputString,
// We aren't inserting anything, but if aDeleteSelection is set, we do want
// to delete everything.
if (aDeleteSelection) {
nsresult rv = DeleteSelectionAsAction(eNone, eStrip);
nsresult rv = DeleteSelectionAsSubAction(eNone, eStrip);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}

Просмотреть файл

@ -681,7 +681,7 @@ HTMLEditor::DeleteTable2(Element* aTable,
rv = AppendNodeToSelectionAsRange(aTable);
NS_ENSURE_SUCCESS(rv, rv);
rv = DeleteSelectionAsAction(eNext, eStrip);
rv = DeleteSelectionAsSubAction(eNext, eStrip);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
@ -980,6 +980,8 @@ HTMLEditor::DeleteTableColumn(int32_t aNumber)
rv = GetTableSize(table, &rowCount, &colCount);
NS_ENSURE_SUCCESS(rv, rv);
AutoPlaceholderBatch beginBatching(this);
// Shortcut the case of deleting all columns in table
if (!startColIndex && aNumber >= colCount) {
return DeleteTable2(table, selection);
@ -988,7 +990,6 @@ HTMLEditor::DeleteTableColumn(int32_t aNumber)
// Check for counts too high
aNumber = std::min(aNumber,(colCount-startColIndex));
AutoPlaceholderBatch beginBatching(this);
// Prevent rules testing until we're done
AutoTopLevelEditSubActionNotifier maybeTopLevelEditSubAction(
*this, EditSubAction::eDeleteNode,

Просмотреть файл

@ -471,8 +471,8 @@ TextEditRules::WillInsertBreak(bool* aCancel,
// if the selection isn't collapsed, delete it.
if (!SelectionRef().IsCollapsed()) {
rv = TextEditorRef().DeleteSelectionAsAction(nsIEditor::eNone,
nsIEditor::eStrip);
rv = TextEditorRef().DeleteSelectionAsSubAction(nsIEditor::eNone,
nsIEditor::eStrip);
if (NS_WARN_IF(!CanHandleEditAction())) {
return NS_ERROR_EDITOR_DESTROYED;
}
@ -725,8 +725,8 @@ TextEditRules::WillInsertText(EditSubAction aEditSubAction,
// if the selection isn't collapsed, delete it.
if (!SelectionRef().IsCollapsed()) {
rv = TextEditorRef().DeleteSelectionAsAction(nsIEditor::eNone,
nsIEditor::eStrip);
rv = TextEditorRef().DeleteSelectionAsSubAction(nsIEditor::eNone,
nsIEditor::eStrip);
if (NS_WARN_IF(!CanHandleEditAction())) {
return NS_ERROR_EDITOR_DESTROYED;
}

Просмотреть файл

@ -656,6 +656,28 @@ TextEditor::DeleteSelectionAsAction(EDirection aDirection,
EStripWrappers aStripWrappers)
{
MOZ_ASSERT(aStripWrappers == eStrip || aStripWrappers == eNoStrip);
// Showing this assertion is fine if this method is called by outside via
// mutation event listener or something. Otherwise, this is called by
// wrong method.
NS_ASSERTION(!mPlaceholderBatch,
"Should be called only when this is the only edit action of the operation "
"unless mutation event listener nests some operations");
// delete placeholder txns merge.
AutoPlaceholderBatch batch(this, nsGkAtoms::DeleteTxnName);
nsresult rv = DeleteSelectionAsSubAction(aDirection, aStripWrappers);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
return NS_OK;
}
nsresult
TextEditor::DeleteSelectionAsSubAction(EDirection aDirection,
EStripWrappers aStripWrappers)
{
MOZ_ASSERT(aStripWrappers == eStrip || aStripWrappers == eNoStrip);
MOZ_ASSERT(mPlaceholderBatch);
if (!mRules) {
return NS_ERROR_NOT_INITIALIZED;
@ -664,16 +686,11 @@ TextEditor::DeleteSelectionAsAction(EDirection aDirection,
// Protect the edit rules object from dying
RefPtr<TextEditRules> rules(mRules);
// delete placeholder txns merge.
AutoPlaceholderBatch batch(this, nsGkAtoms::DeleteTxnName);
AutoTopLevelEditSubActionNotifier maybeTopLevelEditSubAction(
*this,
EditSubAction::eDeleteSelectedContent,
aDirection);
// pre-process
RefPtr<Selection> selection = GetSelection();
NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
if (NS_WARN_IF(!selection)) {
return NS_ERROR_FAILURE;
}
// If there is an existing selection when an extended delete is requested,
// platforms that use "caret-style" caret positioning collapse the
@ -702,6 +719,10 @@ TextEditor::DeleteSelectionAsAction(EDirection aDirection,
}
}
AutoTopLevelEditSubActionNotifier maybeTopLevelEditSubAction(
*this,
EditSubAction::eDeleteSelectedContent,
aDirection);
EditSubActionInfo subActionInfo(EditSubAction::eDeleteSelectedContent);
subActionInfo.collapsedAction = aDirection;
subActionInfo.stripWrappers = aStripWrappers;
@ -864,7 +885,7 @@ TextEditor::DeleteSelectionAndPrepareToCreateNode()
}
if (!selection->GetAnchorFocusRange()->Collapsed()) {
nsresult rv = DeleteSelectionAsAction(eNone, eStrip);
nsresult rv = DeleteSelectionAsSubAction(eNone, eStrip);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
@ -1142,7 +1163,7 @@ TextEditor::SetText(const nsAString& aString)
}
if (NS_SUCCEEDED(rv)) {
if (aString.IsEmpty()) {
rv = DeleteSelectionAsAction(eNone, eStrip);
rv = DeleteSelectionAsSubAction(eNone, eStrip);
NS_WARNING_ASSERTION(NS_FAILED(rv), "Failed to remove all text");
} else {
rv = InsertTextAsAction(aString);
@ -1631,7 +1652,10 @@ TextEditor::Cut()
{
bool actionTaken = false;
if (FireClipboardEvent(eCut, nsIClipboard::kGlobalClipboard, &actionTaken)) {
DeleteSelectionAsAction(eNone, eStrip);
// XXX This transaction name is referred by PlaceholderTransaction::Merge()
// so that we need to keep using it here.
AutoPlaceholderBatch batch(this, nsGkAtoms::DeleteTxnName);
DeleteSelectionAsSubAction(eNone, eStrip);
}
return actionTaken ? NS_OK : NS_ERROR_FAILURE;
}

Просмотреть файл

@ -117,7 +117,8 @@ public:
/**
* DeleteSelectionAsAction() removes selection content or content around
* caret with transactions. This should be used for handling it as an
* edit action.
* edit action. If you'd like to remove selection for preparing to insert
* something, you probably should use DeleteSelectionAsSubAction().
*
* @param aDirection How much range should be removed.
* @param aStripWrappers Whether the parent blocks should be removed
@ -198,6 +199,18 @@ protected: // May be called by friends.
using EditorBase::RemoveAttributeOrEquivalent;
using EditorBase::SetAttributeOrEquivalent;
/**
* DeleteSelectionAsSubAction() removes selection content or content around
* caret with transactions. This should be used for handling it as an
* edit sub-action.
*
* @param aDirection How much range should be removed.
* @param aStripWrappers Whether the parent blocks should be removed
* when they become empty.
*/
nsresult DeleteSelectionAsSubAction(EDirection aDirection,
EStripWrappers aStripWrappers);
/**
* DeleteSelectionWithTransaction() removes selected content or content
* around caret with transactions.

Просмотреть файл

@ -82,7 +82,7 @@ TextEditor::InsertTextAt(const nsAString& aStringToInsert,
// Use an auto tracker so that our drop point is correctly
// positioned after the delete.
AutoTrackDOMPoint tracker(mRangeUpdater, &targetNode, &targetOffset);
nsresult rv = DeleteSelectionAsAction(eNone, eStrip);
nsresult rv = DeleteSelectionAsSubAction(eNone, eStrip);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}

Просмотреть файл

@ -1,9 +1,17 @@
<script>
var target;
function jsfuzzer() {
try { htmlvar00017.addEventListener("DOMSubtreeModified", eventhandler5); } catch(e) { }
try { htmlvar00017.align = ""; } catch(e) { }
target = htmlvar00017; // Cache the target for removing the event handler.
try { target.addEventListener("DOMSubtreeModified", eventhandler5); } catch(e) { }
try { target.align = ""; } catch(e) { }
}
var count = 0;
function eventhandler5() {
if (count++ == 1) {
// If we didn't stop testing this, this event handler would be called too
// many times. It's enough to run twice to reproduce the bug report.
target.removeEventListener("DOMSubtreeModified", eventhandler5);
}
try { document.execCommand("selectAll", false); } catch(e) { }
try { document.execCommand("justifyCenter", false); } catch(e) { }
try { document.execCommand("forwardDelete", false); } catch(e) { }

Просмотреть файл

@ -91,11 +91,11 @@ load 1393171.html
needs-focus load 1402196.html
load 1402469.html
load 1402526.html
load 1402904.html
load 1405747.html
asserts(1) load 1402904.html
asserts(1) load 1405747.html
load 1405897.html
load 1408170.html
load 1414581.html
asserts(0-1) load 1414581.html
load 1415231.html
load 1423767.html
needs-focus load 1423776.html

Просмотреть файл

@ -24,6 +24,9 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1425997
<script class="testbody" type="application/javascript">
SimpleTest.waitForExplicitFinish();
// 3 assertions are recorded due to nested execCommand() but not a problem.
// They are necessary to detect invalid method call without mutation event listers.
SimpleTest.expectAssertions(3, 3);
SimpleTest.waitForFocus(function() {
let selection = window.getSelection();
let editor = document.getElementById("editor");

Просмотреть файл

@ -29,6 +29,7 @@
#include "mozilla/layers/AsyncImagePipelineManager.h"
#include "mozilla/layers/WebRenderImageHost.h"
#include "mozilla/layers/WebRenderTextureHost.h"
#include "mozilla/Telemetry.h"
#include "mozilla/TimeStamp.h"
#include "mozilla/Unused.h"
#include "mozilla/webrender/RenderThread.h"
@ -124,6 +125,26 @@ gecko_profiler_unregister_thread()
PROFILER_UNREGISTER_THREAD();
}
void
record_telemetry_time(mozilla::wr::TelemetryProbe aProbe, uint64_t aTimeNs)
{
uint32_t time_ms = (uint32_t)(aTimeNs / 1000000);
switch (aProbe) {
case mozilla::wr::TelemetryProbe::SceneBuildTime:
mozilla::Telemetry::Accumulate(mozilla::Telemetry::WR_SCENEBUILD_TIME, time_ms);
break;
case mozilla::wr::TelemetryProbe::SceneSwapTime:
mozilla::Telemetry::Accumulate(mozilla::Telemetry::WR_SCENESWAP_TIME, time_ms);
break;
case mozilla::wr::TelemetryProbe::RenderTime:
mozilla::Telemetry::Accumulate(mozilla::Telemetry::WR_RENDER_TIME, time_ms);
break;
default:
MOZ_ASSERT(false);
break;
}
}
namespace mozilla {
namespace layers {

Просмотреть файл

@ -462,6 +462,13 @@ fn get_proc_address(glcontext_ptr: *mut c_void,
symbol as *const _
}
#[repr(C)]
pub enum TelemetryProbe {
SceneBuildTime = 0,
SceneSwapTime = 1,
RenderTime = 2,
}
extern "C" {
fn is_in_compositor_thread() -> bool;
fn is_in_render_thread() -> bool;
@ -481,6 +488,7 @@ extern "C" {
#[allow(dead_code)]
fn gfx_critical_error(msg: *const c_char);
fn gfx_critical_note(msg: *const c_char);
fn record_telemetry_time(probe: TelemetryProbe, time_ns: u64);
}
struct CppNotifier {
@ -515,8 +523,11 @@ impl RenderNotifier for CppNotifier {
_: DocumentId,
_scrolled: bool,
composite_needed: bool,
_render_time_ns: Option<u64>) {
render_time_ns: Option<u64>) {
unsafe {
if let Some(time) = render_time_ns {
record_telemetry_time(TelemetryProbe::RenderTime, time);
}
if composite_needed {
wr_notifier_new_frame_ready(self.window_id);
} else {
@ -718,13 +729,19 @@ impl SceneBuilderHooks for APZCallbacks {
unsafe { apz_register_updater(self.window_id) }
}
fn pre_scene_swap(&self, _scenebuild_time: u64) {
unsafe { apz_pre_scene_swap(self.window_id) }
fn pre_scene_swap(&self, scenebuild_time: u64) {
unsafe {
record_telemetry_time(TelemetryProbe::SceneBuildTime, scenebuild_time);
apz_pre_scene_swap(self.window_id);
}
}
fn post_scene_swap(&self, info: PipelineInfo, _sceneswap_time: u64) {
fn post_scene_swap(&self, info: PipelineInfo, sceneswap_time: u64) {
let info = WrPipelineInfo::new(info);
unsafe { apz_post_scene_swap(self.window_id, info) }
unsafe {
record_telemetry_time(TelemetryProbe::SceneSwapTime, sceneswap_time);
apz_post_scene_swap(self.window_id, info);
}
// After a scene swap we should schedule a render for the next vsync,
// otherwise there's no guarantee that the new scene will get rendered

Просмотреть файл

@ -127,4 +127,10 @@ void apz_deregister_sampler(mozilla::wr::WrWindowId aWindowId);
#undef WR_FUNC
#undef WR_DESTRUCTOR_SAFE_FUNC
// More functions invoked from Rust code. These are down here because they
// refer to data structures from webrender_ffi_generated.h
extern "C" {
void record_telemetry_time(mozilla::wr::TelemetryProbe aProbe, uint64_t aTimeNs);
}
#endif // WR_h

Просмотреть файл

@ -152,6 +152,14 @@ enum class RepeatMode : uint32_t {
Sentinel /* this must be last for serialization purposes. */
};
enum class TelemetryProbe {
SceneBuildTime = 0,
SceneSwapTime = 1,
RenderTime = 2,
Sentinel /* this must be last for serialization purposes. */
};
enum class TransformStyle : uint32_t {
Flat = 0,
Preserve3D = 1,
@ -1005,6 +1013,9 @@ extern bool is_in_main_thread();
extern bool is_in_render_thread();
extern void record_telemetry_time(TelemetryProbe aProbe,
uint64_t aTimeNs);
WR_INLINE
bool remove_program_binary_disk_cache(const nsAString *aProfPath)
WR_FUNC;

Просмотреть файл

@ -2512,7 +2512,7 @@ pref("security.csp.enableStrictDynamic", true);
#if defined(DEBUG) && !defined(ANDROID)
// about:welcome has been added until Bug 1448359 is fixed at which time home, newtab, and welcome will all be removed.
pref("csp.content_privileged_about_uris_without_csp", "blank,blocked,home,newtab,printpreview,srcdoc,welcome");
pref("csp.content_privileged_about_uris_without_csp", "blank,home,newtab,printpreview,srcdoc,welcome");
#endif
#ifdef NIGHTLY_BUILD

Просмотреть файл

@ -61,7 +61,6 @@ DoListAddresses(AddrMapType& aAddrMap)
i += len;
}
autoCloseSocket.release();
return NS_OK;
}

Просмотреть файл

@ -1818,6 +1818,9 @@ nsHostResolver::ThreadFunc()
TimeStamp startTime = TimeStamp::Now();
bool getTtl = rec->mGetTtl;
TimeDuration inQueue = startTime - rec->mNativeStart;
uint32_t ms = static_cast<uint32_t>(inQueue.ToMilliseconds());
Telemetry::Accumulate(Telemetry::DNS_NATIVE_QUEUING, ms);
nsresult status = GetAddrInfo(rec->host, rec->af,
rec->flags, &ai,
getTtl);

Просмотреть файл

@ -20,7 +20,7 @@ CACHE_THRESHOLD=500000
NAMESPACE='releng.releases.partials'
if [ -e "${HOME}/.dogrc" ]
then
METRIC_CMD="$(which dog)"
METRIC_CMD="$(command -v dog)"
else
METRIC_CMD="echo"
fi

Просмотреть файл

@ -56,7 +56,7 @@ with :func:`start_session() <Marionette.start_session>`:
.. parsed-literal::
from marionette_driver.marionette import Marionette
client = Marionette('localhost', port=2828)
client = Marionette('127.0.0.1', port=2828)
client.start_session()
This returns a session id and an object listing the capabilities of the

Просмотреть файл

@ -17,7 +17,7 @@ Now create the client for this session. Assuming you're using the default
port on a Marionette instance running locally:
.. parsed-literal::
client = Marionette(host='localhost', port=2828)
client = Marionette(host='127.0.0.1', port=2828)
client.start_session()
This will return some id representing your session id. Now that you've

Просмотреть файл

@ -454,7 +454,7 @@ class FennecInstance(GeckoInstance):
logcat_args["logfile"] = self.gecko_log
self.runner.device.start_logcat(**logcat_args)
# forward marionette port (localhost:2828)
# forward marionette port
self.runner.device.device.forward(
local="tcp:{}".format(self.marionette_port),
remote="tcp:{}".format(self.marionette_port))

Просмотреть файл

@ -580,7 +580,7 @@ class Marionette(object):
# so that slow builds have enough time to send the timeout error to the client.
DEFAULT_SOCKET_TIMEOUT = 360
def __init__(self, host="localhost", port=2828, app=None, bin=None,
def __init__(self, host="127.0.0.1", port=2828, app=None, bin=None,
baseurl=None, socket_timeout=None,
startup_timeout=None, **instance_args):
"""Construct a holder for the Marionette connection.
@ -589,7 +589,7 @@ class Marionette(object):
connection and start a Marionette session.
:param host: Host where the Marionette server listens.
Defaults to localhost.
Defaults to 127.0.0.1.
:param port: Port where the Marionette server listens.
Defaults to port 2828.
:param baseurl: Where to look for files served from Marionette's
@ -605,7 +605,7 @@ class Marionette(object):
:param instance_args: Arguments to pass to ``instance_class``.
"""
self.host = host
self.host = "127.0.0.1" # host
self.port = self.local_port = int(port)
self.bin = bin
self.client = None

Просмотреть файл

@ -62,7 +62,7 @@ the required options as best search in the log file of the failing job
the interactive task has been created from. Then copy the complete
command and run it inside the already sourced virtual environment:
% /builds/worker/workspace/build/venv/bin/python -u /builds/worker/workspace/build/tests/marionette/harness/marionette_harness/runtests.py --gecko-log=- -vv --binary=/builds/worker/workspace/build/application/firefox/firefox --address=localhost:2828 --symbols-path=https://queue.taskcluster.net/v1/task/GSuwee61Qyibujtxq4UV3A/artifacts/public/build/target.crashreporter-symbols.zip /builds/worker/workspace/build/tests/marionette/tests/testing/marionette/harness/marionette_harness/tests/unit-tests.ini
% /builds/worker/workspace/build/venv/bin/python -u /builds/worker/workspace/build/tests/marionette/harness/marionette_harness/runtests.py --gecko-log=- -vv --binary=/builds/worker/workspace/build/application/firefox/firefox --address=127.0.0.1:2828 --symbols-path=https://queue.taskcluster.net/v1/task/GSuwee61Qyibujtxq4UV3A/artifacts/public/build/target.crashreporter-symbols.zip /builds/worker/workspace/build/tests/marionette/tests/testing/marionette/harness/marionette_harness/tests/unit-tests.ini
#### Fennec
@ -88,7 +88,7 @@ it inside the already sourced virtual environment.
Here an example for chunk 1 which runs all the tests in the current
chunk with some options for logs removed:
% /builds/worker/workspace/build/venv/bin/python -u /builds/worker/workspace/build/tests/marionette/harness/marionette_harness/runtests.py --emulator --app=fennec --package=org.mozilla.fennec_aurora --address=localhost:2828 /builds/worker/workspace/build/tests/marionette/tests/testing/marionette/harness/marionette_harness/tests/unit-tests.ini --disable-e10s --gecko-log=- --symbols-path=/builds/worker/workspace/build/symbols --startup-timeout=300 --this-chunk 1 --total-chunks 10
% /builds/worker/workspace/build/venv/bin/python -u /builds/worker/workspace/build/tests/marionette/harness/marionette_harness/runtests.py --emulator --app=fennec --package=org.mozilla.fennec_aurora --address=127.0.0.1:2828 /builds/worker/workspace/build/tests/marionette/tests/testing/marionette/harness/marionette_harness/tests/unit-tests.ini --disable-e10s --gecko-log=- --symbols-path=/builds/worker/workspace/build/symbols --startup-timeout=300 --this-chunk 1 --total-chunks 10
To execute a specific test only simply replace `unit-tests.ini`
with its name.

Просмотреть файл

@ -77,9 +77,9 @@ has the additional benefit of also working on macOS and Windows.
Prerequisites:
* You have [built Fennec](https://developer.mozilla.org/en-US/docs/Mozilla/Developer_guide/Build_Instructions/Simple_Firefox_for_Android_build) with
* You have [built Fennec](https://developer.mozilla.org/en-US/docs/Mozilla/Developer_guide/Build_Instructions/Simple_Firefox_for_Android_build) with
`ac_add_options --enable-marionette` in your mozconfig.
* You can run an Android [emulator](https://wiki.mozilla.org/Mobile/Fennec/Android/Testing#Running_tests_on_the_Android_emulator),
* You can run an Android [emulator](https://wiki.mozilla.org/Mobile/Fennec/Android/Testing#Running_tests_on_the_Android_emulator),
which means you have the AVD you need.
When running tests on Fennec, you can have Marionette runner take care of
@ -96,9 +96,9 @@ for additional options.
Alternately, you can start an emulator yourself and have the Marionette runner
start Fennec for you:
% ./mach marionette test --emulator --app='fennec' --address=localhost:2828 --disable-e10s
% ./mach marionette test --emulator --app='fennec' --address=127.0.0.1:2828 --disable-e10s
To connect to an already-running Fennec in an already running emulator or on a device, you will need to enable Marionette manually by setting the browser preference
To connect to an already-running Fennec in an already running emulator or on a device, you will need to enable Marionette manually by setting the browser preference
`marionette.enabled` set to true in the Fennec profile.
Make sure port 2828 is forwarded:
@ -107,14 +107,14 @@ Make sure port 2828 is forwarded:
If Fennec is already started:
% ./mach marionette test --app='fennec' --address=localhost:2828 --disable-e10s
% ./mach marionette test --app='fennec' --address=127.0.0.1:2828 --disable-e10s
If Fennec is not already started on the emulator/device, add the `--emulator`
option. Marionette Test Runner will take care of forwarding the port and
starting Fennec with the correct prefs. (You may need to run
`adb forward --remove-all` to allow the runner to start.)
% ./mach marionette test --emulator --app='fennec' --address=localhost:2828 --disable-e10s
% ./mach marionette test --emulator --app='fennec' --address=127.0.0.1:2828 --disable-e10s
--startup-timeout=300
If you need to troubleshoot the Marionette connection, the most basic check is
@ -122,7 +122,7 @@ to start Fennec, make sure the `marionette.enabled` browser preference is
true and port 2828 is forwarded, then see if you get any response from
Marionette when you connect manually:
% telnet localhost:2828
% telnet 127.0.0.1:2828
You should see output like `{"applicationType":"gecko","marionetteProtocol":3}`

Просмотреть файл

@ -728,7 +728,7 @@ class BaseMarionetteTestRunner(object):
}
if self.bin or self.emulator:
kwargs.update({
'host': 'localhost',
'host': '127.0.0.1',
'port': 2828,
'app': self.app,
'app_args': self.app_args,

Просмотреть файл

@ -174,7 +174,7 @@ def test_build_kwargs_with_binary_or_address(expected_driver_args, build_kwargs_
host, port = address.split(":")
expected_driver_args.update({'host': host, 'port': int(port)})
else:
expected_driver_args.update({'host': 'localhost', 'port': 2828})
expected_driver_args.update({'host': '127.0.0.1', 'port': 2828})
expected_driver_args.assert_matches(built_kwargs)
elif address is None:
expected_driver_args.assert_keys_not_in(built_kwargs)
@ -197,7 +197,7 @@ def test_build_kwargs_with_emulator_or_address(expected_driver_args, build_kwarg
host, port = address.split(":")
expected_driver_args.update({'host': host, 'port': int(port)})
else:
expected_driver_args.update({'host': 'localhost', 'port': 2828})
expected_driver_args.update({'host': '127.0.0.1', 'port': 2828})
assert 'connect_to_running_emulator' not in built_kwargs
expected_driver_args.assert_matches(built_kwargs)
elif not address:

Просмотреть файл

@ -1,6 +1,7 @@
// Base preferences file used by both unittest and perf harnesses.
/* globals user_pref */
user_pref("app.update.enabled", false);
user_pref("browser.chrome.guess_favicon", false);
user_pref("browser.dom.window.dump.enabled", true);
// Use an empty list of sites to avoid fetching
user_pref("browser.newtabpage.activity-stream.feeds.section.topstories", false);

Просмотреть файл

@ -15,7 +15,6 @@ user_pref("app.update.url.android", "");
// bug 1210465.
user_pref("apz.content_response_timeout", 60000);
user_pref("browser.EULA.override", true);
user_pref("browser.chrome.guess_favicon", false);
// Make sure we don't try to load snippets from the network.
user_pref("browser.aboutHomeSnippets.updateUrl", "nonexistent://test");
// Disable Bookmark backups by default.

Просмотреть файл

@ -549732,7 +549732,7 @@
"support"
],
"css/mediaqueries/test_media_queries.html": [
"cff3585932589f611a7101329d3b5b6ca27820aa",
"a7d78b13e119f8cd1ffa8812a9af67e59280084d",
"testharness"
],
"css/mediaqueries/viewport-script-dynamic-ref.html": [
@ -619972,11 +619972,11 @@
"wdspec"
],
"webdriver/tests/close_window/user_prompts.py": [
"58390344e980306233cd0daedca99fd4ec855cf8",
"e263ab43c4fa2525fc252a887f77e7b3eec60fd8",
"wdspec"
],
"webdriver/tests/conftest.py": [
"507edfc726b5e4b78937735e3e6969ecc1c135fe",
"ab3d9928bc95453d1a624b894333ed7582a538e2",
"support"
],
"webdriver/tests/delete_all_cookies/__init__.py": [
@ -619996,7 +619996,7 @@
"wdspec"
],
"webdriver/tests/delete_cookie/user_prompts.py": [
"65b753bd80a06c3c20b0330f624a4d395fdb7ab2",
"62585a63779240b0019db5330f19862f8008a3e5",
"wdspec"
],
"webdriver/tests/delete_session/__init__.py": [
@ -620080,7 +620080,7 @@
"wdspec"
],
"webdriver/tests/element_send_keys/user_prompts.py": [
"a351706452816ea1ca9719c22c47bd2fcd3e3ab8",
"46cfac85412cd534ad88f5067d7705a293e2a501",
"wdspec"
],
"webdriver/tests/execute_async_script/__init__.py": [
@ -620092,7 +620092,7 @@
"wdspec"
],
"webdriver/tests/execute_async_script/user_prompts.py": [
"d89434bc628d4800f2a5a8554e7cdcbba3b6c1e7",
"e4b36de9f869a9911316a211541732720ca408ed",
"wdspec"
],
"webdriver/tests/execute_script/__init__.py": [
@ -620112,7 +620112,7 @@
"wdspec"
],
"webdriver/tests/execute_script/user_prompts.py": [
"4bd4e9089185505d8add4d5ebe9806498da42999",
"7534ddf504d1ba2f31b4d0a86aa7a7e11560bfdc",
"wdspec"
],
"webdriver/tests/find_element/__init__.py": [
@ -620164,7 +620164,7 @@
"wdspec"
],
"webdriver/tests/fullscreen_window/user_prompts.py": [
"8d6d68bb6bfa9956db5b33598ae3cf33f7022de4",
"5c8ddcf7bb73b6d6da5d6a3218a8c648b0b7f51b",
"wdspec"
],
"webdriver/tests/get_active_element/__init__.py": [
@ -620192,7 +620192,7 @@
"wdspec"
],
"webdriver/tests/get_current_url/user_prompts.py": [
"3241e1d4dbda430b32763006d558fb53d06ffaa1",
"4ed666d2c265e0645373a97ce54bab32e8104404",
"wdspec"
],
"webdriver/tests/get_element_attribute/get.py": [
@ -620208,7 +620208,7 @@
"wdspec"
],
"webdriver/tests/get_element_property/user_prompts.py": [
"84a1817bd815bd8c2267862eb3d5f927f2849777",
"79079682f52df381334ea17b90c344d36fcbd454",
"wdspec"
],
"webdriver/tests/get_element_tag_name/__init__.py": [
@ -620220,7 +620220,7 @@
"wdspec"
],
"webdriver/tests/get_element_tag_name/user_prompts.py": [
"88ee5a130fb8fb55974b431d24163bd7de8b305f",
"abbd3d0d1fcb687979cb91fb538952cc4ad0f594",
"wdspec"
],
"webdriver/tests/get_element_text/__init__.py": [
@ -620256,7 +620256,7 @@
"wdspec"
],
"webdriver/tests/get_title/user_prompts.py": [
"fd24d562bd7d945de1e5f87c241f4c3717359838",
"a3b2c8753e374ae1cabbad2e9583ea82f1e149c0",
"wdspec"
],
"webdriver/tests/get_window_rect/__init__.py": [
@ -620268,7 +620268,7 @@
"wdspec"
],
"webdriver/tests/get_window_rect/user_prompts.py": [
"5fd2bc48547788bd24b3b5fc38a18209d85a6747",
"c91b4d09f4f76067c159236b016b375c34baa117",
"wdspec"
],
"webdriver/tests/interface.html": [
@ -620284,7 +620284,7 @@
"wdspec"
],
"webdriver/tests/is_element_selected/user_prompts.py": [
"69d7821499ee460f31ed97f3f839cf6899add8fd",
"f8d1f255cf57cbb5d1074282d3c78975350607bd",
"wdspec"
],
"webdriver/tests/maximize_window/__init__.py": [
@ -620296,7 +620296,7 @@
"wdspec"
],
"webdriver/tests/maximize_window/user_prompts.py": [
"99afcf2148c524eabee410c5ebb89327467a5e35",
"c2f6029d8162adc84aca555c1c53587b71b28ee4",
"wdspec"
],
"webdriver/tests/minimize_window/__init__.py": [
@ -620308,7 +620308,7 @@
"wdspec"
],
"webdriver/tests/minimize_window/user_prompts.py": [
"668c775a0e60b377a5a558a2a38b8887ac2683a0",
"075f1aa4cabff2ab1489891ac1088d62f459b007",
"wdspec"
],
"webdriver/tests/navigate_to/__init__.py": [
@ -620324,31 +620324,31 @@
"support"
],
"webdriver/tests/new_session/conftest.py": [
"72798e95422e70158c26b60aabc27a480bac8758",
"84d255cc36553dd1e539a821477302f3671a760c",
"support"
],
"webdriver/tests/new_session/create_alwaysMatch.py": [
"d0100b96ccdcc99bccd7bd611bd9df2f51c4a728",
"7f0524d63065aa566a85e4a13be23e20fafac1a3",
"wdspec"
],
"webdriver/tests/new_session/create_firstMatch.py": [
"a3f2156bea16ae9eb270794a2b5a0c1a39c3e631",
"dabcb551c4887e1fcff2c592ead0ed2fc4e093a0",
"wdspec"
],
"webdriver/tests/new_session/default_values.py": [
"596830d45fc6c9f8a5ad1e91f79d580adf32ff54",
"5098317d830e827f577662dabcb4388dff776609",
"wdspec"
],
"webdriver/tests/new_session/invalid_capabilities.py": [
"547c60d0dd9a5596e7f06a026ee04f071c46e4f5",
"ead06abab030c160f978e54ce33d4b800fdb6fea",
"wdspec"
],
"webdriver/tests/new_session/merge.py": [
"5022b2f05cb618167ad7d6a23f886c7f39907212",
"4a17fed5d366756420f7cac29696e612f405b2a0",
"wdspec"
],
"webdriver/tests/new_session/response.py": [
"6093d95910f0994a8189f3d8df223ccc7ef97062",
"a9021a93a0355e306a8fdbb1a820bb57562e354f",
"wdspec"
],
"webdriver/tests/new_session/support/__init__.py": [
@ -620400,7 +620400,7 @@
"wdspec"
],
"webdriver/tests/set_window_rect/user_prompts.py": [
"4ed66d05835d3ab229cb90928e0ca2a15ba08c8a",
"a84a453cb9d5fe347f7bec75ef88c3a4f59a3358",
"wdspec"
],
"webdriver/tests/status/__init__.py": [
@ -620408,7 +620408,7 @@
"support"
],
"webdriver/tests/status/status.py": [
"1302349ca7d6a3dcc49e26ca0345023a5c6bbe14",
"8a5e17655c8eca835f113533e8a57a233823cd83",
"wdspec"
],
"webdriver/tests/support/__init__.py": [
@ -620420,7 +620420,7 @@
"support"
],
"webdriver/tests/support/fixtures.py": [
"d06b203cf5257d1715ff5ac31f6a291c0efb522e",
"fd53a1ed47cb430351c568227c2bc65a65374a14",
"support"
],
"webdriver/tests/support/http_request.py": [

Просмотреть файл

@ -1,4 +1,4 @@
[user_prompts.py]
[test_handle_prompt_accept]
[test_handle_prompt_accept[capabilities0\]]
expected: FAIL

Просмотреть файл

@ -1,4 +1,4 @@
[user_prompts.py]
[test_handle_prompt_accept]
[test_handle_prompt_accept[capabilities0\]]
expected: FAIL

Просмотреть файл

@ -1,4 +1,4 @@
[user_prompts.py]
[test_handle_prompt_accept]
[test_handle_prompt_accept[capabilities0\]]
expected: FAIL

Просмотреть файл

@ -1,16 +1,16 @@
[user_prompts.py]
disabled:
if webrender: bug 1425588
[test_handle_prompt_accept]
[test_handle_prompt_accept[capabilities0\]]
expected: FAIL
[test_handle_prompt_dismiss]
[test_handle_prompt_dismiss[capabilities0\]]
expected: FAIL
[test_handle_prompt_ignore]
[test_handle_prompt_ignore[capabilities0\]]
expected: FAIL
[test_handle_prompt_twice]
[test_handle_prompt_twice[capabilities0\]]
expected: FAIL
disabled: Bug 1459118

Просмотреть файл

@ -1,16 +1,16 @@
[user_prompts.py]
disabled:
if webrender: bug 1425588
[test_handle_prompt_accept]
[test_handle_prompt_accept[capabilities0\]]
expected: FAIL
[test_handle_prompt_dismiss]
[test_handle_prompt_dismiss[capabilities0\]]
expected: FAIL
[test_handle_prompt_ignore]
[test_handle_prompt_ignore[capabilities0\]]
expected: FAIL
[test_handle_prompt_twice]
[test_handle_prompt_twice[capabilities0\]]
expected: FAIL
disabled: Bug 1459118

Просмотреть файл

@ -1,4 +1,4 @@
[user_prompts.py]
[test_handle_prompt_accept]
[test_handle_prompt_accept[capabilities0\]]
expected: FAIL

Просмотреть файл

@ -1,4 +1,4 @@
[user_prompts.py]
[test_handle_prompt_accept]
[test_handle_prompt_accept[capabilities0\]]
expected: FAIL

Просмотреть файл

@ -1,9 +1,9 @@
[user_prompts.py]
disabled:
if webrender: bug 1425588
[test_handle_prompt_dismiss]
[test_handle_prompt_dismiss[capabilities0\]]
expected: FAIL
[test_handle_prompt_accept]
[test_handle_prompt_accept[capabilities0\]]
expected: FAIL

Просмотреть файл

@ -1,9 +1,9 @@
[user_prompts.py]
disabled:
if webrender: bug 1425588
[test_handle_prompt_dismiss]
[test_handle_prompt_dismiss[capabilities0\]]
expected: FAIL
[test_handle_prompt_accept]
[test_handle_prompt_accept[capabilities0\]]
expected: FAIL

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше