merge autoland to mozilla-central. r=merge a=merge

MozReview-Commit-ID: ALl7GQHEApt
This commit is contained in:
Sebastian Hengst 2017-06-15 10:53:50 +02:00
Родитель c49a70b53f 4358805216
Коммит 02c5cf4167
538 изменённых файлов: 19469 добавлений и 7721 удалений

Просмотреть файл

@ -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 doesnt mean its 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 browsers 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

33
gfx/layers/PaintThread.h Normal file
Просмотреть файл

@ -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);

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше