зеркало из https://github.com/mozilla/gecko-dev.git
merge autoland to mozilla-central. r=merge a=merge
MozReview-Commit-ID: ALl7GQHEApt
This commit is contained in:
Коммит
02c5cf4167
|
@ -240,6 +240,13 @@ pref("general.autoScroll", true);
|
|||
// UI density of the browser chrome. This mostly affects toolbarbutton
|
||||
// and urlbar spacing. The possible values are 0=normal, 1=compact, 2=touch.
|
||||
pref("browser.uidensity", 0);
|
||||
// Whether Firefox will automatically override the uidensity to "touch"
|
||||
// while the user is in a touch environment (such as Windows tablet mode).
|
||||
#ifdef MOZ_PHOTON_THEME
|
||||
pref("browser.touchmode.auto", true);
|
||||
#else
|
||||
pref("browser.touchmode.auto", false);
|
||||
#endif
|
||||
|
||||
// At startup, check if we're the default browser and prompt user if not.
|
||||
pref("browser.shell.checkDefaultBrowser", true);
|
||||
|
@ -716,7 +723,7 @@ pref("browser.preferences.instantApply", true);
|
|||
#endif
|
||||
|
||||
// Toggling Search bar on and off in about:preferences
|
||||
pref("browser.preferences.search", false);
|
||||
pref("browser.preferences.search", true);
|
||||
|
||||
// Use the new in-content about:preferences in Nightly only for now
|
||||
#if defined(NIGHTLY_BUILD)
|
||||
|
|
|
@ -363,7 +363,7 @@ body[narrow] #restorePreviousSession::before {
|
|||
display: block;
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
right: 12px;
|
||||
offset-inline-end: 12px;
|
||||
width: 70px;
|
||||
height: 20px;
|
||||
}
|
||||
|
@ -437,4 +437,3 @@ body[narrow] #restorePreviousSession::before {
|
|||
transform-origin: top center;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5407,6 +5407,7 @@ var TabletModeUpdater = {
|
|||
document.documentElement.removeAttribute("tabletmode");
|
||||
}
|
||||
if (wasInTabletMode != isInTabletMode) {
|
||||
gUIDensity.update();
|
||||
TabsInTitlebar.updateAppearance(true);
|
||||
}
|
||||
},
|
||||
|
@ -5446,7 +5447,10 @@ function displaySecurityInfo() {
|
|||
|
||||
// Updates the UI density (for touch and compact mode) based on the uidensity pref.
|
||||
var gUIDensity = {
|
||||
MODE_COMPACT: 1,
|
||||
MODE_TOUCH: 2,
|
||||
prefDomain: "browser.uidensity",
|
||||
|
||||
observe(aSubject, aTopic, aPrefName) {
|
||||
if (aTopic != "nsPref:changed" || aPrefName != this.prefDomain)
|
||||
return;
|
||||
|
@ -5455,12 +5459,22 @@ var gUIDensity = {
|
|||
},
|
||||
|
||||
update() {
|
||||
let mode;
|
||||
// Automatically override the uidensity to touch in Windows tablet mode.
|
||||
if (AppConstants.isPlatformAndVersionAtLeast("win", "10") &&
|
||||
WindowsUIUtils.inTabletMode &&
|
||||
gPrefService.getBoolPref("browser.touchmode.auto")) {
|
||||
mode = this.MODE_TOUCH;
|
||||
} else {
|
||||
mode = gPrefService.getIntPref(this.prefDomain);
|
||||
}
|
||||
|
||||
let doc = document.documentElement;
|
||||
switch (gPrefService.getIntPref(this.prefDomain)) {
|
||||
case 1:
|
||||
switch (mode) {
|
||||
case this.MODE_COMPACT:
|
||||
doc.setAttribute("uidensity", "compact");
|
||||
break;
|
||||
case 2:
|
||||
case this.MODE_TOUCH:
|
||||
doc.setAttribute("uidensity", "touch");
|
||||
break;
|
||||
default:
|
||||
|
|
|
@ -91,16 +91,6 @@ tabpanels {
|
|||
transition: width .15s ease-out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Optimization for tabs that are restored lazily. We can save a good amount of
|
||||
* memory that to-be-restored tabs would otherwise consume simply by setting
|
||||
* their browsers to 'display: none' as that will prevent them from having to
|
||||
* create a presentation and the like.
|
||||
*/
|
||||
browser[pending] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
browser[blank],
|
||||
browser[pendingpaint] {
|
||||
opacity: 0;
|
||||
|
|
|
@ -2,3 +2,5 @@
|
|||
[browser_popup_blocker.js]
|
||||
support-files = popup_blocker.html
|
||||
skip-if = (os == 'linux') || (e10s && debug) # Frequent bug 1081925 and bug 1125520 failures
|
||||
[browser_popup_frames.js]
|
||||
support-files = popup_blocker.html
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
const baseURL = getRootDirectory(gTestPath).replace("chrome://mochitests/content", "http://example.com");
|
||||
|
||||
add_task(async function test_opening_blocked_popups() {
|
||||
// Enable the popup blocker.
|
||||
await SpecialPowers.pushPrefEnv({set: [["dom.disable_open_during_load", true]]});
|
||||
|
||||
// Open the test page.
|
||||
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "data:text/html,Hello");
|
||||
|
||||
await ContentTask.spawn(tab.linkedBrowser, baseURL + "popup_blocker.html", uri => {
|
||||
let iframe = content.document.createElement("iframe");
|
||||
iframe.id = "popupframe";
|
||||
iframe.src = uri;
|
||||
content.document.body.appendChild(iframe);
|
||||
});
|
||||
|
||||
// Wait for the popup-blocked notification.
|
||||
let notification;
|
||||
await BrowserTestUtils.waitForCondition(() =>
|
||||
notification = gBrowser.getNotificationBox().getNotificationWithValue("popup-blocked"));
|
||||
|
||||
ok(notification, "Should have notification.");
|
||||
ok(!document.getElementById("page-report-button").hidden,
|
||||
"URL bar popup indicator should not be hidden");
|
||||
|
||||
await ContentTask.spawn(tab.linkedBrowser, baseURL, async function(uri) {
|
||||
let iframe = content.document.createElement("iframe");
|
||||
iframe.src = uri;
|
||||
let pageHideHappened = ContentTaskUtils.waitForEvent(this, "pagehide", true);
|
||||
content.document.body.appendChild(iframe);
|
||||
await pageHideHappened;
|
||||
});
|
||||
|
||||
notification = gBrowser.getNotificationBox().getNotificationWithValue("popup-blocked");
|
||||
ok(notification, "Should still have notification");
|
||||
ok(!document.getElementById("page-report-button").hidden,
|
||||
"URL bar popup indicator should still be visible");
|
||||
|
||||
// Now navigate the subframe.
|
||||
await ContentTask.spawn(tab.linkedBrowser, null, async function() {
|
||||
let pageHideHappened = ContentTaskUtils.waitForEvent(this, "pagehide", true);
|
||||
content.document.getElementById("popupframe").contentDocument.location.href = "about:blank";
|
||||
await pageHideHappened;
|
||||
});
|
||||
|
||||
await BrowserTestUtils.waitForCondition(() =>
|
||||
!gBrowser.getNotificationBox().getNotificationWithValue("popup-blocked"));
|
||||
ok(!gBrowser.getNotificationBox().getNotificationWithValue("popup-blocked"),
|
||||
"Should no longer have notification");
|
||||
ok(document.getElementById("page-report-button").hidden,
|
||||
"URL bar popup indicator should be hidden");
|
||||
|
||||
// Remove the frame and add another one:
|
||||
await ContentTask.spawn(tab.linkedBrowser, baseURL + "popup_blocker.html", uri => {
|
||||
content.document.getElementById("popupframe").remove();
|
||||
let iframe = content.document.createElement("iframe");
|
||||
iframe.id = "popupframe";
|
||||
iframe.src = uri;
|
||||
content.document.body.appendChild(iframe);
|
||||
});
|
||||
|
||||
// Wait for the popup-blocked notification.
|
||||
await BrowserTestUtils.waitForCondition(() =>
|
||||
notification = gBrowser.getNotificationBox().getNotificationWithValue("popup-blocked"));
|
||||
|
||||
ok(notification, "Should have notification.");
|
||||
ok(!document.getElementById("page-report-button").hidden,
|
||||
"URL bar popup indicator should not be hidden");
|
||||
|
||||
await ContentTask.spawn(tab.linkedBrowser, null, () => {
|
||||
content.document.getElementById("popupframe").remove();
|
||||
});
|
||||
|
||||
await BrowserTestUtils.waitForCondition(() =>
|
||||
!gBrowser.getNotificationBox().getNotificationWithValue("popup-blocked"));
|
||||
ok(!gBrowser.getNotificationBox().getNotificationWithValue("popup-blocked"),
|
||||
"Should no longer have notification");
|
||||
ok(document.getElementById("page-report-button").hidden,
|
||||
"URL bar popup indicator should be hidden");
|
||||
|
||||
await BrowserTestUtils.removeTab(tab);
|
||||
});
|
||||
|
|
@ -434,30 +434,12 @@ function promisePanelEvent(name) {
|
|||
}
|
||||
|
||||
function promiseViewShown() {
|
||||
return Promise.all([
|
||||
promiseViewShowing(),
|
||||
promiseTransitionEnd(),
|
||||
]).then(values => {
|
||||
return new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
resolve(values[0]);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function promiseViewShowing() {
|
||||
return new Promise(resolve => {
|
||||
gPanel.addEventListener("ViewShowing", (event) => {
|
||||
resolve(event.target);
|
||||
}, { once: true });
|
||||
});
|
||||
}
|
||||
|
||||
function promiseTransitionEnd() {
|
||||
return new Promise(resolve => {
|
||||
gPanel.addEventListener("transitionend", (event) => {
|
||||
resolve(event.target);
|
||||
gPanel.addEventListener("ViewShown", (event) => {
|
||||
let target = event.originalTarget;
|
||||
window.setTimeout(() => {
|
||||
resolve(target);
|
||||
}, 5000);
|
||||
}, { once: true });
|
||||
});
|
||||
}
|
||||
|
|
|
@ -39,6 +39,10 @@ function init(event) {
|
|||
}
|
||||
|
||||
function updateIndicatorState() {
|
||||
// If gStringBundle isn't set, the window hasn't finished loading.
|
||||
if (!gStringBundle)
|
||||
return;
|
||||
|
||||
updateWindowAttr("sharingvideo", webrtcUI.showCameraIndicator);
|
||||
updateWindowAttr("sharingaudio", webrtcUI.showMicrophoneIndicator);
|
||||
updateWindowAttr("sharingscreen", webrtcUI.showScreenSharingIndicator);
|
||||
|
|
|
@ -252,6 +252,8 @@ this.PanelMultiView = class {
|
|||
document.getAnonymousElementByAttribute(this.node, "anonid", "subViews");
|
||||
this._viewStack =
|
||||
document.getAnonymousElementByAttribute(this.node, "anonid", "viewStack");
|
||||
this._offscreenViewStack =
|
||||
document.getAnonymousElementByAttribute(this.node, "anonid", "offscreenViewStack");
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "_panelViewCache", () => {
|
||||
let viewCacheId = this.node.getAttribute("viewCacheId");
|
||||
|
@ -443,6 +445,7 @@ this.PanelMultiView = class {
|
|||
// of the main view, i.e. whilst the panel is shown and/ or visible.
|
||||
if (!this._mainViewHeight) {
|
||||
this._mainViewHeight = previousRect.height;
|
||||
this._viewContainer.style.minHeight = this._mainViewHeight + "px";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -528,114 +531,106 @@ this.PanelMultiView = class {
|
|||
|
||||
if (aAnchor)
|
||||
aAnchor.setAttribute("open", true);
|
||||
|
||||
// Set the viewContainer dimensions to make sure only the current view
|
||||
// is visible.
|
||||
this._viewContainer.style.height = Math.max(previousRect.height, this._mainViewHeight) + "px";
|
||||
this._viewContainer.style.width = previousRect.width + "px";
|
||||
// Lock the dimensions of the window that hosts the popup panel.
|
||||
let rect = this._panel.popupBoxObject.getOuterScreenRect();
|
||||
this._panel.setAttribute("width", rect.width);
|
||||
this._panel.setAttribute("height", rect.height);
|
||||
|
||||
this._transitioning = true;
|
||||
if (this._autoResizeWorkaroundTimer)
|
||||
window.clearTimeout(this._autoResizeWorkaroundTimer);
|
||||
this._viewContainer.setAttribute("transition-reverse", reverse);
|
||||
let nodeToAnimate = reverse ? previousViewNode : viewNode;
|
||||
this._viewBoundsOffscreen(viewNode, viewRect => {
|
||||
this._transitioning = true;
|
||||
if (this._autoResizeWorkaroundTimer)
|
||||
window.clearTimeout(this._autoResizeWorkaroundTimer);
|
||||
this._viewContainer.setAttribute("transition-reverse", reverse);
|
||||
let nodeToAnimate = reverse ? previousViewNode : viewNode;
|
||||
|
||||
if (!reverse) {
|
||||
// We set the margin here to make sure the view is positioned next
|
||||
// to the view that is currently visible. The animation is taken
|
||||
// care of by transitioning the `transform: translateX()` property
|
||||
// instead.
|
||||
// Once the transition finished, we clean both properties up.
|
||||
nodeToAnimate.style.marginInlineStart = previousRect.width + "px";
|
||||
}
|
||||
|
||||
// Set the transition style and listen for its end to clean up and
|
||||
// make sure the box sizing becomes dynamic again.
|
||||
// Somehow, putting these properties in PanelUI.css doesn't work for
|
||||
// newly shown nodes in a XUL parent node.
|
||||
nodeToAnimate.style.transition = "transform ease-" + (reverse ? "in" : "out") +
|
||||
" var(--panelui-subview-transition-duration)";
|
||||
nodeToAnimate.style.willChange = "transform";
|
||||
nodeToAnimate.style.borderInlineStart = "1px solid var(--panel-separator-color)";
|
||||
|
||||
// Wait until after the first paint to ensure setting 'current=true'
|
||||
// has taken full effect; once both views are visible, we want to
|
||||
// correctly measure rects using `dwu.getBoundsWithoutFlushing`.
|
||||
window.addEventListener("MozAfterPaint", () => {
|
||||
let viewRect = viewNode.__lastKnownBoundingRect;
|
||||
if (!viewRect) {
|
||||
viewRect = dwu.getBoundsWithoutFlushing(viewNode);
|
||||
if (!reverse) {
|
||||
// When showing two nodes at the same time (one partly out of view,
|
||||
// but that doesn't seem to make a difference in this case) inside
|
||||
// a XUL node container, the flexible box layout on the vertical
|
||||
// axis gets confused. I.e. it lies.
|
||||
// So what we need to resort to here is count the height of each
|
||||
// individual child element of the view.
|
||||
viewRect.height = [viewNode.header, ...viewNode.children].reduce((acc, node) => {
|
||||
return acc + dwu.getBoundsWithoutFlushing(node).height;
|
||||
}, this._viewVerticalPadding);
|
||||
}
|
||||
if (!reverse) {
|
||||
// We set the margin here to make sure the view is positioned next
|
||||
// to the view that is currently visible. The animation is taken
|
||||
// care of by transitioning the `transform: translateX()` property
|
||||
// instead.
|
||||
// Once the transition finished, we clean both properties up.
|
||||
nodeToAnimate.style.marginInlineStart = previousRect.width + "px";
|
||||
}
|
||||
|
||||
// Set the viewContainer dimensions to make sure only the current view
|
||||
// is visible.
|
||||
this._viewContainer.style.height = Math.max(viewRect.height, this._mainViewHeight) + "px";
|
||||
this._viewContainer.style.width = viewRect.width + "px";
|
||||
// Set the transition style and listen for its end to clean up and
|
||||
// make sure the box sizing becomes dynamic again.
|
||||
// Somehow, putting these properties in PanelUI.css doesn't work for
|
||||
// newly shown nodes in a XUL parent node.
|
||||
nodeToAnimate.style.transition = "transform ease-" + (reverse ? "in" : "out") +
|
||||
" var(--panelui-subview-transition-duration)";
|
||||
nodeToAnimate.style.willChange = "transform";
|
||||
nodeToAnimate.style.borderInlineStart = "1px solid var(--panel-separator-color)";
|
||||
|
||||
// The 'magic' part: build up the amount of pixels to move right or left.
|
||||
let moveToLeft = (this._dir == "rtl" && !reverse) || (this._dir == "ltr" && reverse);
|
||||
let movementX = reverse ? viewRect.width : previousRect.width;
|
||||
let moveX = (moveToLeft ? "" : "-") + movementX;
|
||||
nodeToAnimate.style.transform = "translateX(" + moveX + "px)";
|
||||
// We're setting the width property to prevent flickering during the
|
||||
// sliding animation with smaller views.
|
||||
nodeToAnimate.style.width = viewRect.width + "px";
|
||||
// Wait until after the first paint to ensure setting 'current=true'
|
||||
// has taken full effect; once both views are visible, we want to
|
||||
// correctly measure rects using `dwu.getBoundsWithoutFlushing`.
|
||||
window.addEventListener("MozAfterPaint", () => {
|
||||
// Now set the viewContainer dimensions to that of the new view, which
|
||||
// kicks of the height animation.
|
||||
this._viewContainer.style.height = Math.max(viewRect.height, this._mainViewHeight) + "px";
|
||||
this._viewContainer.style.width = viewRect.width + "px";
|
||||
this._panel.removeAttribute("width");
|
||||
this._panel.removeAttribute("height");
|
||||
|
||||
let listener;
|
||||
this._viewContainer.addEventListener("transitionend", listener = ev => {
|
||||
// It's quite common that `height` on the view container doesn't need
|
||||
// to transition, so we make sure to do all the work on the transform
|
||||
// transition-end, because that is guaranteed to happen.
|
||||
if (ev.target != nodeToAnimate || ev.propertyName != "transform")
|
||||
return;
|
||||
// The 'magic' part: build up the amount of pixels to move right or left.
|
||||
let moveToLeft = (this._dir == "rtl" && !reverse) || (this._dir == "ltr" && reverse);
|
||||
let movementX = reverse ? viewRect.width : previousRect.width;
|
||||
let moveX = (moveToLeft ? "" : "-") + movementX;
|
||||
nodeToAnimate.style.transform = "translateX(" + moveX + "px)";
|
||||
// We're setting the width property to prevent flickering during the
|
||||
// sliding animation with smaller views.
|
||||
nodeToAnimate.style.width = viewRect.width + "px";
|
||||
|
||||
this._viewContainer.removeEventListener("transitionend", listener);
|
||||
onTransitionEnd();
|
||||
this._transitioning = false;
|
||||
this._resetKeyNavigation(previousViewNode);
|
||||
let listener;
|
||||
this._viewContainer.addEventListener("transitionend", listener = ev => {
|
||||
// It's quite common that `height` on the view container doesn't need
|
||||
// to transition, so we make sure to do all the work on the transform
|
||||
// transition-end, because that is guaranteed to happen.
|
||||
if (ev.target != nodeToAnimate || ev.propertyName != "transform")
|
||||
return;
|
||||
|
||||
// Myeah, panel layout auto-resizing is a funky thing. We'll wait
|
||||
// another few milliseconds to remove the width and height 'fixtures',
|
||||
// to be sure we don't flicker annoyingly.
|
||||
// NB: HACK! Bug 1363756 is there to fix this.
|
||||
this._autoResizeWorkaroundTimer = window.setTimeout(() => {
|
||||
// Only remove the height when the view is larger than the main
|
||||
// view, otherwise it'll snap back to its own height.
|
||||
if (viewNode == this._mainView || viewRect.height > this._mainViewHeight)
|
||||
this._viewContainer.removeEventListener("transitionend", listener);
|
||||
onTransitionEnd();
|
||||
this._transitioning = false;
|
||||
this._resetKeyNavigation(previousViewNode);
|
||||
|
||||
// Myeah, panel layout auto-resizing is a funky thing. We'll wait
|
||||
// another few milliseconds to remove the width and height 'fixtures',
|
||||
// to be sure we don't flicker annoyingly.
|
||||
// NB: HACK! Bug 1363756 is there to fix this.
|
||||
this._autoResizeWorkaroundTimer = window.setTimeout(() => {
|
||||
this._viewContainer.style.removeProperty("height");
|
||||
this._viewContainer.style.removeProperty("width");
|
||||
}, 500);
|
||||
this._viewContainer.style.removeProperty("width");
|
||||
}, 500);
|
||||
|
||||
// Take another breather, just like before, to wait for the 'current'
|
||||
// attribute removal to take effect. This prevents a flicker.
|
||||
// The cleanup we do doesn't affect the display anymore, so we're not
|
||||
// too fussed about the timing here.
|
||||
window.addEventListener("MozAfterPaint", () => {
|
||||
nodeToAnimate.style.removeProperty("border-inline-start");
|
||||
nodeToAnimate.style.removeProperty("transition");
|
||||
nodeToAnimate.style.removeProperty("transform");
|
||||
nodeToAnimate.style.removeProperty("width");
|
||||
// Take another breather, just like before, to wait for the 'current'
|
||||
// attribute removal to take effect. This prevents a flicker.
|
||||
// The cleanup we do doesn't affect the display anymore, so we're not
|
||||
// too fussed about the timing here.
|
||||
window.addEventListener("MozAfterPaint", () => {
|
||||
nodeToAnimate.style.removeProperty("border-inline-start");
|
||||
nodeToAnimate.style.removeProperty("transition");
|
||||
nodeToAnimate.style.removeProperty("transform");
|
||||
nodeToAnimate.style.removeProperty("width");
|
||||
|
||||
if (!reverse)
|
||||
viewNode.style.removeProperty("margin-inline-start");
|
||||
if (aAnchor)
|
||||
aAnchor.removeAttribute("open");
|
||||
if (!reverse)
|
||||
viewNode.style.removeProperty("margin-inline-start");
|
||||
if (aAnchor)
|
||||
aAnchor.removeAttribute("open");
|
||||
|
||||
this._viewContainer.removeAttribute("transition-reverse");
|
||||
this._viewContainer.removeAttribute("transition-reverse");
|
||||
|
||||
evt = new window.CustomEvent("ViewShown", { bubbles: true, cancelable: false });
|
||||
viewNode.dispatchEvent(evt);
|
||||
}, { once: true });
|
||||
});
|
||||
}, { once: true });
|
||||
evt = new window.CustomEvent("ViewShown", { bubbles: true, cancelable: false });
|
||||
viewNode.dispatchEvent(evt);
|
||||
}, { once: true });
|
||||
});
|
||||
}, { once: true });
|
||||
});
|
||||
} else if (!this.panelViews) {
|
||||
this._transitionHeight(() => {
|
||||
viewNode.setAttribute("current", true);
|
||||
|
@ -649,6 +644,36 @@ this.PanelMultiView = class {
|
|||
})().catch(e => Cu.reportError(e));
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the correct bounds of a panelview node offscreen to minimize the
|
||||
* amount of paint flashing and keep the stack vs panel layouts from interfering.
|
||||
*
|
||||
* @param {panelview} viewNode Node to measure the bounds of.
|
||||
* @param {Function} callback Called when we got the measurements in and pass
|
||||
* them on as its first argument.
|
||||
*/
|
||||
_viewBoundsOffscreen(viewNode, callback) {
|
||||
if (viewNode.__lastKnownBoundingRect) {
|
||||
callback(viewNode.__lastKnownBoundingRect);
|
||||
return;
|
||||
}
|
||||
|
||||
let oldSibling = viewNode.nextSibling || null;
|
||||
this._offscreenViewStack.appendChild(viewNode);
|
||||
|
||||
this.window.addEventListener("MozAfterPaint", () => {
|
||||
let viewRect = this._dwu.getBoundsWithoutFlushing(viewNode);
|
||||
|
||||
try {
|
||||
this._viewStack.insertBefore(viewNode, oldSibling);
|
||||
} catch (ex) {
|
||||
this._viewStack.appendChild(viewNode);
|
||||
}
|
||||
|
||||
callback(viewRect);
|
||||
}, { once: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the height transition for which <panelmultiview> is designed.
|
||||
*
|
||||
|
|
|
@ -48,4 +48,16 @@ photonpanelmultiview[transitioning] {
|
|||
pointer-events: none;
|
||||
}
|
||||
|
||||
.panel-viewcontainer.offscreen {
|
||||
position: absolute;
|
||||
top: 100000px;
|
||||
left: 100000px;
|
||||
}
|
||||
|
||||
.panel-viewcontainer.offscreen,
|
||||
.panel-viewcontainer.offscreen > .panel-viewstack {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* END photon adjustments */
|
||||
|
|
|
@ -58,6 +58,9 @@
|
|||
<children includes="panelview"/>
|
||||
</xul:stack>
|
||||
</xul:box>
|
||||
<xul:box class="panel-viewcontainer offscreen">
|
||||
<xul:box anonid="offscreenViewStack" class="panel-viewstack"/>
|
||||
</xul:box>
|
||||
</content>
|
||||
</binding>
|
||||
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
|
||||
var gSearchResultsPane = {
|
||||
findSelection: null,
|
||||
listSearchTooltips: [],
|
||||
listSearchTooltips: new Set(),
|
||||
listSearchMenuitemIndicators: new Set(),
|
||||
searchResultsCategory: null,
|
||||
searchInput: null,
|
||||
|
||||
|
@ -193,6 +194,7 @@ var gSearchResultsPane = {
|
|||
let query = event.target.value.trim().toLowerCase();
|
||||
this.findSelection.removeAllRanges();
|
||||
this.removeAllSearchTooltips();
|
||||
this.removeAllSearchMenuitemIndicators();
|
||||
|
||||
let srHeader = document.getElementById("header-searchResults");
|
||||
|
||||
|
@ -242,9 +244,7 @@ var gSearchResultsPane = {
|
|||
strings.getFormattedString("searchResults.needHelp2", [helpUrl, brandName]);
|
||||
} else {
|
||||
// Creating tooltips for all the instances found
|
||||
for (let node of this.listSearchTooltips) {
|
||||
this.createSearchTooltip(node, query);
|
||||
}
|
||||
this.listSearchTooltips.forEach((node) => this.createSearchTooltip(node, query));
|
||||
}
|
||||
} else {
|
||||
this.searchResultsCategory.hidden = true;
|
||||
|
@ -301,8 +301,17 @@ var gSearchResultsPane = {
|
|||
let keywordsResult = this.stringMatchesFilters(nodeObject.getAttribute("searchkeywords"), searchPhrase);
|
||||
|
||||
// Creating tooltips for buttons
|
||||
if (keywordsResult && nodeObject.tagName === "button") {
|
||||
this.listSearchTooltips.push(nodeObject);
|
||||
if (keywordsResult && (nodeObject.tagName === "button" || nodeObject.tagName == "menulist")) {
|
||||
this.listSearchTooltips.add(nodeObject);
|
||||
}
|
||||
|
||||
if (keywordsResult && nodeObject.tagName === "menuitem") {
|
||||
nodeObject.setAttribute("indicator", "true");
|
||||
this.listSearchMenuitemIndicators.add(nodeObject);
|
||||
let menulist = nodeObject.closest("menulist");
|
||||
|
||||
menulist.setAttribute("indicator", "true");
|
||||
this.listSearchMenuitemIndicators.add(menulist);
|
||||
}
|
||||
|
||||
if ((nodeObject.tagName == "button" ||
|
||||
|
@ -315,20 +324,46 @@ var gSearchResultsPane = {
|
|||
matchesFound = matchesFound || complexTextNodesResult || labelResult || valueResult || keywordsResult;
|
||||
}
|
||||
|
||||
for (let i = 0; i < nodeObject.childNodes.length; i++) {
|
||||
// Search only if child node is not hidden
|
||||
if (!nodeObject.childNodes[i].hidden && nodeObject.getAttribute("data-hidden-from-search") !== "true") {
|
||||
let result = this.searchWithinNode(nodeObject.childNodes[i], searchPhrase);
|
||||
// Creating tooltips for menulist element
|
||||
if (result && nodeObject.tagName === "menulist") {
|
||||
this.listSearchTooltips.push(nodeObject);
|
||||
}
|
||||
// Should not search unselected child nodes of a <xul:deck> element
|
||||
// except the "historyPane" <xul:deck> element.
|
||||
if (nodeObject.tagName == "deck" && nodeObject.id != "historyPane") {
|
||||
let index = nodeObject.selectedIndex;
|
||||
if (index != -1) {
|
||||
let result = this.searchChildNodeIfVisible(nodeObject, index, searchPhrase);
|
||||
matchesFound = matchesFound || result;
|
||||
}
|
||||
} else {
|
||||
for (let i = 0; i < nodeObject.childNodes.length; i++) {
|
||||
let result = this.searchChildNodeIfVisible(nodeObject, i, searchPhrase);
|
||||
matchesFound = matchesFound || result;
|
||||
}
|
||||
}
|
||||
return matchesFound;
|
||||
},
|
||||
|
||||
/**
|
||||
* Search for a phrase within a child node if it is visible.
|
||||
*
|
||||
* @param Node nodeObject
|
||||
* The parent DOM Element
|
||||
* @param Number index
|
||||
* The index for the childNode
|
||||
* @param String searchPhrase
|
||||
* @returns boolean
|
||||
* Returns true when found the specific childNode, false otherwise
|
||||
*/
|
||||
searchChildNodeIfVisible(nodeObject, index, searchPhrase) {
|
||||
let result = false;
|
||||
if (!nodeObject.childNodes[index].hidden && nodeObject.getAttribute("data-hidden-from-search") !== "true") {
|
||||
result = this.searchWithinNode(nodeObject.childNodes[index], searchPhrase);
|
||||
// Creating tooltips for menulist element
|
||||
if (result && nodeObject.tagName === "menulist") {
|
||||
this.listSearchTooltips.add(nodeObject);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
},
|
||||
|
||||
/**
|
||||
* Inserting a div structure infront of the DOM element matched textContent.
|
||||
* Then calculation the offsets to position the tooltip in the correct place.
|
||||
|
@ -369,6 +404,14 @@ var gSearchResultsPane = {
|
|||
searchTooltip.parentElement.classList.remove("search-tooltip-parent");
|
||||
searchTooltip.remove();
|
||||
}
|
||||
this.listSearchTooltips = [];
|
||||
this.listSearchTooltips.clear();
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove all indicators on menuitem.
|
||||
*/
|
||||
removeAllSearchMenuitemIndicators() {
|
||||
this.listSearchMenuitemIndicators.forEach((node) => node.removeAttribute("indicator"));
|
||||
this.listSearchMenuitemIndicators.clear();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -168,6 +168,7 @@ function gotoPref(aCategory) {
|
|||
gSearchResultsPane.searchResultsCategory.hidden = true;
|
||||
gSearchResultsPane.findSelection.removeAllRanges();
|
||||
gSearchResultsPane.removeAllSearchTooltips();
|
||||
gSearchResultsPane.removeAllSearchMenuitemIndicators();
|
||||
} else if (!gSearchResultsPane.searchInput.value) {
|
||||
// Something tried to send us to the search results pane without
|
||||
// a query string. Default to the General pane instead.
|
||||
|
|
|
@ -191,9 +191,31 @@
|
|||
</label>
|
||||
<menulist id="historyMode">
|
||||
<menupopup>
|
||||
<menuitem label="&historyHeader.remember.label;" value="remember"/>
|
||||
<menuitem label="&historyHeader.dontremember.label;" value="dontremember"/>
|
||||
<menuitem label="&historyHeader.custom.label;" value="custom"/>
|
||||
<menuitem label="&historyHeader.remember.label;" value="remember" searchkeywords="&rememberDescription.label;
|
||||
&rememberActions.pre.label;
|
||||
&rememberActions.clearHistory.label;
|
||||
&rememberActions.middle.label;
|
||||
&rememberActions.removeCookies.label;
|
||||
&rememberActions.post.label;"/>
|
||||
<menuitem label="&historyHeader.dontremember.label;" value="dontremember" searchkeywords="&dontrememberDescription.label;
|
||||
&dontrememberActions.pre.label;
|
||||
&dontrememberActions.clearHistory.label;
|
||||
&dontrememberActions.post.label;"/>
|
||||
<menuitem label="&historyHeader.custom.label;" value="custom" searchkeywords="&privateBrowsingPermanent2.label;
|
||||
&rememberHistory2.label;
|
||||
&rememberSearchForm.label;
|
||||
&acceptCookies.label;
|
||||
&cookieExceptions.label;
|
||||
&acceptThirdParty.pre.label;
|
||||
&acceptThirdParty.always.label;
|
||||
&acceptThirdParty.visited.label;
|
||||
&acceptThirdParty.never.label;
|
||||
&keepUntil.label;
|
||||
&expire.label;
|
||||
&close.label;
|
||||
&showCookies.label;
|
||||
&clearOnClose.label;
|
||||
&clearOnCloseSettings.label;"/>
|
||||
</menupopup>
|
||||
</menulist>
|
||||
<label>&historyHeader.post.label;</label>
|
||||
|
|
|
@ -13,7 +13,8 @@ skip-if = !updater
|
|||
[browser_bug410900.js]
|
||||
[browser_bug705422.js]
|
||||
[browser_bug731866.js]
|
||||
[browser_search_within_preferences.js]
|
||||
[browser_search_within_preferences_1.js]
|
||||
[browser_search_within_preferences_2.js]
|
||||
[browser_search_subdialogs_within_preferences_1.js]
|
||||
[browser_search_subdialogs_within_preferences_2.js]
|
||||
[browser_search_subdialogs_within_preferences_3.js]
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
/*
|
||||
* This file contains tests for the Preferences search bar.
|
||||
*/
|
||||
"use strict";
|
||||
/**
|
||||
* This file contains tests for the Preferences search bar.
|
||||
*/
|
||||
|
||||
requestLongerTimeout(2);
|
||||
|
||||
|
@ -17,7 +18,9 @@ add_task(async function() {
|
|||
await SpecialPowers.popPrefEnv();
|
||||
});
|
||||
|
||||
// Enabling Searching functionatily. Will display search bar form this testcase forward.
|
||||
/**
|
||||
* Enabling searching functionality. Will display search bar from this testcase forward.
|
||||
*/
|
||||
add_task(async function() {
|
||||
await SpecialPowers.pushPrefEnv({"set": [["browser.preferences.search", true]]});
|
||||
});
|
|
@ -0,0 +1,58 @@
|
|||
"use strict";
|
||||
/**
|
||||
* This file contains tests for the Preferences search bar.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Enabling searching functionality. Will display search bar from this testcase forward.
|
||||
*/
|
||||
add_task(async function() {
|
||||
await SpecialPowers.pushPrefEnv({"set": [["browser.preferences.search", true]]});
|
||||
});
|
||||
|
||||
/**
|
||||
* Test that we only search the selected child of a XUL deck.
|
||||
* When we search "Forget this Email",
|
||||
* it should not show the "Forget this Email" button if the Firefox account is not logged in yet.
|
||||
*/
|
||||
add_task(async function() {
|
||||
await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true});
|
||||
|
||||
// Ensure the "Create Account" button in the hidden child of the <xul:deck>
|
||||
// is selected and displayed on the screen.
|
||||
let weavePrefsDeck = gBrowser.contentDocument.getElementById("weavePrefsDeck");
|
||||
is(weavePrefsDeck.selectedIndex, 0, "Should select the #noFxaAccount child node");
|
||||
let noFxaSignUp = weavePrefsDeck.childNodes[0].querySelector("#noFxaSignUp");
|
||||
is(noFxaSignUp.label, "Create Account", "The Create Account button should exist");
|
||||
|
||||
// Performs search.
|
||||
let searchInput = gBrowser.contentDocument.getElementById("searchInput");
|
||||
searchInput.focus();
|
||||
searchInput.value = "Create Account";
|
||||
searchInput.doCommand();
|
||||
|
||||
let mainPrefTag = gBrowser.contentDocument.getElementById("mainPrefPane");
|
||||
for (let i = 0; i < mainPrefTag.childElementCount; i++) {
|
||||
let child = mainPrefTag.children[i];
|
||||
if (child.id == "header-searchResults" ||
|
||||
child.id == "weavePrefsDeck") {
|
||||
is_element_visible(child, "Should be in search results");
|
||||
} else if (child.id) {
|
||||
is_element_hidden(child, "Should not be in search results");
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure the "Forget this Email" button exists in the hidden child of the <xul:deck>.
|
||||
let unlinkFxaAccount = weavePrefsDeck.childNodes[1].querySelector("#unverifiedUnlinkFxaAccount");
|
||||
is(unlinkFxaAccount.label, "Forget this Email", "The Forget this Email button should exist");
|
||||
|
||||
// Performs search.
|
||||
searchInput.focus();
|
||||
searchInput.value = "Forget this Email";
|
||||
searchInput.doCommand();
|
||||
|
||||
let noResultsEl = gBrowser.contentDocument.querySelector(".no-results-message");
|
||||
is_element_visible(noResultsEl, "Should be reporting no results");
|
||||
|
||||
await BrowserTestUtils.removeTab(gBrowser.selectedTab);
|
||||
});
|
|
@ -3614,7 +3614,6 @@ var SessionStoreInternal = {
|
|||
// Save the index in case we updated it above.
|
||||
tabData.index = activeIndex + 1;
|
||||
|
||||
browser.setAttribute("pending", "true");
|
||||
tab.setAttribute("pending", "true");
|
||||
|
||||
// If we're restoring this tab, it certainly shouldn't be in
|
||||
|
@ -3728,10 +3727,6 @@ var SessionStoreInternal = {
|
|||
}
|
||||
}
|
||||
|
||||
// We have to mark this tab as restoring first, otherwise
|
||||
// the "pending" attribute will be applied to the linked
|
||||
// browser, which removes it from the display list. We cannot
|
||||
// flip the remoteness of any browser that is not being displayed.
|
||||
this.markTabAsRestoring(aTab);
|
||||
|
||||
// We need a new frameloader if we are reloading into a browser with a
|
||||
|
@ -3803,7 +3798,6 @@ var SessionStoreInternal = {
|
|||
|
||||
// Set this tab's state to restoring
|
||||
browser.__SS_restoreState = TAB_STATE_RESTORING;
|
||||
browser.removeAttribute("pending");
|
||||
aTab.removeAttribute("pending");
|
||||
},
|
||||
|
||||
|
@ -4556,7 +4550,6 @@ var SessionStoreInternal = {
|
|||
delete browser.__SS_restoreState;
|
||||
|
||||
aTab.removeAttribute("pending");
|
||||
browser.removeAttribute("pending");
|
||||
|
||||
if (previousState == TAB_STATE_RESTORING) {
|
||||
if (this._tabsRestoringCount)
|
||||
|
|
|
@ -98,7 +98,7 @@ async function crashBackgroundTabs(tabs) {
|
|||
for (let tab of tabs) {
|
||||
Assert.ok(!tab.linkedBrowser.isRemoteBrowser, "tab is not remote");
|
||||
Assert.ok(!tab.linkedBrowser.hasAttribute("crashed"), "tab is not crashed");
|
||||
Assert.ok(tab.linkedBrowser.hasAttribute("pending"), "tab is pending");
|
||||
Assert.ok(tab.hasAttribute("pending"), "tab is pending");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,3 +4,4 @@
|
|||
|
||||
browser.jar:
|
||||
content/browser/content-UITour.js
|
||||
content/browser/UITour-lib.js
|
||||
|
|
|
@ -9,24 +9,9 @@ Cu.import("resource://gre/modules/Services.jsm");
|
|||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Timer.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils",
|
||||
"resource://gre/modules/UpdateUtils.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "TelemetryEnvironment",
|
||||
"resource://gre/modules/TelemetryEnvironment.jsm");
|
||||
|
||||
// The amount of people to be part of the telemetry reporting.
|
||||
const REPORTING_THRESHOLD = {
|
||||
// "default": 1.0, // 100% - self builds, linux distros etc.
|
||||
"nightly": 0.1, // 10%
|
||||
"beta": 0.1, // 10%
|
||||
"release": 0.1, // 10%
|
||||
};
|
||||
|
||||
// Preferences this add-on uses.
|
||||
const kPrefPrefix = "extensions.followonsearch.";
|
||||
const PREF_COHORT_SAMPLE = `${kPrefPrefix}cohortSample`;
|
||||
const PREF_LOGGING = `${kPrefPrefix}logging`;
|
||||
const PREF_CHANNEL_OVERRIDE = `${kPrefPrefix}override`;
|
||||
|
||||
const kExtensionID = "followonsearch@mozilla.com";
|
||||
const kSaveTelemetryMsg = `${kExtensionID}:save-telemetry`;
|
||||
|
@ -89,10 +74,6 @@ function activateTelemetry() {
|
|||
|
||||
Services.mm.addMessageListener(kSaveTelemetryMsg, handleSaveTelemetryMsg);
|
||||
Services.mm.loadFrameScript(frameScript, true);
|
||||
|
||||
// Record the fact we're saving the extra data as a telemetry environment
|
||||
// value.
|
||||
TelemetryEnvironment.setExperimentActive(kExtensionID, "active");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -103,8 +84,6 @@ function deactivateTelemetry() {
|
|||
return;
|
||||
}
|
||||
|
||||
TelemetryEnvironment.setExperimentInactive(kExtensionID);
|
||||
|
||||
Services.mm.removeMessageListener(kSaveTelemetryMsg, handleSaveTelemetryMsg);
|
||||
Services.mm.removeDelayedFrameScript(frameScript);
|
||||
Services.mm.broadcastAsyncMessage(kShutdownMsg);
|
||||
|
@ -137,44 +116,13 @@ var cohortManager = {
|
|||
try {
|
||||
let distId = Services.prefs.getCharPref("distribution.id", "");
|
||||
if (distId) {
|
||||
log("It is a distribution, not setting up nor enabling.");
|
||||
log("It is a distribution, not setting up nor enabling telemetry.");
|
||||
return;
|
||||
}
|
||||
} catch (e) {}
|
||||
|
||||
let cohortSample;
|
||||
try {
|
||||
cohortSample = Services.prefs.getFloatPref(PREF_COHORT_SAMPLE, undefined);
|
||||
} catch (e) {}
|
||||
if (!cohortSample) {
|
||||
cohortSample = Math.random().toString().substr(0, 8);
|
||||
cohortSample = Services.prefs.setCharPref(PREF_COHORT_SAMPLE, cohortSample);
|
||||
}
|
||||
log(`Cohort Sample value is ${cohortSample}`);
|
||||
|
||||
let updateChannel = UpdateUtils.getUpdateChannel(false);
|
||||
log(`Update channel is ${updateChannel}`);
|
||||
if (!(updateChannel in REPORTING_THRESHOLD)) {
|
||||
let prefOverride = "default";
|
||||
try {
|
||||
prefOverride = Services.prefs.getCharPref(PREF_CHANNEL_OVERRIDE, "default");
|
||||
} catch (e) {}
|
||||
if (prefOverride in REPORTING_THRESHOLD) {
|
||||
updateChannel = prefOverride;
|
||||
} else {
|
||||
// Don't enable, we don't know about the channel, and it isn't overriden.
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
log("Not enabling extra telemetry due to bug 1371198");
|
||||
|
||||
// if (cohortSample <= REPORTING_THRESHOLD[updateChannel]) {
|
||||
// log("Enabling telemetry for user");
|
||||
// this.enableForUser = true;
|
||||
// } else {
|
||||
// log("Not enabling telemetry for user - outside threshold.");
|
||||
// }
|
||||
log("Enabling telemetry for user");
|
||||
this.enableForUser = true;
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
"use strict";
|
||||
|
||||
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.importGlobalProperties(["URLSearchParams"]);
|
||||
|
||||
|
@ -18,124 +17,116 @@ const kShutdownMsg = `${kExtensionID}:shutdown`;
|
|||
/**
|
||||
* A map of search domains with their expected codes.
|
||||
*/
|
||||
let searchDomains = {
|
||||
"search.yahoo.co.jp": {
|
||||
"search": "p",
|
||||
"followOnSearch": "ai",
|
||||
"prefix": "fr",
|
||||
"codes": ["mozff"],
|
||||
"sap": "yahoo",
|
||||
},
|
||||
"www.bing.com": {
|
||||
"search": "q",
|
||||
"prefix": "pc",
|
||||
"reportPrefix": "form",
|
||||
"codes": ["MOZI"],
|
||||
"sap": "bing",
|
||||
},
|
||||
};
|
||||
let searchDomains = [{
|
||||
"domains": [ "search.yahoo.co.jp" ],
|
||||
"search": "p",
|
||||
"followOnSearch": "ai",
|
||||
"prefix": "fr",
|
||||
"codes": ["mozff"],
|
||||
"sap": "yahoo",
|
||||
}, {
|
||||
"domains": [ "www.bing.com" ],
|
||||
"search": "q",
|
||||
"prefix": "pc",
|
||||
"reportPrefix": "form",
|
||||
"codes": ["MOZI"],
|
||||
"sap": "bing",
|
||||
}, {
|
||||
// The Yahoo domains to watch for.
|
||||
"domains": [
|
||||
"search.yahoo.com", "ca.search.yahoo.com", "hk.search.yahoo.com",
|
||||
"tw.search.yahoo.com"
|
||||
],
|
||||
"search": "p",
|
||||
"followOnSearch": "fr2",
|
||||
"prefix": "hspart",
|
||||
"reportPrefix": "hsimp",
|
||||
"codes": ["mozilla"],
|
||||
"sap": "yahoo",
|
||||
}, {
|
||||
// The Yahoo legacy domains.
|
||||
"domains": [
|
||||
"no.search.yahoo.com", "ar.search.yahoo.com", "br.search.yahoo.com",
|
||||
"ch.search.yahoo.com", "cl.search.yahoo.com", "de.search.yahoo.com",
|
||||
"uk.search.yahoo.com", "es.search.yahoo.com", "espanol.search.yahoo.com",
|
||||
"fi.search.yahoo.com", "fr.search.yahoo.com", "nl.search.yahoo.com",
|
||||
"id.search.yahoo.com", "in.search.yahoo.com", "it.search.yahoo.com",
|
||||
"mx.search.yahoo.com", "se.search.yahoo.com", "sg.search.yahoo.com",
|
||||
],
|
||||
"search": "p",
|
||||
"followOnSearch": "fr2",
|
||||
"prefix": "fr",
|
||||
"codes": ["moz35"],
|
||||
"sap": "yahoo",
|
||||
}, {
|
||||
// The Google domains.
|
||||
"domains": [
|
||||
"www.google.com", "www.google.ac", "www.google.ad", "www.google.ae",
|
||||
"www.google.com.af", "www.google.com.ag", "www.google.com.ai",
|
||||
"www.google.al", "www.google.am", "www.google.co.ao", "www.google.com.ar",
|
||||
"www.google.as", "www.google.at", "www.google.com.au", "www.google.az",
|
||||
"www.google.ba", "www.google.com.bd", "www.google.be", "www.google.bf",
|
||||
"www.google.bg", "www.google.com.bh", "www.google.bi", "www.google.bj",
|
||||
"www.google.com.bn", "www.google.com.bo", "www.google.com.br",
|
||||
"www.google.bs", "www.google.bt", "www.google.co.bw", "www.google.by",
|
||||
"www.google.com.bz", "www.google.ca", "www.google.com.kh", "www.google.cc",
|
||||
"www.google.cd", "www.google.cf", "www.google.cat", "www.google.cg",
|
||||
"www.google.ch", "www.google.ci", "www.google.co.ck", "www.google.cl",
|
||||
"www.google.cm", "www.google.cn", "www.google.com.co", "www.google.co.cr",
|
||||
"www.google.com.cu", "www.google.cv", "www.google.cx", "www.google.com.cy",
|
||||
"www.google.cz", "www.google.de", "www.google.dj", "www.google.dk",
|
||||
"www.google.dm", "www.google.com.do", "www.google.dz", "www.google.com.ec",
|
||||
"www.google.ee", "www.google.com.eg", "www.google.es", "www.google.com.et",
|
||||
"www.google.eu", "www.google.fi", "www.google.com.fj", "www.google.fm",
|
||||
"www.google.fr", "www.google.ga", "www.google.ge", "www.google.gf",
|
||||
"www.google.gg", "www.google.com.gh", "www.google.com.gi", "www.google.gl",
|
||||
"www.google.gm", "www.google.gp", "www.google.gr", "www.google.com.gt",
|
||||
"www.google.gy", "www.google.com.hk", "www.google.hn", "www.google.hr",
|
||||
"www.google.ht", "www.google.hu", "www.google.co.id", "www.google.iq",
|
||||
"www.google.ie", "www.google.co.il", "www.google.im", "www.google.co.in",
|
||||
"www.google.io", "www.google.is", "www.google.it", "www.google.je",
|
||||
"www.google.com.jm", "www.google.jo", "www.google.co.jp", "www.google.co.ke",
|
||||
"www.google.ki", "www.google.kg", "www.google.co.kr", "www.google.com.kw",
|
||||
"www.google.kz", "www.google.la", "www.google.com.lb", "www.google.com.lc",
|
||||
"www.google.li", "www.google.lk", "www.google.co.ls", "www.google.lt",
|
||||
"www.google.lu", "www.google.lv", "www.google.com.ly", "www.google.co.ma",
|
||||
"www.google.md", "www.google.me", "www.google.mg", "www.google.mk",
|
||||
"www.google.ml", "www.google.com.mm", "www.google.mn", "www.google.ms",
|
||||
"www.google.com.mt", "www.google.mu", "www.google.mv", "www.google.mw",
|
||||
"www.google.com.mx", "www.google.com.my", "www.google.co.mz",
|
||||
"www.google.com.na", "www.google.ne", "www.google.nf", "www.google.com.ng",
|
||||
"www.google.com.ni", "www.google.nl", "www.google.no", "www.google.com.np",
|
||||
"www.google.nr", "www.google.nu", "www.google.co.nz", "www.google.com.om",
|
||||
"www.google.com.pk", "www.google.com.pa", "www.google.com.pe",
|
||||
"www.google.com.ph", "www.google.pl", "www.google.com.pg", "www.google.pn",
|
||||
"www.google.com.pr", "www.google.ps", "www.google.pt", "www.google.com.py",
|
||||
"www.google.com.qa", "www.google.ro", "www.google.rs", "www.google.ru",
|
||||
"www.google.rw", "www.google.com.sa", "www.google.com.sb", "www.google.sc",
|
||||
"www.google.se", "www.google.com.sg", "www.google.sh", "www.google.si",
|
||||
"www.google.sk", "www.google.com.sl", "www.google.sn", "www.google.sm",
|
||||
"www.google.so", "www.google.st", "www.google.sr", "www.google.com.sv",
|
||||
"www.google.td", "www.google.tg", "www.google.co.th", "www.google.com.tj",
|
||||
"www.google.tk", "www.google.tl", "www.google.tm", "www.google.to",
|
||||
"www.google.tn", "www.google.com.tr", "www.google.tt", "www.google.com.tw",
|
||||
"www.google.co.tz", "www.google.com.ua", "www.google.co.ug",
|
||||
"www.google.co.uk", "www.google.us", "www.google.com.uy", "www.google.co.uz",
|
||||
"www.google.com.vc", "www.google.co.ve", "www.google.vg", "www.google.co.vi",
|
||||
"www.google.com.vn", "www.google.vu", "www.google.ws", "www.google.co.za",
|
||||
"www.google.co.zm", "www.google.co.zw",
|
||||
],
|
||||
"search": "q",
|
||||
"prefix": "client",
|
||||
"codes": ["firefox-b-ab", "firefox-b"],
|
||||
"sap": "google",
|
||||
}];
|
||||
|
||||
// The yahoo domains to watch for.
|
||||
const yahooDomains = new Set([
|
||||
"search.yahoo.com", "ca.search.yahoo.com", "hk.search.yahoo.com",
|
||||
"tw.search.yahoo.com",
|
||||
]);
|
||||
|
||||
// Add Yahoo domains to search domains
|
||||
for (let domain of yahooDomains) {
|
||||
searchDomains[domain] = {
|
||||
"search": "p",
|
||||
"followOnSearch": "fr2",
|
||||
"prefix": "hspart",
|
||||
"reportPrefix": "hsimp",
|
||||
"codes": ["mozilla"],
|
||||
"sap": "yahoo",
|
||||
};
|
||||
}
|
||||
|
||||
const yahooLegacyDomains = new Set([
|
||||
"no.search.yahoo.com", "ar.search.yahoo.com", "br.search.yahoo.com",
|
||||
"ch.search.yahoo.com", "cl.search.yahoo.com", "de.search.yahoo.com",
|
||||
"uk.search.yahoo.com", "es.search.yahoo.com", "espanol.search.yahoo.com",
|
||||
"fi.search.yahoo.com", "fr.search.yahoo.com", "nl.search.yahoo.com",
|
||||
"id.search.yahoo.com", "in.search.yahoo.com", "it.search.yahoo.com",
|
||||
"mx.search.yahoo.com", "se.search.yahoo.com", "sg.search.yahoo.com",
|
||||
]);
|
||||
|
||||
// Add Yahoo legacy domains to search domains
|
||||
for (let domain of yahooLegacyDomains) {
|
||||
searchDomains[domain] = {
|
||||
"search": "p",
|
||||
"followOnSearch": "fr2",
|
||||
"prefix": "fr",
|
||||
"codes": ["moz35"],
|
||||
"sap": "yahoo",
|
||||
};
|
||||
}
|
||||
|
||||
const googleDomains = new Set([
|
||||
"www.google.com", "www.google.ac", "www.google.ad", "www.google.ae",
|
||||
"www.google.com.af", "www.google.com.ag", "www.google.com.ai",
|
||||
"www.google.al", "www.google.am", "www.google.co.ao", "www.google.com.ar",
|
||||
"www.google.as", "www.google.at", "www.google.com.au", "www.google.az",
|
||||
"www.google.ba", "www.google.com.bd", "www.google.be", "www.google.bf",
|
||||
"www.google.bg", "www.google.com.bh", "www.google.bi", "www.google.bj",
|
||||
"www.google.com.bn", "www.google.com.bo", "www.google.com.br",
|
||||
"www.google.bs", "www.google.bt", "www.google.co.bw", "www.google.by",
|
||||
"www.google.com.bz", "www.google.ca", "www.google.com.kh", "www.google.cc",
|
||||
"www.google.cd", "www.google.cf", "www.google.cat", "www.google.cg",
|
||||
"www.google.ch", "www.google.ci", "www.google.co.ck", "www.google.cl",
|
||||
"www.google.cm", "www.google.cn", "www.google.com.co", "www.google.co.cr",
|
||||
"www.google.com.cu", "www.google.cv", "www.google.cx", "www.google.com.cy",
|
||||
"www.google.cz", "www.google.de", "www.google.dj", "www.google.dk",
|
||||
"www.google.dm", "www.google.com.do", "www.google.dz", "www.google.com.ec",
|
||||
"www.google.ee", "www.google.com.eg", "www.google.es", "www.google.com.et",
|
||||
"www.google.eu", "www.google.fi", "www.google.com.fj", "www.google.fm",
|
||||
"www.google.fr", "www.google.ga", "www.google.ge", "www.google.gf",
|
||||
"www.google.gg", "www.google.com.gh", "www.google.com.gi", "www.google.gl",
|
||||
"www.google.gm", "www.google.gp", "www.google.gr", "www.google.com.gt",
|
||||
"www.google.gy", "www.google.com.hk", "www.google.hn", "www.google.hr",
|
||||
"www.google.ht", "www.google.hu", "www.google.co.id", "www.google.iq",
|
||||
"www.google.ie", "www.google.co.il", "www.google.im", "www.google.co.in",
|
||||
"www.google.io", "www.google.is", "www.google.it", "www.google.je",
|
||||
"www.google.com.jm", "www.google.jo", "www.google.co.jp", "www.google.co.ke",
|
||||
"www.google.ki", "www.google.kg", "www.google.co.kr", "www.google.com.kw",
|
||||
"www.google.kz", "www.google.la", "www.google.com.lb", "www.google.com.lc",
|
||||
"www.google.li", "www.google.lk", "www.google.co.ls", "www.google.lt",
|
||||
"www.google.lu", "www.google.lv", "www.google.com.ly", "www.google.co.ma",
|
||||
"www.google.md", "www.google.me", "www.google.mg", "www.google.mk",
|
||||
"www.google.ml", "www.google.com.mm", "www.google.mn", "www.google.ms",
|
||||
"www.google.com.mt", "www.google.mu", "www.google.mv", "www.google.mw",
|
||||
"www.google.com.mx", "www.google.com.my", "www.google.co.mz",
|
||||
"www.google.com.na", "www.google.ne", "www.google.nf", "www.google.com.ng",
|
||||
"www.google.com.ni", "www.google.nl", "www.google.no", "www.google.com.np",
|
||||
"www.google.nr", "www.google.nu", "www.google.co.nz", "www.google.com.om",
|
||||
"www.google.com.pk", "www.google.com.pa", "www.google.com.pe",
|
||||
"www.google.com.ph", "www.google.pl", "www.google.com.pg", "www.google.pn",
|
||||
"www.google.com.pr", "www.google.ps", "www.google.pt", "www.google.com.py",
|
||||
"www.google.com.qa", "www.google.ro", "www.google.rs", "www.google.ru",
|
||||
"www.google.rw", "www.google.com.sa", "www.google.com.sb", "www.google.sc",
|
||||
"www.google.se", "www.google.com.sg", "www.google.sh", "www.google.si",
|
||||
"www.google.sk", "www.google.com.sl", "www.google.sn", "www.google.sm",
|
||||
"www.google.so", "www.google.st", "www.google.sr", "www.google.com.sv",
|
||||
"www.google.td", "www.google.tg", "www.google.co.th", "www.google.com.tj",
|
||||
"www.google.tk", "www.google.tl", "www.google.tm", "www.google.to",
|
||||
"www.google.tn", "www.google.com.tr", "www.google.tt", "www.google.com.tw",
|
||||
"www.google.co.tz", "www.google.com.ua", "www.google.co.ug",
|
||||
"www.google.co.uk", "www.google.us", "www.google.com.uy", "www.google.co.uz",
|
||||
"www.google.com.vc", "www.google.co.ve", "www.google.vg", "www.google.co.vi",
|
||||
"www.google.com.vn", "www.google.vu", "www.google.ws", "www.google.co.za",
|
||||
"www.google.co.zm", "www.google.co.zw",
|
||||
]);
|
||||
|
||||
// Add Google domains to search domains
|
||||
for (let domain of googleDomains) {
|
||||
searchDomains[domain] = {
|
||||
"search": "q",
|
||||
"prefix": "client",
|
||||
"codes": ["firefox-b-ab", "firefox-b"],
|
||||
"sap": "google",
|
||||
};
|
||||
function getSearchDomainCodes(host) {
|
||||
for (let domainInfo of searchDomains) {
|
||||
if (domainInfo.domains.includes(host)) {
|
||||
return domainInfo;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -164,15 +155,16 @@ var webProgressListener = {
|
|||
if (!aWebProgress.isTopLevel ||
|
||||
// Not a URL
|
||||
(!aLocation.schemeIs("http") && !aLocation.schemeIs("https")) ||
|
||||
// Not a domain we handle
|
||||
!(aLocation.host in searchDomains) ||
|
||||
// Doesn't have a query string or a ref
|
||||
(!aLocation.query && !aLocation.ref) ||
|
||||
// Is the same as our last search (avoids reloads)
|
||||
aLocation.spec == gLastSearch) {
|
||||
return;
|
||||
}
|
||||
let domainInfo = searchDomains[aLocation.host];
|
||||
let domainInfo = getSearchDomainCodes(aLocation.host);
|
||||
if (!domainInfo) {
|
||||
return;
|
||||
}
|
||||
|
||||
let queries = new URLSearchParams(aLocation.query);
|
||||
let code = queries.get(domainInfo.prefix);
|
||||
|
@ -182,7 +174,7 @@ var webProgressListener = {
|
|||
queries.get(domainInfo.reportPrefix)) {
|
||||
code = queries.get(domainInfo.reportPrefix);
|
||||
}
|
||||
if (googleDomains.has(aLocation.host) && aLocation.ref) {
|
||||
if (domainInfo.sap == "google" && aLocation.ref) {
|
||||
log(`${aLocation.host} search with code ${code} - Follow on`);
|
||||
sendSaveTelemetryMsg(code, domainInfo.sap, "follow-on");
|
||||
} else if (queries.get(domainInfo.followOnSearch)) {
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<Description about="urn:mozilla:install-manifest">
|
||||
<em:id>followonsearch@mozilla.com</em:id>
|
||||
<em:name>Follow-on Search Telemetry</em:name>
|
||||
<em:version>0.8.0</em:version>
|
||||
<em:version>0.9.0</em:version>
|
||||
<em:type>2</em:type>
|
||||
<em:bootstrap>true</em:bootstrap>
|
||||
<em:multiprocessCompatible>true</em:multiprocessCompatible>
|
||||
|
|
|
@ -7,6 +7,8 @@ const TEST_SELECTORS = {
|
|||
btnEdit: "#edit",
|
||||
};
|
||||
|
||||
const DIALOG_SIZE = "width=600,height=400";
|
||||
|
||||
function waitForAddresses() {
|
||||
return new Promise(resolve => {
|
||||
Services.cpmm.addMessageListener("FormAutofill:Addresses", function getResult(result) {
|
||||
|
@ -45,7 +47,7 @@ add_task(async function test_removingSingleAndMultipleProfiles() {
|
|||
await saveAddress(TEST_ADDRESS_2);
|
||||
await saveAddress(TEST_ADDRESS_3);
|
||||
|
||||
let win = window.openDialog(MANAGE_PROFILES_DIALOG_URL);
|
||||
let win = window.openDialog(MANAGE_PROFILES_DIALOG_URL, null, DIALOG_SIZE);
|
||||
await waitForAddresses();
|
||||
|
||||
let selAddresses = win.document.querySelector(TEST_SELECTORS.selAddresses);
|
||||
|
@ -74,7 +76,7 @@ add_task(async function test_removingSingleAndMultipleProfiles() {
|
|||
});
|
||||
|
||||
add_task(async function test_profilesDialogWatchesStorageChanges() {
|
||||
let win = window.openDialog(MANAGE_PROFILES_DIALOG_URL);
|
||||
let win = window.openDialog(MANAGE_PROFILES_DIALOG_URL, null, DIALOG_SIZE);
|
||||
await waitForAddresses();
|
||||
|
||||
let selAddresses = win.document.querySelector(TEST_SELECTORS.selAddresses);
|
||||
|
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
После Ширина: | Высота: | Размер: 52 KiB |
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
После Ширина: | Высота: | Размер: 20 KiB |
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
После Ширина: | Высота: | Размер: 5.8 KiB |
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
После Ширина: | Высота: | Размер: 6.8 KiB |
|
@ -0,0 +1 @@
|
|||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><defs><style>.cls-1{fill:none;}.cls-2{fill:#fff;}.cls-3{fill:#e1e1e5;}.cls-4{fill:#767aa8;}.cls-5{fill:#f9f9fa;}.cls-6{fill:#cd6f14;}</style></defs><title>icons</title><path class="cls-1" d="M5.36,20.85H19.86l5.33,5.81a.63.63,0,0,0,.46.17,2.31,2.31,0,0,0,1.47-.76,2.17,2.17,0,0,0,.75-1.57A2.22,2.22,0,0,1,27.12,26c-.69.69-1.56,1-1.93.59L19.86,20.8H5.36A1.32,1.32,0,0,1,4,19.48V14.35H4v5.17A1.32,1.32,0,0,0,5.36,20.85Z"/><path class="cls-1" d="M24.12,20.8l0,0a1.32,1.32,0,0,0,1.27-1.31v0A1.32,1.32,0,0,1,24.12,20.8Z"/><path class="cls-2" d="M23.6,20.32h.51a.84.84,0,0,0,.84-.84V14.31a.84.84,0,0,0-.84-.84H21.33a7.34,7.34,0,0,1-1.45,3.86l.63.63a.82.82,0,0,1,.77.22Z"/><path class="cls-2" d="M19.06,19.41l-.63-.63a7.39,7.39,0,0,1-11.82-5.3H5.36a.84.84,0,0,0-.84.84v5.17a.84.84,0,0,0,.84.84h14l-.13-.14A.82.82,0,0,1,19.06,19.41Z"/><path class="cls-3" d="M5.36,20.8H19.86l-.44-.48h-14a.84.84,0,0,1-.84-.84V14.31a.84.84,0,0,1,.84-.84H6.61c0-.16,0-.31,0-.47,0,0,0,0,0,0H5.36A1.32,1.32,0,0,0,4,14.35v5.13A1.32,1.32,0,0,0,5.36,20.8Z"/><path class="cls-3" d="M24.1,13.48a.84.84,0,0,1,.84.84v5.17a.84.84,0,0,1-.84.84H23.6l.26.24.26.24a1.32,1.32,0,0,0,1.3-1.32V14.36A1.32,1.32,0,0,0,24.1,13H21.34v0c0,.15,0,.31,0,.46Z"/><path class="cls-4" d="M8.75,18.11a7.4,7.4,0,0,0,9.68.67l.63.63A2.22,2.22,0,0,1,20.51,18l-.63-.63a7.34,7.34,0,0,0,1.45-3.86c0-.15,0-.31,0-.46a7.37,7.37,0,0,0-1.59-4.71l-.1-.12c-.14-.17-.29-.34-.45-.5A7.39,7.39,0,0,0,6.59,13c0,.16,0,.31,0,.47A7.36,7.36,0,0,0,8.75,18.11ZM8.28,10.9a6,6,0,0,1,.43-1h0a6,6,0,0,1,10.46,0h0c0,.07.06.15.1.22s.08.13.11.2l-.06-.08a6.15,6.15,0,0,1,.28.63A6,6,0,0,1,8.87,16.08l0,.08c-.06-.09-.1-.19-.15-.28l-.14-.26c-.05-.11-.11-.21-.15-.32a6,6,0,0,1-.25-.66v0A6,6,0,0,1,8,13.83v-.05a5.79,5.79,0,0,1,.27-2.87Z"/><path class="cls-5" d="M14.36,7.56A6,6,0,0,0,9.58,9.93h9.56A6,6,0,0,0,14.36,7.56Z"/><path class="cls-2" d="M8,12.92a6,6,0,0,1,.35-2h0A5.79,5.79,0,0,0,8,13.77,6.05,6.05,0,0,1,8,12.92Z"/><path class="cls-2" d="M8.33,13.59a6,6,0,0,0,.54,2.49A6,6,0,0,0,19.66,10.9H9A6,6,0,0,0,8.33,13.59Z"/><path class="cls-3" d="M8,12.92a6.05,6.05,0,0,0,.06.86v.05a6,6,0,0,0,.17.78v0a6,6,0,0,0,.25.66c0,.11.1.21.15.32l.14.26c.05.09.1.19.15.28l0-.08A6,6,0,0,1,9,10.9H19.66a6.15,6.15,0,0,0-.28-.63l.06.08c0-.07-.07-.13-.11-.2s-.06-.15-.1-.22h0a6,6,0,0,0-10.46,0h0a6,6,0,0,0-.43,1h0A6,6,0,0,0,8,12.92Zm6.41-5.35a6,6,0,0,1,4.78,2.37H9.58A6,6,0,0,1,14.36,7.56Z"/><path class="cls-6" d="M21.1,22.15,23.25,20l.61.56-.26-.24-2.32-2.13a.82.82,0,0,0-.77-.22,2.22,2.22,0,0,0-1.44,1.44.82.82,0,0,0,.22.77l.13.14.44.48,5.33,5.81c.37.37,1.24.1,1.93-.59a2.22,2.22,0,0,0,.75-1.53.59.59,0,0,0-.16-.36L26.4,22.93,24,25.32Z"/><path class="cls-4" d="M23.86,20.56,23.25,20,21.1,22.15,24,25.32l2.39-2.39-2.28-2.09h0l0,0Z"/><path class="cls-2" d="M5.36,21.85H19.42l5,5.49a1.65,1.65,0,0,0,1.2.49,3.27,3.27,0,0,0,2.18-1c1.12-1.12,1.37-2.56.59-3.34l-2.58-2.37a2.31,2.31,0,0,0,.59-1.54V14.36A2.32,2.32,0,0,0,24.1,12H22.3A8.37,8.37,0,0,0,8,7,8.31,8.31,0,0,0,5.63,12H5.36A2.32,2.32,0,0,0,3,14.36v5.17A2.32,2.32,0,0,0,5.36,21.85ZM4,14.36H4A1.32,1.32,0,0,1,5.36,13H6.59s0,0,0,0A7.39,7.39,0,0,1,19.2,7.69c.16.16.31.33.45.5l.1.12A7.37,7.37,0,0,1,21.34,13v0H24.1a1.32,1.32,0,0,1,1.32,1.32v5.17a1.32,1.32,0,0,1-1.27,1.31h0l2.28,2.09,1.31,1.21a.59.59,0,0,1,.16.36,2.17,2.17,0,0,1-.75,1.57,2.31,2.31,0,0,1-1.47.76.63.63,0,0,1-.46-.17l-5.33-5.81H5.36A1.32,1.32,0,0,1,4,19.52Z"/></svg>
|
После Ширина: | Высота: | Размер: 3.4 KiB |
|
@ -0,0 +1 @@
|
|||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><defs><style>.cls-1{fill:none;}.cls-2{fill:#fff;}.cls-3{fill:#d9ebff;}.cls-4{fill:#f9f9fa;}.cls-5{fill:#e1e1e6;}.cls-6{fill:#59acff;}</style></defs><title>icons</title><path class="cls-1" d="M5.36,20.8A1.32,1.32,0,0,1,4,19.48v0a1.32,1.32,0,0,0,1.32,1.32H19.86l1.2,1.31,0,0L19.86,20.8Z"/><path class="cls-1" d="M24.35,20.75l0,0a1.3,1.3,0,0,0,1-1.26v0A1.3,1.3,0,0,1,24.35,20.75Z"/><path class="cls-1" d="M25.19,26.61,24,25.32l0,0,1.2,1.31a.63.63,0,0,0,.46.17,2.31,2.31,0,0,0,1.47-.76,2.17,2.17,0,0,0,.75-1.57A2.22,2.22,0,0,1,27.12,26C26.43,26.72,25.56,27,25.19,26.61Z"/><path class="cls-2" d="M4.53,14.31v5.17a.84.84,0,0,0,.84.84h14l-.13-.14a.82.82,0,0,1-.22-.77l-.63-.63a7.38,7.38,0,0,1-8.11.49.24.24,0,0,1,0,.15.25.25,0,0,1-.22.13.26.26,0,0,1-.12,0,7.75,7.75,0,0,1-3.78-6H5.36A.84.84,0,0,0,4.53,14.31Z"/><path class="cls-2" d="M20.34,17.8l0,0c.05,0,.53-.37,1.33.48.29.31,1.24,1.19,2.2,2.07h.23a.84.84,0,0,0,.84-.84V14.31a.84.84,0,0,0-.84-.84H21.8A6.19,6.19,0,0,1,21,16.2l0,.07a.24.24,0,0,1-.23.16h-.08a.23.23,0,0,1-.11-.1,7.32,7.32,0,0,1-.64,1Z"/><path class="cls-3" d="M19.87,17.33a7.32,7.32,0,0,0,.64-1,.25.25,0,0,1,0-.22.88.88,0,0,1,.08-.15,5.77,5.77,0,0,0,.78-3.17,7.35,7.35,0,0,0-1.58-4.45l-.1-.12c-.14-.17-.29-.34-.45-.5a7.39,7.39,0,0,0-12.59,4.6,7.58,7.58,0,0,0,3.6,6.78.24.24,0,0,1,.11.2,7.38,7.38,0,0,0,8.11-.49l.63.63a.82.82,0,0,0,.22.77l.13.14h-14a.84.84,0,0,1-.84-.84V14.31a.84.84,0,0,1,.84-.84h.81c0-.16,0-.3,0-.44H5.36A1.32,1.32,0,0,0,4,14.36v5.13A1.32,1.32,0,0,0,5.36,20.8H19.86l1.22,1.33,2-1.92c-.77-.72-1.46-1.36-1.71-1.63s-.62-.44-.68-.41a.24.24,0,0,1-.34,0s0-.06,0-.09a.25.25,0,0,1,.06-.24ZM14,18.91a6,6,0,0,1-5.1-2.82l0,.08c-.06-.09-.1-.19-.15-.28l-.14-.26c-.05-.11-.11-.21-.15-.32a6,6,0,0,1-.25-.66v0A6,6,0,0,1,8,13.83v-.05a6.06,6.06,0,0,1,.7-3.84h0a6,6,0,0,1,10.46,0l.12.22c0,.06.08.13.11.2l-.06-.08-.24-.33H9.58a6,6,0,0,0-.61,1H19.66a6,6,0,0,1-5.69,8Z"/><path class="cls-3" d="M24.94,14.31v5.17a.84.84,0,0,1-.84.84h-.23l.47.43a1.3,1.3,0,0,0,1.07-1.27V14.36A1.32,1.32,0,0,0,24.1,13H21.82c0,.15,0,.3,0,.44H24.1A.84.84,0,0,1,24.94,14.31Z"/><path class="cls-4" d="M14.36,7.56A6,6,0,0,0,9.58,9.93h9.56A6,6,0,0,0,14.36,7.56Z"/><path class="cls-2" d="M19.66,10.9H9a6,6,0,0,0-.1,5.18A6,6,0,0,0,19.66,10.9Z"/><path class="cls-2" d="M8.31,10.9h0A5.79,5.79,0,0,0,8,13.77a5.93,5.93,0,0,1,.29-2.87Z"/><path class="cls-5" d="M8.75,9.93h0a6,6,0,0,0-.43,1h0A6,6,0,0,1,8.75,9.93Z"/><path class="cls-6" d="M9,10.9a6,6,0,0,1,10.17-1l.24.33.06.08c0-.07-.07-.13-.11-.2l-.12-.22A6,6,0,0,0,8,13.77v.05a6,6,0,0,0,.17.78v0a6,6,0,0,0,.25.66c0,.11.1.21.15.32l.14.26c.05.09.1.19.15.28l0-.08A6,6,0,0,1,9,10.9Z"/><path class="cls-3" d="M26.4,22.93,24,25.32l1.18,1.29c.37.37,1.24.1,1.93-.59a2.22,2.22,0,0,0,.75-1.53.59.59,0,0,0-.16-.36Z"/><path class="cls-6" d="M28.42,23.43l-2.58-2.37a2.31,2.31,0,0,0,.59-1.54V14.36A2.32,2.32,0,0,0,24.1,12H22.3A8.37,8.37,0,0,0,8,7,8.31,8.31,0,0,0,5.63,12H5.36A2.32,2.32,0,0,0,3,14.36v5.17a2.32,2.32,0,0,0,2.32,2.32H19.42l5,5.49a1.65,1.65,0,0,0,1.2.49,3.27,3.27,0,0,0,2.18-1C29,25.65,29.2,24.22,28.42,23.43ZM20.28,18s0,.06,0,.09a.24.24,0,0,0,.34,0c.06,0,.29,0,.68.41s.94.91,1.71,1.63l-2,1.92,0,0-1.2-1.31H5.36A1.32,1.32,0,0,1,4,19.52V14.36A1.32,1.32,0,0,1,5.36,13h.77c0,.14,0,.28,0,.44a7.75,7.75,0,0,0,3.78,6,.26.26,0,0,0,.12,0,.25.25,0,0,0,.22-.12.24.24,0,0,0,0-.15.24.24,0,0,0-.11-.2,7.58,7.58,0,0,1-3.6-6.78A7.39,7.39,0,0,1,19.2,7.69c.16.16.31.33.45.5l.1.12a7.35,7.35,0,0,1,1.58,4.45,5.77,5.77,0,0,1-.78,3.17.88.88,0,0,0-.08.15.25.25,0,0,0,0,.22.23.23,0,0,0,.11.1h.08a.24.24,0,0,0,.23-.16l0-.07a6.19,6.19,0,0,0,.82-2.72c0-.14,0-.29,0-.44H24.1a1.32,1.32,0,0,1,1.32,1.32v5.17a1.3,1.3,0,0,1-1,1.26l0,0-.47-.43c-1-.88-1.9-1.76-2.2-2.07-.8-.85-1.28-.52-1.33-.48l0,0A.25.25,0,0,0,20.28,18Zm6.84,8a2.31,2.31,0,0,1-1.47.76.63.63,0,0,1-.46-.17L24,25.34l0,0,2.39-2.39,1.31,1.21a.59.59,0,0,1,.16.36A2.17,2.17,0,0,1,27.12,26.07Z"/></svg>
|
После Ширина: | Высота: | Размер: 3.9 KiB |
|
@ -0,0 +1,19 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/* globals Mozilla */
|
||||
|
||||
"use strict";
|
||||
|
||||
document.getElementById("onboarding-overlay-dialog")
|
||||
.addEventListener("click", evt => {
|
||||
switch (evt.target.id) {
|
||||
case "onboarding-tour-private-browsing-button":
|
||||
Mozilla.UITour.showHighlight("privateWindow");
|
||||
break;
|
||||
case "onboarding-tour-search-button":
|
||||
Mozilla.UITour.openSearchPanel(() => {});
|
||||
break;
|
||||
}
|
||||
});
|
|
@ -19,13 +19,13 @@
|
|||
display: none;
|
||||
}
|
||||
|
||||
#onboarding-overlay.opened {
|
||||
#onboarding-overlay.onboarding-opened {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#onboarding-overlay-icon {
|
||||
width: 52px;
|
||||
height: 40px;
|
||||
width: 36px;
|
||||
height: 29px;
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
top: 30px;
|
||||
|
@ -54,7 +54,7 @@
|
|||
background-color: rgba(204, 204, 204, 0.6);
|
||||
}
|
||||
|
||||
#onboarding-overlay.opened > #onboarding-overlay-dialog {
|
||||
#onboarding-overlay.onboarding-opened > #onboarding-overlay-dialog {
|
||||
width: 1200px;
|
||||
height: 550px;
|
||||
background: #f5f5f7;
|
||||
|
@ -68,7 +68,7 @@
|
|||
}
|
||||
|
||||
@media (max-height: 550px) {
|
||||
#onboarding-overlay.opened > #onboarding-overlay-dialog {
|
||||
#onboarding-overlay.onboarding-opened > #onboarding-overlay-dialog {
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
|
@ -97,3 +97,125 @@
|
|||
grid-row: footer-start;
|
||||
grid-column: dialog-start / tour-end;
|
||||
}
|
||||
|
||||
/* Onboarding tour list */
|
||||
#onboarding-tour-list {
|
||||
margin: 40px 0 0 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#onboarding-tour-list > li {
|
||||
list-style: none;
|
||||
height: 48px;
|
||||
border-inline-start: 6px solid transparent;
|
||||
padding-inline-start: 66px;
|
||||
line-height: 48px;
|
||||
background-repeat: no-repeat;
|
||||
background-position: left 27px center;
|
||||
background-size: 34px;
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#onboarding-tour-list > li:dir(rtl) {
|
||||
background-position: right 27px center;
|
||||
}
|
||||
|
||||
#onboarding-tour-list > li.onboarding-active,
|
||||
#onboarding-tour-list > li:hover {
|
||||
border-inline-start-color: #5ce6e6;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
/* Onboarding tour pages */
|
||||
.onboarding-tour-page {
|
||||
grid-row: page-start / footer-end;
|
||||
grid-column: page-start;
|
||||
display: grid;
|
||||
grid-template-rows: [tour-page-start] 393px [tour-button-start] 1fr [tour-page-end];
|
||||
grid-template-columns: [tour-page-start] 480px [tour-content-start] 1fr [tour-page-end];
|
||||
}
|
||||
|
||||
.onboarding-tour-description {
|
||||
grid-row: tour-page-start / tour-page-end;
|
||||
grid-column: tour-page-start / tour-content-start;
|
||||
padding: 40px;
|
||||
font-size: 15px;
|
||||
padding-inline-end: 40px;
|
||||
}
|
||||
|
||||
.onboarding-tour-description > h1 {
|
||||
font-size: 36px;
|
||||
margin: 40px 0 20px 0;
|
||||
}
|
||||
|
||||
.onboarding-tour-content {
|
||||
grid-row: tour-page-start / tour-button-start;
|
||||
grid-column: tour-content-start / tour-page-end;
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
padding-inline-start: 0;
|
||||
padding-inline-end: 27px;
|
||||
}
|
||||
|
||||
.onboarding-tour-content > img {
|
||||
width: 352px;
|
||||
margin: 0 calc(50% - 176px);
|
||||
}
|
||||
|
||||
.onboarding-tour-content > iframe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.onboarding-tour-page.onboarding-no-button > .onboarding-tour-content {
|
||||
grid-row: tour-page-start / tour-page-end;
|
||||
grid-column: tour-content-start / tour-page-end;
|
||||
}
|
||||
|
||||
.onboarding-tour-button {
|
||||
grid-row: tour-button-start / tour-page-end;
|
||||
grid-column: tour-content-start / tour-page-end;
|
||||
}
|
||||
|
||||
.onboarding-tour-page.onboarding-no-button > .onboarding-tour-button {
|
||||
display: none;
|
||||
grid-row: tour-page-end;
|
||||
grid-column: tour-page-end;
|
||||
}
|
||||
|
||||
.onboarding-tour-button > button {
|
||||
background: #0d96ff;
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
padding: 10px 20px;
|
||||
font-size: 14px;
|
||||
color: #fff;
|
||||
box-shadow: 0 1px 0 rgba(0,0,0,0.23);
|
||||
float: inline-end;
|
||||
margin-inline-end: 70px;
|
||||
}
|
||||
|
||||
.onboarding-tour-button > button:active {
|
||||
background: #0881dd;
|
||||
}
|
||||
|
||||
/* Tour Icons */
|
||||
#onboarding-tour-search {
|
||||
background-image: url("img/icons_search.svg");
|
||||
}
|
||||
|
||||
#onboarding-tour-search.onboarding-active,
|
||||
#onboarding-tour-search:hover {
|
||||
background-image: url("img/icons_search-colored.svg");
|
||||
}
|
||||
|
||||
#onboarding-tour-private-browsing {
|
||||
background-image: url("img/icons_private.svg");
|
||||
}
|
||||
|
||||
#onboarding-tour-private-browsing.onboarding-active,
|
||||
#onboarding-tour-private-browsing:hover {
|
||||
background-image: url("img/icons_private-colored.svg");
|
||||
}
|
||||
|
|
|
@ -13,6 +13,87 @@ const ONBOARDING_CSS_URL = "resource://onboarding/onboarding.css";
|
|||
const ABOUT_HOME_URL = "about:home";
|
||||
const ABOUT_NEWTAB_URL = "about:newtab";
|
||||
const BUNDLE_URI = "chrome://onboarding/locale/onboarding.properties";
|
||||
const UITOUR_JS_URI = "chrome://browser/content/UITour-lib.js";
|
||||
const TOUR_AGENT_JS_URI = "resource://onboarding/onboarding-tour-agent.js";
|
||||
const BRAND_SHORT_NAME = Services.strings
|
||||
.createBundle("chrome://branding/locale/brand.properties")
|
||||
.GetStringFromName("brandShortName");
|
||||
|
||||
/**
|
||||
* Add any number of tours, following the format
|
||||
* {
|
||||
* // The unique tour id
|
||||
* id: "onboarding-tour-addons",
|
||||
* // The string id of tour name which would be displayed on the navigation bar
|
||||
* tourNameId: "onboarding.tour-addon",
|
||||
* // Return a div appended with elements for this tours.
|
||||
* // Each tour should contain the following 3 sections in the div:
|
||||
* // .onboarding-tour-description, .onboarding-tour-content, .onboarding-tour-button.
|
||||
* // Add no-button css class in the div if this tour does not need a button.
|
||||
* // The overlay layout will responsively position and distribute space for these 3 sections based on viewport size
|
||||
* getPage() {},
|
||||
* isCompleted() {},
|
||||
* setCompleted() {},
|
||||
* },
|
||||
**/
|
||||
var onboardingTours = [
|
||||
{
|
||||
id: "onboarding-tour-private-browsing",
|
||||
tourNameId: "onboarding.tour-private-browsing",
|
||||
getPage(win) {
|
||||
let div = win.document.createElement("div");
|
||||
div.innerHTML = `
|
||||
<section class="onboarding-tour-description">
|
||||
<h1 data-l10n-id="onboarding.tour-private-browsing.title"></h1>
|
||||
<p data-l10n-id="onboarding.tour-private-browsing.description"></p>
|
||||
</section>
|
||||
<section class="onboarding-tour-content">
|
||||
<img src="resource://onboarding/img/figure_private.svg" />
|
||||
</section>
|
||||
<aside class="onboarding-tour-button">
|
||||
<button id="onboarding-tour-private-browsing-button" data-l10n-id="onboarding.tour-private-browsing.button"></button>
|
||||
</aside>
|
||||
`;
|
||||
return div;
|
||||
},
|
||||
isCompleted() {
|
||||
// TODO: determine completion by looking up preferences.
|
||||
return false;
|
||||
},
|
||||
setCompleted() {
|
||||
// TODO: set completion to preferences.
|
||||
return true;
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "onboarding-tour-search",
|
||||
tourNameId: "onboarding.tour-search",
|
||||
getPage(win) {
|
||||
let div = win.document.createElement("div");
|
||||
div.innerHTML = `
|
||||
<section class="onboarding-tour-description">
|
||||
<h1 data-l10n-id="onboarding.tour-search.title"></h1>
|
||||
<p data-l10n-id="onboarding.tour-search.description"></p>
|
||||
</section>
|
||||
<section class="onboarding-tour-content">
|
||||
<img src="resource://onboarding/img/figure_search.svg" />
|
||||
</section>
|
||||
<aside class="onboarding-tour-button">
|
||||
<button id="onboarding-tour-search-button" data-l10n-id="onboarding.tour-search.button"></button>
|
||||
</aside>
|
||||
`;
|
||||
return div;
|
||||
},
|
||||
isCompleted() {
|
||||
// TODO: determine completion by looking up preferences.
|
||||
return false;
|
||||
},
|
||||
setCompleted() {
|
||||
// TODO: set completion to preferences.
|
||||
return true;
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* The script won't be initialized if we turned off onboarding by
|
||||
|
@ -26,6 +107,8 @@ class Onboarding {
|
|||
|
||||
async init(contentWindow) {
|
||||
this._window = contentWindow;
|
||||
this._tourItems = [];
|
||||
this._tourPages = [];
|
||||
// We want to create and append elements after CSS is loaded so
|
||||
// no flash of style changes and no additional reflow.
|
||||
await this._loadCSS();
|
||||
|
@ -34,6 +117,9 @@ class Onboarding {
|
|||
this._window.document.body.appendChild(this._overlayIcon);
|
||||
this._window.document.body.appendChild(this._overlay);
|
||||
|
||||
this._loadJS(UITOUR_JS_URI);
|
||||
this._loadJS(TOUR_AGENT_JS_URI);
|
||||
|
||||
this._overlayIcon.addEventListener("click", this);
|
||||
this._overlay.addEventListener("click", this);
|
||||
// Destroy on unload. This is to ensure we remove all the stuff we left.
|
||||
|
@ -52,6 +138,9 @@ class Onboarding {
|
|||
this.toggleOverlay();
|
||||
break;
|
||||
}
|
||||
if (evt.target.classList.contains("onboarding-tour-item")) {
|
||||
this.gotoPage(evt.target.id);
|
||||
}
|
||||
}
|
||||
|
||||
destroy() {
|
||||
|
@ -60,13 +149,29 @@ class Onboarding {
|
|||
}
|
||||
|
||||
toggleOverlay() {
|
||||
this._overlay.classList.toggle("opened");
|
||||
if (this._tourItems.length == 0) {
|
||||
// Lazy loading until first toggle.
|
||||
this._loadTours(onboardingTours);
|
||||
}
|
||||
|
||||
this._overlay.classList.toggle("onboarding-opened");
|
||||
}
|
||||
|
||||
gotoPage(tourId) {
|
||||
let targetPageId = `${tourId}-page`;
|
||||
for (let page of this._tourPages) {
|
||||
page.style.display = page.id != targetPageId ? "none" : "";
|
||||
}
|
||||
for (let li of this._tourItems) {
|
||||
if (li.id == tourId) {
|
||||
li.classList.add("onboarding-active");
|
||||
} else {
|
||||
li.classList.remove("onboarding-active");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_renderOverlay() {
|
||||
const BRAND_SHORT_NAME = Services.strings
|
||||
.createBundle("chrome://branding/locale/brand.properties")
|
||||
.GetStringFromName("brandShortName");
|
||||
let div = this._window.document.createElement("div");
|
||||
div.id = "onboarding-overlay";
|
||||
// Here we use `innerHTML` is for more friendly reading.
|
||||
|
@ -77,9 +182,9 @@ class Onboarding {
|
|||
<span id="onboarding-overlay-close-btn"></span>
|
||||
<header id="onboarding-header"></header>
|
||||
<nav>
|
||||
<ul></ul>
|
||||
<ul id="onboarding-tour-list"></ul>
|
||||
</nav>
|
||||
<footer>
|
||||
<footer id="onboarding-footer">
|
||||
</footer>
|
||||
</div>
|
||||
`;
|
||||
|
@ -95,6 +200,47 @@ class Onboarding {
|
|||
return img;
|
||||
}
|
||||
|
||||
_loadTours(tours) {
|
||||
let itemsFrag = this._window.document.createDocumentFragment();
|
||||
let pagesFrag = this._window.document.createDocumentFragment();
|
||||
for (let tour of tours) {
|
||||
// Create tour navigation items dynamically
|
||||
let li = this._window.document.createElement("li");
|
||||
li.textContent = this._bundle.GetStringFromName(tour.tourNameId);
|
||||
li.id = tour.id;
|
||||
li.className = "onboarding-tour-item";
|
||||
itemsFrag.appendChild(li);
|
||||
// Dynamically create tour pages
|
||||
let div = tour.getPage(this._window);
|
||||
|
||||
// Do a traverse for elements in the page that need to be localized.
|
||||
let l10nElements = div.querySelectorAll("[data-l10n-id]");
|
||||
for (let i = 0; i < l10nElements.length; i++) {
|
||||
let element = l10nElements[i];
|
||||
// We always put brand short name as the first argument for it's the
|
||||
// only and frequently used arguments in our l10n case. Rewrite it if
|
||||
// other arguments appears.
|
||||
element.textContent = this._bundle.formatStringFromName(
|
||||
element.dataset.l10nId, [BRAND_SHORT_NAME], 1);
|
||||
}
|
||||
|
||||
div.id = `${tour.id}-page`;
|
||||
div.classList.add("onboarding-tour-page");
|
||||
div.style.display = "none";
|
||||
pagesFrag.appendChild(div);
|
||||
// Cache elements in arrays for later use to avoid cost of querying elements
|
||||
this._tourItems.push(li);
|
||||
this._tourPages.push(div);
|
||||
}
|
||||
|
||||
let dialog = this._window.document.getElementById("onboarding-overlay-dialog");
|
||||
let ul = this._window.document.getElementById("onboarding-tour-list");
|
||||
ul.appendChild(itemsFrag);
|
||||
let footer = this._window.document.getElementById("onboarding-footer");
|
||||
dialog.insertBefore(pagesFrag, footer);
|
||||
this.gotoPage(tours[0].id);
|
||||
}
|
||||
|
||||
_loadCSS() {
|
||||
// Returning a Promise so we can inform caller of loading complete
|
||||
// by resolving it.
|
||||
|
@ -108,6 +254,14 @@ class Onboarding {
|
|||
doc.head.appendChild(link);
|
||||
});
|
||||
}
|
||||
|
||||
_loadJS(uri) {
|
||||
let doc = this._window.document;
|
||||
let script = doc.createElement("script");
|
||||
script.type = "text/javascript";
|
||||
script.src = uri;
|
||||
doc.head.appendChild(script);
|
||||
}
|
||||
}
|
||||
|
||||
addEventListener("load", function onLoad(evt) {
|
||||
|
|
|
@ -1,7 +1,23 @@
|
|||
# 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/.
|
||||
|
||||
# LOCALIZATION NOTE(onboarding.tour-title): This string will be used in the overlay title
|
||||
# %S is brandShortName
|
||||
# LOCALIZATION NOTE(onboarding.overlay-title): This string will be used in the
|
||||
# overlay title. %S is brandShortName.
|
||||
onboarding.overlay-title=Getting started with %S
|
||||
onboarding.tour-search=One-Click Search
|
||||
onboarding.tour-search.title=Find the needle or the haystack.
|
||||
|
||||
# LOCALIZATION NOTE (onboarding.tour-search.description): If Amazon is not part
|
||||
# of the default searchplugins for your locale, you can replace it with another
|
||||
# ecommerce website (if you're shipping one), but not with a general purpose
|
||||
# search engine (Google, Bing, Yandex, etc.). Alternatively, only reference
|
||||
# Wikipedia and drop Amazon from the text.
|
||||
onboarding.tour-search.description=Having a default search engine doesn’t mean it’s the only one you use. Pick a search engine or a site, like Amazon or Wikipedia, to search on the fly.
|
||||
onboarding.tour-search.button=Open One-Click Search
|
||||
onboarding.tour-private-browsing=Private Browsing
|
||||
onboarding.tour-private-browsing.title=A little privacy goes a long way.
|
||||
|
||||
# LOCALIZATION NOTE(onboarding.tour-private-browsing.description): %S is
|
||||
# brandShortName.
|
||||
onboarding.tour-private-browsing.description=Browse the internet without saving your searches or the sites you visited. When your session ends, the cookies disappear from %S like they were never there.
|
||||
onboarding.tour-private-browsing.button=Show Private Browsing in Menu
|
||||
|
|
|
@ -128,6 +128,7 @@ VIAddVersionKey "ProductVersion" "${AppVersion}"
|
|||
!define NOW_INSTALLING_TOP_DU 70u
|
||||
!define INSTALL_BLURB_TOP_DU 137u
|
||||
!define INSTALL_FOOTER_TOP_DU -48u
|
||||
!define INSTALL_FOOTER_WIDTH_DU 300u
|
||||
!define PROGRESS_BAR_TOP_DU 112u
|
||||
!define APPNAME_BMP_EDGE_DU 19u
|
||||
!define APPNAME_BMP_TOP_DU 12u
|
||||
|
|
|
@ -831,9 +831,17 @@ Function createInstall
|
|||
|
||||
StrCpy $CurrentBlurbIdx "0"
|
||||
|
||||
; In some locales, the footer message may be too long to fit on one line.
|
||||
; Figure out how much height it needs and give it that much.
|
||||
${GetTextExtent} "$(STUB_BLURB_FOOTER)" $FontFooter $R1 $R2
|
||||
StrCpy $1 0
|
||||
${While} $R1 > 0
|
||||
IntOp $1 $1 + $R2
|
||||
IntOp $R1 $R1 - ${INSTALL_FOOTER_WIDTH_DU}
|
||||
${EndWhile}
|
||||
nsDialogs::CreateControl STATIC ${DEFAULT_STYLES}|${SS_NOTIFY}|${SS_RIGHT} \
|
||||
${WS_EX_TRANSPARENT} -433u ${INSTALL_FOOTER_TOP_DU} 400u 20u \
|
||||
"$(STUB_BLURB_FOOTER)"
|
||||
${WS_EX_TRANSPARENT} -320u ${INSTALL_FOOTER_TOP_DU} \
|
||||
${INSTALL_FOOTER_WIDTH_DU} "$1u" "$(STUB_BLURB_FOOTER)"
|
||||
Pop $0
|
||||
SendMessage $0 ${WM_SETFONT} $FontFooter 0
|
||||
SetCtlColors $0 ${INSTALL_BLURB_TEXT_COLOR} transparent
|
||||
|
|
|
@ -101,6 +101,7 @@ webextPerms.description.clipboardWrite=Input data to the clipboard
|
|||
webextPerms.description.downloads=Download files and read and modify the browser’s download history
|
||||
webextPerms.description.geolocation=Access your location
|
||||
webextPerms.description.history=Access browsing history
|
||||
webextPerms.description.management=Monitor extension usage and manage themes
|
||||
# LOCALIZATION NOTE (webextPerms.description.nativeMessaging)
|
||||
# %S will be replaced with the name of the application
|
||||
webextPerms.description.nativeMessaging=Exchange messages with programs other than %S
|
||||
|
|
|
@ -356,12 +356,6 @@ photonpanelmultiview panelview[title] {
|
|||
padding-top: 0;
|
||||
}
|
||||
|
||||
photonpanelmultiview .panel-subview-body {
|
||||
/*XXXmikedeboer this flex is unnecessary, so I unset it for our case. It might
|
||||
break other panels, though, so I refrain from removing it above. */
|
||||
-moz-box-flex: unset;
|
||||
}
|
||||
|
||||
/* END photonpanelview adjustments */
|
||||
|
||||
.cui-widget-panel.cui-widget-panelWithFooter > .panel-arrowcontainer > .panel-arrowcontent {
|
||||
|
|
|
@ -637,3 +637,17 @@ description > html|a {
|
|||
.search-tooltip-parent {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
menulist[indicator=true] > menupopup menuitem:not([image]) > .menu-iconic-left {
|
||||
display: -moz-box;
|
||||
width: 10px;
|
||||
min-width: auto; /* Override the min-width defined in menu.css */
|
||||
height: 10px;
|
||||
margin-inline-end: 6px;
|
||||
}
|
||||
|
||||
menulist[indicator=true] > menupopup menuitem[indicator=true]:not([image]) > .menu-iconic-left {
|
||||
background-image: url(chrome://global/skin/icons/search-arrow-indicator.svg);
|
||||
background-repeat: no-repeat;
|
||||
background-size: 12px 10px;
|
||||
}
|
||||
|
|
|
@ -338,9 +338,6 @@ essentially the same as the above mentioned templates, prefixed with "Gecko":
|
|||
- ``GeckoSharedLibrary``
|
||||
- ``GeckoFramework``
|
||||
|
||||
There is also ``XPCOMBinaryComponent`` for XPCOM components, which is a
|
||||
special kind of library.
|
||||
|
||||
All the Gecko-prefixed templates take the same arguments as their
|
||||
non-Gecko-prefixed counterparts, and can take a few more arguments
|
||||
for non-standard cases. See the definition of ``GeckoBinary`` in
|
||||
|
|
|
@ -14,7 +14,10 @@ Linking Rust Crates into libxul
|
|||
|
||||
Rust crates that you want to link into libxul should be listed in the
|
||||
``dependencies`` section of `toolkit/library/rust/shared/Cargo.toml <https://dxr.mozilla.org/mozilla-central/source/toolkit/library/rust/shared/Cargo.toml>`_.
|
||||
You'll also need to add an ``extern crate`` reference to `toolkit/library/rust/shared/lib.rs <https://dxr.mozilla.org/mozilla-central/source/toolkit/library/rust/shared/lib.rs>`_.
|
||||
After adding your crate, execute ``cargo update -p gkrust-shared`` in
|
||||
``toolkit/library/rust`` to update the Cargo.lock file. You'll also
|
||||
need to add an ``extern crate`` reference to
|
||||
`toolkit/library/rust/shared/lib.rs <https://dxr.mozilla.org/mozilla-central/source/toolkit/library/rust/shared/lib.rs>`_.
|
||||
This ensures that the Rust code will be linked properly into libxul as well
|
||||
as the copy of libxul used for gtests.
|
||||
|
||||
|
|
|
@ -148,14 +148,3 @@ def GeckoFramework(name, **kwargs):
|
|||
kwargs.setdefault('mozglue', 'library')
|
||||
|
||||
GeckoBinary(**kwargs)
|
||||
|
||||
|
||||
@template
|
||||
def XPCOMBinaryComponent(name):
|
||||
'''Template defining an XPCOM binary component for Gecko.
|
||||
|
||||
`name` is the name of the component.
|
||||
'''
|
||||
GeckoSharedLibrary(name)
|
||||
|
||||
IS_COMPONENT = True
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
_NSModule
|
|
@ -1,7 +0,0 @@
|
|||
EXPORTED {
|
||||
global:
|
||||
NSModule;
|
||||
NSGetModule;
|
||||
__RLD_MAP;
|
||||
local: *;
|
||||
};
|
|
@ -14,18 +14,10 @@ PROGRAMS_TARGET := target
|
|||
INSTALL_TARGETS += PROGRAMS
|
||||
endif
|
||||
|
||||
ifdef LIBRARY
|
||||
ifdef DIST_INSTALL
|
||||
ifdef IS_COMPONENT
|
||||
$(error Shipping static component libs makes no sense.)
|
||||
endif
|
||||
endif # DIST_INSTALL
|
||||
endif # LIBRARY
|
||||
|
||||
|
||||
ifdef SHARED_LIBRARY
|
||||
SHARED_LIBRARY_FILES = $(SHARED_LIBRARY)
|
||||
SHARED_LIBRARY_DEST ?= $(FINAL_TARGET)$(if $(IS_COMPONENT),/components)
|
||||
SHARED_LIBRARY_DEST ?= $(FINAL_TARGET)
|
||||
SHARED_LIBRARY_TARGET = target
|
||||
INSTALL_TARGETS += SHARED_LIBRARY
|
||||
endif # SHARED_LIBRARY
|
||||
|
|
|
@ -328,23 +328,12 @@ ifneq ($(HOST_CPPSRCS)$(HOST_CMMSRCS),)
|
|||
HOST_CPP_PROG_LINK = 1
|
||||
endif
|
||||
|
||||
#
|
||||
# This will strip out symbols that the component should not be
|
||||
# exporting from the .dynsym section.
|
||||
#
|
||||
ifdef IS_COMPONENT
|
||||
EXTRA_DSO_LDOPTS += $(MOZ_COMPONENTS_VERSION_SCRIPT_LDFLAGS)
|
||||
endif # IS_COMPONENT
|
||||
|
||||
#
|
||||
# MacOS X specific stuff
|
||||
#
|
||||
|
||||
ifeq ($(OS_ARCH),Darwin)
|
||||
ifdef SHARED_LIBRARY
|
||||
ifdef IS_COMPONENT
|
||||
EXTRA_DSO_LDOPTS += -bundle
|
||||
else
|
||||
ifdef MOZ_IOS
|
||||
_LOADER_PATH := @rpath
|
||||
else
|
||||
|
@ -353,25 +342,6 @@ endif
|
|||
EXTRA_DSO_LDOPTS += -dynamiclib -install_name $(_LOADER_PATH)/$(SHARED_LIBRARY) -compatibility_version 1 -current_version 1 -single_module
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
#
|
||||
# On NetBSD a.out systems, use -Bsymbolic. This fixes what would otherwise be
|
||||
# fatal symbol name clashes between components.
|
||||
#
|
||||
ifeq ($(OS_ARCH),NetBSD)
|
||||
ifeq ($(DLL_SUFFIX),.so.1.0)
|
||||
ifdef IS_COMPONENT
|
||||
EXTRA_DSO_LDOPTS += -Wl,-Bsymbolic
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
ifeq ($(OS_ARCH),FreeBSD)
|
||||
ifdef IS_COMPONENT
|
||||
EXTRA_DSO_LDOPTS += -Wl,-Bsymbolic
|
||||
endif
|
||||
endif
|
||||
|
||||
ifeq ($(OS_ARCH),NetBSD)
|
||||
ifneq (,$(filter arc cobalt hpcmips mipsco newsmips pmax sgimips,$(OS_TEST)))
|
||||
|
@ -382,42 +352,10 @@ endif
|
|||
endif
|
||||
endif
|
||||
|
||||
#
|
||||
# HP-UXBeOS specific section: for COMPONENTS only, add -Bsymbolic flag
|
||||
# which uses internal symbols first
|
||||
#
|
||||
ifeq ($(OS_ARCH),HP-UX)
|
||||
ifdef IS_COMPONENT
|
||||
ifeq ($(GNU_CC)$(GNU_CXX),)
|
||||
EXTRA_DSO_LDOPTS += -Wl,-Bsymbolic
|
||||
ifneq ($(HAS_EXTRAEXPORTS),1)
|
||||
MKSHLIB += -Wl,+eNSGetModule -Wl,+eerrno
|
||||
MKCSHLIB += +eNSGetModule +eerrno
|
||||
ifneq ($(OS_TEST),ia64)
|
||||
MKSHLIB += -Wl,+e_shlInit
|
||||
MKCSHLIB += +e_shlInit
|
||||
endif # !ia64
|
||||
endif # !HAS_EXTRAEXPORTS
|
||||
endif # non-gnu compilers
|
||||
endif # IS_COMPONENT
|
||||
endif # HP-UX
|
||||
|
||||
ifeq ($(OS_ARCH),AIX)
|
||||
ifdef IS_COMPONENT
|
||||
ifneq ($(HAS_EXTRAEXPORTS),1)
|
||||
MKSHLIB += -bE:$(MOZILLA_DIR)/build/unix/aix.exp -bnoexpall
|
||||
MKCSHLIB += -bE:$(MOZILLA_DIR)/build/unix/aix.exp -bnoexpall
|
||||
endif # HAS_EXTRAEXPORTS
|
||||
endif # IS_COMPONENT
|
||||
endif # AIX
|
||||
|
||||
#
|
||||
# Linux: add -Bsymbolic flag for components
|
||||
#
|
||||
ifeq ($(OS_ARCH),Linux)
|
||||
ifdef IS_COMPONENT
|
||||
EXTRA_DSO_LDOPTS += -Wl,-Bsymbolic
|
||||
endif
|
||||
ifdef LD_VERSION_SCRIPT
|
||||
EXTRA_DSO_LDOPTS += -Wl,--version-script,$(LD_VERSION_SCRIPT)
|
||||
EXTRA_DEPS += $(LD_VERSION_SCRIPT)
|
||||
|
@ -455,11 +393,9 @@ endif
|
|||
#
|
||||
ifeq ($(OS_ARCH),WINNT)
|
||||
ifdef GNU_CC
|
||||
ifndef IS_COMPONENT
|
||||
DSO_LDOPTS += -Wl,--out-implib -Wl,$(IMPORT_LIBRARY)
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
ifeq ($(USE_TVFS),1)
|
||||
IFLAGS1 = -rb
|
||||
|
|
|
@ -262,6 +262,8 @@ AnimationsTimeline.prototype = {
|
|||
this.animationDetailCloseButton = null;
|
||||
this.animationRootEl = null;
|
||||
this.selectedAnimation = null;
|
||||
|
||||
this.isDestroyed = true;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -486,6 +488,11 @@ AnimationsTimeline.prototype = {
|
|||
|
||||
// Draw the animation time block.
|
||||
const tracks = yield this.getTracks(animation);
|
||||
// If we're destroyed by now, just give up.
|
||||
if (this.isDestroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
let timeBlock = new AnimationTimeBlock();
|
||||
timeBlock.init(timeBlockEl);
|
||||
timeBlock.render(animation, tracks);
|
||||
|
@ -674,7 +681,16 @@ AnimationsTimeline.prototype = {
|
|||
* handle.
|
||||
*/
|
||||
if (this.serverTraits.hasGetProperties) {
|
||||
let properties = yield animation.getProperties();
|
||||
let properties = [];
|
||||
try {
|
||||
properties = yield animation.getProperties();
|
||||
} catch (e) {
|
||||
// Expected if we've already been destroyed in the meantime.
|
||||
if (!this.isDestroyed) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
for (let {name, values} of properties) {
|
||||
if (!tracks[name]) {
|
||||
tracks[name] = [];
|
||||
|
@ -686,7 +702,16 @@ AnimationsTimeline.prototype = {
|
|||
}
|
||||
}
|
||||
} else {
|
||||
let frames = yield animation.getFrames();
|
||||
let frames = [];
|
||||
try {
|
||||
frames = yield animation.getFrames();
|
||||
} catch (e) {
|
||||
// Expected if we've already been destroyed in the meantime.
|
||||
if (!this.isDestroyed) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
for (let frame of frames) {
|
||||
for (let name in frame) {
|
||||
if (this.NON_PROPERTIES.indexOf(name) != -1) {
|
||||
|
|
|
@ -77,6 +77,7 @@ skip-if = os == "mac" # Full keyboard navigation on OSX only works if Full Keybo
|
|||
[browser_inspector_highlighter-cssgrid_02.js]
|
||||
[browser_inspector_highlighter-cssshape_01.js]
|
||||
[browser_inspector_highlighter-cssshape_02.js]
|
||||
[browser_inspector_highlighter-cssshape_03.js]
|
||||
[browser_inspector_highlighter-csstransform_01.js]
|
||||
[browser_inspector_highlighter-csstransform_02.js]
|
||||
[browser_inspector_highlighter-embed.js]
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Make sure that the CSS shapes highlighters have the correct size
|
||||
// in different zoom levels and with different geometry-box.
|
||||
|
||||
const TEST_URL = URL_ROOT + "doc_inspector_highlighter_cssshapes.html";
|
||||
const HIGHLIGHTER_TYPE = "ShapesHighlighter";
|
||||
const TEST_LEVELS = [0.5, 1, 2];
|
||||
|
||||
add_task(function* () {
|
||||
let inspector = yield openInspectorForURL(TEST_URL);
|
||||
let helper = yield getHighlighterHelperFor(HIGHLIGHTER_TYPE)(inspector);
|
||||
let {testActor} = inspector;
|
||||
|
||||
yield testZoomSize(testActor, helper);
|
||||
yield testGeometryBox(testActor, helper);
|
||||
yield testStrokeBox(testActor, helper);
|
||||
|
||||
yield helper.finalize();
|
||||
});
|
||||
|
||||
function* testZoomSize(testActor, helper) {
|
||||
yield helper.show("#polygon", {mode: "cssClipPath"});
|
||||
let quads = yield testActor.getAllAdjustedQuads("#polygon");
|
||||
let { top, left, width, height } = quads.border[0].bounds;
|
||||
let expectedStyle = `top:${top}px;left:${left}px;width:${width}px;height:${height}px;`;
|
||||
|
||||
// The top/left/width/height of the highlighter should not change at any zoom level.
|
||||
// It should always match the element being highlighted.
|
||||
for (let zoom of TEST_LEVELS) {
|
||||
info(`Setting zoom level to ${zoom}.`);
|
||||
yield testActor.zoomPageTo(zoom, helper.actorID);
|
||||
let style = yield helper.getElementAttribute("shapes-shape-container", "style");
|
||||
|
||||
is(style, expectedStyle, `Highlighter has correct quads at zoom level ${zoom}`);
|
||||
}
|
||||
}
|
||||
|
||||
function* testGeometryBox(testActor, helper) {
|
||||
yield testActor.zoomPageTo(1, helper.actorID);
|
||||
yield helper.show("#ellipse", {mode: "cssClipPath"});
|
||||
let quads = yield testActor.getAllAdjustedQuads("#ellipse");
|
||||
let { top: cTop, left: cLeft,
|
||||
width: cWidth, height: cHeight } = quads.content[0].bounds;
|
||||
let expectedStyle = `top:${cTop}px;left:${cLeft}px;` +
|
||||
`width:${cWidth}px;height:${cHeight}px;`;
|
||||
let style = yield helper.getElementAttribute("shapes-shape-container", "style");
|
||||
is(style, expectedStyle, "Highlighter has correct quads for content-box");
|
||||
|
||||
yield helper.show("#ellipse-padding-box", {mode: "cssClipPath"});
|
||||
quads = yield testActor.getAllAdjustedQuads("#ellipse-padding-box");
|
||||
let { top: pTop, left: pLeft,
|
||||
width: pWidth, height: pHeight } = quads.padding[0].bounds;
|
||||
expectedStyle = `top:${pTop}px;left:${pLeft}px;` +
|
||||
`width:${pWidth}px;height:${pHeight}px;`;
|
||||
style = yield helper.getElementAttribute("shapes-shape-container", "style");
|
||||
is(style, expectedStyle, "Highlighter has correct quads for padding-box");
|
||||
}
|
||||
|
||||
function* testStrokeBox(testActor, helper) {
|
||||
// #rect has a stroke and doesn't have the clip-path option stroke-box,
|
||||
// so we must adjust the quads to reflect the object bounding box.
|
||||
yield helper.show("#rect", {mode: "cssClipPath"});
|
||||
let quads = yield testActor.getAllAdjustedQuads("#rect");
|
||||
let { top, left, width, height } = quads.border[0].bounds;
|
||||
let { highlightedNode } = helper;
|
||||
let computedStyle = yield highlightedNode.getComputedStyle();
|
||||
let strokeWidth = computedStyle["stroke-width"].value;
|
||||
let delta = parseFloat(strokeWidth) / 2;
|
||||
|
||||
let expectedStyle = `top:${top + delta}px;left:${left + delta}px;` +
|
||||
`width:${width - 2 * delta}px;height:${height - 2 * delta}px;`;
|
||||
let style = yield helper.getElementAttribute("shapes-shape-container", "style");
|
||||
is(style, expectedStyle,
|
||||
"Highlighter has correct quads for SVG rect with stroke and stroke-box");
|
||||
}
|
|
@ -30,13 +30,43 @@
|
|||
clip-path: circle(25% at 30% 40%);
|
||||
}
|
||||
#ellipse {
|
||||
clip-path: ellipse(40% 30% at 25% 75%);
|
||||
clip-path: ellipse(40% 30% at 25% 75%) content-box;
|
||||
padding: 20px;
|
||||
}
|
||||
#ellipse-padding-box {
|
||||
clip-path: ellipse(40% 30% at 25% 75%) padding-box;
|
||||
padding: 20px;
|
||||
}
|
||||
#inset {
|
||||
clip-path: inset(200px 100px 30% 15%);
|
||||
}
|
||||
.svg {
|
||||
width: 800px;
|
||||
height: 800px;
|
||||
}
|
||||
#rect {
|
||||
clip-path: polygon(0 0,
|
||||
100px 50%,
|
||||
200px 0,
|
||||
300px 50%,
|
||||
400px 0,
|
||||
500px 50%,
|
||||
600px 0,
|
||||
700px 50%,
|
||||
800px 0,
|
||||
90% 100%,
|
||||
50% 60%,
|
||||
10% 100%);
|
||||
stroke: red;
|
||||
stroke-width: 20px;
|
||||
fill: blue;
|
||||
}
|
||||
</style>
|
||||
<div class="wrapper" id="polygon"></div>
|
||||
<div class="wrapper" id="circle"></div>
|
||||
<div class="wrapper" id="ellipse"></div>
|
||||
<div class="wrapper" id="ellipse-padding-box"></div>
|
||||
<div class="wrapper" id="inset"></div>
|
||||
<svg class="svg">
|
||||
<rect id="rect" x="10" y="10" width="700" height="700"></rect>
|
||||
</svg>
|
||||
|
|
|
@ -439,6 +439,14 @@ const getHighlighterHelperFor = (type) => Task.async(
|
|||
};
|
||||
},
|
||||
|
||||
get actorID() {
|
||||
if (!highlighter) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return highlighter.actorID;
|
||||
},
|
||||
|
||||
show: function* (selector = ":root", options) {
|
||||
highlightedNode = yield getNodeFront(selector, inspector);
|
||||
return yield highlighter.show(highlightedNode, options);
|
||||
|
|
|
@ -59,6 +59,7 @@ class FirefoxConnector {
|
|||
this.actions = actions;
|
||||
this.getState = getState;
|
||||
this.tabTarget = connection.tabConnection.tabTarget;
|
||||
this.toolbox = connection.toolbox;
|
||||
|
||||
this.webConsoleClient = this.tabTarget.activeConsole;
|
||||
|
||||
|
|
|
@ -66,7 +66,7 @@ function triggerActivity() {
|
|||
}
|
||||
|
||||
function viewSourceInDebugger() {
|
||||
return connector.viewSourceInDebugger();
|
||||
return connector.viewSourceInDebugger(...arguments);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
|
|
@ -23,6 +23,7 @@ const { showMenu } = require("./utils/menu");
|
|||
const {
|
||||
getUrlQuery,
|
||||
parseQueryString,
|
||||
getUrlBaseName,
|
||||
} = require("./utils/request-utils");
|
||||
|
||||
function RequestListContextMenu({
|
||||
|
@ -309,7 +310,7 @@ RequestListContextMenu.prototype = {
|
|||
*/
|
||||
saveImageAs() {
|
||||
let { encoding, text } = this.selectedRequest.responseContent.content;
|
||||
let fileName = this.selectedRequest.urlDetails.baseNameWithQuery;
|
||||
let fileName = getUrlBaseName(this.selectedRequest.url);
|
||||
let data;
|
||||
if (encoding === "base64") {
|
||||
let decoded = atob(text);
|
||||
|
|
|
@ -813,11 +813,12 @@ var TestActorFront = exports.TestActorFront = protocol.FrontClassWithSpec(testSp
|
|||
/**
|
||||
* Zoom the current page to a given level.
|
||||
* @param {Number} level The new zoom level.
|
||||
* @param {String} actorID Optional. The highlighter actor ID.
|
||||
* @return {Promise} The returned promise will only resolve when the
|
||||
* highlighter has updated to the new zoom level.
|
||||
*/
|
||||
zoomPageTo: function (level) {
|
||||
return this.changeZoomLevel(level, this.toolbox.highlighter.actorID);
|
||||
zoomPageTo: function (level, actorID = this.toolbox.highlighter.actorID) {
|
||||
return this.changeZoomLevel(level, actorID);
|
||||
},
|
||||
|
||||
/* eslint-disable max-len */
|
||||
|
|
|
@ -947,9 +947,11 @@ TableWidget.prototype = {
|
|||
// Loop through all items and hide unmatched items
|
||||
for (let [id, val] of this.items) {
|
||||
for (let prop in val) {
|
||||
if (ignoreProps.includes(prop)) {
|
||||
let column = this.columns.get(prop);
|
||||
if (ignoreProps.includes(prop) || column.hidden) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let propValue = val[prop].toString().toLowerCase();
|
||||
if (propValue.includes(value)) {
|
||||
itemsToHide.splice(itemsToHide.indexOf(id), 1);
|
||||
|
@ -1082,6 +1084,13 @@ Column.prototype = {
|
|||
return this._sortState || 0;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a boolean indicating whether the column is hidden.
|
||||
*/
|
||||
get hidden() {
|
||||
return this.wrapper.hasAttribute("hidden");
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the private state of the column (visibility in the UI).
|
||||
*/
|
||||
|
|
|
@ -4,9 +4,14 @@
|
|||
add_task(function* () {
|
||||
yield openTabAndSetupStorage(MAIN_DOMAIN + "storage-search.html");
|
||||
|
||||
let $$ = sel => gPanelWindow.document.querySelectorAll(sel);
|
||||
gUI.tree.expandAll();
|
||||
yield selectTreeItem(["localStorage", "http://test1.example.org"]);
|
||||
yield selectTreeItem(["cookies", "http://test1.example.org"]);
|
||||
|
||||
showColumn("expires", false);
|
||||
showColumn("host", false);
|
||||
showColumn("isHttpOnly", false);
|
||||
showColumn("lastAccessed", false);
|
||||
showColumn("path", false);
|
||||
|
||||
// Results: 0=hidden, 1=visible
|
||||
let testcases = [
|
||||
|
@ -52,7 +57,7 @@ add_task(function* () {
|
|||
// Test input with whitespace
|
||||
{
|
||||
value: "energy b",
|
||||
results: [0, 0, 0, 1, 0, 0, 0]
|
||||
results: [0, 0, 1, 0, 0, 0, 0]
|
||||
},
|
||||
// Test no input at all
|
||||
{
|
||||
|
@ -63,25 +68,72 @@ add_task(function* () {
|
|||
{
|
||||
value: "input that matches nothing",
|
||||
results: [0, 0, 0, 0, 0, 0, 0]
|
||||
}
|
||||
},
|
||||
];
|
||||
|
||||
let names = $$("#name .table-widget-cell");
|
||||
let rows = $$("#value .table-widget-cell");
|
||||
for (let testcase of testcases) {
|
||||
info(`Testing input: ${testcase.value}`);
|
||||
let testcasesAfterHiding = [
|
||||
// Test that search isn't case-sensitive
|
||||
{
|
||||
value: "OR",
|
||||
results: [0, 0, 0, 0, 0, 1, 0]
|
||||
},
|
||||
{
|
||||
value: "01",
|
||||
results: [1, 0, 0, 0, 0, 0, 0]
|
||||
},
|
||||
{
|
||||
value: "2016",
|
||||
results: [0, 0, 0, 0, 0, 0, 0]
|
||||
},
|
||||
{
|
||||
value: "56789",
|
||||
results: [0, 0, 0, 0, 0, 0, 0]
|
||||
},
|
||||
// Test filtering by value
|
||||
{
|
||||
value: "horse",
|
||||
results: [0, 0, 0, 0, 0, 0, 0]
|
||||
},
|
||||
{
|
||||
value: "$$$",
|
||||
results: [0, 0, 0, 0, 0, 0, 0]
|
||||
},
|
||||
{
|
||||
value: "bar",
|
||||
results: [0, 0, 0, 0, 0, 0, 0]
|
||||
},
|
||||
// Test input with whitespace
|
||||
{
|
||||
value: "energy b",
|
||||
results: [0, 0, 0, 0, 0, 0, 0]
|
||||
},
|
||||
];
|
||||
|
||||
gUI.searchBox.value = testcase.value;
|
||||
gUI.filterItems();
|
||||
|
||||
for (let i = 0; i < rows.length; i++) {
|
||||
info(`Testing row ${i}`);
|
||||
info(`key: ${names[i].value}, value: ${rows[i].value}`);
|
||||
let state = testcase.results[i] ? "visible" : "hidden";
|
||||
is(rows[i].hasAttribute("hidden"), !testcase.results[i],
|
||||
`Row ${i} should be ${state}`);
|
||||
}
|
||||
}
|
||||
runTests(testcases);
|
||||
showColumn("value", false);
|
||||
runTests(testcasesAfterHiding);
|
||||
|
||||
yield finishTests();
|
||||
});
|
||||
|
||||
function runTests(testcases) {
|
||||
let $$ = sel => gPanelWindow.document.querySelectorAll(sel);
|
||||
let names = $$("#name .table-widget-cell");
|
||||
let rows = $$("#value .table-widget-cell");
|
||||
for (let testcase of testcases) {
|
||||
let {value, results} = testcase;
|
||||
|
||||
info(`Testing input: ${value}`);
|
||||
|
||||
gUI.searchBox.value = value;
|
||||
gUI.filterItems();
|
||||
|
||||
for (let i = 0; i < rows.length; i++) {
|
||||
info(`Testing row ${i} for "${value}"`);
|
||||
info(`key: ${names[i].value}, value: ${rows[i].value}`);
|
||||
let state = results[i] ? "visible" : "hidden";
|
||||
is(rows[i].hasAttribute("hidden"), !results[i],
|
||||
`Row ${i} should be ${state}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,18 +6,23 @@ Bug 1224115 - Storage Inspector table filtering
|
|||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Storage inspector table filtering test</title>
|
||||
</head>
|
||||
<body>
|
||||
<script type="text/javascript">
|
||||
"use strict";
|
||||
|
||||
localStorage.setItem("01234", "56789");
|
||||
localStorage.setItem("ANIMAL", "hOrSe");
|
||||
localStorage.setItem("FOO", "bArBaz");
|
||||
localStorage.setItem("food", "energy bar");
|
||||
localStorage.setItem("money", "##$$$**");
|
||||
localStorage.setItem("sport", "football");
|
||||
localStorage.setItem("year", "2016");
|
||||
</script>
|
||||
<script type="text/javascript">
|
||||
"use strict";
|
||||
|
||||
/* exported init */
|
||||
function init() {
|
||||
document.cookie = "01234=56789";
|
||||
document.cookie = "ANIMAL=hOrSe";
|
||||
document.cookie = "food=energy bar";
|
||||
document.cookie = "FOO=bArBaz";
|
||||
document.cookie = "money=##$$$**";
|
||||
document.cookie = "sport=football";
|
||||
document.cookie = "year=2016";
|
||||
}
|
||||
</script>
|
||||
|
||||
</head>
|
||||
<body onload="init()">
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -16,7 +16,6 @@ loader.lazyRequireGetter(this, "gDevTools", "devtools/client/framework/devtools"
|
|||
loader.lazyRequireGetter(this, "TableWidget", "devtools/client/shared/widgets/TableWidget", true);
|
||||
loader.lazyRequireGetter(this, "ObjectClient", "devtools/shared/client/main", true);
|
||||
|
||||
const { extend } = require("sdk/core/heritage");
|
||||
const XHTML_NS = "http://www.w3.org/1999/xhtml";
|
||||
|
||||
const WebConsoleUtils = require("devtools/client/webconsole/utils").Utils;
|
||||
|
@ -31,6 +30,9 @@ const {ELLIPSIS} = require("devtools/shared/l10n");
|
|||
|
||||
const validProtocols = /^(http|https|ftp|data|javascript|resource|chrome):/i;
|
||||
|
||||
const extend = (prototype, properties) =>
|
||||
Object.create(prototype, Object.getOwnPropertyDescriptors(properties));
|
||||
|
||||
// Constants for compatibility with the Web Console output implementation before
|
||||
// bug 778766.
|
||||
// TODO: remove these once bug 778766 is fixed.
|
||||
|
|
|
@ -36,6 +36,7 @@
|
|||
--highlighter-bubble-arrow-size: 8px;
|
||||
--highlighter-font-family: message-box;
|
||||
--highlighter-font-size: 11px;
|
||||
--highlighter-marker-color: #000;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -595,18 +596,9 @@
|
|||
|
||||
/* Shapes highlighter */
|
||||
|
||||
:-moz-native-anonymous .shapes-shape-container,
|
||||
:-moz-native-anonymous .shapes-markers-container {
|
||||
:-moz-native-anonymous .shapes-shape-container {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
:-moz-native-anonymous .shapes-markers-container {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
transform: translate(-5px, -5px);
|
||||
background: transparent;
|
||||
border-radius: 50%;
|
||||
color: var(--highlighter-bubble-background-color);
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
:-moz-native-anonymous .shapes-polygon,
|
||||
|
@ -614,6 +606,10 @@
|
|||
:-moz-native-anonymous .shapes-rect {
|
||||
fill: transparent;
|
||||
stroke: var(--highlighter-guide-color);
|
||||
shape-rendering: crispEdges;
|
||||
shape-rendering: geometricPrecision;
|
||||
vector-effect: non-scaling-stroke;
|
||||
}
|
||||
|
||||
:-moz-native-anonymous .shapes-markers {
|
||||
fill: var(--highlighter-marker-color);
|
||||
}
|
||||
|
|
|
@ -6,11 +6,11 @@
|
|||
|
||||
const { CanvasFrameAnonymousContentHelper,
|
||||
createSVGNode, createNode, getComputedStyle } = require("./utils/markup");
|
||||
const { setIgnoreLayoutChanges } = require("devtools/shared/layout/utils");
|
||||
const { setIgnoreLayoutChanges, getCurrentZoom } = require("devtools/shared/layout/utils");
|
||||
const { AutoRefreshHighlighter } = require("./auto-refresh");
|
||||
|
||||
// We use this as an offset to avoid the marker itself from being on top of its shadow.
|
||||
const MARKER_SIZE = 10;
|
||||
const BASE_MARKER_SIZE = 10;
|
||||
|
||||
/**
|
||||
* The ShapesHighlighter draws an outline shapes in the page.
|
||||
|
@ -23,6 +23,9 @@ class ShapesHighlighter extends AutoRefreshHighlighter {
|
|||
|
||||
this.ID_CLASS_PREFIX = "shapes-";
|
||||
|
||||
this.referenceBox = "border";
|
||||
this.useStrokeBox = false;
|
||||
|
||||
this.markup = new CanvasFrameAnonymousContentHelper(this.highlighterEnv,
|
||||
this._buildMarkup.bind(this));
|
||||
}
|
||||
|
@ -56,18 +59,6 @@ class ShapesHighlighter extends AutoRefreshHighlighter {
|
|||
prefix: this.ID_CLASS_PREFIX
|
||||
});
|
||||
|
||||
// We also need a separate element to draw the shapes' points markers. We can't use
|
||||
// the SVG because it is scaled.
|
||||
createNode(this.win, {
|
||||
nodeType: "div",
|
||||
parent: rootWrapper,
|
||||
attributes: {
|
||||
"id": "markers-container",
|
||||
"class": "markers-container"
|
||||
},
|
||||
prefix: this.ID_CLASS_PREFIX
|
||||
});
|
||||
|
||||
// Append a polygon for polygon shapes.
|
||||
createSVGNode(this.win, {
|
||||
nodeType: "polygon",
|
||||
|
@ -104,14 +95,33 @@ class ShapesHighlighter extends AutoRefreshHighlighter {
|
|||
prefix: this.ID_CLASS_PREFIX
|
||||
});
|
||||
|
||||
// Append a path to display the markers for the shape.
|
||||
createSVGNode(this.win, {
|
||||
nodeType: "path",
|
||||
parent: mainSvg,
|
||||
attributes: {
|
||||
"id": "markers",
|
||||
"class": "markers",
|
||||
},
|
||||
prefix: this.ID_CLASS_PREFIX
|
||||
});
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
get currentDimensions() {
|
||||
return {
|
||||
width: this.currentQuads.border[0].bounds.width,
|
||||
height: this.currentQuads.border[0].bounds.height
|
||||
};
|
||||
let { top, left, width, height } = this.currentQuads[this.referenceBox][0].bounds;
|
||||
|
||||
// If an SVG element has a stroke, currentQuads will return the stroke bounding box.
|
||||
// However, clip-path always uses the object bounding box unless "stroke-box" is
|
||||
// specified. So, we must calculate the object bounding box if there is a stroke
|
||||
// and "stroke-box" is not specified. stroke only applies to SVG elements, so use
|
||||
// getBBox, which only exists for SVG, to check if currentNode is an SVG element.
|
||||
if (this.currentNode.getBBox &&
|
||||
getComputedStyle(this.currentNode).stroke !== "none" && !this.useStrokeBox) {
|
||||
return getObjectBoundingBox(top, left, width, height, this.currentNode);
|
||||
}
|
||||
return { top, left, width, height };
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -124,7 +134,7 @@ class ShapesHighlighter extends AutoRefreshHighlighter {
|
|||
* or object of the coordinates needed to draw the shape.
|
||||
*/
|
||||
_parseCSSShapeValue(definition) {
|
||||
const types = [{
|
||||
const shapeTypes = [{
|
||||
name: "polygon",
|
||||
prefix: "polygon(",
|
||||
coordParser: this.polygonPoints.bind(this)
|
||||
|
@ -141,10 +151,23 @@ class ShapesHighlighter extends AutoRefreshHighlighter {
|
|||
prefix: "inset(",
|
||||
coordParser: this.insetPoints.bind(this)
|
||||
}];
|
||||
const geometryTypes = ["margin", "border", "padding", "content"];
|
||||
|
||||
for (let { name, prefix, coordParser } of types) {
|
||||
// default to border
|
||||
let referenceBox = "border";
|
||||
for (let geometry of geometryTypes) {
|
||||
if (definition.includes(geometry)) {
|
||||
referenceBox = geometry;
|
||||
}
|
||||
}
|
||||
this.referenceBox = referenceBox;
|
||||
|
||||
this.useStrokeBox = definition.includes("stroke-box");
|
||||
|
||||
for (let { name, prefix, coordParser } of shapeTypes) {
|
||||
if (definition.includes(prefix)) {
|
||||
definition = definition.substring(prefix.length, definition.length - 1);
|
||||
// the closing paren of the shape function is always the last one in definition.
|
||||
definition = definition.substring(prefix.length, definition.lastIndexOf(")"));
|
||||
return {
|
||||
shapeType: name,
|
||||
coordinates: coordParser(definition)
|
||||
|
@ -180,8 +203,9 @@ class ShapesHighlighter extends AutoRefreshHighlighter {
|
|||
// The computed value of circle() always has the keyword "at".
|
||||
let values = definition.split(" at ");
|
||||
let radius = values[0];
|
||||
let elemWidth = this.currentDimensions.width;
|
||||
let elemHeight = this.currentDimensions.height;
|
||||
let zoom = getCurrentZoom(this.win);
|
||||
let elemWidth = this.currentDimensions.width / zoom;
|
||||
let elemHeight = this.currentDimensions.height / zoom;
|
||||
let center = splitCoords(values[1]).map(this.convertCoordsToPercent.bind(this));
|
||||
|
||||
if (radius === "closest-side") {
|
||||
|
@ -219,8 +243,9 @@ class ShapesHighlighter extends AutoRefreshHighlighter {
|
|||
*/
|
||||
ellipsePoints(definition) {
|
||||
let values = definition.split(" at ");
|
||||
let elemWidth = this.currentDimensions.width;
|
||||
let elemHeight = this.currentDimensions.height;
|
||||
let zoom = getCurrentZoom(this.win);
|
||||
let elemWidth = this.currentDimensions.width / zoom;
|
||||
let elemHeight = this.currentDimensions.height / zoom;
|
||||
let center = splitCoords(values[1]).map(this.convertCoordsToPercent.bind(this));
|
||||
|
||||
let radii = values[0].trim().split(" ").map((radius, i) => {
|
||||
|
@ -280,8 +305,9 @@ class ShapesHighlighter extends AutoRefreshHighlighter {
|
|||
}
|
||||
|
||||
convertCoordsToPercent(coord, i) {
|
||||
let elemWidth = this.currentDimensions.width;
|
||||
let elemHeight = this.currentDimensions.height;
|
||||
let zoom = getCurrentZoom(this.win);
|
||||
let elemWidth = this.currentDimensions.width / zoom;
|
||||
let elemHeight = this.currentDimensions.height / zoom;
|
||||
let size = i % 2 === 0 ? elemWidth : elemHeight;
|
||||
if (coord.includes("calc(")) {
|
||||
return evalCalcExpression(coord.substring(5, coord.length - 1), size);
|
||||
|
@ -350,6 +376,7 @@ class ShapesHighlighter extends AutoRefreshHighlighter {
|
|||
this.getElement("ellipse").setAttribute("hidden", true);
|
||||
this.getElement("polygon").setAttribute("hidden", true);
|
||||
this.getElement("rect").setAttribute("hidden", true);
|
||||
this.getElement("markers").setAttribute("d", "");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -360,23 +387,28 @@ class ShapesHighlighter extends AutoRefreshHighlighter {
|
|||
_update() {
|
||||
setIgnoreLayoutChanges(true);
|
||||
|
||||
let { top, left, width, height } = this.currentQuads.border[0].bounds;
|
||||
let { top, left, width, height } = this.currentDimensions;
|
||||
let zoom = getCurrentZoom(this.win);
|
||||
|
||||
top /= zoom;
|
||||
left /= zoom;
|
||||
width /= zoom;
|
||||
height /= zoom;
|
||||
|
||||
// Size the SVG like the current node.
|
||||
this.getElement("shape-container").setAttribute("style",
|
||||
`top:${top}px;left:${left}px;width:${width}px;height:${height}px;`);
|
||||
|
||||
this._hideShapes();
|
||||
this.getElement("markers-container").setAttribute("style", "");
|
||||
|
||||
if (this.shapeType === "polygon") {
|
||||
this._updatePolygonShape(top, left, width, height);
|
||||
this._updatePolygonShape(width, height, zoom);
|
||||
} else if (this.shapeType === "circle") {
|
||||
this._updateCircleShape(top, left, width, height);
|
||||
this._updateCircleShape(width, height, zoom);
|
||||
} else if (this.shapeType === "ellipse") {
|
||||
this._updateEllipseShape(top, left, width, height);
|
||||
this._updateEllipseShape(width, height, zoom);
|
||||
} else if (this.shapeType === "inset") {
|
||||
this._updateInsetShape(top, left, width, height);
|
||||
this._updateInsetShape();
|
||||
}
|
||||
|
||||
setIgnoreLayoutChanges(false, this.highlighterEnv.window.document.documentElement);
|
||||
|
@ -386,12 +418,11 @@ class ShapesHighlighter extends AutoRefreshHighlighter {
|
|||
|
||||
/**
|
||||
* Update the SVG polygon to fit the CSS polygon.
|
||||
* @param {Number} top the top bound of the element quads
|
||||
* @param {Number} left the left bound of the element quads
|
||||
* @param {Number} width the width of the element quads
|
||||
* @param {Number} height the height of the element quads
|
||||
* @param {Number} zoom the zoom level of the window
|
||||
*/
|
||||
_updatePolygonShape(top, left, width, height) {
|
||||
_updatePolygonShape(width, height, zoom) {
|
||||
// Draw and show the polygon.
|
||||
let points = this.coordinates.map(point => point.join(",")).join(" ");
|
||||
|
||||
|
@ -399,23 +430,16 @@ class ShapesHighlighter extends AutoRefreshHighlighter {
|
|||
polygonEl.setAttribute("points", points);
|
||||
polygonEl.removeAttribute("hidden");
|
||||
|
||||
// Draw the points themselves, using the markers-container and multiple box-shadows.
|
||||
let shadows = this.coordinates.map(([x, y]) => {
|
||||
return `${MARKER_SIZE + x * width / 100}px ${MARKER_SIZE + y * height / 100}px 0 0`;
|
||||
}).join(", ");
|
||||
|
||||
this.getElement("markers-container").setAttribute("style",
|
||||
`top:${top - MARKER_SIZE}px;left:${left - MARKER_SIZE}px;box-shadow:${shadows};`);
|
||||
this._drawMarkers(this.coordinates, width, height, zoom);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the SVG ellipse to fit the CSS circle.
|
||||
* @param {Number} top the top bound of the element quads
|
||||
* @param {Number} left the left bound of the element quads
|
||||
* @param {Number} width the width of the element quads
|
||||
* @param {Number} height the height of the element quads
|
||||
* @param {Number} zoom the zoom level of the window
|
||||
*/
|
||||
_updateCircleShape(top, left, width, height) {
|
||||
_updateCircleShape(width, height, zoom) {
|
||||
let { rx, ry, cx, cy } = this.coordinates;
|
||||
let ellipseEl = this.getElement("ellipse");
|
||||
ellipseEl.setAttribute("rx", rx);
|
||||
|
@ -424,21 +448,16 @@ class ShapesHighlighter extends AutoRefreshHighlighter {
|
|||
ellipseEl.setAttribute("cy", cy);
|
||||
ellipseEl.removeAttribute("hidden");
|
||||
|
||||
let shadows = `${MARKER_SIZE + cx * width / 100}px
|
||||
${MARKER_SIZE + cy * height / 100}px 0 0`;
|
||||
|
||||
this.getElement("markers-container").setAttribute("style",
|
||||
`top:${top - MARKER_SIZE}px;left:${left - MARKER_SIZE}px;box-shadow:${shadows};`);
|
||||
this._drawMarkers([[cx, cy]], width, height, zoom);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the SVG ellipse to fit the CSS ellipse.
|
||||
* @param {Number} top the top bound of the element quads
|
||||
* @param {Number} left the left bound of the element quads
|
||||
* @param {Number} width the width of the element quads
|
||||
* @param {Number} height the height of the element quads
|
||||
* @param {Number} zoom the zoom level of the window
|
||||
*/
|
||||
_updateEllipseShape(top, left, width, height) {
|
||||
_updateEllipseShape(width, height, zoom) {
|
||||
let { rx, ry, cx, cy } = this.coordinates;
|
||||
let ellipseEl = this.getElement("ellipse");
|
||||
ellipseEl.setAttribute("rx", rx);
|
||||
|
@ -447,34 +466,35 @@ class ShapesHighlighter extends AutoRefreshHighlighter {
|
|||
ellipseEl.setAttribute("cy", cy);
|
||||
ellipseEl.removeAttribute("hidden");
|
||||
|
||||
let shadows = `${MARKER_SIZE + cx * width / 100}px
|
||||
${MARKER_SIZE + cy * height / 100}px 0 0,
|
||||
${MARKER_SIZE + (cx + rx) * height / 100}px
|
||||
${MARKER_SIZE + cy * height / 100}px 0 0,
|
||||
${MARKER_SIZE + cx * height / 100}px
|
||||
${MARKER_SIZE + (cy + ry) * height / 100}px 0 0`;
|
||||
|
||||
this.getElement("markers-container").setAttribute("style",
|
||||
`top:${top - MARKER_SIZE}px;left:${left - MARKER_SIZE}px;box-shadow:${shadows};`);
|
||||
let markerCoords = [ [cx, cy], [cx + rx, cy], [cx, cy + ry] ];
|
||||
this._drawMarkers(markerCoords, width, height, zoom);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the SVG rect to fit the CSS inset.
|
||||
* @param {Number} top the top bound of the element quads
|
||||
* @param {Number} left the left bound of the element quads
|
||||
* @param {Number} width the width of the element quads
|
||||
* @param {Number} height the height of the element quads
|
||||
*/
|
||||
_updateInsetShape(top, left, width, height) {
|
||||
_updateInsetShape() {
|
||||
let rectEl = this.getElement("rect");
|
||||
rectEl.setAttribute("x", this.coordinates.x);
|
||||
rectEl.setAttribute("y", this.coordinates.y);
|
||||
rectEl.setAttribute("width", this.coordinates.width);
|
||||
rectEl.setAttribute("height", this.coordinates.height);
|
||||
rectEl.removeAttribute("hidden");
|
||||
}
|
||||
|
||||
this.getElement("markers-container").setAttribute("style",
|
||||
`top:${top - MARKER_SIZE}px;left:${left - MARKER_SIZE}px;box-shadow:none;`);
|
||||
/**
|
||||
* Draw markers for the given coordinates.
|
||||
* @param {Array} coords an array of coordinate arrays, of form [[x, y] ...]
|
||||
* @param {Number} width the width of the element markers are being drawn for
|
||||
* @param {Number} height the height of the element markers are being drawn for
|
||||
* @param {Number} zoom the zoom level of the window
|
||||
*/
|
||||
_drawMarkers(coords, width, height, zoom) {
|
||||
let markers = coords.map(([x, y]) => {
|
||||
return getCirclePath(x, y, width, height, zoom);
|
||||
}).join(" ");
|
||||
|
||||
this.getElement("markers").setAttribute("d", markers);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -484,7 +504,7 @@ class ShapesHighlighter extends AutoRefreshHighlighter {
|
|||
setIgnoreLayoutChanges(true);
|
||||
|
||||
this._hideShapes();
|
||||
this.getElement("markers-container").setAttribute("style", "");
|
||||
this.getElement("markers").setAttribute("d", "");
|
||||
|
||||
setIgnoreLayoutChanges(false, this.highlighterEnv.window.document.documentElement);
|
||||
}
|
||||
|
@ -552,6 +572,70 @@ const shapeModeToCssPropertyName = mode => {
|
|||
return property.substring(0, 1).toLowerCase() + property.substring(1);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the SVG path definition for a circle with given attributes.
|
||||
* @param {Number} cx the x coordinate of the centre of the circle
|
||||
* @param {Number} cy the y coordinate of the centre of the circle
|
||||
* @param {Number} width the width of the element the circle is being drawn for
|
||||
* @param {Number} height the height of the element the circle is being drawn for
|
||||
* @param {Number} zoom the zoom level of the window the circle is drawn in
|
||||
* @returns {String} the definition of the circle in SVG path description format.
|
||||
*/
|
||||
const getCirclePath = (cx, cy, width, height, zoom) => {
|
||||
// We use a viewBox of 100x100 for shape-container so it's easy to position things
|
||||
// based on their percentage, but this makes it more difficult to create circles.
|
||||
// Therefor, 100px is the base size of shape-container. In order to make the markers'
|
||||
// size scale properly, we must adjust the radius based on zoom and the width/height of
|
||||
// the element being highlighted, then calculate a radius for both x/y axes based
|
||||
// on the aspect ratio of the element.
|
||||
let radius = BASE_MARKER_SIZE * (100 / Math.max(width, height)) / zoom;
|
||||
let ratio = width / height;
|
||||
let rx = (ratio > 1) ? radius : radius / ratio;
|
||||
let ry = (ratio > 1) ? radius * ratio : radius;
|
||||
// a circle is drawn as two arc lines, starting at the leftmost point of the circle.
|
||||
return `M${cx - rx},${cy}a${rx},${ry} 0 1,0 ${rx * 2},0` +
|
||||
`a${rx},${ry} 0 1,0 ${rx * -2},0`;
|
||||
};
|
||||
|
||||
/**
|
||||
* Calculates the object bounding box for a node given its stroke bounding box.
|
||||
* @param {Number} top the y coord of the top edge of the stroke bounding box
|
||||
* @param {Number} left the x coord of the left edge of the stroke bounding box
|
||||
* @param {Number} width the width of the stroke bounding box
|
||||
* @param {Number} height the height of the stroke bounding box
|
||||
* @param {Object} node the node object
|
||||
* @returns {Object} an object of the form { top, left, width, height }, which
|
||||
* are the top/left/width/height of the object bounding box for the node.
|
||||
*/
|
||||
const getObjectBoundingBox = (top, left, width, height, node) => {
|
||||
// See https://drafts.fxtf.org/css-masking-1/#stroke-bounding-box for details
|
||||
// on this algorithm. Note that we intentionally do not check "stroke-linecap".
|
||||
let strokeWidth = parseFloat(getComputedStyle(node).strokeWidth);
|
||||
let delta = strokeWidth / 2;
|
||||
let tagName = node.tagName;
|
||||
|
||||
if (tagName !== "rect" && tagName !== "ellipse"
|
||||
&& tagName !== "circle" && tagName !== "image") {
|
||||
if (getComputedStyle(node).strokeLinejoin === "miter") {
|
||||
let miter = getComputedStyle(node).strokeMiterlimit;
|
||||
if (miter < Math.SQRT2) {
|
||||
delta *= Math.SQRT2;
|
||||
} else {
|
||||
delta *= miter;
|
||||
}
|
||||
} else {
|
||||
delta *= Math.SQRT2;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
top: top + delta,
|
||||
left: left + delta,
|
||||
width: width - 2 * delta,
|
||||
height: height - 2 * delta
|
||||
};
|
||||
};
|
||||
|
||||
exports.ShapesHighlighter = ShapesHighlighter;
|
||||
|
||||
// Export helper functions so they can be tested
|
||||
|
@ -559,3 +643,4 @@ exports.splitCoords = splitCoords;
|
|||
exports.coordToPercent = coordToPercent;
|
||||
exports.evalCalcExpression = evalCalcExpression;
|
||||
exports.shapeModeToCssPropertyName = shapeModeToCssPropertyName;
|
||||
exports.getCirclePath = getCirclePath;
|
||||
|
|
|
@ -11,7 +11,8 @@ const {
|
|||
splitCoords,
|
||||
coordToPercent,
|
||||
evalCalcExpression,
|
||||
shapeModeToCssPropertyName
|
||||
shapeModeToCssPropertyName,
|
||||
getCirclePath
|
||||
} = require("devtools/server/actors/highlighters/shapes");
|
||||
|
||||
function run_test() {
|
||||
|
@ -19,6 +20,7 @@ function run_test() {
|
|||
test_coord_to_percent();
|
||||
test_eval_calc_expression();
|
||||
test_shape_mode_to_css_property_name();
|
||||
test_get_circle_path();
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
|
@ -99,3 +101,27 @@ function test_shape_mode_to_css_property_name() {
|
|||
equal(shapeModeToCssPropertyName(expr), expected, desc);
|
||||
}
|
||||
}
|
||||
|
||||
function test_get_circle_path() {
|
||||
const tests = [{
|
||||
desc: "getCirclePath with no resizing, no zoom, 1:1 ratio",
|
||||
cx: 0, cy: 0, width: 100, height: 100, zoom: 1,
|
||||
expected: "M-10,0a10,10 0 1,0 20,0a10,10 0 1,0 -20,0"
|
||||
}, {
|
||||
desc: "getCirclePath with resizing, no zoom, 1:1 ratio",
|
||||
cx: 0, cy: 0, width: 200, height: 200, zoom: 1,
|
||||
expected: "M-5,0a5,5 0 1,0 10,0a5,5 0 1,0 -10,0"
|
||||
}, {
|
||||
desc: "getCirclePath with resizing, zoom, 1:1 ratio",
|
||||
cx: 0, cy: 0, width: 200, height: 200, zoom: 2,
|
||||
expected: "M-2.5,0a2.5,2.5 0 1,0 5,0a2.5,2.5 0 1,0 -5,0"
|
||||
}, {
|
||||
desc: "getCirclePath with resizing, zoom, non-square ratio",
|
||||
cx: 0, cy: 0, width: 100, height: 200, zoom: 2,
|
||||
expected: "M-5,0a5,2.5 0 1,0 10,0a5,2.5 0 1,0 -10,0"
|
||||
}];
|
||||
|
||||
for (let { desc, cx, cy, width, height, zoom, expected } of tests) {
|
||||
equal(getCirclePath(cx, cy, width, height, zoom), expected, desc);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -152,7 +152,11 @@ KeyframeEffectParams::ParseSpacing(const nsAString& aSpacing,
|
|||
nsCSSProps::LookupProperty(identToken, CSSEnabledState::eForAllContent);
|
||||
if (aPacedProperty == eCSSProperty_UNKNOWN ||
|
||||
aPacedProperty == eCSSPropertyExtra_variable ||
|
||||
!KeyframeUtils::IsAnimatableProperty(aPacedProperty)) {
|
||||
// We just unconditionally pass Gecko as the backend type here since
|
||||
// Servo doesn't support paced timing and this feature will soon be
|
||||
// removed (bug 1339690).
|
||||
!KeyframeUtils::IsAnimatableProperty(aPacedProperty,
|
||||
StyleBackendType::Gecko)) {
|
||||
aPacedProperty = eCSSProperty_UNKNOWN;
|
||||
aInvalidPacedProperty = identToken;
|
||||
}
|
||||
|
|
|
@ -368,6 +368,7 @@ static bool
|
|||
GetPropertyValuesPairs(JSContext* aCx,
|
||||
JS::Handle<JSObject*> aObject,
|
||||
ListAllowance aAllowLists,
|
||||
StyleBackendType aBackend,
|
||||
nsTArray<PropertyValuesPair>& aResult);
|
||||
|
||||
static bool
|
||||
|
@ -493,7 +494,10 @@ KeyframeUtils::ApplySpacing(nsTArray<Keyframe>& aKeyframes,
|
|||
|
||||
nsTArray<double> cumulativeDistances;
|
||||
if (aSpacingMode == SpacingMode::paced) {
|
||||
MOZ_ASSERT(IsAnimatableProperty(aProperty),
|
||||
// We just unconditionally pass Gecko as the backend type here since
|
||||
// Servo doesn't support paced timing and this feature will soon be removed
|
||||
// (bug 1339690).
|
||||
MOZ_ASSERT(IsAnimatableProperty(aProperty, StyleBackendType::Gecko),
|
||||
"Paced property should be animatable");
|
||||
|
||||
cumulativeDistances = GetCumulativeDistances(aComputedValues, aProperty,
|
||||
|
@ -717,8 +721,20 @@ KeyframeUtils::GetAnimationPropertiesFromKeyframes(
|
|||
}
|
||||
|
||||
/* static */ bool
|
||||
KeyframeUtils::IsAnimatableProperty(nsCSSPropertyID aProperty)
|
||||
KeyframeUtils::IsAnimatableProperty(nsCSSPropertyID aProperty,
|
||||
StyleBackendType aBackend)
|
||||
{
|
||||
// Regardless of the backend type, treat the 'display' property as not
|
||||
// animatable. (The Servo backend will report it as being animatable, since
|
||||
// it is in fact animatable by SMIL.)
|
||||
if (aProperty == eCSSProperty_display) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (aBackend == StyleBackendType::Servo) {
|
||||
return Servo_Property_IsAnimatable(aProperty);
|
||||
}
|
||||
|
||||
if (aProperty == eCSSProperty_UNKNOWN) {
|
||||
return false;
|
||||
}
|
||||
|
@ -869,6 +885,7 @@ ConvertKeyframeSequence(JSContext* aCx,
|
|||
JS::Rooted<JSObject*> object(aCx, &value.toObject());
|
||||
if (!GetPropertyValuesPairs(aCx, object,
|
||||
ListAllowance::eDisallow,
|
||||
aDocument->GetStyleBackendType(),
|
||||
propertyValuePairs)) {
|
||||
return false;
|
||||
}
|
||||
|
@ -905,6 +922,8 @@ ConvertKeyframeSequence(JSContext* aCx,
|
|||
* @param aAllowLists If eAllow, values will be converted to
|
||||
* (DOMString or sequence<DOMString); if eDisallow, values
|
||||
* will be converted to DOMString.
|
||||
* @param aBackend The style backend in use. Used to determine which properties
|
||||
* are animatable since only animatable properties are read.
|
||||
* @param aResult The array into which the enumerated property-values
|
||||
* pairs will be stored.
|
||||
* @return false on failure or JS exception thrown while interacting
|
||||
|
@ -914,6 +933,7 @@ static bool
|
|||
GetPropertyValuesPairs(JSContext* aCx,
|
||||
JS::Handle<JSObject*> aObject,
|
||||
ListAllowance aAllowLists,
|
||||
StyleBackendType aBackend,
|
||||
nsTArray<PropertyValuesPair>& aResult)
|
||||
{
|
||||
nsTArray<AdditionalProperty> properties;
|
||||
|
@ -936,7 +956,7 @@ GetPropertyValuesPairs(JSContext* aCx,
|
|||
nsCSSPropertyID property =
|
||||
nsCSSProps::LookupPropertyByIDLName(propName,
|
||||
CSSEnabledState::eForAllContent);
|
||||
if (KeyframeUtils::IsAnimatableProperty(property)) {
|
||||
if (KeyframeUtils::IsAnimatableProperty(property, aBackend)) {
|
||||
AdditionalProperty* p = properties.AppendElement();
|
||||
p->mProperty = property;
|
||||
p->mJsidIndex = i;
|
||||
|
@ -1450,6 +1470,7 @@ GetKeyframeListFromPropertyIndexedKeyframe(JSContext* aCx,
|
|||
JS::Rooted<JSObject*> object(aCx, &aValue.toObject());
|
||||
nsTArray<PropertyValuesPair> propertyValuesPairs;
|
||||
if (!GetPropertyValuesPairs(aCx, object, ListAllowance::eAllow,
|
||||
aDocument->GetStyleBackendType(),
|
||||
propertyValuesPairs)) {
|
||||
aRv.Throw(NS_ERROR_FAILURE);
|
||||
return;
|
||||
|
|
|
@ -158,9 +158,12 @@ public:
|
|||
* its subproperties, is animatable.
|
||||
*
|
||||
* @param aProperty The property to check.
|
||||
* @param aBackend The style backend, Servo or Gecko, that should determine
|
||||
* if the property is animatable or not.
|
||||
* @return true if |aProperty| is animatable.
|
||||
*/
|
||||
static bool IsAnimatableProperty(nsCSSPropertyID aProperty);
|
||||
static bool IsAnimatableProperty(nsCSSPropertyID aProperty,
|
||||
StyleBackendType aBackend);
|
||||
|
||||
/**
|
||||
* Parse a string representing a CSS property value into a
|
||||
|
|
|
@ -5,8 +5,8 @@ fuzzy-if(skiaContent,1,3) needs-focus skip-if(styloVsGecko) == input-number.html
|
|||
fuzzy-if(skiaContent,1,3) needs-focus fails-if(styloVsGecko) == input-time.html input-time-ref.html
|
||||
fuzzy-if(skiaContent,1,3) needs-focus == button-load.html button-ref.html
|
||||
fuzzy-if(skiaContent,1,3) needs-focus == button-create.html button-ref.html
|
||||
fuzzy-if(skiaContent,1,3) needs-focus fails-if(styloVsGecko) == textarea-load.html textarea-ref.html
|
||||
fuzzy-if(skiaContent,1,3) needs-focus fails-if(styloVsGecko) == textarea-create.html textarea-ref.html
|
||||
fuzzy-if(skiaContent,1,3) needs-focus == textarea-load.html textarea-ref.html
|
||||
fuzzy-if(skiaContent,1,3) needs-focus == textarea-create.html textarea-ref.html
|
||||
fuzzy-if(skiaContent,9,6) needs-focus == select-load.html select-ref.html
|
||||
fuzzy-if(skiaContent,2,4) needs-focus == select-create.html select-ref.html
|
||||
needs-focus == autofocus-after-load.html autofocus-after-load-ref.html
|
||||
|
|
|
@ -34,7 +34,10 @@ HLSResource::HLSResource(MediaResourceCallback* aCallback,
|
|||
nsIChannel* aChannel,
|
||||
nsIURI* aURI,
|
||||
const MediaContainerType& aContainerType)
|
||||
: BaseMediaResource(aCallback, aChannel, aURI, aContainerType)
|
||||
: mCallback(aCallback)
|
||||
, mChannel(aChannel)
|
||||
, mURI(aURI)
|
||||
, mContainerType(aContainerType)
|
||||
{
|
||||
nsCString spec;
|
||||
nsresult rv = aURI->GetSpec(spec);
|
||||
|
|
|
@ -36,7 +36,7 @@ private:
|
|||
HLSResource* mResource;
|
||||
};
|
||||
|
||||
class HLSResource final : public BaseMediaResource
|
||||
class HLSResource final : public MediaResource
|
||||
{
|
||||
public:
|
||||
HLSResource(MediaResourceCallback* aCallback,
|
||||
|
@ -120,6 +120,10 @@ private:
|
|||
return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
|
||||
}
|
||||
|
||||
RefPtr<MediaResourceCallback> mCallback;
|
||||
nsCOMPtr<nsIChannel> mChannel;
|
||||
nsCOMPtr<nsIURI> mURI;
|
||||
const MediaContainerType mContainerType;
|
||||
java::GeckoHLSResourceWrapper::GlobalRef mHLSResourceWrapper;
|
||||
java::GeckoHLSResourceWrapper::Callbacks::GlobalRef mJavaCallbacks;
|
||||
};
|
||||
|
|
|
@ -41,7 +41,20 @@ var statsExpectedByType = {
|
|||
"data-channel": { skip: true },
|
||||
"track": { skip: true },
|
||||
"transport": { skip: true },
|
||||
"candidate-pair": { skip : true },
|
||||
"candidate-pair": {
|
||||
expected: ["id", "timestamp", "type",
|
||||
"transportId", "localCandidateId", "remoteCandidateId", "state",
|
||||
"priority", "nominated", "writable", "readable",
|
||||
"bytesSent", "bytesReceived",
|
||||
"lastPacketSentTimestamp", "lastPacketReceivedTimestamp",],
|
||||
optional: ["selected",],
|
||||
unimplemented: ["totalRoundTripTime", "currentRoundTripTime",
|
||||
"availableOutgoingBitrate", "availableIncomingBitrate",
|
||||
"requestsReceived", "requestsSent", "responsesReceived",
|
||||
"responsesSent", "retransmissionsReceived", "retransmissionsSent",
|
||||
"consentRequestsSent",],
|
||||
deprecated: [],
|
||||
},
|
||||
"local-candidate": { skip: true },
|
||||
"remote-candidate": { skip: true },
|
||||
"certificate": { skip: true },
|
||||
|
@ -360,6 +373,79 @@ var pedanticChecks = report => {
|
|||
+ ".framesEncoded is a sane number for a short test. value="
|
||||
+ stat.framesEncoded);
|
||||
}
|
||||
} else if (stat.type == "candidate-pair") {
|
||||
//
|
||||
// Required fields
|
||||
//
|
||||
|
||||
// transportId
|
||||
ok(stat.transportId,
|
||||
stat.type + ".transportId has a value. value="
|
||||
+ stat.transportId);
|
||||
|
||||
// localCandidateId
|
||||
ok(stat.localCandidateId,
|
||||
stat.type + ".localCandidateId has a value. value="
|
||||
+ stat.localCandidateId);
|
||||
|
||||
// remoteCandidateId
|
||||
ok(stat.remoteCandidateId,
|
||||
stat.type + ".remoteCandidateId has a value. value="
|
||||
+ stat.remoteCandidateId);
|
||||
|
||||
// state
|
||||
ok(stat.state == "succeeded",
|
||||
stat.type + ".state is succeeded. value="
|
||||
+ stat.state);
|
||||
|
||||
// priority
|
||||
ok(stat.priority,
|
||||
stat.type + ".priority has a value. value="
|
||||
+ stat.priority);
|
||||
|
||||
// nominated
|
||||
ok(stat.nominated,
|
||||
stat.type + ".nominated is true. value="
|
||||
+ stat.nominated);
|
||||
|
||||
// readable
|
||||
ok(stat.readable,
|
||||
stat.type + ".readable is true. value="
|
||||
+ stat.readable);
|
||||
|
||||
// writable
|
||||
ok(stat.writable,
|
||||
stat.type + ".writable is true. value="
|
||||
+ stat.writable);
|
||||
|
||||
// bytesSent
|
||||
ok(stat.bytesSent > 20000 && stat.bytesSent < 800000,
|
||||
stat.type + ".bytesSent is a sane number (20,000<>800,000)for a short test. value="
|
||||
+ stat.bytesSent);
|
||||
|
||||
// bytesReceived
|
||||
ok(stat.bytesReceived > 20000 && stat.bytesReceived < 800000,
|
||||
stat.type + ".bytesReceived is a sane number (20,000<>800,000) for a short test. value="
|
||||
+ stat.bytesReceived);
|
||||
|
||||
// lastPacketSentTimestamp
|
||||
ok(stat.lastPacketSentTimestamp,
|
||||
stat.type + ".lastPacketSentTimestamp has a value. value="
|
||||
+ stat.lastPacketSentTimestamp);
|
||||
|
||||
// lastPacketReceivedTimestamp
|
||||
ok(stat.lastPacketReceivedTimestamp,
|
||||
stat.type + ".lastPacketReceivedTimestamp has a value. value="
|
||||
+ stat.lastPacketReceivedTimestamp);
|
||||
|
||||
//
|
||||
// Optional fields
|
||||
//
|
||||
// selected
|
||||
ok(stat.selected === undefined || stat.selected == true,
|
||||
stat.type + ".selected is undefined or true. value="
|
||||
+ stat.selected);
|
||||
|
||||
}
|
||||
|
||||
//
|
||||
|
|
|
@ -203,27 +203,37 @@ struct ParamTraits<mozilla::dom::RTCIceCandidatePairStats>
|
|||
|
||||
static void Write(Message* aMsg, const paramType& aParam)
|
||||
{
|
||||
WriteParam(aMsg, aParam.mComponentId);
|
||||
WriteParam(aMsg, aParam.mTransportId);
|
||||
WriteParam(aMsg, aParam.mLocalCandidateId);
|
||||
WriteParam(aMsg, aParam.mPriority);
|
||||
WriteParam(aMsg, aParam.mNominated);
|
||||
WriteParam(aMsg, aParam.mWritable);
|
||||
WriteParam(aMsg, aParam.mReadable);
|
||||
WriteParam(aMsg, aParam.mRemoteCandidateId);
|
||||
WriteParam(aMsg, aParam.mSelected);
|
||||
WriteParam(aMsg, aParam.mState);
|
||||
WriteParam(aMsg, aParam.mBytesSent);
|
||||
WriteParam(aMsg, aParam.mBytesReceived);
|
||||
WriteParam(aMsg, aParam.mLastPacketSentTimestamp);
|
||||
WriteParam(aMsg, aParam.mLastPacketReceivedTimestamp);
|
||||
WriteRTCStats(aMsg, aParam);
|
||||
}
|
||||
|
||||
static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
|
||||
{
|
||||
if (!ReadParam(aMsg, aIter, &(aResult->mComponentId)) ||
|
||||
if (!ReadParam(aMsg, aIter, &(aResult->mTransportId)) ||
|
||||
!ReadParam(aMsg, aIter, &(aResult->mLocalCandidateId)) ||
|
||||
!ReadParam(aMsg, aIter, &(aResult->mPriority)) ||
|
||||
!ReadParam(aMsg, aIter, &(aResult->mNominated)) ||
|
||||
!ReadParam(aMsg, aIter, &(aResult->mWritable)) ||
|
||||
!ReadParam(aMsg, aIter, &(aResult->mReadable)) ||
|
||||
!ReadParam(aMsg, aIter, &(aResult->mRemoteCandidateId)) ||
|
||||
!ReadParam(aMsg, aIter, &(aResult->mSelected)) ||
|
||||
!ReadParam(aMsg, aIter, &(aResult->mState)) ||
|
||||
!ReadParam(aMsg, aIter, &(aResult->mBytesSent)) ||
|
||||
!ReadParam(aMsg, aIter, &(aResult->mBytesReceived)) ||
|
||||
!ReadParam(aMsg, aIter, &(aResult->mLastPacketSentTimestamp)) ||
|
||||
!ReadParam(aMsg, aIter, &(aResult->mLastPacketReceivedTimestamp)) ||
|
||||
!ReadRTCStats(aMsg, aIter, aResult)) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -124,13 +124,18 @@ enum RTCStatsIceCandidatePairState {
|
|||
};
|
||||
|
||||
dictionary RTCIceCandidatePairStats : RTCStats {
|
||||
DOMString componentId;
|
||||
DOMString transportId;
|
||||
DOMString localCandidateId;
|
||||
DOMString remoteCandidateId;
|
||||
RTCStatsIceCandidatePairState state;
|
||||
unsigned long long priority;
|
||||
boolean readable;
|
||||
boolean nominated;
|
||||
boolean writable;
|
||||
boolean readable;
|
||||
unsigned long long bytesSent;
|
||||
unsigned long long bytesReceived;
|
||||
DOMHighResTimeStamp lastPacketSentTimestamp;
|
||||
DOMHighResTimeStamp lastPacketReceivedTimestamp;
|
||||
boolean selected;
|
||||
};
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ skip-if(Android) load 336744-1.html # bug 1268050
|
|||
load 336960-1.html
|
||||
load 342954-1.xhtml
|
||||
load 342954-2.xhtml
|
||||
asserts-if(stylo,4) load 368276-1.xhtml # bug 1370830
|
||||
load 368276-1.xhtml
|
||||
load 368641-1.xhtml
|
||||
load 378521-1.xhtml
|
||||
load 382376-1.xhtml
|
||||
|
|
|
@ -8,7 +8,7 @@ load 326644-2.html
|
|||
load 326864-1.xul
|
||||
load 326875-1.xul
|
||||
load 326881-1.xul
|
||||
asserts-if(stylo,4) load 329982-1.xhtml # bug 1370830
|
||||
load 329982-1.xhtml
|
||||
load 336096-1.xhtml
|
||||
load 344215-1.xul
|
||||
load 354611-1.html
|
||||
|
|
|
@ -66,7 +66,7 @@ DeleteNodeTransaction::DoTransaction()
|
|||
// *before* we do the action, unlike some of the other RangeItem update
|
||||
// methods.
|
||||
if (mRangeUpdater) {
|
||||
mRangeUpdater->SelAdjDeleteNode(mNodeToDelete->AsDOMNode());
|
||||
mRangeUpdater->SelAdjDeleteNode(mNodeToDelete);
|
||||
}
|
||||
|
||||
ErrorResult error;
|
||||
|
@ -96,7 +96,7 @@ DeleteNodeTransaction::RedoTransaction()
|
|||
}
|
||||
|
||||
if (mRangeUpdater) {
|
||||
mRangeUpdater->SelAdjDeleteNode(mNodeToDelete->AsDOMNode());
|
||||
mRangeUpdater->SelAdjDeleteNode(mNodeToDelete);
|
||||
}
|
||||
|
||||
ErrorResult error;
|
||||
|
|
|
@ -1453,7 +1453,7 @@ EditorBase::InsertNode(nsIContent& aNode,
|
|||
CreateTxnForInsertNode(aNode, aParent, aPosition);
|
||||
nsresult rv = DoTransaction(transaction);
|
||||
|
||||
mRangeUpdater.SelAdjInsertNode(aParent.AsDOMNode(), aPosition);
|
||||
mRangeUpdater.SelAdjInsertNode(&aParent, aPosition);
|
||||
|
||||
{
|
||||
AutoActionListenerArray listeners(mActionListeners);
|
||||
|
|
|
@ -242,14 +242,6 @@ RangeUpdater::SelAdjCreateNode(nsINode* aParent,
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
RangeUpdater::SelAdjCreateNode(nsIDOMNode* aParent,
|
||||
int32_t aPosition)
|
||||
{
|
||||
nsCOMPtr<nsINode> parent = do_QueryInterface(aParent);
|
||||
return SelAdjCreateNode(parent, aPosition);
|
||||
}
|
||||
|
||||
nsresult
|
||||
RangeUpdater::SelAdjInsertNode(nsINode* aParent,
|
||||
int32_t aPosition)
|
||||
|
@ -257,13 +249,6 @@ RangeUpdater::SelAdjInsertNode(nsINode* aParent,
|
|||
return SelAdjCreateNode(aParent, aPosition);
|
||||
}
|
||||
|
||||
nsresult
|
||||
RangeUpdater::SelAdjInsertNode(nsIDOMNode* aParent,
|
||||
int32_t aPosition)
|
||||
{
|
||||
return SelAdjCreateNode(aParent, aPosition);
|
||||
}
|
||||
|
||||
void
|
||||
RangeUpdater::SelAdjDeleteNode(nsINode* aNode)
|
||||
{
|
||||
|
@ -319,14 +304,6 @@ RangeUpdater::SelAdjDeleteNode(nsINode* aNode)
|
|||
}
|
||||
}
|
||||
|
||||
void
|
||||
RangeUpdater::SelAdjDeleteNode(nsIDOMNode* aNode)
|
||||
{
|
||||
nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
|
||||
NS_ENSURE_TRUE_VOID(node);
|
||||
return SelAdjDeleteNode(node);
|
||||
}
|
||||
|
||||
nsresult
|
||||
RangeUpdater::SelAdjSplitNode(nsIContent& aOldRightNode,
|
||||
int32_t aOffset,
|
||||
|
|
|
@ -109,11 +109,8 @@ public:
|
|||
// DOM Range gravity will promote the selection out of the node on deletion,
|
||||
// which is not what you want if you know you are reinserting it.
|
||||
nsresult SelAdjCreateNode(nsINode* aParent, int32_t aPosition);
|
||||
nsresult SelAdjCreateNode(nsIDOMNode* aParent, int32_t aPosition);
|
||||
nsresult SelAdjInsertNode(nsINode* aParent, int32_t aPosition);
|
||||
nsresult SelAdjInsertNode(nsIDOMNode* aParent, int32_t aPosition);
|
||||
void SelAdjDeleteNode(nsINode* aNode);
|
||||
void SelAdjDeleteNode(nsIDOMNode* aNode);
|
||||
nsresult SelAdjSplitNode(nsIContent& aOldRightNode, int32_t aOffset,
|
||||
nsIContent* aNewLeftNode);
|
||||
nsresult SelAdjJoinNodes(nsINode& aLeftNode,
|
||||
|
|
|
@ -25,6 +25,7 @@ namespace gfx {
|
|||
_(DIRECT_DRAW, Feature, "DirectDraw") \
|
||||
_(GPU_PROCESS, Feature, "GPU Process") \
|
||||
_(WEBRENDER, Feature, "WebRender") \
|
||||
_(OMTP, Feature, "Off Main Thread Painting") \
|
||||
/* Add new entries above this comment */
|
||||
|
||||
enum class Feature : uint32_t {
|
||||
|
|
|
@ -38,6 +38,7 @@ class gfxVarReceiver;
|
|||
_(UseWebRenderANGLE, bool, false) \
|
||||
_(ScreenDepth, int32_t, 0) \
|
||||
_(GREDirectory, nsCString, nsCString()) \
|
||||
_(UseOMTP, bool, false) \
|
||||
|
||||
/* Add new entries above this line. */
|
||||
|
||||
|
|
|
@ -79,4 +79,4 @@ to make sure that mozjs_sys also has its Cargo.lock file updated if needed, henc
|
|||
the need to run the cargo update command in js/src as well. Hopefully this will
|
||||
be resolved soon.
|
||||
|
||||
Latest Commit: b2614e4eb58f9dee08b8c38f96bc3bac834c837b
|
||||
Latest Commit: 6752684fcc7402b0a5480e0b9f73152b2f9ed1e5
|
||||
|
|
|
@ -1850,6 +1850,9 @@ Layer::PrintInfo(std::stringstream& aStream, const char* aPrefix)
|
|||
}
|
||||
if (mSimpleAttrs.ScrolledClip()) {
|
||||
AppendToString(aStream, mSimpleAttrs.ScrolledClip()->GetClipRect(), " [scrolled-clip=", "]");
|
||||
if (const Maybe<size_t>& ix = mSimpleAttrs.ScrolledClip()->GetMaskLayerIndex()) {
|
||||
AppendToString(aStream, ix.value(), " [scrolled-mask=", "]");
|
||||
}
|
||||
}
|
||||
if (1.0 != mSimpleAttrs.PostXScale() || 1.0 != mSimpleAttrs.PostYScale()) {
|
||||
aStream << nsPrintfCString(" [postScale=%g, %g]", mSimpleAttrs.PostXScale(), mSimpleAttrs.PostYScale()).get();
|
||||
|
|
|
@ -170,6 +170,9 @@ AppendToString(std::stringstream& aStream, const ScrollMetadata& m,
|
|||
if (m.HasScrollClip()) {
|
||||
AppendToString(aStream, m.ScrollClip().GetClipRect(), "] [clip=");
|
||||
}
|
||||
if (m.HasMaskLayer()) {
|
||||
AppendToString(aStream, m.ScrollClip().GetMaskLayerIndex().value(), "] [mask=");
|
||||
}
|
||||
aStream << "] }" << sfx;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=99: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "PaintThread.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace layers {
|
||||
|
||||
StaticAutoPtr<PaintThread> PaintThread::sSingleton;
|
||||
|
||||
bool
|
||||
PaintThread::Init()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
nsresult rv = NS_NewNamedThread("PaintThread", getter_AddRefs(PaintThread::sSingleton->mThread));
|
||||
|
||||
if (NS_FAILED(rv)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* static */ void
|
||||
PaintThread::Start()
|
||||
{
|
||||
PaintThread::sSingleton = new PaintThread();
|
||||
|
||||
if (!PaintThread::sSingleton->Init()) {
|
||||
gfxCriticalNote << "Unable to start paint thread";
|
||||
PaintThread::sSingleton = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
/* static */ PaintThread*
|
||||
PaintThread::Get()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
return PaintThread::sSingleton.get();
|
||||
}
|
||||
|
||||
/* static */ void
|
||||
PaintThread::Shutdown()
|
||||
{
|
||||
if (!PaintThread::sSingleton) {
|
||||
return;
|
||||
}
|
||||
|
||||
PaintThread::sSingleton->ShutdownImpl();
|
||||
PaintThread::sSingleton = nullptr;
|
||||
}
|
||||
|
||||
void
|
||||
PaintThread::ShutdownImpl()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
PaintThread::sSingleton->mThread->AsyncShutdown();
|
||||
}
|
||||
|
||||
} // namespace layers
|
||||
} // namespace mozilla
|
|
@ -0,0 +1,33 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set sw=2 sts=2 ts=8 et tw=99 : */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef MOZILLA_LAYERS_PAINTTHREAD_H
|
||||
#define MOZILLA_LAYERS_PAINTTHREAD_H
|
||||
|
||||
#include "mozilla/StaticPtr.h"
|
||||
#include "nsThreadUtils.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace layers {
|
||||
|
||||
class PaintThread final
|
||||
{
|
||||
public:
|
||||
static void Start();
|
||||
static void Shutdown();
|
||||
static PaintThread* Get();
|
||||
|
||||
private:
|
||||
bool Init();
|
||||
void ShutdownImpl();
|
||||
static StaticAutoPtr<PaintThread> sSingleton;
|
||||
RefPtr<nsIThread> mThread;
|
||||
};
|
||||
|
||||
} // namespace layers
|
||||
} // namespace mozilla
|
||||
|
||||
#endif
|
|
@ -196,6 +196,7 @@ EXPORTS.mozilla.layers += [
|
|||
'opengl/MacIOSurfaceTextureHostOGL.h',
|
||||
'opengl/TextureClientOGL.h',
|
||||
'opengl/TextureHostOGL.h',
|
||||
'PaintThread.h',
|
||||
'PersistentBufferProvider.h',
|
||||
'RenderTrace.h',
|
||||
'SourceSurfaceSharedData.h',
|
||||
|
@ -392,6 +393,7 @@ UNIFIED_SOURCES += [
|
|||
'opengl/TextureClientOGL.cpp',
|
||||
'opengl/TextureHostOGL.cpp',
|
||||
'opengl/TexturePoolOGL.cpp',
|
||||
'PaintThread.cpp',
|
||||
'protobuf/LayerScopePacket.pb.cc',
|
||||
'ReadbackProcessor.cpp',
|
||||
'RenderTrace.cpp',
|
||||
|
|
|
@ -388,7 +388,7 @@ DriverCrashGuard::FlushPreferences()
|
|||
MOZ_ASSERT(XRE_IsParentProcess());
|
||||
|
||||
if (nsIPrefService* prefService = Preferences::GetService()) {
|
||||
prefService->SavePrefFile(nullptr);
|
||||
static_cast<Preferences *>(prefService)->SavePrefFileBlocking();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include "mozilla/layers/ImageBridgeChild.h"
|
||||
#include "mozilla/layers/ISurfaceAllocator.h" // for GfxMemoryImageReporter
|
||||
#include "mozilla/webrender/RenderThread.h"
|
||||
#include "mozilla/layers/PaintThread.h"
|
||||
#include "mozilla/gfx/gfxVars.h"
|
||||
#include "mozilla/gfx/GPUProcessManager.h"
|
||||
#include "mozilla/gfx/GraphicsMessages.h"
|
||||
|
@ -694,6 +695,7 @@ gfxPlatform::Init()
|
|||
#endif
|
||||
gPlatform->InitAcceleration();
|
||||
gPlatform->InitWebRenderConfig();
|
||||
gPlatform->InitOMTPConfig();
|
||||
|
||||
if (gfxConfig::IsEnabled(Feature::GPU_PROCESS)) {
|
||||
GPUProcessManager* gpu = GPUProcessManager::Get();
|
||||
|
@ -942,18 +944,22 @@ gfxPlatform::Shutdown()
|
|||
/* static */ void
|
||||
gfxPlatform::InitLayersIPC()
|
||||
{
|
||||
if (sLayersIPCIsUp) {
|
||||
return;
|
||||
}
|
||||
sLayersIPCIsUp = true;
|
||||
if (sLayersIPCIsUp) {
|
||||
return;
|
||||
}
|
||||
sLayersIPCIsUp = true;
|
||||
|
||||
if (XRE_IsParentProcess())
|
||||
{
|
||||
if (gfxVars::UseWebRender()) {
|
||||
wr::RenderThread::Start();
|
||||
}
|
||||
layers::CompositorThreadHolder::Start();
|
||||
if (XRE_IsContentProcess()) {
|
||||
if (gfxVars::UseOMTP()) {
|
||||
layers::PaintThread::Start();
|
||||
}
|
||||
} else if (XRE_IsParentProcess()) {
|
||||
if (gfxVars::UseWebRender()) {
|
||||
wr::RenderThread::Start();
|
||||
}
|
||||
|
||||
layers::CompositorThreadHolder::Start();
|
||||
}
|
||||
}
|
||||
|
||||
/* static */ void
|
||||
|
@ -971,6 +977,10 @@ gfxPlatform::ShutdownLayersIPC()
|
|||
layers::CompositorBridgeChild::ShutDown();
|
||||
layers::ImageBridgeChild::ShutDown();
|
||||
}
|
||||
|
||||
if (gfxVars::UseOMTP()) {
|
||||
layers::PaintThread::Shutdown();
|
||||
}
|
||||
} else if (XRE_IsParentProcess()) {
|
||||
gfx::VRManagerChild::ShutDown();
|
||||
layers::CompositorBridgeChild::ShutDown();
|
||||
|
@ -978,8 +988,9 @@ gfxPlatform::ShutdownLayersIPC()
|
|||
// This has to happen after shutting down the child protocols.
|
||||
layers::CompositorThreadHolder::Shutdown();
|
||||
if (gfxVars::UseWebRender()) {
|
||||
wr::RenderThread::ShutDown();
|
||||
wr::RenderThread::ShutDown();
|
||||
}
|
||||
|
||||
} else {
|
||||
// TODO: There are other kind of processes and we should make sure gfx
|
||||
// stuff is either not created there or shut down properly.
|
||||
|
@ -2397,6 +2408,48 @@ gfxPlatform::InitWebRenderConfig()
|
|||
}
|
||||
}
|
||||
|
||||
void
|
||||
gfxPlatform::InitOMTPConfig()
|
||||
{
|
||||
bool prefEnabled = Preferences::GetBool("layers.omtp.enabled", false);
|
||||
|
||||
// We don't want to report anything for this feature when turned off, as it is still early in development
|
||||
if (!prefEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
ScopedGfxFeatureReporter reporter("OMTP", prefEnabled);
|
||||
|
||||
if (!XRE_IsParentProcess()) {
|
||||
// The parent process runs through all the real decision-making code
|
||||
// later in this function. For other processes we still want to report
|
||||
// the state of the feature for crash reports.
|
||||
if (gfxVars::UseOMTP()) {
|
||||
reporter.SetSuccessful();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
FeatureState& featureOMTP = gfxConfig::GetFeature(Feature::OMTP);
|
||||
|
||||
featureOMTP.DisableByDefault(
|
||||
FeatureStatus::OptIn,
|
||||
"OMTP is an opt-in feature",
|
||||
NS_LITERAL_CSTRING("FEATURE_FAILURE_DEFAULT_OFF"));
|
||||
|
||||
featureOMTP.UserEnable("Enabled by pref");
|
||||
|
||||
if (InSafeMode()) {
|
||||
featureOMTP.ForceDisable(FeatureStatus::Blocked, "OMTP blocked by safe-mode",
|
||||
NS_LITERAL_CSTRING("FEATURE_FAILURE_COMP_SAFEMODE"));
|
||||
}
|
||||
|
||||
if (gfxConfig::IsEnabled(Feature::OMTP)) {
|
||||
gfxVars::SetUseOMTP(true);
|
||||
reporter.SetSuccessful();
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
gfxPlatform::CanUseHardwareVideoDecoding()
|
||||
{
|
||||
|
|
|
@ -245,7 +245,7 @@ public:
|
|||
|
||||
static already_AddRefed<DrawTarget>
|
||||
CreateDrawTargetForData(unsigned char* aData,
|
||||
const mozilla::gfx::IntSize& aSize,
|
||||
const mozilla::gfx::IntSize& aSize,
|
||||
int32_t aStride,
|
||||
mozilla::gfx::SurfaceFormat aFormat,
|
||||
bool aUninitialized = false);
|
||||
|
@ -376,7 +376,7 @@ public:
|
|||
gfxTextPerfMetrics* aTextPerf,
|
||||
gfxUserFontSet *aUserFontSet,
|
||||
gfxFloat aDevToCssSize) = 0;
|
||||
|
||||
|
||||
/**
|
||||
* Look up a local platform font using the full font face name.
|
||||
* (Needed to support @font-face src local().)
|
||||
|
@ -780,7 +780,7 @@ protected:
|
|||
|
||||
int8_t mBidiNumeralOption;
|
||||
|
||||
// whether to always search font cmaps globally
|
||||
// whether to always search font cmaps globally
|
||||
// when doing system font fallback
|
||||
int8_t mFallbackUsesCmaps;
|
||||
|
||||
|
@ -827,6 +827,7 @@ private:
|
|||
void InitCompositorAccelerationPrefs();
|
||||
void InitGPUProcessPrefs();
|
||||
void InitWebRenderConfig();
|
||||
void InitOMTPConfig();
|
||||
|
||||
static bool IsDXInterop2Blocked();
|
||||
|
||||
|
|
|
@ -17,20 +17,20 @@ app_units = "0.4"
|
|||
bincode = "1.0.0-alpha6"
|
||||
bit-set = "0.4"
|
||||
byteorder = "1.0"
|
||||
euclid = "0.13"
|
||||
euclid = "0.14.4"
|
||||
fnv = "1.0"
|
||||
gleam = "0.4.3"
|
||||
lazy_static = "0.2"
|
||||
log = "0.3"
|
||||
num-traits = "0.1.32"
|
||||
offscreen_gl_context = {version = "0.8.0", features = ["serde", "osmesa"], optional = true}
|
||||
offscreen_gl_context = {version = "0.9.0", features = ["serde", "osmesa"], optional = true}
|
||||
time = "0.1"
|
||||
rayon = {version = "0.7", features = ["unstable"]}
|
||||
rayon = "0.8"
|
||||
webrender_traits = {path = "../webrender_traits"}
|
||||
bitflags = "0.7"
|
||||
gamma-lut = "0.2"
|
||||
thread_profiler = "0.1.1"
|
||||
plane-split = "0.4"
|
||||
plane-split = "0.5"
|
||||
|
||||
[dev-dependencies]
|
||||
angle = {git = "https://github.com/servo/angle", branch = "servo"}
|
||||
|
@ -46,15 +46,3 @@ dwrote = "0.3"
|
|||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
core-graphics = "0.7.0"
|
||||
core-text = "4.0"
|
||||
|
||||
[[example]]
|
||||
name = "basic"
|
||||
|
||||
[[example]]
|
||||
name = "blob"
|
||||
|
||||
[[example]]
|
||||
name = "scrolling"
|
||||
|
||||
[[example]]
|
||||
name = "yuv"
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
/* 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/. */
|
||||
|
||||
extern crate gleam;
|
||||
extern crate glutin;
|
||||
extern crate webrender;
|
||||
extern crate webrender_traits;
|
||||
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
|
||||
#[path="common/boilerplate.rs"]
|
||||
mod boilerplate;
|
||||
|
||||
use boilerplate::HandyDandyRectBuilder;
|
||||
use std::sync::Mutex;
|
||||
use webrender_traits::*;
|
||||
|
||||
// This example creates a 100x100 white rect and allows the user to move it
|
||||
// around by using the arrow keys. It does this by using the animation API.
|
||||
|
||||
fn body(_api: &RenderApi,
|
||||
builder: &mut DisplayListBuilder,
|
||||
_pipeline_id: &PipelineId,
|
||||
_layout_size: &LayoutSize)
|
||||
{
|
||||
// Create a 100x100 stacking context with an animatable transform property.
|
||||
// Note the magic "42" we use as the animation key. That is used to update
|
||||
// the transform in the keyboard event handler code.
|
||||
let bounds = (0,0).to(100, 100);
|
||||
builder.push_stacking_context(webrender_traits::ScrollPolicy::Scrollable,
|
||||
bounds,
|
||||
Some(PropertyBinding::Binding(PropertyBindingKey::new(42))),
|
||||
TransformStyle::Flat,
|
||||
None,
|
||||
webrender_traits::MixBlendMode::Normal,
|
||||
Vec::new());
|
||||
|
||||
// Fill it with a white rect
|
||||
let clip = builder.push_clip_region(&bounds, vec![], None);
|
||||
builder.push_rect(bounds,
|
||||
clip,
|
||||
ColorF::new(1.0, 1.0, 1.0, 1.0));
|
||||
|
||||
builder.pop_stacking_context();
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref TRANSFORM: Mutex<LayoutTransform> = Mutex::new(LayoutTransform::identity());
|
||||
}
|
||||
|
||||
fn event_handler(event: &glutin::Event,
|
||||
api: &RenderApi)
|
||||
{
|
||||
match *event {
|
||||
glutin::Event::KeyboardInput(glutin::ElementState::Pressed, _, Some(key)) => {
|
||||
let offset = match key {
|
||||
glutin::VirtualKeyCode::Down => (0.0, 10.0),
|
||||
glutin::VirtualKeyCode::Up => (0.0, -10.0),
|
||||
glutin::VirtualKeyCode::Right => (10.0, 0.0),
|
||||
glutin::VirtualKeyCode::Left => (-10.0, 0.0),
|
||||
_ => return,
|
||||
};
|
||||
// Update the transform based on the keyboard input and push it to
|
||||
// webrender using the generate_frame API. This will recomposite with
|
||||
// the updated transform.
|
||||
let new_transform = TRANSFORM.lock().unwrap().post_translate(LayoutVector3D::new(offset.0, offset.1, 0.0));
|
||||
api.generate_frame(Some(DynamicProperties {
|
||||
transforms: vec![
|
||||
PropertyValue {
|
||||
key: PropertyBindingKey::new(42),
|
||||
value: new_transform,
|
||||
},
|
||||
],
|
||||
floats: vec![],
|
||||
}));
|
||||
*TRANSFORM.lock().unwrap() = new_transform;
|
||||
}
|
||||
_ => ()
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
boilerplate::main_wrapper(body, event_handler, None);
|
||||
}
|
|
@ -21,6 +21,7 @@ use webrender_traits::{ClipRegionToken, ColorF, DisplayListBuilder, Epoch, Glyph
|
|||
use webrender_traits::{DeviceIntPoint, DeviceUintSize, LayoutPoint, LayoutRect, LayoutSize};
|
||||
use webrender_traits::{ImageData, ImageDescriptor, ImageFormat};
|
||||
use webrender_traits::{PipelineId, RenderApi, TransformStyle, BoxShadowClipMode};
|
||||
use euclid::vec2;
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Gesture {
|
||||
|
@ -381,7 +382,7 @@ fn main() {
|
|||
let rect = LayoutRect::new(LayoutPoint::new(0.0, 0.0), LayoutSize::new(0.0, 0.0));
|
||||
let simple_box_bounds = LayoutRect::new(LayoutPoint::new(20.0, 200.0),
|
||||
LayoutSize::new(50.0, 50.0));
|
||||
let offset = LayoutPoint::new(10.0, 10.0);
|
||||
let offset = vec2(10.0, 10.0);
|
||||
let color = ColorF::new(1.0, 1.0, 1.0, 1.0);
|
||||
let blur_radius = 0.0;
|
||||
let spread_radius = 0.0;
|
||||
|
|
|
@ -10,37 +10,36 @@ extern crate webrender;
|
|||
extern crate webrender_traits;
|
||||
extern crate rayon;
|
||||
|
||||
use gleam::gl;
|
||||
#[path="common/boilerplate.rs"]
|
||||
mod boilerplate;
|
||||
|
||||
use boilerplate::HandyDandyRectBuilder;
|
||||
use rayon::ThreadPool;
|
||||
use rayon::Configuration as ThreadPoolConfig;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::hash_map::Entry;
|
||||
use std::sync::Arc;
|
||||
use std::sync::mpsc::{channel, Sender, Receiver};
|
||||
use webrender_traits::{BlobImageData, BlobImageDescriptor, BlobImageError, BlobImageRenderer, BlobImageRequest};
|
||||
use webrender_traits::{BlobImageResult, TileOffset, ImageStore, ColorF, ColorU, Epoch};
|
||||
use webrender_traits::{DeviceUintSize, DeviceUintRect, LayoutPoint, LayoutRect, LayoutSize};
|
||||
use webrender_traits::{ImageData, ImageDescriptor, ImageFormat, ImageRendering, ImageKey, TileSize};
|
||||
use webrender_traits::{PipelineId, RasterizedBlobImage, TransformStyle};
|
||||
use webrender_traits as wt;
|
||||
|
||||
// This example shows how to implement a very basic BlobImageRenderer that can only render
|
||||
// a checkerboard pattern.
|
||||
|
||||
// The deserialized command list internally used by this example is just a color.
|
||||
type ImageRenderingCommands = ColorU;
|
||||
type ImageRenderingCommands = wt::ColorU;
|
||||
|
||||
// Serialize/deserialze the blob.
|
||||
// Ror real usecases you should probably use serde rather than doing it by hand.
|
||||
|
||||
fn serialize_blob(color: ColorU) -> Vec<u8> {
|
||||
fn serialize_blob(color: wt::ColorU) -> Vec<u8> {
|
||||
vec![color.r, color.g, color.b, color.a]
|
||||
}
|
||||
|
||||
fn deserialize_blob(blob: &[u8]) -> Result<ImageRenderingCommands, ()> {
|
||||
let mut iter = blob.iter();
|
||||
return match (iter.next(), iter.next(), iter.next(), iter.next()) {
|
||||
(Some(&r), Some(&g), Some(&b), Some(&a)) => Ok(ColorU::new(r, g, b, a)),
|
||||
(Some(&a), None, None, None) => Ok(ColorU::new(a, a, a, a)),
|
||||
(Some(&r), Some(&g), Some(&b), Some(&a)) => Ok(wt::ColorU::new(r, g, b, a)),
|
||||
(Some(&a), None, None, None) => Ok(wt::ColorU::new(a, a, a, a)),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
|
@ -49,9 +48,9 @@ fn deserialize_blob(blob: &[u8]) -> Result<ImageRenderingCommands, ()> {
|
|||
// actual image data.
|
||||
fn render_blob(
|
||||
commands: Arc<ImageRenderingCommands>,
|
||||
descriptor: &BlobImageDescriptor,
|
||||
tile: Option<TileOffset>,
|
||||
) -> BlobImageResult {
|
||||
descriptor: &wt::BlobImageDescriptor,
|
||||
tile: Option<wt::TileOffset>,
|
||||
) -> wt::BlobImageResult {
|
||||
let color = *commands;
|
||||
|
||||
// Allocate storage for the result. Right now the resource cache expects the
|
||||
|
@ -78,17 +77,17 @@ fn render_blob(
|
|||
let tc = if tile_checker { 0 } else { (1 - checker) * 40 };
|
||||
|
||||
match descriptor.format {
|
||||
ImageFormat::RGBA8 => {
|
||||
wt::ImageFormat::RGBA8 => {
|
||||
texels.push(color.b * checker + tc);
|
||||
texels.push(color.g * checker + tc);
|
||||
texels.push(color.r * checker + tc);
|
||||
texels.push(color.a * checker + tc);
|
||||
}
|
||||
ImageFormat::A8 => {
|
||||
wt::ImageFormat::A8 => {
|
||||
texels.push(color.a * checker + tc);
|
||||
}
|
||||
_ => {
|
||||
return Err(BlobImageError::Other(format!(
|
||||
return Err(wt::BlobImageError::Other(format!(
|
||||
"Usupported image format {:?}",
|
||||
descriptor.format
|
||||
)));
|
||||
|
@ -97,7 +96,7 @@ fn render_blob(
|
|||
}
|
||||
}
|
||||
|
||||
Ok(RasterizedBlobImage {
|
||||
Ok(wt::RasterizedBlobImage {
|
||||
data: texels,
|
||||
width: descriptor.width,
|
||||
height: descriptor.height,
|
||||
|
@ -112,18 +111,18 @@ struct CheckerboardRenderer {
|
|||
workers: Arc<ThreadPool>,
|
||||
|
||||
// the workers will use an mpsc channel to communicate the result.
|
||||
tx: Sender<(BlobImageRequest, BlobImageResult)>,
|
||||
rx: Receiver<(BlobImageRequest, BlobImageResult)>,
|
||||
tx: Sender<(wt::BlobImageRequest, wt::BlobImageResult)>,
|
||||
rx: Receiver<(wt::BlobImageRequest, wt::BlobImageResult)>,
|
||||
|
||||
// The deserialized drawing commands.
|
||||
// In this example we store them in Arcs. This isn't necessary since in this simplified
|
||||
// case the command list is a simple 32 bits value and would be cheap to clone before sending
|
||||
// to the workers. But in a more realistic scenario the commands would typically be bigger
|
||||
// and more expensive to clone, so let's pretend it is also the case here.
|
||||
image_cmds: HashMap<ImageKey, Arc<ImageRenderingCommands>>,
|
||||
image_cmds: HashMap<wt::ImageKey, Arc<ImageRenderingCommands>>,
|
||||
|
||||
// The images rendered in the current frame (not kept here between frames).
|
||||
rendered_images: HashMap<BlobImageRequest, Option<BlobImageResult>>,
|
||||
rendered_images: HashMap<wt::BlobImageRequest, Option<wt::BlobImageResult>>,
|
||||
}
|
||||
|
||||
impl CheckerboardRenderer {
|
||||
|
@ -139,26 +138,26 @@ impl CheckerboardRenderer {
|
|||
}
|
||||
}
|
||||
|
||||
impl BlobImageRenderer for CheckerboardRenderer {
|
||||
fn add(&mut self, key: ImageKey, cmds: BlobImageData, _: Option<TileSize>) {
|
||||
impl wt::BlobImageRenderer for CheckerboardRenderer {
|
||||
fn add(&mut self, key: wt::ImageKey, cmds: wt::BlobImageData, _: Option<wt::TileSize>) {
|
||||
self.image_cmds.insert(key, Arc::new(deserialize_blob(&cmds[..]).unwrap()));
|
||||
}
|
||||
|
||||
fn update(&mut self, key: ImageKey, cmds: BlobImageData) {
|
||||
fn update(&mut self, key: wt::ImageKey, cmds: wt::BlobImageData) {
|
||||
// Here, updating is just replacing the current version of the commands with
|
||||
// the new one (no incremental updates).
|
||||
self.image_cmds.insert(key, Arc::new(deserialize_blob(&cmds[..]).unwrap()));
|
||||
}
|
||||
|
||||
fn delete(&mut self, key: ImageKey) {
|
||||
fn delete(&mut self, key: wt::ImageKey) {
|
||||
self.image_cmds.remove(&key);
|
||||
}
|
||||
|
||||
fn request(&mut self,
|
||||
request: BlobImageRequest,
|
||||
descriptor: &BlobImageDescriptor,
|
||||
_dirty_rect: Option<DeviceUintRect>,
|
||||
_images: &ImageStore) {
|
||||
resources: &wt::BlobImageResources,
|
||||
request: wt::BlobImageRequest,
|
||||
descriptor: &wt::BlobImageDescriptor,
|
||||
_dirty_rect: Option<wt::DeviceUintRect>) {
|
||||
// This method is where we kick off our rendering jobs.
|
||||
// It should avoid doing work on the calling thread as much as possible.
|
||||
// In this example we will use the thread pool to render individual tiles.
|
||||
|
@ -168,7 +167,7 @@ impl BlobImageRenderer for CheckerboardRenderer {
|
|||
let tx = self.tx.clone();
|
||||
let descriptor = descriptor.clone();
|
||||
|
||||
self.workers.spawn_async(move || {
|
||||
self.workers.spawn(move || {
|
||||
let result = render_blob(cmds, &descriptor, request.tile);
|
||||
tx.send((request, result)).unwrap();
|
||||
});
|
||||
|
@ -180,7 +179,7 @@ impl BlobImageRenderer for CheckerboardRenderer {
|
|||
self.rendered_images.insert(request, None);
|
||||
}
|
||||
|
||||
fn resolve(&mut self, request: BlobImageRequest) -> BlobImageResult {
|
||||
fn resolve(&mut self, request: wt::BlobImageRequest) -> wt::BlobImageResult {
|
||||
// In this method we wait until the work is complete on the worker threads and
|
||||
// gather the results.
|
||||
|
||||
|
@ -188,7 +187,7 @@ impl BlobImageRenderer for CheckerboardRenderer {
|
|||
// that we are looking for.
|
||||
match self.rendered_images.entry(request) {
|
||||
Entry::Vacant(_) => {
|
||||
return Err(BlobImageError::InvalidKey);
|
||||
return Err(wt::BlobImageError::InvalidKey);
|
||||
}
|
||||
Entry::Occupied(entry) => {
|
||||
// None means we haven't yet received the result.
|
||||
|
@ -209,34 +208,70 @@ impl BlobImageRenderer for CheckerboardRenderer {
|
|||
}
|
||||
|
||||
// If we break out of the loop above it means the channel closed unexpectedly.
|
||||
Err(BlobImageError::Other("Channel closed".into()))
|
||||
Err(wt::BlobImageError::Other("Channel closed".into()))
|
||||
}
|
||||
fn delete_font(&mut self, font: wt::FontKey) {}
|
||||
}
|
||||
|
||||
fn body(api: &wt::RenderApi,
|
||||
builder: &mut wt::DisplayListBuilder,
|
||||
_pipeline_id: &wt::PipelineId,
|
||||
layout_size: &wt::LayoutSize)
|
||||
{
|
||||
let blob_img1 = api.generate_image_key();
|
||||
api.add_image(
|
||||
blob_img1,
|
||||
wt::ImageDescriptor::new(500, 500, wt::ImageFormat::RGBA8, true),
|
||||
wt::ImageData::new_blob_image(serialize_blob(wt::ColorU::new(50, 50, 150, 255))),
|
||||
Some(128),
|
||||
);
|
||||
|
||||
let blob_img2 = api.generate_image_key();
|
||||
api.add_image(
|
||||
blob_img2,
|
||||
wt::ImageDescriptor::new(200, 200, wt::ImageFormat::RGBA8, true),
|
||||
wt::ImageData::new_blob_image(serialize_blob(wt::ColorU::new(50, 150, 50, 255))),
|
||||
None,
|
||||
);
|
||||
|
||||
let bounds = wt::LayoutRect::new(wt::LayoutPoint::zero(), *layout_size);
|
||||
builder.push_stacking_context(wt::ScrollPolicy::Scrollable,
|
||||
bounds,
|
||||
None,
|
||||
wt::TransformStyle::Flat,
|
||||
None,
|
||||
wt::MixBlendMode::Normal,
|
||||
Vec::new());
|
||||
|
||||
let clip = builder.push_clip_region(&bounds, vec![], None);
|
||||
builder.push_image(
|
||||
(30, 30).by(500, 500),
|
||||
clip,
|
||||
wt::LayoutSize::new(500.0, 500.0),
|
||||
wt::LayoutSize::new(0.0, 0.0),
|
||||
wt::ImageRendering::Auto,
|
||||
blob_img1,
|
||||
);
|
||||
|
||||
let clip = builder.push_clip_region(&bounds, vec![], None);
|
||||
builder.push_image(
|
||||
(600, 600).by(200, 200),
|
||||
clip,
|
||||
wt::LayoutSize::new(200.0, 200.0),
|
||||
wt::LayoutSize::new(0.0, 0.0),
|
||||
wt::ImageRendering::Auto,
|
||||
blob_img2,
|
||||
);
|
||||
|
||||
builder.pop_stacking_context();
|
||||
}
|
||||
|
||||
fn event_handler(_event: &glutin::Event,
|
||||
_api: &wt::RenderApi)
|
||||
{
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let window = glutin::WindowBuilder::new()
|
||||
.with_title("WebRender Sample (BlobImageRenderer)")
|
||||
.with_multitouch()
|
||||
.with_gl(glutin::GlRequest::GlThenGles {
|
||||
opengl_version: (3, 2),
|
||||
opengles_version: (3, 0)
|
||||
})
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
unsafe {
|
||||
window.make_current().ok();
|
||||
}
|
||||
|
||||
let gl = match gl::GlType::default() {
|
||||
gl::GlType::Gl => unsafe { gl::GlFns::load_with(|symbol| window.get_proc_address(symbol) as *const _) },
|
||||
gl::GlType::Gles => unsafe { gl::GlesFns::load_with(|symbol| window.get_proc_address(symbol) as *const _) },
|
||||
};
|
||||
|
||||
println!("OpenGL version {}", gl.get_string(gl::VERSION));
|
||||
|
||||
let (width, height) = window.get_inner_size_pixels().unwrap();
|
||||
|
||||
let worker_config = ThreadPoolConfig::new().thread_name(|idx|{
|
||||
format!("WebRender:Worker#{}", idx)
|
||||
});
|
||||
|
@ -244,134 +279,12 @@ fn main() {
|
|||
let workers = Arc::new(ThreadPool::new(worker_config).unwrap());
|
||||
|
||||
let opts = webrender::RendererOptions {
|
||||
debug: true,
|
||||
workers: Some(Arc::clone(&workers)),
|
||||
// Register our blob renderer, so that WebRender integrates it in the resource cache..
|
||||
// Share the same pool of worker threads between WebRender and our blob renderer.
|
||||
blob_image_renderer: Some(Box::new(CheckerboardRenderer::new(Arc::clone(&workers)))),
|
||||
device_pixel_ratio: window.hidpi_factor(),
|
||||
.. Default::default()
|
||||
};
|
||||
|
||||
let size = DeviceUintSize::new(width, height);
|
||||
let (mut renderer, sender) = webrender::renderer::Renderer::new(gl, opts, size).unwrap();
|
||||
let api = sender.create_api();
|
||||
|
||||
let notifier = Box::new(Notifier::new(window.create_window_proxy()));
|
||||
renderer.set_render_notifier(notifier);
|
||||
|
||||
let epoch = Epoch(0);
|
||||
let root_background_color = ColorF::new(0.2, 0.2, 0.2, 1.0);
|
||||
|
||||
let blob_img1 = api.generate_image_key();
|
||||
api.add_image(
|
||||
blob_img1,
|
||||
ImageDescriptor::new(500, 500, ImageFormat::RGBA8, true),
|
||||
ImageData::new_blob_image(serialize_blob(ColorU::new(50, 50, 150, 255))),
|
||||
Some(128),
|
||||
);
|
||||
|
||||
let blob_img2 = api.generate_image_key();
|
||||
api.add_image(
|
||||
blob_img2,
|
||||
ImageDescriptor::new(200, 200, ImageFormat::RGBA8, true),
|
||||
ImageData::new_blob_image(serialize_blob(ColorU::new(50, 150, 50, 255))),
|
||||
None,
|
||||
);
|
||||
|
||||
let pipeline_id = PipelineId(0, 0);
|
||||
let layout_size = LayoutSize::new(width as f32, height as f32);
|
||||
let mut builder = webrender_traits::DisplayListBuilder::new(pipeline_id, layout_size);
|
||||
|
||||
let bounds = LayoutRect::new(LayoutPoint::zero(), layout_size);
|
||||
builder.push_stacking_context(webrender_traits::ScrollPolicy::Scrollable,
|
||||
bounds,
|
||||
None,
|
||||
TransformStyle::Flat,
|
||||
None,
|
||||
webrender_traits::MixBlendMode::Normal,
|
||||
Vec::new());
|
||||
|
||||
let clip = builder.push_clip_region(&bounds, vec![], None);
|
||||
builder.push_image(
|
||||
LayoutRect::new(LayoutPoint::new(30.0, 30.0), LayoutSize::new(500.0, 500.0)),
|
||||
clip,
|
||||
LayoutSize::new(500.0, 500.0),
|
||||
LayoutSize::new(0.0, 0.0),
|
||||
ImageRendering::Auto,
|
||||
blob_img1,
|
||||
);
|
||||
|
||||
let clip = builder.push_clip_region(&bounds, vec![], None);
|
||||
builder.push_image(
|
||||
LayoutRect::new(LayoutPoint::new(600.0, 60.0), LayoutSize::new(200.0, 200.0)),
|
||||
clip,
|
||||
LayoutSize::new(200.0, 200.0),
|
||||
LayoutSize::new(0.0, 0.0),
|
||||
ImageRendering::Auto,
|
||||
blob_img2,
|
||||
);
|
||||
|
||||
builder.pop_stacking_context();
|
||||
|
||||
api.set_display_list(
|
||||
Some(root_background_color),
|
||||
epoch,
|
||||
LayoutSize::new(width as f32, height as f32),
|
||||
builder.finalize(),
|
||||
true);
|
||||
api.set_root_pipeline(pipeline_id);
|
||||
api.generate_frame(None);
|
||||
|
||||
'outer: for event in window.wait_events() {
|
||||
let mut events = Vec::new();
|
||||
events.push(event);
|
||||
|
||||
for event in window.poll_events() {
|
||||
events.push(event);
|
||||
}
|
||||
|
||||
for event in events {
|
||||
match event {
|
||||
glutin::Event::Closed |
|
||||
glutin::Event::KeyboardInput(_, _, Some(glutin::VirtualKeyCode::Escape)) |
|
||||
glutin::Event::KeyboardInput(_, _, Some(glutin::VirtualKeyCode::Q)) => break 'outer,
|
||||
glutin::Event::KeyboardInput(glutin::ElementState::Pressed,
|
||||
_, Some(glutin::VirtualKeyCode::P)) => {
|
||||
let enable_profiler = !renderer.get_profiler_enabled();
|
||||
renderer.set_profiler_enabled(enable_profiler);
|
||||
api.generate_frame(None);
|
||||
}
|
||||
_ => ()
|
||||
}
|
||||
}
|
||||
|
||||
renderer.update();
|
||||
renderer.render(DeviceUintSize::new(width, height));
|
||||
window.swap_buffers().ok();
|
||||
}
|
||||
}
|
||||
|
||||
struct Notifier {
|
||||
window_proxy: glutin::WindowProxy,
|
||||
}
|
||||
|
||||
impl Notifier {
|
||||
fn new(window_proxy: glutin::WindowProxy) -> Notifier {
|
||||
Notifier {
|
||||
window_proxy: window_proxy,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl webrender_traits::RenderNotifier for Notifier {
|
||||
fn new_frame_ready(&mut self) {
|
||||
#[cfg(not(target_os = "android"))]
|
||||
self.window_proxy.wakeup_event_loop();
|
||||
}
|
||||
|
||||
fn new_scroll_frame_ready(&mut self, _composite_needed: bool) {
|
||||
#[cfg(not(target_os = "android"))]
|
||||
self.window_proxy.wakeup_event_loop();
|
||||
}
|
||||
boilerplate::main_wrapper(body, event_handler, Some(opts));
|
||||
}
|
||||
|
|
|
@ -0,0 +1,155 @@
|
|||
/* 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 gleam::gl;
|
||||
use glutin;
|
||||
use std::env;
|
||||
use std::path::PathBuf;
|
||||
use webrender;
|
||||
use webrender_traits::*;
|
||||
|
||||
struct Notifier {
|
||||
window_proxy: glutin::WindowProxy,
|
||||
}
|
||||
|
||||
impl Notifier {
|
||||
fn new(window_proxy: glutin::WindowProxy) -> Notifier {
|
||||
Notifier {
|
||||
window_proxy: window_proxy,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderNotifier for Notifier {
|
||||
fn new_frame_ready(&mut self) {
|
||||
#[cfg(not(target_os = "android"))]
|
||||
self.window_proxy.wakeup_event_loop();
|
||||
}
|
||||
|
||||
fn new_scroll_frame_ready(&mut self, _composite_needed: bool) {
|
||||
#[cfg(not(target_os = "android"))]
|
||||
self.window_proxy.wakeup_event_loop();
|
||||
}
|
||||
}
|
||||
|
||||
pub trait HandyDandyRectBuilder {
|
||||
fn to(&self, x2: i32, y2: i32) -> LayoutRect;
|
||||
fn by(&self, w: i32, h: i32) -> LayoutRect;
|
||||
}
|
||||
// Allows doing `(x, y).to(x2, y2)` or `(x, y).by(width, height)` with i32
|
||||
// values to build a f32 LayoutRect
|
||||
impl HandyDandyRectBuilder for (i32, i32) {
|
||||
fn to(&self, x2: i32, y2: i32) -> LayoutRect {
|
||||
LayoutRect::new(LayoutPoint::new(self.0 as f32, self.1 as f32),
|
||||
LayoutSize::new((x2 - self.0) as f32, (y2 - self.1) as f32))
|
||||
}
|
||||
|
||||
fn by(&self, w: i32, h: i32) -> LayoutRect {
|
||||
LayoutRect::new(LayoutPoint::new(self.0 as f32, self.1 as f32),
|
||||
LayoutSize::new(w as f32, h as f32))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn main_wrapper(builder_callback: fn(&RenderApi,
|
||||
&mut DisplayListBuilder,
|
||||
&PipelineId,
|
||||
&LayoutSize) -> (),
|
||||
event_handler: fn(&glutin::Event,
|
||||
&RenderApi) -> (),
|
||||
options: Option<webrender::RendererOptions>)
|
||||
{
|
||||
let args: Vec<String> = env::args().collect();
|
||||
let res_path = if args.len() > 1 {
|
||||
Some(PathBuf::from(&args[1]))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let window = glutin::WindowBuilder::new()
|
||||
.with_title("WebRender Sample App")
|
||||
.with_multitouch()
|
||||
.with_gl(glutin::GlRequest::GlThenGles {
|
||||
opengl_version: (3, 2),
|
||||
opengles_version: (3, 0)
|
||||
})
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
unsafe {
|
||||
window.make_current().ok();
|
||||
}
|
||||
|
||||
let gl = match gl::GlType::default() {
|
||||
gl::GlType::Gl => unsafe { gl::GlFns::load_with(|symbol| window.get_proc_address(symbol) as *const _) },
|
||||
gl::GlType::Gles => unsafe { gl::GlesFns::load_with(|symbol| window.get_proc_address(symbol) as *const _) },
|
||||
};
|
||||
|
||||
println!("OpenGL version {}", gl.get_string(gl::VERSION));
|
||||
println!("Shader resource path: {:?}", res_path);
|
||||
|
||||
let (width, height) = window.get_inner_size_pixels().unwrap();
|
||||
|
||||
let opts = webrender::RendererOptions {
|
||||
resource_override_path: res_path,
|
||||
debug: true,
|
||||
precache_shaders: true,
|
||||
device_pixel_ratio: window.hidpi_factor(),
|
||||
.. options.unwrap_or(webrender::RendererOptions::default())
|
||||
};
|
||||
|
||||
let size = DeviceUintSize::new(width, height);
|
||||
let (mut renderer, sender) = webrender::renderer::Renderer::new(gl, opts, size).unwrap();
|
||||
let api = sender.create_api();
|
||||
|
||||
let notifier = Box::new(Notifier::new(window.create_window_proxy()));
|
||||
renderer.set_render_notifier(notifier);
|
||||
|
||||
let epoch = Epoch(0);
|
||||
let root_background_color = ColorF::new(0.3, 0.0, 0.0, 1.0);
|
||||
|
||||
let pipeline_id = PipelineId(0, 0);
|
||||
let layout_size = LayoutSize::new(width as f32, height as f32);
|
||||
let mut builder = DisplayListBuilder::new(pipeline_id, layout_size);
|
||||
|
||||
builder_callback(&api, &mut builder, &pipeline_id, &layout_size);
|
||||
|
||||
api.set_display_list(
|
||||
Some(root_background_color),
|
||||
epoch,
|
||||
LayoutSize::new(width as f32, height as f32),
|
||||
builder.finalize(),
|
||||
true);
|
||||
api.set_root_pipeline(pipeline_id);
|
||||
api.generate_frame(None);
|
||||
|
||||
'outer: for event in window.wait_events() {
|
||||
let mut events = Vec::new();
|
||||
events.push(event);
|
||||
|
||||
for event in window.poll_events() {
|
||||
events.push(event);
|
||||
}
|
||||
|
||||
for event in events {
|
||||
match event {
|
||||
glutin::Event::Closed |
|
||||
glutin::Event::KeyboardInput(_, _, Some(glutin::VirtualKeyCode::Escape)) |
|
||||
glutin::Event::KeyboardInput(_, _, Some(glutin::VirtualKeyCode::Q)) => break 'outer,
|
||||
|
||||
glutin::Event::KeyboardInput(glutin::ElementState::Pressed,
|
||||
_, Some(glutin::VirtualKeyCode::P)) => {
|
||||
let enable_profiler = !renderer.get_profiler_enabled();
|
||||
renderer.set_profiler_enabled(enable_profiler);
|
||||
api.generate_frame(None);
|
||||
}
|
||||
|
||||
_ => event_handler(&event, &api),
|
||||
}
|
||||
}
|
||||
|
||||
renderer.update();
|
||||
renderer.render(DeviceUintSize::new(width, height));
|
||||
window.swap_buffers().ok();
|
||||
}
|
||||
}
|
|
@ -7,103 +7,22 @@ extern crate glutin;
|
|||
extern crate webrender;
|
||||
extern crate webrender_traits;
|
||||
|
||||
use gleam::gl;
|
||||
use std::env;
|
||||
use std::path::PathBuf;
|
||||
use webrender_traits::{ClipId, ColorF, DeviceUintSize, Epoch, LayoutPoint, LayoutRect};
|
||||
use webrender_traits::{LayoutSize, PipelineId, ScrollEventPhase, ScrollLocation, TransformStyle};
|
||||
use webrender_traits::WorldPoint;
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
|
||||
struct Notifier {
|
||||
window_proxy: glutin::WindowProxy,
|
||||
}
|
||||
#[path="common/boilerplate.rs"]
|
||||
mod boilerplate;
|
||||
|
||||
impl Notifier {
|
||||
fn new(window_proxy: glutin::WindowProxy) -> Notifier {
|
||||
Notifier {
|
||||
window_proxy: window_proxy,
|
||||
}
|
||||
}
|
||||
}
|
||||
use boilerplate::HandyDandyRectBuilder;
|
||||
use std::sync::Mutex;
|
||||
use webrender_traits::*;
|
||||
|
||||
impl webrender_traits::RenderNotifier for Notifier {
|
||||
fn new_frame_ready(&mut self) {
|
||||
#[cfg(not(target_os = "android"))]
|
||||
self.window_proxy.wakeup_event_loop();
|
||||
}
|
||||
|
||||
fn new_scroll_frame_ready(&mut self, _composite_needed: bool) {
|
||||
#[cfg(not(target_os = "android"))]
|
||||
self.window_proxy.wakeup_event_loop();
|
||||
}
|
||||
}
|
||||
|
||||
trait HandyDandyRectBuilder {
|
||||
fn to(&self, x2: i32, y2: i32) -> LayoutRect;
|
||||
}
|
||||
// Allows doing `(x, y).to(x2, y2)` to build a LayoutRect
|
||||
impl HandyDandyRectBuilder for (i32, i32) {
|
||||
fn to(&self, x2: i32, y2: i32) -> LayoutRect {
|
||||
LayoutRect::new(LayoutPoint::new(self.0 as f32, self.1 as f32),
|
||||
LayoutSize::new((x2 - self.0) as f32, (y2 - self.1) as f32))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn main() {
|
||||
let args: Vec<String> = env::args().collect();
|
||||
let res_path = if args.len() > 1 {
|
||||
Some(PathBuf::from(&args[1]))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let window = glutin::WindowBuilder::new()
|
||||
.with_title("WebRender Scrolling Sample")
|
||||
.with_gl(glutin::GlRequest::GlThenGles {
|
||||
opengl_version: (3, 2),
|
||||
opengles_version: (3, 0)
|
||||
})
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
unsafe {
|
||||
window.make_current().ok();
|
||||
}
|
||||
|
||||
let gl = match gl::GlType::default() {
|
||||
gl::GlType::Gl => unsafe { gl::GlFns::load_with(|symbol| window.get_proc_address(symbol) as *const _) },
|
||||
gl::GlType::Gles => unsafe { gl::GlesFns::load_with(|symbol| window.get_proc_address(symbol) as *const _) },
|
||||
};
|
||||
|
||||
println!("OpenGL version {}", gl.get_string(gl::VERSION));
|
||||
println!("Shader resource path: {:?}", res_path);
|
||||
|
||||
let (width, height) = window.get_inner_size_pixels().unwrap();
|
||||
|
||||
let opts = webrender::RendererOptions {
|
||||
resource_override_path: res_path,
|
||||
debug: true,
|
||||
precache_shaders: true,
|
||||
device_pixel_ratio: window.hidpi_factor(),
|
||||
.. Default::default()
|
||||
};
|
||||
|
||||
let size = DeviceUintSize::new(width, height);
|
||||
let (mut renderer, sender) = webrender::renderer::Renderer::new(gl, opts, size).unwrap();
|
||||
let api = sender.create_api();
|
||||
|
||||
let notifier = Box::new(Notifier::new(window.create_window_proxy()));
|
||||
renderer.set_render_notifier(notifier);
|
||||
|
||||
let epoch = Epoch(0);
|
||||
let root_background_color = ColorF::new(0.3, 0.0, 0.0, 1.0);
|
||||
|
||||
let pipeline_id = PipelineId(0, 0);
|
||||
let layout_size = LayoutSize::new(width as f32, height as f32);
|
||||
let mut builder = webrender_traits::DisplayListBuilder::new(pipeline_id, layout_size);
|
||||
|
||||
let bounds = LayoutRect::new(LayoutPoint::zero(), layout_size);
|
||||
fn body(_api: &RenderApi,
|
||||
builder: &mut DisplayListBuilder,
|
||||
pipeline_id: &PipelineId,
|
||||
layout_size: &LayoutSize)
|
||||
{
|
||||
let bounds = LayoutRect::new(LayoutPoint::zero(), *layout_size);
|
||||
builder.push_stacking_context(webrender_traits::ScrollPolicy::Scrollable,
|
||||
bounds,
|
||||
None,
|
||||
|
@ -127,7 +46,7 @@ fn main() {
|
|||
let clip = builder.push_clip_region(&scrollbox, vec![], None);
|
||||
let clip_id = builder.define_clip((0, 0).to(1000, 1000),
|
||||
clip,
|
||||
Some(ClipId::new(42, pipeline_id)));
|
||||
Some(ClipId::new(42, *pipeline_id)));
|
||||
builder.push_clip_id(clip_id);
|
||||
// now put some content into it.
|
||||
// start with a white background
|
||||
|
@ -153,7 +72,7 @@ fn main() {
|
|||
let clip = builder.push_clip_region(&(0, 100).to(200, 300), vec![], None);
|
||||
let nested_clip_id = builder.define_clip((0, 100).to(300, 400),
|
||||
clip,
|
||||
Some(ClipId::new(43, pipeline_id)));
|
||||
Some(ClipId::new(43, *pipeline_id)));
|
||||
builder.push_clip_id(nested_clip_id);
|
||||
// give it a giant gray background just to distinguish it and to easily
|
||||
// visually identify the nested scrollbox
|
||||
|
@ -181,68 +100,51 @@ fn main() {
|
|||
}
|
||||
|
||||
builder.pop_stacking_context();
|
||||
}
|
||||
|
||||
api.set_display_list(
|
||||
Some(root_background_color),
|
||||
epoch,
|
||||
LayoutSize::new(width as f32, height as f32),
|
||||
builder.finalize(),
|
||||
true);
|
||||
api.set_root_pipeline(pipeline_id);
|
||||
api.generate_frame(None);
|
||||
lazy_static! {
|
||||
static ref CURSOR_POSITION: Mutex<WorldPoint> = Mutex::new(WorldPoint::zero());
|
||||
}
|
||||
|
||||
let mut cursor_position = WorldPoint::zero();
|
||||
fn event_handler(event: &glutin::Event,
|
||||
api: &RenderApi)
|
||||
{
|
||||
match *event {
|
||||
glutin::Event::KeyboardInput(glutin::ElementState::Pressed, _, Some(key)) => {
|
||||
let offset = match key {
|
||||
glutin::VirtualKeyCode::Down => (0.0, -10.0),
|
||||
glutin::VirtualKeyCode::Up => (0.0, 10.0),
|
||||
glutin::VirtualKeyCode::Right => (-10.0, 0.0),
|
||||
glutin::VirtualKeyCode::Left => (10.0, 0.0),
|
||||
_ => return,
|
||||
};
|
||||
|
||||
'outer: for event in window.wait_events() {
|
||||
let mut events = Vec::new();
|
||||
events.push(event);
|
||||
|
||||
for event in window.poll_events() {
|
||||
events.push(event);
|
||||
api.scroll(ScrollLocation::Delta(LayoutVector2D::new(offset.0, offset.1)),
|
||||
*CURSOR_POSITION.lock().unwrap(),
|
||||
ScrollEventPhase::Start);
|
||||
}
|
||||
|
||||
for event in events {
|
||||
match event {
|
||||
glutin::Event::Closed |
|
||||
glutin::Event::KeyboardInput(_, _, Some(glutin::VirtualKeyCode::Escape)) |
|
||||
glutin::Event::KeyboardInput(_, _, Some(glutin::VirtualKeyCode::Q)) => break 'outer,
|
||||
glutin::Event::KeyboardInput(glutin::ElementState::Pressed, _, Some(key)) => {
|
||||
let offset = match key {
|
||||
glutin::VirtualKeyCode::Down => (0.0, -10.0),
|
||||
glutin::VirtualKeyCode::Up => (0.0, 10.0),
|
||||
glutin::VirtualKeyCode::Right => (-10.0, 0.0),
|
||||
glutin::VirtualKeyCode::Left => (10.0, 0.0),
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
api.scroll(ScrollLocation::Delta(LayoutPoint::new(offset.0, offset.1)),
|
||||
cursor_position,
|
||||
ScrollEventPhase::Start);
|
||||
}
|
||||
glutin::Event::MouseMoved(x, y) => {
|
||||
cursor_position = WorldPoint::new(x as f32, y as f32);
|
||||
}
|
||||
glutin::Event::MouseWheel(delta, _, event_cursor_position) => {
|
||||
if let Some((x, y)) = event_cursor_position {
|
||||
cursor_position = WorldPoint::new(x as f32, y as f32);
|
||||
}
|
||||
|
||||
const LINE_HEIGHT: f32 = 38.0;
|
||||
let (dx, dy) = match delta {
|
||||
glutin::MouseScrollDelta::LineDelta(dx, dy) => (dx, dy * LINE_HEIGHT),
|
||||
glutin::MouseScrollDelta::PixelDelta(dx, dy) => (dx, dy),
|
||||
};
|
||||
|
||||
api.scroll(ScrollLocation::Delta(LayoutPoint::new(dx, dy)),
|
||||
cursor_position,
|
||||
ScrollEventPhase::Start);
|
||||
}
|
||||
_ => ()
|
||||
glutin::Event::MouseMoved(x, y) => {
|
||||
*CURSOR_POSITION.lock().unwrap() = WorldPoint::new(x as f32, y as f32);
|
||||
}
|
||||
glutin::Event::MouseWheel(delta, _, event_cursor_position) => {
|
||||
if let Some((x, y)) = event_cursor_position {
|
||||
*CURSOR_POSITION.lock().unwrap() = WorldPoint::new(x as f32, y as f32);
|
||||
}
|
||||
}
|
||||
|
||||
renderer.update();
|
||||
renderer.render(DeviceUintSize::new(width, height));
|
||||
window.swap_buffers().ok();
|
||||
const LINE_HEIGHT: f32 = 38.0;
|
||||
let (dx, dy) = match delta {
|
||||
glutin::MouseScrollDelta::LineDelta(dx, dy) => (dx, dy * LINE_HEIGHT),
|
||||
glutin::MouseScrollDelta::PixelDelta(dx, dy) => (dx, dy),
|
||||
};
|
||||
|
||||
api.scroll(ScrollLocation::Delta(LayoutVector2D::new(dx, dy)),
|
||||
*CURSOR_POSITION.lock().unwrap(),
|
||||
ScrollEventPhase::Start);
|
||||
}
|
||||
_ => ()
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
boilerplate::main_wrapper(body, event_handler, None);
|
||||
}
|
||||
|
|
|
@ -8,18 +8,19 @@
|
|||
// as text-shadow.
|
||||
|
||||
void main(void) {
|
||||
PrimitiveInstance pi = fetch_prim_instance();
|
||||
RenderTaskData task = fetch_render_task(pi.render_task_index);
|
||||
TextRun text = fetch_text_run(pi.specific_prim_address);
|
||||
Glyph glyph = fetch_glyph(pi.user_data0);
|
||||
PrimitiveGeometry pg = fetch_prim_geometry(pi.global_prim_index);
|
||||
ResourceRect res = fetch_resource_rect(pi.user_data1);
|
||||
Primitive prim = load_primitive();
|
||||
TextRun text = fetch_text_run(prim.specific_prim_address);
|
||||
|
||||
int glyph_index = prim.user_data0;
|
||||
int resource_address = prim.user_data1;
|
||||
Glyph glyph = fetch_glyph(prim.specific_prim_address, glyph_index);
|
||||
ResourceRect res = fetch_resource_rect(resource_address + glyph_index);
|
||||
|
||||
// Glyphs size is already in device-pixels.
|
||||
// The render task origin is in device-pixels. Offset that by
|
||||
// the glyph offset, relative to its primitive bounding rect.
|
||||
vec2 size = res.uv_rect.zw - res.uv_rect.xy;
|
||||
vec2 origin = task.data0.xy + uDevicePixelRatio * (glyph.offset.xy - pg.local_rect.p0);
|
||||
vec2 origin = prim.task.screen_space_origin + uDevicePixelRatio * (glyph.offset - prim.local_rect.p0);
|
||||
vec4 local_rect = vec4(origin, size);
|
||||
|
||||
vec2 texture_size = vec2(textureSize(sColor0, 0));
|
||||
|
|
|
@ -106,21 +106,43 @@ varying vec3 vClipMaskUv;
|
|||
flat varying vec4 vLocalBounds;
|
||||
#endif
|
||||
|
||||
// TODO(gw): This is here temporarily while we have
|
||||
// both GPU store and cache. When the GPU
|
||||
// store code is removed, we can change the
|
||||
// PrimitiveInstance instance structure to
|
||||
// use 2x unsigned shorts as vertex attributes
|
||||
// instead of an int, and encode the UV directly
|
||||
// in the vertices.
|
||||
ivec2 get_resource_cache_uv(int address) {
|
||||
return ivec2(address % WR_MAX_VERTEX_TEXTURE_WIDTH,
|
||||
address / WR_MAX_VERTEX_TEXTURE_WIDTH);
|
||||
}
|
||||
|
||||
uniform sampler2D sResourceCache;
|
||||
|
||||
vec4[2] fetch_from_resource_cache_2(int address) {
|
||||
ivec2 uv = get_resource_cache_uv(address);
|
||||
return vec4[2](
|
||||
texelFetchOffset(sResourceCache, uv, 0, ivec2(0, 0)),
|
||||
texelFetchOffset(sResourceCache, uv, 0, ivec2(1, 0))
|
||||
);
|
||||
}
|
||||
|
||||
#ifdef WR_VERTEX_SHADER
|
||||
|
||||
#define VECS_PER_LAYER 9
|
||||
#define VECS_PER_RENDER_TASK 3
|
||||
#define VECS_PER_PRIM_GEOM 2
|
||||
#define VECS_PER_SPLIT_GEOM 3
|
||||
#define VECS_PER_PRIM_HEADER 2
|
||||
#define VECS_PER_TEXT_RUN 1
|
||||
#define VECS_PER_GRADIENT 3
|
||||
#define VECS_PER_GRADIENT_STOP 2
|
||||
|
||||
uniform sampler2D sLayers;
|
||||
uniform sampler2D sRenderTasks;
|
||||
uniform sampler2D sPrimGeometry;
|
||||
|
||||
uniform sampler2D sData16;
|
||||
uniform sampler2D sData32;
|
||||
uniform sampler2D sResourceRects;
|
||||
uniform sampler2D sResourceCache;
|
||||
|
||||
// Instanced attributes
|
||||
in ivec4 aData0;
|
||||
|
@ -145,18 +167,6 @@ vec4[2] fetch_data_2(int index) {
|
|||
);
|
||||
}
|
||||
|
||||
// TODO(gw): This is here temporarily while we have
|
||||
// both GPU store and cache. When the GPU
|
||||
// store code is removed, we can change the
|
||||
// PrimitiveInstance instance structure to
|
||||
// use 2x unsigned shorts as vertex attributes
|
||||
// instead of an int, and encode the UV directly
|
||||
// in the vertices.
|
||||
ivec2 get_resource_cache_uv(int address) {
|
||||
return ivec2(address % WR_MAX_VERTEX_TEXTURE_WIDTH,
|
||||
address / WR_MAX_VERTEX_TEXTURE_WIDTH);
|
||||
}
|
||||
|
||||
vec4[8] fetch_from_resource_cache_8(int address) {
|
||||
ivec2 uv = get_resource_cache_uv(address);
|
||||
return vec4[8](
|
||||
|
@ -321,8 +331,8 @@ struct GradientStop {
|
|||
vec4 offset;
|
||||
};
|
||||
|
||||
GradientStop fetch_gradient_stop(int index) {
|
||||
vec4 data[2] = fetch_data_2(index);
|
||||
GradientStop fetch_gradient_stop(int address) {
|
||||
vec4 data[2] = fetch_from_resource_cache_2(address);
|
||||
return GradientStop(data[0], data[1]);
|
||||
}
|
||||
|
||||
|
@ -417,12 +427,18 @@ BorderCorners get_border_corners(Border border, RectWithSize local_rect) {
|
|||
}
|
||||
|
||||
struct Glyph {
|
||||
vec4 offset;
|
||||
vec2 offset;
|
||||
};
|
||||
|
||||
Glyph fetch_glyph(int index) {
|
||||
vec4 data = fetch_data_1(index);
|
||||
return Glyph(data);
|
||||
Glyph fetch_glyph(int specific_prim_address, int glyph_index) {
|
||||
// Two glyphs are packed in each texel in the GPU cache.
|
||||
int glyph_address = specific_prim_address +
|
||||
VECS_PER_TEXT_RUN +
|
||||
glyph_index / 2;
|
||||
vec4 data = fetch_from_resource_cache_1(glyph_address);
|
||||
// Select XY or ZW based on glyph index.
|
||||
vec2 glyph = mix(data.xy, data.zw, bvec2(glyph_index % 2 == 1));
|
||||
return Glyph(glyph);
|
||||
}
|
||||
|
||||
RectWithSize fetch_instance_geometry(int address) {
|
||||
|
@ -430,26 +446,8 @@ RectWithSize fetch_instance_geometry(int address) {
|
|||
return RectWithSize(data.xy, data.zw);
|
||||
}
|
||||
|
||||
struct PrimitiveGeometry {
|
||||
RectWithSize local_rect;
|
||||
RectWithSize local_clip_rect;
|
||||
};
|
||||
|
||||
PrimitiveGeometry fetch_prim_geometry(int index) {
|
||||
PrimitiveGeometry pg;
|
||||
|
||||
ivec2 uv = get_fetch_uv(index, VECS_PER_PRIM_GEOM);
|
||||
|
||||
vec4 local_rect = texelFetchOffset(sPrimGeometry, uv, 0, ivec2(0, 0));
|
||||
pg.local_rect = RectWithSize(local_rect.xy, local_rect.zw);
|
||||
vec4 local_clip_rect = texelFetchOffset(sPrimGeometry, uv, 0, ivec2(1, 0));
|
||||
pg.local_clip_rect = RectWithSize(local_clip_rect.xy, local_clip_rect.zw);
|
||||
|
||||
return pg;
|
||||
}
|
||||
|
||||
struct PrimitiveInstance {
|
||||
int global_prim_index;
|
||||
int prim_address;
|
||||
int specific_prim_address;
|
||||
int render_task_index;
|
||||
int clip_task_index;
|
||||
|
@ -462,14 +460,14 @@ struct PrimitiveInstance {
|
|||
PrimitiveInstance fetch_prim_instance() {
|
||||
PrimitiveInstance pi;
|
||||
|
||||
pi.global_prim_index = aData0.x;
|
||||
pi.specific_prim_address = aData0.y;
|
||||
pi.render_task_index = aData0.z;
|
||||
pi.clip_task_index = aData0.w;
|
||||
pi.layer_index = aData1.x;
|
||||
pi.z = aData1.y;
|
||||
pi.user_data0 = aData1.z;
|
||||
pi.user_data1 = aData1.w;
|
||||
pi.prim_address = aData0.x;
|
||||
pi.specific_prim_address = pi.prim_address + VECS_PER_PRIM_HEADER;
|
||||
pi.render_task_index = aData0.y;
|
||||
pi.clip_task_index = aData0.z;
|
||||
pi.layer_index = aData0.w;
|
||||
pi.z = aData1.x;
|
||||
pi.user_data0 = aData1.y;
|
||||
pi.user_data1 = aData1.z;
|
||||
|
||||
return pi;
|
||||
}
|
||||
|
@ -503,24 +501,26 @@ struct Primitive {
|
|||
AlphaBatchTask task;
|
||||
RectWithSize local_rect;
|
||||
RectWithSize local_clip_rect;
|
||||
int prim_index;
|
||||
int specific_prim_address;
|
||||
int user_data0;
|
||||
int user_data1;
|
||||
float z;
|
||||
};
|
||||
|
||||
Primitive load_primitive_custom(PrimitiveInstance pi) {
|
||||
Primitive load_primitive() {
|
||||
PrimitiveInstance pi = fetch_prim_instance();
|
||||
|
||||
Primitive prim;
|
||||
|
||||
prim.layer = fetch_layer(pi.layer_index);
|
||||
prim.clip_area = fetch_clip_area(pi.clip_task_index);
|
||||
prim.task = fetch_alpha_batch_task(pi.render_task_index);
|
||||
|
||||
PrimitiveGeometry pg = fetch_prim_geometry(pi.global_prim_index);
|
||||
prim.local_rect = pg.local_rect;
|
||||
prim.local_clip_rect = pg.local_clip_rect;
|
||||
vec4 geom[2] = fetch_from_resource_cache_2(pi.prim_address);
|
||||
prim.local_rect = RectWithSize(geom[0].xy, geom[0].zw);
|
||||
prim.local_clip_rect = RectWithSize(geom[1].xy, geom[1].zw);
|
||||
|
||||
prim.prim_index = pi.specific_prim_address;
|
||||
prim.specific_prim_address = pi.specific_prim_address;
|
||||
prim.user_data0 = pi.user_data0;
|
||||
prim.user_data1 = pi.user_data1;
|
||||
prim.z = float(pi.z);
|
||||
|
@ -528,13 +528,6 @@ Primitive load_primitive_custom(PrimitiveInstance pi) {
|
|||
return prim;
|
||||
}
|
||||
|
||||
Primitive load_primitive() {
|
||||
PrimitiveInstance pi = fetch_prim_instance();
|
||||
|
||||
return load_primitive_custom(pi);
|
||||
}
|
||||
|
||||
|
||||
// Return the intersection of the plane (set up by "normal" and "point")
|
||||
// with the ray (set up by "ray_origin" and "ray_dir"),
|
||||
// writing the resulting scaler into "t".
|
||||
|
@ -857,10 +850,8 @@ vec4 dither(vec4 color) {
|
|||
}
|
||||
#endif //WR_FEATURE_DITHERING
|
||||
|
||||
vec4 sample_gradient(float offset, float gradient_repeat, float gradient_index, vec2 gradient_size) {
|
||||
// Modulo the offset if the gradient repeats. We don't need to clamp non-repeating
|
||||
// gradients because the gradient data texture is bound with CLAMP_TO_EDGE, and the
|
||||
// first and last color entries are filled with the first and last stop colors
|
||||
vec4 sample_gradient(int address, float offset, float gradient_repeat) {
|
||||
// Modulo the offset if the gradient repeats.
|
||||
float x = mix(offset, fract(offset), gradient_repeat);
|
||||
|
||||
// Calculate the color entry index to use for this offset:
|
||||
|
@ -868,22 +859,25 @@ vec4 sample_gradient(float offset, float gradient_repeat, float gradient_index,
|
|||
// offsets from [0, 1) use the color entries in the range of [1, N-1)
|
||||
// offsets >= 1 use the last color entry, N-1
|
||||
// so transform the range [0, 1) -> [1, N-1)
|
||||
float gradient_entries = 0.5 * gradient_size.x;
|
||||
x = x * (gradient_entries - 2.0) + 1.0;
|
||||
|
||||
// TODO(gw): In the future we might consider making the size of the
|
||||
// LUT vary based on number / distribution of stops in the gradient.
|
||||
const int GRADIENT_ENTRIES = 128;
|
||||
x = 1.0 + x * float(GRADIENT_ENTRIES);
|
||||
|
||||
// Calculate the texel to index into the gradient color entries:
|
||||
// floor(x) is the gradient color entry index
|
||||
// fract(x) is the linear filtering factor between start and end
|
||||
// so, 2 * floor(x) + 0.5 is the center of the start color
|
||||
// finally, add floor(x) to interpolate to end
|
||||
x = 2.0 * floor(x) + 0.5 + fract(x);
|
||||
int lut_offset = 2 * int(floor(x)); // There is a [start, end] color per entry.
|
||||
|
||||
// Gradient color entries are encoded with high bits in one row and low bits in the next
|
||||
// So use linear filtering to mix (gradient_index + 1) with (gradient_index)
|
||||
float y = gradient_index * 2.0 + 0.5 + 1.0 / 256.0;
|
||||
// Ensure we don't fetch outside the valid range of the LUT.
|
||||
lut_offset = clamp(lut_offset, 0, 2 * (GRADIENT_ENTRIES + 1));
|
||||
|
||||
// Finally sample and apply dithering
|
||||
return dither(texture(sGradients, vec2(x, y) / gradient_size));
|
||||
// Fetch the start and end color.
|
||||
vec4 texels[2] = fetch_from_resource_cache_2(address + lut_offset);
|
||||
|
||||
// Finally interpolate and apply dithering
|
||||
return dither(mix(texels[0], texels[1], fract(x)));
|
||||
}
|
||||
|
||||
//
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#line 1
|
||||
|
||||
/* 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/. */
|
||||
|
@ -12,8 +14,7 @@ void main(void) {
|
|||
|
||||
float offset = dot(pos - vStartPoint, vScaledDir);
|
||||
|
||||
oFragColor = sample_gradient(offset,
|
||||
vGradientRepeat,
|
||||
vGradientIndex,
|
||||
vGradientTextureSize);
|
||||
oFragColor = sample_gradient(vGradientAddress,
|
||||
offset,
|
||||
vGradientRepeat);
|
||||
}
|
||||
|
|
|
@ -2,8 +2,7 @@
|
|||
* 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/. */
|
||||
|
||||
flat varying float vGradientIndex;
|
||||
flat varying vec2 vGradientTextureSize;
|
||||
flat varying int vGradientAddress;
|
||||
flat varying float vGradientRepeat;
|
||||
|
||||
flat varying vec2 vScaledDir;
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
void main(void) {
|
||||
Primitive prim = load_primitive();
|
||||
Gradient gradient = fetch_gradient(prim.prim_index);
|
||||
Gradient gradient = fetch_gradient(prim.specific_prim_address);
|
||||
|
||||
VertexInfo vi = write_vertex(prim.local_rect,
|
||||
prim.local_clip_rect,
|
||||
|
@ -26,11 +26,7 @@ void main(void) {
|
|||
vTileSize = gradient.tile_size_repeat.xy;
|
||||
vTileRepeat = gradient.tile_size_repeat.zw;
|
||||
|
||||
// V coordinate of gradient row in lookup texture.
|
||||
vGradientIndex = float(prim.user_data0);
|
||||
|
||||
// The texture size of the lookup texture
|
||||
vGradientTextureSize = vec2(textureSize(sGradients, 0));
|
||||
vGradientAddress = prim.specific_prim_address + VECS_PER_GRADIENT;
|
||||
|
||||
// Whether to repeat the gradient instead of clamping.
|
||||
vGradientRepeat = float(int(gradient.extend_mode.x) == EXTEND_MODE_REPEAT);
|
||||
|
|
|
@ -117,7 +117,7 @@ int select_style(int color_select, vec2 fstyle) {
|
|||
|
||||
void main(void) {
|
||||
Primitive prim = load_primitive();
|
||||
Border border = fetch_border(prim.prim_index);
|
||||
Border border = fetch_border(prim.specific_prim_address);
|
||||
int sub_part = prim.user_data0;
|
||||
BorderCorners corners = get_border_corners(border, prim.local_rect);
|
||||
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче