зеркало из https://github.com/mozilla/gecko-dev.git
Merge mozilla-central to mozilla-inbound. a=merge CLOSED TREE
This commit is contained in:
Коммит
a83eeaf068
|
@ -333,15 +333,6 @@ dependencies = [
|
|||
"time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"core-foundation-sys 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation"
|
||||
version = "0.6.0"
|
||||
|
@ -351,14 +342,6 @@ dependencies = [
|
|||
"libc 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.6.0"
|
||||
|
@ -366,22 +349,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
|
||||
[[package]]
|
||||
name = "core-graphics"
|
||||
version = "0.13.0"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"core-foundation 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"core-foundation 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"foreign-types 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-text"
|
||||
version = "9.2.0"
|
||||
version = "10.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"core-foundation 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"core-graphics 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"core-foundation 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"core-graphics 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"foreign-types 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
@ -2362,9 +2345,9 @@ dependencies = [
|
|||
"bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"core-foundation 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"core-graphics 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"core-text 9.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"core-foundation 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"core-graphics 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"core-text 10.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"dwrote 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"euclid 0.17.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"freetype 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -2391,8 +2374,8 @@ dependencies = [
|
|||
"bincode 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"core-foundation 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"core-graphics 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"core-foundation 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"core-graphics 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"dwrote 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"euclid 0.17.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -2407,8 +2390,8 @@ version = "0.1.0"
|
|||
dependencies = [
|
||||
"app_units 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"bincode 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"core-foundation 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"core-graphics 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"core-foundation 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"core-graphics 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"dwrote 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"euclid 0.17.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"foreign-types 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -2575,12 +2558,10 @@ dependencies = [
|
|||
"checksum clap 2.31.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f0f16b89cbb9ee36d87483dc939fe9f1e13c05898d56d7b230a0d4dff033a536"
|
||||
"checksum cmake 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)" = "56d741ea7a69e577f6d06b36b7dff4738f680593dc27a701ffa8506b73ce28bb"
|
||||
"checksum cookie 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "746858cae4eae40fff37e1998320068df317bc247dc91a67c6cfa053afdc2abb"
|
||||
"checksum core-foundation 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "286e0b41c3a20da26536c6000a280585d519fd07b3956b43aed8a79e9edce980"
|
||||
"checksum core-foundation 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c7caa6cb9e76ddddbea09a03266d6b3bc98cd41e9fb9b017c473e7cca593ec25"
|
||||
"checksum core-foundation-sys 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "716c271e8613ace48344f723b60b900a93150271e5be206212d052bbc0883efa"
|
||||
"checksum core-foundation-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b2a53cce0ddcf7e7e1f998738d757d5a3bf08bf799a180e50ebe50d298f52f5a"
|
||||
"checksum core-graphics 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fb0ed45fdc32f9ab426238fba9407dfead7bacd7900c9b4dd3f396f46eafdae3"
|
||||
"checksum core-text 9.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2bd581c37283d0c23311d179aefbb891f2324ee0405da58a26e8594ab76e5748"
|
||||
"checksum core-graphics 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e54c4ab33705fa1fc8af375bb7929d68e1c1546c1ecef408966d8c3e49a1d84a"
|
||||
"checksum core-text 10.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "81f59bff773954e5cd058a3f5983406b52bec7cc65202bef340ba64a0c40ac91"
|
||||
"checksum cose 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "72fa26cb151d3ae4b70f63d67d0fed57ce04220feafafbae7f503bef7aae590d"
|
||||
"checksum cose-c 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "49726015ab0ca765144fcca61e4a7a543a16b795a777fa53f554da2fffff9a94"
|
||||
"checksum crc 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bd5d02c0aac6bd68393ed69e00bbc2457f3e89075c6349db7189618dc4ddc1d7"
|
||||
|
|
|
@ -8,6 +8,7 @@ var TrackingProtection = {
|
|||
PREF_ENABLED_GLOBALLY: "privacy.trackingprotection.enabled",
|
||||
PREF_ENABLED_IN_PRIVATE_WINDOWS: "privacy.trackingprotection.pbmode.enabled",
|
||||
PREF_APP_MENU_TOGGLE: "privacy.trackingprotection.appMenuToggle.enabled",
|
||||
PREF_ANIMATIONS_ENABLED: "toolkit.cosmeticAnimations.enabled",
|
||||
enabledGlobally: false,
|
||||
enabledInPrivateWindows: false,
|
||||
container: null,
|
||||
|
@ -36,6 +37,10 @@ var TrackingProtection = {
|
|||
this.icon = $("#tracking-protection-icon");
|
||||
this.appMenuContainer = $("#appMenu-tp-container");
|
||||
this.appMenuSeparator = $("#appMenu-tp-separator");
|
||||
this.iconBox = $("#tracking-protection-icon-box");
|
||||
this.animatedIcon = $("#tracking-protection-icon-animatable-image");
|
||||
this.animatedIcon.addEventListener("animationend", () => this.iconBox.removeAttribute("animate"));
|
||||
|
||||
this.broadcaster = $("#trackingProtectionBroadcaster");
|
||||
|
||||
this.enableTooltip =
|
||||
|
@ -47,6 +52,15 @@ var TrackingProtection = {
|
|||
this.disableTooltipPB =
|
||||
gNavigatorBundle.getString("trackingProtection.toggle.disable.pbmode.tooltip");
|
||||
|
||||
this.updateAnimationsEnabled = () => {
|
||||
this.iconBox.toggleAttribute("animationsenabled",
|
||||
Services.prefs.getBoolPref(this.PREF_ANIMATIONS_ENABLED, false));
|
||||
};
|
||||
|
||||
this.updateAnimationsEnabled();
|
||||
|
||||
Services.prefs.addObserver(this.PREF_ANIMATIONS_ENABLED, this.updateAnimationsEnabled);
|
||||
|
||||
this.updateEnabled();
|
||||
|
||||
this.updateAppMenuToggle = () => {
|
||||
|
@ -78,6 +92,7 @@ var TrackingProtection = {
|
|||
Services.prefs.removeObserver(this.PREF_ENABLED_GLOBALLY, this);
|
||||
Services.prefs.removeObserver(this.PREF_ENABLED_IN_PRIVATE_WINDOWS, this);
|
||||
Services.prefs.removeObserver(this.PREF_APP_MENU_TOGGLE, this.updateAppMenuToggle);
|
||||
Services.prefs.removeObserver(this.PREF_ANIMATIONS_ENABLED, this.updateAnimationsEnabled);
|
||||
},
|
||||
|
||||
observe() {
|
||||
|
@ -156,21 +171,29 @@ var TrackingProtection = {
|
|||
Services.telemetry.getHistogramById("TRACKING_PROTECTION_SHIELD").add(value);
|
||||
},
|
||||
|
||||
onSecurityChange(state, isSimulated) {
|
||||
cancelAnimation() {
|
||||
let iconAnimation = this.animatedIcon.getAnimations()[0];
|
||||
if (iconAnimation && iconAnimation.currentTime) {
|
||||
iconAnimation.cancel();
|
||||
}
|
||||
this.iconBox.removeAttribute("animate");
|
||||
},
|
||||
|
||||
onSecurityChange(state, webProgress, isSimulated) {
|
||||
let baseURI = this._baseURIForChannelClassifier;
|
||||
|
||||
// Don't deal with about:, file: etc.
|
||||
if (!baseURI) {
|
||||
this.icon.removeAttribute("state");
|
||||
this.cancelAnimation();
|
||||
this.iconBox.removeAttribute("state");
|
||||
return;
|
||||
}
|
||||
|
||||
// Only animate the shield if the event was not fired directly from
|
||||
// the tabbrowser (due to a browser change).
|
||||
if (isSimulated) {
|
||||
this.icon.removeAttribute("animate");
|
||||
} else {
|
||||
this.icon.setAttribute("animate", "true");
|
||||
// The user might have navigated before the shield animation
|
||||
// finished. In this case, reset the animation to be able to
|
||||
// play it in full again and avoid choppiness.
|
||||
if (webProgress.isTopLevel) {
|
||||
this.cancelAnimation();
|
||||
}
|
||||
|
||||
let isBlocking = state & Ci.nsIWebProgressListener.STATE_BLOCKED_TRACKING_CONTENT;
|
||||
|
@ -192,8 +215,14 @@ var TrackingProtection = {
|
|||
}
|
||||
|
||||
if (isBlocking && this.enabled) {
|
||||
this.icon.setAttribute("tooltiptext", this.activeTooltipText);
|
||||
this.icon.setAttribute("state", "blocked-tracking-content");
|
||||
if (isSimulated) {
|
||||
this.cancelAnimation();
|
||||
} else if (webProgress.isTopLevel) {
|
||||
this.iconBox.setAttribute("animate", "true");
|
||||
}
|
||||
|
||||
this.iconBox.setAttribute("tooltiptext", this.activeTooltipText);
|
||||
this.iconBox.setAttribute("state", "blocked-tracking-content");
|
||||
this.content.setAttribute("state", "blocked-tracking-content");
|
||||
|
||||
// Open the tracking protection introduction panel, if applicable.
|
||||
|
@ -208,22 +237,28 @@ var TrackingProtection = {
|
|||
|
||||
this.shieldHistogramAdd(2);
|
||||
} else if (isAllowing) {
|
||||
if (isSimulated) {
|
||||
this.cancelAnimation();
|
||||
} else if (webProgress.isTopLevel) {
|
||||
this.iconBox.setAttribute("animate", "true");
|
||||
}
|
||||
|
||||
// Only show the shield when TP is enabled for now.
|
||||
if (this.enabled) {
|
||||
this.icon.setAttribute("tooltiptext", this.disabledTooltipText);
|
||||
this.icon.setAttribute("state", "loaded-tracking-content");
|
||||
this.iconBox.setAttribute("tooltiptext", this.disabledTooltipText);
|
||||
this.iconBox.setAttribute("state", "loaded-tracking-content");
|
||||
this.shieldHistogramAdd(1);
|
||||
} else {
|
||||
this.icon.removeAttribute("tooltiptext");
|
||||
this.icon.removeAttribute("state");
|
||||
this.iconBox.removeAttribute("tooltiptext");
|
||||
this.iconBox.removeAttribute("state");
|
||||
this.shieldHistogramAdd(0);
|
||||
}
|
||||
|
||||
// Warn in the control center even with TP disabled.
|
||||
this.content.setAttribute("state", "loaded-tracking-content");
|
||||
} else {
|
||||
this.icon.removeAttribute("tooltiptext");
|
||||
this.icon.removeAttribute("state");
|
||||
this.iconBox.removeAttribute("tooltiptext");
|
||||
this.iconBox.removeAttribute("state");
|
||||
this.content.removeAttribute("state");
|
||||
|
||||
// We didn't show the shield
|
||||
|
|
|
@ -4898,7 +4898,7 @@ var XULBrowserWindow = {
|
|||
uri = Services.uriFixup.createExposableURI(uri);
|
||||
} catch (e) {}
|
||||
gIdentityHandler.updateIdentity(this._state, uri);
|
||||
TrackingProtection.onSecurityChange(this._state, aIsSimulated);
|
||||
TrackingProtection.onSecurityChange(this._state, aWebProgress, aIsSimulated);
|
||||
},
|
||||
|
||||
// simulate all change notifications after switching tabs
|
||||
|
|
|
@ -798,7 +798,12 @@
|
|||
consumeanchor="identity-box"
|
||||
onclick="PageProxyClickHandler(event);"/>
|
||||
<image id="sharing-icon" mousethrough="always"/>
|
||||
<image id="tracking-protection-icon"/>
|
||||
<box id="tracking-protection-icon-box" animationsenabled="true">
|
||||
<image id="tracking-protection-icon"/>
|
||||
<box id="tracking-protection-icon-animatable-box" flex="1">
|
||||
<image id="tracking-protection-icon-animatable-image" flex="1"/>
|
||||
</box>
|
||||
</box>
|
||||
<box id="blocked-permissions-container" align="center">
|
||||
<image data-permission-id="geo" class="blocked-permission-icon geo-icon" role="button"
|
||||
tooltiptext="&urlbar.geolocationBlocked.tooltip;"/>
|
||||
|
|
|
@ -2495,12 +2495,22 @@ window._gBrowser = {
|
|||
gBrowser._numPinnedTabs;
|
||||
break;
|
||||
case this.closingTabsEnum.OTHER:
|
||||
tabsToClose = this.visibleTabs.length - 1 - gBrowser._numPinnedTabs;
|
||||
if (!aTab) {
|
||||
throw new Error("Required argument missing: aTab");
|
||||
}
|
||||
if (aTab.multiselected) {
|
||||
tabsToClose = this.visibleTabs.filter(tab => !tab.multiselected && !tab.pinned).length;
|
||||
} else {
|
||||
// If aTab is pinned, it will already be considered
|
||||
// with gBrowser._numPinnedTabs.
|
||||
tabsToClose = this.visibleTabs.length - gBrowser._numPinnedTabs -
|
||||
(aTab.pinned ? 0 : 1);
|
||||
}
|
||||
break;
|
||||
case this.closingTabsEnum.TO_END:
|
||||
if (!aTab)
|
||||
if (!aTab) {
|
||||
throw new Error("Required argument missing: aTab");
|
||||
|
||||
}
|
||||
tabsToClose = this.getTabsToTheEndFrom(aTab).length;
|
||||
break;
|
||||
case this.closingTabsEnum.MULTI_SELECTED:
|
||||
|
@ -2578,14 +2588,23 @@ window._gBrowser = {
|
|||
this.removeTabs(tabs);
|
||||
},
|
||||
|
||||
/**
|
||||
* In a multi-select context, all unpinned and unselected tabs are removed.
|
||||
* Otherwise all unpinned tabs except aTab are removed.
|
||||
*/
|
||||
removeAllTabsBut(aTab) {
|
||||
if (!this.warnAboutClosingTabs(this.closingTabsEnum.OTHER)) {
|
||||
if (!this.warnAboutClosingTabs(this.closingTabsEnum.OTHER, aTab)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let tabs = this.visibleTabs.filter(tab => tab != aTab && !tab.pinned);
|
||||
this.selectedTab = aTab;
|
||||
this.removeTabs(tabs);
|
||||
let tabsToRemove = [];
|
||||
if (aTab && aTab.multiselected) {
|
||||
tabsToRemove = this.visibleTabs.filter(tab => !tab.multiselected && !tab.pinned);
|
||||
} else {
|
||||
tabsToRemove = this.visibleTabs.filter(tab => tab != aTab && !tab.pinned);
|
||||
this.selectedTab = aTab;
|
||||
}
|
||||
this.removeTabs(tabsToRemove);
|
||||
},
|
||||
|
||||
removeMultiSelectedTabs() {
|
||||
|
|
|
@ -44,38 +44,38 @@ async function testTrackingProtectionAnimation() {
|
|||
info("Load a test page not containing tracking elements");
|
||||
let benignTab = await BrowserTestUtils.openNewForegroundTab(tabbrowser, BENIGN_PAGE);
|
||||
|
||||
ok(!TrackingProtection.icon.hasAttribute("state"), "icon: no state");
|
||||
ok(TrackingProtection.icon.hasAttribute("animate"), "icon: animate");
|
||||
ok(!TrackingProtection.iconBox.hasAttribute("state"), "iconBox: no state");
|
||||
ok(!TrackingProtection.iconBox.hasAttribute("animate"), "iconBox: no animate");
|
||||
|
||||
info("Load a test page containing tracking elements");
|
||||
let trackingTab = await BrowserTestUtils.openNewForegroundTab(tabbrowser, TRACKING_PAGE);
|
||||
|
||||
ok(TrackingProtection.icon.hasAttribute("state"), "icon: state");
|
||||
ok(TrackingProtection.icon.hasAttribute("animate"), "icon: animate");
|
||||
ok(TrackingProtection.iconBox.hasAttribute("state"), "iconBox: state");
|
||||
ok(TrackingProtection.iconBox.hasAttribute("animate"), "iconBox: animate");
|
||||
|
||||
info("Switch from tracking -> benign tab");
|
||||
let securityChanged = waitForSecurityChange();
|
||||
tabbrowser.selectedTab = benignTab;
|
||||
await securityChanged;
|
||||
|
||||
ok(!TrackingProtection.icon.hasAttribute("state"), "icon: no state");
|
||||
ok(!TrackingProtection.icon.hasAttribute("animate"), "icon: no animate");
|
||||
ok(!TrackingProtection.iconBox.hasAttribute("state"), "iconBox: no state");
|
||||
ok(!TrackingProtection.iconBox.hasAttribute("animate"), "iconBox: no animate");
|
||||
|
||||
info("Switch from benign -> tracking tab");
|
||||
securityChanged = waitForSecurityChange();
|
||||
tabbrowser.selectedTab = trackingTab;
|
||||
await securityChanged;
|
||||
|
||||
ok(TrackingProtection.icon.hasAttribute("state"), "icon: state");
|
||||
ok(!TrackingProtection.icon.hasAttribute("animate"), "icon: no animate");
|
||||
ok(TrackingProtection.iconBox.hasAttribute("state"), "iconBox: state");
|
||||
ok(!TrackingProtection.iconBox.hasAttribute("animate"), "iconBox: no animate");
|
||||
|
||||
info("Reload tracking tab");
|
||||
securityChanged = waitForSecurityChange(2);
|
||||
tabbrowser.reload();
|
||||
await securityChanged;
|
||||
|
||||
ok(TrackingProtection.icon.hasAttribute("state"), "icon: state");
|
||||
ok(TrackingProtection.icon.hasAttribute("animate"), "icon: animate");
|
||||
ok(TrackingProtection.iconBox.hasAttribute("state"), "iconBox: state");
|
||||
ok(TrackingProtection.iconBox.hasAttribute("animate"), "iconBox: animate");
|
||||
}
|
||||
|
||||
add_task(async function testNormalBrowsing() {
|
||||
|
|
|
@ -38,10 +38,10 @@ function testTrackingPage(window) {
|
|||
ok(!TrackingProtection.container.hidden, "The container is visible");
|
||||
is(TrackingProtection.content.getAttribute("state"), "blocked-tracking-content",
|
||||
'content: state="blocked-tracking-content"');
|
||||
is(TrackingProtection.icon.getAttribute("state"), "blocked-tracking-content",
|
||||
'icon: state="blocked-tracking-content"');
|
||||
is(TrackingProtection.iconBox.getAttribute("state"), "blocked-tracking-content",
|
||||
'iconBox: state="blocked-tracking-content"');
|
||||
|
||||
ok(!hidden("#tracking-protection-icon"), "icon is visible");
|
||||
ok(!hidden("#tracking-protection-icon-box"), "icon box is visible");
|
||||
ok(hidden("#tracking-action-block"), "blockButton is hidden");
|
||||
|
||||
ok(hidden("#tracking-action-unblock"), "unblockButton is hidden");
|
||||
|
@ -59,10 +59,10 @@ function testTrackingPageUnblocked() {
|
|||
ok(!TrackingProtection.container.hidden, "The container is visible");
|
||||
is(TrackingProtection.content.getAttribute("state"), "loaded-tracking-content",
|
||||
'content: state="loaded-tracking-content"');
|
||||
is(TrackingProtection.icon.getAttribute("state"), "loaded-tracking-content",
|
||||
'icon: state="loaded-tracking-content"');
|
||||
is(TrackingProtection.iconBox.getAttribute("state"), "loaded-tracking-content",
|
||||
'iconBox: state="loaded-tracking-content"');
|
||||
|
||||
ok(!hidden("#tracking-protection-icon"), "icon is visible");
|
||||
ok(!hidden("#tracking-protection-icon-box"), "icon box is visible");
|
||||
ok(!hidden("#tracking-action-block"), "blockButton is visible");
|
||||
ok(hidden("#tracking-action-unblock"), "unblockButton is hidden");
|
||||
|
||||
|
|
|
@ -35,9 +35,9 @@ add_task(async function test_fetch() {
|
|||
|
||||
is(TrackingProtection.content.getAttribute("state"), "blocked-tracking-content",
|
||||
'content: state="blocked-tracking-content"');
|
||||
is(TrackingProtection.icon.getAttribute("state"), "blocked-tracking-content",
|
||||
'icon: state="blocked-tracking-content"');
|
||||
is(TrackingProtection.icon.getAttribute("tooltiptext"),
|
||||
is(TrackingProtection.iconBox.getAttribute("state"), "blocked-tracking-content",
|
||||
'iconBox: state="blocked-tracking-content"');
|
||||
is(TrackingProtection.iconBox.getAttribute("tooltiptext"),
|
||||
gNavigatorBundle.getString("trackingProtection.icon.activeTooltip"), "correct tooltip");
|
||||
});
|
||||
});
|
||||
|
|
|
@ -20,6 +20,7 @@ support-files =
|
|||
test_bug1358314.html
|
||||
[browser_isLocalAboutURI.js]
|
||||
[browser_multiselect_tabs_active_tab_selected_by_default.js]
|
||||
[browser_multiselect_tabs_close_other_tabs.js]
|
||||
[browser_multiselect_tabs_close_using_shortcuts.js]
|
||||
[browser_multiselect_tabs_close.js]
|
||||
[browser_multiselect_tabs_mute_unmute.js]
|
||||
|
|
|
@ -0,0 +1,108 @@
|
|||
const PREF_MULTISELECT_TABS = "browser.tabs.multiselect";
|
||||
const PREF_WARN_ON_CLOSE = "browser.tabs.warnOnCloseOtherTabs";
|
||||
|
||||
add_task(async function setPref() {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [
|
||||
[PREF_MULTISELECT_TABS, true],
|
||||
[PREF_WARN_ON_CLOSE, false]
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function withAMultiSelectedTab() {
|
||||
let initialTab = gBrowser.selectedTab;
|
||||
let tab1 = await addTab();
|
||||
let tab2 = await addTab();
|
||||
let tab3 = await addTab();
|
||||
let tab4 = await addTab();
|
||||
|
||||
is(gBrowser.multiSelectedTabsCount, 0, "Zero multiselected tabs");
|
||||
|
||||
await triggerClickOn(tab1, { ctrlKey: true });
|
||||
|
||||
let tab4Pinned = BrowserTestUtils.waitForEvent(tab4, "TabPinned");
|
||||
gBrowser.pinTab(tab4);
|
||||
await tab4Pinned;
|
||||
|
||||
ok(initialTab.multiselected, "InitialTab is multiselected");
|
||||
ok(tab1.multiselected, "Tab1 is multiselected");
|
||||
ok(!tab2.multiselected, "Tab2 is not multiselected");
|
||||
ok(!tab3.multiselected, "Tab3 is not multiselected");
|
||||
ok(!tab4.multiselected, "Tab4 is not multiselected");
|
||||
ok(tab4.pinned, "Tab4 is pinned");
|
||||
is(gBrowser.multiSelectedTabsCount, 2, "Two multiselected tabs");
|
||||
is(gBrowser.selectedTab, initialTab, "InitialTab is the active tab");
|
||||
|
||||
let closingTabs = [tab2, tab3];
|
||||
let tabClosingPromises = [];
|
||||
for (let tab of closingTabs) {
|
||||
tabClosingPromises.push(BrowserTestUtils.waitForTabClosing(tab));
|
||||
}
|
||||
|
||||
gBrowser.removeAllTabsBut(tab1);
|
||||
|
||||
for (let promise of tabClosingPromises) {
|
||||
await promise;
|
||||
}
|
||||
|
||||
ok(!initialTab.closing, "InitialTab is not closing");
|
||||
ok(!tab1.closing, "Tab1 is not closing");
|
||||
ok(tab2.closing, "Tab2 is closing");
|
||||
ok(tab3.closing, "Tab3 is closing");
|
||||
ok(!tab4.closing, "Tab4 is not closing");
|
||||
is(gBrowser.multiSelectedTabsCount, 2, "Two multiselected tabs");
|
||||
is(gBrowser.selectedTab, initialTab, "InitialTab is still the active tab");
|
||||
|
||||
gBrowser.clearMultiSelectedTabs(false);
|
||||
BrowserTestUtils.removeTab(tab1);
|
||||
BrowserTestUtils.removeTab(tab4);
|
||||
});
|
||||
|
||||
add_task(async function withNotAMultiSelectedTab() {
|
||||
let initialTab = gBrowser.selectedTab;
|
||||
let tab1 = await addTab();
|
||||
let tab2 = await addTab();
|
||||
let tab3 = await addTab();
|
||||
let tab4 = await addTab();
|
||||
|
||||
is(gBrowser.multiSelectedTabsCount, 0, "Zero multiselected tabs");
|
||||
|
||||
await BrowserTestUtils.switchTab(gBrowser, tab1);
|
||||
await triggerClickOn(tab2, { ctrlKey: true });
|
||||
|
||||
let tab4Pinned = BrowserTestUtils.waitForEvent(tab4, "TabPinned");
|
||||
gBrowser.pinTab(tab4);
|
||||
await tab4Pinned;
|
||||
|
||||
ok(!initialTab.multiselected, "InitialTab is not multiselected");
|
||||
ok(tab1.multiselected, "Tab1 is multiselected");
|
||||
ok(tab2.multiselected, "Tab2 is multiselected");
|
||||
ok(!tab3.multiselected, "Tab3 is not multiselected");
|
||||
ok(!tab4.multiselected, "Tab4 is not multiselected");
|
||||
ok(tab4.pinned, "Tab4 is pinned");
|
||||
is(gBrowser.multiSelectedTabsCount, 2, "Two multiselected tabs");
|
||||
is(gBrowser.selectedTab, tab1, "Tab1 is the active tab");
|
||||
|
||||
let closingTabs = [tab1, tab2, tab3];
|
||||
let tabClosingPromises = [];
|
||||
for (let tab of closingTabs) {
|
||||
tabClosingPromises.push(BrowserTestUtils.waitForTabClosing(tab));
|
||||
}
|
||||
|
||||
await BrowserTestUtils.switchTab(gBrowser, gBrowser.removeAllTabsBut(initialTab));
|
||||
|
||||
for (let promise of tabClosingPromises) {
|
||||
await promise;
|
||||
}
|
||||
|
||||
ok(!initialTab.closing, "InitialTab is not closing");
|
||||
ok(tab1.closing, "Tab1 is closing");
|
||||
ok(tab2.closing, "Tab2 is closing");
|
||||
ok(tab3.closing, "Tab3 is closing");
|
||||
ok(!tab4.closing, "Tab4 is not closing");
|
||||
is(gBrowser.multiSelectedTabsCount, 0, "Zero multiselected tabs");
|
||||
is(gBrowser.selectedTab, initialTab, "InitialTab is the active tab now");
|
||||
|
||||
BrowserTestUtils.removeTab(tab4);
|
||||
});
|
|
@ -5,6 +5,7 @@ support-files =
|
|||
../general/benignPage.html
|
||||
../general/trackingPage.html
|
||||
|
||||
[browser_trackingUI_animation.js]
|
||||
[browser_trackingUI_appMenu.js]
|
||||
[browser_trackingUI_appMenu_toggle.js]
|
||||
[browser_trackingUI_open_preferences.js]
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
const TRACKING_PAGE = "http://tracking.example.org/browser/browser/base/content/test/trackingUI/trackingPage.html";
|
||||
const BENIGN_PAGE = "http://tracking.example.org/browser/browser/base/content/test/trackingUI/benignPage.html";
|
||||
const TP_PREF = "privacy.trackingprotection.enabled";
|
||||
const ANIMATIONS_PREF = "toolkit.cosmeticAnimations.enabled";
|
||||
|
||||
// Test that the shield icon animation can be controlled by the cosmetic
|
||||
// animations pref and that one of the icons is visible in each case.
|
||||
add_task(async function testShieldAnimation() {
|
||||
await UrlClassifierTestUtils.addTestTrackers();
|
||||
Services.prefs.setBoolPref(TP_PREF, true);
|
||||
|
||||
let tab = gBrowser.selectedTab = gBrowser.addTab();
|
||||
|
||||
let animationIcon = document.getElementById("tracking-protection-icon-animatable-image");
|
||||
let noAnimationIcon = document.getElementById("tracking-protection-icon");
|
||||
|
||||
Services.prefs.setBoolPref(ANIMATIONS_PREF, true);
|
||||
await promiseTabLoadEvent(tab, TRACKING_PAGE);
|
||||
ok(BrowserTestUtils.is_hidden(noAnimationIcon), "the default icon is hidden when animations are enabled");
|
||||
ok(BrowserTestUtils.is_visible(animationIcon), "the animated icon is shown when animations are enabled");
|
||||
|
||||
await promiseTabLoadEvent(tab, BENIGN_PAGE);
|
||||
ok(BrowserTestUtils.is_hidden(animationIcon), "the animated icon is hidden");
|
||||
ok(BrowserTestUtils.is_hidden(noAnimationIcon), "the default icon is hidden");
|
||||
|
||||
Services.prefs.setBoolPref(ANIMATIONS_PREF, false);
|
||||
await promiseTabLoadEvent(tab, TRACKING_PAGE);
|
||||
ok(BrowserTestUtils.is_visible(noAnimationIcon), "the default icon is shown when animations are disabled");
|
||||
ok(BrowserTestUtils.is_hidden(animationIcon), "the animated icon is hidden when animations are disabled");
|
||||
|
||||
gBrowser.removeCurrentTab();
|
||||
Services.prefs.clearUserPref(ANIMATIONS_PREF);
|
||||
Services.prefs.clearUserPref(TP_PREF);
|
||||
UrlClassifierTestUtils.cleanupTestTrackers();
|
||||
});
|
|
@ -6,8 +6,6 @@
|
|||
const PREF = "privacy.trackingprotection.enabled";
|
||||
const TRACKING_PAGE = "http://tracking.example.org/browser/browser/base/content/test/trackingUI/trackingPage.html";
|
||||
|
||||
var {UrlClassifierTestUtils} = ChromeUtils.import("resource://testing-common/UrlClassifierTestUtils.jsm", {});
|
||||
|
||||
async function waitAndAssertPreferencesShown() {
|
||||
await BrowserTestUtils.waitForEvent(gIdentityHandler._identityPopup, "popuphidden");
|
||||
await TestUtils.waitForCondition(() => gBrowser.currentURI.spec == "about:preferences#privacy",
|
||||
|
|
|
@ -6,8 +6,6 @@
|
|||
const PREF = "privacy.trackingprotection.enabled";
|
||||
const TRACKING_PAGE = "http://tracking.example.org/browser/browser/base/content/test/trackingUI/trackingPage.html";
|
||||
|
||||
var {UrlClassifierTestUtils} = ChromeUtils.import("resource://testing-common/UrlClassifierTestUtils.jsm", {});
|
||||
|
||||
// TODO: replace this once bug 1428847 is done.
|
||||
function hidden(el) {
|
||||
let win = el.ownerGlobal;
|
||||
|
|
|
@ -21,8 +21,6 @@ const TRACKING_PAGE = "http://tracking.example.org/browser/browser/base/content/
|
|||
var TrackingProtection = null;
|
||||
var tabbrowser = null;
|
||||
|
||||
var {UrlClassifierTestUtils} = ChromeUtils.import("resource://testing-common/UrlClassifierTestUtils.jsm", {});
|
||||
|
||||
registerCleanupFunction(function() {
|
||||
TrackingProtection = tabbrowser = null;
|
||||
UrlClassifierTestUtils.cleanupTestTrackers();
|
||||
|
@ -33,6 +31,9 @@ registerCleanupFunction(function() {
|
|||
}
|
||||
});
|
||||
|
||||
// This is a special version of "hidden" that doesn't check for item
|
||||
// visibility and just asserts the display and opacity attributes.
|
||||
// That way we can test elements even when their panel is hidden...
|
||||
function hidden(sel) {
|
||||
let win = tabbrowser.ownerGlobal;
|
||||
let el = win.document.querySelector(sel);
|
||||
|
@ -51,10 +52,11 @@ function testBenignPage() {
|
|||
info("Non-tracking content must not be blocked");
|
||||
ok(!TrackingProtection.container.hidden, "The container is visible");
|
||||
ok(!TrackingProtection.content.hasAttribute("state"), "content: no state");
|
||||
ok(!TrackingProtection.icon.hasAttribute("state"), "icon: no state");
|
||||
ok(!TrackingProtection.icon.hasAttribute("tooltiptext"), "icon: no tooltip");
|
||||
ok(!TrackingProtection.iconBox.hasAttribute("state"), "icon box: no state");
|
||||
ok(!TrackingProtection.iconBox.hasAttribute("tooltiptext"), "icon box: no tooltip");
|
||||
|
||||
ok(hidden("#tracking-protection-icon"), "icon is hidden");
|
||||
let doc = tabbrowser.ownerGlobal.document;
|
||||
ok(BrowserTestUtils.is_hidden(doc.getElementById("tracking-protection-icon-box")), "icon box is hidden");
|
||||
ok(hidden("#tracking-action-block"), "blockButton is hidden");
|
||||
ok(hidden("#tracking-action-unblock"), "unblockButton is hidden");
|
||||
ok(!hidden("#tracking-protection-preferences-button"), "preferences button is visible");
|
||||
|
@ -70,10 +72,11 @@ function testBenignPageWithException() {
|
|||
ok(!TrackingProtection.container.hidden, "The container is visible");
|
||||
ok(!TrackingProtection.content.hasAttribute("state"), "content: no state");
|
||||
ok(TrackingProtection.content.hasAttribute("hasException"), "content has exception attribute");
|
||||
ok(!TrackingProtection.icon.hasAttribute("state"), "icon: no state");
|
||||
ok(!TrackingProtection.icon.hasAttribute("tooltiptext"), "icon: no tooltip");
|
||||
ok(!TrackingProtection.iconBox.hasAttribute("state"), "icon box: no state");
|
||||
ok(!TrackingProtection.iconBox.hasAttribute("tooltiptext"), "icon box: no tooltip");
|
||||
|
||||
ok(hidden("#tracking-protection-icon"), "icon is hidden");
|
||||
let doc = tabbrowser.ownerGlobal.document;
|
||||
ok(BrowserTestUtils.is_hidden(doc.getElementById("tracking-protection-icon-box")), "icon box is hidden");
|
||||
is(!hidden("#tracking-action-block"), TrackingProtection.enabled,
|
||||
"blockButton is visible if TP is on");
|
||||
ok(hidden("#tracking-action-unblock"), "unblockButton is hidden");
|
||||
|
@ -92,12 +95,13 @@ function testTrackingPage(window) {
|
|||
ok(!TrackingProtection.container.hidden, "The container is visible");
|
||||
is(TrackingProtection.content.getAttribute("state"), "blocked-tracking-content",
|
||||
'content: state="blocked-tracking-content"');
|
||||
is(TrackingProtection.icon.getAttribute("state"), "blocked-tracking-content",
|
||||
'icon: state="blocked-tracking-content"');
|
||||
is(TrackingProtection.icon.getAttribute("tooltiptext"),
|
||||
is(TrackingProtection.iconBox.getAttribute("state"), "blocked-tracking-content",
|
||||
'icon box: state="blocked-tracking-content"');
|
||||
is(TrackingProtection.iconBox.getAttribute("tooltiptext"),
|
||||
gNavigatorBundle.getString("trackingProtection.icon.activeTooltip"), "correct tooltip");
|
||||
|
||||
ok(!hidden("#tracking-protection-icon"), "icon is visible");
|
||||
let doc = tabbrowser.ownerGlobal.document;
|
||||
ok(BrowserTestUtils.is_visible(doc.getElementById("tracking-protection-icon-box")), "icon box is visible");
|
||||
ok(hidden("#tracking-action-block"), "blockButton is hidden");
|
||||
ok(!hidden("#tracking-protection-preferences-button"), "preferences button is visible");
|
||||
|
||||
|
@ -123,13 +127,14 @@ function testTrackingPageUnblocked() {
|
|||
is(TrackingProtection.content.getAttribute("state"), "loaded-tracking-content",
|
||||
'content: state="loaded-tracking-content"');
|
||||
if (TrackingProtection.enabled) {
|
||||
is(TrackingProtection.icon.getAttribute("state"), "loaded-tracking-content",
|
||||
'icon: state="loaded-tracking-content"');
|
||||
is(TrackingProtection.icon.getAttribute("tooltiptext"),
|
||||
is(TrackingProtection.iconBox.getAttribute("state"), "loaded-tracking-content",
|
||||
'icon box: state="loaded-tracking-content"');
|
||||
is(TrackingProtection.iconBox.getAttribute("tooltiptext"),
|
||||
gNavigatorBundle.getString("trackingProtection.icon.disabledTooltip"), "correct tooltip");
|
||||
}
|
||||
|
||||
is(!hidden("#tracking-protection-icon"), TrackingProtection.enabled, "icon is visible if TP is on");
|
||||
let doc = tabbrowser.ownerGlobal.document;
|
||||
is(BrowserTestUtils.is_visible(doc.getElementById("tracking-protection-icon-box")), TrackingProtection.enabled, "icon box is visible if TP is on");
|
||||
is(!hidden("#tracking-action-block"), TrackingProtection.enabled, "blockButton is visible if TP is on");
|
||||
ok(hidden("#tracking-action-unblock"), "unblockButton is hidden");
|
||||
ok(!hidden("#tracking-protection-preferences-button"), "preferences button is visible");
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
var {UrlClassifierTestUtils} = ChromeUtils.import("resource://testing-common/UrlClassifierTestUtils.jsm", {});
|
||||
|
||||
/**
|
||||
* Waits for a load (or custom) event to finish in a given tab. If provided
|
||||
* load an uri into the tab.
|
||||
|
|
|
@ -187,7 +187,12 @@ var UITour = {
|
|||
},
|
||||
}],
|
||||
["trackingProtection", {
|
||||
query: "#tracking-protection-icon",
|
||||
query: (aDocument) => {
|
||||
if (Services.prefs.getBoolPref("toolkit.cosmeticAnimations.enabled", false)) {
|
||||
return aDocument.getElementById("tracking-protection-icon-animatable-box");
|
||||
}
|
||||
return aDocument.getElementById("tracking-protection-icon");
|
||||
},
|
||||
}],
|
||||
["urlbar", {
|
||||
query: "#urlbar",
|
||||
|
|
|
@ -5,7 +5,7 @@ const PREF_TP_ENABLED = "privacy.trackingprotection.enabled";
|
|||
const BENIGN_PAGE = "http://tracking.example.org/browser/browser/base/content/test/general/benignPage.html";
|
||||
const TRACKING_PAGE = "http://tracking.example.org/browser/browser/base/content/test/general/trackingPage.html";
|
||||
const TOOLTIP_PANEL = document.getElementById("UITourTooltip");
|
||||
const TOOLTIP_ANCHOR = document.getElementById("tracking-protection-icon");
|
||||
const TOOLTIP_ANCHOR = document.getElementById("tracking-protection-icon-animatable-box");
|
||||
|
||||
var {UrlClassifierTestUtils} = ChromeUtils.import("resource://testing-common/UrlClassifierTestUtils.jsm", {});
|
||||
|
||||
|
|
|
@ -23,8 +23,6 @@
|
|||
--chrome-secondary-background-color: hsl(240, 1%, 20%);
|
||||
--toolbox-border-bottom-color: hsl(240, 5%, 5%);
|
||||
--chrome-nav-bar-controls-border-color: hsla(240, 5%, 5%, .3);
|
||||
--chrome-selection-color: #fff;
|
||||
--chrome-selection-background-color: #5675B9;
|
||||
|
||||
/* Url and search bars */
|
||||
--lwt-toolbar-field-background-color: rgb(71, 71, 73);
|
||||
|
@ -45,8 +43,6 @@
|
|||
--chrome-secondary-background-color: #f5f6f7;
|
||||
--toolbox-border-bottom-color: #cccccc;
|
||||
--chrome-nav-bar-controls-border-color: #ccc;
|
||||
--chrome-selection-color: #f5f7fa;
|
||||
--chrome-selection-background-color: #4c9ed9;
|
||||
}
|
||||
|
||||
#tabbrowser-tabs:-moz-lwtheme {
|
||||
|
@ -59,13 +55,6 @@ toolbar[brighttext] .toolbarbutton-1 {
|
|||
fill: rgb(249, 249, 250);
|
||||
}
|
||||
|
||||
#urlbar ::-moz-selection,
|
||||
#navigator-toolbox .searchbar-textbox ::-moz-selection,
|
||||
.browserContainer > findbar ::-moz-selection {
|
||||
background-color: var(--chrome-selection-background-color);
|
||||
color: var(--chrome-selection-color);
|
||||
}
|
||||
|
||||
/* Change the base colors for the browser chrome */
|
||||
|
||||
#TabsToolbar,
|
||||
|
|
|
@ -147,33 +147,122 @@
|
|||
|
||||
/* TRACKING PROTECTION ICON */
|
||||
|
||||
#tracking-protection-icon {
|
||||
list-style-image: url(chrome://browser/skin/tracking-protection.svg);
|
||||
margin-inline-end: 0;
|
||||
#tracking-protection-icon-box {
|
||||
visibility: collapse;
|
||||
overflow: hidden;
|
||||
width: 20px;
|
||||
margin-inline-end: -20px;
|
||||
}
|
||||
|
||||
#tracking-protection-icon[state="loaded-tracking-content"] {
|
||||
list-style-image: url(chrome://browser/skin/tracking-protection-disabled.svg);
|
||||
#tracking-protection-icon-box[state] {
|
||||
margin-inline-end: 0px;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
#tracking-protection-icon[animate] {
|
||||
#tracking-protection-icon-box[animationsenabled][animate] {
|
||||
transition: margin-left 200ms ease-out, margin-right 200ms ease-out;
|
||||
}
|
||||
|
||||
#tracking-protection-icon:not([state]) {
|
||||
margin-inline-end: -20px;
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
/* Only animate the shield in, when it disappears hide it immediately. */
|
||||
transition: none;
|
||||
list-style-image: none;
|
||||
#tracking-protection-icon-box[state="blocked-tracking-content"][animationsenabled] > #tracking-protection-icon,
|
||||
#tracking-protection-icon-box:not([animationsenabled]) > #tracking-protection-icon-animatable-box {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#tracking-protection-icon-box > #tracking-protection-icon-animatable-box {
|
||||
position: absolute;
|
||||
overflow: hidden;
|
||||
top: calc(50% - 10px); /* half the height of the sprite */
|
||||
margin-inline-start: 4px;
|
||||
width: 16px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
#tracking-protection-icon-box[state="blocked-tracking-content"] #tracking-protection-icon-animatable-image {
|
||||
background-image: url(chrome://browser/skin/tracking-protection-animation.svg);
|
||||
transform: translateX(-1232px);
|
||||
width: 1248px;
|
||||
background-size: auto;
|
||||
height: 16px;
|
||||
min-height: 20px;
|
||||
-moz-context-properties: fill, fill-opacity;
|
||||
}
|
||||
|
||||
#tracking-protection-icon-box[state="blocked-tracking-content"] #tracking-protection-icon-animatable-image:-moz-locale-dir(rtl) {
|
||||
transform: scaleX(-1) translateX(-1232px);
|
||||
}
|
||||
|
||||
#tracking-protection-icon-box[state="blocked-tracking-content"][animate] #tracking-protection-icon-animatable-image {
|
||||
animation-name: tp-icon-animation;
|
||||
animation-timing-function: steps(77);
|
||||
animation-duration: 3s;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
|
||||
#tracking-protection-icon-box[state="blocked-tracking-content"][animate] #tracking-protection-icon-animatable-image:-moz-locale-dir(rtl) {
|
||||
animation-name: tp-icon-animation-rtl;
|
||||
}
|
||||
|
||||
#tracking-protection-icon-box[state="blocked-tracking-content"] > #tracking-protection-icon {
|
||||
list-style-image: url(chrome://browser/skin/tracking-protection.svg);
|
||||
}
|
||||
|
||||
#tracking-protection-icon-box[state="loaded-tracking-content"] > #tracking-protection-icon {
|
||||
list-style-image: url(chrome://browser/skin/tracking-protection-disabled.svg);
|
||||
}
|
||||
|
||||
#urlbar[pageproxystate="invalid"] > #identity-box > #extension-icon,
|
||||
#urlbar[pageproxystate="invalid"] > #identity-box > #tracking-protection-icon {
|
||||
#urlbar[pageproxystate="invalid"] > #identity-box > #tracking-protection-icon-box {
|
||||
visibility: collapse;
|
||||
}
|
||||
|
||||
@keyframes tp-icon-animation {
|
||||
from {
|
||||
transform: translateX(0);
|
||||
fill-opacity: 0.3;
|
||||
}
|
||||
38% {
|
||||
fill: inherit;
|
||||
fill-opacity: 0.3;
|
||||
}
|
||||
39% {
|
||||
fill: #7f00d6;
|
||||
fill-opacity: 1;
|
||||
}
|
||||
75% {
|
||||
transform: translateX(-1232px);
|
||||
fill: #7f00d6;
|
||||
fill-opacity: 1;
|
||||
}
|
||||
to {
|
||||
fill: inherit;
|
||||
fill-opacity: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes tp-icon-animation-rtl {
|
||||
from {
|
||||
transform: scaleX(-1) translateX(0);
|
||||
fill-opacity: 0.3;
|
||||
}
|
||||
38% {
|
||||
fill: inherit;
|
||||
fill-opacity: 0.3;
|
||||
}
|
||||
39% {
|
||||
fill: #7f00d6;
|
||||
fill-opacity: 1;
|
||||
}
|
||||
75% {
|
||||
transform: scaleX(-1) translateX(-1232px);
|
||||
fill: #7f00d6;
|
||||
fill-opacity: 1;
|
||||
}
|
||||
to {
|
||||
fill: inherit;
|
||||
fill-opacity: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
/* CONNECTION ICON, EXTENSION ICON */
|
||||
|
||||
#connection-icon,
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
После Ширина: | Высота: | Размер: 86 KiB |
|
@ -85,6 +85,7 @@
|
|||
|
||||
skin/classic/browser/tracking-protection.svg (../shared/identity-block/tracking-protection.svg)
|
||||
skin/classic/browser/tracking-protection-disabled.svg (../shared/identity-block/tracking-protection-disabled.svg)
|
||||
skin/classic/browser/tracking-protection-animation.svg (../shared/identity-block/tracking-protection-animation.svg)
|
||||
skin/classic/browser/panel-icon-arrow-left.svg (../shared/panel-icon-arrow-left.svg)
|
||||
skin/classic/browser/panel-icon-arrow-right.svg (../shared/panel-icon-arrow-right.svg)
|
||||
skin/classic/browser/panel-icon-cancel.svg (../shared/panel-icon-cancel.svg)
|
||||
|
|
|
@ -15,7 +15,11 @@ function test() {
|
|||
DebuggerServer.init();
|
||||
DebuggerServer.registerAllActors();
|
||||
|
||||
DebuggerServer.addActors(ACTORS_URL);
|
||||
DebuggerServer.registerModule(ACTORS_URL, {
|
||||
prefix: "testOne",
|
||||
constructor: "TestActor1",
|
||||
type: { global: true },
|
||||
});
|
||||
|
||||
let transport = DebuggerServer.connectPipe();
|
||||
gClient = new DebuggerClient(transport);
|
||||
|
@ -24,9 +28,9 @@ function test() {
|
|||
"Root actor should identify itself as a browser.");
|
||||
|
||||
gClient.listTabs().then(aResponse => {
|
||||
let globalActor = aResponse.testGlobalActor1;
|
||||
let globalActor = aResponse.testOneActor;
|
||||
ok(globalActor, "Found the test global actor.");
|
||||
ok(globalActor.includes("test_one"),
|
||||
ok(globalActor.includes("testOne"),
|
||||
"testGlobalActor1's actorPrefix should be used.");
|
||||
|
||||
gClient.request({ to: globalActor, type: "ping" }, aResponse => {
|
||||
|
@ -40,7 +44,7 @@ function test() {
|
|||
let count = 0;
|
||||
for (let connID of Object.getOwnPropertyNames(DebuggerServer._connections)) {
|
||||
let conn = DebuggerServer._connections[connID];
|
||||
let actorPrefix = conn._prefix + "test_one";
|
||||
let actorPrefix = conn._prefix + "testOne";
|
||||
for (let pool of conn._extraPools) {
|
||||
count += Object.keys(pool._actors).filter(e => {
|
||||
return e.startsWith(actorPrefix);
|
||||
|
@ -48,8 +52,8 @@ function test() {
|
|||
}
|
||||
}
|
||||
|
||||
is(count, 2,
|
||||
"Only two actor exists in all pools. One target-scoped actor and one global.");
|
||||
is(count, 1,
|
||||
"Only one actor exists in all pools. One global actor.");
|
||||
|
||||
gClient.close().then(finish);
|
||||
});
|
||||
|
|
|
@ -16,7 +16,11 @@ function test() {
|
|||
DebuggerServer.init();
|
||||
DebuggerServer.registerAllActors();
|
||||
|
||||
DebuggerServer.addActors(ACTORS_URL);
|
||||
DebuggerServer.registerModule(ACTORS_URL, {
|
||||
prefix: "testOne",
|
||||
constructor: "TestActor1",
|
||||
type: { target: true },
|
||||
});
|
||||
|
||||
let transport = DebuggerServer.connectPipe();
|
||||
gClient = new DebuggerClient(transport);
|
||||
|
@ -39,12 +43,12 @@ function test() {
|
|||
function testTargetScopedActor([aGrip, aResponse]) {
|
||||
let deferred = promise.defer();
|
||||
|
||||
ok(aGrip.testTargetScopedActor1,
|
||||
ok(aGrip.testOneActor,
|
||||
"Found the test target-scoped actor.");
|
||||
ok(aGrip.testTargetScopedActor1.includes("test_one"),
|
||||
"testTargetScopedActor1's actorPrefix should be used.");
|
||||
ok(aGrip.testOneActor.includes("testOne"),
|
||||
"testOneActor's actorPrefix should be used.");
|
||||
|
||||
gClient.request({ to: aGrip.testTargetScopedActor1, type: "ping" }, aResponse => {
|
||||
gClient.request({ to: aGrip.testOneActor, type: "ping" }, aResponse => {
|
||||
is(aResponse.pong, "pong",
|
||||
"Actor should respond to requests.");
|
||||
|
||||
|
|
|
@ -16,7 +16,11 @@ function test() {
|
|||
DebuggerServer.init();
|
||||
DebuggerServer.registerAllActors();
|
||||
|
||||
DebuggerServer.addActors(ACTORS_URL);
|
||||
DebuggerServer.registerModule(ACTORS_URL, {
|
||||
prefix: "testOne",
|
||||
constructor: "TestActor1",
|
||||
type: { target: true },
|
||||
});
|
||||
|
||||
let transport = DebuggerServer.connectPipe();
|
||||
gClient = new DebuggerClient(transport);
|
||||
|
@ -39,12 +43,12 @@ function test() {
|
|||
function testTargetScopedActor([aGrip, aResponse]) {
|
||||
let deferred = promise.defer();
|
||||
|
||||
ok(aGrip.testTargetScopedActor1,
|
||||
ok(aGrip.testOneActor,
|
||||
"Found the test target-scoped actor.");
|
||||
ok(aGrip.testTargetScopedActor1.includes("test_one"),
|
||||
"testTargetScopedActor1's actorPrefix should be used.");
|
||||
ok(aGrip.testOneActor.includes("testOne"),
|
||||
"testOneActor's actorPrefix should be used.");
|
||||
|
||||
gClient.request({ to: aGrip.testTargetScopedActor1, type: "ping" }, aResponse => {
|
||||
gClient.request({ to: aGrip.testOneActor, type: "ping" }, aResponse => {
|
||||
is(aResponse.pong, "pong",
|
||||
"Actor should respond to requests.");
|
||||
|
||||
|
@ -64,7 +68,7 @@ function closeTab(aTestActor) {
|
|||
deferred.reject(aResponse);
|
||||
});
|
||||
} catch (e) {
|
||||
is(e.message, "'ping' request packet has no destination.", "testTargetScopedActor1 went away.");
|
||||
is(e.message, "'ping' request packet has no destination.", "testOnActor went away.");
|
||||
deferred.resolve();
|
||||
}
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ function TestActor1(aConnection, aTab) {
|
|||
}
|
||||
|
||||
TestActor1.prototype = {
|
||||
actorPrefix: "test_one",
|
||||
actorPrefix: "testOne",
|
||||
|
||||
grip: function TA1_grip() {
|
||||
return { actor: this.actorID,
|
||||
|
@ -24,15 +24,4 @@ TestActor1.prototype = {
|
|||
TestActor1.prototype.requestTypes = {
|
||||
"ping": TestActor1.prototype.onPing
|
||||
};
|
||||
|
||||
DebuggerServer.removeTargetScopedActor("testTargetScopedActor1");
|
||||
DebuggerServer.removeGlobalActor("testGlobalActor1");
|
||||
|
||||
DebuggerServer.addTargetScopedActor({
|
||||
constructorName: "TestActor1",
|
||||
constructorFun: TestActor1,
|
||||
}, "testTargetScopedActor1");
|
||||
DebuggerServer.addGlobalActor({
|
||||
constructorName: "TestActor1",
|
||||
constructorFun: TestActor1,
|
||||
}, "testGlobalActor1");
|
||||
exports.TestActor1 = TestActor1;
|
||||
|
|
|
@ -47,9 +47,8 @@ class FontPropertyValue extends PureComponent {
|
|||
}
|
||||
|
||||
onUnitChange(e) {
|
||||
// TODO implement conversion.
|
||||
// Bug 1459898: https://bugzilla.mozilla.org/show_bug.cgi?id=1459898
|
||||
this.props.onChange(this.props.name, this.props.value, e.target.value);
|
||||
this.props.onChange(this.props.name, this.props.value, this.props.unit,
|
||||
e.target.value);
|
||||
}
|
||||
|
||||
onMouseDown(e) {
|
||||
|
|
|
@ -137,6 +137,143 @@ class FontInspector {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a value for font-size between two CSS unit types.
|
||||
* Conversion is done via pixels. If neither of the two given unit types is "px",
|
||||
* recursively get the value in pixels, then convert that result to the desired unit.
|
||||
*
|
||||
* @param {Number} value
|
||||
* Numeric value to convert.
|
||||
* @param {String} fromUnit
|
||||
* CSS unit to convert from.
|
||||
* @param {String} toUnit
|
||||
* CSS unit to convert to.
|
||||
* @return {Number}
|
||||
* Converted numeric value.
|
||||
*/
|
||||
async convertUnits(value, fromUnit, toUnit) {
|
||||
if (value !== parseFloat(value)) {
|
||||
throw TypeError(`Invalid value for conversion. Expected Number, got ${value}`);
|
||||
}
|
||||
|
||||
if (fromUnit === toUnit) {
|
||||
return value;
|
||||
}
|
||||
|
||||
// If neither unit is in pixels, first convert the value to pixels.
|
||||
// Reassign input value and source CSS unit.
|
||||
if (toUnit !== "px" && fromUnit !== "px") {
|
||||
value = await this.convertUnits(value, fromUnit, "px");
|
||||
fromUnit = "px";
|
||||
}
|
||||
|
||||
// Whether the conversion is done from pixels.
|
||||
const fromPx = fromUnit === "px";
|
||||
// Determine the target CSS unit for conversion.
|
||||
const unit = toUnit === "px" ? fromUnit : toUnit;
|
||||
// NodeFront instance of selected element.
|
||||
const node = this.inspector.selection.nodeFront;
|
||||
// Default output value to input value for a 1-to-1 conversion as a guard against
|
||||
// unrecognized CSS units. It will not be correct, but it will also not break.
|
||||
let out = value;
|
||||
// Computed style for reference node used for conversion of "em", "rem", "%".
|
||||
let computedStyle;
|
||||
// Raw DOM node of selected element used for conversion of "vh", "vw", "vmin", "vmax".
|
||||
let rawNode;
|
||||
|
||||
if (unit === "in") {
|
||||
out = fromPx
|
||||
? value / 96
|
||||
: value * 96;
|
||||
}
|
||||
|
||||
if (unit === "cm") {
|
||||
out = fromPx
|
||||
? value * 0.02645833333
|
||||
: value / 0.02645833333;
|
||||
}
|
||||
|
||||
if (unit === "mm") {
|
||||
out = fromPx
|
||||
? value * 0.26458333333
|
||||
: value / 0.26458333333;
|
||||
}
|
||||
|
||||
if (unit === "pt") {
|
||||
out = fromPx
|
||||
? value * 0.75
|
||||
: value / 0.75;
|
||||
}
|
||||
|
||||
if (unit === "pc") {
|
||||
out = fromPx
|
||||
? value * 0.0625
|
||||
: value / 0.0625;
|
||||
}
|
||||
|
||||
if (unit === "%") {
|
||||
computedStyle = await this.pageStyle.getComputed(node.parentNode());
|
||||
out = fromPx
|
||||
? value * 100 / parseFloat(computedStyle["font-size"].value)
|
||||
: value / 100 * parseFloat(computedStyle["font-size"].value);
|
||||
}
|
||||
|
||||
if (unit === "em") {
|
||||
computedStyle = await this.pageStyle.getComputed(node.parentNode());
|
||||
out = fromPx
|
||||
? value / parseFloat(computedStyle["font-size"].value)
|
||||
: value * parseFloat(computedStyle["font-size"].value);
|
||||
}
|
||||
|
||||
if (unit === "rem") {
|
||||
const document = await this.inspector.walker.documentElement();
|
||||
computedStyle = await this.pageStyle.getComputed(document);
|
||||
out = fromPx
|
||||
? value / parseFloat(computedStyle["font-size"].value)
|
||||
: value * parseFloat(computedStyle["font-size"].value);
|
||||
}
|
||||
|
||||
if (unit === "vh") {
|
||||
rawNode = await node.rawNode();
|
||||
out = fromPx
|
||||
? value * 100 / rawNode.ownerGlobal.innerHeight
|
||||
: value / 100 * rawNode.ownerGlobal.innerHeight;
|
||||
}
|
||||
|
||||
if (unit === "vw") {
|
||||
rawNode = await node.rawNode();
|
||||
out = fromPx
|
||||
? value * 100 / rawNode.ownerGlobal.innerWidth
|
||||
: value / 100 * rawNode.ownerGlobal.innerWidth;
|
||||
}
|
||||
|
||||
if (unit === "vmin") {
|
||||
rawNode = await node.rawNode();
|
||||
out = fromPx
|
||||
? value * 100 / Math.min(
|
||||
rawNode.ownerGlobal.innerWidth, rawNode.ownerGlobal.innerHeight)
|
||||
: value / 100 * Math.min(
|
||||
rawNode.ownerGlobal.innerWidth, rawNode.ownerGlobal.innerHeight);
|
||||
}
|
||||
|
||||
if (unit === "vmax") {
|
||||
rawNode = await node.rawNode();
|
||||
out = fromPx
|
||||
? value * 100 / Math.max(
|
||||
rawNode.ownerGlobal.innerWidth, rawNode.ownerGlobal.innerHeight)
|
||||
: value / 100 * Math.max(
|
||||
rawNode.ownerGlobal.innerWidth, rawNode.ownerGlobal.innerHeight);
|
||||
}
|
||||
|
||||
// Return rounded pixel values. Limit other values to 3 decimals.
|
||||
if (fromPx) {
|
||||
// Round values like 1.000 to 1
|
||||
return out === Math.round(out) ? Math.round(out) : out.toFixed(3);
|
||||
}
|
||||
|
||||
return Math.round(out);
|
||||
}
|
||||
|
||||
/**
|
||||
* Destruction function called when the inspector is destroyed. Removes event listeners
|
||||
* and cleans up references.
|
||||
|
@ -290,14 +427,19 @@ class FontInspector {
|
|||
/**
|
||||
* Get a reference to a TextProperty instance from the current selected rule for a
|
||||
* given property name. If one doesn't exist, create one with the given value.
|
||||
* If the selected rule no longer exists (ex: during test teardown), return null.
|
||||
*
|
||||
* @param {String} name
|
||||
* CSS property name
|
||||
* @param {String} value
|
||||
* CSS property value
|
||||
* @return {TextProperty}
|
||||
* @return {TextProperty|null}
|
||||
*/
|
||||
getTextProperty(name, value) {
|
||||
if (!this.selectedRule) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let textProperty =
|
||||
this.selectedRule.textProps.find(prop => prop.name === name);
|
||||
if (!textProperty) {
|
||||
|
@ -480,7 +622,14 @@ class FontInspector {
|
|||
syncChanges(name, value) {
|
||||
const textProperty = this.getTextProperty(name, value);
|
||||
if (textProperty) {
|
||||
textProperty.setValue(value);
|
||||
// This method may be called after the connection to the page style actor is closed.
|
||||
// For example, during teardown of automated tests. Here, we catch any failure that
|
||||
// may occur because of that. We're not interested in handling the error.
|
||||
try {
|
||||
textProperty.setValue(value);
|
||||
} catch (e) {
|
||||
// Silent error.
|
||||
}
|
||||
}
|
||||
|
||||
this.ruleView.on("property-value-updated", this.onRulePropertyUpdated);
|
||||
|
@ -569,11 +718,24 @@ class FontInspector {
|
|||
* CSS font property name or axis name
|
||||
* @param {String} value
|
||||
* CSS font property numeric value or axis value
|
||||
* @param {String|null} unit
|
||||
* CSS unit or null
|
||||
* @param {String|undefined} fromUnit
|
||||
* Optional CSS unit to convert from
|
||||
* @param {String|undefined} toUnit
|
||||
* Optional CSS unit to convert to
|
||||
*/
|
||||
onPropertyChange(property, value, unit) {
|
||||
async onPropertyChange(property, value, fromUnit, toUnit) {
|
||||
if (FONT_PROPERTIES.includes(property)) {
|
||||
let unit = fromUnit;
|
||||
|
||||
if (toUnit && fromUnit) {
|
||||
try {
|
||||
value = await this.convertUnits(value, fromUnit, toUnit);
|
||||
unit = toUnit;
|
||||
} catch (err) {
|
||||
// Silent error
|
||||
}
|
||||
}
|
||||
|
||||
this.onFontPropertyUpdate(property, value, unit);
|
||||
} else {
|
||||
this.onAxisUpdate(property, value);
|
||||
|
@ -670,13 +832,21 @@ class FontInspector {
|
|||
}
|
||||
|
||||
if (!this.store || !this.isSelectedNodeValid()) {
|
||||
this.store.dispatch(resetFontEditor());
|
||||
|
||||
if (this.inspector.selection.isPseudoElementNode()) {
|
||||
const noPseudoWarning = getStr("fontinspector.noPseduoWarning");
|
||||
this.store.dispatch(resetFontEditor());
|
||||
this.store.dispatch(updateWarningMessage(noPseudoWarning));
|
||||
return;
|
||||
}
|
||||
|
||||
// If the selection is a TextNode, switch selection to be its parent node.
|
||||
if (this.inspector.selection.isTextNode()) {
|
||||
const selection = this.inspector.selection;
|
||||
selection.setNodeFront(selection.nodeFront.parentNode());
|
||||
return;
|
||||
}
|
||||
|
||||
this.store.dispatch(resetFontEditor());
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -851,7 +1021,13 @@ class FontInspector {
|
|||
// Prevent reacting to changes we caused.
|
||||
this.ruleView.off("property-value-updated", this.onRulePropertyUpdated);
|
||||
// Live preview font property changes on the page.
|
||||
textProperty.rule.previewPropertyValue(textProperty, value, "");
|
||||
|
||||
try {
|
||||
textProperty.rule.previewPropertyValue(textProperty, value, "");
|
||||
} catch (e) {
|
||||
// Silent error
|
||||
}
|
||||
|
||||
// Sync Rule view with changes reflected on the page (debounced).
|
||||
this.syncChanges(name, value);
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ support-files =
|
|||
skip-if = !e10s # too slow on !e10s, logging fully serialized actors (Bug 1446595)
|
||||
subsuite = clipboard
|
||||
[browser_fontinspector_edit-previews.js]
|
||||
[browser_fontinspector_editor-font-size-conversion.js]
|
||||
[browser_fontinspector_editor-values.js]
|
||||
[browser_fontinspector_editor-keywords.js]
|
||||
[browser_fontinspector_expand-css-code.js]
|
||||
|
@ -28,4 +29,5 @@ subsuite = clipboard
|
|||
[browser_fontinspector_other-fonts.js]
|
||||
[browser_fontinspector_rendered-fonts.js]
|
||||
[browser_fontinspector_reveal-in-page.js]
|
||||
[browser_fontinspector_text-node.js]
|
||||
[browser_fontinspector_theme-change.js]
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
/* global getPropertyValue waitFor */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Test that changes to font-size units converts the value to the destination unit.
|
||||
// Check that converted values are applied to the inline style of the selected element.
|
||||
|
||||
const TEST_URI = URL_ROOT + "browser_fontinspector.html";
|
||||
|
||||
add_task(async function() {
|
||||
await pushPref("devtools.inspector.fonteditor.enabled", true);
|
||||
const { inspector, view, tab, testActor } = await openFontInspectorForURL(TEST_URI);
|
||||
const viewDoc = view.document;
|
||||
const property = "font-size";
|
||||
const selector = "div";
|
||||
const UNITS = {
|
||||
"px": "36px",
|
||||
"%": "100%",
|
||||
"em": "1em",
|
||||
};
|
||||
|
||||
await selectNode(selector, inspector);
|
||||
|
||||
info("Check that font editor shows font-size value in original units");
|
||||
let fontSize = getPropertyValue(viewDoc, property);
|
||||
is(fontSize.unit, "em", "Original unit for font size is em");
|
||||
is(fontSize.value + fontSize.unit, "1em", "Original font size is 1em");
|
||||
|
||||
let prevValue = fontSize.value;
|
||||
let prevUnit = fontSize.unit;
|
||||
|
||||
for (const unit in UNITS) {
|
||||
const value = UNITS[unit];
|
||||
const onEditorUpdated = inspector.once("fonteditor-updated");
|
||||
|
||||
info(`Convert font-size from ${prevValue}${prevUnit} to ${unit}`);
|
||||
await view.onPropertyChange(property, prevValue, prevUnit, unit);
|
||||
info("Waiting for font editor to re-render");
|
||||
await onEditorUpdated;
|
||||
info(`Waiting for font-size unit dropdown to re-render with selected value: ${unit}`);
|
||||
await waitFor(() => {
|
||||
const sel = `#font-editor .font-value-slider[name=${property}] ~ .font-unit-select`;
|
||||
return viewDoc.querySelector(sel).value === unit;
|
||||
});
|
||||
info("Waiting for testactor reflow");
|
||||
await testActor.reflow();
|
||||
|
||||
info(`Check that font editor font-size value is converted to ${unit}`);
|
||||
fontSize = getPropertyValue(viewDoc, property);
|
||||
is(fontSize.unit, unit, `Font size unit is converted to ${unit}`);
|
||||
is(fontSize.value + fontSize.unit, value, `Font size in font editor is ${value}`);
|
||||
|
||||
info(`Check that inline style font-size value is converted to ${unit}`);
|
||||
const inlineStyleValue = await getInlineStyleValue(tab, selector, property);
|
||||
is(inlineStyleValue, value, `Font size on inline style is ${value}`);
|
||||
|
||||
// Store current unit and value to use in conversion on the next iteration.
|
||||
prevUnit = fontSize.unit;
|
||||
prevValue = fontSize.value;
|
||||
}
|
||||
});
|
||||
|
||||
async function getInlineStyleValue(tab, selector, property) {
|
||||
return ContentTask.spawn(tab.linkedBrowser, { selector, property }, function(args) {
|
||||
const el = content.document.querySelector(args.selector);
|
||||
return el && el.style[args.property];
|
||||
});
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
/* global getPropertyValue */
|
||||
|
||||
"use strict";
|
||||
|
||||
|
@ -17,16 +18,6 @@ add_task(async function() {
|
|||
await testKeywordValues(inspector, viewDoc);
|
||||
});
|
||||
|
||||
function getPropertyValue(viewDoc, name) {
|
||||
const selector = `#font-editor .font-value-slider[name=${name}]`;
|
||||
return {
|
||||
value: viewDoc.querySelector(selector).value,
|
||||
// Ensure unit dropdown exists before querying its value
|
||||
unit: viewDoc.querySelector(selector + ` ~ .font-unit-select`) &&
|
||||
viewDoc.querySelector(selector + ` ~ .font-unit-select`).value
|
||||
};
|
||||
}
|
||||
|
||||
async function testKeywordValues(inspector, viewDoc) {
|
||||
await selectNode(".bold-text", inspector);
|
||||
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Test that selecting a text node invokes the font editor on its parent node.
|
||||
|
||||
const TEST_URI = URL_ROOT + "browser_fontinspector.html";
|
||||
|
||||
add_task(async function() {
|
||||
await pushPref("devtools.inspector.fonteditor.enabled", true);
|
||||
const { inspector, view } = await openFontInspectorForURL(TEST_URI);
|
||||
const viewDoc = view.document;
|
||||
|
||||
info("Select the first text node of <body>");
|
||||
const bodyNode = await getNodeFront("body", inspector);
|
||||
const { nodes } = await inspector.walker.children(bodyNode);
|
||||
const onInspectorUpdated = inspector.once("fontinspector-updated");
|
||||
await selectNode(nodes[0], inspector);
|
||||
|
||||
info("Waiting for font editor to render");
|
||||
await onInspectorUpdated;
|
||||
|
||||
const textFonts = getUsedFontsEls(viewDoc);
|
||||
|
||||
info("Select the <body> element");
|
||||
await selectNode("body", inspector);
|
||||
|
||||
const parentFonts = getUsedFontsEls(viewDoc);
|
||||
is(textFonts.length, parentFonts.length, "Font inspector shows same number of fonts");
|
||||
});
|
|
@ -37,7 +37,7 @@ selectNode = async function(node, inspector, reason) {
|
|||
*/
|
||||
var openFontInspectorForURL = async function(url) {
|
||||
const tab = await addTab(url);
|
||||
const {toolbox, inspector} = await openInspector();
|
||||
const {toolbox, inspector, testActor } = await openInspector();
|
||||
|
||||
// Call selectNode again here to force a fontinspector update since we don't
|
||||
// know if the fontinspector-updated event has been sent while the inspector
|
||||
|
@ -46,6 +46,7 @@ var openFontInspectorForURL = async function(url) {
|
|||
|
||||
return {
|
||||
tab,
|
||||
testActor,
|
||||
toolbox,
|
||||
inspector,
|
||||
view: inspector.fontinspector
|
||||
|
@ -204,3 +205,45 @@ function getURL(fontEl) {
|
|||
function getFamilyName(fontEl) {
|
||||
return fontEl.querySelector(".font-family-name").textContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value and unit of a CSS font property or font axis from the font editor.
|
||||
*
|
||||
* @param {document} viewDoc
|
||||
* Host document of the font inspector panel.
|
||||
* @param {String} name
|
||||
* Font property name or axis tag
|
||||
* @return {Object}
|
||||
* Object with the value and unit of the given font property or axis tag
|
||||
* from the corresponding input fron the font editor.
|
||||
* @Example:
|
||||
* {
|
||||
* value: {Number|String|null}
|
||||
* unit: {String|null}
|
||||
* }
|
||||
*/
|
||||
function getPropertyValue(viewDoc, name) {
|
||||
const selector = `#font-editor .font-value-slider[name=${name}]`;
|
||||
return {
|
||||
// Ensure value input exists before querying its value
|
||||
value: viewDoc.querySelector(selector) &&
|
||||
parseFloat(viewDoc.querySelector(selector).value),
|
||||
// Ensure unit dropdown exists before querying its value
|
||||
unit: viewDoc.querySelector(selector + ` ~ .font-unit-select`) &&
|
||||
viewDoc.querySelector(selector + ` ~ .font-unit-select`).value
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for a predicate to return a result.
|
||||
*
|
||||
* @param {Function} condition
|
||||
* Invoked every 10ms for a maximum of 500 retries until it returns a truthy
|
||||
* value.
|
||||
* @return {Promise}
|
||||
* A promise that is resolved with the result of the condition.
|
||||
*/
|
||||
async function waitFor(condition) {
|
||||
await BrowserTestUtils.waitForCondition(condition, "waitFor", 10, 500);
|
||||
return condition();
|
||||
}
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
const { require } = ChromeUtils.import("resource://devtools/shared/Loader.jsm", {});
|
||||
let { Assert } = require("resource://testing-common/Assert.jsm");
|
||||
const { BrowserLoader } = ChromeUtils.import("resource://devtools/client/shared/browser-loader.js", {});
|
||||
const defer = require("devtools/shared/defer");
|
||||
const flags = require("devtools/shared/flags");
|
||||
let { TargetFactory } = require("devtools/client/framework/target");
|
||||
let { Toolbox } = require("devtools/client/framework/toolbox");
|
||||
|
@ -37,15 +36,15 @@ function onNextAnimationFrame(fn) {
|
|||
}
|
||||
|
||||
function setState(component, newState) {
|
||||
const deferred = defer();
|
||||
component.setState(newState, onNextAnimationFrame(deferred.resolve));
|
||||
return deferred.promise;
|
||||
return new Promise(resolve => {
|
||||
component.setState(newState, onNextAnimationFrame(resolve));
|
||||
});
|
||||
}
|
||||
|
||||
function setProps(component, newState) {
|
||||
const deferred = defer();
|
||||
component.setProps(newState, onNextAnimationFrame(deferred.resolve));
|
||||
return deferred.promise;
|
||||
return new Promise(resolve => {
|
||||
component.setProps(newState, onNextAnimationFrame(resolve));
|
||||
});
|
||||
}
|
||||
|
||||
function dumpn(msg) {
|
||||
|
|
|
@ -12,7 +12,6 @@ const LineGraphWidget = require("devtools/client/shared/widgets/LineGraphWidget"
|
|||
const MountainGraphWidget = require("devtools/client/shared/widgets/MountainGraphWidget");
|
||||
const { CanvasGraphUtils } = require("devtools/client/shared/widgets/Graphs");
|
||||
|
||||
const defer = require("devtools/shared/defer");
|
||||
const EventEmitter = require("devtools/shared/event-emitter");
|
||||
|
||||
const { colorUtils } = require("devtools/shared/css/color");
|
||||
|
@ -210,7 +209,7 @@ GraphsController.prototype = {
|
|||
// until the previous render cycle completes, which can occur
|
||||
// especially when a recording is finished, and triggers a
|
||||
// fresh rendering at a higher rate
|
||||
await (this._rendering && this._rendering.promise);
|
||||
await this._rendering;
|
||||
|
||||
// Check after yielding to ensure we're not tearing down,
|
||||
// as this can create a race condition in tests
|
||||
|
@ -218,12 +217,14 @@ GraphsController.prototype = {
|
|||
return;
|
||||
}
|
||||
|
||||
this._rendering = defer();
|
||||
for (const graph of (await this._getEnabled())) {
|
||||
await graph.setPerformanceData(recordingData, resolution);
|
||||
this.emit("rendered", graph.graphName);
|
||||
}
|
||||
this._rendering.resolve();
|
||||
this._rendering = new Promise(async (resolve) => {
|
||||
for (const graph of (await this._getEnabled())) {
|
||||
await graph.setPerformanceData(recordingData, resolution);
|
||||
this.emit("rendered", graph.graphName);
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
await this._rendering;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -241,7 +242,7 @@ GraphsController.prototype = {
|
|||
// If there was rendering, wait until the most recent render cycle
|
||||
// has finished
|
||||
if (this._rendering) {
|
||||
await this._rendering.promise;
|
||||
await this._rendering;
|
||||
}
|
||||
|
||||
for (const graph of this.getWidgets()) {
|
||||
|
|
|
@ -5,8 +5,6 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const defer = require("devtools/shared/defer");
|
||||
|
||||
loader.lazyRequireGetter(this, "EventEmitter", "devtools/shared/event-emitter");
|
||||
|
||||
function PerformancePanel(iframeWindow, toolbox) {
|
||||
|
@ -30,8 +28,6 @@ PerformancePanel.prototype = {
|
|||
if (this._opening) {
|
||||
return this._opening;
|
||||
}
|
||||
const deferred = defer();
|
||||
this._opening = deferred.promise;
|
||||
|
||||
this.panelWin.gToolbox = this.toolbox;
|
||||
this.panelWin.gTarget = this.target;
|
||||
|
@ -64,7 +60,9 @@ PerformancePanel.prototype = {
|
|||
this.isReady = true;
|
||||
this.emit("ready");
|
||||
|
||||
deferred.resolve(this);
|
||||
this._opening = new Promise(resolve => {
|
||||
resolve(this);
|
||||
});
|
||||
return this._opening;
|
||||
},
|
||||
|
||||
|
|
|
@ -43,7 +43,6 @@ var RecordingListItem = React.createFactory(require("devtools/client/performance
|
|||
|
||||
var Services = require("Services");
|
||||
var promise = require("promise");
|
||||
const defer = require("devtools/shared/defer");
|
||||
var EventEmitter = require("devtools/shared/event-emitter");
|
||||
var DevToolsUtils = require("devtools/shared/DevToolsUtils");
|
||||
var flags = require("devtools/shared/flags");
|
||||
|
@ -542,14 +541,14 @@ var PerformanceController = {
|
|||
* @return {Promise}
|
||||
*/
|
||||
async waitForStateChangeOnRecording(recording, expectedState) {
|
||||
const deferred = defer();
|
||||
this.on(EVENTS.RECORDING_STATE_CHANGE, function handler(state, model) {
|
||||
if (state === expectedState && model === recording) {
|
||||
this.off(EVENTS.RECORDING_STATE_CHANGE, handler);
|
||||
deferred.resolve();
|
||||
}
|
||||
await new Promise(resolve => {
|
||||
this.on(EVENTS.RECORDING_STATE_CHANGE, function handler(state, model) {
|
||||
if (state === expectedState && model === recording) {
|
||||
this.off(EVENTS.RECORDING_STATE_CHANGE, handler);
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
await deferred.promise;
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -38,19 +38,17 @@ function getUnicodeConverter() {
|
|||
}
|
||||
|
||||
function asyncCopy(data, file) {
|
||||
let deferred = defer();
|
||||
|
||||
let string = JSON.stringify(data);
|
||||
let inputStream = getUnicodeConverter().convertToInputStream(string);
|
||||
let outputStream = FileUtils.openSafeFileOutputStream(file);
|
||||
|
||||
NetUtil.asyncCopy(inputStream, outputStream, status => {
|
||||
if (!Components.isSuccessCode(status)) {
|
||||
deferred.reject(new Error("Could not save data to file."));
|
||||
}
|
||||
deferred.resolve();
|
||||
return new Promise((resolve, reject) => {
|
||||
NetUtil.asyncCopy(inputStream, outputStream, status => {
|
||||
if (!Components.isSuccessCode(status)) {
|
||||
reject(new Error("Could not save data to file."));
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
/* eslint-enable */
|
||||
|
|
|
@ -160,19 +160,17 @@ function getUnicodeConverter() {
|
|||
}
|
||||
|
||||
function asyncCopy(data, file) {
|
||||
let deferred = defer();
|
||||
|
||||
let string = JSON.stringify(data);
|
||||
let inputStream = getUnicodeConverter().convertToInputStream(string);
|
||||
let outputStream = FileUtils.openSafeFileOutputStream(file);
|
||||
|
||||
NetUtil.asyncCopy(inputStream, outputStream, status => {
|
||||
if (!Components.isSuccessCode(status)) {
|
||||
deferred.reject(new Error("Could not save data to file."));
|
||||
}
|
||||
deferred.resolve();
|
||||
return new Promise((resolve, reject) => {
|
||||
NetUtil.asyncCopy(inputStream, outputStream, status => {
|
||||
if (!Components.isSuccessCode(status)) {
|
||||
reject(new Error("Could not save data to file."));
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
/* eslint-enable */
|
||||
|
|
|
@ -124,19 +124,17 @@ function getUnicodeConverter() {
|
|||
}
|
||||
|
||||
function asyncCopy(data, file) {
|
||||
let deferred = defer();
|
||||
|
||||
let string = JSON.stringify(data);
|
||||
let inputStream = getUnicodeConverter().convertToInputStream(string);
|
||||
let outputStream = FileUtils.openSafeFileOutputStream(file);
|
||||
|
||||
NetUtil.asyncCopy(inputStream, outputStream, status => {
|
||||
if (!Components.isSuccessCode(status)) {
|
||||
deferred.reject(new Error("Could not save data to file."));
|
||||
}
|
||||
deferred.resolve();
|
||||
return new Promise((resolve, reject) => {
|
||||
NetUtil.asyncCopy(inputStream, outputStream, status => {
|
||||
if (!Components.isSuccessCode(status)) {
|
||||
reject(new Error("Could not save data to file."));
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
/* eslint-enable */
|
||||
|
|
|
@ -73,25 +73,23 @@ function testWorkerMarkerUI(node) {
|
|||
*/
|
||||
function evalInDebuggee(script) {
|
||||
let { generateUUID } = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator);
|
||||
let deferred = defer();
|
||||
|
||||
if (!mm) {
|
||||
throw new Error("`loadFrameScripts()` must be called when using MessageManager.");
|
||||
}
|
||||
return new Promise(resolve => {
|
||||
let id = generateUUID().toString();
|
||||
mm.sendAsyncMessage("devtools:test:eval", {script: script, id: id});
|
||||
mm.addMessageListener("devtools:test:eval:response", handler);
|
||||
|
||||
let id = generateUUID().toString();
|
||||
mm.sendAsyncMessage("devtools:test:eval", { script: script, id: id });
|
||||
mm.addMessageListener("devtools:test:eval:response", handler);
|
||||
function handler({data}) {
|
||||
if (id !== data.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
function handler({ data }) {
|
||||
if (id !== data.id) {
|
||||
return;
|
||||
mm.removeMessageListener("devtools:test:eval:response", handler);
|
||||
resolve(data.value);
|
||||
}
|
||||
|
||||
mm.removeMessageListener("devtools:test:eval:response", handler);
|
||||
deferred.resolve(data.value);
|
||||
}
|
||||
|
||||
return deferred.promise;
|
||||
});
|
||||
}
|
||||
/* eslint-enable */
|
||||
|
|
|
@ -17,7 +17,7 @@ const Services = require("Services");
|
|||
const { ChromeDebuggerActor } = require("devtools/server/actors/thread");
|
||||
const { WebConsoleActor } = require("devtools/server/actors/webconsole");
|
||||
const makeDebugger = require("devtools/server/actors/utils/make-debugger");
|
||||
const { ActorPool } = require("devtools/server/main");
|
||||
const { ActorPool } = require("devtools/server/actors/common");
|
||||
const { assert } = require("devtools/shared/DevToolsUtils");
|
||||
const { TabSources } = require("devtools/server/actors/utils/TabSources");
|
||||
|
||||
|
|
|
@ -10,7 +10,8 @@
|
|||
|
||||
const Services = require("Services");
|
||||
const { Cc, Ci, Cu } = require("chrome");
|
||||
const { DebuggerServer, ActorPool } = require("devtools/server/main");
|
||||
const { DebuggerServer } = require("devtools/server/main");
|
||||
const { ActorPool } = require("devtools/server/actors/common");
|
||||
const { ThreadActor } = require("devtools/server/actors/thread");
|
||||
const { ObjectActor } = require("devtools/server/actors/object");
|
||||
const { LongStringActor } = require("devtools/server/actors/object/long-string");
|
||||
|
|
|
@ -10,53 +10,28 @@
|
|||
*/
|
||||
var { Ci, Cc } = require("chrome");
|
||||
var Services = require("Services");
|
||||
var { ActorPool, OriginalLocation, RegisteredActorFactory,
|
||||
var { ActorPool, RegisteredActorFactory,
|
||||
ObservedActorFactory } = require("devtools/server/actors/common");
|
||||
var { LocalDebuggerTransport, ChildDebuggerTransport, WorkerDebuggerTransport } =
|
||||
require("devtools/shared/transport/transport");
|
||||
var DevToolsUtils = require("devtools/shared/DevToolsUtils");
|
||||
var { dumpn } = DevToolsUtils;
|
||||
|
||||
DevToolsUtils.defineLazyGetter(this, "DebuggerSocket", () => {
|
||||
// eslint-disable-next-line no-shadow
|
||||
const { DebuggerSocket } = require("devtools/shared/security/socket");
|
||||
return DebuggerSocket;
|
||||
});
|
||||
DevToolsUtils.defineLazyGetter(this, "Authentication", () => {
|
||||
return require("devtools/shared/security/auth");
|
||||
});
|
||||
DevToolsUtils.defineLazyGetter(this, "generateUUID", () => {
|
||||
loader.lazyRequireGetter(this, "DebuggerSocket", "devtools/shared/security/socket", true);
|
||||
loader.lazyRequireGetter(this, "Authentication", "devtools/shared/security/auth");
|
||||
loader.lazyRequireGetter(this, "LocalDebuggerTransport", "devtools/shared/transport/local-transport", true);
|
||||
loader.lazyRequireGetter(this, "ChildDebuggerTransport", "devtools/shared/transport/child-transport", true);
|
||||
loader.lazyRequireGetter(this, "WorkerThreadWorkerDebuggerTransport", "devtools/shared/transport/worker-transport", true);
|
||||
loader.lazyRequireGetter(this, "MainThreadWorkerDebuggerTransport", "devtools/shared/transport/worker-transport", true);
|
||||
|
||||
loader.lazyGetter(this, "generateUUID", () => {
|
||||
// eslint-disable-next-line no-shadow
|
||||
const { generateUUID } = Cc["@mozilla.org/uuid-generator;1"]
|
||||
.getService(Ci.nsIUUIDGenerator);
|
||||
return generateUUID;
|
||||
});
|
||||
|
||||
// Overload `Components` to prevent DevTools loader exception on Components
|
||||
// object usage
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
Object.defineProperty(this, "Components", {
|
||||
get() {
|
||||
return require("chrome").components;
|
||||
}
|
||||
});
|
||||
|
||||
const CONTENT_PROCESS_SERVER_STARTUP_SCRIPT =
|
||||
"resource://devtools/server/startup/content-process.js";
|
||||
|
||||
function loadSubScript(url) {
|
||||
try {
|
||||
Services.scriptloader.loadSubScript(url, this);
|
||||
} catch (e) {
|
||||
const errorStr = "Error loading: " + url + ":\n" +
|
||||
(e.fileName ? "at " + e.fileName + " : " + e.lineNumber + "\n" : "") +
|
||||
e + " - " + e.stack + "\n";
|
||||
dump(errorStr);
|
||||
reportError(errorStr);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
loader.lazyRequireGetter(this, "EventEmitter", "devtools/shared/event-emitter");
|
||||
|
||||
var gRegisteredModules = Object.create(null);
|
||||
|
@ -159,7 +134,7 @@ var DebuggerServer = {
|
|||
}
|
||||
|
||||
if (!this.rootlessServer && !this.createRootActor) {
|
||||
throw new Error("Use DebuggerServer.addActors() to add a root actor " +
|
||||
throw new Error("Use DebuggerServer.setRootActor() to add a root actor " +
|
||||
"implementation.");
|
||||
}
|
||||
},
|
||||
|
@ -200,18 +175,6 @@ var DebuggerServer = {
|
|||
this.registerActors({ root: true, browser: true, target: true });
|
||||
},
|
||||
|
||||
/**
|
||||
* Load a subscript into the debugging global.
|
||||
*
|
||||
* @param url string A url that will be loaded as a subscript into the
|
||||
* debugging global. The user must load at least one script
|
||||
* that implements a createRootActor() function to create the
|
||||
* server's root actor.
|
||||
*/
|
||||
addActors(url) {
|
||||
loadSubScript.call(this, url);
|
||||
},
|
||||
|
||||
/**
|
||||
* Register a CommonJS module with the debugger server.
|
||||
* @param id string
|
||||
|
@ -603,7 +566,7 @@ var DebuggerServer = {
|
|||
this._checkInit();
|
||||
|
||||
const transport = isWorker ?
|
||||
new WorkerDebuggerTransport(scopeOrManager, prefix) :
|
||||
new WorkerThreadWorkerDebuggerTransport(scopeOrManager, prefix) :
|
||||
new ChildDebuggerTransport(scopeOrManager, prefix);
|
||||
|
||||
return this._onConnection(transport, prefix, true);
|
||||
|
@ -772,7 +735,7 @@ var DebuggerServer = {
|
|||
dbg.removeListener(listener);
|
||||
|
||||
// Step 7: Create a transport for the connection to the worker.
|
||||
const transport = new WorkerDebuggerTransport(dbg, id);
|
||||
const transport = new MainThreadWorkerDebuggerTransport(dbg, id);
|
||||
transport.ready();
|
||||
transport.hooks = {
|
||||
onClosed: () => {
|
||||
|
@ -1336,25 +1299,7 @@ DevToolsUtils.defineLazyGetter(DebuggerServer, "AuthenticationResult", () => {
|
|||
|
||||
EventEmitter.decorate(DebuggerServer);
|
||||
|
||||
if (this.exports) {
|
||||
exports.DebuggerServer = DebuggerServer;
|
||||
exports.ActorPool = ActorPool;
|
||||
exports.OriginalLocation = OriginalLocation;
|
||||
}
|
||||
|
||||
// Needed on B2G (See header note)
|
||||
this.DebuggerServer = DebuggerServer;
|
||||
this.ActorPool = ActorPool;
|
||||
this.OriginalLocation = OriginalLocation;
|
||||
|
||||
// When using DebuggerServer.addActors, some symbols are expected to be in
|
||||
// the scope of the added actor even before the corresponding modules are
|
||||
// loaded, so let's explicitly bind the expected symbols here.
|
||||
var includes = ["Components", "Ci", "Cu", "require", "Services", "DebuggerServer",
|
||||
"ActorPool", "DevToolsUtils"];
|
||||
includes.forEach(name => {
|
||||
DebuggerServer[name] = this[name];
|
||||
});
|
||||
exports.DebuggerServer = DebuggerServer;
|
||||
|
||||
/**
|
||||
* Creates a DebuggerServerConnection.
|
||||
|
|
|
@ -78,7 +78,7 @@ function init(msg) {
|
|||
|
||||
const { ContentProcessTargetActor } =
|
||||
loader.require("devtools/server/actors/targets/content-process");
|
||||
const { ActorPool } = loader.require("devtools/server/main");
|
||||
const { ActorPool } = loader.require("devtools/server/actors/common");
|
||||
const actor = new ContentProcessTargetActor(conn);
|
||||
const actorPool = new ActorPool(conn);
|
||||
actorPool.addActor(actor);
|
||||
|
|
|
@ -21,7 +21,8 @@ try {
|
|||
|
||||
const DevToolsUtils = require("devtools/shared/DevToolsUtils");
|
||||
const { dumpn } = DevToolsUtils;
|
||||
const { DebuggerServer, ActorPool } = require("devtools/server/main");
|
||||
const { DebuggerServer } = require("devtools/server/main");
|
||||
const { ActorPool } = require("devtools/server/actors/common");
|
||||
|
||||
DebuggerServer.init();
|
||||
// We want a special server without any root actor and only target-scoped actors.
|
||||
|
|
|
@ -16,7 +16,11 @@ async function test() {
|
|||
DebuggerServer.init();
|
||||
DebuggerServer.registerAllActors();
|
||||
|
||||
DebuggerServer.addActors(ACTORS_URL);
|
||||
DebuggerServer.registerModule(ACTORS_URL, {
|
||||
prefix: "error",
|
||||
constructor: "ErrorActor",
|
||||
type: { global: true },
|
||||
});
|
||||
|
||||
const transport = DebuggerServer.connectPipe();
|
||||
const gClient = new DebuggerClient(transport);
|
||||
|
|
|
@ -27,9 +27,4 @@ ErrorActor.prototype = {
|
|||
ErrorActor.prototype.requestTypes = {
|
||||
"error": ErrorActor.prototype.onError
|
||||
};
|
||||
|
||||
DebuggerServer.removeGlobalActor("errorActor");
|
||||
DebuggerServer.addGlobalActor({
|
||||
constructorName: "ErrorActor",
|
||||
constructorFun: ErrorActor,
|
||||
}, "errorActor");
|
||||
exports.ErrorActor = ErrorActor;
|
||||
|
|
|
@ -18,8 +18,4 @@ PostInitGlobalActor.prototype = {
|
|||
PostInitGlobalActor.prototype.requestTypes = {
|
||||
"ping": PostInitGlobalActor.prototype.onPing,
|
||||
};
|
||||
|
||||
DebuggerServer.addGlobalActor({
|
||||
constructorName: "PostInitGlobalActor",
|
||||
constructorFun: PostInitGlobalActor,
|
||||
}, "postInitGlobalActor");
|
||||
exports.PostInitGlobalActor = PostInitGlobalActor;
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
function PostInitTargetScopedActor(connection) {}
|
||||
|
||||
PostInitTargetScopedActor.prototype = {
|
||||
actorPostfix: "postInitTab",
|
||||
actorPostfix: "postInitTargetScoped",
|
||||
onPing(request) {
|
||||
return { message: "pong" };
|
||||
},
|
||||
|
@ -18,8 +18,4 @@ PostInitTargetScopedActor.prototype = {
|
|||
PostInitTargetScopedActor.prototype.requestTypes = {
|
||||
"ping": PostInitTargetScopedActor.prototype.onPing,
|
||||
};
|
||||
|
||||
DebuggerServer.addTargetScopedActor({
|
||||
constructorName: "PostInitTargetScopedActor",
|
||||
constructorFun: PostInitTargetScopedActor,
|
||||
}, "postInitTargetScopedActor");
|
||||
exports.PostInitTargetScopedActor = PostInitTargetScopedActor;
|
||||
|
|
|
@ -18,8 +18,4 @@ PreInitGlobalActor.prototype = {
|
|||
PreInitGlobalActor.prototype.requestTypes = {
|
||||
"ping": PreInitGlobalActor.prototype.onPing,
|
||||
};
|
||||
|
||||
DebuggerServer.addGlobalActor({
|
||||
constructorName: "PreInitGlobalActor",
|
||||
constructorFun: PreInitGlobalActor,
|
||||
}, "preInitGlobalActor");
|
||||
exports.PreInitGlobalActor = PreInitGlobalActor;
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
function PreInitTargetScopedActor(connection) {}
|
||||
|
||||
PreInitTargetScopedActor.prototype = {
|
||||
actorPrefix: "preInitTab",
|
||||
actorPrefix: "preInitTargetScoped",
|
||||
onPing(request) {
|
||||
return { message: "pong" };
|
||||
},
|
||||
|
@ -18,8 +18,4 @@ PreInitTargetScopedActor.prototype = {
|
|||
PreInitTargetScopedActor.prototype.requestTypes = {
|
||||
"ping": PreInitTargetScopedActor.prototype.onPing,
|
||||
};
|
||||
|
||||
DebuggerServer.addTargetScopedActor({
|
||||
constructorName: "PreInitTargetScopedActor",
|
||||
constructorFun: PreInitTargetScopedActor,
|
||||
}, "preInitTargetScopedActor");
|
||||
exports.PreInitTargetScopedActor = PreInitTargetScopedActor;
|
||||
|
|
|
@ -16,13 +16,29 @@ function getActorInstance(connID, actorID) {
|
|||
* regardless of the object's state.
|
||||
*/
|
||||
add_task(async function() {
|
||||
DebuggerServer.addActors("resource://test/pre_init_global_actors.js");
|
||||
DebuggerServer.addActors("resource://test/pre_init_target_scoped_actors.js");
|
||||
DebuggerServer.registerModule("resource://test/pre_init_global_actors.js", {
|
||||
prefix: "preInitGlobal",
|
||||
constructor: "PreInitGlobalActor",
|
||||
type: { global: true },
|
||||
});
|
||||
DebuggerServer.registerModule("resource://test/pre_init_target_scoped_actors.js", {
|
||||
prefix: "preInitTargetScoped",
|
||||
constructor: "PreInitTargetScopedActor",
|
||||
type: { target: true },
|
||||
});
|
||||
|
||||
const client = await startTestDebuggerServer("example tab");
|
||||
|
||||
DebuggerServer.addActors("resource://test/post_init_global_actors.js");
|
||||
DebuggerServer.addActors("resource://test/post_init_target_scoped_actors.js");
|
||||
DebuggerServer.registerModule("resource://test/post_init_global_actors.js", {
|
||||
prefix: "postInitGlobal",
|
||||
constructor: "PostInitGlobalActor",
|
||||
type: { global: true },
|
||||
});
|
||||
DebuggerServer.registerModule("resource://test/post_init_target_scoped_actors.js", {
|
||||
prefix: "postInitTargetScoped",
|
||||
constructor: "PostInitTargetScopedActor",
|
||||
type: { target: true },
|
||||
});
|
||||
|
||||
let actors = await client.listTabs();
|
||||
Assert.equal(actors.tabs.length, 1);
|
||||
|
|
|
@ -0,0 +1,128 @@
|
|||
/* 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 { Cr } = require("chrome");
|
||||
const flags = require("devtools/shared/flags");
|
||||
|
||||
/**
|
||||
* A transport for the debugging protocol that uses nsIMessageManagers to
|
||||
* exchange packets with servers running in child processes.
|
||||
*
|
||||
* In the parent process, |mm| should be the nsIMessageSender for the
|
||||
* child process. In a child process, |mm| should be the child process
|
||||
* message manager, which sends packets to the parent.
|
||||
*
|
||||
* |prefix| is a string included in the message names, to distinguish
|
||||
* multiple servers running in the same child process.
|
||||
*
|
||||
* This transport exchanges messages named 'debug:<prefix>:packet', where
|
||||
* <prefix> is |prefix|, whose data is the protocol packet.
|
||||
*/
|
||||
function ChildDebuggerTransport(mm, prefix) {
|
||||
this._mm = mm;
|
||||
this._messageName = "debug:" + prefix + ":packet";
|
||||
}
|
||||
|
||||
/*
|
||||
* To avoid confusion, we use 'message' to mean something that
|
||||
* nsIMessageSender conveys, and 'packet' to mean a remote debugging
|
||||
* protocol packet.
|
||||
*/
|
||||
ChildDebuggerTransport.prototype = {
|
||||
constructor: ChildDebuggerTransport,
|
||||
|
||||
hooks: null,
|
||||
|
||||
_addListener() {
|
||||
this._mm.addMessageListener(this._messageName, this);
|
||||
},
|
||||
|
||||
_removeListener() {
|
||||
try {
|
||||
this._mm.removeMessageListener(this._messageName, this);
|
||||
} catch (e) {
|
||||
if (e.result != Cr.NS_ERROR_NULL_POINTER) {
|
||||
throw e;
|
||||
}
|
||||
// In some cases, especially when using messageManagers in non-e10s mode, we reach
|
||||
// this point with a dead messageManager which only throws errors but does not
|
||||
// seem to indicate in any other way that it is dead.
|
||||
}
|
||||
},
|
||||
|
||||
ready: function() {
|
||||
this._addListener();
|
||||
},
|
||||
|
||||
close: function() {
|
||||
this._removeListener();
|
||||
this.hooks.onClosed();
|
||||
},
|
||||
|
||||
receiveMessage: function({data}) {
|
||||
this.hooks.onPacket(data);
|
||||
},
|
||||
|
||||
/**
|
||||
* Helper method to ensure a given `object` can be sent across message manager
|
||||
* without being serialized to JSON.
|
||||
* See https://searchfox.org/mozilla-central/rev/6bfadf95b4a6aaa8bb3b2a166d6c3545983e179a/dom/base/nsFrameMessageManager.cpp#458-469
|
||||
*/
|
||||
_canBeSerialized: function(object) {
|
||||
try {
|
||||
const holder = new StructuredCloneHolder(object);
|
||||
holder.deserialize(this);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
pathToUnserializable: function(object) {
|
||||
for (const key in object) {
|
||||
const value = object[key];
|
||||
if (!this._canBeSerialized(value)) {
|
||||
if (typeof value == "object") {
|
||||
return [key].concat(this.pathToUnserializable(value));
|
||||
}
|
||||
return [key];
|
||||
}
|
||||
}
|
||||
return [];
|
||||
},
|
||||
|
||||
send: function(packet) {
|
||||
if (flags.testing && !this._canBeSerialized(packet)) {
|
||||
const attributes = this.pathToUnserializable(packet);
|
||||
let msg = "Following packet can't be serialized: " + JSON.stringify(packet);
|
||||
msg += "\nBecause of attributes: " + attributes.join(", ") + "\n";
|
||||
msg += "Did you pass a function or an XPCOM object in it?";
|
||||
throw new Error(msg);
|
||||
}
|
||||
try {
|
||||
this._mm.sendAsyncMessage(this._messageName, packet);
|
||||
} catch (e) {
|
||||
if (e.result != Cr.NS_ERROR_NULL_POINTER) {
|
||||
throw e;
|
||||
}
|
||||
// In some cases, especially when using messageManagers in non-e10s mode, we reach
|
||||
// this point with a dead messageManager which only throws errors but does not
|
||||
// seem to indicate in any other way that it is dead.
|
||||
}
|
||||
},
|
||||
|
||||
startBulkSend: function() {
|
||||
throw new Error("Can't send bulk data to child processes.");
|
||||
},
|
||||
|
||||
swapBrowser(mm) {
|
||||
this._removeListener();
|
||||
this._mm = mm;
|
||||
this._addListener();
|
||||
},
|
||||
};
|
||||
|
||||
exports.ChildDebuggerTransport = ChildDebuggerTransport;
|
|
@ -0,0 +1,190 @@
|
|||
/* 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";
|
||||
|
||||
/* global uneval */
|
||||
|
||||
const { CC } = require("chrome");
|
||||
const DevToolsUtils = require("devtools/shared/DevToolsUtils");
|
||||
const { dumpn } = DevToolsUtils;
|
||||
const flags = require("devtools/shared/flags");
|
||||
const StreamUtils = require("devtools/shared/transport/stream-utils");
|
||||
const promise = require("promise");
|
||||
const defer = require("devtools/shared/defer");
|
||||
|
||||
loader.lazyGetter(this, "Pipe", () => {
|
||||
return CC("@mozilla.org/pipe;1", "nsIPipe", "init");
|
||||
});
|
||||
|
||||
/**
|
||||
* An adapter that handles data transfers between the debugger client and
|
||||
* server when they both run in the same process. It presents the same API as
|
||||
* DebuggerTransport, but instead of transmitting serialized messages across a
|
||||
* connection it merely calls the packet dispatcher of the other side.
|
||||
*
|
||||
* @param other LocalDebuggerTransport
|
||||
* The other endpoint for this debugger connection.
|
||||
*
|
||||
* @see DebuggerTransport
|
||||
*/
|
||||
function LocalDebuggerTransport(other) {
|
||||
this.other = other;
|
||||
this.hooks = null;
|
||||
|
||||
// A packet number, shared between this and this.other. This isn't used by the
|
||||
// protocol at all, but it makes the packet traces a lot easier to follow.
|
||||
this._serial = this.other ? this.other._serial : { count: 0 };
|
||||
this.close = this.close.bind(this);
|
||||
}
|
||||
|
||||
LocalDebuggerTransport.prototype = {
|
||||
/**
|
||||
* Transmit a message by directly calling the onPacket handler of the other
|
||||
* endpoint.
|
||||
*/
|
||||
send: function(packet) {
|
||||
const serial = this._serial.count++;
|
||||
if (flags.wantLogging) {
|
||||
// Check 'from' first, as 'echo' packets have both.
|
||||
if (packet.from) {
|
||||
dumpn("Packet " + serial + " sent from " + uneval(packet.from));
|
||||
} else if (packet.to) {
|
||||
dumpn("Packet " + serial + " sent to " + uneval(packet.to));
|
||||
}
|
||||
}
|
||||
this._deepFreeze(packet);
|
||||
const other = this.other;
|
||||
if (other) {
|
||||
DevToolsUtils.executeSoon(DevToolsUtils.makeInfallible(() => {
|
||||
// Avoid the cost of JSON.stringify() when logging is disabled.
|
||||
if (flags.wantLogging) {
|
||||
dumpn("Received packet " + serial + ": " + JSON.stringify(packet, null, 2));
|
||||
}
|
||||
if (other.hooks) {
|
||||
other.hooks.onPacket(packet);
|
||||
}
|
||||
}, "LocalDebuggerTransport instance's this.other.hooks.onPacket"));
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Send a streaming bulk packet directly to the onBulkPacket handler of the
|
||||
* other endpoint.
|
||||
*
|
||||
* This case is much simpler than the full DebuggerTransport, since there is
|
||||
* no primary stream we have to worry about managing while we hand it off to
|
||||
* others temporarily. Instead, we can just make a single use pipe and be
|
||||
* done with it.
|
||||
*/
|
||||
startBulkSend: function({actor, type, length}) {
|
||||
const serial = this._serial.count++;
|
||||
|
||||
dumpn("Sent bulk packet " + serial + " for actor " + actor);
|
||||
if (!this.other) {
|
||||
const error = new Error("startBulkSend: other side of transport missing");
|
||||
return promise.reject(error);
|
||||
}
|
||||
|
||||
const pipe = new Pipe(true, true, 0, 0, null);
|
||||
|
||||
DevToolsUtils.executeSoon(DevToolsUtils.makeInfallible(() => {
|
||||
dumpn("Received bulk packet " + serial);
|
||||
if (!this.other.hooks) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Receiver
|
||||
const deferred = defer();
|
||||
const packet = {
|
||||
actor: actor,
|
||||
type: type,
|
||||
length: length,
|
||||
copyTo: (output) => {
|
||||
const copying =
|
||||
StreamUtils.copyStream(pipe.inputStream, output, length);
|
||||
deferred.resolve(copying);
|
||||
return copying;
|
||||
},
|
||||
stream: pipe.inputStream,
|
||||
done: deferred
|
||||
};
|
||||
|
||||
this.other.hooks.onBulkPacket(packet);
|
||||
|
||||
// Await the result of reading from the stream
|
||||
deferred.promise.then(() => pipe.inputStream.close(), this.close);
|
||||
}, "LocalDebuggerTransport instance's this.other.hooks.onBulkPacket"));
|
||||
|
||||
// Sender
|
||||
const sendDeferred = defer();
|
||||
|
||||
// The remote transport is not capable of resolving immediately here, so we
|
||||
// shouldn't be able to either.
|
||||
DevToolsUtils.executeSoon(() => {
|
||||
const copyDeferred = defer();
|
||||
|
||||
sendDeferred.resolve({
|
||||
copyFrom: (input) => {
|
||||
const copying =
|
||||
StreamUtils.copyStream(input, pipe.outputStream, length);
|
||||
copyDeferred.resolve(copying);
|
||||
return copying;
|
||||
},
|
||||
stream: pipe.outputStream,
|
||||
done: copyDeferred
|
||||
});
|
||||
|
||||
// Await the result of writing to the stream
|
||||
copyDeferred.promise.then(() => pipe.outputStream.close(), this.close);
|
||||
});
|
||||
|
||||
return sendDeferred.promise;
|
||||
},
|
||||
|
||||
/**
|
||||
* Close the transport.
|
||||
*/
|
||||
close: function() {
|
||||
if (this.other) {
|
||||
// Remove the reference to the other endpoint before calling close(), to
|
||||
// avoid infinite recursion.
|
||||
const other = this.other;
|
||||
this.other = null;
|
||||
other.close();
|
||||
}
|
||||
if (this.hooks) {
|
||||
try {
|
||||
this.hooks.onClosed();
|
||||
} catch (ex) {
|
||||
console.error(ex);
|
||||
}
|
||||
this.hooks = null;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* An empty method for emulating the DebuggerTransport API.
|
||||
*/
|
||||
ready: function() {},
|
||||
|
||||
/**
|
||||
* Helper function that makes an object fully immutable.
|
||||
*/
|
||||
_deepFreeze: function(object) {
|
||||
Object.freeze(object);
|
||||
for (const prop in object) {
|
||||
// Freeze the properties that are objects, not on the prototype, and not
|
||||
// already frozen. Note that this might leave an unfrozen reference
|
||||
// somewhere in the object if there is an already frozen object containing
|
||||
// an unfrozen object.
|
||||
if (object.hasOwnProperty(prop) && typeof object === "object" &&
|
||||
!Object.isFrozen(object)) {
|
||||
this._deepFreeze(object[prop]);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
exports.LocalDebuggerTransport = LocalDebuggerTransport;
|
|
@ -7,8 +7,11 @@
|
|||
XPCSHELL_TESTS_MANIFESTS += ['tests/unit/xpcshell.ini']
|
||||
|
||||
DevToolsModules(
|
||||
'child-transport.js',
|
||||
'local-transport.js',
|
||||
'packets.js',
|
||||
'stream-utils.js',
|
||||
'transport.js',
|
||||
'websocket-transport.js',
|
||||
'worker-transport.js',
|
||||
)
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,110 @@
|
|||
/* 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";
|
||||
|
||||
// Each worker debugger supports only a single connection to the main thread.
|
||||
// However, its theoretically possible for multiple servers to connect to the
|
||||
// same worker. Consequently, each transport has a connection id, to allow
|
||||
// messages from multiple connections to be multiplexed on a single channel.
|
||||
|
||||
/**
|
||||
* A transport that uses a WorkerDebugger to send packets from the main
|
||||
* thread to a worker thread.
|
||||
*/
|
||||
function MainThreadWorkerDebuggerTransport(dbg, id) {
|
||||
this._dbg = dbg;
|
||||
this._id = id;
|
||||
this.onMessage = this._onMessage.bind(this);
|
||||
}
|
||||
|
||||
MainThreadWorkerDebuggerTransport.prototype = {
|
||||
constructor: MainThreadWorkerDebuggerTransport,
|
||||
|
||||
ready: function() {
|
||||
this._dbg.addListener(this);
|
||||
},
|
||||
|
||||
close: function() {
|
||||
this._dbg.removeListener(this);
|
||||
if (this.hooks) {
|
||||
this.hooks.onClosed();
|
||||
}
|
||||
},
|
||||
|
||||
send: function(packet) {
|
||||
this._dbg.postMessage(JSON.stringify({
|
||||
type: "message",
|
||||
id: this._id,
|
||||
message: packet
|
||||
}));
|
||||
},
|
||||
|
||||
startBulkSend: function() {
|
||||
throw new Error("Can't send bulk data from worker threads!");
|
||||
},
|
||||
|
||||
_onMessage: function(message) {
|
||||
const packet = JSON.parse(message);
|
||||
if (packet.type !== "message" || packet.id !== this._id) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.hooks) {
|
||||
this.hooks.onPacket(packet.message);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
exports.MainThreadWorkerDebuggerTransport = MainThreadWorkerDebuggerTransport;
|
||||
|
||||
/**
|
||||
* A transport that uses a WorkerDebuggerGlobalScope to send packets from a
|
||||
* worker thread to the main thread.
|
||||
*/
|
||||
function WorkerThreadWorkerDebuggerTransport(scope, id) {
|
||||
this._scope = scope;
|
||||
this._id = id;
|
||||
this._onMessage = this._onMessage.bind(this);
|
||||
}
|
||||
|
||||
WorkerThreadWorkerDebuggerTransport.prototype = {
|
||||
constructor: WorkerThreadWorkerDebuggerTransport,
|
||||
|
||||
ready: function() {
|
||||
this._scope.addEventListener("message", this._onMessage);
|
||||
},
|
||||
|
||||
close: function() {
|
||||
this._scope.removeEventListener("message", this._onMessage);
|
||||
if (this.hooks) {
|
||||
this.hooks.onClosed();
|
||||
}
|
||||
},
|
||||
|
||||
send: function(packet) {
|
||||
this._scope.postMessage(JSON.stringify({
|
||||
type: "message",
|
||||
id: this._id,
|
||||
message: packet
|
||||
}));
|
||||
},
|
||||
|
||||
startBulkSend: function() {
|
||||
throw new Error("Can't send bulk data from worker threads!");
|
||||
},
|
||||
|
||||
_onMessage: function(event) {
|
||||
const packet = JSON.parse(event.data);
|
||||
if (packet.type !== "message" || packet.id !== this._id) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.hooks) {
|
||||
this.hooks.onPacket(packet.message);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
exports.WorkerThreadWorkerDebuggerTransport = WorkerThreadWorkerDebuggerTransport;
|
|
@ -78,7 +78,7 @@ DocGroup::ReportPerformanceInfo()
|
|||
#else
|
||||
uint32_t pid = getpid();
|
||||
#endif
|
||||
uint64_t pwid = 0;
|
||||
uint64_t windowID = 0;
|
||||
uint16_t count = 0;
|
||||
uint64_t duration = 0;
|
||||
bool isTopLevel = false;
|
||||
|
@ -88,8 +88,17 @@ DocGroup::ReportPerformanceInfo()
|
|||
for (const auto& document : *this) {
|
||||
nsCOMPtr<nsIDocument> doc = do_QueryInterface(document);
|
||||
MOZ_ASSERT(doc);
|
||||
nsCOMPtr<nsIURI> docURI = doc->GetDocumentURI();
|
||||
if (!docURI) {
|
||||
continue;
|
||||
}
|
||||
docURI->GetHost(host);
|
||||
// If the host is empty, using the url
|
||||
if (host.IsEmpty()) {
|
||||
host = docURI->GetSpecOrDefault();
|
||||
}
|
||||
// looking for the top level document URI
|
||||
nsPIDOMWindowInner* win = doc->GetInnerWindow();
|
||||
nsPIDOMWindowOuter* win = doc->GetWindow();
|
||||
if (!win) {
|
||||
continue;
|
||||
}
|
||||
|
@ -101,20 +110,12 @@ DocGroup::ReportPerformanceInfo()
|
|||
if (!top) {
|
||||
continue;
|
||||
}
|
||||
nsCOMPtr<nsIURI> docURI = doc->GetDocumentURI();
|
||||
if (!docURI) {
|
||||
continue;
|
||||
}
|
||||
pwid = top->WindowID();
|
||||
windowID = top->WindowID();
|
||||
isTopLevel = outer->IsTopLevelWindow();
|
||||
docURI->GetHost(host);
|
||||
// If the host is empty, using the url
|
||||
if (host.IsEmpty()) {
|
||||
docURI->GetSpec(host);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(!host.IsEmpty());
|
||||
duration = mPerformanceCounter->GetExecutionDuration();
|
||||
FallibleTArray<CategoryDispatch> items;
|
||||
|
||||
|
@ -125,11 +126,11 @@ DocGroup::ReportPerformanceInfo()
|
|||
CategoryDispatch item = CategoryDispatch(index, count);
|
||||
if (!items.AppendElement(item, fallible)) {
|
||||
NS_ERROR("Could not complete the operation");
|
||||
return PerformanceInfo(host, pid, pwid, duration, false, isTopLevel, items);
|
||||
return PerformanceInfo(host, pid, windowID, duration, false, isTopLevel, items);
|
||||
}
|
||||
}
|
||||
|
||||
return PerformanceInfo(host, pid, pwid, duration, false, isTopLevel, items);
|
||||
return PerformanceInfo(host, pid, windowID, duration, false, isTopLevel, items);
|
||||
}
|
||||
|
||||
nsresult
|
||||
|
|
|
@ -295,8 +295,7 @@ nsCommandParams::RemoveValue(const char* aName)
|
|||
nsCommandParams::HashEntry*
|
||||
nsCommandParams::GetNamedEntry(const char* aName) const
|
||||
{
|
||||
return static_cast<HashEntry*>(
|
||||
const_cast<PLDHashTable&>(mValuesHash).Search((void*)aName));
|
||||
return static_cast<HashEntry*>(mValuesHash.Search((void*)aName));
|
||||
}
|
||||
|
||||
nsCommandParams::HashEntry*
|
||||
|
|
|
@ -67,6 +67,7 @@ TextComposition::TextComposition(nsPresContext* aPresContext,
|
|||
, mIsRequestingCommit(false)
|
||||
, mIsRequestingCancel(false)
|
||||
, mRequestedToCommitOrCancel(false)
|
||||
, mHasDispatchedDOMTextEvent(false)
|
||||
, mHasReceivedCommitEvent(false)
|
||||
, mWasNativeCompositionEndEventDiscarded(false)
|
||||
, mAllowControlCharacters(
|
||||
|
@ -108,6 +109,13 @@ TextComposition::MaybeDispatchCompositionUpdate(
|
|||
return false;
|
||||
}
|
||||
|
||||
// Note that we don't need to dispatch eCompositionUpdate event even if
|
||||
// mHasDispatchedDOMTextEvent is false and eCompositionCommit event is
|
||||
// dispatched with empty string immediately after eCompositionStart
|
||||
// because composition string has never been changed from empty string to
|
||||
// non-empty string in such composition even if selected string was not
|
||||
// empty string (mLastData isn't set to selected text when this receives
|
||||
// eCompositionStart).
|
||||
if (mLastData == aCompositionEvent->mData) {
|
||||
return true;
|
||||
}
|
||||
|
@ -356,10 +364,15 @@ TextComposition::DispatchCompositionEvent(
|
|||
// When mIsComposing is false but the committing string is different from
|
||||
// the last data (E.g., previous eCompositionChange event made the
|
||||
// composition string empty or didn't have clause information), we don't
|
||||
// need to dispatch redundant DOM text event.
|
||||
// need to dispatch redundant DOM text event. (But note that we need to
|
||||
// dispatch eCompositionChange event if we have not dispatched
|
||||
// eCompositionChange event yet and commit string replaces selected string
|
||||
// with empty string since selected string hasn't been replaced with empty
|
||||
// string yet.)
|
||||
if (dispatchDOMTextEvent &&
|
||||
aCompositionEvent->mMessage != eCompositionChange &&
|
||||
!mIsComposing && mLastData == aCompositionEvent->mData) {
|
||||
!mIsComposing && mHasDispatchedDOMTextEvent &&
|
||||
mLastData == aCompositionEvent->mData) {
|
||||
dispatchEvent = dispatchDOMTextEvent = false;
|
||||
}
|
||||
|
||||
|
@ -387,10 +400,14 @@ TextComposition::DispatchCompositionEvent(
|
|||
// we cannot map multiple event messages to a DOM event type.
|
||||
if (dispatchDOMTextEvent &&
|
||||
aCompositionEvent->mMessage != eCompositionChange) {
|
||||
mHasDispatchedDOMTextEvent = true;
|
||||
aCompositionEvent->mFlags =
|
||||
CloneAndDispatchAs(aCompositionEvent, eCompositionChange,
|
||||
aStatus, aCallBack);
|
||||
} else {
|
||||
if (aCompositionEvent->mMessage == eCompositionChange) {
|
||||
mHasDispatchedDOMTextEvent = true;
|
||||
}
|
||||
DispatchEvent(aCompositionEvent, aStatus, aCallBack);
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -369,6 +369,9 @@ private:
|
|||
// mIsRequestingCancel are set to false.
|
||||
bool mRequestedToCommitOrCancel;
|
||||
|
||||
// Set to true if the instance dispatches an eCompositionChange event.
|
||||
bool mHasDispatchedDOMTextEvent;
|
||||
|
||||
// Before this dispatches commit event into the tree, this is set to true.
|
||||
// So, this means if native IME already commits the composition.
|
||||
bool mHasReceivedCommitEvent;
|
||||
|
|
|
@ -153,12 +153,6 @@ public:
|
|||
aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
|
||||
return false;
|
||||
}
|
||||
for (uint32_t i = 0; i < aEvent.mCurveLength; ++i) {
|
||||
if (!IsValid(aEvent.mCurve[i])) {
|
||||
aRv.Throw(NS_ERROR_TYPE_ERR);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool timeAndValueValid = IsValid(aEvent.mValue) &&
|
||||
|
|
|
@ -228,6 +228,13 @@ AudioBlockInPlaceScale(float aBlock[WEBAUDIO_BLOCK_SIZE],
|
|||
AudioBufferInPlaceScale(aBlock, aScale, WEBAUDIO_BLOCK_SIZE);
|
||||
}
|
||||
|
||||
void
|
||||
AudioBlockInPlaceScale(float aBlock[WEBAUDIO_BLOCK_SIZE],
|
||||
float aScale[WEBAUDIO_BLOCK_SIZE])
|
||||
{
|
||||
AudioBufferInPlaceScale(aBlock, aScale, WEBAUDIO_BLOCK_SIZE);
|
||||
}
|
||||
|
||||
void
|
||||
AudioBufferInPlaceScale(float* aBlock,
|
||||
float aScale,
|
||||
|
@ -255,6 +262,30 @@ AudioBufferInPlaceScale(float* aBlock,
|
|||
}
|
||||
}
|
||||
|
||||
void
|
||||
AudioBufferInPlaceScale(float* aBlock,
|
||||
float* aScale,
|
||||
uint32_t aSize)
|
||||
{
|
||||
#ifdef BUILD_ARM_NEON
|
||||
if (mozilla::supports_neon()) {
|
||||
AudioBufferInPlaceScale_NEON(aBlock, aScale, aSize);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_SSE2
|
||||
if (mozilla::supports_sse2()) {
|
||||
AudioBufferInPlaceScale_SSE(aBlock, aScale, aSize);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
for (uint32_t i = 0; i < aSize; ++i) {
|
||||
*aBlock++ *= *aScale++;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
AudioBlockPanMonoToStereo(const float aInput[WEBAUDIO_BLOCK_SIZE],
|
||||
float aGainL[WEBAUDIO_BLOCK_SIZE],
|
||||
|
|
|
@ -209,6 +209,18 @@ void AudioBufferInPlaceScale(float* aBlock,
|
|||
float aScale,
|
||||
uint32_t aSize);
|
||||
|
||||
/**
|
||||
* a-rate in place gain.
|
||||
*/
|
||||
void AudioBlockInPlaceScale(float aBlock[WEBAUDIO_BLOCK_SIZE],
|
||||
float aScale[WEBAUDIO_BLOCK_SIZE]);
|
||||
/**
|
||||
* a-rate in place gain.
|
||||
*/
|
||||
void AudioBufferInPlaceScale(float* aBlock,
|
||||
float* aScale,
|
||||
uint32_t aSize);
|
||||
|
||||
/**
|
||||
* Upmix a mono input to a stereo output, scaling the two output channels by two
|
||||
* different gain value.
|
||||
|
|
|
@ -161,6 +161,47 @@ AudioBufferInPlaceScale_NEON(float* aBlock,
|
|||
}
|
||||
}
|
||||
|
||||
void
|
||||
AudioBufferInPlaceScale_NEON(float* aBlock,
|
||||
float* aScale,
|
||||
uint32_t aSize)
|
||||
{
|
||||
ASSERT_ALIGNED(aBlock);
|
||||
|
||||
float32x4_t vin0, vin1, vin2, vin3;
|
||||
float32x4_t vout0, vout1, vout2, vout3;
|
||||
float32x4_t vscale0, vscale1, vscale2, vscale3;
|
||||
|
||||
uint32_t dif = aSize % 16;
|
||||
uint32_t vectorSize = aSize - dif;
|
||||
uint32_t i = 0;
|
||||
for (; i < vectorSize; i+=16) {
|
||||
vin0 = vld1q_f32(ADDRESS_OF(aBlock, i));
|
||||
vin1 = vld1q_f32(ADDRESS_OF(aBlock, i+4));
|
||||
vin2 = vld1q_f32(ADDRESS_OF(aBlock, i+8));
|
||||
vin3 = vld1q_f32(ADDRESS_OF(aBlock, i+12));
|
||||
|
||||
vscale0 = vld1q_f32(ADDRESS_OF(aScale, i));
|
||||
vscale1 = vld1q_f32(ADDRESS_OF(aScale, i+4));
|
||||
vscale2 = vld1q_f32(ADDRESS_OF(aScale, i+8));
|
||||
vscale3 = vld1q_f32(ADDRESS_OF(aScale, i+12));
|
||||
|
||||
vout0 = vmulq_f32(vin0, vscale0);
|
||||
vout1 = vmulq_f32(vin1, vscale1);
|
||||
vout2 = vmulq_f32(vin2, vscale2);
|
||||
vout3 = vmulq_f32(vin3, vscale3);
|
||||
|
||||
vst1q_f32(ADDRESS_OF(aBlock, i), vout0);
|
||||
vst1q_f32(ADDRESS_OF(aBlock, i+4), vout1);
|
||||
vst1q_f32(ADDRESS_OF(aBlock, i+8), vout2);
|
||||
vst1q_f32(ADDRESS_OF(aBlock, i+12), vout3);
|
||||
}
|
||||
|
||||
for (unsigned j = 0; j < dif; ++i, ++j) {
|
||||
aBlock[i] *= aScale[i];
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
AudioBlockPanStereoToStereo_NEON(const float aInputL[WEBAUDIO_BLOCK_SIZE],
|
||||
const float aInputR[WEBAUDIO_BLOCK_SIZE],
|
||||
|
|
|
@ -28,6 +28,10 @@ void
|
|||
AudioBufferInPlaceScale_NEON(float* aBlock,
|
||||
float aScale,
|
||||
uint32_t aSize);
|
||||
void
|
||||
AudioBufferInPlaceScale_NEON(float* aBlock,
|
||||
float* aScale,
|
||||
uint32_t aSize);
|
||||
|
||||
void
|
||||
AudioBlockPanStereoToStereo_NEON(const float aInputL[WEBAUDIO_BLOCK_SIZE],
|
||||
|
|
|
@ -148,6 +148,38 @@ AudioBufferInPlaceScale_SSE(float* aBlock,
|
|||
}
|
||||
}
|
||||
|
||||
void
|
||||
AudioBufferInPlaceScale_SSE(float* aBlock,
|
||||
float* aScale,
|
||||
uint32_t aSize)
|
||||
{
|
||||
__m128 vout0, vout1, vout2, vout3,
|
||||
vgain0, vgain1, vgain2, vgain3,
|
||||
vin0, vin1, vin2, vin3;
|
||||
|
||||
ASSERT_ALIGNED16(aBlock);
|
||||
ASSERT_MULTIPLE16(aSize);
|
||||
|
||||
for (unsigned i = 0; i < aSize; i+=16) {
|
||||
vin0 = _mm_load_ps(&aBlock[i]);
|
||||
vin1 = _mm_load_ps(&aBlock[i + 4]);
|
||||
vin2 = _mm_load_ps(&aBlock[i + 8]);
|
||||
vin3 = _mm_load_ps(&aBlock[i + 12]);
|
||||
vgain0 = _mm_load_ps(&aScale[i]);
|
||||
vgain1 = _mm_load_ps(&aScale[i + 4]);
|
||||
vgain2 = _mm_load_ps(&aScale[i + 8]);
|
||||
vgain3 = _mm_load_ps(&aScale[i + 12]);
|
||||
vout0 = _mm_mul_ps(vin0, vgain0);
|
||||
vout1 = _mm_mul_ps(vin1, vgain1);
|
||||
vout2 = _mm_mul_ps(vin2, vgain2);
|
||||
vout3 = _mm_mul_ps(vin3, vgain3);
|
||||
_mm_store_ps(&aBlock[i], vout0);
|
||||
_mm_store_ps(&aBlock[i + 4], vout1);
|
||||
_mm_store_ps(&aBlock[i + 8], vout2);
|
||||
_mm_store_ps(&aBlock[i + 12], vout3);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
AudioBlockPanStereoToStereo_SSE(const float aInputL[WEBAUDIO_BLOCK_SIZE],
|
||||
const float aInputR[WEBAUDIO_BLOCK_SIZE],
|
||||
|
|
|
@ -26,6 +26,10 @@ void
|
|||
AudioBufferInPlaceScale_SSE(float* aBlock,
|
||||
float aScale,
|
||||
uint32_t aSize);
|
||||
void
|
||||
AudioBufferInPlaceScale_SSE(float* aBlock,
|
||||
float* aScale,
|
||||
uint32_t aSize);
|
||||
|
||||
void
|
||||
AudioBlockPanStereoToStereo_SSE(const float aInputL[WEBAUDIO_BLOCK_SIZE],
|
||||
|
|
|
@ -45,7 +45,7 @@ public:
|
|||
|
||||
// We override SetValueCurveAtTime to convert the Float32Array to the wrapper
|
||||
// object.
|
||||
AudioParam* SetValueCurveAtTime(const Float32Array& aValues,
|
||||
AudioParam* SetValueCurveAtTime(const nsTArray<float>& aValues,
|
||||
double aStartTime,
|
||||
double aDuration,
|
||||
ErrorResult& aRv)
|
||||
|
@ -54,11 +54,9 @@ public:
|
|||
aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
|
||||
return this;
|
||||
}
|
||||
aValues.ComputeLengthAndData();
|
||||
|
||||
aStartTime = std::max(aStartTime, GetParentObject()->CurrentTime());
|
||||
EventInsertionHelper(aRv, AudioTimelineEvent::SetValueCurve,
|
||||
aStartTime, 0.0f, 0.0f, aDuration, aValues.Data(),
|
||||
aStartTime, 0.0f, 0.0f, aDuration, aValues.Elements(),
|
||||
aValues.Length());
|
||||
return this;
|
||||
}
|
||||
|
|
|
@ -567,11 +567,16 @@ PannerNodeEngine::EqualPowerPanningFunction(const AudioBlock& aInput,
|
|||
orientationZ[0] = mOrientationZ.GetValueAtTime(tick);
|
||||
}
|
||||
|
||||
float computedGain[2*WEBAUDIO_BLOCK_SIZE + 4];
|
||||
float buffer[3*WEBAUDIO_BLOCK_SIZE + 4];
|
||||
bool onLeft[WEBAUDIO_BLOCK_SIZE];
|
||||
|
||||
float* alignedComputedGain = ALIGNED16(computedGain);
|
||||
ASSERT_ALIGNED16(alignedComputedGain);
|
||||
float* alignedPanningL = ALIGNED16(buffer);
|
||||
float* alignedPanningR = alignedPanningL + WEBAUDIO_BLOCK_SIZE;
|
||||
float* alignedGain = alignedPanningR + WEBAUDIO_BLOCK_SIZE;
|
||||
ASSERT_ALIGNED16(alignedPanningL);
|
||||
ASSERT_ALIGNED16(alignedPanningR);
|
||||
ASSERT_ALIGNED16(alignedGain);
|
||||
|
||||
for (size_t counter = 0; counter < WEBAUDIO_BLOCK_SIZE; ++counter) {
|
||||
ThreeDPoint position(mPositionX.HasSimpleValue() ? positionX[0] : positionX[counter],
|
||||
mPositionY.HasSimpleValue() ? positionY[0] : positionY[counter],
|
||||
|
@ -611,17 +616,24 @@ PannerNodeEngine::EqualPowerPanningFunction(const AudioBlock& aInput,
|
|||
distanceGain = ComputeDistanceGain(position);
|
||||
|
||||
// Actually compute the left and right gain.
|
||||
float gainL = cos(0.5 * M_PI * normalizedAzimuth) * aInput.mVolume * distanceGain * coneGain;
|
||||
float gainR = sin(0.5 * M_PI * normalizedAzimuth) * aInput.mVolume * distanceGain * coneGain;
|
||||
float gainL = cos(0.5 * M_PI * normalizedAzimuth);
|
||||
float gainR = sin(0.5 * M_PI * normalizedAzimuth);
|
||||
|
||||
alignedComputedGain[counter] = gainL;
|
||||
alignedComputedGain[WEBAUDIO_BLOCK_SIZE + counter] = gainR;
|
||||
|
||||
alignedPanningL[counter] = gainL;
|
||||
alignedPanningR[counter] = gainR;
|
||||
alignedGain[counter] = aInput.mVolume * distanceGain * coneGain;
|
||||
onLeft[counter] = azimuth <= 0;
|
||||
}
|
||||
|
||||
// Apply the gain to the output buffer
|
||||
ApplyStereoPanning(aInput, aOutput, alignedComputedGain, &alignedComputedGain[WEBAUDIO_BLOCK_SIZE], onLeft);
|
||||
// Apply the panning to the output buffer
|
||||
ApplyStereoPanning(aInput, aOutput, alignedPanningL, alignedPanningR, onLeft);
|
||||
|
||||
// Apply the input volume, cone and distance gain to the output buffer.
|
||||
float* outputL = aOutput->ChannelFloatsForWrite(0);
|
||||
float* outputR = aOutput->ChannelFloatsForWrite(1);
|
||||
AudioBlockInPlaceScale(outputL, alignedGain);
|
||||
AudioBlockInPlaceScale(outputR, alignedGain);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -162,9 +162,6 @@ TEST(AudioEventTimeline, InvalidEvents)
|
|||
Timeline timeline(10.0f);
|
||||
|
||||
float curve[] = { -1.0f, 0.0f, 1.0f };
|
||||
float badCurve1[] = { -1.0f, NaN, 1.0f };
|
||||
float badCurve2[] = { -1.0f, Infinity, 1.0f };
|
||||
float badCurve3[] = { -1.0f, -Infinity, 1.0f };
|
||||
|
||||
ErrorResultMock rv;
|
||||
|
||||
|
@ -202,12 +199,6 @@ TEST(AudioEventTimeline, InvalidEvents)
|
|||
is(rv, NS_ERROR_DOM_SYNTAX_ERR, "Correct error code returned");
|
||||
timeline.SetValueCurveAtTime(nullptr, 0, 1.0, 1.0, rv);
|
||||
is(rv, NS_ERROR_DOM_SYNTAX_ERR, "Correct error code returned");
|
||||
timeline.SetValueCurveAtTime(badCurve1, ArrayLength(badCurve1), 1.0, 1.0, rv);
|
||||
is(rv, NS_ERROR_TYPE_ERR, "Correct error code returned");
|
||||
timeline.SetValueCurveAtTime(badCurve2, ArrayLength(badCurve2), 1.0, 1.0, rv);
|
||||
is(rv, NS_ERROR_TYPE_ERR, "Correct error code returned");
|
||||
timeline.SetValueCurveAtTime(badCurve3, ArrayLength(badCurve3), 1.0, 1.0, rv);
|
||||
is(rv, NS_ERROR_TYPE_ERR, "Correct error code returned");
|
||||
timeline.SetValueCurveAtTime(curve, ArrayLength(curve), NaN, 1.0, rv);
|
||||
is(rv, NS_ERROR_DOM_SYNTAX_ERR, "Correct error code returned");
|
||||
timeline.SetValueCurveAtTime(curve, ArrayLength(curve), Infinity, 1.0, rv);
|
||||
|
|
|
@ -198,6 +198,7 @@ skip-if = toolkit == 'android' # bug 1091965
|
|||
[test_oscillatorTypeChange.html]
|
||||
[test_pannerNode.html]
|
||||
[test_pannerNode_equalPower.html]
|
||||
[test_pannerNode_audioparam_distance.html]
|
||||
[test_pannerNodeAbove.html]
|
||||
[test_pannerNodeAtZeroDistance.html]
|
||||
[test_pannerNodeChannelCount.html]
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Distance effect of a PannerNode with the position set via AudioParams (Bug 1472550)</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="text/javascript" src="webaudio.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
<pre id="test">
|
||||
<script class="testbody" type="text/javascript">
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
var o = new OfflineAudioContext(2, 256, 44100);
|
||||
|
||||
// We want a stereo constant source.
|
||||
var b = o.createBuffer(2, 1, 44100);
|
||||
b.getChannelData(0)[0] = 1;
|
||||
b.getChannelData(1)[0] = 1;
|
||||
var c = o.createBufferSource();
|
||||
c.buffer = b;
|
||||
c.loop = true;
|
||||
|
||||
var p = o.createPanner();
|
||||
p.positionY.setValueAtTime(1, 0);
|
||||
p.positionX.setValueAtTime(1, 0);
|
||||
p.positionZ.setValueAtTime(1, 0);
|
||||
|
||||
// Set the listener somewhere far
|
||||
o.listener.setPosition(20, 2, 20);
|
||||
|
||||
c.start();
|
||||
c.connect(p).connect(o.destination);
|
||||
|
||||
o.startRendering().then((ab) => {
|
||||
// Check that the distance attenuates the sound.
|
||||
ok(ab.getChannelData(0)[0] < 0.1, "left channel must be very quiet");
|
||||
ok(ab.getChannelData(1)[0] < 0.1, "right channel must be very quiet");
|
||||
SimpleTest.finish();
|
||||
});
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
|
@ -54,8 +54,8 @@ function selection_is(s, text)
|
|||
|
||||
function deselect()
|
||||
{
|
||||
// Click outside text to deselect.
|
||||
click(1, 1);
|
||||
// Click outside text (and outside all <rect> elements>) to deselect.
|
||||
click(15, 15);
|
||||
selection_is("", "deselecting by clicking outside text");
|
||||
}
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ support-files =
|
|||
geo_leak_test.html
|
||||
dummy.html
|
||||
ping_worker.html
|
||||
ping_worker2.html
|
||||
test_largeAllocation.html
|
||||
test_largeAllocation.html^headers^
|
||||
test_largeAllocation2.html
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
const ROOT_URL = "http://example.com/browser/dom/tests/browser";
|
||||
const DUMMY_URL = ROOT_URL + "/dummy.html";
|
||||
const WORKER_URL = ROOT_URL + "/ping_worker.html";
|
||||
const WORKER_URL2 = ROOT_URL + "/ping_worker2.html";
|
||||
|
||||
|
||||
let nextId = 0;
|
||||
|
@ -52,8 +53,11 @@ add_task(async function test() {
|
|||
gBrowser, opening: "about:memory", forceNewProcess: false
|
||||
});
|
||||
|
||||
let page3 = await BrowserTestUtils.openNewForegroundTab({
|
||||
gBrowser, opening: WORKER_URL
|
||||
});
|
||||
// load a 4th tab with a worker
|
||||
await BrowserTestUtils.withNewTab({ gBrowser, url: WORKER_URL },
|
||||
await BrowserTestUtils.withNewTab({ gBrowser, url: WORKER_URL2 },
|
||||
async function(browser) {
|
||||
// grab events..
|
||||
let workerDuration = 0;
|
||||
|
@ -64,9 +68,22 @@ add_task(async function test() {
|
|||
let aboutMemoryFound = false;
|
||||
let parentProcessEvent = false;
|
||||
let workerEvent = false;
|
||||
let subFrameIds = [];
|
||||
let topLevelIds = [];
|
||||
let sharedWorker = false;
|
||||
|
||||
function exploreResults(data) {
|
||||
for (let entry of data) {
|
||||
sharedWorker = entry.host.endsWith("shared_worker.js") || sharedWorker;
|
||||
|
||||
Assert.ok(entry.host != "" || entry.windowId !=0,
|
||||
"An entry should have a host or a windowId");
|
||||
if (entry.windowId != 0 && !entry.isToplevel && !entry.isWorker && !subFrameIds.includes(entry.windowId)) {
|
||||
subFrameIds.push(entry.windowId);
|
||||
}
|
||||
if (entry.isTopLevel && !topLevelIds.includes(entry.windowId)) {
|
||||
topLevelIds.push(entry.windowId);
|
||||
}
|
||||
if (entry.host == "example.com" && entry.isTopLevel) {
|
||||
isTopLevel = true;
|
||||
}
|
||||
|
@ -84,6 +101,7 @@ add_task(async function test() {
|
|||
}
|
||||
// let's look at the data we got back
|
||||
for (let item of entry.items) {
|
||||
Assert.ok(item.count > 0, "Categories with an empty count are dropped");
|
||||
if (entry.isWorker) {
|
||||
workerTotal += item.count;
|
||||
} else {
|
||||
|
@ -104,6 +122,12 @@ add_task(async function test() {
|
|||
Assert.ok(parentProcessEvent, "parent process sent back some events");
|
||||
Assert.ok(isTopLevel, "example.com as a top level window");
|
||||
Assert.ok(aboutMemoryFound, "about:memory");
|
||||
Assert.ok(sharedWorker, "We got some info from a shared worker");
|
||||
|
||||
// checking that subframes are not orphans
|
||||
for (let frameId of subFrameIds) {
|
||||
Assert.ok(topLevelIds.includes(frameId), "subframe is not orphan ");
|
||||
}
|
||||
|
||||
// Doing a second call, we shoud get bigger values
|
||||
let previousWorkerDuration = workerDuration;
|
||||
|
@ -122,5 +146,6 @@ add_task(async function test() {
|
|||
|
||||
BrowserTestUtils.removeTab(page1);
|
||||
BrowserTestUtils.removeTab(page2);
|
||||
BrowserTestUtils.removeTab(page3);
|
||||
SpecialPowers.clearUserPref("dom.performance.enable_scheduler_timing");
|
||||
});
|
||||
|
|
|
@ -5,14 +5,22 @@
|
|||
<script type="text/javascript">
|
||||
|
||||
var myWorker;
|
||||
var shared;
|
||||
|
||||
function init() {
|
||||
myWorker = new Worker('ping_worker.js');
|
||||
for (let i = 0; i++; i < 10) myWorker.postMessage("ping");
|
||||
|
||||
shared = new SharedWorker('shared_worker.js');
|
||||
shared.port.start();
|
||||
shared.port.onmessage = function(e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
<h1>A page with a worker</h1>
|
||||
<h1>A page with a worker and a shared worker</h1>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en" dir="ltr">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<script type="text/javascript">
|
||||
|
||||
var shared;
|
||||
|
||||
function init() {
|
||||
shared = new SharedWorker('shared_worker.js');
|
||||
shared.port.start();
|
||||
for (let i = 0; i++; i < 10) shared.port.postMessage(["ok"]);
|
||||
}
|
||||
|
||||
</script>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
<h1>A page with a shared worker</h1>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,9 @@
|
|||
|
||||
onconnect = function(e) {
|
||||
var port = e.ports[0];
|
||||
|
||||
port.onmessage = function(e) {
|
||||
var workerResult = 'Result: ' + (e.data[0] * e.data[1]);
|
||||
port.postMessage(e.data[0]);
|
||||
}
|
||||
}
|
|
@ -33,7 +33,7 @@ interface AudioParam {
|
|||
// Sets an array of arbitrary parameter values starting at time for the given duration.
|
||||
// The number of values will be scaled to fit into the desired duration.
|
||||
[Throws]
|
||||
AudioParam setValueCurveAtTime(Float32Array values, double startTime, double duration);
|
||||
AudioParam setValueCurveAtTime(sequence<float> values, double startTime, double duration);
|
||||
|
||||
// Cancels all scheduled parameter changes with times greater than or equal to startTime.
|
||||
[Throws]
|
||||
|
|
|
@ -485,19 +485,28 @@ WorkerDebugger::ReportPerformanceInfo()
|
|||
uint32_t pid = getpid();
|
||||
#endif
|
||||
bool isTopLevel= false;
|
||||
uint64_t pwid = 0;
|
||||
nsPIDOMWindowInner* win = mWorkerPrivate->GetWindow();
|
||||
uint64_t windowID = mWorkerPrivate->WindowID();
|
||||
|
||||
// Walk up to our containing page and its window
|
||||
WorkerPrivate* wp = mWorkerPrivate;
|
||||
while (wp->GetParent()) {
|
||||
wp = wp->GetParent();
|
||||
}
|
||||
nsPIDOMWindowInner* win = wp->GetWindow();
|
||||
if (win) {
|
||||
nsPIDOMWindowOuter* outer = win->GetOuterWindow();
|
||||
if (outer) {
|
||||
nsCOMPtr<nsPIDOMWindowOuter> top = outer->GetTop();
|
||||
if (top) {
|
||||
pwid = top->WindowID();
|
||||
isTopLevel = pwid == mWorkerPrivate->WindowID();
|
||||
windowID = top->WindowID();
|
||||
isTopLevel = outer->IsTopLevelWindow();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// getting the worker URL
|
||||
RefPtr<nsIURI> scriptURI = mWorkerPrivate->GetResolvedScriptURI();
|
||||
nsCString url = scriptURI->GetSpecOrDefault();
|
||||
|
||||
// Workers only produce metrics for a single category - DispatchCategory::Worker.
|
||||
// We still return an array of CategoryDispatch so the PerformanceInfo
|
||||
|
@ -505,7 +514,6 @@ WorkerDebugger::ReportPerformanceInfo()
|
|||
FallibleTArray<CategoryDispatch> items;
|
||||
uint64_t duration = 0;
|
||||
uint16_t count = 0;
|
||||
RefPtr<nsIURI> uri = mWorkerPrivate->GetResolvedScriptURI();
|
||||
|
||||
RefPtr<PerformanceCounter> perf = mWorkerPrivate->GetPerformanceCounter();
|
||||
if (perf) {
|
||||
|
@ -514,13 +522,11 @@ WorkerDebugger::ReportPerformanceInfo()
|
|||
CategoryDispatch item = CategoryDispatch(DispatchCategory::Worker.GetValue(), count);
|
||||
if (!items.AppendElement(item, fallible)) {
|
||||
NS_ERROR("Could not complete the operation");
|
||||
return PerformanceInfo(uri->GetSpecOrDefault(), pid, pwid, duration,
|
||||
true, isTopLevel, items);
|
||||
return PerformanceInfo(url, pid, windowID, duration, true, isTopLevel, items);
|
||||
}
|
||||
}
|
||||
|
||||
return PerformanceInfo(uri->GetSpecOrDefault(), pid, pwid, duration,
|
||||
true, isTopLevel, items);
|
||||
return PerformanceInfo(url, pid, windowID, duration, true, isTopLevel, items);
|
||||
}
|
||||
|
||||
} // dom namespace
|
||||
|
|
|
@ -51,6 +51,15 @@ SimpleTest.waitForFocus(()=>{
|
|||
|
||||
clear();
|
||||
|
||||
// FYI: Chrome commits composition if blur() and focus() are called during
|
||||
// composition. But note that if they are called by compositionupdate
|
||||
// listener, the behavior is unstable. On Windows, composition is
|
||||
// canceled. On Linux and macOS, the composition is committed
|
||||
// internally but the string keeps underlined. If they are called
|
||||
// by input event listener, committed on any platforms though.
|
||||
// On the other hand, Edge and Safari keeps composition even with
|
||||
// calling both blur() and focus().
|
||||
|
||||
// Committing at compositionstart
|
||||
aEditor.focus();
|
||||
aEditor.addEventListener("compositionstart", committer, true);
|
||||
|
@ -68,7 +77,7 @@ SimpleTest.waitForFocus(()=>{
|
|||
caret: { start: 1, length: 0 }, key: { key: "a" }});
|
||||
aEditor.removeEventListener("compositionupdate", committer, true);
|
||||
ok(!isComposing(), "composition in " + aEditor.id + " should be committed by compositionupdate event handler");
|
||||
is(value(), "", "composition in " + aEditor.id + " shouldn't have inserted any text since it's committed at first compositionupdate");
|
||||
is(value(), "a", "composition in " + aEditor.id + " should have \"a\" since IME committed with it");
|
||||
clear();
|
||||
|
||||
// Committing at first text (eCompositionChange)
|
||||
|
@ -93,7 +102,7 @@ SimpleTest.waitForFocus(()=>{
|
|||
caret: { start: 2, length: 0 }, key: { key: "b" }});
|
||||
aEditor.removeEventListener("compositionupdate", committer, true);
|
||||
ok(!isComposing(), "composition in " + aEditor.id + " should be committed by compositionupdate event handler");
|
||||
todo_is(value(), "a", "composition in " + aEditor.id + " shouldn't have been modified since it's committed at second compositionupdate");
|
||||
is(value(), "ab", "composition in " + aEditor.id + " should have \"ab\" since IME committed with it");
|
||||
clear();
|
||||
|
||||
// Committing at second text (eCompositionChange)
|
||||
|
@ -108,7 +117,7 @@ SimpleTest.waitForFocus(()=>{
|
|||
caret: { start: 2, length: 0 }, key: { key: "b" }});
|
||||
aEditor.removeEventListener("text", committer, true);
|
||||
ok(!isComposing(), "composition in " + aEditor.id + " should be committed by text event handler");
|
||||
todo_is(value(), "a", "composition in " + aEditor.id + " shouldn't have been modified since it's committed at second text");
|
||||
is(value(), "ab", "composition in " + aEditor.id + " should have \"ab\" since IME committed with it");
|
||||
clear();
|
||||
}
|
||||
runTest(document.getElementById("input"));
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
#include "nsDebug.h" // for NS_ERROR
|
||||
#include "nsPoint.h" // for nsPoint
|
||||
#include "nsRect.h" // for nsRect
|
||||
#include "nsRectAbsolute.h" // for nsRectAbsolute
|
||||
#include "base/basictypes.h"
|
||||
|
||||
using namespace mozilla::gfx;
|
||||
|
@ -68,6 +69,17 @@ AppendToString(std::stringstream& aStream, const nsRect& r,
|
|||
aStream << sfx;
|
||||
}
|
||||
|
||||
void
|
||||
AppendToString(std::stringstream& aStream, const nsRectAbsolute& r,
|
||||
const char* pfx, const char* sfx)
|
||||
{
|
||||
aStream << pfx;
|
||||
aStream << nsPrintfCString(
|
||||
"(l=%d, t=%d, r=%d, b=%d)",
|
||||
r.Left(), r.Top(), r.Right(), r.Bottom()).get();
|
||||
aStream << sfx;
|
||||
}
|
||||
|
||||
void
|
||||
AppendToString(std::stringstream& aStream, const wr::ColorF& c,
|
||||
const char* pfx, const char* sfx)
|
||||
|
|
|
@ -19,6 +19,8 @@
|
|||
#include "nsRegion.h" // for nsRegion, nsIntRegion
|
||||
#include "nscore.h" // for nsACString, etc
|
||||
|
||||
struct nsRectAbsolute;
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
namespace gfx {
|
||||
|
@ -49,6 +51,10 @@ void
|
|||
AppendToString(std::stringstream& aStream, const nsRect& r,
|
||||
const char* pfx="", const char* sfx="");
|
||||
|
||||
void
|
||||
AppendToString(std::stringstream& aStream, const nsRectAbsolute& r,
|
||||
const char* pfx="", const char* sfx="");
|
||||
|
||||
template<class T>
|
||||
void
|
||||
AppendToString(std::stringstream& aStream, const mozilla::gfx::PointTyped<T>& p,
|
||||
|
@ -97,6 +103,30 @@ AppendToString(std::stringstream& aStream, const mozilla::gfx::IntRectTyped<T>&
|
|||
aStream << sfx;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void
|
||||
AppendToString(std::stringstream& aStream, const mozilla::gfx::RectAbsoluteTyped<T>& r,
|
||||
const char* pfx="", const char* sfx="")
|
||||
{
|
||||
aStream << pfx;
|
||||
aStream << nsPrintfCString(
|
||||
"(l=%f, t=%f, r=%f, b=%f)",
|
||||
r.Left(), r.Top(), r.Right(), r.Bottom()).get();
|
||||
aStream << sfx;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void
|
||||
AppendToString(std::stringstream& aStream, const mozilla::gfx::IntRectAbsoluteTyped<T>& r,
|
||||
const char* pfx="", const char* sfx="")
|
||||
{
|
||||
aStream << pfx;
|
||||
aStream << nsPrintfCString(
|
||||
"(l=%d, t=%d, r=%d, b=%d)",
|
||||
r.Left(), r.Top(), r.Right(), r.Bottom()).get();
|
||||
aStream << sfx;
|
||||
}
|
||||
|
||||
void
|
||||
AppendToString(std::stringstream& aStream, const wr::ColorF& c,
|
||||
const char* pfx="", const char* sfx="");
|
||||
|
|
|
@ -74,6 +74,16 @@ RecenterDisplayPort(mozilla::layers::FrameMetrics& aFrameMetrics)
|
|||
aFrameMetrics.SetDisplayPortMargins(margins);
|
||||
}
|
||||
|
||||
static already_AddRefed<nsIPresShell>
|
||||
GetPresShell(const nsIContent* aContent)
|
||||
{
|
||||
nsCOMPtr<nsIPresShell> result;
|
||||
if (nsIDocument* doc = aContent->GetComposedDoc()) {
|
||||
result = doc->GetShell();
|
||||
}
|
||||
return result.forget();
|
||||
}
|
||||
|
||||
static CSSPoint
|
||||
ScrollFrameTo(nsIScrollableFrame* aFrame, const FrameMetrics& aMetrics, bool& aSuccessOut)
|
||||
{
|
||||
|
@ -146,6 +156,11 @@ ScrollFrame(nsIContent* aContent,
|
|||
if (sf) {
|
||||
sf->ResetScrollInfoIfGeneration(aMetrics.GetScrollGeneration());
|
||||
sf->SetScrollableByAPZ(!aMetrics.IsScrollInfoLayer());
|
||||
if (sf->IsRootScrollFrameOfDocument()) {
|
||||
if (nsCOMPtr<nsIPresShell> shell = GetPresShell(aContent)) {
|
||||
shell->SetVisualViewportOffset(CSSPoint::ToAppUnits(aMetrics.GetScrollOffset()));
|
||||
}
|
||||
}
|
||||
}
|
||||
bool scrollUpdated = false;
|
||||
CSSPoint apzScrollOffset = aMetrics.GetScrollOffset();
|
||||
|
@ -230,16 +245,6 @@ SetDisplayPortMargins(nsIPresShell* aPresShell,
|
|||
nsLayoutUtils::SetDisplayPortBaseIfNotSet(aContent, base);
|
||||
}
|
||||
|
||||
static already_AddRefed<nsIPresShell>
|
||||
GetPresShell(const nsIContent* aContent)
|
||||
{
|
||||
nsCOMPtr<nsIPresShell> result;
|
||||
if (nsIDocument* doc = aContent->GetComposedDoc()) {
|
||||
result = doc->GetShell();
|
||||
}
|
||||
return result.forget();
|
||||
}
|
||||
|
||||
static void
|
||||
SetPaintRequestTime(nsIContent* aContent, const TimeStamp& aPaintRequestTime)
|
||||
{
|
||||
|
|
|
@ -240,6 +240,7 @@ static bool
|
|||
IsContainerLayerItem(nsDisplayItem* aItem)
|
||||
{
|
||||
switch (aItem->GetType()) {
|
||||
case DisplayItemType::TYPE_WRAP_LIST:
|
||||
case DisplayItemType::TYPE_TRANSFORM:
|
||||
case DisplayItemType::TYPE_OPACITY:
|
||||
case DisplayItemType::TYPE_FILTER:
|
||||
|
@ -518,9 +519,9 @@ struct DIGroup
|
|||
combined = clip.ApplyNonRoundedIntersection(geometry->ComputeInvalidationRegion());
|
||||
IntRect transformedRect = ToDeviceSpace(combined.GetBounds(), aMatrix, appUnitsPerDevPixel, mLayerBounds.TopLeft());
|
||||
auto rect = transformedRect.Intersect(imageRect);
|
||||
MOZ_RELEASE_ASSERT(rect.IsEqualEdges(aData->mRect));
|
||||
GP("Layer NoChange: %s %d %d %d %d\n", aItem->Name(),
|
||||
aData->mRect.x, aData->mRect.y, aData->mRect.XMost(), aData->mRect.YMost());
|
||||
MOZ_RELEASE_ASSERT(rect.IsEqualEdges(aData->mRect));
|
||||
}
|
||||
} else {
|
||||
// XXX: this code can eventually be deleted/made debug only
|
||||
|
@ -528,9 +529,9 @@ struct DIGroup
|
|||
combined = clip.ApplyNonRoundedIntersection(geometry->ComputeInvalidationRegion());
|
||||
IntRect transformedRect = ToDeviceSpace(combined.GetBounds(), aMatrix, appUnitsPerDevPixel, mLayerBounds.TopLeft());
|
||||
auto rect = transformedRect.Intersect(imageRect);
|
||||
MOZ_RELEASE_ASSERT(rect.IsEqualEdges(aData->mRect));
|
||||
GP("NoChange: %s %d %d %d %d\n", aItem->Name(),
|
||||
aData->mRect.x, aData->mRect.y, aData->mRect.XMost(), aData->mRect.YMost());
|
||||
MOZ_RELEASE_ASSERT(rect.IsEqualEdges(aData->mRect));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -71,6 +71,6 @@ freetype = { version = "0.4", default-features = false }
|
|||
dwrote = "0.4.1"
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
core-foundation = "0.5"
|
||||
core-graphics = "0.13"
|
||||
core-text = { version = "9.2.0", default-features = false }
|
||||
core-foundation = "0.6"
|
||||
core-graphics = "0.14"
|
||||
core-text = { version = "10", default-features = false }
|
||||
|
|
|
@ -475,8 +475,8 @@ impl AlphaBatchBuilder {
|
|||
|
||||
// Add each run in this picture to the batch.
|
||||
for run in &pic.runs {
|
||||
let scroll_node = &ctx.clip_scroll_tree.nodes[run.clip_and_scroll.scroll_node_id.0];
|
||||
let transform_id = ctx.transforms.get_id(scroll_node.transform_index);
|
||||
let transform_id =
|
||||
ctx.transforms.get_id(run.clip_and_scroll.scroll_node_id.transform_index());
|
||||
self.add_run_to_batch(
|
||||
run,
|
||||
transform_id,
|
||||
|
@ -670,7 +670,7 @@ impl AlphaBatchBuilder {
|
|||
debug_assert!(picture.surface.is_some());
|
||||
|
||||
let real_xf = &ctx.clip_scroll_tree
|
||||
.nodes[picture.reference_frame_index.0]
|
||||
.spatial_nodes[picture.reference_frame_index.0]
|
||||
.world_content_transform
|
||||
.into();
|
||||
let polygon = make_polygon(
|
||||
|
|
|
@ -7,11 +7,11 @@ use api::{ImageRendering, LayoutRect, LayoutSize, LayoutPoint, LayoutVector2D, L
|
|||
use api::{BoxShadowClipMode, LayoutToWorldScale, LineOrientation, LineStyle};
|
||||
use border::{ensure_no_corner_overlap};
|
||||
use box_shadow::{BLUR_SAMPLE_SCALE, BoxShadowClipSource, BoxShadowCacheKey};
|
||||
use clip_scroll_tree::{ClipChainIndex, CoordinateSystemId, TransformIndex};
|
||||
use clip_scroll_tree::{ClipChainIndex, CoordinateSystemId};
|
||||
use ellipse::Ellipse;
|
||||
use freelist::{FreeList, FreeListHandle, WeakFreeListHandle};
|
||||
use gpu_cache::{GpuCache, GpuCacheHandle, ToGpuBlocks};
|
||||
use gpu_types::{BoxShadowStretchMode};
|
||||
use gpu_types::{BoxShadowStretchMode, TransformIndex};
|
||||
use prim_store::{ClipData, ImageMaskData};
|
||||
use render_task::to_cache_size;
|
||||
use resource_cache::{ImageRequest, ResourceCache};
|
||||
|
@ -257,19 +257,39 @@ impl ClipSource {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_rect(&self) -> bool {
|
||||
match *self {
|
||||
ClipSource::Rectangle(..) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_image_or_line_decoration_clip(&self) -> bool {
|
||||
match *self {
|
||||
ClipSource::Image(..) | ClipSource::LineDecoration(..) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ClipSources {
|
||||
pub clips: Vec<(ClipSource, GpuCacheHandle)>,
|
||||
pub local_inner_rect: LayoutRect,
|
||||
pub local_outer_rect: Option<LayoutRect>
|
||||
pub local_outer_rect: Option<LayoutRect>,
|
||||
pub only_rectangular_clips: bool,
|
||||
pub has_image_or_line_decoration_clip: bool,
|
||||
}
|
||||
|
||||
impl ClipSources {
|
||||
pub fn new(clips: Vec<ClipSource>) -> Self {
|
||||
let (local_inner_rect, local_outer_rect) = Self::calculate_inner_and_outer_rects(&clips);
|
||||
|
||||
let has_image_or_line_decoration_clip =
|
||||
clips.iter().any(|clip| clip.is_image_or_line_decoration_clip());
|
||||
let only_rectangular_clips =
|
||||
!has_image_or_line_decoration_clip && clips.iter().all(|clip| clip.is_rect());
|
||||
let clips = clips
|
||||
.into_iter()
|
||||
.map(|clip| (clip, GpuCacheHandle::new()))
|
||||
|
@ -279,6 +299,8 @@ impl ClipSources {
|
|||
clips,
|
||||
local_inner_rect,
|
||||
local_outer_rect,
|
||||
only_rectangular_clips,
|
||||
has_image_or_line_decoration_clip,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,100 @@
|
|||
/* 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::DevicePixelScale;
|
||||
use clip::{ClipChain, ClipChainNode, ClipSourcesHandle, ClipStore, ClipWorkItem};
|
||||
use clip_scroll_tree::{ClipChainIndex, SpatialNodeIndex};
|
||||
use gpu_cache::GpuCache;
|
||||
use resource_cache::ResourceCache;
|
||||
use spatial_node::SpatialNode;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ClipNode {
|
||||
/// The node that determines how this clip node is positioned.
|
||||
pub spatial_node: SpatialNodeIndex,
|
||||
|
||||
/// A handle to this clip nodes clips in the ClipStore.
|
||||
pub handle: Option<ClipSourcesHandle>,
|
||||
|
||||
/// An index to a ClipChain defined by this ClipNode's hiearchy in the display
|
||||
/// list.
|
||||
pub clip_chain_index: ClipChainIndex,
|
||||
|
||||
/// The index of the parent ClipChain of this node's hiearchical ClipChain.
|
||||
pub parent_clip_chain_index: ClipChainIndex,
|
||||
|
||||
/// A copy of the ClipChainNode this node would produce. We need to keep a copy,
|
||||
/// because the ClipChain may not contain our node if is optimized out, but API
|
||||
/// defined ClipChains will still need to access it.
|
||||
pub clip_chain_node: Option<ClipChainNode>,
|
||||
}
|
||||
|
||||
impl ClipNode {
|
||||
const EMPTY: ClipNode = ClipNode {
|
||||
spatial_node: SpatialNodeIndex(0),
|
||||
handle: None,
|
||||
clip_chain_index: ClipChainIndex(0),
|
||||
parent_clip_chain_index: ClipChainIndex(0),
|
||||
clip_chain_node: None,
|
||||
};
|
||||
|
||||
pub fn empty() -> ClipNode {
|
||||
ClipNode::EMPTY
|
||||
}
|
||||
|
||||
pub fn update(
|
||||
&mut self,
|
||||
spatial_node: &SpatialNode,
|
||||
device_pixel_scale: DevicePixelScale,
|
||||
clip_store: &mut ClipStore,
|
||||
resource_cache: &mut ResourceCache,
|
||||
gpu_cache: &mut GpuCache,
|
||||
clip_chains: &mut [ClipChain],
|
||||
) {
|
||||
let (clip_sources, weak_handle) = match self.handle {
|
||||
Some(ref handle) => (clip_store.get_mut(handle), handle.weak()),
|
||||
None => {
|
||||
warn!("Tried to process an empty clip node");
|
||||
return;
|
||||
}
|
||||
};
|
||||
clip_sources.update(gpu_cache, resource_cache, device_pixel_scale);
|
||||
|
||||
let (screen_inner_rect, screen_outer_rect) = clip_sources.get_screen_bounds(
|
||||
&spatial_node.world_content_transform,
|
||||
device_pixel_scale,
|
||||
None,
|
||||
);
|
||||
|
||||
// All clipping SpatialNodes should have outer rectangles, because they never
|
||||
// use the BorderCorner clip type and they always have at last one non-ClipOut
|
||||
// Rectangle ClipSource.
|
||||
let screen_outer_rect = screen_outer_rect
|
||||
.expect("Clipping node didn't have outer rect.");
|
||||
let local_outer_rect = clip_sources.local_outer_rect
|
||||
.expect("Clipping node didn't have outer rect.");
|
||||
|
||||
let new_node = ClipChainNode {
|
||||
work_item: ClipWorkItem {
|
||||
transform_index: self.spatial_node.transform_index(),
|
||||
clip_sources: weak_handle,
|
||||
coordinate_system_id: spatial_node.coordinate_system_id,
|
||||
},
|
||||
local_clip_rect: spatial_node
|
||||
.coordinate_system_relative_transform
|
||||
.transform_rect(&local_outer_rect),
|
||||
screen_outer_rect,
|
||||
screen_inner_rect,
|
||||
prev: None,
|
||||
};
|
||||
|
||||
let mut clip_chain =
|
||||
clip_chains[self.parent_clip_chain_index.0]
|
||||
.new_with_added_node(&new_node);
|
||||
|
||||
self.clip_chain_node = Some(new_node);
|
||||
clip_chain.parent_index = Some(self.parent_clip_chain_index);
|
||||
clip_chains[self.clip_chain_index.0] = clip_chain;
|
||||
}
|
||||
}
|
|
@ -6,13 +6,14 @@ use api::{DeviceIntRect, DevicePixelScale, ExternalScrollId, LayoutPoint, Layout
|
|||
use api::{PipelineId, ScrollClamping, ScrollLocation, ScrollNodeState};
|
||||
use api::{LayoutSize, LayoutTransform, PropertyBinding, ScrollSensitivity, WorldPoint};
|
||||
use clip::{ClipChain, ClipSourcesHandle, ClipStore};
|
||||
use clip_scroll_node::{ClipScrollNode, NodeType, SpatialNodeKind, ScrollFrameInfo, StickyFrameInfo};
|
||||
use clip_node::ClipNode;
|
||||
use gpu_cache::GpuCache;
|
||||
use gpu_types::TransformPalette;
|
||||
use gpu_types::{TransformIndex, TransformPalette};
|
||||
use internal_types::{FastHashMap, FastHashSet};
|
||||
use print_tree::{PrintTree, PrintTreePrinter};
|
||||
use resource_cache::ResourceCache;
|
||||
use scene::SceneProperties;
|
||||
use spatial_node::{ScrollFrameInfo, SpatialNode, SpatialNodeType, StickyFrameInfo};
|
||||
use util::{LayoutFastTransform, LayoutToWorldFastTransform};
|
||||
|
||||
pub type ScrollStates = FastHashMap<ExternalScrollId, ScrollFrameInfo>;
|
||||
|
@ -27,19 +28,19 @@ pub type ScrollStates = FastHashMap<ExternalScrollId, ScrollFrameInfo>;
|
|||
pub struct CoordinateSystemId(pub u32);
|
||||
|
||||
#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)]
|
||||
pub struct ClipScrollNodeIndex(pub usize);
|
||||
pub struct SpatialNodeIndex(pub usize);
|
||||
|
||||
// Used to index the smaller subset of nodes in the CST that define
|
||||
// new transform / positioning.
|
||||
// TODO(gw): In the future if we split the CST into a positioning and
|
||||
// clipping tree, this can be tidied up a bit.
|
||||
#[derive(Copy, Debug, Clone, PartialEq)]
|
||||
#[cfg_attr(feature = "capture", derive(Serialize))]
|
||||
#[cfg_attr(feature = "replay", derive(Deserialize))]
|
||||
pub struct TransformIndex(pub u32);
|
||||
#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)]
|
||||
pub struct ClipNodeIndex(pub usize);
|
||||
|
||||
const ROOT_REFERENCE_FRAME_INDEX: ClipScrollNodeIndex = ClipScrollNodeIndex(0);
|
||||
const TOPMOST_SCROLL_NODE_INDEX: ClipScrollNodeIndex = ClipScrollNodeIndex(1);
|
||||
impl SpatialNodeIndex {
|
||||
pub fn transform_index(&self) -> TransformIndex {
|
||||
TransformIndex(self.0 as u32)
|
||||
}
|
||||
}
|
||||
|
||||
const ROOT_REFERENCE_FRAME_INDEX: SpatialNodeIndex = SpatialNodeIndex(0);
|
||||
const TOPMOST_SCROLL_NODE_INDEX: SpatialNodeIndex = SpatialNodeIndex(1);
|
||||
|
||||
impl CoordinateSystemId {
|
||||
pub fn root() -> Self {
|
||||
|
@ -59,14 +60,19 @@ impl CoordinateSystemId {
|
|||
pub struct ClipChainDescriptor {
|
||||
pub index: ClipChainIndex,
|
||||
pub parent: Option<ClipChainIndex>,
|
||||
pub clips: Vec<ClipScrollNodeIndex>,
|
||||
pub clips: Vec<ClipNodeIndex>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub struct ClipChainIndex(pub usize);
|
||||
|
||||
pub struct ClipScrollTree {
|
||||
pub nodes: Vec<ClipScrollNode>,
|
||||
/// Nodes which determine the positions (offsets and transforms) for primitives
|
||||
/// and clips.
|
||||
pub spatial_nodes: Vec<SpatialNode>,
|
||||
|
||||
/// Nodes which clip primitives.
|
||||
pub clip_nodes: Vec<ClipNode>,
|
||||
|
||||
/// A Vec of all descriptors that describe ClipChains in the order in which they are
|
||||
/// encountered during display list flattening. ClipChains are expected to never be
|
||||
|
@ -82,10 +88,6 @@ pub struct ClipScrollTree {
|
|||
/// A set of pipelines which should be discarded the next time this
|
||||
/// tree is drained.
|
||||
pub pipelines_to_discard: FastHashSet<PipelineId>,
|
||||
|
||||
/// The number of nodes in the CST that are spatial. Currently, this is all
|
||||
/// nodes that are not clip nodes.
|
||||
spatial_node_count: usize,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
|
@ -95,9 +97,6 @@ pub struct TransformUpdateState {
|
|||
pub nearest_scrolling_ancestor_offset: LayoutVector2D,
|
||||
pub nearest_scrolling_ancestor_viewport: LayoutRect,
|
||||
|
||||
/// The index of the current parent's clip chain.
|
||||
pub parent_clip_chain_index: ClipChainIndex,
|
||||
|
||||
/// An id for keeping track of the axis-aligned space of this node. This is used in
|
||||
/// order to to track what kinds of clip optimizations can be done for a particular
|
||||
/// display list item, since optimizations can usually only be done among
|
||||
|
@ -116,35 +115,35 @@ pub struct TransformUpdateState {
|
|||
impl ClipScrollTree {
|
||||
pub fn new() -> Self {
|
||||
ClipScrollTree {
|
||||
nodes: Vec::new(),
|
||||
spatial_nodes: Vec::new(),
|
||||
clip_nodes: Vec::new(),
|
||||
clip_chains_descriptors: Vec::new(),
|
||||
clip_chains: vec![ClipChain::empty(&DeviceIntRect::zero())],
|
||||
pending_scroll_offsets: FastHashMap::default(),
|
||||
pipelines_to_discard: FastHashSet::default(),
|
||||
spatial_node_count: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// The root reference frame, which is the true root of the ClipScrollTree. Initially
|
||||
/// this ID is not valid, which is indicated by ```nodes``` being empty.
|
||||
pub fn root_reference_frame_index(&self) -> ClipScrollNodeIndex {
|
||||
/// this ID is not valid, which is indicated by ```spatial_nodes``` being empty.
|
||||
pub fn root_reference_frame_index(&self) -> SpatialNodeIndex {
|
||||
// TODO(mrobinson): We should eventually make this impossible to misuse.
|
||||
debug_assert!(!self.nodes.is_empty());
|
||||
debug_assert!(!self.spatial_nodes.is_empty());
|
||||
ROOT_REFERENCE_FRAME_INDEX
|
||||
}
|
||||
|
||||
/// The root scroll node which is the first child of the root reference frame.
|
||||
/// Initially this ID is not valid, which is indicated by ```nodes``` being empty.
|
||||
pub fn topmost_scroll_node_index(&self) -> ClipScrollNodeIndex {
|
||||
/// Initially this ID is not valid, which is indicated by ```spatial_nodes``` being empty.
|
||||
pub fn topmost_scroll_node_index(&self) -> SpatialNodeIndex {
|
||||
// TODO(mrobinson): We should eventually make this impossible to misuse.
|
||||
debug_assert!(self.nodes.len() >= 1);
|
||||
debug_assert!(self.spatial_nodes.len() >= 1);
|
||||
TOPMOST_SCROLL_NODE_INDEX
|
||||
}
|
||||
|
||||
pub fn get_scroll_node_state(&self) -> Vec<ScrollNodeState> {
|
||||
let mut result = vec![];
|
||||
for node in &self.nodes {
|
||||
if let NodeType::Spatial { kind: SpatialNodeKind::ScrollFrame(info), .. } = node.node_type {
|
||||
for node in &self.spatial_nodes {
|
||||
if let SpatialNodeType::ScrollFrame(info) = node.node_type {
|
||||
if let Some(id) = info.external_id {
|
||||
result.push(ScrollNodeState { id, scroll_offset: info.offset })
|
||||
}
|
||||
|
@ -155,20 +154,20 @@ impl ClipScrollTree {
|
|||
|
||||
pub fn drain(&mut self) -> ScrollStates {
|
||||
let mut scroll_states = FastHashMap::default();
|
||||
for old_node in &mut self.nodes.drain(..) {
|
||||
for old_node in &mut self.spatial_nodes.drain(..) {
|
||||
if self.pipelines_to_discard.contains(&old_node.pipeline_id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
match old_node.node_type {
|
||||
NodeType::Spatial { kind: SpatialNodeKind::ScrollFrame(info), .. } if info.external_id.is_some() => {
|
||||
SpatialNodeType::ScrollFrame(info) if info.external_id.is_some() => {
|
||||
scroll_states.insert(info.external_id.unwrap(), info);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
self.spatial_node_count = 0;
|
||||
self.clip_nodes.clear();
|
||||
self.pipelines_to_discard.clear();
|
||||
self.clip_chains = vec![ClipChain::empty(&DeviceIntRect::zero())];
|
||||
self.clip_chains_descriptors.clear();
|
||||
|
@ -181,7 +180,7 @@ impl ClipScrollTree {
|
|||
id: ExternalScrollId,
|
||||
clamp: ScrollClamping
|
||||
) -> bool {
|
||||
for node in &mut self.nodes {
|
||||
for node in &mut self.spatial_nodes {
|
||||
if node.matches_external_id(id) {
|
||||
return node.set_scroll_origin(&origin, clamp);
|
||||
}
|
||||
|
@ -193,16 +192,16 @@ impl ClipScrollTree {
|
|||
|
||||
fn find_nearest_scrolling_ancestor(
|
||||
&self,
|
||||
index: Option<ClipScrollNodeIndex>
|
||||
) -> ClipScrollNodeIndex {
|
||||
index: Option<SpatialNodeIndex>
|
||||
) -> SpatialNodeIndex {
|
||||
let index = match index {
|
||||
Some(index) => index,
|
||||
None => return self.topmost_scroll_node_index(),
|
||||
};
|
||||
|
||||
let node = &self.nodes[index.0];
|
||||
let node = &self.spatial_nodes[index.0];
|
||||
match node.node_type {
|
||||
NodeType::Spatial { kind: SpatialNodeKind::ScrollFrame(state), .. } if state.sensitive_to_input_events() => index,
|
||||
SpatialNodeType::ScrollFrame(state) if state.sensitive_to_input_events() => index,
|
||||
_ => self.find_nearest_scrolling_ancestor(node.parent)
|
||||
}
|
||||
}
|
||||
|
@ -210,13 +209,13 @@ impl ClipScrollTree {
|
|||
pub fn scroll_nearest_scrolling_ancestor(
|
||||
&mut self,
|
||||
scroll_location: ScrollLocation,
|
||||
node_index: Option<ClipScrollNodeIndex>,
|
||||
node_index: Option<SpatialNodeIndex>,
|
||||
) -> bool {
|
||||
if self.nodes.is_empty() {
|
||||
if self.spatial_nodes.is_empty() {
|
||||
return false;
|
||||
}
|
||||
let node_index = self.find_nearest_scrolling_ancestor(node_index);
|
||||
self.nodes[node_index.0].scroll(scroll_location)
|
||||
self.spatial_nodes[node_index.0].scroll(scroll_location)
|
||||
}
|
||||
|
||||
pub fn update_tree(
|
||||
|
@ -229,50 +228,54 @@ impl ClipScrollTree {
|
|||
pan: WorldPoint,
|
||||
scene_properties: &SceneProperties,
|
||||
) -> TransformPalette {
|
||||
let mut transform_palette = TransformPalette::new(self.spatial_node_count);
|
||||
let mut transform_palette = TransformPalette::new(self.spatial_nodes.len());
|
||||
if self.spatial_nodes.is_empty() {
|
||||
return transform_palette;
|
||||
}
|
||||
|
||||
if !self.nodes.is_empty() {
|
||||
self.clip_chains[0] = ClipChain::empty(screen_rect);
|
||||
self.clip_chains[0] = ClipChain::empty(screen_rect);
|
||||
|
||||
let root_reference_frame_index = self.root_reference_frame_index();
|
||||
let mut state = TransformUpdateState {
|
||||
parent_reference_frame_transform: LayoutVector2D::new(pan.x, pan.y).into(),
|
||||
parent_accumulated_scroll_offset: LayoutVector2D::zero(),
|
||||
nearest_scrolling_ancestor_offset: LayoutVector2D::zero(),
|
||||
nearest_scrolling_ancestor_viewport: LayoutRect::zero(),
|
||||
parent_clip_chain_index: ClipChainIndex(0),
|
||||
current_coordinate_system_id: CoordinateSystemId::root(),
|
||||
coordinate_system_relative_transform: LayoutFastTransform::identity(),
|
||||
invertible: true,
|
||||
};
|
||||
let mut next_coordinate_system_id = state.current_coordinate_system_id.next();
|
||||
self.update_node(
|
||||
root_reference_frame_index,
|
||||
&mut state,
|
||||
&mut next_coordinate_system_id,
|
||||
let root_reference_frame_index = self.root_reference_frame_index();
|
||||
let mut state = TransformUpdateState {
|
||||
parent_reference_frame_transform: LayoutVector2D::new(pan.x, pan.y).into(),
|
||||
parent_accumulated_scroll_offset: LayoutVector2D::zero(),
|
||||
nearest_scrolling_ancestor_offset: LayoutVector2D::zero(),
|
||||
nearest_scrolling_ancestor_viewport: LayoutRect::zero(),
|
||||
current_coordinate_system_id: CoordinateSystemId::root(),
|
||||
coordinate_system_relative_transform: LayoutFastTransform::identity(),
|
||||
invertible: true,
|
||||
};
|
||||
|
||||
let mut next_coordinate_system_id = state.current_coordinate_system_id.next();
|
||||
self.update_node(
|
||||
root_reference_frame_index,
|
||||
&mut state,
|
||||
&mut next_coordinate_system_id,
|
||||
&mut transform_palette,
|
||||
scene_properties,
|
||||
);
|
||||
|
||||
for clip_node in &mut self.clip_nodes {
|
||||
let spatial_node = &self.spatial_nodes[clip_node.spatial_node.0];
|
||||
clip_node.update(
|
||||
spatial_node,
|
||||
device_pixel_scale,
|
||||
clip_store,
|
||||
resource_cache,
|
||||
gpu_cache,
|
||||
&mut transform_palette,
|
||||
scene_properties,
|
||||
&mut self.clip_chains,
|
||||
);
|
||||
|
||||
self.build_clip_chains(screen_rect);
|
||||
}
|
||||
self.build_clip_chains(screen_rect);
|
||||
|
||||
transform_palette
|
||||
}
|
||||
|
||||
fn update_node(
|
||||
&mut self,
|
||||
node_index: ClipScrollNodeIndex,
|
||||
node_index: SpatialNodeIndex,
|
||||
state: &mut TransformUpdateState,
|
||||
next_coordinate_system_id: &mut CoordinateSystemId,
|
||||
device_pixel_scale: DevicePixelScale,
|
||||
clip_store: &mut ClipStore,
|
||||
resource_cache: &mut ResourceCache,
|
||||
gpu_cache: &mut GpuCache,
|
||||
transform_palette: &mut TransformPalette,
|
||||
scene_properties: &SceneProperties,
|
||||
) {
|
||||
|
@ -280,23 +283,13 @@ impl ClipScrollTree {
|
|||
// Restructure this to avoid the clones!
|
||||
let mut state = state.clone();
|
||||
let node_children = {
|
||||
let node = match self.nodes.get_mut(node_index.0) {
|
||||
let node = match self.spatial_nodes.get_mut(node_index.0) {
|
||||
Some(node) => node,
|
||||
None => return,
|
||||
};
|
||||
|
||||
node.update(
|
||||
&mut state,
|
||||
next_coordinate_system_id,
|
||||
device_pixel_scale,
|
||||
clip_store,
|
||||
resource_cache,
|
||||
gpu_cache,
|
||||
scene_properties,
|
||||
&mut self.clip_chains,
|
||||
);
|
||||
|
||||
node.push_gpu_data(transform_palette);
|
||||
node.update(&mut state, next_coordinate_system_id, scene_properties);
|
||||
node.push_gpu_data(transform_palette, node_index);
|
||||
|
||||
if node.children.is_empty() {
|
||||
return;
|
||||
|
@ -311,10 +304,6 @@ impl ClipScrollTree {
|
|||
child_node_index,
|
||||
&mut state,
|
||||
next_coordinate_system_id,
|
||||
device_pixel_scale,
|
||||
clip_store,
|
||||
resource_cache,
|
||||
gpu_cache,
|
||||
transform_palette,
|
||||
scene_properties,
|
||||
);
|
||||
|
@ -324,22 +313,21 @@ impl ClipScrollTree {
|
|||
pub fn build_clip_chains(&mut self, screen_rect: &DeviceIntRect) {
|
||||
for descriptor in &self.clip_chains_descriptors {
|
||||
// A ClipChain is an optional parent (which is another ClipChain) and a list of
|
||||
// ClipScrollNode clipping nodes. Here we start the ClipChain with a clone of the
|
||||
// SpatialNode clipping nodes. Here we start the ClipChain with a clone of the
|
||||
// parent's node, if necessary.
|
||||
let mut chain = match descriptor.parent {
|
||||
Some(index) => self.clip_chains[index.0].clone(),
|
||||
None => ClipChain::empty(screen_rect),
|
||||
};
|
||||
|
||||
// Now we walk through each ClipScrollNode in the vector of clip nodes and
|
||||
// extract their ClipChain nodes to construct the final list.
|
||||
// Now we walk through each ClipNode in the vector and extract their ClipChain nodes to
|
||||
// construct the final list.
|
||||
for clip_index in &descriptor.clips {
|
||||
match self.nodes[clip_index.0].node_type {
|
||||
NodeType::Clip { clip_chain_node: Some(ref node), .. } => {
|
||||
match self.clip_nodes[clip_index.0] {
|
||||
ClipNode { clip_chain_node: Some(ref node), .. } => {
|
||||
chain.add_node(node.clone());
|
||||
}
|
||||
NodeType::Clip { .. } => warn!("Found uninitialized clipping ClipScrollNode."),
|
||||
_ => warn!("Tried to create a clip chain with non-clipping node."),
|
||||
ClipNode { .. } => warn!("Found uninitialized clipping ClipNode."),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -349,9 +337,9 @@ impl ClipScrollTree {
|
|||
}
|
||||
|
||||
pub fn finalize_and_apply_pending_scroll_offsets(&mut self, old_states: ScrollStates) {
|
||||
for node in &mut self.nodes {
|
||||
for node in &mut self.spatial_nodes {
|
||||
let external_id = match node.node_type {
|
||||
NodeType::Spatial { kind: SpatialNodeKind::ScrollFrame(ScrollFrameInfo { external_id: Some(id), ..} ), .. } => id,
|
||||
SpatialNodeType::ScrollFrame(ScrollFrameInfo { external_id: Some(id), ..} ) => id,
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
|
@ -365,133 +353,131 @@ impl ClipScrollTree {
|
|||
}
|
||||
}
|
||||
|
||||
// Generate the next valid TransformIndex for the CST.
|
||||
fn next_transform_index(&mut self) -> TransformIndex {
|
||||
let transform_index = TransformIndex(self.spatial_node_count as u32);
|
||||
self.spatial_node_count += 1;
|
||||
transform_index
|
||||
}
|
||||
|
||||
pub fn add_clip_node(
|
||||
&mut self,
|
||||
index: ClipScrollNodeIndex,
|
||||
parent_index: ClipScrollNodeIndex,
|
||||
index: ClipNodeIndex,
|
||||
parent_clip_chain_index: ClipChainIndex,
|
||||
spatial_node: SpatialNodeIndex,
|
||||
handle: ClipSourcesHandle,
|
||||
pipeline_id: PipelineId,
|
||||
) -> ClipChainIndex {
|
||||
let clip_chain_index = self.allocate_clip_chain();
|
||||
let transform_index = self.nodes[parent_index.0].transform_index;
|
||||
|
||||
let node_type = NodeType::Clip {
|
||||
handle,
|
||||
let node = ClipNode {
|
||||
parent_clip_chain_index,
|
||||
spatial_node,
|
||||
handle: Some(handle),
|
||||
clip_chain_index,
|
||||
clip_chain_node: None,
|
||||
};
|
||||
let node = ClipScrollNode::new(
|
||||
pipeline_id,
|
||||
Some(parent_index),
|
||||
node_type,
|
||||
transform_index,
|
||||
);
|
||||
self.add_node(node, index);
|
||||
self.push_clip_node(node, index);
|
||||
clip_chain_index
|
||||
}
|
||||
|
||||
pub fn add_scroll_frame(
|
||||
&mut self,
|
||||
index: ClipScrollNodeIndex,
|
||||
parent_index: ClipScrollNodeIndex,
|
||||
index: SpatialNodeIndex,
|
||||
parent_index: SpatialNodeIndex,
|
||||
external_id: Option<ExternalScrollId>,
|
||||
pipeline_id: PipelineId,
|
||||
frame_rect: &LayoutRect,
|
||||
content_size: &LayoutSize,
|
||||
scroll_sensitivity: ScrollSensitivity,
|
||||
) {
|
||||
let node = ClipScrollNode::new_scroll_frame(
|
||||
let node = SpatialNode::new_scroll_frame(
|
||||
pipeline_id,
|
||||
parent_index,
|
||||
external_id,
|
||||
frame_rect,
|
||||
content_size,
|
||||
scroll_sensitivity,
|
||||
self.next_transform_index(),
|
||||
);
|
||||
self.add_node(node, index);
|
||||
self.add_spatial_node(node, index);
|
||||
}
|
||||
|
||||
pub fn add_reference_frame(
|
||||
&mut self,
|
||||
index: ClipScrollNodeIndex,
|
||||
parent_index: Option<ClipScrollNodeIndex>,
|
||||
index: SpatialNodeIndex,
|
||||
parent_index: Option<SpatialNodeIndex>,
|
||||
source_transform: Option<PropertyBinding<LayoutTransform>>,
|
||||
source_perspective: Option<LayoutTransform>,
|
||||
origin_in_parent_reference_frame: LayoutVector2D,
|
||||
pipeline_id: PipelineId,
|
||||
) {
|
||||
let node = ClipScrollNode::new_reference_frame(
|
||||
let node = SpatialNode::new_reference_frame(
|
||||
parent_index,
|
||||
source_transform,
|
||||
source_perspective,
|
||||
origin_in_parent_reference_frame,
|
||||
pipeline_id,
|
||||
self.next_transform_index(),
|
||||
);
|
||||
self.add_node(node, index);
|
||||
self.add_spatial_node(node, index);
|
||||
}
|
||||
|
||||
pub fn add_sticky_frame(
|
||||
&mut self,
|
||||
index: ClipScrollNodeIndex,
|
||||
parent_index: ClipScrollNodeIndex,
|
||||
index: SpatialNodeIndex,
|
||||
parent_index: SpatialNodeIndex,
|
||||
sticky_frame_info: StickyFrameInfo,
|
||||
pipeline_id: PipelineId,
|
||||
) {
|
||||
let node = ClipScrollNode::new_sticky_frame(
|
||||
let node = SpatialNode::new_sticky_frame(
|
||||
parent_index,
|
||||
sticky_frame_info,
|
||||
pipeline_id,
|
||||
self.next_transform_index(),
|
||||
);
|
||||
self.add_node(node, index);
|
||||
self.add_spatial_node(node, index);
|
||||
}
|
||||
|
||||
pub fn add_clip_chain_descriptor(
|
||||
&mut self,
|
||||
parent: Option<ClipChainIndex>,
|
||||
clips: Vec<ClipScrollNodeIndex>
|
||||
clips: Vec<ClipNodeIndex>
|
||||
) -> ClipChainIndex {
|
||||
let index = self.allocate_clip_chain();
|
||||
self.clip_chains_descriptors.push(ClipChainDescriptor { index, parent, clips });
|
||||
index
|
||||
}
|
||||
|
||||
pub fn add_node(&mut self, node: ClipScrollNode, index: ClipScrollNodeIndex) {
|
||||
// When the parent node is None this means we are adding the root.
|
||||
if let Some(parent_index) = node.parent {
|
||||
self.nodes[parent_index.0].add_child(index);
|
||||
}
|
||||
|
||||
if index.0 == self.nodes.len() {
|
||||
self.nodes.push(node);
|
||||
pub fn push_clip_node(&mut self, node: ClipNode, index: ClipNodeIndex) {
|
||||
if index.0 == self.clip_nodes.len() {
|
||||
self.clip_nodes.push(node);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if let Some(empty_node) = self.nodes.get_mut(index.0) {
|
||||
if let Some(empty_node) = self.clip_nodes.get_mut(index.0) {
|
||||
*empty_node = node;
|
||||
return
|
||||
}
|
||||
|
||||
let length_to_reserve = index.0 + 1 - self.nodes.len();
|
||||
self.nodes.reserve_exact(length_to_reserve);
|
||||
let length_to_reserve = index.0 + 1 - self.clip_nodes.len();
|
||||
self.clip_nodes.reserve_exact(length_to_reserve);
|
||||
|
||||
// We would like to use `Vec::resize` here, but the Clone trait is not supported
|
||||
// for ClipScrollNodes. We can fix this either by splitting the clip nodes out into
|
||||
// their own tree or when support is added for something like `Vec::resize_default`.
|
||||
let length_to_extend = self.nodes.len() .. index.0;
|
||||
self.nodes.extend(length_to_extend.map(|_| ClipScrollNode::empty()));
|
||||
// for ClipNodes. We can fix this either when support is added for something like
|
||||
// `Vec::resize_default`.
|
||||
let length_to_extend = self.clip_nodes.len() .. index.0;
|
||||
self.clip_nodes.extend(length_to_extend.map(|_| ClipNode::empty()));
|
||||
self.clip_nodes.push(node);
|
||||
}
|
||||
|
||||
self.nodes.push(node);
|
||||
pub fn add_spatial_node(&mut self, node: SpatialNode, index: SpatialNodeIndex) {
|
||||
// When the parent node is None this means we are adding the root.
|
||||
if let Some(parent_index) = node.parent {
|
||||
self.spatial_nodes[parent_index.0].add_child(index);
|
||||
}
|
||||
|
||||
if index.0 == self.spatial_nodes.len() {
|
||||
self.spatial_nodes.push(node);
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(empty_node) = self.spatial_nodes.get_mut(index.0) {
|
||||
*empty_node = node;
|
||||
return
|
||||
}
|
||||
|
||||
debug_assert!(index.0 > self.spatial_nodes.len() - 1);
|
||||
self.spatial_nodes.resize(index.0, SpatialNode::empty());
|
||||
self.spatial_nodes.push(node);
|
||||
}
|
||||
|
||||
pub fn discard_frame_state_for_pipeline(&mut self, pipeline_id: PipelineId) {
|
||||
|
@ -500,44 +486,29 @@ impl ClipScrollTree {
|
|||
|
||||
fn print_node<T: PrintTreePrinter>(
|
||||
&self,
|
||||
index: ClipScrollNodeIndex,
|
||||
index: SpatialNodeIndex,
|
||||
pt: &mut T,
|
||||
clip_store: &ClipStore
|
||||
) {
|
||||
let node = &self.nodes[index.0];
|
||||
let node = &self.spatial_nodes[index.0];
|
||||
match node.node_type {
|
||||
NodeType::Spatial { ref kind, .. } => {
|
||||
match *kind {
|
||||
SpatialNodeKind::StickyFrame(ref sticky_frame_info) => {
|
||||
pt.new_level(format!("StickyFrame"));
|
||||
pt.add_item(format!("index: {:?}", index));
|
||||
pt.add_item(format!("sticky info: {:?}", sticky_frame_info));
|
||||
}
|
||||
SpatialNodeKind::ScrollFrame(scrolling_info) => {
|
||||
pt.new_level(format!("ScrollFrame"));
|
||||
pt.add_item(format!("index: {:?}", index));
|
||||
pt.add_item(format!("viewport: {:?}", scrolling_info.viewport_rect));
|
||||
pt.add_item(format!("scrollable_size: {:?}", scrolling_info.scrollable_size));
|
||||
pt.add_item(format!("scroll offset: {:?}", scrolling_info.offset));
|
||||
}
|
||||
SpatialNodeKind::ReferenceFrame(ref info) => {
|
||||
pt.new_level(format!("ReferenceFrame {:?}", info.resolved_transform));
|
||||
pt.add_item(format!("index: {:?}", index));
|
||||
}
|
||||
}
|
||||
}
|
||||
NodeType::Clip { ref handle, .. } => {
|
||||
pt.new_level("Clip".to_owned());
|
||||
|
||||
SpatialNodeType::StickyFrame(ref sticky_frame_info) => {
|
||||
pt.new_level(format!("StickyFrame"));
|
||||
pt.add_item(format!("index: {:?}", index));
|
||||
let clips = clip_store.get(handle).clips();
|
||||
pt.new_level(format!("Clip Sources [{}]", clips.len()));
|
||||
for source in clips {
|
||||
pt.add_item(format!("{:?}", source));
|
||||
}
|
||||
pt.end_level();
|
||||
pt.add_item(format!("sticky info: {:?}", sticky_frame_info));
|
||||
}
|
||||
NodeType::Empty => unreachable!("Empty node remaining in ClipScrollTree."),
|
||||
SpatialNodeType::ScrollFrame(scrolling_info) => {
|
||||
pt.new_level(format!("ScrollFrame"));
|
||||
pt.add_item(format!("index: {:?}", index));
|
||||
pt.add_item(format!("viewport: {:?}", scrolling_info.viewport_rect));
|
||||
pt.add_item(format!("scrollable_size: {:?}", scrolling_info.scrollable_size));
|
||||
pt.add_item(format!("scroll offset: {:?}", scrolling_info.offset));
|
||||
}
|
||||
SpatialNodeType::ReferenceFrame(ref info) => {
|
||||
pt.new_level(format!("ReferenceFrame {:?}", info.resolved_transform));
|
||||
pt.add_item(format!("index: {:?}", index));
|
||||
}
|
||||
SpatialNodeType::Empty => unreachable!("Empty node remaining in ClipScrollTree."),
|
||||
}
|
||||
|
||||
pt.add_item(format!("world_viewport_transform: {:?}", node.world_viewport_transform));
|
||||
|
@ -553,14 +524,14 @@ impl ClipScrollTree {
|
|||
|
||||
#[allow(dead_code)]
|
||||
pub fn print(&self, clip_store: &ClipStore) {
|
||||
if !self.nodes.is_empty() {
|
||||
if !self.spatial_nodes.is_empty() {
|
||||
let mut pt = PrintTree::new("clip_scroll tree");
|
||||
self.print_with(clip_store, &mut pt);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn print_with<T: PrintTreePrinter>(&self, clip_store: &ClipStore, pt: &mut T) {
|
||||
if !self.nodes.is_empty() {
|
||||
if !self.spatial_nodes.is_empty() {
|
||||
self.print_node(self.root_reference_frame_index(), pt, clip_store);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,8 +14,7 @@ use api::{PropertyBinding, ReferenceFrame, RepeatMode, ScrollFrameDisplayItem, S
|
|||
use api::{Shadow, SpecificDisplayItem, StackingContext, StickyFrameDisplayItem, TexelRect};
|
||||
use api::{TransformStyle, YuvColorSpace, YuvData};
|
||||
use clip::{ClipRegion, ClipSource, ClipSources, ClipStore};
|
||||
use clip_scroll_node::{NodeType, SpatialNodeKind, StickyFrameInfo};
|
||||
use clip_scroll_tree::{ClipChainIndex, ClipScrollNodeIndex, ClipScrollTree};
|
||||
use clip_scroll_tree::{ClipChainIndex, ClipNodeIndex, ClipScrollTree, SpatialNodeIndex};
|
||||
use euclid::vec2;
|
||||
use frame_builder::{ChasePrimitive, FrameBuilder, FrameBuilderConfig};
|
||||
use glyph_rasterizer::FontInstance;
|
||||
|
@ -33,6 +32,7 @@ use render_backend::{DocumentView};
|
|||
use resource_cache::{FontInstanceMap, ImageRequest};
|
||||
use scene::{Scene, ScenePipeline, StackingContextHelpers};
|
||||
use scene_builder::{BuiltScene, SceneRequest};
|
||||
use spatial_node::{SpatialNodeType, StickyFrameInfo};
|
||||
use std::{f32, mem, usize};
|
||||
use tiling::{CompositeOps, ScrollbarPrimitive};
|
||||
use util::{MaxRect, RectHelpers, recycle_vec};
|
||||
|
@ -44,9 +44,16 @@ static DEFAULT_SCROLLBAR_COLOR: ColorF = ColorF {
|
|||
a: 0.6,
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct PipelineOffset {
|
||||
pipeline: PipelineId,
|
||||
spatial_offset: usize,
|
||||
clip_offset: usize,
|
||||
}
|
||||
|
||||
/// A data structure that keeps track of mapping between API ClipIds and the indices used
|
||||
/// internally in the ClipScrollTree to avoid having to do HashMap lookups. ClipIdToIndexMapper is
|
||||
/// responsible for mapping both ClipId to ClipChainIndex and ClipId to ClipScrollNodeIndex. We
|
||||
/// responsible for mapping both ClipId to ClipChainIndex and ClipId to SpatialNodeIndex. We
|
||||
/// also include two small LRU caches. Currently the caches are small (1 entry), but in the future
|
||||
/// we could use uluru here to do something more involved.
|
||||
#[derive(Default)]
|
||||
|
@ -60,18 +67,23 @@ pub struct ClipIdToIndexMapper {
|
|||
/// HashMap lookups.
|
||||
cached_clip_chain_index: Option<(ClipId, ClipChainIndex)>,
|
||||
|
||||
/// The offset in the ClipScrollTree's array of ClipScrollNodes for a particular pipeline.
|
||||
/// This is used to convert a ClipId into a ClipScrollNodeIndex.
|
||||
pipeline_offsets: FastHashMap<PipelineId, usize>,
|
||||
/// The offset in the ClipScrollTree's array of SpatialNodes and ClipNodes for a particular
|
||||
/// pipeline. This is used to convert ClipIds into SpatialNodeIndex or ClipNodeIndex.
|
||||
pipeline_offsets: FastHashMap<PipelineId, PipelineOffset>,
|
||||
|
||||
/// The last mapped pipeline offset for this mapper. This is used to avoid having to
|
||||
/// consult `pipeline_offsets` repeatedly when flattening the display list.
|
||||
cached_pipeline_offset: Option<(PipelineId, usize)>,
|
||||
cached_pipeline_offset: Option<PipelineOffset>,
|
||||
|
||||
/// The next available pipeline offset for ClipScrollNodeIndex. When we encounter a pipeline
|
||||
/// we will use this value and increment it by the total number of ClipScrollNodes in the
|
||||
/// The next available pipeline offset for ClipNodeIndex. When we encounter a pipeline
|
||||
/// we will use this value and increment it by the total number of clip nodes in the
|
||||
/// pipeline's display list.
|
||||
next_available_offset: usize,
|
||||
next_available_clip_offset: usize,
|
||||
|
||||
/// The next available pipeline offset for SpatialNodeIndex. When we encounter a pipeline
|
||||
/// we will use this value and increment it by the total number of spatial nodes in the
|
||||
/// pipeline's display list.
|
||||
next_available_spatial_offset: usize,
|
||||
}
|
||||
|
||||
impl ClipIdToIndexMapper {
|
||||
|
@ -101,39 +113,46 @@ impl ClipIdToIndexMapper {
|
|||
index
|
||||
}
|
||||
|
||||
pub fn map_clip_and_scroll(&mut self, info: &ClipAndScrollInfo) -> ScrollNodeAndClipChain {
|
||||
ScrollNodeAndClipChain::new(
|
||||
self.get_node_index(info.scroll_node_id),
|
||||
self.get_clip_chain_index_and_cache_result(&info.clip_node_id())
|
||||
)
|
||||
}
|
||||
|
||||
pub fn simple_scroll_and_clip_chain(&mut self, id: &ClipId) -> ScrollNodeAndClipChain {
|
||||
self.map_clip_and_scroll(&ClipAndScrollInfo::simple(*id))
|
||||
}
|
||||
|
||||
pub fn initialize_for_pipeline(&mut self, pipeline: &ScenePipeline) {
|
||||
debug_assert!(!self.pipeline_offsets.contains_key(&pipeline.pipeline_id));
|
||||
self.pipeline_offsets.insert(pipeline.pipeline_id, self.next_available_offset);
|
||||
self.next_available_offset += pipeline.display_list.total_clip_ids();
|
||||
self.pipeline_offsets.insert(
|
||||
pipeline.pipeline_id,
|
||||
PipelineOffset {
|
||||
pipeline: pipeline.pipeline_id,
|
||||
spatial_offset: self.next_available_spatial_offset,
|
||||
clip_offset: self.next_available_clip_offset,
|
||||
}
|
||||
);
|
||||
|
||||
self.next_available_clip_offset += pipeline.display_list.total_clip_nodes();
|
||||
self.next_available_spatial_offset += pipeline.display_list.total_spatial_nodes();
|
||||
}
|
||||
|
||||
pub fn get_node_index(&mut self, id: ClipId) -> ClipScrollNodeIndex {
|
||||
let (index, pipeline_id) = match id {
|
||||
ClipId::Clip(index, pipeline_id) => (index, pipeline_id),
|
||||
ClipId::ClipChain(_) => panic!("Tried to use ClipChain as scroll node."),
|
||||
};
|
||||
|
||||
let pipeline_offset = match self.cached_pipeline_offset {
|
||||
Some((last_used_id, offset)) if last_used_id == pipeline_id => offset,
|
||||
pub fn get_pipeline_offet<'a>(&'a mut self, id: PipelineId) -> &'a PipelineOffset {
|
||||
match self.cached_pipeline_offset {
|
||||
Some(ref offset) if offset.pipeline == id => offset,
|
||||
_ => {
|
||||
let offset = self.pipeline_offsets[&pipeline_id];
|
||||
self.cached_pipeline_offset = Some((pipeline_id, offset));
|
||||
let offset = &self.pipeline_offsets[&id];
|
||||
self.cached_pipeline_offset = Some(*offset);
|
||||
offset
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
ClipScrollNodeIndex(pipeline_offset + index)
|
||||
pub fn get_clip_node_index(&mut self, id: ClipId) -> ClipNodeIndex {
|
||||
match id {
|
||||
ClipId::Clip(index, pipeline_id) => {
|
||||
let pipeline_offset = self.get_pipeline_offet(pipeline_id);
|
||||
ClipNodeIndex(pipeline_offset.clip_offset + index)
|
||||
}
|
||||
ClipId::Spatial(..) => {
|
||||
// We could theoretically map back to the containing clip node with the current
|
||||
// design, but we will eventually fully separate out clipping from spatial nodes
|
||||
// in the display list. We don't ever need to do this anyway.
|
||||
panic!("Tried to use positioning node as clip node.");
|
||||
}
|
||||
ClipId::ClipChain(_) => panic!("Tried to use ClipChain as scroll node."),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -160,7 +179,7 @@ pub struct DisplayListFlattener<'a> {
|
|||
|
||||
/// A stack of scroll nodes used during display list processing to properly
|
||||
/// parent new scroll nodes.
|
||||
reference_frame_stack: Vec<(ClipId, ClipScrollNodeIndex)>,
|
||||
reference_frame_stack: Vec<(ClipId, SpatialNodeIndex)>,
|
||||
|
||||
/// A stack of stacking context properties.
|
||||
sc_stack: Vec<FlattenedStackingContext>,
|
||||
|
@ -273,14 +292,12 @@ impl<'a> DisplayListFlattener<'a> {
|
|||
|
||||
fn flatten_root(&mut self, pipeline: &'a ScenePipeline, frame_size: &LayoutSize) {
|
||||
let pipeline_id = pipeline.pipeline_id;
|
||||
let reference_frame_info = self.id_to_index_mapper.simple_scroll_and_clip_chain(
|
||||
&ClipId::root_reference_frame(pipeline_id)
|
||||
let reference_frame_info = self.simple_scroll_and_clip_chain(
|
||||
&ClipId::root_reference_frame(pipeline_id),
|
||||
);
|
||||
|
||||
let root_scroll_node = ClipId::root_scroll_node(pipeline_id);
|
||||
let scroll_frame_info = self.id_to_index_mapper.simple_scroll_and_clip_chain(
|
||||
&root_scroll_node,
|
||||
);
|
||||
let scroll_frame_info = self.simple_scroll_and_clip_chain(&root_scroll_node);
|
||||
|
||||
self.push_stacking_context(
|
||||
pipeline_id,
|
||||
|
@ -380,7 +397,7 @@ impl<'a> DisplayListFlattener<'a> {
|
|||
info.previously_applied_offset,
|
||||
);
|
||||
|
||||
let index = self.id_to_index_mapper.get_node_index(info.id);
|
||||
let index = self.get_spatial_node_index_for_clip_id(info.id);
|
||||
self.clip_scroll_tree.add_sticky_frame(
|
||||
index,
|
||||
clip_and_scroll.scroll_node_id, /* parent id */
|
||||
|
@ -407,7 +424,7 @@ impl<'a> DisplayListFlattener<'a> {
|
|||
);
|
||||
// Just use clip rectangle as the frame rect for this scroll frame.
|
||||
// This is useful when calculating scroll extents for the
|
||||
// ClipScrollNode::scroll(..) API as well as for properly setting sticky
|
||||
// SpatialNode::scroll(..) API as well as for properly setting sticky
|
||||
// positioning offsets.
|
||||
let frame_rect = item.clip_rect().translate(reference_frame_relative_offset);
|
||||
let content_rect = item.rect().translate(reference_frame_relative_offset);
|
||||
|
@ -556,7 +573,7 @@ impl<'a> DisplayListFlattener<'a> {
|
|||
reference_frame_relative_offset: LayoutVector2D,
|
||||
) -> Option<BuiltDisplayListIter<'a>> {
|
||||
let clip_and_scroll_ids = item.clip_and_scroll();
|
||||
let clip_and_scroll = self.id_to_index_mapper.map_clip_and_scroll(&clip_and_scroll_ids);
|
||||
let clip_and_scroll = self.map_clip_and_scroll(&clip_and_scroll_ids);
|
||||
|
||||
let prim_info = item.get_layout_primitive_info(&reference_frame_relative_offset);
|
||||
match *item.item() {
|
||||
|
@ -717,7 +734,7 @@ impl<'a> DisplayListFlattener<'a> {
|
|||
SpecificDisplayItem::ClipChain(ref info) => {
|
||||
let items = self.get_clip_chain_items(pipeline_id, item.clip_chain_items())
|
||||
.iter()
|
||||
.map(|id| self.id_to_index_mapper.get_node_index(*id))
|
||||
.map(|id| self.id_to_index_mapper.get_clip_node_index(*id))
|
||||
.collect();
|
||||
let parent = info.parent.map(|id|
|
||||
self.id_to_index_mapper.get_clip_chain_index(&ClipId::ClipChain(id))
|
||||
|
@ -883,7 +900,7 @@ impl<'a> DisplayListFlattener<'a> {
|
|||
transform_style: TransformStyle,
|
||||
is_backface_visible: bool,
|
||||
is_pipeline_root: bool,
|
||||
positioning_node: ClipId,
|
||||
spatial_node: ClipId,
|
||||
clipping_node: Option<ClipId>,
|
||||
glyph_raster_space: GlyphRasterSpace,
|
||||
) {
|
||||
|
@ -892,7 +909,7 @@ impl<'a> DisplayListFlattener<'a> {
|
|||
None => ClipChainIndex(0), // This means no clipping.
|
||||
};
|
||||
let clip_and_scroll = ScrollNodeAndClipChain::new(
|
||||
self.id_to_index_mapper.get_node_index(positioning_node),
|
||||
self.get_spatial_node_index_for_clip_id(spatial_node),
|
||||
clip_chain_id
|
||||
);
|
||||
|
||||
|
@ -1191,9 +1208,9 @@ impl<'a> DisplayListFlattener<'a> {
|
|||
source_transform: Option<PropertyBinding<LayoutTransform>>,
|
||||
source_perspective: Option<LayoutTransform>,
|
||||
origin_in_parent_reference_frame: LayoutVector2D,
|
||||
) -> ClipScrollNodeIndex {
|
||||
let index = self.id_to_index_mapper.get_node_index(reference_frame_id);
|
||||
let parent_index = parent_id.map(|id| self.id_to_index_mapper.get_node_index(id));
|
||||
) -> SpatialNodeIndex {
|
||||
let index = self.get_spatial_node_index_for_clip_id(reference_frame_id);
|
||||
let parent_index = parent_id.map(|id| self.get_spatial_node_index_for_clip_id(id));
|
||||
self.clip_scroll_tree.add_reference_frame(
|
||||
index,
|
||||
parent_index,
|
||||
|
@ -1212,7 +1229,7 @@ impl<'a> DisplayListFlattener<'a> {
|
|||
index
|
||||
}
|
||||
|
||||
pub fn current_reference_frame_index(&self) -> ClipScrollNodeIndex {
|
||||
pub fn current_reference_frame_index(&self) -> SpatialNodeIndex {
|
||||
self.reference_frame_stack.last().unwrap().1
|
||||
}
|
||||
|
||||
|
@ -1223,8 +1240,8 @@ impl<'a> DisplayListFlattener<'a> {
|
|||
) {
|
||||
let viewport_offset = (inner_rect.origin.to_vector().to_f32() / device_pixel_scale).round();
|
||||
let root_id = self.clip_scroll_tree.root_reference_frame_index();
|
||||
let root_node = &mut self.clip_scroll_tree.nodes[root_id.0];
|
||||
if let NodeType::Spatial { kind: SpatialNodeKind::ReferenceFrame(ref mut info), .. } = root_node.node_type {
|
||||
let root_node = &mut self.clip_scroll_tree.spatial_nodes[root_id.0];
|
||||
if let SpatialNodeType::ReferenceFrame(ref mut info) = root_node.node_type {
|
||||
info.resolved_transform =
|
||||
LayoutVector2D::new(viewport_offset.x, viewport_offset.y).into();
|
||||
}
|
||||
|
@ -1261,19 +1278,21 @@ impl<'a> DisplayListFlattener<'a> {
|
|||
new_node_id: ClipId,
|
||||
parent_id: ClipId,
|
||||
clip_region: ClipRegion,
|
||||
) -> ClipScrollNodeIndex {
|
||||
) {
|
||||
let clip_sources = ClipSources::from(clip_region);
|
||||
let handle = self.clip_store.insert(clip_sources);
|
||||
|
||||
let node_index = self.id_to_index_mapper.get_node_index(new_node_id);
|
||||
let node_index = self.id_to_index_mapper.get_clip_node_index(new_node_id);
|
||||
let parent_clip_chain_index =
|
||||
self.id_to_index_mapper.get_clip_chain_index_and_cache_result(&parent_id);
|
||||
let spatial_node = self.get_spatial_node_index_for_clip_id(parent_id);
|
||||
let clip_chain_index = self.clip_scroll_tree.add_clip_node(
|
||||
node_index,
|
||||
self.id_to_index_mapper.get_node_index(parent_id),
|
||||
parent_clip_chain_index,
|
||||
spatial_node,
|
||||
handle,
|
||||
new_node_id.pipeline_id(),
|
||||
);
|
||||
self.id_to_index_mapper.add_clip_chain(new_node_id, clip_chain_index);
|
||||
node_index
|
||||
}
|
||||
|
||||
pub fn add_scroll_frame(
|
||||
|
@ -1285,11 +1304,12 @@ impl<'a> DisplayListFlattener<'a> {
|
|||
frame_rect: &LayoutRect,
|
||||
content_size: &LayoutSize,
|
||||
scroll_sensitivity: ScrollSensitivity,
|
||||
) -> ClipScrollNodeIndex {
|
||||
let node_index = self.id_to_index_mapper.get_node_index(new_node_id);
|
||||
) -> SpatialNodeIndex {
|
||||
let node_index = self.get_spatial_node_index_for_clip_id(new_node_id);
|
||||
let parent_node_index = self.get_spatial_node_index_for_clip_id(parent_id);
|
||||
self.clip_scroll_tree.add_scroll_frame(
|
||||
node_index,
|
||||
self.id_to_index_mapper.get_node_index(parent_id),
|
||||
parent_node_index,
|
||||
external_id,
|
||||
pipeline_id,
|
||||
frame_rect,
|
||||
|
@ -1933,6 +1953,31 @@ impl<'a> DisplayListFlattener<'a> {
|
|||
PrimitiveContainer::Brush(prim),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn map_clip_and_scroll(&mut self, info: &ClipAndScrollInfo) -> ScrollNodeAndClipChain {
|
||||
ScrollNodeAndClipChain::new(
|
||||
self.get_spatial_node_index_for_clip_id(info.scroll_node_id),
|
||||
self.id_to_index_mapper.get_clip_chain_index_and_cache_result(&info.clip_node_id())
|
||||
)
|
||||
}
|
||||
|
||||
pub fn simple_scroll_and_clip_chain(&mut self, id: &ClipId) -> ScrollNodeAndClipChain {
|
||||
self.map_clip_and_scroll(&ClipAndScrollInfo::simple(*id))
|
||||
}
|
||||
|
||||
pub fn get_spatial_node_index_for_clip_id(&mut self, id: ClipId,) -> SpatialNodeIndex {
|
||||
match id {
|
||||
ClipId::Spatial(index, pipeline_id) => {
|
||||
let pipeline_offset = self.id_to_index_mapper.get_pipeline_offet(pipeline_id);
|
||||
SpatialNodeIndex(pipeline_offset.spatial_offset + index)
|
||||
}
|
||||
ClipId::Clip(..) => {
|
||||
let clip_node_index = self.id_to_index_mapper.get_clip_node_index(id);
|
||||
self.clip_scroll_tree.clip_nodes[clip_node_index.0].spatial_node
|
||||
}
|
||||
ClipId::ClipChain(_) => panic!("Tried to use ClipChain as scroll node."),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_scene(config: &FrameBuilderConfig, request: SceneRequest) -> BuiltScene {
|
||||
|
@ -1987,4 +2032,4 @@ struct FlattenedStackingContext {
|
|||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ScrollbarInfo(pub ClipScrollNodeIndex, pub LayoutRect);
|
||||
pub struct ScrollbarInfo(pub SpatialNodeIndex, pub LayoutRect);
|
||||
|
|
|
@ -6,11 +6,10 @@ use api::{BuiltDisplayList, ColorF, DeviceIntPoint, DeviceIntRect, DevicePixelSc
|
|||
use api::{DeviceUintPoint, DeviceUintRect, DeviceUintSize, DocumentLayer, FontRenderMode};
|
||||
use api::{LayoutPoint, LayoutRect, LayoutSize, PipelineId, WorldPoint};
|
||||
use clip::{ClipChain, ClipStore};
|
||||
use clip_scroll_node::{ClipScrollNode};
|
||||
use clip_scroll_tree::{ClipScrollNodeIndex, ClipScrollTree};
|
||||
use clip_scroll_tree::{ClipScrollTree, SpatialNodeIndex};
|
||||
use display_list_flattener::{DisplayListFlattener};
|
||||
use gpu_cache::GpuCache;
|
||||
use gpu_types::{PrimitiveHeaders, TransformData, UvRectKind};
|
||||
use gpu_types::{PrimitiveHeaders, TransformData, TransformIndex, UvRectKind};
|
||||
use hit_test::{HitTester, HitTestingRun};
|
||||
use internal_types::{FastHashMap};
|
||||
use picture::PictureSurface;
|
||||
|
@ -20,6 +19,7 @@ use render_backend::FrameId;
|
|||
use render_task::{RenderTask, RenderTaskId, RenderTaskLocation, RenderTaskTree};
|
||||
use resource_cache::{ResourceCache};
|
||||
use scene::{ScenePipeline, SceneProperties};
|
||||
use spatial_node::SpatialNode;
|
||||
use std::{mem, f32};
|
||||
use std::sync::Arc;
|
||||
use tiling::{Frame, RenderPass, RenderPassKind, RenderTargetContext};
|
||||
|
@ -88,7 +88,7 @@ pub struct FrameBuildingState<'a> {
|
|||
pub struct PictureContext<'a> {
|
||||
pub pipeline_id: PipelineId,
|
||||
pub prim_runs: Vec<PrimitiveRun>,
|
||||
pub original_reference_frame_index: Option<ClipScrollNodeIndex>,
|
||||
pub original_reference_frame_index: Option<SpatialNodeIndex>,
|
||||
pub display_list: &'a BuiltDisplayList,
|
||||
pub inv_world_transform: Option<WorldToLayoutFastTransform>,
|
||||
pub apply_local_clip_rect: bool,
|
||||
|
@ -114,20 +114,23 @@ impl PictureState {
|
|||
|
||||
pub struct PrimitiveRunContext<'a> {
|
||||
pub clip_chain: &'a ClipChain,
|
||||
pub scroll_node: &'a ClipScrollNode,
|
||||
pub scroll_node: &'a SpatialNode,
|
||||
pub transform_index: TransformIndex,
|
||||
pub local_clip_rect: LayoutRect,
|
||||
}
|
||||
|
||||
impl<'a> PrimitiveRunContext<'a> {
|
||||
pub fn new(
|
||||
clip_chain: &'a ClipChain,
|
||||
scroll_node: &'a ClipScrollNode,
|
||||
scroll_node: &'a SpatialNode,
|
||||
transform_index: TransformIndex,
|
||||
local_clip_rect: LayoutRect,
|
||||
) -> Self {
|
||||
PrimitiveRunContext {
|
||||
clip_chain,
|
||||
scroll_node,
|
||||
local_clip_rect,
|
||||
transform_index,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -195,11 +198,11 @@ impl FrameBuilder {
|
|||
}
|
||||
|
||||
// The root picture is always the first one added.
|
||||
let root_clip_scroll_node =
|
||||
&clip_scroll_tree.nodes[clip_scroll_tree.root_reference_frame_index().0];
|
||||
let root_spatial_node =
|
||||
&clip_scroll_tree.spatial_nodes[clip_scroll_tree.root_reference_frame_index().0];
|
||||
|
||||
let display_list = &pipelines
|
||||
.get(&root_clip_scroll_node.pipeline_id)
|
||||
.get(&root_spatial_node.pipeline_id)
|
||||
.expect("No display list?")
|
||||
.display_list;
|
||||
|
||||
|
@ -229,7 +232,7 @@ impl FrameBuilder {
|
|||
};
|
||||
|
||||
let pic_context = PictureContext {
|
||||
pipeline_id: root_clip_scroll_node.pipeline_id,
|
||||
pipeline_id: root_spatial_node.pipeline_id,
|
||||
prim_runs: mem::replace(&mut self.prim_store.pictures[0].runs, Vec::new()),
|
||||
original_reference_frame_index: None,
|
||||
display_list,
|
||||
|
@ -270,7 +273,7 @@ impl FrameBuilder {
|
|||
|
||||
for scrollbar_prim in &self.scrollbar_prims {
|
||||
let metadata = &mut self.prim_store.cpu_metadata[scrollbar_prim.prim_index.0];
|
||||
let scroll_frame = &clip_scroll_tree.nodes[scrollbar_prim.scroll_frame_index.0];
|
||||
let scroll_frame = &clip_scroll_tree.spatial_nodes[scrollbar_prim.scroll_frame_index.0];
|
||||
|
||||
// Invalidate what's in the cache so it will get rebuilt.
|
||||
gpu_cache.invalidate(&metadata.gpu_location);
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
|
||||
use api::{DevicePoint, DeviceSize, DeviceRect, LayoutRect, LayoutToWorldTransform};
|
||||
use api::{PremultipliedColorF, WorldToLayoutTransform};
|
||||
use clip_scroll_tree::TransformIndex;
|
||||
use gpu_cache::{GpuCacheAddress, GpuDataRequest};
|
||||
use prim_store::{EdgeAaSegmentMask};
|
||||
use render_task::RenderTaskAddress;
|
||||
|
@ -400,6 +399,11 @@ pub struct TransformPalette {
|
|||
metadata: Vec<TransformMetadata>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Debug, Clone, PartialEq)]
|
||||
#[cfg_attr(feature = "capture", derive(Serialize))]
|
||||
#[cfg_attr(feature = "replay", derive(Deserialize))]
|
||||
pub struct TransformIndex(pub u32);
|
||||
|
||||
impl TransformPalette {
|
||||
pub fn new(spatial_node_count: usize) -> TransformPalette {
|
||||
TransformPalette {
|
||||
|
|
|
@ -5,8 +5,8 @@
|
|||
use api::{BorderRadius, ClipMode, HitTestFlags, HitTestItem, HitTestResult, ItemTag, LayoutPoint};
|
||||
use api::{LayoutPrimitiveInfo, LayoutRect, PipelineId, WorldPoint};
|
||||
use clip::{ClipSource, ClipStore, rounded_rectangle_contains_point};
|
||||
use clip_scroll_node::{ClipScrollNode, NodeType};
|
||||
use clip_scroll_tree::{ClipChainIndex, ClipScrollNodeIndex, ClipScrollTree};
|
||||
use clip_node::ClipNode;
|
||||
use clip_scroll_tree::{ClipChainIndex, ClipNodeIndex, SpatialNodeIndex, ClipScrollTree};
|
||||
use internal_types::FastHashMap;
|
||||
use prim_store::ScrollNodeAndClipChain;
|
||||
use util::LayoutToWorldFastTransform;
|
||||
|
@ -14,14 +14,10 @@ use util::LayoutToWorldFastTransform;
|
|||
/// A copy of important clip scroll node data to use during hit testing. This a copy of
|
||||
/// data from the ClipScrollTree that will persist as a new frame is under construction,
|
||||
/// allowing hit tests consistent with the currently rendered frame.
|
||||
pub struct HitTestClipScrollNode {
|
||||
pub struct HitTestSpatialNode {
|
||||
/// The pipeline id of this node.
|
||||
pipeline_id: PipelineId,
|
||||
|
||||
/// A particular point must be inside all of these regions to be considered clipped in
|
||||
/// for the purposes of a hit test.
|
||||
regions: Vec<HitTestRegion>,
|
||||
|
||||
/// World transform for content transformed by this node.
|
||||
world_content_transform: LayoutToWorldFastTransform,
|
||||
|
||||
|
@ -29,6 +25,15 @@ pub struct HitTestClipScrollNode {
|
|||
world_viewport_transform: LayoutToWorldFastTransform,
|
||||
}
|
||||
|
||||
pub struct HitTestClipNode {
|
||||
/// The positioning node for this clip node.
|
||||
spatial_node: SpatialNodeIndex,
|
||||
|
||||
/// A particular point must be inside all of these regions to be considered clipped in
|
||||
/// for the purposes of a hit test.
|
||||
regions: Vec<HitTestRegion>,
|
||||
}
|
||||
|
||||
/// A description of a clip chain in the HitTester. This is used to describe
|
||||
/// hierarchical clip scroll nodes as well as ClipChains, so that they can be
|
||||
/// handled the same way during hit testing. Once we represent all ClipChains
|
||||
|
@ -37,7 +42,7 @@ pub struct HitTestClipScrollNode {
|
|||
#[derive(Clone)]
|
||||
struct HitTestClipChainDescriptor {
|
||||
parent: Option<ClipChainIndex>,
|
||||
clips: Vec<ClipScrollNodeIndex>,
|
||||
clips: Vec<ClipNodeIndex>,
|
||||
}
|
||||
|
||||
impl HitTestClipChainDescriptor {
|
||||
|
@ -93,9 +98,10 @@ impl HitTestRegion {
|
|||
|
||||
pub struct HitTester {
|
||||
runs: Vec<HitTestingRun>,
|
||||
nodes: Vec<HitTestClipScrollNode>,
|
||||
spatial_nodes: Vec<HitTestSpatialNode>,
|
||||
clip_nodes: Vec<HitTestClipNode>,
|
||||
clip_chains: Vec<HitTestClipChainDescriptor>,
|
||||
pipeline_root_nodes: FastHashMap<PipelineId, ClipScrollNodeIndex>,
|
||||
pipeline_root_nodes: FastHashMap<PipelineId, SpatialNodeIndex>,
|
||||
}
|
||||
|
||||
impl HitTester {
|
||||
|
@ -106,7 +112,8 @@ impl HitTester {
|
|||
) -> HitTester {
|
||||
let mut hit_tester = HitTester {
|
||||
runs: runs.clone(),
|
||||
nodes: Vec::new(),
|
||||
spatial_nodes: Vec::new(),
|
||||
clip_nodes: Vec::new(),
|
||||
clip_chains: Vec::new(),
|
||||
pipeline_root_nodes: FastHashMap::default(),
|
||||
};
|
||||
|
@ -119,33 +126,41 @@ impl HitTester {
|
|||
clip_scroll_tree: &ClipScrollTree,
|
||||
clip_store: &ClipStore
|
||||
) {
|
||||
self.nodes.clear();
|
||||
self.spatial_nodes.clear();
|
||||
self.clip_chains.clear();
|
||||
self.clip_chains.resize(
|
||||
clip_scroll_tree.clip_chains.len(),
|
||||
HitTestClipChainDescriptor::empty()
|
||||
);
|
||||
|
||||
for (index, node) in clip_scroll_tree.nodes.iter().enumerate() {
|
||||
let index = ClipScrollNodeIndex(index);
|
||||
for (index, node) in clip_scroll_tree.spatial_nodes.iter().enumerate() {
|
||||
let index = SpatialNodeIndex(index);
|
||||
|
||||
// If we haven't already seen a node for this pipeline, record this one as the root
|
||||
// node.
|
||||
self.pipeline_root_nodes.entry(node.pipeline_id).or_insert(index);
|
||||
|
||||
self.nodes.push(HitTestClipScrollNode {
|
||||
self.spatial_nodes.push(HitTestSpatialNode {
|
||||
pipeline_id: node.pipeline_id,
|
||||
regions: get_regions_for_clip_scroll_node(node, clip_store),
|
||||
world_content_transform: node.world_content_transform,
|
||||
world_viewport_transform: node.world_viewport_transform,
|
||||
});
|
||||
}
|
||||
|
||||
if let NodeType::Clip { clip_chain_index, .. } = node.node_type {
|
||||
let clip_chain = self.clip_chains.get_mut(clip_chain_index.0).unwrap();
|
||||
clip_chain.parent =
|
||||
clip_scroll_tree.get_clip_chain(clip_chain_index).parent_index;
|
||||
clip_chain.clips = vec![index];
|
||||
}
|
||||
for (index, node) in clip_scroll_tree.clip_nodes.iter().enumerate() {
|
||||
let regions = match get_regions_for_clip_node(node, clip_store) {
|
||||
Some(regions) => regions,
|
||||
None => continue,
|
||||
};
|
||||
self.clip_nodes.push(HitTestClipNode {
|
||||
spatial_node: node.spatial_node,
|
||||
regions,
|
||||
});
|
||||
|
||||
let clip_chain = self.clip_chains.get_mut(node.clip_chain_index.0).unwrap();
|
||||
clip_chain.parent =
|
||||
clip_scroll_tree.get_clip_chain(node.clip_chain_index).parent_index;
|
||||
clip_chain.clips = vec![ClipNodeIndex(index)];
|
||||
}
|
||||
|
||||
for descriptor in &clip_scroll_tree.clip_chains_descriptors {
|
||||
|
@ -177,7 +192,7 @@ impl HitTester {
|
|||
}
|
||||
|
||||
for clip_node_index in &descriptor.clips {
|
||||
if !self.is_point_clipped_in_for_node(point, *clip_node_index, test) {
|
||||
if !self.is_point_clipped_in_for_clip_node(point, *clip_node_index, test) {
|
||||
test.set_in_clip_chain_cache(clip_chain_index, ClippedIn::NotClippedIn);
|
||||
return false;
|
||||
}
|
||||
|
@ -187,18 +202,18 @@ impl HitTester {
|
|||
true
|
||||
}
|
||||
|
||||
fn is_point_clipped_in_for_node(
|
||||
fn is_point_clipped_in_for_clip_node(
|
||||
&self,
|
||||
point: WorldPoint,
|
||||
node_index: ClipScrollNodeIndex,
|
||||
node_index: ClipNodeIndex,
|
||||
test: &mut HitTest
|
||||
) -> bool {
|
||||
if let Some(clipped_in) = test.node_cache.get(&node_index) {
|
||||
return *clipped_in == ClippedIn::ClippedIn;
|
||||
}
|
||||
|
||||
let node = &self.nodes[node_index.0];
|
||||
let transform = node.world_viewport_transform;
|
||||
let node = &self.clip_nodes[node_index.0];
|
||||
let transform = self.spatial_nodes[node.spatial_node.0].world_viewport_transform;
|
||||
let transformed_point = match transform.inverse() {
|
||||
Some(inverted) => inverted.transform_point2d(&point),
|
||||
None => {
|
||||
|
@ -218,12 +233,12 @@ impl HitTester {
|
|||
true
|
||||
}
|
||||
|
||||
pub fn find_node_under_point(&self, mut test: HitTest) -> Option<ClipScrollNodeIndex> {
|
||||
pub fn find_node_under_point(&self, mut test: HitTest) -> Option<SpatialNodeIndex> {
|
||||
let point = test.get_absolute_point(self);
|
||||
|
||||
for &HitTestingRun(ref items, ref clip_and_scroll) in self.runs.iter().rev() {
|
||||
let scroll_node_id = clip_and_scroll.scroll_node_id;
|
||||
let scroll_node = &self.nodes[scroll_node_id.0];
|
||||
let scroll_node = &self.spatial_nodes[scroll_node_id.0];
|
||||
let transform = scroll_node.world_content_transform;
|
||||
let point_in_layer = match transform.inverse() {
|
||||
Some(inverted) => inverted.transform_point2d(&point),
|
||||
|
@ -257,7 +272,7 @@ impl HitTester {
|
|||
let mut result = HitTestResult::default();
|
||||
for &HitTestingRun(ref items, ref clip_and_scroll) in self.runs.iter().rev() {
|
||||
let scroll_node_id = clip_and_scroll.scroll_node_id;
|
||||
let scroll_node = &self.nodes[scroll_node_id.0];
|
||||
let scroll_node = &self.spatial_nodes[scroll_node_id.0];
|
||||
let pipeline_id = scroll_node.pipeline_id;
|
||||
match (test.pipeline_id, pipeline_id) {
|
||||
(Some(id), node_id) if node_id != id => continue,
|
||||
|
@ -296,7 +311,7 @@ impl HitTester {
|
|||
// the pipeline of the hit item. If we cannot get a transformed point, we are
|
||||
// in a situation with an uninvertible transformation so we should just skip this
|
||||
// result.
|
||||
let root_node = &self.nodes[self.pipeline_root_nodes[&pipeline_id].0];
|
||||
let root_node = &self.spatial_nodes[self.pipeline_root_nodes[&pipeline_id].0];
|
||||
let point_in_viewport = match root_node.world_viewport_transform.inverse() {
|
||||
Some(inverted) => inverted.transform_point2d(&point),
|
||||
None => continue,
|
||||
|
@ -318,21 +333,25 @@ impl HitTester {
|
|||
result
|
||||
}
|
||||
|
||||
pub fn get_pipeline_root(&self, pipeline_id: PipelineId) -> &HitTestClipScrollNode {
|
||||
&self.nodes[self.pipeline_root_nodes[&pipeline_id].0]
|
||||
pub fn get_pipeline_root(&self, pipeline_id: PipelineId) -> &HitTestSpatialNode {
|
||||
&self.spatial_nodes[self.pipeline_root_nodes[&pipeline_id].0]
|
||||
}
|
||||
}
|
||||
|
||||
fn get_regions_for_clip_scroll_node(
|
||||
node: &ClipScrollNode,
|
||||
fn get_regions_for_clip_node(
|
||||
node: &ClipNode,
|
||||
clip_store: &ClipStore
|
||||
) -> Vec<HitTestRegion> {
|
||||
let clips = match node.node_type {
|
||||
NodeType::Clip{ ref handle, .. } => clip_store.get(handle).clips(),
|
||||
_ => return Vec::new(),
|
||||
) -> Option<Vec<HitTestRegion>> {
|
||||
let handle = match node.handle.as_ref() {
|
||||
Some(handle) => handle,
|
||||
None => {
|
||||
warn!("Encountered an empty clip node unexpectedly.");
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
clips.iter().map(|source| {
|
||||
let clips = clip_store.get(handle).clips();
|
||||
Some(clips.iter().map(|source| {
|
||||
match source.0 {
|
||||
ClipSource::Rectangle(ref rect, mode) => HitTestRegion::Rectangle(*rect, mode),
|
||||
ClipSource::RoundedRectangle(ref rect, ref radii, ref mode) =>
|
||||
|
@ -343,7 +362,7 @@ fn get_regions_for_clip_scroll_node(
|
|||
unreachable!("Didn't expect to hit test against BorderCorner / BoxShadow / LineDecoration");
|
||||
}
|
||||
}
|
||||
}).collect()
|
||||
}).collect())
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
|
@ -356,7 +375,7 @@ pub struct HitTest {
|
|||
pipeline_id: Option<PipelineId>,
|
||||
point: WorldPoint,
|
||||
flags: HitTestFlags,
|
||||
node_cache: FastHashMap<ClipScrollNodeIndex, ClippedIn>,
|
||||
node_cache: FastHashMap<ClipNodeIndex, ClippedIn>,
|
||||
clip_chain_cache: Vec<Option<ClippedIn>>,
|
||||
}
|
||||
|
||||
|
|
|
@ -60,7 +60,7 @@ mod box_shadow;
|
|||
#[cfg(any(feature = "capture", feature = "replay"))]
|
||||
mod capture;
|
||||
mod clip;
|
||||
mod clip_scroll_node;
|
||||
mod clip_node;
|
||||
mod clip_scroll_tree;
|
||||
mod debug_colors;
|
||||
#[cfg(feature = "debug_renderer")]
|
||||
|
@ -98,6 +98,7 @@ mod scene;
|
|||
mod scene_builder;
|
||||
mod segment;
|
||||
mod shade;
|
||||
mod spatial_node;
|
||||
mod texture_allocator;
|
||||
mod texture_cache;
|
||||
mod tiling;
|
||||
|
|
|
@ -6,8 +6,8 @@ use api::{DeviceRect, FilterOp, MixBlendMode, PipelineId, PremultipliedColorF};
|
|||
use api::{DeviceIntRect, DeviceIntSize, DevicePoint, LayoutPoint, LayoutRect};
|
||||
use api::{DevicePixelScale, PictureIntPoint, PictureIntRect, PictureIntSize};
|
||||
use box_shadow::{BLUR_SAMPLE_SCALE};
|
||||
use clip_scroll_node::ClipScrollNode;
|
||||
use clip_scroll_tree::ClipScrollNodeIndex;
|
||||
use spatial_node::SpatialNode;
|
||||
use clip_scroll_tree::SpatialNodeIndex;
|
||||
use frame_builder::{FrameBuildingContext, FrameBuildingState, PictureState, PrimitiveRunContext};
|
||||
use gpu_cache::{GpuCacheHandle};
|
||||
use gpu_types::UvRectKind;
|
||||
|
@ -150,7 +150,7 @@ pub struct PicturePrimitive {
|
|||
// The original reference frame ID for this picture.
|
||||
// It is only different if this is part of a 3D
|
||||
// rendering context.
|
||||
pub reference_frame_index: ClipScrollNodeIndex,
|
||||
pub reference_frame_index: SpatialNodeIndex,
|
||||
pub real_local_rect: LayoutRect,
|
||||
// An optional cache handle for storing extra data
|
||||
// in the GPU cache, depending on the type of
|
||||
|
@ -183,7 +183,7 @@ impl PicturePrimitive {
|
|||
composite_mode: Option<PictureCompositeMode>,
|
||||
is_in_3d_context: bool,
|
||||
pipeline_id: PipelineId,
|
||||
reference_frame_index: ClipScrollNodeIndex,
|
||||
reference_frame_index: SpatialNodeIndex,
|
||||
frame_output_pipeline_id: Option<PipelineId>,
|
||||
apply_local_clip_rect: bool,
|
||||
) -> Self {
|
||||
|
@ -590,18 +590,18 @@ impl PicturePrimitive {
|
|||
// Calculate a single screen-space UV for a picture.
|
||||
fn calculate_screen_uv(
|
||||
local_pos: &LayoutPoint,
|
||||
clip_scroll_node: &ClipScrollNode,
|
||||
spatial_node: &SpatialNode,
|
||||
rendered_rect: &DeviceRect,
|
||||
device_pixel_scale: DevicePixelScale,
|
||||
) -> DevicePoint {
|
||||
let world_pos = clip_scroll_node
|
||||
let world_pos = spatial_node
|
||||
.world_content_transform
|
||||
.transform_point2d(local_pos);
|
||||
|
||||
let mut device_pos = world_pos * device_pixel_scale;
|
||||
|
||||
// Apply snapping for axis-aligned scroll nodes, as per prim_shared.glsl.
|
||||
if clip_scroll_node.transform_kind == TransformedRectKind::AxisAligned {
|
||||
if spatial_node.transform_kind == TransformedRectKind::AxisAligned {
|
||||
device_pos.x = (device_pos.x + 0.5).floor();
|
||||
device_pos.y = (device_pos.y + 0.5).floor();
|
||||
}
|
||||
|
@ -616,7 +616,7 @@ fn calculate_screen_uv(
|
|||
// vertex positions of a picture.
|
||||
fn calculate_uv_rect_kind(
|
||||
local_rect: &LayoutRect,
|
||||
clip_scroll_node: &ClipScrollNode,
|
||||
spatial_node: &SpatialNode,
|
||||
rendered_rect: &DeviceIntRect,
|
||||
device_pixel_scale: DevicePixelScale,
|
||||
) -> UvRectKind {
|
||||
|
@ -624,28 +624,28 @@ fn calculate_uv_rect_kind(
|
|||
|
||||
let top_left = calculate_screen_uv(
|
||||
&local_rect.origin,
|
||||
clip_scroll_node,
|
||||
spatial_node,
|
||||
&rendered_rect,
|
||||
device_pixel_scale,
|
||||
);
|
||||
|
||||
let top_right = calculate_screen_uv(
|
||||
&local_rect.top_right(),
|
||||
clip_scroll_node,
|
||||
spatial_node,
|
||||
&rendered_rect,
|
||||
device_pixel_scale,
|
||||
);
|
||||
|
||||
let bottom_left = calculate_screen_uv(
|
||||
&local_rect.bottom_left(),
|
||||
clip_scroll_node,
|
||||
spatial_node,
|
||||
&rendered_rect,
|
||||
device_pixel_scale,
|
||||
);
|
||||
|
||||
let bottom_right = calculate_screen_uv(
|
||||
&local_rect.bottom_right(),
|
||||
clip_scroll_node,
|
||||
spatial_node,
|
||||
&rendered_rect,
|
||||
device_pixel_scale,
|
||||
);
|
||||
|
|
|
@ -187,7 +187,7 @@ fn new_ct_font_with_variations(cg_font: &CGFont, size: f64, variations: &[FontVa
|
|||
}
|
||||
let tag_val = match axis.find(kCTFontVariationAxisIdentifierKey as *const _) {
|
||||
Some(tag_ptr) => {
|
||||
let tag: CFNumber = TCFType::wrap_under_get_rule(tag_ptr as CFNumberRef);
|
||||
let tag: CFNumber = TCFType::wrap_under_get_rule(*tag_ptr as CFNumberRef);
|
||||
if !tag.instance_of::<CFNumber>() {
|
||||
return ct_font;
|
||||
}
|
||||
|
@ -204,7 +204,7 @@ fn new_ct_font_with_variations(cg_font: &CGFont, size: f64, variations: &[FontVa
|
|||
};
|
||||
|
||||
let name: CFString = match axis.find(kCTFontVariationAxisNameKey as *const _) {
|
||||
Some(name_ptr) => TCFType::wrap_under_get_rule(name_ptr as CFStringRef),
|
||||
Some(name_ptr) => TCFType::wrap_under_get_rule(*name_ptr as CFStringRef),
|
||||
None => return ct_font,
|
||||
};
|
||||
if !name.instance_of::<CFString>() {
|
||||
|
@ -213,7 +213,7 @@ fn new_ct_font_with_variations(cg_font: &CGFont, size: f64, variations: &[FontVa
|
|||
|
||||
let min_val = match axis.find(kCTFontVariationAxisMinimumValueKey as *const _) {
|
||||
Some(min_ptr) => {
|
||||
let min: CFNumber = TCFType::wrap_under_get_rule(min_ptr as CFNumberRef);
|
||||
let min: CFNumber = TCFType::wrap_under_get_rule(*min_ptr as CFNumberRef);
|
||||
if !min.instance_of::<CFNumber>() {
|
||||
return ct_font;
|
||||
}
|
||||
|
@ -226,7 +226,7 @@ fn new_ct_font_with_variations(cg_font: &CGFont, size: f64, variations: &[FontVa
|
|||
};
|
||||
let max_val = match axis.find(kCTFontVariationAxisMaximumValueKey as *const _) {
|
||||
Some(max_ptr) => {
|
||||
let max: CFNumber = TCFType::wrap_under_get_rule(max_ptr as CFNumberRef);
|
||||
let max: CFNumber = TCFType::wrap_under_get_rule(*max_ptr as CFNumberRef);
|
||||
if !max.instance_of::<CFNumber>() {
|
||||
return ct_font;
|
||||
}
|
||||
|
@ -239,7 +239,7 @@ fn new_ct_font_with_variations(cg_font: &CGFont, size: f64, variations: &[FontVa
|
|||
};
|
||||
let def_val = match axis.find(kCTFontVariationAxisDefaultValueKey as *const _) {
|
||||
Some(def_ptr) => {
|
||||
let def: CFNumber = TCFType::wrap_under_get_rule(def_ptr as CFNumberRef);
|
||||
let def: CFNumber = TCFType::wrap_under_get_rule(*def_ptr as CFNumberRef);
|
||||
if !def.instance_of::<CFNumber>() {
|
||||
return ct_font;
|
||||
}
|
||||
|
|
|
@ -11,8 +11,7 @@ use api::{BorderWidths, LayoutToWorldScale, NormalBorder};
|
|||
use app_units::Au;
|
||||
use border::{BorderCacheKey, BorderRenderTaskInfo};
|
||||
use box_shadow::BLUR_SAMPLE_SCALE;
|
||||
use clip_scroll_tree::{ClipChainIndex, ClipScrollNodeIndex, CoordinateSystemId};
|
||||
use clip_scroll_node::ClipScrollNode;
|
||||
use clip_scroll_tree::{ClipChainIndex, CoordinateSystemId, SpatialNodeIndex};
|
||||
use clip::{ClipChain, ClipChainNode, ClipChainNodeIter, ClipChainNodeRef, ClipSource};
|
||||
use clip::{ClipSourcesHandle, ClipWorkItem};
|
||||
use frame_builder::{FrameBuildingContext, FrameBuildingState, PictureContext, PictureState};
|
||||
|
@ -31,9 +30,10 @@ use renderer::{MAX_VERTEX_TEXTURE_WIDTH};
|
|||
use resource_cache::{ImageProperties, ImageRequest, ResourceCache};
|
||||
use scene::SceneProperties;
|
||||
use segment::SegmentBuilder;
|
||||
use spatial_node::SpatialNode;
|
||||
use std::{mem, usize};
|
||||
use std::sync::Arc;
|
||||
use util::{MatrixHelpers, WorldToLayoutFastTransform, calculate_screen_bounding_rect};
|
||||
use util::{MatrixHelpers, calculate_screen_bounding_rect};
|
||||
use util::{pack_as_float, recycle_vec};
|
||||
|
||||
|
||||
|
@ -42,13 +42,13 @@ pub const VECS_PER_SEGMENT: usize = 2;
|
|||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub struct ScrollNodeAndClipChain {
|
||||
pub scroll_node_id: ClipScrollNodeIndex,
|
||||
pub scroll_node_id: SpatialNodeIndex,
|
||||
pub clip_chain_index: ClipChainIndex,
|
||||
}
|
||||
|
||||
impl ScrollNodeAndClipChain {
|
||||
pub fn new(
|
||||
scroll_node_id: ClipScrollNodeIndex,
|
||||
scroll_node_id: SpatialNodeIndex,
|
||||
clip_chain_index: ClipChainIndex
|
||||
) -> Self {
|
||||
ScrollNodeAndClipChain { scroll_node_id, clip_chain_index }
|
||||
|
@ -1242,7 +1242,7 @@ impl PrimitiveStore {
|
|||
composite_mode: Option<PictureCompositeMode>,
|
||||
is_in_3d_context: bool,
|
||||
pipeline_id: PipelineId,
|
||||
reference_frame_index: ClipScrollNodeIndex,
|
||||
reference_frame_index: SpatialNodeIndex,
|
||||
frame_output_pipeline_id: Option<PipelineId>,
|
||||
apply_local_clip_rect: bool,
|
||||
) -> PictureIndex {
|
||||
|
@ -1994,7 +1994,6 @@ impl PrimitiveStore {
|
|||
prim_run_context: &PrimitiveRunContext,
|
||||
clips: &Vec<ClipWorkItem>,
|
||||
has_clips_from_other_coordinate_systems: bool,
|
||||
frame_context: &FrameBuildingContext,
|
||||
frame_state: &mut FrameBuildingState,
|
||||
) {
|
||||
match brush.segment_desc {
|
||||
|
@ -2047,19 +2046,39 @@ impl PrimitiveStore {
|
|||
}
|
||||
|
||||
let local_clips = frame_state.clip_store.get_opt(&clip_item.clip_sources).expect("bug");
|
||||
rect_clips_only = rect_clips_only && local_clips.only_rectangular_clips;
|
||||
|
||||
// TODO(gw): We can easily extend the segment builder to support these clip sources in
|
||||
// the future, but they are rarely used.
|
||||
// We must do this check here in case we continue early below.
|
||||
if local_clips.has_image_or_line_decoration_clip {
|
||||
clip_mask_kind = BrushClipMaskKind::Global;
|
||||
}
|
||||
|
||||
// If this clip item is positioned by another positioning node, its relative position
|
||||
// could change during scrolling. This means that we would need to resegment. Instead
|
||||
// of doing that, only segment with clips that have the same positioning node.
|
||||
// TODO(mrobinson, #2858): It may make sense to include these nodes, resegmenting only
|
||||
// when necessary while scrolling.
|
||||
if clip_item.transform_index != prim_run_context.transform_index {
|
||||
// We don't need to generate a global clip mask for rectangle clips because we are
|
||||
// in the same coordinate system and rectangular clips are handled by the local
|
||||
// clip chain rectangle.
|
||||
if !local_clips.only_rectangular_clips {
|
||||
clip_mask_kind = BrushClipMaskKind::Global;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
for &(ref clip, _) in &local_clips.clips {
|
||||
let (local_clip_rect, radius, mode) = match *clip {
|
||||
ClipSource::RoundedRectangle(rect, radii, clip_mode) => {
|
||||
rect_clips_only = false;
|
||||
|
||||
(rect, Some(radii), clip_mode)
|
||||
}
|
||||
ClipSource::Rectangle(rect, mode) => {
|
||||
(rect, None, mode)
|
||||
}
|
||||
ClipSource::BoxShadow(ref info) => {
|
||||
rect_clips_only = false;
|
||||
|
||||
// For inset box shadows, we can clip out any
|
||||
// pixels that are inside the shadow region
|
||||
// and are beyond the inner rect, as they can't
|
||||
|
@ -2084,35 +2103,7 @@ impl PrimitiveStore {
|
|||
|
||||
continue;
|
||||
}
|
||||
ClipSource::LineDecoration(..) |
|
||||
ClipSource::Image(..) => {
|
||||
rect_clips_only = false;
|
||||
|
||||
// TODO(gw): We can easily extend the segment builder
|
||||
// to support these clip sources in the
|
||||
// future, but they are rarely used.
|
||||
clip_mask_kind = BrushClipMaskKind::Global;
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
// If the scroll node transforms are different between the clip
|
||||
// node and the primitive, we need to get the clip rect in the
|
||||
// local space of the primitive, in order to generate correct
|
||||
// local segments.
|
||||
let local_clip_rect = if clip_item.transform_index == prim_run_context.scroll_node.transform_index {
|
||||
local_clip_rect
|
||||
} else {
|
||||
let clip_transform = frame_context
|
||||
.transforms[clip_item.transform_index.0 as usize]
|
||||
.transform;
|
||||
let prim_transform = &prim_run_context.scroll_node.world_content_transform;
|
||||
let relative_transform = prim_transform
|
||||
.inverse()
|
||||
.unwrap_or(WorldToLayoutFastTransform::identity())
|
||||
.pre_mul(&clip_transform.into());
|
||||
|
||||
relative_transform.transform_rect(&local_clip_rect)
|
||||
ClipSource::LineDecoration(..) | ClipSource::Image(..) => continue,
|
||||
};
|
||||
|
||||
segment_builder.push_clip_rect(local_clip_rect, radius, mode);
|
||||
|
@ -2181,7 +2172,6 @@ impl PrimitiveStore {
|
|||
prim_run_context,
|
||||
clips,
|
||||
has_clips_from_other_coordinate_systems,
|
||||
frame_context,
|
||||
frame_state,
|
||||
);
|
||||
|
||||
|
@ -2303,11 +2293,11 @@ impl PrimitiveStore {
|
|||
|
||||
Arc::new(ClipChainNode {
|
||||
work_item: ClipWorkItem {
|
||||
transform_index: prim_run_context.scroll_node.transform_index,
|
||||
transform_index: prim_run_context.transform_index,
|
||||
clip_sources: clip_sources.weak(),
|
||||
coordinate_system_id: prim_coordinate_system_id,
|
||||
},
|
||||
// The local_clip_rect a property of ClipChain nodes that are ClipScrollNodes.
|
||||
// The local_clip_rect a property of ClipChain nodes that are ClipNodes.
|
||||
// It's used to calculate a local clipping rectangle before we reach this
|
||||
// point, so we can set it to zero here. It should be unused from this point
|
||||
// on.
|
||||
|
@ -2649,7 +2639,7 @@ impl PrimitiveStore {
|
|||
// lookups ever show up in a profile).
|
||||
let scroll_node = &frame_context
|
||||
.clip_scroll_tree
|
||||
.nodes[run.clip_and_scroll.scroll_node_id.0];
|
||||
.spatial_nodes[run.clip_and_scroll.scroll_node_id.0];
|
||||
let clip_chain = frame_context
|
||||
.clip_scroll_tree
|
||||
.get_clip_chain(run.clip_and_scroll.clip_chain_index);
|
||||
|
@ -2685,7 +2675,7 @@ impl PrimitiveStore {
|
|||
.and_then(|original_reference_frame_index| {
|
||||
frame_context
|
||||
.clip_scroll_tree
|
||||
.nodes[original_reference_frame_index.0]
|
||||
.spatial_nodes[original_reference_frame_index.0]
|
||||
.world_content_transform
|
||||
.inverse()
|
||||
})
|
||||
|
@ -2722,6 +2712,7 @@ impl PrimitiveStore {
|
|||
let child_prim_run_context = PrimitiveRunContext::new(
|
||||
clip_chain,
|
||||
scroll_node,
|
||||
run.clip_and_scroll.scroll_node_id.transform_index(),
|
||||
local_clip_chain_rect,
|
||||
);
|
||||
|
||||
|
@ -2914,7 +2905,7 @@ fn convert_clip_chain_to_clip_vector(
|
|||
}
|
||||
|
||||
fn get_local_clip_rect_for_nodes(
|
||||
scroll_node: &ClipScrollNode,
|
||||
scroll_node: &SpatialNode,
|
||||
clip_chain: &ClipChain,
|
||||
) -> Option<LayoutRect> {
|
||||
ClipChainNodeIter { current: clip_chain.nodes.clone() }
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче