Merge autoland to mozilla-central. a=merge
|
@ -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
|
||||
|
||||
|
|