mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-25 19:25:43 +00:00
Merge autoland to mozilla-central a=merge
This commit is contained in:
commit
01d1011ca4
@ -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()) {
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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"));
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
@ -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"] {
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -51,8 +51,7 @@ function nodeLoadProperties(node: Node, actor) {
|
||||
try {
|
||||
const properties = await loadItemProperties(
|
||||
node,
|
||||
client.createObjectClient,
|
||||
client.createLongStringClient,
|
||||
client,
|
||||
loadedProperties
|
||||
);
|
||||
|
||||
|
@ -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)) {
|
||||
|
@ -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]]),
|
||||
|
@ -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);
|
||||
|
@ -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");
|
||||
|
@ -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
|
||||
|
@ -11,6 +11,8 @@ const actionModules = [
|
||||
require("./messages"),
|
||||
require("./ui"),
|
||||
require("./notifications"),
|
||||
require("./object"),
|
||||
require("./toolbox"),
|
||||
require("./history"),
|
||||
];
|
||||
|
||||
|
@ -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,
|
||||
};
|
||||
|
@ -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,
|
||||
};
|
||||
|
@ -11,5 +11,7 @@ DevToolsModules(
|
||||
'input.js',
|
||||
'messages.js',
|
||||
'notifications.js',
|
||||
'object.js',
|
||||
'toolbox.js',
|
||||
'ui.js',
|
||||
)
|
||||
|
53
devtools/client/webconsole/actions/object.js
Normal file
53
devtools/client/webconsole/actions/object.js
Normal 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,
|
||||
};
|
22
devtools/client/webconsole/actions/toolbox.js
Normal file
22
devtools/client/webconsole/actions/toolbox.js
Normal 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,
|
||||
};
|
@ -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,
|
||||
};
|
||||
|
59
devtools/client/webconsole/commands.js
Normal file
59
devtools/client/webconsole/commands.js
Normal 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;
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -17,6 +17,7 @@ DIRS += [
|
||||
DevToolsModules(
|
||||
'browser-console-manager.js',
|
||||
'browser-console.js',
|
||||
'commands.js',
|
||||
'constants.js',
|
||||
'panel.js',
|
||||
'service-container.js',
|
||||
|
@ -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),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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")
|
||||
|
@ -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" },
|
||||
|
@ -6,7 +6,7 @@
|
||||
module.exports = {
|
||||
attachRefToWebConsoleUI: () => {},
|
||||
canRewind: () => false,
|
||||
emitNewMessage: () => {},
|
||||
emitEvent: () => {},
|
||||
proxy: {
|
||||
client: {},
|
||||
releaseActor: actor => console.log("Release actor", actor),
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -459,8 +459,6 @@ class Element : public FragmentOrElement {
|
||||
}
|
||||
}
|
||||
|
||||
mozilla::StyleUrlOrNone GetBindingURL(Document* aDocument);
|
||||
|
||||
Directionality GetComputedDirectionality() const;
|
||||
|
||||
static const uint32_t kAllServoDescendantBits =
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
2
gfx/wr/debugger/dist/build.js.map
vendored
2
gfx/wr/debugger/dist/build.js.map
vendored
File diff suppressed because one or more lines are too long
81
gfx/wr/debugger/package-lock.json
generated
81
gfx/wr/debugger/package-lock.json
generated
@ -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": {
|
||||
|
44
gfx/wr/webrender/res/composite.glsl
Normal file
44
gfx/wr/webrender/res/composite.glsl
Normal 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
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
55
gfx/wr/webrender/src/composite.rs
Normal file
55
gfx/wr/webrender/src/composite.rs
Normal 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),
|
||||
}
|
||||
}
|
||||
}
|
@ -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 {
|
||||
|
@ -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))]
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -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,
|
||||
¤t_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,
|
||||
¤t_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)]
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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());
|
||||
|
||||
|
@ -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");
|
||||
|
@ -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`
|
||||
|
@ -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);
|
||||
},
|
||||
|
@ -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");
|
||||
|
@ -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'
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
195
taskcluster/ci/iris/kind.yml
Normal file
195
taskcluster/ci/iris/kind.yml
Normal 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)
|
29
taskcluster/docker/debian10-test-iris/Dockerfile
Normal file
29
taskcluster/docker/debian10-test-iris/Dockerfile
Normal 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"]
|
88
taskcluster/docker/debian10-test-iris/install_iris_deps.sh
Executable file
88
taskcluster/docker/debian10-test-iris/install_iris_deps.sh
Executable 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"
|
5
taskcluster/docker/debian10-test-iris/motd
Normal file
5
taskcluster/docker/debian10-test-iris/motd
Normal 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.
|
@ -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;
|
@ -607,3 +607,7 @@ visual-metrics
|
||||
--------------
|
||||
Tasks that compute visual performance metrics from videos and images captured
|
||||
by other tasks.
|
||||
|
||||
iris
|
||||
----
|
||||
Iris testing suite
|
||||
|
40
taskcluster/scripts/iris/run-iris-linux.sh
Executable file
40
taskcluster/scripts/iris/run-iris-linux.sh
Executable 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
|
38
taskcluster/scripts/iris/run-iris-macos.sh
Executable file
38
taskcluster/scripts/iris/run-iris-macos.sh
Executable 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
|
68
taskcluster/scripts/iris/run-iris-windows.sh
Executable file
68
taskcluster/scripts/iris/run-iris-windows.sh
Executable 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
|
62
taskcluster/taskgraph/transforms/iris.py
Normal file
62
taskcluster/taskgraph/transforms/iris.py
Normal 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
|
@ -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",
|
||||
|
@ -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)
|
||||
|
@ -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"
|
||||
|
866
toolkit/content/widgets/arrowscrollbox.js
Normal file
866
toolkit/content/widgets/arrowscrollbox.js
Normal 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);
|
||||
}
|
@ -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>
|
@ -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 => {
|
||||
|
@ -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>
|
@ -535,10 +535,6 @@ scrollbox[smoothscroll=true] {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
arrowscrollbox {
|
||||
-moz-binding: url("chrome://global/content/bindings/scrollbox.xml#arrowscrollbox");
|
||||
}
|
||||
|
||||
/********** stringbundle **********/
|
||||
|
||||
stringbundle,
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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");
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user