Merge autoland to mozilla-central a=merge

This commit is contained in:
Razvan Maries 2019-10-03 12:33:33 +03:00
commit 01d1011ca4
91 changed files with 2778 additions and 1828 deletions

View File

@ -1215,16 +1215,19 @@ Accessible* DocAccessible::GetAccessibleOrDescendant(nsINode* aNode) const {
Accessible* acc = GetAccessible(aNode);
if (acc) return acc;
acc = GetContainerAccessible(aNode);
if (acc == this && aNode == mContent) {
// The body node is the doc's content node.
return acc;
if (aNode == mContent || aNode == mDocumentNode->GetRootElement()) {
// If the node is the doc's body or root element, return the doc accessible.
return const_cast<DocAccessible*>(this);
}
acc = GetContainerAccessible(aNode);
if (acc) {
uint32_t childCnt = acc->ChildCount();
// We access the `mChildren` array directly so that we don't access
// lazily created children in places like `XULTreeAccessible` and
// `XULTreeGridAccessible`.
uint32_t childCnt = acc->mChildren.Length();
for (uint32_t idx = 0; idx < childCnt; idx++) {
Accessible* child = acc->GetChildAt(idx);
Accessible* child = acc->mChildren.ElementAt(idx);
for (nsIContent* elm = child->GetContent();
elm && elm != acc->GetContent();
elm = elm->GetFlattenedTreeParent()) {

View File

@ -53,7 +53,7 @@
this._scrollButtonWidth = 0;
this._lastNumPinned = 0;
this._pinnedTabsLayoutCache = null;
this._animateElement = this.arrowScrollbox._scrollButtonDown;
this._animateElement = this.arrowScrollbox;
this._tabClipWidth = Services.prefs.getIntPref(
"browser.tabs.tabClipWidth"
);
@ -518,12 +518,11 @@
// return to avoid drawing the drop indicator
var pixelsToScroll = 0;
if (this.getAttribute("overflow") == "true") {
var targetAnonid = event.originalTarget.getAttribute("anonid");
switch (targetAnonid) {
case "scrollbutton-up":
switch (event.originalTarget) {
case arrowScrollbox._scrollButtonUp:
pixelsToScroll = arrowScrollbox.scrollIncrement * -1;
break;
case "scrollbutton-down":
case arrowScrollbox._scrollButtonDown:
pixelsToScroll = arrowScrollbox.scrollIncrement;
break;
}
@ -956,7 +955,7 @@
_initializeArrowScrollbox() {
let arrowScrollbox = this.arrowScrollbox;
arrowScrollbox.addEventListener(
arrowScrollbox.shadowRoot.addEventListener(
"underflow",
event => {
// Ignore underflow events:
@ -986,7 +985,7 @@
true
);
arrowScrollbox.addEventListener("overflow", event => {
arrowScrollbox.shadowRoot.addEventListener("overflow", event => {
// Ignore overflow events:
// - from nested scrollable elements
// - for vertical orientation
@ -1002,7 +1001,7 @@
this._handleTabSelect(true);
});
// Override scrollbox.xml method, since our scrollbox's children are
// Override arrowscrollbox.js method, since our scrollbox's children are
// inherited from the scrollbox binding parent (this).
arrowScrollbox._getScrollableElements = () => {
return this.allTabs.filter(arrowScrollbox._canScrollToElement);

View File

@ -117,7 +117,7 @@
#prefs > tr.deleted > th {
font-weight: bold;
opacity: 0.4;
color: var(--in-content-deemphasized-text);
}
#prefs > tr > td.cell-edit,
@ -182,3 +182,8 @@ td.cell-value > form > input[type="number"] {
.button-reset:dir(rtl) {
transform: scaleX(-1);
}
#prefs[has-visible-prefs] > .add > th,
#prefs[has-visible-prefs] > .add > td {
border-top: 1px solid var(--in-content-box-border-color);
}

View File

@ -71,12 +71,13 @@ let gFilterPattern = null;
let gFilterShowAll = false;
class PrefRow {
constructor(name) {
constructor(name, opts) {
this.name = name;
this.value = true;
this.hidden = false;
this.odd = false;
this.editing = false;
this.isAddRow = opts && opts.isAddRow;
this.refreshValue();
}
@ -336,6 +337,7 @@ class PrefRow {
(this.hasUserValue ? "has-user-value " : "") +
(this.isLocked ? "locked " : "") +
(this.exists ? "" : "deleted ") +
(this.isAddRow ? "add " : "") +
(this.odd ? "odd " : "");
}
@ -516,8 +518,11 @@ function loadPrefs() {
let button = event.target.closest("button");
if (button.classList.contains("button-add")) {
pref.isAddRow = false;
Preferences.set(pref.name, pref.value);
if (pref.type != "Boolean") {
if (pref.type == "Boolean") {
pref.refreshClass();
} else {
pref.edit();
}
} else if (
@ -583,6 +588,7 @@ function filterPrefs(options = {}) {
let indexInArray = 0;
let elementInTable = gPrefsTable.firstElementChild;
let odd = false;
let hasVisiblePrefs = false;
while (indexInArray < prefArray.length || elementInTable) {
// For efficiency, filter the array while we are iterating.
let prefInArray = prefArray[indexInArray];
@ -630,14 +636,17 @@ function filterPrefs(options = {}) {
prefInArray.refreshClass();
odd = !odd;
indexInArray++;
hasVisiblePrefs = true;
}
if (fragment) {
gPrefsTable.appendChild(fragment);
}
gPrefsTable.toggleAttribute("has-visible-prefs", hasVisiblePrefs);
if (searchName && !gExistingPrefs.has(searchName)) {
let addPrefRow = new PrefRow(searchName);
let addPrefRow = new PrefRow(searchName, { isAddRow: true });
addPrefRow.odd = odd;
gPrefsTable.appendChild(addPrefRow.getElement());
}

View File

@ -34,19 +34,22 @@ add_task(async function test_add_user_pref() {
await AboutConfigTest.withNewTab(async function() {
// The row for a new preference appears when searching for its name.
Assert.ok(!this.getRow(PREF_NEW));
this.search(PREF_NEW);
let row = this.getRow(PREF_NEW);
Assert.ok(row.hasClass("deleted"));
for (let [radioIndex, expectedValue, expectedEditingMode] of [
[0, true, false],
[1, 0, true],
[2, "", true],
]) {
this.search(PREF_NEW);
let row = this.getRow(PREF_NEW);
Assert.ok(row.hasClass("deleted"));
Assert.ok(row.hasClass("add"));
// Adding the preference should set the default for the data type.
row.element.querySelectorAll("input")[radioIndex].click();
row.editColumnButton.click();
Assert.ok(!row.hasClass("deleted"));
Assert.ok(!row.hasClass("add"));
Assert.ok(Preferences.get(PREF_NEW) === expectedValue);
// Number and String preferences should be in edit mode.
@ -56,6 +59,7 @@ add_task(async function test_add_user_pref() {
this.search(PREF_NEW);
row = this.getRow(PREF_NEW);
Assert.ok(!row.hasClass("deleted"));
Assert.ok(!row.hasClass("add"));
Assert.ok(!row.valueInput);
// Reset the preference, then continue by adding a different type.

View File

@ -54,6 +54,10 @@ add_task(async function test_search() {
// Expecting 1 row to be returned since it offers the ability to add.
this.search("aJunkValueasdf");
Assert.equal(this.rows.length, 1);
// The has-visible-prefs attribute is used to style the border of the add row.
Assert.ok(!this.prefsTable.hasAttribute("has-visible-prefs"));
let addRow = this.getRow("aJunkValueasdf");
Assert.equal(getComputedStyle(addRow.valueCell)["border-top-width"], "0px");
// Pressing ESC clears the field and returns to the initial page.
EventUtils.sendKey("escape");
@ -68,10 +72,15 @@ add_task(async function test_search() {
// new preference with the same name but a different case.
this.search("TEST.aboutconfig.a");
Assert.equal(this.rows.length, 3);
// The has-visible-prefs attribute is used to style the border of the add row.
Assert.ok(this.prefsTable.hasAttribute("has-visible-prefs"));
addRow = this.getRow("TEST.aboutconfig.a");
Assert.equal(getComputedStyle(addRow.valueCell)["border-top-width"], "1px");
// Entering an empty string returns to the initial page.
this.search("");
Assert.equal(this.rows.length, 0);
Assert.ok(!this.prefsTable.hasAttribute("has-visible-prefs"));
});
});

View File

@ -479,11 +479,10 @@
}
// Autoscroll the popup strip if we drag over the scroll buttons.
let anonid = event.originalTarget.getAttribute("anonid");
let scrollDir = 0;
if (anonid == "scrollbutton-up") {
if (event.originalTarget == this.scrollBox._scrollButtonUp) {
scrollDir = -1;
} else if (anonid == "scrollbutton-down") {
} else if (event.originalTarget == this.scrollBox._scrollButtonDown) {
scrollDir = 1;
}
if (scrollDir != 0) {

View File

@ -24,7 +24,6 @@
<script src="chrome://global/content/preferencesBindings.js"/>
<script src="chrome://browser/content/preferences/in-content/syncChooseWhatToSync.js"/>
<description data-l10n-id="sync-choose-heading"></description>
<html:div class="sync-engines-list">
<html:div class="sync-engine-bookmarks">
<checkbox data-l10n-id="sync-engine-bookmarks"

View File

@ -792,8 +792,6 @@ sync-choose-what-to-sync-dialog =
.buttonlabelextra2 = Disconnect…
.buttonaccesskeyextra2 = D
sync-choose-heading = Choose what to sync to your account for devices using { -sync-brand-short-name }:
sync-engine-bookmarks =
.label = Bookmarks
.accesskey = m

View File

@ -380,13 +380,6 @@ notification[value="translation"] menulist > .menulist-dropmarker {
z-index: 3;
}
/* Tab bar scroll arrows */
.tabbrowser-arrowscrollbox > .scrollbutton-up > .toolbarbutton-icon,
.tabbrowser-arrowscrollbox > .scrollbutton-down > .toolbarbutton-icon {
-moz-appearance: none;
}
/* All tabs menupopup */
.alltabs-item[selected="true"] {

View File

@ -63,7 +63,7 @@
min-width: 0;
}
.tabbrowser-arrowscrollbox > .arrowscrollbox-scrollbox {
.tabbrowser-arrowscrollbox::part(scrollbox) {
/* Needed to prevent tabstrip from growing as wide as the sum of the tabs'
page-title widths (when we'd rather have it be as wide as the window and
compress the tabs to their minimum size): */
@ -434,8 +434,8 @@
}
/* Tab Overflow */
.tabbrowser-arrowscrollbox > .arrowscrollbox-overflow-start-indicator:not([collapsed]),
.tabbrowser-arrowscrollbox > .arrowscrollbox-overflow-end-indicator:not([collapsed]) {
.tabbrowser-arrowscrollbox:not([scrolledtostart])::part(arrowscrollbox-overflow-start-indicator),
.tabbrowser-arrowscrollbox:not([scrolledtoend])::part(arrowscrollbox-overflow-end-indicator) {
width: 18px;
background-image: url(chrome://browser/skin/tabbrowser/tab-overflow-indicator.png);
background-size: 17px 100%;
@ -450,28 +450,28 @@
z-index: 3; /* the selected tab's z-index + 1 */
}
.tabbrowser-arrowscrollbox > .arrowscrollbox-overflow-start-indicator:-moz-locale-dir(rtl),
.tabbrowser-arrowscrollbox > .arrowscrollbox-overflow-end-indicator:-moz-locale-dir(ltr) {
.tabbrowser-arrowscrollbox:-moz-locale-dir(rtl)::part(arrowscrollbox-overflow-start-indicator),
.tabbrowser-arrowscrollbox:-moz-locale-dir(ltr)::part(arrowscrollbox-overflow-end-indicator) {
transform: scaleX(-1);
}
.tabbrowser-arrowscrollbox > .arrowscrollbox-overflow-start-indicator:not([collapsed]) {
.tabbrowser-arrowscrollbox:not([scrolledtostart])::part(arrowscrollbox-overflow-start-indicator) {
margin-inline-start: -1px;
margin-inline-end: -17px;
}
.tabbrowser-arrowscrollbox > .arrowscrollbox-overflow-end-indicator:not([collapsed]) {
.tabbrowser-arrowscrollbox:not([scrolledtoend])::part(arrowscrollbox-overflow-end-indicator) {
margin-inline-start: -17px;
margin-inline-end: -1px;
}
.tabbrowser-arrowscrollbox > .arrowscrollbox-overflow-start-indicator[collapsed],
.tabbrowser-arrowscrollbox > .arrowscrollbox-overflow-end-indicator[collapsed] {
.tabbrowser-arrowscrollbox[scrolledtostart]::part(arrowscrollbox-overflow-start-indicator),
.tabbrowser-arrowscrollbox[scrolledtoend]::part(arrowscrollbox-overflow-end-indicator) {
opacity: 0;
}
.tabbrowser-arrowscrollbox > .arrowscrollbox-overflow-start-indicator,
.tabbrowser-arrowscrollbox > .arrowscrollbox-overflow-end-indicator {
.tabbrowser-arrowscrollbox::part(arrowscrollbox-overflow-start-indicator),
.tabbrowser-arrowscrollbox::part(arrowscrollbox-overflow-end-indicator) {
transition: opacity 150ms ease;
}
@ -686,8 +686,8 @@
/* Tab bar scroll arrows */
.tabbrowser-arrowscrollbox > .scrollbutton-up,
.tabbrowser-arrowscrollbox > .scrollbutton-down {
.tabbrowser-arrowscrollbox::part(scrollbutton-up),
.tabbrowser-arrowscrollbox::part(scrollbutton-down) {
list-style-image: url(chrome://browser/skin/arrow-left.svg) !important;
-moz-context-properties: fill, fill-opacity;
fill: var(--lwt-toolbarbutton-icon-fill, currentColor);
@ -695,8 +695,8 @@
color: inherit;
}
.tabbrowser-arrowscrollbox > .scrollbutton-up:-moz-locale-dir(rtl),
.tabbrowser-arrowscrollbox > .scrollbutton-down:-moz-locale-dir(ltr) {
.tabbrowser-arrowscrollbox:-moz-locale-dir(rtl)::part(scrollbutton-up),
.tabbrowser-arrowscrollbox:-moz-locale-dir(ltr)::part(scrollbutton-down) {
transform: scaleX(-1);
}

View File

@ -55,8 +55,8 @@ toolbar[brighttext] {
/* ::::: primary toolbar buttons ::::: */
.tabbrowser-arrowscrollbox > .scrollbutton-up[disabled=true],
.tabbrowser-arrowscrollbox > .scrollbutton-down[disabled=true],
.tabbrowser-arrowscrollbox[scrolledtostart=true]::part(scrollbutton-up),
.tabbrowser-arrowscrollbox[scrolledtoend=true]::part(scrollbutton-down),
:root:not([customizing]) .toolbarbutton-1[disabled=true],
/* specialcase the overflow and the hamburger button so they show up disabled in customize mode. */
#nav-bar-overflow-button[disabled=true],
@ -74,22 +74,22 @@ toolbar[brighttext] {
}
#TabsToolbar .toolbarbutton-1,
.tabbrowser-arrowscrollbox > .scrollbutton-up,
.tabbrowser-arrowscrollbox > .scrollbutton-down {
.tabbrowser-arrowscrollbox::part(scrollbutton-up),
.tabbrowser-arrowscrollbox::part(scrollbutton-down) {
margin: 0 0 var(--tabs-navbar-shadow-size) !important;
}
.tabbrowser-arrowscrollbox > .scrollbutton-up,
.tabbrowser-arrowscrollbox > .scrollbutton-down {
.tabbrowser-arrowscrollbox::part(scrollbutton-up),
.tabbrowser-arrowscrollbox::part(scrollbutton-down) {
-moz-appearance: none;
padding: 0 var(--toolbarbutton-inner-padding) !important;
}
#navigator-toolbox:not(:hover) .tabbrowser-arrowscrollbox > .scrollbutton-down:not([highlight]) {
#navigator-toolbox:not(:hover) .tabbrowser-arrowscrollbox:not([highlight])::part(scrollbutton-down) {
transition: 1s background-color ease-out;
}
.tabbrowser-arrowscrollbox > .scrollbutton-down[highlight] {
.tabbrowser-arrowscrollbox[highlight]::part(scrollbutton-down) {
background-color: Highlight;
}
@ -175,8 +175,8 @@ toolbar[brighttext] .toolbaritem-combined-buttons > separator {
}
#PersonalToolbar .toolbarbutton-1:not([disabled=true]):not([checked]):not([open]):not(:active):hover,
.tabbrowser-arrowscrollbox > .scrollbutton-up:not([disabled=true]):hover,
.tabbrowser-arrowscrollbox > .scrollbutton-down:not([disabled=true]):hover,
.tabbrowser-arrowscrollbox:not([scrolledtostart=true])::part(scrollbutton-up):hover,
.tabbrowser-arrowscrollbox:not([scrolledtoend=true])::part(scrollbutton-down):hover,
.findbar-button:not(:-moz-any([checked="true"],[disabled="true"])):hover,
toolbarbutton.bookmark-item:not(.subviewbutton):hover:not([disabled="true"]):not([open]),
toolbar .toolbarbutton-1:not([disabled=true]):not([checked]):not([open]):not(:active):hover > .toolbarbutton-icon,

View File

@ -138,7 +138,7 @@ var Tabs = {
});
// Make sure the tabstrip is scrolled all the way to the left.
let scrolled = BrowserTestUtils.waitForEvent(
browserWindow.gBrowser.tabContainer,
browserWindow.gBrowser.tabContainer.arrowScrollbox,
"scrollend"
);
browserWindow.gBrowser.tabContainer.arrowScrollbox.scrollByIndex(-100);

View File

@ -35,13 +35,48 @@ must read::
where <OFFICE> is, for instance, sfo1. A complete list of office short names
to be used can be found `here <https://docs.google.com/spreadsheets/d/1alscUTcfFyu3L0vs_S_cGi9JxF4uPrfsmwJko9annWE/edit#gid=0>`_
* To use distributed sccache from a Mozilla office, you must be on the corporate
network. Use the ``Mozilla`` ssid for wireless. The corp vlan is the default
if wired.
* If you're compiling from a macOS client, there are a handful of additional
considerations detailed here:
https://github.com/mozilla/sccache/blob/master/docs/DistributedQuickstart.md#considerations-when-distributing-from-macos.
In particular, custom toolchains will need to be specified.
Run ``./mach bootstrap`` to download prebuilt toolchains and place them in
Run ``./mach bootstrap`` to download prebuilt toolchains to
``~/.mozbuild/clang-dist-toolchain.tar.xz`` and
``~/.mozbuild/rustc-dist-toolchain.tar.xz``.
``~/.mozbuild/rustc-dist-toolchain.tar.xz``. This is an example of the paths
that should be added to your client config to specify toolchains to build on
macOS, located at ``~/Library/Preferences/Mozilla.sccache/config``::
[[dist.toolchains]]
type = "path_override"
compiler_executable = "/path/to/home/.rustup/toolchains/stable-x86_64-apple-darwin/bin/rustc"
archive = "/path/to/home/.mozbuild/rustc-dist-toolchain.tar.xz"
archive_compiler_executable = "/builds/worker/toolchains/rustc/bin/rustc"
[[dist.toolchains]]
type = "path_override"
compiler_executable = "/path/to/home/.mozbuild/clang/bin/clang"
archive = "/path/to/home/.mozbuild/clang-dist-toolchain.tar.xz"
archive_compiler_executable = "/builds/worker/toolchains/clang/bin/clang"
[[dist.toolchains]]
type = "path_override"
compiler_executable = "/path/to/home/.mozbuild/clang/bin/clang++"
archive = "/path/to/home/.mozbuild/clang-dist-toolchain.tar.xz"
archive_compiler_executable = "/builds/worker/toolchains/clang/bin/clang"
Note that the version of ``rustc`` found in ``rustc-dist-toolchain.tar.xz``
must match the version of ``rustc`` used locally. The distributed archive
will contain the version of ``rustc`` used by automation builds, which may
lag behind stable for a few days after Rust releases, which is specified by
the task definition in
`this file <https://hg.mozilla.org/mozilla-central/file/tip/taskcluster/ci/toolchain/dist-toolchains.yml>`_.
For instance, to specify 1.37.0 rather than the current stable, run
``rustup toolchain add 1.37.0`` and point to
``~/.rustup/toolchains/1.37.0-x86_64-apple-darwin/bin/rustc`` in your
client config.
* Add the following to your mozconfig::
@ -79,14 +114,12 @@ similar.
* Collect the IP of your builder and request assignment of a static IP in a bug
filed in
`NetOps :: Other <https://bugzilla.mozilla.org/enter_bug.cgi?product=Infrastructure%20%26%20Operations&component=NetOps%3A%20Office%20Other>`_
This bug should include your office (SFO, YVR, etc.), your MAC address, and a
description of why you want a static IP (“To serve as an sccache builder”
should be sufficient).
* File a bug in
`Infrastructure :: Other <https://bugzilla.mozilla.org/enter_bug.cgi?product=Infrastructure+%26+Operations&component=Infrastructure%3A+Other>`_
asking for an sccache builder auth token to be generated for your builder.
The bug should include your name, office, where in the office the builder is
located (desk, closet, etc - so IT can physically find it if needed), the IP
address, and a secure method to reach you (gpg, keybase.io, etc). An auth
token will be generated and delivered to you.
* Visit the ``sccache`` section of https://login.mozilla.com to generate an auth
token for your builder.
* The instructions at https://github.com/mozilla/sccache/blob/master/docs/DistributedQuickstart.md#configure-a-build-server
should contain everything else required to configure and run the server.
@ -94,6 +127,12 @@ similar.
*NOTE* Port 10500 will be used by convention for builders in offices.
Please use port 10500 in the ``public_addr`` section of your builder config.
Extra logging may be helpful when setting up a server. To enable logging,
run your server with
``sudo env RUST_LOG=sccache=trace ~/.mozbuild/sccache/sccache-dist server --config ~/.config/sccache/server.conf``
(or similar). *NOTE* ``sudo`` *must* come before setting environment variables
for this to work.
As when configuring a client, the scheduler url to use is:
``https://sccache1.corpdmz.<OFFICE>.mozilla.com``, where <OFFICE> is an
office abbreviation found

View File

@ -51,8 +51,7 @@ function nodeLoadProperties(node: Node, actor) {
try {
const properties = await loadItemProperties(
node,
client.createObjectClient,
client.createLongStringClient,
client,
loadedProperties
);

View File

@ -37,10 +37,14 @@ import type {
Node,
} from "../types";
function loadItemProperties(
item: Node,
type Client = {
createObjectClient: CreateObjectClient,
createLongStringClient: CreateLongStringClient,
};
function loadItemProperties(
item: Node,
client: Client,
loadedProperties: LoadedProperties
): Promise<GripProperties> {
const gripItem = getClosestGripNode(item);
@ -52,7 +56,8 @@ function loadItemProperties(
const promises = [];
let objectClient;
const getObjectClient = () => objectClient || createObjectClient(value);
const getObjectClient = () =>
objectClient || client.createObjectClient(value);
if (shouldLoadItemIndexedProperties(item, loadedProperties)) {
promises.push(enumIndexedProperties(getObjectClient(), start, end));
@ -75,7 +80,7 @@ function loadItemProperties(
}
if (shouldLoadItemFullText(item, loadedProperties)) {
promises.push(getFullText(createLongStringClient(value), item));
promises.push(getFullText(client.createLongStringClient(value), item));
}
if (shouldLoadItemProxySlots(item, loadedProperties)) {

View File

@ -77,10 +77,9 @@ function createObjectClient(grip: Grip) {
async function loadObjectProperties(root: Node) {
const utils = Reps.objectInspector.utils;
const properties = await utils.loadProperties.loadItemProperties(
root,
createObjectClient
);
const properties = await utils.loadProperties.loadItemProperties(root, {
createObjectClient,
});
return utils.node.getChildren({
item: root,
loadedProperties: new Map([[root.path, properties]]),

View File

@ -3914,14 +3914,14 @@ const {
nodeIsLongString
} = __webpack_require__(114);
function loadItemProperties(item, createObjectClient, createLongStringClient, loadedProperties) {
function loadItemProperties(item, client, loadedProperties) {
const gripItem = getClosestGripNode(item);
const value = getValue(gripItem);
const [start, end] = item.meta ? [item.meta.startIndex, item.meta.endIndex] : [];
const promises = [];
let objectClient;
const getObjectClient = () => objectClient || createObjectClient(value);
const getObjectClient = () => objectClient || client.createObjectClient(value);
if (shouldLoadItemIndexedProperties(item, loadedProperties)) {
promises.push(enumIndexedProperties(getObjectClient(), start, end));
@ -3944,7 +3944,7 @@ function loadItemProperties(item, createObjectClient, createLongStringClient, lo
}
if (shouldLoadItemFullText(item, loadedProperties)) {
promises.push(getFullText(createLongStringClient(value), item));
promises.push(getFullText(client.createLongStringClient(value), item));
}
if (shouldLoadItemProxySlots(item, loadedProperties)) {
@ -7925,7 +7925,7 @@ function nodeLoadProperties(node, actor) {
}
try {
const properties = await loadItemProperties(node, client.createObjectClient, client.createLongStringClient, loadedProperties);
const properties = await loadItemProperties(node, client, loadedProperties);
dispatch(nodePropertiesLoaded(node, actor, properties));
} catch (e) {
console.error(e);

View File

@ -444,7 +444,7 @@ function loadHelperScript(filePath) {
* @return {Promise}
*/
function waitForTick() {
return new Promise(resolve => executeSoon(resolve));
return new Promise(resolve => DevToolsUtils.executeSoon(resolve));
}
/**
@ -620,7 +620,7 @@ function waitForContextMenu(popup, button, onShown, onHidden) {
// Use executeSoon() to get out of the popupshown event.
popup.addEventListener("popuphidden", onPopupHidden);
executeSoon(() => popup.hidePopup());
DevToolsUtils.executeSoon(() => popup.hidePopup());
}
function onPopupHidden() {
info("onPopupHidden");

View File

@ -20,14 +20,14 @@ const {
* `a.b.c.d.` is described as ['a', 'b', 'c', 'd'] ).
*/
function autocompleteUpdate(force, getterPath) {
return ({ dispatch, getState, services }) => {
if (services.inputHasSelection()) {
return ({ dispatch, getState, webConsoleUI, hud }) => {
if (hud.inputHasSelection()) {
return dispatch(autocompleteClear());
}
const inputValue = services.getInputValue();
const { frameActor: frameActorId, client } = services.getFrameActor();
const cursor = services.getInputCursor();
const inputValue = hud.getInputValue();
const { frameActor: frameActorId, client } = webConsoleUI.getFrameActor();
const cursor = webConsoleUI.getInputCursor();
const state = getState().autocomplete;
const { cache } = state;
@ -131,8 +131,8 @@ function autocompleteDataFetch({
client,
authorizedEvaluations,
}) {
return ({ dispatch, services }) => {
const selectedNodeActor = services.getSelectedNodeActor();
return ({ dispatch, webConsoleUI }) => {
const selectedNodeActor = webConsoleUI.getSelectedNodeActor();
const id = generateRequestId();
dispatch({ type: AUTOCOMPLETE_PENDING_REQUEST, id });
client

View File

@ -11,6 +11,8 @@ const actionModules = [
require("./messages"),
require("./ui"),
require("./notifications"),
require("./object"),
require("./toolbox"),
require("./history"),
];

View File

@ -37,9 +37,9 @@ loader.lazyRequireGetter(
const HELP_URL = "https://developer.mozilla.org/docs/Tools/Web_Console/Helpers";
function evaluateExpression(expression) {
return async ({ dispatch, services }) => {
return async ({ dispatch, webConsoleUI, hud }) => {
if (!expression) {
expression = services.getInputSelection() || services.getInputValue();
expression = hud.getInputSelection() || hud.getInputValue();
}
if (!expression) {
return null;
@ -63,7 +63,7 @@ function evaluateExpression(expression) {
let mappedExpressionRes;
try {
mappedExpressionRes = await services.getMappedExpression(expression);
mappedExpressionRes = await hud.getMappedExpression(expression);
} catch (e) {
console.warn("Error when calling getMappedExpression", e);
}
@ -72,24 +72,21 @@ function evaluateExpression(expression) {
? mappedExpressionRes.expression
: expression;
const { frameActor, client } = services.getFrameActor();
const { frameActor, client } = webConsoleUI.getFrameActor();
// Even if requestEvaluation rejects (because of webConsoleClient.evaluateJSAsync),
// Even if the evaluation fails,
// we still need to pass the error response to onExpressionEvaluated.
const onSettled = res => res;
const response = await client
.evaluateJSAsync(expression, {
frameActor,
selectedNodeActor: services.getSelectedNodeActor(),
selectedNodeActor: webConsoleUI.getSelectedNodeActor(),
mapped: mappedExpressionRes ? mappedExpressionRes.mapped : null,
})
.then(onSettled, onSettled);
return onExpressionEvaluated(response, {
dispatch,
services,
});
return dispatch(onExpressionEvaluated(response));
};
}
@ -100,81 +97,105 @@ function evaluateExpression(expression) {
* @param {Object} response
* The message received from the server.
*/
async function onExpressionEvaluated(response, { dispatch, services } = {}) {
if (response.error) {
console.error(`Evaluation error`, response.error, ": ", response.message);
return;
}
function onExpressionEvaluated(response) {
return async ({ dispatch, webConsoleUI }) => {
if (response.error) {
console.error(`Evaluation error`, response.error, ": ", response.message);
return;
}
// If the evaluation was a top-level await expression that was rejected, there will
// be an uncaught exception reported, so we don't need to do anything.
if (response.topLevelAwaitRejected === true) {
return;
}
// If the evaluation was a top-level await expression that was rejected, there will
// be an uncaught exception reported, so we don't need to do anything.
if (response.topLevelAwaitRejected === true) {
return;
}
if (!response.helperResult) {
dispatch(messagesActions.messagesAdd([response]));
return;
}
if (!response.helperResult) {
dispatch(messagesActions.messagesAdd([response]));
return;
}
await handleHelperResult(response, { dispatch, services });
await dispatch(handleHelperResult(response));
};
}
async function handleHelperResult(response, { dispatch, services }) {
const result = response.result;
const helperResult = response.helperResult;
const helperHasRawOutput = !!(helperResult || {}).rawOutput;
function handleHelperResult(response) {
return async ({ dispatch, hud, webConsoleUI }) => {
const result = response.result;
const helperResult = response.helperResult;
const helperHasRawOutput = !!(helperResult || {}).rawOutput;
if (helperResult && helperResult.type) {
switch (helperResult.type) {
case "clearOutput":
dispatch(messagesActions.messagesClear());
break;
case "clearHistory":
dispatch(historyActions.clearHistory());
break;
case "inspectObject":
services.inspectObjectActor(helperResult.object);
break;
case "help":
services.openLink(HELP_URL);
break;
case "copyValueToClipboard":
clipboardHelper.copyString(helperResult.value);
break;
case "screenshotOutput":
const { args, value } = helperResult;
const screenshotMessages = await saveScreenshot(
services.getPanelWindow(),
args,
value
);
dispatch(
messagesActions.messagesAdd(
screenshotMessages.map(message => ({
message,
type: "logMessage",
}))
)
);
// early return as we already dispatched necessary messages.
return;
if (helperResult && helperResult.type) {
switch (helperResult.type) {
case "clearOutput":
dispatch(messagesActions.messagesClear());
break;
case "clearHistory":
dispatch(historyActions.clearHistory());
break;
case "inspectObject": {
const objectActor = helperResult.object;
if (hud.toolbox) {
hud.toolbox.inspectObjectActor(objectActor);
} else {
webConsoleUI.inspectObjectActor(objectActor);
}
break;
}
case "help":
hud.openLink(HELP_URL);
break;
case "copyValueToClipboard":
clipboardHelper.copyString(helperResult.value);
break;
case "screenshotOutput":
const { args, value } = helperResult;
const screenshotMessages = await saveScreenshot(
webConsoleUI.getPanelWindow(),
args,
value
);
dispatch(
messagesActions.messagesAdd(
screenshotMessages.map(message => ({
message,
type: "logMessage",
}))
)
);
// early return as we already dispatched necessary messages.
return;
}
}
}
const hasErrorMessage =
response.exceptionMessage ||
(helperResult && helperResult.type === "error");
const hasErrorMessage =
response.exceptionMessage ||
(helperResult && helperResult.type === "error");
// Hide undefined results coming from helper functions.
const hasUndefinedResult =
result && typeof result == "object" && result.type == "undefined";
// Hide undefined results coming from helper functions.
const hasUndefinedResult =
result && typeof result == "object" && result.type == "undefined";
if (hasErrorMessage || helperHasRawOutput || !hasUndefinedResult) {
dispatch(messagesActions.messagesAdd([response]));
}
if (hasErrorMessage || helperHasRawOutput || !hasUndefinedResult) {
dispatch(messagesActions.messagesAdd([response]));
}
};
}
function focusInput() {
return ({ hud }) => {
return hud.focusInput();
};
}
function setInputValue(value) {
return ({ hud }) => {
return hud.setInputValue(value);
};
}
module.exports = {
evaluateExpression,
focusInput,
setInputValue,
};

View File

@ -104,24 +104,24 @@ function messageClose(id) {
* @return {[type]} [description]
*/
function messageGetMatchingElements(id, cssSelectors) {
return ({ dispatch, services }) => {
services
.requestEvaluation(`document.querySelectorAll('${cssSelectors}')`)
.then(response => {
dispatch(messageUpdatePayload(id, response.result));
})
.catch(err => {
console.error(err);
});
return async ({ dispatch, client }) => {
try {
const response = await client.evaluateJSAsync(
`document.querySelectorAll('${cssSelectors}')`
);
dispatch(messageUpdatePayload(id, response.result));
} catch (err) {
console.error(err);
}
};
}
function messageGetTableData(id, grip, dataType) {
return async ({ dispatch, services }) => {
return async ({ dispatch, client }) => {
const needEntries = ["Map", "WeakMap", "Set", "WeakSet"].includes(dataType);
const results = await (needEntries
? services.fetchObjectEntries(grip)
: services.fetchObjectProperties(grip, dataType === "Array"));
? client.fetchObjectEntries(grip)
: client.fetchObjectProperties(grip, dataType === "Array"));
dispatch(messageUpdatePayload(id, results));
};
@ -165,6 +165,12 @@ function networkUpdateRequest(id, data) {
};
}
function jumpToExecutionPoint(executionPoint) {
return ({ client }) => {
client.timeWarp(executionPoint);
};
}
module.exports = {
messagesAdd,
messagesClear,
@ -179,4 +185,5 @@ module.exports = {
privateMessagesClear,
// for test purpose only.
setPauseExecutionPoint,
jumpToExecutionPoint,
};

View File

@ -11,5 +11,7 @@ DevToolsModules(
'input.js',
'messages.js',
'notifications.js',
'object.js',
'toolbox.js',
'ui.js',
)

View File

@ -0,0 +1,53 @@
/* 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";
loader.lazyServiceGetter(
this,
"clipboardHelper",
"@mozilla.org/widget/clipboardhelper;1",
"nsIClipboardHelper"
);
function storeAsGlobal(actor) {
return async ({ client, hud }) => {
const evalString = `{ let i = 0;
while (this.hasOwnProperty("temp" + i) && i < 1000) {
i++;
}
this["temp" + i] = _self;
"temp" + i;
}`;
const options = {
selectedObjectActor: actor,
};
const res = await client.evaluateJSAsync(evalString, options);
hud.focusInput();
hud.setInputValue(res.result);
};
}
function copyMessageObject(actor, variableText) {
return async ({ client }) => {
if (actor) {
// The Debugger.Object of the OA will be bound to |_self| during evaluation.
// See server/actors/webconsole/eval-with-debugger.js `evalWithDebugger`.
const res = await client.evaluateJSAsync("copy(_self)", {
selectedObjectActor: actor,
});
clipboardHelper.copyString(res.helperResult.value);
} else {
clipboardHelper.copyString(variableText);
}
};
}
module.exports = {
storeAsGlobal,
copyMessageObject,
};

View File

@ -0,0 +1,22 @@
/* 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";
function openNetworkPanel(messageId) {
return ({ hud }) => {
hud.openNetworkPanel(messageId);
};
}
function resendNetworkRequest(messageId) {
return ({ hud }) => {
hud.resendNetworkRequest(messageId);
};
}
module.exports = {
openNetworkPanel,
resendNetworkRequest,
};

View File

@ -25,6 +25,12 @@ const {
EDITOR_ONBOARDING_DISMISS,
} = require("devtools/client/webconsole/constants");
function openLink(url, e) {
return ({ hud }) => {
return hud.openLink(url, e);
};
}
function persistToggle() {
return ({ dispatch, getState, prefsService }) => {
dispatch({
@ -159,6 +165,18 @@ function filterBarDisplayModeSet(displayMode) {
};
}
function openSidebar(messageId, rootActorId) {
return ({ dispatch }) => {
dispatch(showMessageObjectInSidebar(rootActorId, messageId));
};
}
function timeWarp(executionPoint) {
return ({ client }) => {
client.timeWarp(executionPoint);
};
}
module.exports = {
contentMessagesToggle,
editorOnboardingDismiss,
@ -175,4 +193,7 @@ module.exports = {
splitConsoleCloseButtonToggle,
timestampsToggle,
warningGroupsToggle,
openLink,
openSidebar,
timeWarp,
};

View File

@ -0,0 +1,59 @@
/* 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 ObjectClient = require("devtools/shared/client/object-client");
const LongStringClient = require("devtools/shared/client/long-string-client");
class ConsoleCommands {
constructor({ debuggerClient, proxy, threadFront, currentTarget }) {
this.debuggerClient = debuggerClient;
this.proxy = proxy;
this.threadFront = threadFront;
this.currentTarget = currentTarget;
}
evaluateJSAsync(expression, options) {
return this.proxy.webConsoleClient.evaluateJSAsync(expression, options);
}
createObjectClient(object) {
return new ObjectClient(this.debuggerClient, object);
}
createLongStringClient(object) {
return new LongStringClient(this.debuggerClient, object);
}
releaseActor(actor) {
if (!actor) {
return null;
}
return this.debuggerClient.release(actor);
}
async fetchObjectProperties(grip, ignoreNonIndexedProperties) {
const client = new ObjectClient(this.currentTarget.client, grip);
const { iterator } = await client.enumProperties({
ignoreNonIndexedProperties,
});
const { ownProperties } = await iterator.slice(0, iterator.count);
return ownProperties;
}
async fetchObjectEntries(grip) {
const client = new ObjectClient(this.currentTarget.client, grip);
const { iterator } = await client.enumEntries();
const { ownProperties } = await iterator.slice(0, iterator.count);
return ownProperties;
}
timeWarp(executionPoint) {
return this.threadFront.timeWarp(executionPoint);
}
}
module.exports = ConsoleCommands;

View File

@ -79,7 +79,7 @@ class Message extends Component {
timeStamp: PropTypes.number,
timestampsVisible: PropTypes.bool.isRequired,
serviceContainer: PropTypes.shape({
emitNewMessage: PropTypes.func.isRequired,
emitEvent: PropTypes.func.isRequired,
onViewSource: PropTypes.func.isRequired,
onViewSourceInDebugger: PropTypes.func,
onViewSourceInScratchpad: PropTypes.func,
@ -123,15 +123,8 @@ class Message extends Component {
if (this.props.scrollToMessage) {
this.messageNode.scrollIntoView();
}
// Event used in tests. Some message types don't pass it in because existing tests
// did not emit for them.
if (this.props.serviceContainer) {
this.props.serviceContainer.emitNewMessage(
this.messageNode,
this.props.messageId,
this.props.timeStamp
);
}
this.emitNewMessage(this.messageNode);
}
}
@ -139,6 +132,16 @@ class Message extends Component {
this.setState({ error: e });
}
// Event used in tests. Some message types don't pass it in because existing tests
// did not emit for them.
emitNewMessage(node) {
const { serviceContainer, messageId, timeStamp } = this.props;
serviceContainer.emitEvent(
"new-messages",
new Set([{ node, messageId, timeStamp }])
);
}
onLearnMoreClick(e) {
const { exceptionDocURL } = this.props;
this.props.serviceContainer.openLink(exceptionDocURL, e);
@ -182,9 +185,9 @@ class Message extends Component {
}
onMouseEvent(ev) {
const { messageId, serviceContainer, executionPoint } = this.props;
const { message, serviceContainer, executionPoint } = this.props;
if (serviceContainer.canRewind() && executionPoint) {
serviceContainer.onMessageHover(ev.type, messageId);
serviceContainer.onMessageHover(ev.type, message);
}
}

View File

@ -17,6 +17,7 @@ DIRS += [
DevToolsModules(
'browser-console-manager.js',
'browser-console.js',
'commands.js',
'constants.js',
'panel.js',
'service-container.js',

View File

@ -4,8 +4,6 @@
"use strict";
const { getMessage } = require("devtools/client/webconsole/selectors/messages");
const {
createContextMenu,
} = require("devtools/client/webconsole/utils/context-menu");
@ -14,361 +12,57 @@ const {
createEditContextMenu,
} = require("devtools/client/framework/toolbox-context-menu");
const ObjectClient = require("devtools/shared/client/object-client");
const LongStringClient = require("devtools/shared/client/long-string-client");
loader.lazyRequireGetter(
this,
"getElementText",
"devtools/client/webconsole/utils/clipboard",
true
);
// webConsoleUI
function setupServiceContainer({
webConsoleUI,
actions,
debuggerClient,
hud,
toolbox,
webconsoleWrapper,
webConsoleWrapper,
}) {
const attachRefToWebConsoleUI = (id, node) => {
webConsoleUI[id] = node;
};
const serviceContainer = {
attachRefToWebConsoleUI,
emitNewMessage: (node, messageId, timeStamp) => {
webConsoleUI.emit(
"new-messages",
new Set([
{
node,
messageId,
timeStamp,
},
])
);
},
openLink: (url, e) => {
webConsoleUI.hud.openLink(url, e);
},
canRewind: () => {
const target = webConsoleUI.hud && webConsoleUI.hud.currentTarget;
const traits = target && target.traits;
return traits && traits.canRewind;
},
createElement: nodename => {
return webconsoleWrapper.document.createElement(nodename);
},
fetchObjectProperties: async (grip, ignoreNonIndexedProperties) => {
const client = new ObjectClient(hud.currentTarget.client, grip);
const { iterator } = await client.enumProperties({
ignoreNonIndexedProperties,
});
const { ownProperties } = await iterator.slice(0, iterator.count);
return ownProperties;
},
fetchObjectEntries: async grip => {
const client = new ObjectClient(hud.currentTarget.client, grip);
const { iterator } = await client.enumEntries();
const { ownProperties } = await iterator.slice(0, iterator.count);
return ownProperties;
},
getLongString: grip => {
const proxy = webConsoleUI.getProxy();
return proxy.webConsoleClient.getString(grip);
},
requestData: (id, type) => {
return webconsoleWrapper.networkDataProvider.requestData(id, type);
},
onViewSource(frame) {
if (webConsoleUI && webConsoleUI.hud && webConsoleUI.hud.viewSource) {
webConsoleUI.hud.viewSource(frame.url, frame.line);
}
},
recordTelemetryEvent: (eventName, extra = {}) => {
webconsoleWrapper.telemetry.recordEvent(eventName, "webconsole", null, {
...extra,
session_id: (toolbox && toolbox.sessionId) || -1,
});
},
createObjectClient: object => {
return new ObjectClient(debuggerClient, object);
openContextMenu: (event, message) =>
createContextMenu(event, message, webConsoleWrapper),
openEditContextMenu: event => {
const { screenX, screenY } = event;
const menu = createEditContextMenu(window, "webconsole-menu");
// Emit the "menu-open" event for testing.
menu.once("open", () => webConsoleWrapper.emit("menu-open"));
menu.popup(screenX, screenY, hud.chromeWindow.document);
},
createLongStringClient: object => {
return new LongStringClient(debuggerClient, object);
},
releaseActor: actor => {
if (!actor) {
return null;
}
return debuggerClient.release(actor);
},
/**
* Retrieve the FrameActor ID given a frame depth, or the selected one if no
* frame depth given.
*
* @return { frameActor: String|null, client: Object }:
* frameActor is the FrameActor ID for the given frame depth
* (or the selected frame if it exists), null if no frame was found.
* client is the WebConsole client for the thread the frame is
* associated with.
*/
getFrameActor: () => {
const state = hud.getDebuggerFrames();
if (!state) {
return { frameActor: null, client: webConsoleUI.webConsoleClient };
}
const grip = state.frames[state.selected];
if (!grip) {
return { frameActor: null, client: webConsoleUI.webConsoleClient };
}
return {
frameActor: grip.actor,
client: state.target.activeConsole,
};
},
inputHasSelection: () => {
const { editor } = webConsoleUI.jsterm || {};
return editor && !!editor.getSelection();
},
getInputValue: () => {
return hud.getInputValue();
},
getInputSelection: () => {
if (!webConsoleUI.jsterm || !webConsoleUI.jsterm.editor) {
return null;
}
return webConsoleUI.jsterm.editor.getSelection();
},
setInputValue: value => {
hud.setInputValue(value);
},
focusInput: () => {
return webConsoleUI.jsterm && webConsoleUI.jsterm.focus();
},
requestEvaluation: (string, options) => {
return webConsoleUI.evaluateJSAsync(string, options);
},
getInputCursor: () => {
return webConsoleUI.jsterm && webConsoleUI.jsterm.getSelectionStart();
},
getSelectedNodeActor: () => {
const inspectorSelection = hud.getInspectorSelection();
if (inspectorSelection && inspectorSelection.nodeFront) {
return inspectorSelection.nodeFront.actorID;
}
return null;
},
getJsTermTooltipAnchor: () => {
return webConsoleUI.outputNode.querySelector(".CodeMirror-cursor");
},
getMappedExpression: hud.getMappedExpression.bind(hud),
getPanelWindow: () => webConsoleUI.window,
inspectObjectActor: objectActor => {
if (toolbox) {
toolbox.inspectObjectActor(objectActor);
} else {
webConsoleUI.inspectObjectActor(objectActor);
}
},
};
// Set `openContextMenu` this way so, `serviceContainer` variable
// is available in the current scope and we can pass it into
// `createContextMenu` method.
serviceContainer.openContextMenu = (e, message) => {
const { screenX, screenY, target } = e;
const messageEl = target.closest(".message");
const clipboardText = getElementText(messageEl);
const linkEl = target.closest("a[href]");
const url = linkEl && linkEl.href;
const messageVariable = target.closest(".objectBox");
// Ensure that console.group and console.groupCollapsed commands are not captured
const variableText =
messageVariable &&
!messageEl.classList.contains("startGroup") &&
!messageEl.classList.contains("startGroupCollapsed")
? messageVariable.textContent
: null;
// Retrieve closes actor id from the DOM.
const actorEl =
target.closest("[data-link-actor-id]") ||
target.querySelector("[data-link-actor-id]");
const actor = actorEl ? actorEl.dataset.linkActorId : null;
const rootObjectInspector = target.closest(".object-inspector");
const rootActor = rootObjectInspector
? rootObjectInspector.querySelector("[data-link-actor-id]")
: null;
const rootActorId = rootActor ? rootActor.dataset.linkActorId : null;
const sidebarTogglePref = webconsoleWrapper.getStore().getState().prefs
.sidebarToggle;
const openSidebar = sidebarTogglePref
? messageId => {
webconsoleWrapper
.getStore()
.dispatch(
actions.showMessageObjectInSidebar(rootActorId, messageId)
);
}
: null;
const messageData = getMessage(
webconsoleWrapper.getStore().getState(),
message.messageId
);
const executionPoint = messageData && messageData.executionPoint;
const menu = createContextMenu(
webconsoleWrapper.webConsoleUI,
webconsoleWrapper.parentNode,
{
actor,
clipboardText,
variableText,
message,
serviceContainer,
openSidebar,
rootActorId,
executionPoint,
toolbox: toolbox,
url,
}
);
// Emit the "menu-open" event for testing.
menu.once("open", () => webconsoleWrapper.emit("menu-open"));
menu.popup(screenX, screenY, hud.chromeWindow.document);
return menu;
};
serviceContainer.openEditContextMenu = e => {
const { screenX, screenY } = e;
const menu = createEditContextMenu(window, "webconsole-menu");
// Emit the "menu-open" event for testing.
menu.once("open", () => webconsoleWrapper.emit("menu-open"));
menu.popup(screenX, screenY, hud.chromeWindow.document);
return menu;
// NOTE these methods are proxied currently because the
// service container is passed down the tree. These methods should eventually
// be moved to redux actions.
recordTelemetryEvent: (event, extra = {}) => hud.recordEvent(event, extra),
openLink: (url, e) => hud.openLink(url, e),
openNodeInInspector: grip => hud.openNodeInInspector(grip),
getInputSelection: () => hud.getInputSelection(),
onViewSource: frame => hud.viewSource(frame.url, frame.line),
resendNetworkRequest: requestId => hud.resendNetworkRequest(requestId),
focusInput: () => hud.focusInput(),
setInputValue: value => hud.setInputValue(value),
canRewind: () => hud.canRewind(),
onMessageHover: (type, message) => webConsoleUI.onMessageHover(message),
getLongString: grip => webConsoleUI.getLongString(grip),
getJsTermTooltipAnchor: () => webConsoleUI.getJsTermTooltipAnchor(),
emitEvent: (event, value) => webConsoleUI.emit(event, value),
attachRefToWebConsoleUI: (id, node) => webConsoleUI.attachRef(id, node),
requestData: (id, type) => webConsoleWrapper.requestData(id, type),
createElement: nodename => webConsoleWrapper.createElement(nodename),
};
if (toolbox) {
const { highlight, unhighlight } = toolbox.getHighlighter(true);
Object.assign(serviceContainer, {
onViewSourceInDebugger: frame => {
toolbox
.viewSourceInDebugger(
frame.url,
frame.line,
frame.column,
frame.sourceId
)
.then(() => {
webconsoleWrapper.telemetry.recordEvent(
"jump_to_source",
"webconsole",
null,
{
session_id: toolbox.sessionId,
}
);
webconsoleWrapper.webConsoleUI.emit("source-in-debugger-opened");
});
},
onViewSourceInScratchpad: frame =>
toolbox.viewSourceInScratchpad(frame.url, frame.line).then(() => {
webconsoleWrapper.telemetry.recordEvent(
"jump_to_source",
"webconsole",
null,
{
session_id: toolbox.sessionId,
}
);
}),
onViewSourceInStyleEditor: frame =>
toolbox
.viewSourceInStyleEditor(frame.url, frame.line, frame.column)
.then(() => {
webconsoleWrapper.telemetry.recordEvent(
"jump_to_source",
"webconsole",
null,
{
session_id: toolbox.sessionId,
}
);
}),
openNetworkPanel: requestId => {
return toolbox.selectTool("netmonitor").then(panel => {
return panel.panelWin.Netmonitor.inspectRequest(requestId);
});
},
resendNetworkRequest: requestId => {
return toolbox.getNetMonitorAPI().then(api => {
return api.resendRequest(requestId);
});
},
sourceMapService: toolbox ? toolbox.sourceMapURLService : null,
sourceMapService: toolbox.sourceMapURLService,
highlightDomElement: highlight,
unHighlightDomElement: unhighlight,
openNodeInInspector: async grip => {
const onSelectInspector = toolbox.selectTool(
"inspector",
"inspect_dom"
);
// TODO: Bug1574506 - Use the contextual WalkerFront for gripToNodeFront.
const walkerFront = (await toolbox.target.getFront("inspector")).walker;
const onGripNodeToFront = walkerFront.gripToNodeFront(grip);
const [front, inspector] = await Promise.all([
onGripNodeToFront,
onSelectInspector,
]);
const onInspectorUpdated = inspector.once("inspector-updated");
const onNodeFrontSet = toolbox.selection.setNodeFront(front, {
reason: "console",
});
return Promise.all([onNodeFrontSet, onInspectorUpdated]);
},
jumpToExecutionPoint: executionPoint =>
toolbox.threadFront.timeWarp(executionPoint),
onMessageHover: (type, messageId) => {
const message = getMessage(
webconsoleWrapper.getStore().getState(),
messageId
);
webconsoleWrapper.webConsoleUI.emit("message-hover", type, message);
},
onViewSourceInDebugger: frame => hud.onViewSourceInDebugger(frame),
onViewSourceInStyleEditor: frame => hud.onViewSourceInStyleEditor(frame),
onViewSourceInScratchpad: frame => hud.onViewSourceInScratchpad(frame),
});
}

View File

@ -84,19 +84,10 @@ function configureStore(webConsoleUI, options = {}) {
}),
};
// Prepare middleware.
const services = options.services || {};
const middleware = applyMiddleware(
thunkWithOptions.bind(null, {
prefsService,
services,
// Needed for the ObjectInspector
client: {
createObjectClient: services.createObjectClient,
createLongStringClient: services.createLongStringClient,
releaseActor: services.releaseActor,
},
...options.thunkArgs,
}),
historyPersistence,
eventTelemetry.bind(null, options.telemetry, options.sessionId)

View File

@ -22,7 +22,7 @@ async function clickFirstStackElement(hud, message, needsExpansion) {
EventUtils.sendMouseEvent({ type: "mousedown" }, frame);
await once(hud.ui, "source-in-debugger-opened");
await once(hud, "source-in-debugger-opened");
}
// Test that stack/message links in console API and error messages originating

View File

@ -47,7 +47,7 @@ async function testOpenInDebugger(hud, toolbox, text) {
async function checkClickOnNode(hud, toolbox, frameNode) {
info("checking click on node location");
const onSourceInDebuggerOpened = once(hud.ui, "source-in-debugger-opened");
const onSourceInDebuggerOpened = once(hud, "source-in-debugger-opened");
EventUtils.sendMouseEvent(
{ type: "mousedown" },
frameNode.querySelector(".location")

View File

@ -469,7 +469,7 @@ async function checkClickOnNode(
) {
info("checking click on node location");
const onSourceInDebuggerOpened = once(hud.ui, "source-in-debugger-opened");
const onSourceInDebuggerOpened = once(hud, "source-in-debugger-opened");
EventUtils.sendMouseEvent(
{ type: "click" },

View File

@ -6,7 +6,7 @@
module.exports = {
attachRefToWebConsoleUI: () => {},
canRewind: () => false,
emitNewMessage: () => {},
emitEvent: () => {},
proxy: {
client: {},
releaseActor: actor => console.log("Release actor", actor),

View File

@ -11,6 +11,7 @@ const { MESSAGE_SOURCE } = require("devtools/client/webconsole/constants");
const clipboardHelper = require("devtools/shared/platform/clipboard");
const { l10n } = require("devtools/client/webconsole/utils/messages");
const actions = require("devtools/client/webconsole/actions/index");
loader.lazyRequireGetter(this, "saveAs", "devtools/shared/DevToolsUtils", true);
loader.lazyRequireGetter(
@ -29,48 +30,54 @@ loader.lazyRequireGetter(
/**
* Create a Menu instance for the webconsole.
*
* @param {WebConsoleUI} webConsoleUI
* The webConsoleUI instance.
* @param {Element} parentNode
* The container of the new console frontend output wrapper.
* @param {Event} context menu event
* {Object} message (optional) message object containing metadata such as:
* - {String} source
* - {String} request
* @param {Object} options
* - {String} actor (optional) actor id to use for context menu actions
* - {String} clipboardText (optional) text to "Copy" if no selection is available
* - {String} variableText (optional) which is the textual frontend
* representation of the variable
* - {Object} message (optional) message object containing metadata such as:
* - {String} source
* - {String} request
* - {Function} openSidebar (optional) function that will open the object
* inspector sidebar
* - {String} rootActorId (optional) actor id for the root object being clicked on
* - {Object} executionPoint (optional) when replaying, the execution point where
* this message was logged
* - {Actions} bound actions
* - {WebConsoleWrapper} wrapper instance used for accessing properties like the store
* and window.
*/
function createContextMenu(
webConsoleUI,
parentNode,
{
actor,
clipboardText,
variableText,
message,
serviceContainer,
openSidebar,
rootActorId,
executionPoint,
toolbox,
url,
}
) {
function createContextMenu(event, message, webConsoleWrapper) {
const { target } = event;
const { parentNode, toolbox, hud } = webConsoleWrapper;
const store = webConsoleWrapper.getStore();
const { dispatch } = store;
const messageEl = target.closest(".message");
const clipboardText = getElementText(messageEl);
const linkEl = target.closest("a[href]");
const url = linkEl && linkEl.href;
const messageVariable = target.closest(".objectBox");
// Ensure that console.group and console.groupCollapsed commands are not captured
const variableText =
messageVariable &&
!messageEl.classList.contains("startGroup") &&
!messageEl.classList.contains("startGroupCollapsed")
? messageVariable.textContent
: null;
// Retrieve closes actor id from the DOM.
const actorEl =
target.closest("[data-link-actor-id]") ||
target.querySelector("[data-link-actor-id]");
const actor = actorEl ? actorEl.dataset.linkActorId : null;
const rootObjectInspector = target.closest(".object-inspector");
const rootActor = rootObjectInspector
? rootObjectInspector.querySelector("[data-link-actor-id]")
: null;
const rootActorId = rootActor ? rootActor.dataset.linkActorId : null;
const win = parentNode.ownerDocument.defaultView;
const selection = win.getSelection();
const { source, request } = message || {};
const { source, request, executionPoint, messageId } = message || {};
const menu = new Menu({
id: "webconsole-menu",
});
const menu = new Menu({ id: "webconsole-menu" });
// Copy URL for a network request.
menu.append(
@ -89,20 +96,20 @@ function createContextMenu(
);
// Open Network message in the Network panel.
if (serviceContainer.openNetworkPanel && request) {
if (toolbox && request) {
menu.append(
new MenuItem({
id: "console-menu-open-in-network-panel",
label: l10n.getStr("webconsole.menu.openInNetworkPanel.label"),
accesskey: l10n.getStr("webconsole.menu.openInNetworkPanel.accesskey"),
visible: source === MESSAGE_SOURCE.NETWORK,
click: () => serviceContainer.openNetworkPanel(message.messageId),
click: () => dispatch(actions.openNetworkPanel(message.messageId)),
})
);
}
// Resend Network message.
if (serviceContainer.resendNetworkRequest && request) {
if (toolbox && request) {
menu.append(
new MenuItem({
id: "console-menu-resend-network-request",
@ -111,7 +118,7 @@ function createContextMenu(
"webconsole.menu.resendNetworkRequest.accesskey"
),
visible: source === MESSAGE_SOURCE.NETWORK,
click: () => serviceContainer.resendNetworkRequest(message.messageId),
click: () => dispatch(actions.resendNetworkRequest(messageId)),
})
);
}
@ -139,23 +146,7 @@ function createContextMenu(
label: l10n.getStr("webconsole.menu.storeAsGlobalVar.label"),
accesskey: l10n.getStr("webconsole.menu.storeAsGlobalVar.accesskey"),
disabled: !actor,
click: () => {
const evalString = `{ let i = 0;
while (this.hasOwnProperty("temp" + i) && i < 1000) {
i++;
}
this["temp" + i] = _self;
"temp" + i;
}`;
const options = {
selectedObjectActor: actor,
};
webConsoleUI.evaluateJSAsync(evalString, options).then(res => {
webConsoleUI.jsterm.focus();
webConsoleUI.hud.setInputValue(res.result);
});
},
click: () => dispatch(actions.storeAsGlobal(actor)),
})
);
@ -187,21 +178,7 @@ function createContextMenu(
accesskey: l10n.getStr("webconsole.menu.copyObject.accesskey"),
// Disabled if there is no actor and no variable text associated.
disabled: !actor && !variableText,
click: () => {
if (actor) {
// The Debugger.Object of the OA will be bound to |_self| during evaluation.
// See server/actors/webconsole/eval-with-debugger.js `evalWithDebugger`.
webConsoleUI
.evaluateJSAsync("copy(_self)", {
selectedObjectActor: actor,
})
.then(res => {
clipboardHelper.copyString(res.helperResult.value);
});
} else {
clipboardHelper.copyString(variableText);
}
},
click: () => dispatch(actions.copyMessageObject(actor, variableText)),
})
);
@ -266,14 +243,15 @@ function createContextMenu(
);
// Open object in sidebar.
if (openSidebar) {
const shouldOpenSidebar = store.getState().prefs.sidebarToggle;
if (shouldOpenSidebar) {
menu.append(
new MenuItem({
id: "console-menu-open-sidebar",
label: l10n.getStr("webconsole.menu.openInSidebar.label"),
accesskey: l10n.getStr("webconsole.menu.openInSidebar.accesskey"),
disabled: !rootActorId,
click: () => openSidebar(message.messageId),
click: () => dispatch(actions.openSidebar(messageId, rootActorId)),
})
);
}
@ -285,10 +263,7 @@ function createContextMenu(
id: "console-menu-time-warp",
label: l10n.getStr("webconsole.menu.timeWarp.label"),
disabled: false,
click: () => {
const threadFront = toolbox.threadFront;
threadFront.timeWarp(executionPoint);
},
click: () => dispatch(actions.jumpToExecutionPoint(executionPoint)),
})
);
}
@ -304,6 +279,11 @@ function createContextMenu(
);
}
// Emit the "menu-open" event for testing.
const { screenX, screenY } = event;
menu.once("open", () => webConsoleWrapper.emit("menu-open"));
menu.popup(screenX, screenY, hud.chromeWindow.document);
return menu;
}

View File

@ -51,6 +51,7 @@ class WebConsoleUI {
this.hud = hud;
this.hudId = this.hud.hudId;
this.isBrowserConsole = this.hud.isBrowserConsole;
this.isBrowserToolboxConsole =
this.hud.currentTarget &&
this.hud.currentTarget.isParentProcess &&
@ -230,6 +231,10 @@ class WebConsoleUI {
return this.wrapper;
}
getPanelWindow() {
return this.window;
}
logWarningAboutReplacedAPI() {
return this.hud.currentTarget.logWarningInPage(
l10n.getStr("ConsoleAPIDisabled"),
@ -442,6 +447,10 @@ class WebConsoleUI {
);
}
getLongString(grip) {
this.getProxy().webConsoleClient.getString(grip);
}
/**
* Sets the focus to JavaScript input field when the web console tab is
* selected or when there is a split console present.
@ -481,6 +490,58 @@ class WebConsoleUI {
handleTabWillNavigate(packet) {
this.wrapper.dispatchTabWillNavigate(packet);
}
getInputCursor() {
return this.jsterm && this.jsterm.getSelectionStart();
}
getJsTermTooltipAnchor() {
return this.outputNode.querySelector(".CodeMirror-cursor");
}
attachRef(id, node) {
this[id] = node;
}
/**
* Retrieve the FrameActor ID given a frame depth, or the selected one if no
* frame depth given.
*
* @return { frameActor: String|null, client: Object }:
* frameActor is the FrameActor ID for the given frame depth
* (or the selected frame if it exists), null if no frame was found.
* client is the WebConsole client for the thread the frame is
* associated with.
*/
getFrameActor() {
const state = this.hud.getDebuggerFrames();
if (!state) {
return { frameActor: null, client: this.webConsoleClient };
}
const grip = state.frames[state.selected];
if (!grip) {
return { frameActor: null, client: this.webConsoleClient };
}
return {
frameActor: grip.actor,
client: state.target.activeConsole,
};
}
getSelectedNodeActor() {
const inspectorSelection = this.hud.getInspectorSelection();
if (inspectorSelection && inspectorSelection.nodeFront) {
return inspectorSelection.nodeFront.actorID;
}
return null;
}
onMessageHover(type, message) {
this.emit("message-hover", type, message);
}
}
/* This is the same as DevelopmentHelpers.quickRestart, but it runs in all

View File

@ -27,6 +27,7 @@ const Telemetry = require("devtools/client/shared/telemetry");
const EventEmitter = require("devtools/shared/event-emitter");
const App = createFactory(require("devtools/client/webconsole/components/App"));
const DataProvider = require("devtools/client/netmonitor/src/connector/firefox-data-provider");
const ConsoleCommands = require("devtools/client/webconsole/commands.js");
const {
setupServiceContainer,
@ -73,30 +74,38 @@ class WebConsoleWrapper {
const debuggerClient = this.hud.currentTarget.client;
const webConsoleClient = await this.hud.currentTarget.getFront("console");
this.networkDataProvider = new DataProvider({
actions: {
updateRequest: (id, data) => {
return this.batchedRequestUpdates({ id, data });
},
updateRequest: (id, data) => this.batchedRequestUpdates({ id, data }),
},
webConsoleClient,
});
return new Promise(resolve => {
const serviceContainer = setupServiceContainer({
const commands = new ConsoleCommands({
debuggerClient,
webConsoleUI,
actions,
toolbox: this.toolbox,
hud: this.hud,
webconsoleWrapper: this,
proxy: webConsoleUI.getProxy(),
threadFront: this.toolbox && this.toolbox.threadFront,
currentTarget: this.hud.currentTarget,
});
store = configureStore(this.webConsoleUI, {
// We may not have access to the toolbox (e.g. in the browser console).
sessionId: (this.toolbox && this.toolbox.sessionId) || -1,
telemetry: this.telemetry,
services: serviceContainer,
thunkArgs: {
webConsoleUI,
hud: this.hud,
client: commands,
},
});
const serviceContainer = setupServiceContainer({
webConsoleUI,
toolbox: this.toolbox,
hud: this.hud,
webConsoleWrapper: this,
});
if (this.toolbox) {
@ -336,6 +345,10 @@ class WebConsoleWrapper {
this.setTimeoutIfNeeded();
}
requestData(id, type) {
this.networkDataProvider.requestData(id, type);
}
dispatchClearLogpointMessages(logpointId) {
store.dispatch(actions.messagesClearLogpoint(logpointId));
}
@ -434,6 +447,10 @@ class WebConsoleWrapper {
store.subscribe(() => callback(store.getState()));
}
createElement(nodename) {
return this.document.createElement(nodename);
}
// Called by pushing close button.
closeSplitConsole() {
this.toolbox.closeSplitConsole();

View File

@ -40,6 +40,7 @@ loader.lazyRequireGetter(
"devtools/shared/DevToolsUtils"
);
const EventEmitter = require("devtools/shared/event-emitter");
const Telemetry = require("devtools/client/shared/telemetry");
var gHudId = 0;
const isMacOS = Services.appinfo.OS === "Darwin";
@ -71,6 +72,7 @@ class WebConsole {
this.hudId = "hud_" + ++gHudId;
this.browserWindow = DevToolsUtils.getTopWindow(this.chromeWindow);
this.isBrowserConsole = isBrowserConsole;
this.telemetry = new Telemetry();
const element = this.browserWindow.document.documentElement;
if (element.getAttribute("windowtype") != gDevTools.chromeWindowType) {
@ -84,6 +86,13 @@ class WebConsole {
EventEmitter.decorate(this);
}
recordEvent(event, extra = {}) {
this.telemetry.recordEvent(event, "webconsole", null, {
session_id: (this.toolbox && this.toolbox.sessionId) || -1,
...extra,
});
}
get currentTarget() {
return this.toolbox.target;
}
@ -126,6 +135,12 @@ class WebConsole {
return this.ui ? this.ui.jsterm : null;
}
canRewind() {
const target = this.hud && this.hud.currentTarget;
const traits = target && target.traits;
return traits && traits.canRewind;
}
/**
* Get the value from the input field.
* @returns {String|null} returns null if there's no input.
@ -138,6 +153,18 @@ class WebConsole {
return this.jsterm._getValue();
}
inputHasSelection() {
const { editor } = this.jsterm || {};
return editor && !!editor.getSelection();
}
getInputSelection() {
if (!this.jsterm || !this.jsterm.editor) {
return null;
}
return this.jsterm.editor.getSelection();
}
/**
* Sets the value of the input field (command line)
*
@ -151,6 +178,10 @@ class WebConsole {
this.jsterm._setValue(newValue);
}
focusInput() {
return this.jsterm && this.jsterm.focus();
}
/**
* Open a link in a new tab.
*
@ -217,17 +248,15 @@ class WebConsole {
* @param integer sourceColumn
* The column number which you want to place the caret.
*/
viewSourceInDebugger(sourceURL, sourceLine, sourceColumn) {
async viewSourceInDebugger(sourceURL, sourceLine, sourceColumn) {
const toolbox = this.toolbox;
if (!toolbox) {
this.viewSource(sourceURL, sourceLine, sourceColumn);
return;
}
toolbox
.viewSourceInDebugger(sourceURL, sourceLine, sourceColumn)
.then(() => {
this.ui.emit("source-in-debugger-opened");
});
await toolbox.viewSourceInDebugger(sourceURL, sourceLine, sourceColumn);
this.ui.emit("source-in-debugger-opened");
}
/**
@ -350,6 +379,82 @@ class WebConsole {
return panel.selection;
}
async onViewSourceInDebugger(frame) {
if (this.toolbox) {
await this.toolbox.viewSourceInDebugger(
frame.url,
frame.line,
frame.column,
frame.sourceId
);
this.recordEvent("jump_to_source");
this.emit("source-in-debugger-opened");
}
}
async onViewSourceInScratchpad(frame) {
if (this.toolbox) {
await this.toolbox.viewSourceInScratchpad(frame.url, frame.line);
this.recordEvent("jump_to_source");
}
}
async onViewSourceInStyleEditor(frame) {
if (!this.toolbox) {
return;
}
await this.toolbox.viewSourceInStyleEditor(
frame.url,
frame.line,
frame.column
);
this.recordEvent("jump_to_source");
}
async openNetworkPanel(requestId) {
if (!this.toolbox) {
return;
}
const netmonitor = await this.toolbox.selectTool("netmonitor");
await netmonitor.panelWin.Netmonitor.inspectRequest(requestId);
}
async resendNetworkRequest(requestId) {
if (!this.toolbox) {
return;
}
const api = await this.toolbox.getNetMonitorAPI();
await api.resendRequest(requestId);
}
async openNodeInInspector(grip) {
if (!this.toolbox) {
return;
}
const onSelectInspector = this.toolbox.selectTool(
"inspector",
"inspect_dom"
);
// TODO: Bug1574506 - Use the contextual WalkerFront for gripToNodeFront.
const walkerFront = (await this.toolbox.target.getFront("inspector"))
.walker;
const onGripNodeToFront = walkerFront.gripToNodeFront(grip);
const [front, inspector] = await Promise.all([
onGripNodeToFront,
onSelectInspector,
]);
const onInspectorUpdated = inspector.once("inspector-updated");
const onNodeFrontSet = this.toolbox.selection.setNodeFront(front, {
reason: "console",
});
await Promise.all([onNodeFrontSet, onInspectorUpdated]);
}
/**
* Destroy the object. Call this method to avoid memory leaks when the Web
* Console is closed.

View File

@ -371,16 +371,6 @@ class CustomElementRegistry final : public nsISupports, public nsWrapperCache {
};
public:
/**
* Returns whether there's a definition that is likely to match this type
* atom. This is not exact, so should only be used for optimization, but it's
* good enough to prove that the chrome code doesn't need an XBL binding.
*/
bool IsLikelyToBeCustomElement(nsAtom* aTypeAtom) const {
return mCustomDefinitions.GetWeak(aTypeAtom) ||
mElementCreationCallbacks.GetWeak(aTypeAtom);
}
/**
* Looking up a custom element definition.
* https://html.spec.whatwg.org/#look-up-a-custom-element-definition

View File

@ -517,51 +517,25 @@ void Element::ClearStyleStateLocks() {
NotifyStyleStateChange(locks.mLocks);
}
static bool IsLikelyCustomElement(const nsXULElement& aElement) {
const CustomElementData* data = aElement.GetCustomElementData();
if (!data) {
static bool MayNeedToLoadXBLBinding(const Element& aElement) {
if (!aElement.IsAnyOfXULElements(nsGkAtoms::panel, nsGkAtoms::textbox)) {
// Other elements no longer have XBL bindings. Please don't add to the list
// above unless completely necessary.
return false;
}
const CustomElementRegistry* registry =
nsContentUtils::GetCustomElementRegistry(aElement.OwnerDoc());
if (!registry) {
if (!aElement.IsInComposedDoc()) {
return false;
}
return registry->IsLikelyToBeCustomElement(data->GetCustomElementType());
}
static bool MayNeedToLoadXBLBinding(const Document& aDocument,
const Element& aElement) {
// If we have a frame, the frame has already loaded the binding.
// Otherwise, don't do anything else here unless we're dealing with
// XUL or an HTML element that may have a plugin-related overlay
// (i.e. object or embed).
if (!aDocument.GetPresShell() || aElement.GetPrimaryFrame()) {
if (aElement.GetPrimaryFrame() || !aElement.OwnerDoc()->GetPresShell()) {
return false;
}
if (auto* xulElem = nsXULElement::FromNode(aElement)) {
return !IsLikelyCustomElement(*xulElem);
// If we have a binding, well..
if (aElement.GetXBLBinding()) {
return false;
}
return aElement.IsAnyOfHTMLElements(nsGkAtoms::object, nsGkAtoms::embed);
}
StyleUrlOrNone Element::GetBindingURL(Document* aDocument) {
if (!MayNeedToLoadXBLBinding(*aDocument, *this)) {
return StyleUrlOrNone::None();
}
// Get the computed -moz-binding directly from the ComputedStyle
RefPtr<ComputedStyle> style =
nsComputedDOMStyle::GetComputedStyleNoFlush(this, nullptr);
if (!style) {
return StyleUrlOrNone::None();
}
return style->StyleDisplay()->mBinding;
// We need to try.
return true;
}
JSObject* Element::WrapObject(JSContext* aCx,
@ -571,60 +545,43 @@ JSObject* Element::WrapObject(JSContext* aCx,
return nullptr;
}
if (XRE_IsContentProcess() && !NodePrincipal()->IsSystemPrincipal()) {
// We don't use XBL in content privileged content processes.
if (!MayNeedToLoadXBLBinding(*this)) {
return obj;
}
Document* doc = GetComposedDoc();
if (!doc) {
// There's no baseclass that cares about this call so we just
// return here.
return obj;
}
// We must ensure that the XBL Binding is installed before we hand
// back this object.
if (HasFlag(NODE_MAY_BE_IN_BINDING_MNGR) && GetXBLBinding()) {
// There's already a binding for this element so nothing left to
// be done here.
// In theory we could call ExecuteAttachedHandler here when it's safe to
// run script if we also removed the binding from the PAQ queue, but that
// seems like a scary change that would mosly just add more
// inconsistencies.
return obj;
}
// Make sure the ComputedStyle goes away _before_ we load the binding
// since that can destroy the relevant presshell.
{
StyleUrlOrNone result = GetBindingURL(doc);
if (result.IsUrl()) {
auto& url = result.AsUrl();
nsCOMPtr<nsIURI> uri = url.GetURI();
nsCOMPtr<nsIPrincipal> principal = url.ExtraData().Principal();
RefPtr<ComputedStyle> style =
nsComputedDOMStyle::GetComputedStyleNoFlush(this, nullptr);
if (!style) {
return obj;
}
// We have a binding that must be installed.
nsXBLService* xblService = nsXBLService::GetInstance();
if (!xblService) {
dom::Throw(aCx, NS_ERROR_NOT_AVAILABLE);
return nullptr;
}
// We have a binding that must be installed.
const StyleUrlOrNone& computedBinding = style->StyleDisplay()->mBinding;
if (!computedBinding.IsUrl()) {
return obj;
}
RefPtr<nsXBLBinding> binding;
xblService->LoadBindings(this, uri, principal, getter_AddRefs(binding));
auto& url = computedBinding.AsUrl();
nsCOMPtr<nsIURI> uri = url.GetURI();
nsCOMPtr<nsIPrincipal> principal = url.ExtraData().Principal();
if (binding) {
if (nsContentUtils::IsSafeToRunScript()) {
binding->ExecuteAttachedHandler();
} else {
nsContentUtils::AddScriptRunner(
NewRunnableMethod("nsXBLBinding::ExecuteAttachedHandler", binding,
&nsXBLBinding::ExecuteAttachedHandler));
}
nsXBLService* xblService = nsXBLService::GetInstance();
if (!xblService) {
dom::Throw(aCx, NS_ERROR_NOT_AVAILABLE);
return nullptr;
}
RefPtr<nsXBLBinding> binding;
xblService->LoadBindings(this, uri, principal, getter_AddRefs(binding));
if (binding) {
if (nsContentUtils::IsSafeToRunScript()) {
binding->ExecuteAttachedHandler();
} else {
nsContentUtils::AddScriptRunner(
NewRunnableMethod("nsXBLBinding::ExecuteAttachedHandler", binding,
&nsXBLBinding::ExecuteAttachedHandler));
}
}
}

View File

@ -459,8 +459,6 @@ class Element : public FragmentOrElement {
}
}
mozilla::StyleUrlOrNone GetBindingURL(Document* aDocument);
Directionality GetComputedDirectionality() const;
static const uint32_t kAllServoDescendantBits =

View File

@ -90,8 +90,8 @@ DrawEventRecorderMemory::DrawEventRecorderMemory() {
}
DrawEventRecorderMemory::DrawEventRecorderMemory(
const SerializeResourcesFn& aFn, IntPoint aOrigin)
: mSerializeCallback(aFn), mOrigin(aOrigin) {
const SerializeResourcesFn& aFn)
: mSerializeCallback(aFn) {
mExternalFonts = !!mSerializeCallback;
WriteHeader(mOutputStream);
}
@ -132,7 +132,6 @@ bool DrawEventRecorderMemory::Finish() {
mIndex = MemStream();
// write out the offset of the Index to the end of the output stream
WriteElement(mOutputStream, indexOffset);
WriteElement(mOutputStream, mOrigin);
ClearResources();
return hasItems;
}

View File

@ -179,8 +179,7 @@ class DrawEventRecorderMemory : public DrawEventRecorderPrivate {
* Constructs a DrawEventRecorder that stores the recording in memory.
*/
DrawEventRecorderMemory();
explicit DrawEventRecorderMemory(const SerializeResourcesFn& aSerialize,
IntPoint aOrigin = IntPoint());
explicit DrawEventRecorderMemory(const SerializeResourcesFn& aSerialize);
void RecordEvent(const RecordedEvent& aEvent) override;
@ -217,7 +216,6 @@ class DrawEventRecorderMemory : public DrawEventRecorderPrivate {
private:
SerializeResourcesFn mSerializeCallback;
nsTHashtable<nsUint64HashKey> mDependentSurfaces;
IntPoint mOrigin;
void Flush() override;
};

View File

@ -659,8 +659,7 @@ struct DIGroup {
aStream.write((const char*)&font, sizeof(font));
}
fonts = std::move(aScaledFonts);
},
mVisibleRect.ToUnknownRect().TopLeft());
});
RefPtr<gfx::DrawTarget> dummyDt = gfx::Factory::CreateDrawTarget(
gfx::BackendType::SKIA, gfx::IntSize(1, 1), format);
@ -2259,8 +2258,7 @@ WebRenderCommandBuilder::GenerateFallbackData(
aStream.write((const char*)&font, sizeof(font));
}
fonts = std::move(aScaledFonts);
},
visibleRect.ToUnknownRect().TopLeft());
});
RefPtr<gfx::DrawTarget> dummyDt = gfx::Factory::CreateDrawTarget(
gfx::BackendType::SKIA, gfx::IntSize(1, 1), format);
RefPtr<gfx::DrawTarget> dt = gfx::Factory::CreateRecordingDrawTarget(
@ -2491,8 +2489,7 @@ Maybe<wr::ImageMask> WebRenderCommandBuilder::BuildWrMaskImage(
}
fonts = std::move(aScaledFonts);
},
IntPoint(0, 0));
});
RefPtr<DrawTarget> dummyDt = Factory::CreateDrawTarget(
BackendType::SKIA, IntSize(1, 1), SurfaceFormat::A8);

View File

@ -9,7 +9,6 @@
#include "mozilla/gfx/DrawEventRecorder.h"
#include "mozilla/gfx/InlineTranslator.h"
#include "mozilla/webrender/webrender_ffi.h"
#include "mozilla/gfx/Point.h"
namespace mozilla {
namespace layers {
@ -24,8 +23,8 @@ class WebRenderDrawEventRecorder final : public gfx::DrawEventRecorderMemory {
MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(WebRenderDrawEventRecorder, final)
explicit WebRenderDrawEventRecorder(
const gfx::SerializeResourcesFn& aSerialize, gfx::IntPoint aOrigin)
: DrawEventRecorderMemory(aSerialize, aOrigin) {}
const gfx::SerializeResourcesFn& aSerialize)
: DrawEventRecorderMemory(aSerialize) {}
void StoreSourceSurfaceRecording(gfx::SourceSurface* aSurface,
const char* aReason) final;

View File

@ -367,7 +367,7 @@ static bool Moz2DRenderCallback(const Range<const uint8_t> aBlob,
// We try hard to not have empty blobs but we can end up with
// them because of CompositorHitTestInfo and merging.
size_t footerSize = sizeof(size_t) + sizeof(IntPoint);
size_t footerSize = sizeof(size_t);
MOZ_RELEASE_ASSERT(aBlob.length() >= footerSize);
size_t indexOffset = ConvertFromBytes<size_t>(aBlob.end().get() - footerSize);

View File

@ -172,7 +172,6 @@ struct BlobReader<'a> {
reader: BufReader<'a>,
/// Where the buffer head is.
begin: usize,
origin: IntPoint,
}
#[derive(PartialEq, Debug, Eq, Clone, Copy)]
@ -200,12 +199,11 @@ impl<'a> BlobReader<'a> {
/// Creates a new BlobReader for the given buffer.
fn new(buf: &'a[u8]) -> BlobReader<'a> {
// The offset of the index is at the end of the buffer.
let index_offset_pos = buf.len()-(mem::size_of::<usize>() + mem::size_of::<IntPoint>());
let index_offset_pos = buf.len()-mem::size_of::<usize>();
assert!(index_offset_pos < buf.len());
let index_offset = unsafe { convert_from_bytes::<usize>(&buf[index_offset_pos..]) };
let origin = unsafe { convert_from_bytes(&buf[(index_offset_pos + mem::size_of::<usize>())..]) };
BlobReader { reader: BufReader::new(&buf[index_offset..index_offset_pos]), begin: 0, origin }
BlobReader { reader: BufReader::new(&buf[index_offset..index_offset_pos]), begin: 0}
}
/// Reads the next display item's metadata.
@ -251,13 +249,12 @@ impl BlobWriter {
}
/// Completes the blob image, producing a single buffer containing it.
fn finish(mut self, origin: IntPoint) -> Vec<u8> {
fn finish(mut self) -> Vec<u8> {
// Append the index to the end of the buffer
// and then append the offset to the beginning of the index.
let index_begin = self.data.len();
self.data.extend_from_slice(&self.index);
self.data.extend_from_slice(convert_to_bytes(&index_begin));
self.data.extend_from_slice(convert_to_bytes(&origin));
self.data
}
}
@ -461,7 +458,7 @@ fn merge_blob_images(old_buf: &[u8], new_buf: &[u8], dirty_rect: Box2d, old_visi
//assert!(old_reader.cache.is_empty());
let result = result.finish(new_reader.origin);
let result = result.finish();
dump_index(&result);
result
}

File diff suppressed because one or more lines are too long

View File

@ -2241,9 +2241,9 @@
}
},
"esprima": {
"version": "2.7.3",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz",
"integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=",
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
"dev": true
},
"esrecurse": {
@ -3949,13 +3949,13 @@
"dev": true
},
"js-yaml": {
"version": "3.7.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.7.0.tgz",
"integrity": "sha1-XJZ93YN6m/3KXy3oQlOr6KHAO4A=",
"version": "3.13.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz",
"integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==",
"dev": true,
"requires": {
"argparse": "^1.0.7",
"esprima": "^2.6.0"
"esprima": "^4.0.0"
}
},
"jsesc": {
@ -4083,9 +4083,9 @@
}
},
"lodash": {
"version": "4.17.11",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
"integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==",
"version": "4.17.15",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
"dev": true
},
"lodash.camelcase": {
@ -4430,9 +4430,9 @@
"dev": true
},
"mixin-deep": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz",
"integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==",
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz",
"integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==",
"dev": true,
"requires": {
"for-in": "^1.0.2",
@ -6122,9 +6122,9 @@
"dev": true
},
"set-value": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz",
"integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==",
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz",
"integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==",
"dev": true,
"requires": {
"extend-shallow": "^2.0.1",
@ -6645,6 +6645,24 @@
"mkdirp": "~0.5.1",
"sax": "~1.2.1",
"whet.extend": "~0.9.9"
},
"dependencies": {
"esprima": {
"version": "2.7.3",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz",
"integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=",
"dev": true
},
"js-yaml": {
"version": "3.7.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.7.0.tgz",
"integrity": "sha1-XJZ93YN6m/3KXy3oQlOr6KHAO4A=",
"dev": true,
"requires": {
"argparse": "^1.0.7",
"esprima": "^2.6.0"
}
}
}
},
"tapable": {
@ -6789,38 +6807,15 @@
}
},
"union-value": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz",
"integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=",
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz",
"integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==",
"dev": true,
"requires": {
"arr-union": "^3.1.0",
"get-value": "^2.0.6",
"is-extendable": "^0.1.1",
"set-value": "^0.4.3"
},
"dependencies": {
"extend-shallow": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
"integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
"dev": true,
"requires": {
"is-extendable": "^0.1.0"
}
},
"set-value": {
"version": "0.4.3",
"resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz",
"integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=",
"dev": true,
"requires": {
"extend-shallow": "^2.0.1",
"is-extendable": "^0.1.1",
"is-plain-object": "^2.0.1",
"to-object-path": "^0.3.0"
}
}
"set-value": "^2.0.1"
}
},
"uniq": {

View File

@ -0,0 +1,44 @@
/* 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/. */
// Composite a picture cache tile into the framebuffer.
#include shared
varying vec2 vUv;
flat varying vec4 vColor;
flat varying float vLayer;
#ifdef WR_VERTEX_SHADER
in vec4 aDeviceRect;
in vec4 aDeviceClipRect;
in vec4 aColor;
in float aLayer;
in float aZId;
void main(void) {
// Get world position
vec2 world_pos = aDeviceRect.xy + aPosition.xy * aDeviceRect.zw;
// Clip the position to the world space clip rect
vec2 clipped_world_pos = clamp(world_pos, aDeviceClipRect.xy, aDeviceClipRect.xy + aDeviceClipRect.zw);
// Derive the normalized UV from the clipped vertex position
vUv = (clipped_world_pos - aDeviceRect.xy) / aDeviceRect.zw;
// Pass through color and texture array layer
vColor = aColor;
vLayer = aLayer;
gl_Position = uTransform * vec4(clipped_world_pos, aZId, 1.0);
}
#endif
#ifdef WR_FRAGMENT_SHADER
void main(void) {
// The color is just the texture sample modulated by a supplied color
vec4 texel = textureLod(sColor0, vec3(vUv, vLayer), 0.0);
vec4 color = vColor * texel;
write_output(color);
}
#endif

View File

@ -7,6 +7,7 @@ use api::{YuvColorSpace, YuvFormat, ColorDepth, ColorRange, PremultipliedColorF,
use api::units::*;
use crate::clip::{ClipDataStore, ClipNodeFlags, ClipNodeRange, ClipItemKind, ClipStore};
use crate::clip_scroll_tree::{ClipScrollTree, ROOT_SPATIAL_NODE_INDEX, SpatialNodeIndex, CoordinateSystemId};
use crate::composite::{CompositeConfig, CompositeTile, CompositeTileSurface};
use crate::glyph_rasterizer::GlyphFormat;
use crate::gpu_cache::{GpuBlockData, GpuCache, GpuCacheHandle, GpuCacheAddress};
use crate::gpu_types::{BrushFlags, BrushInstance, PrimitiveHeaders, ZBufferId, ZBufferIdGenerator};
@ -17,7 +18,7 @@ use crate::internal_types::{FastHashMap, SavedTargetIndex, Swizzle, TextureSourc
use crate::picture::{Picture3DContext, PictureCompositeMode, PicturePrimitive, TileSurface};
use crate::prim_store::{DeferredResolve, EdgeAaSegmentMask, PrimitiveInstanceKind, PrimitiveVisibilityIndex, PrimitiveVisibilityMask};
use crate::prim_store::{VisibleGradientTile, PrimitiveInstance, PrimitiveOpacity, SegmentInstanceIndex};
use crate::prim_store::{BrushSegment, ClipMaskKind, ClipTaskIndex, VECS_PER_SEGMENT};
use crate::prim_store::{BrushSegment, ClipMaskKind, ClipTaskIndex, VECS_PER_SEGMENT, SpaceMapper};
use crate::prim_store::image::ImageSource;
use crate::render_backend::DataStores;
use crate::render_target::RenderTargetContext;
@ -644,6 +645,7 @@ impl BatchBuilder {
root_spatial_node_index: SpatialNodeIndex,
surface_spatial_node_index: SpatialNodeIndex,
z_generator: &mut ZBufferIdGenerator,
composite_config: &mut CompositeConfig,
) {
for cluster in &pic.prim_list.clusters {
// Add each run in this picture to the batch.
@ -660,6 +662,7 @@ impl BatchBuilder {
root_spatial_node_index,
surface_spatial_node_index,
z_generator,
composite_config,
);
}
}
@ -682,6 +685,7 @@ impl BatchBuilder {
root_spatial_node_index: SpatialNodeIndex,
surface_spatial_node_index: SpatialNodeIndex,
z_generator: &mut ZBufferIdGenerator,
composite_config: &mut CompositeConfig,
) {
if prim_instance.visibility_info == PrimitiveVisibilityIndex::INVALID {
return;
@ -1174,8 +1178,16 @@ impl BatchBuilder {
match raster_config.composite_mode {
PictureCompositeMode::TileCache { .. } => {
// Tile cache instances are added to the composite config, rather than
// directly added to batches. This allows them to be drawn with various
// present modes during render, such as partial present etc.
let tile_cache = picture.tile_cache.as_ref().unwrap();
let map_local_to_world = SpaceMapper::new_with_target(
ROOT_SPATIAL_NODE_INDEX,
tile_cache.spatial_node_index,
ctx.screen_world_rect,
ctx.clip_scroll_tree,
);
let local_tile_clip_rect = LayoutRect::from_untyped(&tile_cache.local_rect.to_untyped());
let local_tile_clip_rect = match local_tile_clip_rect.intersection(&prim_info.combined_local_clip_rect) {
Some(rect) => rect,
@ -1183,140 +1195,41 @@ impl BatchBuilder {
return;
}
};
// Add tile cache instances in two passes.
// 1) Add all color/rectangle instances.
// 2) Add remaining texture/image instances.
// This ensures we batch together as many solid rectangles
// as possible, which typically reduces the written pixel
// count on most pages significantly.
// TODO(gw): We could consider separating the tiles_to_draw
// array, to make this a bit tidier.
let world_clip_rect = map_local_to_world
.map(&local_tile_clip_rect)
.expect("bug: unable to map clip rect");
let device_clip_rect = (world_clip_rect * ctx.global_device_pixel_scale).round();
let z_id = composite_config.z_generator.next();
for key in &tile_cache.tiles_to_draw {
let tile = &tile_cache.tiles[key];
let device_rect = (tile.world_rect * ctx.global_device_pixel_scale).round();
let surface = tile.surface.as_ref().expect("no tile surface set!");
let (surface, is_opaque) = match surface {
TileSurface::Color { color } => {
(CompositeTileSurface::Color { color: *color }, true)
}
TileSurface::Texture { handle, .. } => {
let cache_item = ctx.resource_cache.texture_cache.get(handle);
if let TileSurface::Color { color } = surface {
debug_assert!(tile.is_valid);
let local_tile_rect = LayoutRect::from_untyped(&tile.rect.to_untyped());
let batch_params = BrushBatchParameters::shared(
BrushBatchKind::Solid,
BatchTextures::no_texture(),
[get_shader_opacity(1.0), 0, 0, 0],
0,
);
// TODO(gw): Maybe we could retain this GPU cache handle inside
// the tile to avoid pushing per-frame GPU cache blocks.
let gpu_blocks = [
color.premultiplied().into(),
];
let gpu_handle = gpu_cache.push_per_frame_blocks(&gpu_blocks);
let prim_cache_address = gpu_cache.get_address(&gpu_handle);
let opacity = PrimitiveOpacity::opaque();
let blend_mode = BlendMode::None;
let prim_header = PrimitiveHeader {
local_rect: local_tile_rect,
local_clip_rect: local_tile_clip_rect,
specific_prim_address: prim_cache_address,
transform_id,
};
let prim_header_index = prim_headers.push(
&prim_header,
z_id,
batch_params.prim_user_data,
);
self.add_segmented_prim_to_batch(
None,
opacity,
&batch_params,
blend_mode,
blend_mode,
batch_features,
prim_header_index,
bounding_rect,
transform_kind,
render_tasks,
z_id,
prim_info.clip_task_index,
prim_vis_mask,
ctx,
);
}
}
for key in &tile_cache.tiles_to_draw {
let tile = &tile_cache.tiles[key];
let surface = tile.surface.as_ref().expect("no tile surface set!");
if let TileSurface::Texture { ref handle, .. } = surface {
debug_assert!(tile.is_valid);
let local_tile_rect = LayoutRect::from_untyped(&tile.rect.to_untyped());
let cache_item = ctx.resource_cache.texture_cache.get(handle);
let uv_rect_address = gpu_cache
.get_address(&cache_item.uv_rect_handle)
.as_int();
let batch_params = BrushBatchParameters::shared(
BrushBatchKind::Image(ImageBufferKind::Texture2DArray),
BatchTextures::color(cache_item.texture_id),
[
ShaderColorMode::Image as i32 | ((AlphaType::PremultipliedAlpha as i32) << 16),
RasterizationSpace::Local as i32,
get_shader_opacity(1.0),
0,
],
uv_rect_address,
);
let (opacity, blend_mode) = if tile.is_opaque || tile_cache.is_opaque() {
(
PrimitiveOpacity::opaque(),
BlendMode::None,
CompositeTileSurface::Texture {
texture_id: cache_item.texture_id,
texture_layer: cache_item.texture_layer,
},
tile.is_opaque || tile_cache.is_opaque(),
)
} else {
(
PrimitiveOpacity::translucent(),
BlendMode::PremultipliedAlpha,
)
};
let prim_header = PrimitiveHeader {
local_rect: local_tile_rect,
local_clip_rect: local_tile_clip_rect,
specific_prim_address: prim_cache_address,
transform_id,
};
let prim_header_index = prim_headers.push(
&prim_header,
z_id,
batch_params.prim_user_data,
);
self.add_segmented_prim_to_batch(
None,
opacity,
&batch_params,
blend_mode,
blend_mode,
batch_features,
prim_header_index,
bounding_rect,
transform_kind,
render_tasks,
z_id,
prim_info.clip_task_index,
prim_vis_mask,
ctx,
);
}
};
let composite_tile = CompositeTile {
surface,
rect: device_rect,
clip_rect: device_clip_rect,
z_id,
};
if is_opaque {
composite_config.opaque_tiles.push(composite_tile);
} else {
composite_config.alpha_tiles.push(composite_tile);
}
}
}
@ -1787,6 +1700,7 @@ impl BatchBuilder {
root_spatial_node_index,
surface_spatial_node_index,
z_generator,
composite_config,
);
}
}

View File

@ -0,0 +1,55 @@
/* 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 api::ColorF;
use api::units::DeviceRect;
use crate::gpu_types::{ZBufferId, ZBufferIdGenerator};
use crate::internal_types::TextureSource;
/*
Types and definitions related to compositing picture cache tiles
and/or OS compositor integration.
*/
/// Describes the source surface information for a tile to be composited
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub enum CompositeTileSurface {
Texture {
texture_id: TextureSource,
texture_layer: i32,
},
Color {
color: ColorF,
},
}
/// Describes the geometry and surface of a tile to be composited
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub struct CompositeTile {
pub surface: CompositeTileSurface,
pub rect: DeviceRect,
pub clip_rect: DeviceRect,
pub z_id: ZBufferId,
}
/// The list of tiles to be drawn this frame
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub struct CompositeConfig {
pub opaque_tiles: Vec<CompositeTile>,
pub alpha_tiles: Vec<CompositeTile>,
pub z_generator: ZBufferIdGenerator,
}
impl CompositeConfig {
pub fn new() -> Self {
CompositeConfig {
opaque_tiles: Vec::new(),
alpha_tiles: Vec::new(),
z_generator: ZBufferIdGenerator::new(0),
}
}
}

View File

@ -8,6 +8,7 @@ use api::units::*;
use crate::batch::{BatchBuilder, AlphaBatchBuilder, AlphaBatchContainer};
use crate::clip::{ClipStore, ClipChainStack};
use crate::clip_scroll_tree::{ClipScrollTree, ROOT_SPATIAL_NODE_INDEX, SpatialNodeIndex};
use crate::composite::CompositeConfig;
use crate::debug_render::DebugItem;
use crate::gpu_cache::{GpuCache, GpuCacheHandle};
use crate::gpu_types::{PrimitiveHeaders, TransformPalette, UvRectKind, ZBufferIdGenerator};
@ -496,6 +497,7 @@ impl FrameBuilder {
let mut deferred_resolves = vec![];
let mut has_texture_cache_tasks = false;
let mut prim_headers = PrimitiveHeaders::new();
let mut composite_config = CompositeConfig::new();
{
profile_marker!("Batching");
@ -538,6 +540,7 @@ impl FrameBuilder {
&mut transform_palette,
&mut prim_headers,
&mut z_generator,
&mut composite_config,
);
match pass.kind {
@ -580,6 +583,7 @@ impl FrameBuilder {
recorded_dirty_regions: mem::replace(&mut scratch.recorded_dirty_regions, Vec::new()),
dirty_rects: mem::replace(&mut scratch.dirty_rects, Vec::new()),
debug_items: mem::replace(&mut scratch.debug_items, Vec::new()),
composite_config,
}
}
}
@ -599,6 +603,7 @@ pub fn build_render_pass(
transforms: &mut TransformPalette,
prim_headers: &mut PrimitiveHeaders,
z_generator: &mut ZBufferIdGenerator,
composite_config: &mut CompositeConfig,
) {
profile_scope!("RenderPass::build");
@ -624,6 +629,7 @@ pub fn build_render_pass(
prim_headers,
transforms,
z_generator,
composite_config,
);
}
RenderPassKind::OffScreen {
@ -821,6 +827,7 @@ pub fn build_render_pass(
root_spatial_node_index,
surface_spatial_node_index,
z_generator,
composite_config,
);
// Create picture cache targets, one per render task, and assign
@ -875,6 +882,7 @@ pub fn build_render_pass(
prim_headers,
transforms,
z_generator,
composite_config,
);
alpha.build(
ctx,
@ -885,6 +893,7 @@ pub fn build_render_pass(
prim_headers,
transforms,
z_generator,
composite_config,
);
}
}
@ -937,6 +946,11 @@ pub struct Frame {
/// Debugging information to overlay for this frame.
pub debug_items: Vec<DebugItem>,
/// Contains picture cache tiles, and associated information.
/// Used by the renderer to composite tiles into the framebuffer,
/// or hand them off to an OS compositor.
pub composite_config: CompositeConfig,
}
impl Frame {

View File

@ -212,6 +212,35 @@ impl ResolveInstanceData {
}
}
/// Vertex format for picture cache composite shader.
#[derive(Debug, Clone)]
#[repr(C)]
pub struct CompositeInstance {
rect: DeviceRect,
clip_rect: DeviceRect,
color: PremultipliedColorF,
layer: f32,
z_id: f32,
}
impl CompositeInstance {
pub fn new(
rect: DeviceRect,
clip_rect: DeviceRect,
color: PremultipliedColorF,
layer: f32,
z_id: ZBufferId,
) -> Self {
CompositeInstance {
rect,
clip_rect,
color,
layer,
z_id: z_id.0 as f32,
}
}
}
#[derive(Debug, Copy, Clone)]
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]

View File

@ -270,6 +270,9 @@ pub enum TextureSource {
/// passes, these are not made available automatically, but are instead
/// opt-in by the `RenderTask` (see `mark_for_saving()`).
RenderTaskCache(SavedTargetIndex, Swizzle),
/// Select a dummy 1x1 white texture. This can be used by image
/// shaders that want to draw a solid color.
Dummy,
}
// See gpu_types.rs where we declare the number of possible documents and

View File

@ -82,6 +82,7 @@ mod box_shadow;
mod capture;
mod clip;
mod clip_scroll_tree;
mod composite;
mod debug_colors;
mod debug_font_data;
mod debug_render;

View File

@ -9,6 +9,7 @@ use crate::batch::{AlphaBatchBuilder, AlphaBatchContainer, BatchTextures, resolv
use crate::batch::{ClipBatcher, BatchBuilder};
use crate::clip_scroll_tree::{ClipScrollTree, ROOT_SPATIAL_NODE_INDEX};
use crate::clip::ClipStore;
use crate::composite::CompositeConfig;
use crate::device::Texture;
use crate::frame_builder::{FrameGlobalResources};
use crate::gpu_cache::{GpuCache, GpuCacheAddress};
@ -101,6 +102,7 @@ pub trait RenderTarget {
_prim_headers: &mut PrimitiveHeaders,
_transforms: &mut TransformPalette,
_z_generator: &mut ZBufferIdGenerator,
_composite_config: &mut CompositeConfig,
) {
}
@ -201,6 +203,7 @@ impl<T: RenderTarget> RenderTargetList<T> {
prim_headers: &mut PrimitiveHeaders,
transforms: &mut TransformPalette,
z_generator: &mut ZBufferIdGenerator,
composite_config: &mut CompositeConfig,
) {
debug_assert_eq!(None, self.saved_index);
self.saved_index = saved_index;
@ -214,6 +217,7 @@ impl<T: RenderTarget> RenderTargetList<T> {
prim_headers,
transforms,
z_generator,
composite_config,
);
}
}
@ -332,6 +336,7 @@ impl RenderTarget for ColorRenderTarget {
prim_headers: &mut PrimitiveHeaders,
transforms: &mut TransformPalette,
z_generator: &mut ZBufferIdGenerator,
composite_config: &mut CompositeConfig,
) {
let mut merged_batches = AlphaBatchContainer::new(None);
@ -399,6 +404,7 @@ impl RenderTarget for ColorRenderTarget {
raster_spatial_node_index,
pic_task.surface_spatial_node_index,
z_generator,
composite_config,
);
let alpha_batch_builders = batch_builder.finalize();

View File

@ -47,6 +47,7 @@ use api::channel::{MsgSender, PayloadReceiverHelperMethods};
use crate::batch::{AlphaBatchContainer, BatchKind, BatchFeatures, BatchTextures, BrushBatchKind, ClipBatchList};
#[cfg(any(feature = "capture", feature = "replay"))]
use crate::capture::{CaptureConfig, ExternalCaptureImage, PlainExternalImage};
use crate::composite::{CompositeConfig, CompositeTileSurface, CompositeTile};
use crate::debug_colors;
use crate::debug_render::{DebugItem, DebugRenderer};
use crate::device::{DepthFunction, Device, GpuFrameId, Program, UploadMethod, Texture, PBO};
@ -62,7 +63,8 @@ use crate::glyph_cache::GlyphCache;
use crate::glyph_rasterizer::{GlyphFormat, GlyphRasterizer};
use crate::gpu_cache::{GpuBlockData, GpuCacheUpdate, GpuCacheUpdateList};
use crate::gpu_cache::{GpuCacheDebugChunk, GpuCacheDebugCmd};
use crate::gpu_types::{PrimitiveHeaderI, PrimitiveHeaderF, ScalingInstance, SvgFilterInstance, TransformData, ResolveInstanceData};
use crate::gpu_types::{PrimitiveHeaderI, PrimitiveHeaderF, ScalingInstance, SvgFilterInstance, TransformData};
use crate::gpu_types::{CompositeInstance, ResolveInstanceData};
use crate::internal_types::{TextureSource, ORTHO_FAR_PLANE, ORTHO_NEAR_PLANE, ResourceCacheError};
use crate::internal_types::{CacheTextureId, DebugOutput, FastHashMap, FastHashSet, LayerIndex, RenderedDocument, ResultMsg};
use crate::internal_types::{TextureCacheAllocationKind, TextureCacheUpdate, TextureUpdateList, TextureUpdateSource};
@ -224,6 +226,10 @@ const GPU_TAG_SVG_FILTER: GpuProfileTag = GpuProfileTag {
label: "SvgFilter",
color: debug_colors::LEMONCHIFFON,
};
const GPU_TAG_COMPOSITE: GpuProfileTag = GpuProfileTag {
label: "Composite",
color: debug_colors::TOMATO,
};
/// The clear color used for the texture cache when the debug display is enabled.
/// We use a shade of blue so that we can still identify completely blue items in
@ -796,6 +802,43 @@ pub(crate) mod desc {
},
],
};
pub const COMPOSITE: VertexDescriptor = VertexDescriptor {
vertex_attributes: &[
VertexAttribute {
name: "aPosition",
count: 2,
kind: VertexAttributeKind::F32,
},
],
instance_attributes: &[
VertexAttribute {
name: "aDeviceRect",
count: 4,
kind: VertexAttributeKind::F32,
},
VertexAttribute {
name: "aDeviceClipRect",
count: 4,
kind: VertexAttributeKind::F32,
},
VertexAttribute {
name: "aColor",
count: 4,
kind: VertexAttributeKind::F32,
},
VertexAttribute {
name: "aLayer",
count: 1,
kind: VertexAttributeKind::F32,
},
VertexAttribute {
name: "aZId",
count: 1,
kind: VertexAttributeKind::F32,
},
],
};
}
#[derive(Debug, Copy, Clone)]
@ -811,6 +854,7 @@ pub(crate) enum VertexArrayKind {
Gradient,
Resolve,
SvgFilter,
Composite,
}
#[derive(Clone, Debug, PartialEq)]
@ -959,6 +1003,10 @@ impl TextureResolver {
None,
1,
);
device.upload_texture_immediate(
&dummy_cache_texture,
&[0xff, 0xff, 0xff, 0xff],
);
TextureResolver {
texture_cache_map: FastHashMap::default(),
@ -1068,6 +1116,11 @@ impl TextureResolver {
TextureSource::Invalid => {
Swizzle::default()
}
TextureSource::Dummy => {
let swizzle = Swizzle::default();
device.bind_texture(sampler, &self.dummy_cache_texture, swizzle);
swizzle
}
TextureSource::PrevPassAlpha => {
let texture = match self.prev_pass_alpha {
Some(ref at) => &at.texture,
@ -1129,6 +1182,9 @@ impl TextureResolver {
fn resolve(&self, texture_id: &TextureSource) -> Option<(&Texture, Swizzle)> {
match *texture_id {
TextureSource::Invalid => None,
TextureSource::Dummy => {
Some((&self.dummy_cache_texture, Swizzle::default()))
}
TextureSource::PrevPassAlpha => Some((
match self.prev_pass_alpha {
Some(ref at) => &at.texture,
@ -1666,6 +1722,7 @@ pub struct RendererVAOs {
gradient_vao: VAO,
resolve_vao: VAO,
svg_filter_vao: VAO,
composite_vao: VAO,
}
@ -2012,6 +2069,7 @@ impl Renderer {
let gradient_vao = device.create_vao_with_new_instances(&desc::GRADIENT, &prim_vao);
let resolve_vao = device.create_vao_with_new_instances(&desc::RESOLVE, &prim_vao);
let svg_filter_vao = device.create_vao_with_new_instances(&desc::SVG_FILTER, &prim_vao);
let composite_vao = device.create_vao_with_new_instances(&desc::COMPOSITE, &prim_vao);
let texture_cache_upload_pbo = device.create_pbo();
let texture_resolver = TextureResolver::new(&mut device);
@ -2249,6 +2307,7 @@ impl Renderer {
resolve_vao,
line_vao,
svg_filter_vao,
composite_vao,
},
transforms_texture,
prim_header_i_texture,
@ -3773,6 +3832,111 @@ impl Renderer {
}
}
/// Draw a list of tiles to the framebuffer
fn draw_tile_list<'a, I: Iterator<Item = &'a CompositeTile>>(
&mut self,
tiles_iter: I,
stats: &mut RendererStats,
) {
let mut current_textures = BatchTextures::no_texture();
let mut instances = Vec::new();
for tile in tiles_iter {
// Work out the draw params based on the tile surface
let (texture, layer, color) = match tile.surface {
CompositeTileSurface::Color { color } => {
(TextureSource::Dummy, 0.0, color)
}
CompositeTileSurface::Texture { texture_id, texture_layer } => {
(texture_id, texture_layer as f32, ColorF::WHITE)
}
};
let textures = BatchTextures::color(texture);
// Flush this batch if the textures aren't compatible
if !current_textures.is_compatible_with(&textures) {
self.draw_instanced_batch(
&instances,
VertexArrayKind::Composite,
&current_textures,
stats,
);
instances.clear();
}
current_textures = textures;
// Create the instance and add to current batch
let instance = CompositeInstance::new(
tile.rect,
tile.clip_rect,
color.premultiplied(),
layer,
tile.z_id,
);
instances.push(instance);
}
// Flush the last batch
if !instances.is_empty() {
self.draw_instanced_batch(
&instances,
VertexArrayKind::Composite,
&current_textures,
stats,
);
}
}
/// Composite picture cache tiles into the framebuffer. This is currently
/// the only way that picture cache tiles get drawn. In future, the tiles
/// will often be handed to the OS compositor, and this method will be
/// rarely used.
fn composite(
&mut self,
composite_config: &CompositeConfig,
draw_target: DrawTarget,
projection: &default::Transform3D<f32>,
stats: &mut RendererStats,
) {
let _gm = self.gpu_profile.start_marker("framebuffer");
let _timer = self.gpu_profile.start_timer(GPU_TAG_COMPOSITE);
self.device.bind_draw_target(draw_target);
self.device.enable_depth();
self.shaders.borrow_mut().composite.bind(
&mut self.device,
&projection,
&mut self.renderer_errors
);
// Draw opaque tiles first, front-to-back to get maxmum
// z-reject efficiency.
if !composite_config.opaque_tiles.is_empty() {
let opaque_sampler = self.gpu_profile.start_sampler(GPU_SAMPLER_TAG_OPAQUE);
self.device.enable_depth_write();
self.set_blend(false, FramebufferKind::Main);
self.draw_tile_list(
composite_config.opaque_tiles.iter().rev(),
stats,
);
self.gpu_profile.finish_sampler(opaque_sampler);
}
// Draw alpha tiles
if !composite_config.alpha_tiles.is_empty() {
let transparent_sampler = self.gpu_profile.start_sampler(GPU_SAMPLER_TAG_TRANSPARENT);
self.device.disable_depth_write();
self.set_blend(true, FramebufferKind::Main);
self.set_blend_mode_premultiplied_alpha(FramebufferKind::Main);
self.draw_tile_list(
composite_config.alpha_tiles.iter(),
stats,
);
self.gpu_profile.finish_sampler(transparent_sampler);
}
}
fn draw_color_target(
&mut self,
draw_target: DrawTarget,
@ -4556,7 +4720,7 @@ impl Renderer {
);
match pass.kind {
RenderPassKind::MainFramebuffer { ref main_target, .. } => {
RenderPassKind::MainFramebuffer { .. } => {
if let Some(device_size) = device_size {
stats.color_target_count += 1;
@ -4587,15 +4751,10 @@ impl Renderer {
None);
}
self.draw_color_target(
self.composite(
&frame.composite_config,
draw_target,
main_target,
frame.content_origin,
None,
None,
&frame.render_tasks,
&projection,
frame_id,
stats,
);
}
@ -5245,6 +5404,7 @@ impl Renderer {
self.device.delete_vao(self.vaos.border_vao);
self.device.delete_vao(self.vaos.scale_vao);
self.device.delete_vao(self.vaos.svg_filter_vao);
self.device.delete_vao(self.vaos.composite_vao);
self.debug.deinit(&mut self.device);
@ -6097,6 +6257,7 @@ fn get_vao<'a>(vertex_array_kind: VertexArrayKind,
VertexArrayKind::Gradient => &vaos.gradient_vao,
VertexArrayKind::Resolve => &vaos.resolve_vao,
VertexArrayKind::SvgFilter => &vaos.svg_filter_vao,
VertexArrayKind::Composite => &vaos.composite_vao,
}
}
#[derive(Clone, Copy, PartialEq)]

View File

@ -66,6 +66,7 @@ pub(crate) enum ShaderKind {
#[allow(dead_code)]
VectorCover,
Resolve,
Composite,
}
pub struct LazilyCompiledShader {
@ -156,6 +157,13 @@ impl LazilyCompiledShader {
&self.features,
)
}
ShaderKind::Composite => {
create_prim_shader(
self.name,
device,
&self.features,
)
}
ShaderKind::ClipCache => {
create_clip_shader(
self.name,
@ -179,6 +187,7 @@ impl LazilyCompiledShader {
ShaderKind::VectorCover => VertexArrayKind::VectorCover,
ShaderKind::ClipCache => VertexArrayKind::Clip,
ShaderKind::Resolve => VertexArrayKind::Resolve,
ShaderKind::Composite => VertexArrayKind::Composite,
};
let vertex_descriptor = match vertex_format {
@ -193,6 +202,7 @@ impl LazilyCompiledShader {
VertexArrayKind::Scale => &desc::SCALE,
VertexArrayKind::Resolve => &desc::RESOLVE,
VertexArrayKind::SvgFilter => &desc::SVG_FILTER,
VertexArrayKind::Composite => &desc::COMPOSITE,
};
device.link_program(program, vertex_descriptor)?;
@ -549,6 +559,13 @@ pub struct Shaders {
pub pls_resolve: LazilyCompiledShader,
ps_split_composite: LazilyCompiledShader,
// Composite shader. This is a very simple shader used to composite
// picture cache tiles into the framebuffer. In future, this will
// only be used on platforms that aren't directly handing picture
// cache surfaces to an OS compositor, such as DirectComposite or
// CoreAnimation.
pub composite: LazilyCompiledShader,
}
impl Shaders {
@ -848,6 +865,14 @@ impl Shaders {
options.precache_flags,
)?;
let composite = LazilyCompiledShader::new(
ShaderKind::Composite,
"composite",
&[],
device,
options.precache_flags,
)?;
Ok(Shaders {
cs_blur_a8,
cs_blur_rgba8,
@ -874,6 +899,7 @@ impl Shaders {
ps_text_run,
ps_text_run_dual_source,
ps_split_composite,
composite,
})
}
@ -975,6 +1001,7 @@ impl Shaders {
self.cs_line_decoration.deinit(device);
self.cs_border_segment.deinit(device);
self.ps_split_composite.deinit(device);
self.composite.deinit(device);
}
}

View File

@ -959,7 +959,6 @@ void nsSubDocumentFrame::DestroyFrom(nsIFrame* aDestructRoot,
::BeginSwapDocShellsForViews(mInnerView->GetFirstChild());
if (detachedViews && detachedViews->GetFrame()) {
MOZ_ASSERT(mContent->OwnerDoc());
frameloader->SetDetachedSubdocFrame(detachedViews->GetFrame(),
mContent->OwnerDoc());

View File

@ -4212,7 +4212,7 @@ pref("network.connectivity-service.IPv4.url", "http://detectportal.firefox.com/s
pref("network.connectivity-service.IPv6.url", "http://detectportal.firefox.com/success.txt?ipv6");
// DNS Trusted Recursive Resolver
// 0 - default off, 1 - race, 2 TRR first, 3 TRR only, 4 shadow, 5 off by choice
// 0 - default off, 1 - reserved/off, 2 - TRR first, 3 - TRR only, 4 - reserved/off, 5 off by choice
pref("network.trr.mode", 0);
// DNS-over-HTTP service to use, must be HTTPS://
pref("network.trr.uri", "https://mozilla.cloudflare-dns.com/dns-query");

View File

@ -78,7 +78,8 @@ registerCleanupFunction(() => {
Services.prefs.clearUserPref("network.trr.confirmationNS");
Services.prefs.clearUserPref("network.trr.bootstrapAddress");
Services.prefs.clearUserPref("network.trr.blacklist-duration");
Services.prefs.clearUserPref("network.trr.request-timeout");
Services.prefs.clearUserPref("network.trr.request_timeout_ms");
Services.prefs.clearUserPref("network.trr.request_timeout_mode_trronly_ms");
Services.prefs.clearUserPref("network.trr.disable-ECS");
Services.prefs.clearUserPref("network.trr.early-AAAA");
Services.prefs.clearUserPref("network.trr.skip-AAAA-when-not-supported");
@ -479,7 +480,7 @@ add_task(async function test11() {
"network.trr.uri",
`https://foo.example.com:${h2Port}/dns-750ms`
);
Services.prefs.setIntPref("network.trr.request-timeout", 10);
Services.prefs.setIntPref("network.trr.request_timeout_mode_trronly_ms", 10);
let [, , inStatus] = await new DNSListener(
"test11.example.com",
undefined,
@ -495,11 +496,13 @@ add_task(async function test11() {
add_task(async function test12() {
dns.clearCache(true);
Services.prefs.setIntPref("network.trr.mode", 2); // TRR-first
Services.prefs.setIntPref("network.trr.request_timeout_ms", 10);
Services.prefs.setCharPref(
"network.trr.uri",
`https://foo.example.com:${h2Port}/doh?responseIP=none`
);
Services.prefs.clearUserPref("network.trr.request-timeout");
Services.prefs.clearUserPref("network.trr.request_timeout_ms");
Services.prefs.clearUserPref("network.trr.request_timeout_mode_trronly_ms");
await new DNSListener("confirm.example.com", "127.0.0.1");
});
@ -540,7 +543,8 @@ add_task(async function test15() {
"network.trr.uri",
`https://foo.example.com:${h2Port}/dns-750ms`
);
Services.prefs.setIntPref("network.trr.request-timeout", 10);
Services.prefs.setIntPref("network.trr.request_timeout_ms", 10);
Services.prefs.setIntPref("network.trr.request_timeout_mode_trronly_ms", 10);
await new DNSListener("test15.example.com", "127.0.0.1");
Services.prefs.setIntPref("network.trr.mode", 4); // MODE_RESERVED4. Interpreted as TRR off.
@ -559,7 +563,8 @@ add_task(async function test16() {
"network.trr.uri",
`https://foo.example.com:${h2Port}/dns-750ms`
);
Services.prefs.setIntPref("network.trr.request-timeout", 10);
Services.prefs.setIntPref("network.trr.request_timeout_ms", 10);
Services.prefs.setIntPref("network.trr.request_timeout_mode_trronly_ms", 10);
await new DNSListener("test16.example.com", "127.0.0.1");
});
@ -571,7 +576,8 @@ add_task(async function test17() {
"network.trr.uri",
`https://foo.example.com:${h2Port}/dns-cname`
);
Services.prefs.clearUserPref("network.trr.request-timeout");
Services.prefs.clearUserPref("network.trr.request_timeout_ms");
Services.prefs.clearUserPref("network.trr.request_timeout_mode_trronly_ms");
await new DNSListener("cname.example.com", "99.88.77.66");
});
@ -921,6 +927,8 @@ add_task(async function test_connection_closed() {
dns.clearCache(true);
Services.prefs.setIntPref("network.trr.mode", 3); // TRR-only
Services.prefs.setCharPref("network.trr.excluded-domains", "");
// We don't need to wait for 30 seconds for the request to fail
Services.prefs.setIntPref("network.trr.request_timeout_mode_trronly_ms", 500);
Services.prefs.setCharPref(
"network.trr.uri",
`https://foo.example.com:${h2Port}/doh?responseIP=2.2.2.2`

View File

@ -1452,26 +1452,14 @@ FxAccountsInternal.prototype = {
log.debug("startVerifiedCheck with user data", data);
}
// Get us to the verified state, then get the keys. This returns a promise
// that will fire when we are completely ready.
//
// Login is truly complete once keys have been fetched, so once getKeys()
// obtains and stores kSync kXCS kExtSync and kExtKbHash, it will fire the
// onverified observer notification.
// Get us to the verified state. This returns a promise that will fire when
// verification is complete.
// The callers of startVerifiedCheck never consume a returned promise (ie,
// this is simply kicking off a background fetch) so we must add a rejection
// handler to avoid runtime warnings about the rejection not being handled.
this.whenVerified(data).then(
() => {
log.info("the user became verified");
// We are now ready for business. This should only be invoked once
// per setSignedInUser(), regardless of whether we've rebooted since
// setSignedInUser() was called.
return this.notifyObservers(ONVERIFIED_NOTIFICATION);
},
err => log.info("startVerifiedCheck promise was rejected: " + err)
this.whenVerified(data).catch(err =>
log.info("startVerifiedCheck promise was rejected: " + err)
);
},
@ -1520,9 +1508,18 @@ FxAccountsInternal.prototype = {
// is yet to start up.) This might cause "A promise chain failed to
// handle a rejection" messages, so add an error handler directly
// on the promise to log the error.
currentState.whenVerifiedDeferred.promise.catch(err => {
log.info("the wait for user verification was stopped: " + err);
});
currentState.whenVerifiedDeferred.promise.then(
() => {
log.info("the user became verified");
// We are now ready for business. This should only be invoked once
// per setSignedInUser(), regardless of whether we've rebooted since
// setSignedInUser() was called.
this.notifyObservers(ONVERIFIED_NOTIFICATION);
},
err => {
log.info("the wait for user verification was stopped: " + err);
}
);
}
return this.pollEmailStatus(currentState, sessionToken, why);
},

View File

@ -564,6 +564,50 @@ add_test(function test_polling_timeout() {
});
});
// For bug 1585299 - ensure we only get a single ONVERIFIED notification.
add_task(async function test_onverified_once() {
let fxa = new MockFxAccounts();
let user = getTestUser("francine");
let numNotifications = 0;
function observe(aSubject, aTopic, aData) {
numNotifications += 1;
}
Services.obs.addObserver(observe, ONVERIFIED_NOTIFICATION);
fxa._internal.POLL_SESSION = 1;
await fxa.setSignedInUser(user);
Assert.ok(!(await fxa.getSignedInUser()).verified, "starts unverified");
await fxa._internal.startPollEmailStatus(
fxa._internal.currentAccountState,
user.sessionToken,
"start"
);
Assert.ok(!(await fxa.getSignedInUser()).verified, "still unverified");
log.debug("Mocking verification of francine's email");
fxa._internal.fxAccountsClient._email = user.email;
fxa._internal.fxAccountsClient._verified = true;
await fxa._internal.startPollEmailStatus(
fxa._internal.currentAccountState,
user.sessionToken,
"again"
);
Assert.ok((await fxa.getSignedInUser()).verified, "now verified");
Assert.equal(numNotifications, 1, "expect exactly 1 ONVERIFIED");
Services.obs.removeObserver(observe, ONVERIFIED_NOTIFICATION);
await fxa.signOut();
});
add_test(function test_pollEmailStatus_start_verified() {
let fxa = new MockFxAccounts();
let test_user = getTestUser("carol");

View File

@ -33,6 +33,7 @@ treeherder:
'Fxfn-l-1proc': 'Firefox functional tests (local) without e10s'
'Fxfn-r': 'Firefox functional tests (remote)'
'Fxfn-r-1proc': 'Firefox functional tests (remote) without e10s'
'iris': 'Iris testing suite'
'M': 'Mochitests'
'M-1proc': 'Mochitests without e10s'
'M-fis': 'Mochitests with fission enabled'

View File

@ -192,6 +192,11 @@ jobs:
symbol: I(toolchain-arm64)
parent: debian10-arm64-build
definition: toolchain-build
debian10-test-iris:
symbol: I(deb10-ti)
parent: debian10-test
packages:
- deb10-python-zstandard
android-build:
symbol: I(agb)
parent: debian9-base

View File

@ -114,6 +114,13 @@ gmp-6.1.0:
strip-components: 1
add-prefix: gmp-source/
iris-2.0:
description: Iris_Firefox source code
fetch:
type: git
repo: https://github.com/mozilla/iris_firefox
revision: 4618e109428150a6418526a2f95ccf77e71d3b8a
isl-0.15:
description: ISL 0.15 source code
fetch:

View File

@ -0,0 +1,195 @@
# 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/.
---
loader: taskgraph.loader.transform:loader
transforms:
- taskgraph.transforms.iris:transforms
- taskgraph.transforms.job:transforms
- taskgraph.transforms.task:transforms
kind-dependencies:
- build
- fetch
- toolchain
# These are the platforms that will run iris tests
iris-build-platforms:
- linux64-shippable/opt
# This is disabled while we sort out bootstrapping issues
# - osx-shippable/opt
- windows10-64-shippable/opt
job-defaults:
attributes:
retrigger: true
dependencies:
build:
by-platform:
linux64.*: build-linux64-shippable/opt
osx.*: build-macosx64-shippable/opt
windows10-64.*: build-win64-shippable/opt
description: Run the iris test suite's {} tests
fetches:
build:
by-platform:
linux64.*:
- target.tar.bz2
osx.*:
- target.dmg
windows10-64.*:
- target.zip
fetch:
- iris-2.0
run:
cwd: '{checkout}'
command:
by-platform:
linux64.*: >-
./taskcluster/scripts/iris/run-iris-linux.sh
osx.*: >-
./taskcluster/scripts/iris/run-iris-macos.sh
windows10-64.*: >-
./taskcluster/scripts/iris/run-iris-windows.sh
using: run-task
tooltool-downloads: public
run-on-projects: [try, mozilla-central]
treeherder:
kind: test
platform:
by-platform:
linux64.*: linux64-shippable/opt
osx.*: macosx1014-64-shippable/opt
windows10-64.*: windows10-64-shippable/opt
tier: 3
worker:
docker-image:
by-platform:
linux64.*:
in-tree: debian10-test-iris
default: null
artifacts:
by-platform:
linux64.*:
- type: file
name: public/runs.zip
path: /builds/worker/runs.zip
osx.*:
- type: file
name: public/runs.zip
path: runs.zip
windows10-64.*:
- type: file
name: public/runs.zip
path: runs.zip
env:
PATH:
by-platform:
windows10-64.*: "%PATH%;%USERPROFILE%\\scoop\\shims"
default: null
max-run-time:
by-name:
# Some chunks need longer to complete than others
bookmark.*: 10800
download_manager.*: 7200
history.*: 10800
default: 5400
worker-type:
by-platform:
linux64.*: t-linux-xlarge
osx.*: t-osx-1014
windows10-64.*: t-win10-64
jobs:
anti_tracking:
name: anti_tracking
treeherder:
symbol: iris(at)
antivirus:
name: antivirus
treeherder:
symbol: iris(av)
awesomebar:
name: awesomebar
treeherder:
symbol: iris(ab)
bookmark:
name: bookmark
treeherder:
symbol: iris(bm)
ci_tests:
name: ci_tests
treeherder:
symbol: iris(ct)
content_scroll:
name: content_scroll
treeherder:
symbol: iris(cs)
download_manager:
name: download_manager
treeherder:
symbol: iris(dm)
drag_and_drop:
name: drag_and_drop
treeherder:
symbol: iris(dnd)
find_toolbar:
name: find_toolbar
treeherder:
symbol: iris(ft)
history:
name: history
treeherder:
symbol: iris(h)
in_browser_pdf:
name: in_browser_pdf
treeherder:
symbol: iris(ibp)
# Nightly is a special case running a subset of tests as a smoketest of the
# entire test suite. Giving it a capital initial as a symbol will make
# Treeherder sort it in front of the other chunks for visibility.
nightly:
name: nightly
treeherder:
symbol: iris(N)
plugin_compatibility:
name: plugin_compatibility
treeherder:
symbol: iris(pc)
prefs:
name: prefs
treeherder:
symbol: iris(p)
private_browsing:
name: private_browsing
treeherder:
symbol: iris(pb)
safe_browsing:
name: safe_browsing
treeherder:
symbol: iris(sb)
search_and_update:
name: search_and_update
treeherder:
symbol: iris(sau)
session_restore:
name: session_restore
treeherder:
symbol: iris(sr)
themes:
name: themes
treeherder:
symbol: iris(t)
toolbars_window_controls:
name: toolbars_window_controls
treeherder:
symbol: iris(twc)
unit_tests:
name: unit_tests
treeherder:
symbol: iris(ut)
zoom_indicator:
name: zoom_indicator
treeherder:
symbol: iris(zi)

View File

@ -0,0 +1,29 @@
# %ARG DOCKER_IMAGE_PARENT
FROM $DOCKER_IMAGE_PARENT
MAINTAINER Wes Kocher <wkocher@mozilla.com>
RUN mkdir -p /builds
RUN id worker || useradd -d /builds/worker -s /bin/bash -m worker
WORKDIR /builds/worker
# We need to declare all potentially cache volumes as caches. Also,
# making high I/O paths volumes increase I/O throughput because of
# AUFS slowness.
VOLUME /builds/worker/.cache
VOLUME /builds/worker/checkouts
VOLUME /builds/worker/tooltool-cache
VOLUME /builds/worker/workspace
# %include taskcluster/docker/debian10-test-iris/install_iris_deps.sh
ADD topsrcdir/taskcluster/docker/debian10-test-iris/install_iris_deps.sh /setup/install_iris_deps.sh
RUN bash /setup/install_iris_deps.sh
# Set up first-run experience for interactive mode
ADD motd /etc/taskcluster-motd
ADD taskcluster-interactive-shell /bin/taskcluster-interactive-shell
RUN chmod +x /bin/taskcluster-interactive-shell
RUN chown -R worker:worker /builds/worker
# Set a default command useful for debugging
CMD ["/bin/bash", "--login"]

View File

@ -0,0 +1,88 @@
#!/bin/bash
# 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/.
# This script installs and configures everything the iris
# testing suite requires.
#!/usr/bin/env bash
set -ve
apt_packages=()
apt_packages+=('autoconf')
apt_packages+=('autoconf-archive')
apt_packages+=('automake')
apt_packages+=('libcairo2-dev')
apt_packages+=('libgtk2.0-dev')
apt_packages+=('libicu-dev')
apt_packages+=('libjpeg62-turbo-dev')
apt_packages+=('libopencv-contrib-dev')
apt_packages+=('libopencv-dev')
apt_packages+=('libopencv-objdetect-dev')
apt_packages+=('libopencv-superres-dev')
apt_packages+=('libopencv-videostab-dev')
apt_packages+=('libpango1.0-dev')
apt_packages+=('libpng-dev')
apt_packages+=('libpng16-16')
apt_packages+=('libtiff5-dev')
apt_packages+=('libtool')
apt_packages+=('p7zip-full')
apt_packages+=('pkg-config')
apt_packages+=('python3.7-tk')
apt_packages+=('python3.7-dev')
apt_packages+=('python3-pip')
apt_packages+=('scrot')
apt_packages+=('wmctrl')
apt_packages+=('xdotool')
apt_packages+=('xsel')
apt_packages+=('zlib1g-dev')
apt-get update
# This allows packages to be installed without human interaction
export DEBIAN_FRONTEND=noninteractive
apt-get install -y -f "${apt_packages[@]}"
python3.7 -m pip install pipenv
python3.7 -m pip install psutil
python3.7 -m pip install zstandard
mkdir -p /setup
cd /setup
wget http://www.leptonica.org/source/leptonica-1.76.0.tar.gz
tar xopf leptonica-1.76.0.tar.gz
cd leptonica-1.76.0
./configure && make && make install
cd /setup
wget https://github.com/tesseract-ocr/tesseract/archive/4.0.0.tar.gz
tar xopf 4.0.0.tar.gz
cd tesseract-4.0.0
./autogen.sh &&\
./configure --enable-debug &&\
LDFLAGS="-L/usr/local/lib" CFLAGS="-I/usr/local/include" make &&\
make install &&\
make install -langs &&\
ldconfig
cd /setup
wget https://github.com/tesseract-ocr/tessdata/archive/4.0.0.zip
unzip 4.0.0.zip
cd tessdata-4.0.0
ls /usr/local/share/tessdata/
mv ./* /usr/local/share/tessdata/
cd /
rm -rf /setup
rm -rf ~/.ccache
ls ~/.cache
rm -rf ~/.npm
apt-get clean
apt-get autoclean
rm -f "$0"

View File

@ -0,0 +1,5 @@
Welcome to your taskcluster interactive shell! The regularly scheduled task
has been paused to give you a chance to set up your debugging environment.
For your convenience, the exact mozharness command needed for this task can
be invoked using the 'run-mozharness' command.

View File

@ -0,0 +1,22 @@
#!/usr/bin/env bash
download() {
name=`basename $1`
url=${GECKO_HEAD_REPOSITORY}/raw-file/${GECKO_HEAD_REV}/$1
if ! curl --fail --silent -o ./$name --retry 10 $url; then
fail "failed downloading $1 from ${GECKO_HEAD_REPOSITORY}"
fi
}
cd $HOME/bin;
download taskcluster/scripts/tester/run-wizard;
chmod +x run-wizard;
./run-wizard;
SPAWN="$SHELL";
if [ "$SHELL" = "bash" ]; then
SPAWN="bash -li";
fi;
cd $HOME;
exec $SPAWN;

View File

@ -607,3 +607,7 @@ visual-metrics
--------------
Tasks that compute visual performance metrics from videos and images captured
by other tasks.
iris
----
Iris testing suite

View File

@ -0,0 +1,40 @@
#!/bin/bash
# Debian10 linux bootstrap
set -x +e -v
# Set up a virtual display since we don't have an xdisplay
. $HOME/scripts/xvfb.sh
start_xvfb '1920x1080x24+32' 0
# Re-set `+e` after start_xvfb changes it
set +e
# Install iris's pipenv
cd $MOZ_FETCHES_DIR/iris_firefox
PIPENV_MAX_RETRIES="5" pipenv install
status=$?
# If pipenv installation fails for any reason, make another attempt.
if [ $status -eq 0 ]
then
echo "Pipenv installed correctly, proceeding to Iris test run:"
else
echo "Pipenv failed to install, attempting again:"
pipenv lock --clear # This purges any partially/incorrectly generated lock files
pipenv install
fi
# Handle the nightly smoketest suite differently
[ "$CURRENT_TEST_DIR" != "nightly" ] && irisstring="firefox -t $CURRENT_TEST_DIR" || irisstring="$CURRENT_TEST_DIR"
echo "$irisstring"
# Actually run the iris tests
pipenv run iris $irisstring -w ../../iris_runs -n --treeherder -f ../../fetches/firefox/firefox -y
status=$?
# Zip up the test run output
cd ../..
zip -r runs.zip iris_runs/runs
# Exit with iris's exit code so treeherder knows if tests failed
exit $status

View File

@ -0,0 +1,38 @@
#!/bin/bash
set -x +e -v
cd $MOZ_FETCHES_DIR/iris_firefox
# FIXME: Install the following without homebrew:
# tesseract
# p7zip
# xquartz
# pipenv
# pyobjc
# pyobjc-core
# FIXME: Find a way to set these values:
# https://github.com/mozilla/iris_firefox/blob/master/bootstrap/osx_bootstrap.sh#L87-L91
# FIXME: Maybe find a way to create these download files:
# https://github.com/mozilla/iris_firefox/blob/master/bootstrap/osx_bootstrap.sh#L93-L104
# FIXME: Ensure all of the necessary python packages are available in the pypi mirror
# Mount the downloaded Firefox and install iris's pipenv
hdiutil attach ../target.dmg
python3 -m ensurepip --upgrade
pipenv install
# Handle the nightly smoketest suite differently
[ "$CURRENT_TEST_DIR" != "nightly" ] && irisstring="firefox -t $CURRENT_TEST_DIR" || irisstring="$CURRENT_TEST_DIR"
echo "$irisstring"
# Run this chunk of iris tests
pipenv run iris $irisstring -w ../../iris_runs -f /Volumes/Firefox\ Nightly/Firefox\ Nightly.app/Contents/MacOS/firefox-bin -n --treeherder -y
runstatus=$?
# FIXME: Return to the starting dir (../..) and zip up the iris_runs/runs dir
# Exit with the iris test run's exit code
exit $runstatus

View File

@ -0,0 +1,68 @@
#!/bin/bash
set -x +e -v
# Store our starting dir so we can get back to it later
dir=$(pwd)
# Install scoop
powershell -Command "Set-ExecutionPolicy RemoteSigned -scope CurrentUser"
powershell -Command "iex (new-object net.webclient).downloadstring('https://get.scoop.sh')"
scoopstatus=$?
# Install some packages
scoop install git # Needed to update scoop and pick up newer packages
scoop install python # Worker only has 3.6.5 out of the box, we need 3.7.3+
# Enable some extra packages to be installed
scoop bucket add versions
scoop bucket add extras
# Update scoop and scoop manifests
scoop update
# `scoop update` seems to intermittently fail, add a retry attempt
if [ $scoopstatus -eq 0 ]
then
echo "Scoop updated successfully"
else
echo "scoop update failed, retrying"
scoop update
fi
# Install the rest of the needed packages
scoop install which
# Install tesseract-ocr
cd $MOZ_FETCHES_DIR/iris_firefox
scoop install bootstrap\\tesseract.json
# Set up the pipenv
python3 -m pip install --upgrade pip
python3 -m pip install --upgrade pipenv
python3 -m pipenv install
pipstatus=$?
# If any part of the pipenv's install failed, try it again
if [ $pipstatus -eq 0 ]
then
echo "Pipenv installed correctly, proceeding to Iris test run:"
else
echo "Pipenv failed to install, attempting again:"
python3 -m pipenv lock --clear
python3 -m pipenv install
fi
# Handle the nightly smoketest suite differently
[ "$CURRENT_TEST_DIR" != "nightly" ] && irisstring="firefox -t $CURRENT_TEST_DIR" || irisstring="$CURRENT_TEST_DIR"
echo "$irisstring"
# Run the iris test suite
python3 -m pipenv run iris $irisstring -w ../../iris_runs -n --treeherder -f ../../fetches/firefox/firefox.exe -y
runstatus=$?
# Return to our starting dir and zip up the output of the test run
cd ../..
zip -r runs.zip iris_runs/runs
# Exit with the status from the iris run
exit $runstatus

View File

@ -0,0 +1,62 @@
# 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/.
"""
Take the base iris task definition and generate all of the actual test chunks
for all combinations of test categories and test platforms.
"""
from __future__ import absolute_import, print_function, unicode_literals
from copy import deepcopy
from taskgraph.transforms.base import TransformSequence
from taskgraph.util.schema import resolve_keyed_by
transforms = TransformSequence()
@transforms.add
def make_iris_tasks(config, jobs):
# Each platform will get a copy of the test categories
platforms = config.config.get('iris-build-platforms')
# The fields needing to be resolve_keyed_by'd
fields = [
'dependencies.build',
'fetches.build',
'run.command',
'treeherder.platform',
'worker.docker-image',
'worker.artifacts',
'worker.env.PATH',
'worker.max-run-time',
'worker-type',
]
for job in jobs:
for platform in platforms:
# Make platform-specific clones of each iris task
clone = deepcopy(job)
basename = clone["name"]
clone["description"] = clone["description"].format(basename)
clone["name"] = clone["name"] + "-" + platform
# resolve_keyed_by picks the correct values based on
# the `by-platform` keys in the task definitions
for field in fields:
resolve_keyed_by(clone, field, clone['name'], **{
'platform': platform,
})
# iris uses this to select the tests to run in this chunk
clone["worker"]["env"]["CURRENT_TEST_DIR"] = basename
# Clean up some entries when they aren't needed
if clone["worker"]["docker-image"] is None:
del clone["worker"]["docker-image"]
if clone["worker"]["env"]["PATH"] is None:
del clone["worker"]["env"]["PATH"]
yield clone

View File

@ -753,6 +753,7 @@
document.documentURI == "chrome://extensions/content/dummy.xul";
if (!isDummyDocument) {
for (let script of [
"chrome://global/content/elements/arrowscrollbox.js",
"chrome://global/content/elements/dialog.js",
"chrome://global/content/elements/general.js",
"chrome://global/content/elements/button.js",

View File

@ -65,9 +65,7 @@ toolkit.jar:
content/global/bindings/datekeeper.js (widgets/datekeeper.js)
content/global/bindings/datepicker.js (widgets/datepicker.js)
content/global/bindings/datetimebox.css (widgets/datetimebox.css)
content/global/bindings/general.xml (widgets/general.xml)
content/global/bindings/popup.xml (widgets/popup.xml)
content/global/bindings/scrollbox.xml (widgets/scrollbox.xml)
content/global/bindings/spinner.js (widgets/spinner.js)
content/global/bindings/textbox.xml (widgets/textbox.xml)
content/global/bindings/timekeeper.js (widgets/timekeeper.js)
@ -93,6 +91,7 @@ toolkit.jar:
content/global/elements/marquee.js (widgets/marquee.js)
content/global/elements/menulist.js (widgets/menulist.js)
content/global/elements/popupnotification.js (widgets/popupnotification.js)
content/global/elements/arrowscrollbox.js (widgets/arrowscrollbox.js)
content/global/elements/search-textbox.js (widgets/search-textbox.js)
content/global/elements/stringbundle.js (widgets/stringbundle.js)
content/global/elements/tabbox.js (widgets/tabbox.js)

View File

@ -3,7 +3,7 @@
<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
<!--
XUL Widget Test for basic properties - this test checks that the basic
properties defined in general.xml and inherited by a number of elements
properties defined in general.js and inherited by a number of elements
work properly.
-->
<window title="Basic Properties Test"

View File

@ -0,0 +1,866 @@
/* 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";
// This is loaded into all XUL windows. Wrap in a block to prevent
// leaking to window scope.
{
const { Services } = ChromeUtils.import(
"resource://gre/modules/Services.jsm"
);
class MozArrowScrollbox extends MozElements.BaseControl {
static get inheritedAttributes() {
return {
".scrollbutton-up": "orient,disabled=scrolledtostart",
"[part=scrollbox]": "orient,align,pack,dir,smoothscroll",
".scrollbutton-down": "orient,disabled=scrolledtoend",
};
}
get markup() {
return `
<html:link rel="stylesheet" href="chrome://global/skin/global.css"/>
<toolbarbutton class="scrollbutton-up" part="scrollbutton-up"/>
<spacer part="arrowscrollbox-overflow-start-indicator"/>
<scrollbox part="scrollbox" flex="1">
<html:slot/>
</scrollbox>
<spacer part="arrowscrollbox-overflow-end-indicator"/>
<toolbarbutton class="scrollbutton-down" part="scrollbutton-down"/>
`;
}
constructor() {
super();
this.attachShadow({ mode: "open" });
this.shadowRoot.appendChild(this.fragment);
this.scrollbox = this.shadowRoot.querySelector("[part=scrollbox]");
this._scrollButtonUp = this.shadowRoot.querySelector(".scrollbutton-up");
this._scrollButtonDown = this.shadowRoot.querySelector(
".scrollbutton-down"
);
this._arrowScrollAnim = {
scrollbox: this,
requestHandle: 0,
/* 0 indicates there is no pending request */
start: function arrowSmoothScroll_start() {
this.lastFrameTime = window.performance.now();
if (!this.requestHandle) {
this.requestHandle = window.requestAnimationFrame(
this.sample.bind(this)
);
}
},
stop: function arrowSmoothScroll_stop() {
window.cancelAnimationFrame(this.requestHandle);
this.requestHandle = 0;
},
sample: function arrowSmoothScroll_handleEvent(timeStamp) {
const scrollIndex = this.scrollbox._scrollIndex;
const timePassed = timeStamp - this.lastFrameTime;
this.lastFrameTime = timeStamp;
const scrollDelta = 0.5 * timePassed * scrollIndex;
this.scrollbox.scrollByPixels(scrollDelta, true);
this.requestHandle = window.requestAnimationFrame(
this.sample.bind(this)
);
},
};
this._scrollIndex = 0;
this._scrollIncrement = null;
this._ensureElementIsVisibleAnimationFrame = 0;
this._prevMouseScrolls = [null, null];
this._touchStart = -1;
this._scrollButtonUpdatePending = false;
this._isScrolling = false;
this._destination = 0;
this._direction = 0;
this.addEventListener("wheel", this.on_wheel);
this.addEventListener("touchstart", this.on_touchstart);
this.addEventListener("touchmove", this.on_touchmove);
this.addEventListener("touchend", this.on_touchend);
this.shadowRoot.addEventListener("click", this.on_click.bind(this));
this.shadowRoot.addEventListener(
"mousedown",
this.on_mousedown.bind(this)
);
this.shadowRoot.addEventListener(
"mouseover",
this.on_mouseover.bind(this)
);
this.shadowRoot.addEventListener("mouseup", this.on_mouseup.bind(this));
this.shadowRoot.addEventListener("mouseout", this.on_mouseout.bind(this));
// These events don't get retargeted outside of the shadow root, but
// some callers like tests wait for these events. So run handlers
// and then retarget events from the scrollbox to the host.
this.scrollbox.addEventListener(
"underflow",
event => {
this.on_underflow(event);
this.dispatchEvent(new Event("underflow"));
},
true
);
this.scrollbox.addEventListener(
"overflow",
event => {
this.on_overflow(event);
this.dispatchEvent(new Event("overflow"));
},
true
);
this.scrollbox.addEventListener("scroll", event => {
this.on_scroll(event);
this.dispatchEvent(new Event("scroll"));
});
this.scrollbox.addEventListener("scrollend", event => {
this.on_scrollend(event);
this.dispatchEvent(new Event("scrollend"));
});
}
connectedCallback() {
if (this.hasConnected) {
return;
}
this.hasConnected = true;
this.setAttribute("notoverflowing", "true");
this.initializeAttributeInheritance();
this._updateScrollButtonsDisabledState();
}
get fragment() {
if (!this.constructor.hasOwnProperty("_fragment")) {
this.constructor._fragment = MozXULElement.parseXULToFragment(
this.markup
);
}
return document.importNode(this.constructor._fragment, true);
}
get _clickToScroll() {
return this.hasAttribute("clicktoscroll");
}
get _scrollDelay() {
if (this._clickToScroll) {
return Services.prefs.getIntPref(
"toolkit.scrollbox.clickToScroll.scrollDelay",
150
);
}
// Use the same REPEAT_DELAY as "nsRepeatService.h".
return /Mac/.test(navigator.platform) ? 25 : 50;
}
get scrollIncrement() {
if (this._scrollIncrement === null) {
this._scrollIncrement = Services.prefs.getIntPref(
"toolkit.scrollbox.scrollIncrement",
20
);
}
return this._scrollIncrement;
}
set smoothScroll(val) {
this.setAttribute("smoothscroll", !!val);
return val;
}
get smoothScroll() {
if (!this.hasAttribute("smoothscroll")) {
this.smoothScroll = Services.prefs.getBoolPref(
"toolkit.scrollbox.smoothScroll",
true
);
}
return this.getAttribute("smoothscroll") == "true";
}
get scrollClientRect() {
return this.scrollbox.getBoundingClientRect();
}
get scrollClientSize() {
return this.orient == "vertical"
? this.scrollbox.clientHeight
: this.scrollbox.clientWidth;
}
get scrollSize() {
return this.orient == "vertical"
? this.scrollbox.scrollHeight
: this.scrollbox.scrollWidth;
}
get lineScrollAmount() {
// line scroll amout should be the width (at horizontal scrollbox) or
// the height (at vertical scrollbox) of the scrolled elements.
// However, the elements may have different width or height. So,
// for consistent speed, let's use avalage with of the elements.
var elements = this._getScrollableElements();
return elements.length && this.scrollSize / elements.length;
}
get scrollPosition() {
return this.orient == "vertical"
? this.scrollbox.scrollTop
: this.scrollbox.scrollLeft;
}
get startEndProps() {
if (!this._startEndProps) {
this._startEndProps =
this.orient == "vertical" ? ["top", "bottom"] : ["left", "right"];
}
return this._startEndProps;
}
get isRTLScrollbox() {
if (!this._isRTLScrollbox) {
this._isRTLScrollbox =
this.orient != "vertical" &&
document.defaultView.getComputedStyle(this.scrollbox).direction ==
"rtl";
}
return this._isRTLScrollbox;
}
_onButtonClick(event) {
if (this._clickToScroll) {
this._distanceScroll(event);
}
}
_onButtonMouseDown(event, index) {
if (this._clickToScroll && event.button == 0) {
this._startScroll(index);
}
}
_onButtonMouseUp(event) {
if (this._clickToScroll && event.button == 0) {
this._stopScroll();
}
}
_onButtonMouseOver(index) {
if (this._clickToScroll) {
this._continueScroll(index);
} else {
this._startScroll(index);
}
}
_onButtonMouseOut() {
if (this._clickToScroll) {
this._pauseScroll();
} else {
this._stopScroll();
}
}
_boundsWithoutFlushing(element) {
if (!("_DOMWindowUtils" in this)) {
this._DOMWindowUtils = window.windowUtils;
}
return this._DOMWindowUtils
? this._DOMWindowUtils.getBoundsWithoutFlushing(element)
: element.getBoundingClientRect();
}
_canScrollToElement(element) {
if (element.hidden) {
return false;
}
// See if the element is hidden via CSS without the hidden attribute.
// If we get only zeros for the client rect, this means the element
// is hidden. As a performance optimization, we don't flush layout
// here which means that on the fly changes aren't fully supported.
let rect = this._boundsWithoutFlushing(element);
return !!(rect.top || rect.left || rect.width || rect.height);
}
ensureElementIsVisible(element, aInstant) {
if (!this._canScrollToElement(element)) {
return;
}
if (this._ensureElementIsVisibleAnimationFrame) {
window.cancelAnimationFrame(this._ensureElementIsVisibleAnimationFrame);
}
this._ensureElementIsVisibleAnimationFrame = window.requestAnimationFrame(
() => {
element.scrollIntoView({
block: "nearest",
behavior: aInstant ? "instant" : "auto",
});
this._ensureElementIsVisibleAnimationFrame = 0;
}
);
}
scrollByIndex(index, aInstant) {
if (index == 0) {
return;
}
var rect = this.scrollClientRect;
var [start, end] = this.startEndProps;
var x = index > 0 ? rect[end] + 1 : rect[start] - 1;
var nextElement = this._elementFromPoint(x, index);
if (!nextElement) {
return;
}
var targetElement;
if (this.isRTLScrollbox) {
index *= -1;
}
while (index < 0 && nextElement) {
if (this._canScrollToElement(nextElement)) {
targetElement = nextElement;
}
nextElement = nextElement.previousElementSibling;
index++;
}
while (index > 0 && nextElement) {
if (this._canScrollToElement(nextElement)) {
targetElement = nextElement;
}
nextElement = nextElement.nextElementSibling;
index--;
}
if (!targetElement) {
return;
}
this.ensureElementIsVisible(targetElement, aInstant);
}
_getScrollableElements() {
let nodes = this.children;
if (nodes.length == 1) {
let node = nodes[0];
if (
node.localName == "children" &&
node.namespaceURI == "http://www.mozilla.org/xbl"
) {
nodes = document.getBindingParent(this).children;
} else if (
node.localName == "slot" &&
node.namespaceURI == "http://www.w3.org/1999/xhtml"
) {
nodes = node.getRootNode().host.children;
}
}
return Array.prototype.filter.call(nodes, this._canScrollToElement, this);
}
_elementFromPoint(aX, aPhysicalScrollDir) {
var elements = this._getScrollableElements();
if (!elements.length) {
return null;
}
if (this.isRTLScrollbox) {
elements.reverse();
}
var [start, end] = this.startEndProps;
var low = 0;
var high = elements.length - 1;
if (
aX < elements[low].getBoundingClientRect()[start] ||
aX > elements[high].getBoundingClientRect()[end]
) {
return null;
}
var mid, rect;
while (low <= high) {
mid = Math.floor((low + high) / 2);
rect = elements[mid].getBoundingClientRect();
if (rect[start] > aX) {
high = mid - 1;
} else if (rect[end] < aX) {
low = mid + 1;
} else {
return elements[mid];
}
}
// There's no element at the requested coordinate, but the algorithm
// from above yields an element next to it, in a random direction.
// The desired scrolling direction leads to the correct element.
if (!aPhysicalScrollDir) {
return null;
}
if (aPhysicalScrollDir < 0 && rect[start] > aX) {
mid = Math.max(mid - 1, 0);
} else if (aPhysicalScrollDir > 0 && rect[end] < aX) {
mid = Math.min(mid + 1, elements.length - 1);
}
return elements[mid];
}
_startScroll(index) {
if (this.isRTLScrollbox) {
index *= -1;
}
if (this._clickToScroll) {
this._scrollIndex = index;
this._mousedown = true;
if (this.smoothScroll) {
this._arrowScrollAnim.start();
return;
}
}
if (!this._scrollTimer) {
this._scrollTimer = Cc["@mozilla.org/timer;1"].createInstance(
Ci.nsITimer
);
} else {
this._scrollTimer.cancel();
}
let callback;
if (this._clickToScroll) {
callback = () => {
if (!document && this._scrollTimer) {
this._scrollTimer.cancel();
}
this.scrollByIndex(this._scrollIndex);
};
} else {
callback = () => this.scrollByPixels(this.scrollIncrement * index);
}
this._scrollTimer.initWithCallback(
callback,
this._scrollDelay,
Ci.nsITimer.TYPE_REPEATING_SLACK
);
callback();
}
_stopScroll() {
if (this._scrollTimer) {
this._scrollTimer.cancel();
}
if (this._clickToScroll) {
this._mousedown = false;
if (!this._scrollIndex || !this.smoothScroll) {
return;
}
this.scrollByIndex(this._scrollIndex);
this._scrollIndex = 0;
this._arrowScrollAnim.stop();
}
}
_pauseScroll() {
if (this._mousedown) {
this._stopScroll();
this._mousedown = true;
document.addEventListener("mouseup", this);
document.addEventListener("blur", this, true);
}
}
_continueScroll(index) {
if (this._mousedown) {
this._startScroll(index);
}
}
_distanceScroll(aEvent) {
if (aEvent.detail < 2 || aEvent.detail > 3) {
return;
}
var scrollBack = aEvent.originalTarget == this._scrollButtonUp;
var scrollLeftOrUp = this.isRTLScrollbox ? !scrollBack : scrollBack;
var targetElement;
if (aEvent.detail == 2) {
// scroll by the size of the scrollbox
let [start, end] = this.startEndProps;
let x;
if (scrollLeftOrUp) {
x = this.scrollClientRect[start] - this.scrollClientSize;
} else {
x = this.scrollClientRect[end] + this.scrollClientSize;
}
targetElement = this._elementFromPoint(x, scrollLeftOrUp ? -1 : 1);
// the next partly-hidden element will become fully visible,
// so don't scroll too far
if (targetElement) {
targetElement = scrollBack
? targetElement.nextElementSibling
: targetElement.previousElementSibling;
}
}
if (!targetElement) {
// scroll to the first resp. last element
let elements = this._getScrollableElements();
targetElement = scrollBack
? elements[0]
: elements[elements.length - 1];
}
this.ensureElementIsVisible(targetElement);
}
handleEvent(aEvent) {
if (
aEvent.type == "mouseup" ||
(aEvent.type == "blur" && aEvent.target == document)
) {
this._mousedown = false;
document.removeEventListener("mouseup", this);
document.removeEventListener("blur", this, true);
}
}
scrollByPixels(aPixels, aInstant) {
let scrollOptions = { behavior: aInstant ? "instant" : "auto" };
scrollOptions[this.startEndProps[0]] = aPixels;
this.scrollbox.scrollBy(scrollOptions);
}
_updateScrollButtonsDisabledState() {
if (this.hasAttribute("notoverflowing")) {
this.setAttribute("scrolledtoend", "true");
this.setAttribute("scrolledtostart", "true");
return;
}
if (this._scrollButtonUpdatePending) {
return;
}
this._scrollButtonUpdatePending = true;
// Wait until after the next paint to get current layout data from
// getBoundsWithoutFlushing.
window.requestAnimationFrame(() => {
setTimeout(() => {
if (!this.isConnected) {
// We've been destroyed in the meantime.
return;
}
this._scrollButtonUpdatePending = false;
let scrolledToStart = false;
let scrolledToEnd = false;
if (this.hasAttribute("notoverflowing")) {
scrolledToStart = true;
scrolledToEnd = true;
} else {
let [leftOrTop, rightOrBottom] = this.startEndProps;
let leftOrTopEdge = ele =>
Math.round(this._boundsWithoutFlushing(ele)[leftOrTop]);
let rightOrBottomEdge = ele =>
Math.round(this._boundsWithoutFlushing(ele)[rightOrBottom]);
let elements = this._getScrollableElements();
let [leftOrTopElement, rightOrBottomElement] = [
elements[0],
elements[elements.length - 1],
];
if (this.isRTLScrollbox) {
[leftOrTopElement, rightOrBottomElement] = [
rightOrBottomElement,
leftOrTopElement,
];
}
if (
leftOrTopElement &&
leftOrTopEdge(leftOrTopElement) >= leftOrTopEdge(this.scrollbox)
) {
scrolledToStart = !this.isRTLScrollbox;
scrolledToEnd = this.isRTLScrollbox;
} else if (
rightOrBottomElement &&
rightOrBottomEdge(rightOrBottomElement) <=
rightOrBottomEdge(this.scrollbox)
) {
scrolledToStart = this.isRTLScrollbox;
scrolledToEnd = !this.isRTLScrollbox;
}
}
if (scrolledToEnd) {
this.setAttribute("scrolledtoend", "true");
} else {
this.removeAttribute("scrolledtoend");
}
if (scrolledToStart) {
this.setAttribute("scrolledtostart", "true");
} else {
this.removeAttribute("scrolledtostart");
}
}, 0);
});
}
disconnectedCallback() {
// Release timer to avoid reference cycles.
if (this._scrollTimer) {
this._scrollTimer.cancel();
this._scrollTimer = null;
}
}
on_wheel(event) {
// Don't consume the event if we can't scroll.
if (this.hasAttribute("notoverflowing")) {
return;
}
let doScroll = false;
let instant;
let scrollAmount = 0;
if (this.orient == "vertical") {
doScroll = true;
if (event.deltaMode == event.DOM_DELTA_PIXEL) {
scrollAmount = event.deltaY;
} else if (event.deltaMode == event.DOM_DELTA_PAGE) {
scrollAmount = event.deltaY * this.scrollClientSize;
} else {
scrollAmount = event.deltaY * this.lineScrollAmount;
}
} else {
// We allow vertical scrolling to scroll a horizontal scrollbox
// because many users have a vertical scroll wheel but no
// horizontal support.
// Because of this, we need to avoid scrolling chaos on trackpads
// and mouse wheels that support simultaneous scrolling in both axes.
// We do this by scrolling only when the last two scroll events were
// on the same axis as the current scroll event.
// For diagonal scroll events we only respect the dominant axis.
let isVertical = Math.abs(event.deltaY) > Math.abs(event.deltaX);
let delta = isVertical ? event.deltaY : event.deltaX;
let scrollByDelta = isVertical && this.isRTLScrollbox ? -delta : delta;
if (this._prevMouseScrolls.every(prev => prev == isVertical)) {
doScroll = true;
if (event.deltaMode == event.DOM_DELTA_PIXEL) {
scrollAmount = scrollByDelta;
instant = true;
} else if (event.deltaMode == event.DOM_DELTA_PAGE) {
scrollAmount = scrollByDelta * this.scrollClientSize;
} else {
scrollAmount = scrollByDelta * this.lineScrollAmount;
}
}
if (this._prevMouseScrolls.length > 1) {
this._prevMouseScrolls.shift();
}
this._prevMouseScrolls.push(isVertical);
}
if (doScroll) {
let direction = scrollAmount < 0 ? -1 : 1;
let startPos = this.scrollPosition;
if (!this._isScrolling || this._direction != direction) {
this._destination = startPos + scrollAmount;
this._direction = direction;
} else {
// We were already in the process of scrolling in this direction
this._destination = this._destination + scrollAmount;
scrollAmount = this._destination - startPos;
}
this.scrollByPixels(scrollAmount, instant);
}
event.stopPropagation();
event.preventDefault();
}
on_touchstart(event) {
if (event.touches.length > 1) {
// Multiple touch points detected, abort. In particular this aborts
// the panning gesture when the user puts a second finger down after
// already panning with one finger. Aborting at this point prevents
// the pan gesture from being resumed until all fingers are lifted
// (as opposed to when the user is back down to one finger).
this._touchStart = -1;
} else {
this._touchStart =
this.orient == "vertical"
? event.touches[0].screenY
: event.touches[0].screenX;
}
}
on_touchmove(event) {
if (event.touches.length == 1 && this._touchStart >= 0) {
var touchPoint =
this.orient == "vertical"
? event.touches[0].screenY
: event.touches[0].screenX;
var delta = this._touchStart - touchPoint;
if (Math.abs(delta) > 0) {
this.scrollByPixels(delta, true);
this._touchStart = touchPoint;
}
event.preventDefault();
}
}
on_touchend(event) {
this._touchStart = -1;
}
on_underflow(event) {
// Ignore underflow events:
// - from nested scrollable elements
// - corresponding to an overflow event that we ignored
if (
event.target != this.scrollbox ||
this.hasAttribute("notoverflowing")
) {
return;
}
// Ignore events that doesn't match our orientation.
// Scrollport event orientation:
// 0: vertical
// 1: horizontal
// 2: both
if (this.orient == "vertical") {
if (event.detail == 1) {
return;
}
} else if (event.detail == 0) {
// horizontal scrollbox
return;
}
this.setAttribute("notoverflowing", "true");
this._updateScrollButtonsDisabledState();
}
on_overflow(event) {
// Ignore overflow events:
// - from nested scrollable elements
if (event.target != this.scrollbox) {
return;
}
// Ignore events that doesn't match our orientation.
// Scrollport event orientation:
// 0: vertical
// 1: horizontal
// 2: both
if (this.orient == "vertical") {
if (event.detail == 1) {
return;
}
} else if (event.detail == 0) {
// horizontal scrollbox
return;
}
this.removeAttribute("notoverflowing");
this._updateScrollButtonsDisabledState();
}
on_scroll(event) {
this._isScrolling = true;
this._updateScrollButtonsDisabledState();
}
on_scrollend(event) {
this._isScrolling = false;
this._destination = 0;
this._direction = 0;
}
on_click(event) {
if (
event.originalTarget != this._scrollButtonUp &&
event.originalTarget != this._scrollButtonDown
) {
return;
}
this._onButtonClick(event);
}
on_mousedown(event) {
if (event.originalTarget == this._scrollButtonUp) {
this._onButtonMouseDown(event, -1);
}
if (event.originalTarget == this._scrollButtonDown) {
this._onButtonMouseDown(event, 1);
}
}
on_mouseup(event) {
if (
event.originalTarget != this._scrollButtonUp &&
event.originalTarget != this._scrollButtonDown
) {
return;
}
this._onButtonMouseUp(event);
}
on_mouseover(event) {
if (event.originalTarget == this._scrollButtonUp) {
this._onButtonMouseOver(-1);
}
if (event.originalTarget == this._scrollButtonDown) {
this._onButtonMouseOver(1);
}
}
on_mouseout(event) {
if (
event.originalTarget != this._scrollButtonUp &&
event.originalTarget != this._scrollButtonDown
) {
return;
}
this._onButtonMouseOut();
}
}
customElements.define("arrowscrollbox", MozArrowScrollbox);
}

View File

@ -1,25 +0,0 @@
<?xml version="1.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/. -->
<bindings id="generalBindings"
xmlns="http://www.mozilla.org/xbl"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:xbl="http://www.mozilla.org/xbl">
<binding id="basecontrol">
<implementation implements="nsIDOMXULControlElement">
<!-- public implementation -->
<property name="disabled" onset="if (val) this.setAttribute('disabled', 'true');
else this.removeAttribute('disabled');
return val;"
onget="return this.getAttribute('disabled') == 'true';"/>
<property name="tabIndex" onget="return parseInt(this.getAttribute('tabindex')) || 0"
onset="if (val) this.setAttribute('tabindex', val);
else this.removeAttribute('tabindex'); return val;"/>
</implementation>
</binding>
</bindings>

View File

@ -73,7 +73,7 @@
if (!super.shadowRoot.firstElementChild) {
super.shadowRoot.appendChild(this.fragment);
// Retarget events from shadow DOM scrolbox to the popup itself.
// Retarget events from shadow DOM arrowscrollbox to the host.
this.scrollBox.addEventListener("scroll", ev =>
this.dispatchEvent(new Event("scroll"))
);
@ -83,6 +83,10 @@
this.scrollBox.addEventListener("underflow", ev =>
this.dispatchEvent(new Event("underflow"))
);
this.scrollBox._scrollButtonUp.classList.add("menupopup-scrollbutton");
this.scrollBox._scrollButtonDown.classList.add(
"menupopup-scrollbutton"
);
}
return super.shadowRoot;
}
@ -111,13 +115,13 @@
get styles() {
let s = `
:host(.in-menulist) .popup-internal-box > .scrollbutton-up,
:host(.in-menulist) .popup-internal-box > .arrowscrollbox-overflow-start-indicator,
:host(.in-menulist) .popup-internal-box > .arrowscrollbox-overflow-end-indicator,
:host(.in-menulist) .popup-internal-box > .scrollbutton-down {
:host(.in-menulist) .popup-internal-box::part(scrollbutton-up),
:host(.in-menulist) .popup-internal-box::part(arrowscrollbox-overflow-start-indicator),
:host(.in-menulist) .popup-internal-box::part(arrowscrollbox-overflow-end-indicator),
:host(.in-menulist) .popup-internal-box::part(scrollbutton-down) {
display: none;
}
:host(.in-menulist) .popup-internal-box > .arrowscrollbox-scrollbox {
:host(.in-menulist) .popup-internal-box::part(scrollbox) {
overflow: auto;
}
`;
@ -166,6 +170,7 @@
this._draggingState = this.NOT_DRAGGING;
this._clearScrollTimer();
this.releaseCapture();
this.scrollBox.scrollbox.scrollTop = 0;
});
this.addEventListener("mousedown", event => {

View File

@ -1,806 +0,0 @@
<?xml version="1.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/. -->
<bindings id="arrowscrollboxBindings"
xmlns="http://www.mozilla.org/xbl"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:xbl="http://www.mozilla.org/xbl">
<binding id="arrowscrollbox" extends="chrome://global/content/bindings/general.xml#basecontrol">
<content>
<xul:toolbarbutton class="scrollbutton-up"
anonid="scrollbutton-up"
xbl:inherits="orient,collapsed=notoverflowing,disabled=scrolledtostart"/>
<xul:spacer class="arrowscrollbox-overflow-start-indicator"
xbl:inherits="collapsed=scrolledtostart"/>
<xul:scrollbox class="arrowscrollbox-scrollbox"
anonid="scrollbox"
flex="1"
xbl:inherits="orient,align,pack,dir,smoothscroll">
<children/>
</xul:scrollbox>
<xul:spacer class="arrowscrollbox-overflow-end-indicator"
xbl:inherits="collapsed=scrolledtoend"/>
<xul:toolbarbutton class="scrollbutton-down"
anonid="scrollbutton-down"
xbl:inherits="orient,collapsed=notoverflowing,disabled=scrolledtoend"/>
</content>
<implementation>
<constructor><![CDATA[
if (!this.hasAttribute("smoothscroll")) {
this.smoothScroll = this._prefBranch
.getBoolPref("toolkit.scrollbox.smoothScroll", true);
}
this.setAttribute("notoverflowing", "true");
this._updateScrollButtonsDisabledState();
// Ultimately Bug 1514926 will convert arrowscrollbox binding to a custom element.
// For the needs of Bug 1497189, where we apply a custom CSP to about:addons, we had
// to remove inline handlers and hence added event listeners for mouse events here.
this.addEventListener("click", (e) => {
if (e.originalTarget != this._scrollButtonUp && e.originalTarget != this._scrollButtonDown) {
return;
}
this._onButtonClick(e);
});
this.addEventListener("mousedown", (e) => {
if (e.originalTarget == this._scrollButtonUp) {
this._onButtonMouseDown(e, -1);
}
if (e.originalTarget == this._scrollButtonDown) {
this._onButtonMouseDown(e, 1);
}
});
this.addEventListener("mouseup", (e) => {
if (e.originalTarget != this._scrollButtonUp && e.originalTarget != this._scrollButtonDown) {
return;
}
this._onButtonMouseUp(e);
});
this.addEventListener("mouseover", (e) => {
if (e.originalTarget == this._scrollButtonUp) {
this._onButtonMouseOver(-1);
}
if (e.originalTarget == this._scrollButtonDown) {
this._onButtonMouseOver(1);
}
});
this.addEventListener("mouseout", (e) => {
if (e.originalTarget != this._scrollButtonUp && e.originalTarget != this._scrollButtonDown) {
return;
}
this._onButtonMouseOut();
});
]]></constructor>
<destructor><![CDATA[
// Release timer to avoid reference cycles.
if (this._scrollTimer) {
this._scrollTimer.cancel();
this._scrollTimer = null;
}
]]></destructor>
<field name="scrollbox">
document.getAnonymousElementByAttribute(this, "anonid", "scrollbox");
</field>
<field name="_scrollButtonUp">
document.getAnonymousElementByAttribute(this, "anonid", "scrollbutton-up");
</field>
<field name="_scrollButtonDown">
document.getAnonymousElementByAttribute(this, "anonid", "scrollbutton-down");
</field>
<field name="_scrollIndex">0</field>
<field name="_arrowScrollAnim"><![CDATA[({
scrollbox: this,
requestHandle: 0, /* 0 indicates there is no pending request */
start: function arrowSmoothScroll_start() {
this.lastFrameTime = window.performance.now();
if (!this.requestHandle)
this.requestHandle = window.requestAnimationFrame(this.sample.bind(this));
},
stop: function arrowSmoothScroll_stop() {
window.cancelAnimationFrame(this.requestHandle);
this.requestHandle = 0;
},
sample: function arrowSmoothScroll_handleEvent(timeStamp) {
const scrollIndex = this.scrollbox._scrollIndex;
const timePassed = timeStamp - this.lastFrameTime;
this.lastFrameTime = timeStamp;
const scrollDelta = 0.5 * timePassed * scrollIndex;
this.scrollbox.scrollByPixels(scrollDelta, true);
this.requestHandle = window.requestAnimationFrame(this.sample.bind(this));
},
})]]></field>
<property name="_clickToScroll" readonly="true">
<getter><![CDATA[
return this.hasAttribute("clicktoscroll");
]]></getter>
</property>
<property name="_scrollDelay" readonly="true">
<getter><![CDATA[
if (this._clickToScroll) {
return this._prefBranch.getIntPref(
"toolkit.scrollbox.clickToScroll.scrollDelay", 150);
}
// Use the same REPEAT_DELAY as "nsRepeatService.h".
return /Mac/.test(navigator.platform) ? 25 : 50;
]]></getter>
</property>
<field name="__prefBranch">null</field>
<property name="_prefBranch" readonly="true">
<getter><![CDATA[
if (this.__prefBranch === null) {
this.__prefBranch = Cc["@mozilla.org/preferences-service;1"]
.getService(Ci.nsIPrefBranch);
}
return this.__prefBranch;
]]></getter>
</property>
<field name="_scrollIncrement">null</field>
<property name="scrollIncrement" readonly="true">
<getter><![CDATA[
if (this._scrollIncrement === null) {
this._scrollIncrement = this._prefBranch
.getIntPref("toolkit.scrollbox.scrollIncrement", 20);
}
return this._scrollIncrement;
]]></getter>
</property>
<property name="smoothScroll">
<getter><![CDATA[
return this.getAttribute("smoothscroll") == "true";
]]></getter>
<setter><![CDATA[
this.setAttribute("smoothscroll", !!val);
return val;
]]></setter>
</property>
<property name="scrollClientRect" readonly="true">
<getter><![CDATA[
return this.scrollbox.getBoundingClientRect();
]]></getter>
</property>
<property name="scrollClientSize" readonly="true">
<getter><![CDATA[
return this.orient == "vertical" ?
this.scrollbox.clientHeight :
this.scrollbox.clientWidth;
]]></getter>
</property>
<property name="scrollSize" readonly="true">
<getter><![CDATA[
return this.orient == "vertical" ?
this.scrollbox.scrollHeight :
this.scrollbox.scrollWidth;
]]></getter>
</property>
<property name="lineScrollAmount" readonly="true">
<getter><![CDATA[
// line scroll amout should be the width (at horizontal scrollbox) or
// the height (at vertical scrollbox) of the scrolled elements.
// However, the elements may have different width or height. So,
// for consistent speed, let's use avalage with of the elements.
var elements = this._getScrollableElements();
return elements.length && (this.scrollSize / elements.length);
]]></getter>
</property>
<property name="scrollPosition" readonly="true">
<getter><![CDATA[
return this.orient == "vertical" ?
this.scrollbox.scrollTop :
this.scrollbox.scrollLeft;
]]></getter>
</property>
<field name="_startEndProps"><![CDATA[
this.orient == "vertical" ? ["top", "bottom"] : ["left", "right"];
]]></field>
<field name="_isRTLScrollbox"><![CDATA[
this.orient != "vertical" &&
document.defaultView.getComputedStyle(this.scrollbox).direction == "rtl";
]]></field>
<method name="_onButtonClick">
<parameter name="event"/>
<body><![CDATA[
if (this._clickToScroll) {
this._distanceScroll(event);
}
]]></body>
</method>
<method name="_onButtonMouseDown">
<parameter name="event"/>
<parameter name="index"/>
<body><![CDATA[
if (this._clickToScroll && event.button == 0) {
this._startScroll(index);
}
]]></body>
</method>
<method name="_onButtonMouseUp">
<parameter name="event"/>
<body><![CDATA[
if (this._clickToScroll && event.button == 0) {
this._stopScroll();
}
]]></body>
</method>
<method name="_onButtonMouseOver">
<parameter name="index"/>
<body><![CDATA[
if (this._clickToScroll) {
this._continueScroll(index);
} else {
this._startScroll(index);
}
]]></body>
</method>
<method name="_onButtonMouseOut">
<parameter name="index"/>
<body><![CDATA[
if (this._clickToScroll) {
this._pauseScroll();
} else {
this._stopScroll();
}
]]></body>
</method>
<method name="_boundsWithoutFlushing">
<parameter name="element"/>
<body><![CDATA[
if (!("_DOMWindowUtils" in this)) {
this._DOMWindowUtils = window.windowUtils;
}
return this._DOMWindowUtils ?
this._DOMWindowUtils.getBoundsWithoutFlushing(element) :
element.getBoundingClientRect();
]]></body>
</method>
<method name="_canScrollToElement">
<parameter name="element"/>
<body><![CDATA[
if (element.hidden) {
return false;
}
// See if the element is hidden via CSS without the hidden attribute.
// If we get only zeros for the client rect, this means the element
// is hidden. As a performance optimization, we don't flush layout
// here which means that on the fly changes aren't fully supported.
let rect = this._boundsWithoutFlushing(element);
return !!(rect.top || rect.left || rect.width || rect.height);
]]></body>
</method>
<field name="_ensureElementIsVisibleAnimationFrame">0</field>
<method name="ensureElementIsVisible">
<parameter name="element"/>
<parameter name="aInstant"/>
<body><![CDATA[
if (!this._canScrollToElement(element))
return;
if (this._ensureElementIsVisibleAnimationFrame) {
window.cancelAnimationFrame(this._ensureElementIsVisibleAnimationFrame);
}
this._ensureElementIsVisibleAnimationFrame = window.requestAnimationFrame(() => {
element.scrollIntoView({ block: "nearest",
behavior: aInstant ? "instant" : "auto" });
this._ensureElementIsVisibleAnimationFrame = 0;
});
]]></body>
</method>
<method name="scrollByIndex">
<parameter name="index"/>
<parameter name="aInstant"/>
<body><![CDATA[
if (index == 0)
return;
var rect = this.scrollClientRect;
var [start, end] = this._startEndProps;
var x = index > 0 ? rect[end] + 1 : rect[start] - 1;
var nextElement = this._elementFromPoint(x, index);
if (!nextElement)
return;
var targetElement;
if (this._isRTLScrollbox)
index *= -1;
while (index < 0 && nextElement) {
if (this._canScrollToElement(nextElement))
targetElement = nextElement;
nextElement = nextElement.previousElementSibling;
index++;
}
while (index > 0 && nextElement) {
if (this._canScrollToElement(nextElement))
targetElement = nextElement;
nextElement = nextElement.nextElementSibling;
index--;
}
if (!targetElement)
return;
this.ensureElementIsVisible(targetElement, aInstant);
]]></body>
</method>
<method name="_getScrollableElements">
<body><![CDATA[
let nodes = this.children;
if (nodes.length == 1) {
let node = nodes[0];
if (node.localName == "children" &&
node.namespaceURI == "http://www.mozilla.org/xbl") {
nodes = document.getBindingParent(this).children;
} else if (node.localName == "slot" &&
node.namespaceURI == "http://www.w3.org/1999/xhtml") {
nodes = node.getRootNode().host.children;
}
}
return Array.prototype.filter.call(nodes, this._canScrollToElement, this);
]]></body>
</method>
<method name="_elementFromPoint">
<parameter name="aX"/>
<parameter name="aPhysicalScrollDir"/>
<body><![CDATA[
var elements = this._getScrollableElements();
if (!elements.length)
return null;
if (this._isRTLScrollbox)
elements.reverse();
var [start, end] = this._startEndProps;
var low = 0;
var high = elements.length - 1;
if (aX < elements[low].getBoundingClientRect()[start] ||
aX > elements[high].getBoundingClientRect()[end])
return null;
var mid, rect;
while (low <= high) {
mid = Math.floor((low + high) / 2);
rect = elements[mid].getBoundingClientRect();
if (rect[start] > aX)
high = mid - 1;
else if (rect[end] < aX)
low = mid + 1;
else
return elements[mid];
}
// There's no element at the requested coordinate, but the algorithm
// from above yields an element next to it, in a random direction.
// The desired scrolling direction leads to the correct element.
if (!aPhysicalScrollDir)
return null;
if (aPhysicalScrollDir < 0 && rect[start] > aX)
mid = Math.max(mid - 1, 0);
else if (aPhysicalScrollDir > 0 && rect[end] < aX)
mid = Math.min(mid + 1, elements.length - 1);
return elements[mid];
]]></body>
</method>
<method name="_startScroll">
<parameter name="index"/>
<body><![CDATA[
if (this._isRTLScrollbox) {
index *= -1;
}
if (this._clickToScroll) {
this._scrollIndex = index;
this._mousedown = true;
if (this.smoothScroll) {
this._arrowScrollAnim.start();
return;
}
}
if (!this._scrollTimer) {
this._scrollTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
} else {
this._scrollTimer.cancel();
}
let callback;
if (this._clickToScroll) {
callback = () => {
if (!document && this._scrollTimer) {
this._scrollTimer.cancel();
}
this.scrollByIndex(this._scrollIndex);
};
} else {
callback = () => this.scrollByPixels(this.scrollIncrement * index);
}
this._scrollTimer.initWithCallback(callback, this._scrollDelay,
Ci.nsITimer.TYPE_REPEATING_SLACK);
callback();
]]>
</body>
</method>
<method name="_stopScroll">
<body><![CDATA[
if (this._scrollTimer)
this._scrollTimer.cancel();
if (this._clickToScroll) {
this._mousedown = false;
if (!this._scrollIndex || !this.smoothScroll)
return;
this.scrollByIndex(this._scrollIndex);
this._scrollIndex = 0;
this._arrowScrollAnim.stop();
}
]]></body>
</method>
<method name="_pauseScroll">
<body><![CDATA[
if (this._mousedown) {
this._stopScroll();
this._mousedown = true;
document.addEventListener("mouseup", this);
document.addEventListener("blur", this, true);
}
]]></body>
</method>
<method name="_continueScroll">
<parameter name="index"/>
<body><![CDATA[
if (this._mousedown)
this._startScroll(index);
]]></body>
</method>
<method name="_distanceScroll">
<parameter name="aEvent"/>
<body><![CDATA[
if (aEvent.detail < 2 || aEvent.detail > 3)
return;
var scrollBack = (aEvent.originalTarget == this._scrollButtonUp);
var scrollLeftOrUp = this._isRTLScrollbox ? !scrollBack : scrollBack;
var targetElement;
if (aEvent.detail == 2) {
// scroll by the size of the scrollbox
let [start, end] = this._startEndProps;
let x;
if (scrollLeftOrUp)
x = this.scrollClientRect[start] - this.scrollClientSize;
else
x = this.scrollClientRect[end] + this.scrollClientSize;
targetElement = this._elementFromPoint(x, scrollLeftOrUp ? -1 : 1);
// the next partly-hidden element will become fully visible,
// so don't scroll too far
if (targetElement)
targetElement = scrollBack ?
targetElement.nextElementSibling :
targetElement.previousElementSibling;
}
if (!targetElement) {
// scroll to the first resp. last element
let elements = this._getScrollableElements();
targetElement = scrollBack ?
elements[0] :
elements[elements.length - 1];
}
this.ensureElementIsVisible(targetElement);
]]></body>
</method>
<method name="handleEvent">
<parameter name="aEvent"/>
<body><![CDATA[
if (aEvent.type == "mouseup" ||
aEvent.type == "blur" && aEvent.target == document) {
this._mousedown = false;
document.removeEventListener("mouseup", this);
document.removeEventListener("blur", this, true);
}
]]></body>
</method>
<method name="scrollByPixels">
<parameter name="aPixels"/>
<parameter name="aInstant"/>
<body><![CDATA[
let scrollOptions = { behavior: aInstant ? "instant" : "auto" };
scrollOptions[this._startEndProps[0]] = aPixels;
this.scrollbox.scrollBy(scrollOptions);
]]></body>
</method>
<field name="_prevMouseScrolls">[null, null]</field>
<field name="_touchStart">-1</field>
<field name="_scrollButtonUpdatePending">false</field>
<method name="_updateScrollButtonsDisabledState">
<body><![CDATA[
if (this.hasAttribute("notoverflowing")) {
this.setAttribute("scrolledtoend", "true");
this.setAttribute("scrolledtostart", "true");
return;
}
if (this._scrollButtonUpdatePending) {
return;
}
this._scrollButtonUpdatePending = true;
// Wait until after the next paint to get current layout data from
// getBoundsWithoutFlushing.
window.requestAnimationFrame(() => {
setTimeout(() => {
if (!this._startEndProps) {
// We've been destroyed in the meantime.
return;
}
this._scrollButtonUpdatePending = false;
let scrolledToStart = false;
let scrolledToEnd = false;
if (this.hasAttribute("notoverflowing")) {
scrolledToStart = true;
scrolledToEnd = true;
} else {
let [leftOrTop, rightOrBottom] = this._startEndProps;
let leftOrTopEdge = ele => Math.round(this._boundsWithoutFlushing(ele)[leftOrTop]);
let rightOrBottomEdge = ele => Math.round(this._boundsWithoutFlushing(ele)[rightOrBottom]);
let elements = this._getScrollableElements();
let [leftOrTopElement, rightOrBottomElement] = [elements[0], elements[elements.length - 1]];
if (this._isRTLScrollbox) {
[leftOrTopElement, rightOrBottomElement] = [rightOrBottomElement, leftOrTopElement];
}
if (leftOrTopElement &&
leftOrTopEdge(leftOrTopElement) >= leftOrTopEdge(this.scrollbox)) {
scrolledToStart = !this._isRTLScrollbox;
scrolledToEnd = this._isRTLScrollbox;
} else if (rightOrBottomElement &&
rightOrBottomEdge(rightOrBottomElement) <= rightOrBottomEdge(this.scrollbox)) {
scrolledToStart = this._isRTLScrollbox;
scrolledToEnd = !this._isRTLScrollbox;
}
}
if (scrolledToEnd) {
this.setAttribute("scrolledtoend", "true");
} else {
this.removeAttribute("scrolledtoend");
}
if (scrolledToStart) {
this.setAttribute("scrolledtostart", "true");
} else {
this.removeAttribute("scrolledtostart");
}
}, 0);
});
]]></body>
</method>
<field name="_isScrolling">false</field>
<field name="_destination">0</field>
<field name="_direction">0</field>
</implementation>
<handlers>
<handler event="wheel"><![CDATA[
// Don't consume the event if we can't scroll.
if (this.hasAttribute("notoverflowing")) {
return;
}
let doScroll = false;
let instant;
let scrollAmount = 0;
if (this.orient == "vertical") {
doScroll = true;
if (event.deltaMode == event.DOM_DELTA_PIXEL)
scrollAmount = event.deltaY;
else if (event.deltaMode == event.DOM_DELTA_PAGE)
scrollAmount = event.deltaY * this.scrollClientSize;
else
scrollAmount = event.deltaY * this.lineScrollAmount;
} else {
// We allow vertical scrolling to scroll a horizontal scrollbox
// because many users have a vertical scroll wheel but no
// horizontal support.
// Because of this, we need to avoid scrolling chaos on trackpads
// and mouse wheels that support simultaneous scrolling in both axes.
// We do this by scrolling only when the last two scroll events were
// on the same axis as the current scroll event.
// For diagonal scroll events we only respect the dominant axis.
let isVertical = Math.abs(event.deltaY) > Math.abs(event.deltaX);
let delta = isVertical ? event.deltaY : event.deltaX;
let scrollByDelta = isVertical && this._isRTLScrollbox ? -delta : delta;
if (this._prevMouseScrolls.every(prev => prev == isVertical)) {
doScroll = true;
if (event.deltaMode == event.DOM_DELTA_PIXEL) {
scrollAmount = scrollByDelta;
instant = true;
} else if (event.deltaMode == event.DOM_DELTA_PAGE) {
scrollAmount = scrollByDelta * this.scrollClientSize;
} else {
scrollAmount = scrollByDelta * this.lineScrollAmount;
}
}
if (this._prevMouseScrolls.length > 1)
this._prevMouseScrolls.shift();
this._prevMouseScrolls.push(isVertical);
}
if (doScroll) {
let direction = scrollAmount < 0 ? -1 : 1;
let startPos = this.scrollPosition;
if (!this._isScrolling || this._direction != direction) {
this._destination = startPos + scrollAmount;
this._direction = direction;
} else {
// We were already in the process of scrolling in this direction
this._destination = this._destination + scrollAmount;
scrollAmount = this._destination - startPos;
}
this.scrollByPixels(scrollAmount, instant);
}
event.stopPropagation();
event.preventDefault();
]]></handler>
<handler event="touchstart"><![CDATA[
if (event.touches.length > 1) {
// Multiple touch points detected, abort. In particular this aborts
// the panning gesture when the user puts a second finger down after
// already panning with one finger. Aborting at this point prevents
// the pan gesture from being resumed until all fingers are lifted
// (as opposed to when the user is back down to one finger).
this._touchStart = -1;
} else {
this._touchStart = (this.orient == "vertical"
? event.touches[0].screenY
: event.touches[0].screenX);
}
]]></handler>
<handler event="touchmove"><![CDATA[
if (event.touches.length == 1 &&
this._touchStart >= 0) {
var touchPoint = (this.orient == "vertical"
? event.touches[0].screenY
: event.touches[0].screenX);
var delta = this._touchStart - touchPoint;
if (Math.abs(delta) > 0) {
this.scrollByPixels(delta, true);
this._touchStart = touchPoint;
}
event.preventDefault();
}
]]></handler>
<handler event="touchend"><![CDATA[
this._touchStart = -1;
]]></handler>
<handler event="underflow" phase="capturing"><![CDATA[
// Ignore underflow events:
// - from nested scrollable elements
// - corresponding to an overflow event that we ignored
if (event.target != this ||
this.hasAttribute("notoverflowing")) {
return;
}
// Ignore events that doesn't match our orientation.
// Scrollport event orientation:
// 0: vertical
// 1: horizontal
// 2: both
if (this.orient == "vertical") {
if (event.detail == 1)
return;
} else if (event.detail == 0) {
// horizontal scrollbox
return;
}
this.setAttribute("notoverflowing", "true");
this._updateScrollButtonsDisabledState();
]]></handler>
<handler event="overflow" phase="capturing"><![CDATA[
// Ignore overflow events:
// - from nested scrollable elements
if (event.target != this) {
return;
}
// Ignore events that doesn't match our orientation.
// Scrollport event orientation:
// 0: vertical
// 1: horizontal
// 2: both
if (this.orient == "vertical") {
if (event.detail == 1)
return;
} else if (event.detail == 0) {
// horizontal scrollbox
return;
}
this.removeAttribute("notoverflowing");
this._updateScrollButtonsDisabledState();
]]></handler>
<handler event="scroll"><![CDATA[
this._isScrolling = true;
this._updateScrollButtonsDisabledState();
]]></handler>
<handler event="scrollend"><![CDATA[
this._isScrolling = false;
this._destination = 0;
this._direction = 0;
]]></handler>
</handlers>
</binding>
</bindings>

View File

@ -535,10 +535,6 @@ scrollbox[smoothscroll=true] {
scroll-behavior: smooth;
}
arrowscrollbox {
-moz-binding: url("chrome://global/content/bindings/scrollbox.xml#arrowscrollbox");
}
/********** stringbundle **********/
stringbundle,

View File

@ -4,19 +4,26 @@
@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
.scrollbutton-up > .toolbarbutton-icon {
arrowscrollbox[scrolledtoend=true]::part(arrowscrollbox-overflow-end-indicator),
arrowscrollbox[scrolledtostart=true]::part(arrowscrollbox-overflow-start-indicator),
arrowscrollbox[notoverflowing=true]::part(scrollbutton-up),
arrowscrollbox[notoverflowing=true]::part(scrollbutton-down) {
visibility: collapse;
}
.scrollbutton-up.menupopup-scrollbutton > .toolbarbutton-icon {
-moz-appearance: button-arrow-up;
}
.scrollbutton-down > .toolbarbutton-icon {
.scrollbutton-down.menupopup-scrollbutton > .toolbarbutton-icon {
-moz-appearance: button-arrow-down;
}
.scrollbutton-up[orient="horizontal"] > .toolbarbutton-icon {
.scrollbutton-up.menupopup-scrollbutton[orient="horizontal"] > .toolbarbutton-icon {
-moz-appearance: button-arrow-previous;
}
.scrollbutton-down[orient="horizontal"] > .toolbarbutton-icon {
.scrollbutton-down.menupopup-scrollbutton[orient="horizontal"] > .toolbarbutton-icon {
-moz-appearance: button-arrow-next;
}
@ -24,8 +31,8 @@
display: none;
}
arrowscrollbox:not([clicktoscroll="true"]) > .scrollbutton-up,
arrowscrollbox:not([clicktoscroll="true"]) > .scrollbutton-down {
arrowscrollbox:not([clicktoscroll="true"])::part(scrollbutton-up),
arrowscrollbox:not([clicktoscroll="true"])::part(scrollbutton-down) {
-moz-appearance: none;
border: 1px solid ThreeDShadow;
padding: 0;

View File

@ -216,8 +216,7 @@ xul|popupnotificationcontent {
/* autorepeatbuttons in menus */
.popup-internal-box > .scrollbutton-up,
.popup-internal-box > .scrollbutton-down {
.menupopup-scrollbutton {
height: 15px;
position: relative;
list-style-image: none;
@ -233,25 +232,24 @@ xul|popupnotificationcontent {
-moz-appearance: menuitem;
}
.popup-internal-box > .scrollbutton-up {
.scrollbutton-up.menupopup-scrollbutton {
padding-top: 1px; /* 4px padding-top from the .popup-internal-box. */
margin-bottom: -15px;
}
.popup-internal-box > .scrollbutton-up > .toolbarbutton-icon {
.scrollbutton-up.menupopup-scrollbutton > .toolbarbutton-icon {
-moz-appearance: button-arrow-up;
}
.popup-internal-box > .scrollbutton-down {
.scrollbutton-down.menupopup-scrollbutton {
padding-top: 5px;
margin-top: -15px;
}
.popup-internal-box > .scrollbutton-down > .toolbarbutton-icon {
.scrollbutton-down.menupopup-scrollbutton > .toolbarbutton-icon {
-moz-appearance: button-arrow-down;
}
.popup-internal-box > .scrollbutton-up[disabled="true"],
.popup-internal-box > .scrollbutton-down[disabled="true"] {
.menupopup-scrollbutton[disabled="true"] {
visibility: collapse;
}

View File

@ -3,7 +3,14 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
arrowscrollbox[scrolledtoend=true]::part(arrowscrollbox-overflow-end-indicator),
arrowscrollbox[scrolledtostart=true]::part(arrowscrollbox-overflow-start-indicator),
arrowscrollbox[notoverflowing=true]::part(scrollbutton-up),
arrowscrollbox[notoverflowing=true]::part(scrollbutton-down) {
visibility: collapse;
}
/* Horizontal enabled */
.scrollbutton-up[orient="horizontal"] {
list-style-image: url("chrome://global/skin/arrow/arrow-lft-sharp.gif");

View File

@ -4,6 +4,13 @@
@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
arrowscrollbox[scrolledtoend=true]::part(arrowscrollbox-overflow-end-indicator),
arrowscrollbox[scrolledtostart=true]::part(arrowscrollbox-overflow-start-indicator),
arrowscrollbox[notoverflowing=true]::part(scrollbutton-up),
arrowscrollbox[notoverflowing=true]::part(scrollbutton-down) {
visibility: collapse;
}
/*
* Scroll arrows
*/
@ -71,15 +78,15 @@
margin-inline-end: 2px;
}
arrowscrollbox:not([clicktoscroll="true"]) > .scrollbutton-up,
arrowscrollbox:not([clicktoscroll="true"]) > .scrollbutton-down {
arrowscrollbox:not([clicktoscroll="true"])::part(scrollbutton-up),
arrowscrollbox:not([clicktoscroll="true"])::part(scrollbutton-down) {
-moz-appearance: none;
border: 1px solid transparent;
padding: 1px;
}
arrowscrollbox:not([clicktoscroll="true"]) > .scrollbutton-up:not([disabled="true"]):hover,
arrowscrollbox:not([clicktoscroll="true"]) > .scrollbutton-down:not([disabled="true"]):hover {
arrowscrollbox:not([clicktoscroll="true"]):not([scrolledtostart=true])::part(scrollbutton-up):hover,
arrowscrollbox:not([clicktoscroll="true"]):not([scrolledtoend=true])::part(scrollbutton-down):hover {
margin: 1px;
border: 1px inset ThreeDFace;
padding-top: 2px;