diff --git a/browser/extensions/activity-stream/common/Actions.jsm b/browser/extensions/activity-stream/common/Actions.jsm index b39010e27bdd..0cd24d0b5d3d 100644 --- a/browser/extensions/activity-stream/common/Actions.jsm +++ b/browser/extensions/activity-stream/common/Actions.jsm @@ -56,8 +56,10 @@ for (const type of [ "SAVE_TO_POCKET", "SCREENSHOT_UPDATED", "SECTION_DEREGISTER", + "SECTION_DISABLE", + "SECTION_ENABLE", "SECTION_REGISTER", - "SECTION_ROWS_UPDATE", + "SECTION_UPDATE", "SET_PREF", "SNIPPETS_DATA", "SNIPPETS_RESET", diff --git a/browser/extensions/activity-stream/common/Reducers.jsm b/browser/extensions/activity-stream/common/Reducers.jsm index 9965dad9717e..d5fb508fc47d 100644 --- a/browser/extensions/activity-stream/common/Reducers.jsm +++ b/browser/extensions/activity-stream/common/Reducers.jsm @@ -189,14 +189,27 @@ function Sections(prevState = INITIAL_STATE.Sections, action) { } return section; }); - // If section doesn't exist in prevState, create a new section object and - // append it to the sections state + + // Invariant: Sections array sorted in increasing order of property `order`. + // If section doesn't exist in prevState, create a new section object. If + // the section has an order, insert it at the correct place in the array. + // Otherwise, prepend it and set the order to be minimal. if (!hasMatch) { const initialized = action.data.rows && action.data.rows.length > 0; - newState.push(Object.assign({title: "", initialized, rows: []}, action.data)); + let order; + let index; + if (prevState.length > 0) { + order = action.data.order || prevState[0].order - 1; + index = newState.findIndex(section => section.order >= order); + } else { + order = action.data.order || 1; + index = 0; + } + const section = Object.assign({title: "", initialized, rows: [], order, enabled: false}, action.data); + newState.splice(index, 0, section); } return newState; - case at.SECTION_ROWS_UPDATE: + case at.SECTION_UPDATE: return prevState.map(section => { if (section && section.id === action.data.id) { return Object.assign({}, section, action.data); diff --git a/browser/extensions/activity-stream/data/content/activity-stream.bundle.js b/browser/extensions/activity-stream/data/content/activity-stream.bundle.js index 66f75e79742c..c22b081c1801 100644 --- a/browser/extensions/activity-stream/data/content/activity-stream.bundle.js +++ b/browser/extensions/activity-stream/data/content/activity-stream.bundle.js @@ -6,9 +6,9 @@ /******/ function __webpack_require__(moduleId) { /******/ /******/ // Check if module is in cache -/******/ if(installedModules[moduleId]) +/******/ if(installedModules[moduleId]) { /******/ return installedModules[moduleId].exports; -/******/ +/******/ } /******/ // Create a new module (and put it into the cache) /******/ var module = installedModules[moduleId] = { /******/ i: moduleId, @@ -33,9 +33,6 @@ /******/ // expose the module cache /******/ __webpack_require__.c = installedModules; /******/ -/******/ // identity function for calling harmony imports with the correct context -/******/ __webpack_require__.i = function(value) { return value; }; -/******/ /******/ // define getter function for harmony exports /******/ __webpack_require__.d = function(exports, name, getter) { /******/ if(!__webpack_require__.o(exports, name)) { @@ -63,7 +60,7 @@ /******/ __webpack_require__.p = ""; /******/ /******/ // Load entry module and return exports -/******/ return __webpack_require__(__webpack_require__.s = 25); +/******/ return __webpack_require__(__webpack_require__.s = 7); /******/ }) /************************************************************************/ /******/ ([ @@ -103,7 +100,7 @@ const globalImportContext = typeof Window === "undefined" ? BACKGROUND_PROCESS : // UNINIT: "UNINIT" // } const actionTypes = {}; -for (const type of ["BLOCK_URL", "BOOKMARK_URL", "DELETE_BOOKMARK_BY_ID", "DELETE_HISTORY_URL", "DELETE_HISTORY_URL_CONFIRM", "DIALOG_CANCEL", "DIALOG_OPEN", "FEED_INIT", "INIT", "LOCALE_UPDATED", "MIGRATION_CANCEL", "MIGRATION_START", "NEW_TAB_INIT", "NEW_TAB_INITIAL_STATE", "NEW_TAB_LOAD", "NEW_TAB_UNLOAD", "OPEN_LINK", "OPEN_NEW_WINDOW", "OPEN_PRIVATE_WINDOW", "PINNED_SITES_UPDATED", "PLACES_BOOKMARK_ADDED", "PLACES_BOOKMARK_CHANGED", "PLACES_BOOKMARK_REMOVED", "PLACES_HISTORY_CLEARED", "PLACES_LINK_BLOCKED", "PLACES_LINK_DELETED", "PREFS_INITIAL_VALUES", "PREF_CHANGED", "SAVE_SESSION_PERF_DATA", "SAVE_TO_POCKET", "SCREENSHOT_UPDATED", "SECTION_DEREGISTER", "SECTION_REGISTER", "SECTION_ROWS_UPDATE", "SET_PREF", "SNIPPETS_DATA", "SNIPPETS_RESET", "SYSTEM_TICK", "TELEMETRY_IMPRESSION_STATS", "TELEMETRY_PERFORMANCE_EVENT", "TELEMETRY_UNDESIRED_EVENT", "TELEMETRY_USER_EVENT", "TOP_SITES_EDIT_CLOSE", "TOP_SITES_EDIT_OPEN", "TOP_SITES_PIN", "TOP_SITES_UNPIN", "TOP_SITES_UPDATED", "UNINIT"]) { +for (const type of ["BLOCK_URL", "BOOKMARK_URL", "DELETE_BOOKMARK_BY_ID", "DELETE_HISTORY_URL", "DELETE_HISTORY_URL_CONFIRM", "DIALOG_CANCEL", "DIALOG_OPEN", "FEED_INIT", "INIT", "LOCALE_UPDATED", "MIGRATION_CANCEL", "MIGRATION_START", "NEW_TAB_INIT", "NEW_TAB_INITIAL_STATE", "NEW_TAB_LOAD", "NEW_TAB_UNLOAD", "OPEN_LINK", "OPEN_NEW_WINDOW", "OPEN_PRIVATE_WINDOW", "PINNED_SITES_UPDATED", "PLACES_BOOKMARK_ADDED", "PLACES_BOOKMARK_CHANGED", "PLACES_BOOKMARK_REMOVED", "PLACES_HISTORY_CLEARED", "PLACES_LINK_BLOCKED", "PLACES_LINK_DELETED", "PREFS_INITIAL_VALUES", "PREF_CHANGED", "SAVE_SESSION_PERF_DATA", "SAVE_TO_POCKET", "SCREENSHOT_UPDATED", "SECTION_DEREGISTER", "SECTION_DISABLE", "SECTION_ENABLE", "SECTION_REGISTER", "SECTION_UPDATE", "SET_PREF", "SNIPPETS_DATA", "SNIPPETS_RESET", "SYSTEM_TICK", "TELEMETRY_IMPRESSION_STATS", "TELEMETRY_PERFORMANCE_EVENT", "TELEMETRY_UNDESIRED_EVENT", "TELEMETRY_USER_EVENT", "TOP_SITES_EDIT_CLOSE", "TOP_SITES_EDIT_OPEN", "TOP_SITES_PIN", "TOP_SITES_UNPIN", "TOP_SITES_UPDATED", "UNINIT"]) { actionTypes[type] = type; } @@ -324,13 +321,13 @@ var _require = __webpack_require__(2); const injectIntl = _require.injectIntl; -const ContextMenu = __webpack_require__(16); +const ContextMenu = __webpack_require__(11); var _require2 = __webpack_require__(1); const ac = _require2.actionCreators; -const linkMenuOptions = __webpack_require__(23); +const linkMenuOptions = __webpack_require__(12); const DEFAULT_SITE_MENU_OPTIONS = ["CheckPinTopSite", "Separator", "OpenInNewWindow", "OpenInPrivateWindow", "Separator", "BlockUrl"]; class LinkMenu extends React.Component { @@ -545,6 +542,52 @@ module.exports = g; "use strict"; +const React = __webpack_require__(0); +const ReactDOM = __webpack_require__(8); +const Base = __webpack_require__(9); + +var _require = __webpack_require__(3); + +const Provider = _require.Provider; + +const initStore = __webpack_require__(21); + +var _require2 = __webpack_require__(23); + +const reducers = _require2.reducers; + +const DetectUserSessionStart = __webpack_require__(24); + +var _require3 = __webpack_require__(25); + +const addSnippetsSubscriber = _require3.addSnippetsSubscriber; + + +new DetectUserSessionStart().sendEventOrAddListener(); + +const store = initStore(reducers); + +ReactDOM.render(React.createElement( + Provider, + { store: store }, + React.createElement(Base, null) +), document.getElementById("root")); + +addSnippetsSubscriber(store); + +/***/ }), +/* 8 */ +/***/ (function(module, exports) { + +module.exports = ReactDOM; + +/***/ }), +/* 9 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + const React = __webpack_require__(0); var _require = __webpack_require__(3); @@ -556,12 +599,12 @@ var _require2 = __webpack_require__(2); const addLocaleData = _require2.addLocaleData, IntlProvider = _require2.IntlProvider; -const TopSites = __webpack_require__(21); -const Search = __webpack_require__(19); -const ConfirmDialog = __webpack_require__(15); -const ManualMigration = __webpack_require__(17); -const PreferencesPane = __webpack_require__(18); -const Sections = __webpack_require__(20); +const TopSites = __webpack_require__(10); +const Search = __webpack_require__(13); +const ConfirmDialog = __webpack_require__(14); +const ManualMigration = __webpack_require__(15); +const PreferencesPane = __webpack_require__(16); +const Sections = __webpack_require__(17); // Locales that should be displayed RTL const RTL_LIST = ["ar", "he", "fa", "ur"]; @@ -639,7 +682,1973 @@ class Base extends React.Component { module.exports = connect(state => ({ App: state.App, Prefs: state.Prefs }))(Base); /***/ }), -/* 8 */ +/* 10 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +const React = __webpack_require__(0); + +var _require = __webpack_require__(3); + +const connect = _require.connect; + +var _require2 = __webpack_require__(2); + +const FormattedMessage = _require2.FormattedMessage, + injectIntl = _require2.injectIntl; + +const LinkMenu = __webpack_require__(4); + +var _require3 = __webpack_require__(1); + +const ac = _require3.actionCreators, + at = _require3.actionTypes; + +var _require4 = __webpack_require__(5); + +const perfSvc = _require4.perfService; + +const TOP_SITES_SOURCE = "TOP_SITES"; +const TOP_SITES_CONTEXT_MENU_OPTIONS = ["CheckPinTopSite", "Separator", "OpenInNewWindow", "OpenInPrivateWindow", "Separator", "BlockUrl", "DeleteUrl"]; + +class TopSite extends React.Component { + constructor(props) { + super(props); + this.state = { showContextMenu: false, activeTile: null }; + this.onLinkClick = this.onLinkClick.bind(this); + this.onMenuButtonClick = this.onMenuButtonClick.bind(this); + this.onMenuUpdate = this.onMenuUpdate.bind(this); + this.onDismissButtonClick = this.onDismissButtonClick.bind(this); + this.onPinButtonClick = this.onPinButtonClick.bind(this); + } + toggleContextMenu(event, index) { + this.setState({ + activeTile: index, + showContextMenu: true + }); + } + userEvent(event) { + this.props.dispatch(ac.UserEvent({ + event, + source: TOP_SITES_SOURCE, + action_position: this.props.index + })); + } + onLinkClick(ev) { + if (this.props.editMode) { + // Ignore clicks if we are in the edit modal. + ev.preventDefault(); + return; + } + this.userEvent("CLICK"); + } + onMenuButtonClick(event) { + event.preventDefault(); + this.toggleContextMenu(event, this.props.index); + } + onMenuUpdate(showContextMenu) { + this.setState({ showContextMenu }); + } + onDismissButtonClick() { + const link = this.props.link; + + if (link.isPinned) { + this.props.dispatch(ac.SendToMain({ + type: at.TOP_SITES_UNPIN, + data: { site: { url: link.url } } + })); + } + this.props.dispatch(ac.SendToMain({ + type: at.BLOCK_URL, + data: link.url + })); + this.userEvent("BLOCK"); + } + onPinButtonClick() { + var _props = this.props; + const link = _props.link, + index = _props.index; + + if (link.isPinned) { + this.props.dispatch(ac.SendToMain({ + type: at.TOP_SITES_UNPIN, + data: { site: { url: link.url } } + })); + this.userEvent("UNPIN"); + } else { + this.props.dispatch(ac.SendToMain({ + type: at.TOP_SITES_PIN, + data: { site: { url: link.url }, index } + })); + this.userEvent("PIN"); + } + } + render() { + var _props2 = this.props; + const link = _props2.link, + index = _props2.index, + dispatch = _props2.dispatch, + editMode = _props2.editMode; + + const isContextMenuOpen = this.state.showContextMenu && this.state.activeTile === index; + const title = link.hostname; + const topSiteOuterClassName = `top-site-outer${isContextMenuOpen ? " active" : ""}`; + const tippyTopIcon = link.tippyTopIcon; + + let imageClassName; + let imageStyle; + if (tippyTopIcon) { + imageClassName = "tippy-top-icon"; + imageStyle = { + backgroundColor: link.backgroundColor, + backgroundImage: `url(${tippyTopIcon})` + }; + } else { + imageClassName = `screenshot${link.screenshot ? " active" : ""}`; + imageStyle = { backgroundImage: link.screenshot ? `url(${link.screenshot})` : "none" }; + } + return React.createElement( + "li", + { className: topSiteOuterClassName, key: link.guid || link.url }, + React.createElement( + "a", + { href: link.url, onClick: this.onLinkClick }, + React.createElement( + "div", + { className: "tile", "aria-hidden": true }, + React.createElement( + "span", + { className: "letter-fallback" }, + title[0] + ), + React.createElement("div", { className: imageClassName, style: imageStyle }) + ), + React.createElement( + "div", + { className: `title ${link.isPinned ? "pinned" : ""}` }, + link.isPinned && React.createElement("div", { className: "icon icon-pin-small" }), + React.createElement( + "span", + { dir: "auto" }, + title + ) + ) + ), + !editMode && React.createElement( + "div", + null, + React.createElement( + "button", + { className: "context-menu-button icon", onClick: this.onMenuButtonClick }, + React.createElement( + "span", + { className: "sr-only" }, + `Open context menu for ${title}` + ) + ), + React.createElement(LinkMenu, { + dispatch: dispatch, + index: index, + onUpdate: this.onMenuUpdate, + options: TOP_SITES_CONTEXT_MENU_OPTIONS, + site: link, + source: TOP_SITES_SOURCE, + visible: isContextMenuOpen }) + ), + editMode && React.createElement( + "div", + { className: "edit-menu" }, + React.createElement("button", { + className: `icon icon-${link.isPinned ? "unpin" : "pin"}`, + title: this.props.intl.formatMessage({ id: `edit_topsites_${link.isPinned ? "unpin" : "pin"}_button` }), + onClick: this.onPinButtonClick }), + React.createElement("button", { + className: "icon icon-dismiss", + title: this.props.intl.formatMessage({ id: "edit_topsites_dismiss_button" }), + onClick: this.onDismissButtonClick }) + ) + ); + } +} + +TopSite.defaultProps = { editMode: false }; + +/** + * A proxy class that uses double requestAnimationFrame from + * componentDidMount to dispatch a SAVE_SESSION_PERF_DATA to the main procsess + * after the paint. + * + * This uses two callbacks because, after one callback, this part of the tree + * may have rendered but not yet reflowed. This strategy is modeled after + * https://stackoverflow.com/a/34999925 but uses a double rFA because + * we want to get to the closest reliable paint for measuring, and + * setTimeout is often throttled or queued by browsers in ways that could + * make it lag too long. + * + * XXX Should be made more generic by using this.props.children, or potentially + * even split out into a higher-order component to wrap whatever. + * + * @class TopSitesPerfTimer + * @extends {React.Component} + */ +class TopSitesPerfTimer extends React.Component { + constructor(props) { + super(props); + // Just for test dependency injection: + this.perfSvc = this.props.perfSvc || perfSvc; + + this._sendPaintedEvent = this._sendPaintedEvent.bind(this); + this._timestampHandled = false; + } + + componentDidMount() { + this._maybeSendPaintedEvent(); + } + + componentDidUpdate() { + this._maybeSendPaintedEvent(); + } + + /** + * Call the given callback after the upcoming frame paints. + * + * @note Both setTimeout and requestAnimationFrame are throttled when the page + * is hidden, so this callback may get called up to a second or so after the + * requestAnimationFrame "paint" for hidden tabs. + * + * Newtabs hidden while loading will presumably be fairly rare (other than + * preloaded tabs, which we will be filtering out on the server side), so such + * cases should get lost in the noise. + * + * If we decide that it's important to find out when something that's hidden + * has "painted", however, another option is to post a message to this window. + * That should happen even faster than setTimeout, and, at least as of this + * writing, it's not throttled in hidden windows in Firefox. + * + * @param {Function} callback + * + * @returns void + */ + _afterFramePaint(callback) { + requestAnimationFrame(() => setTimeout(callback, 0)); + } + + _maybeSendPaintedEvent() { + // We don't want this to ever happen, but sometimes it does. And when it + // does (typically on the first newtab at startup time calling + // componentDidMount), the paint(s) we care about will be later (eg + // in a subsequent componentDidUpdate). + if (!this.props.TopSites.initialized) { + // XXX should send bad event + return; + } + + // If we've already handled a timestamp, don't do it again + if (this._timestampHandled) { + return; + } + + // And if we haven't, we're doing so now, so remember that. Even if + // something goes wrong in the callback, we can't try again, as we'd be + // sending back the wrong data, and we have to do it here, so that other + // calls to this method while waiting for the next frame won't also try to + // handle handle it. + this._timestampHandled = true; + + this._afterFramePaint(this._sendPaintedEvent); + } + + _sendPaintedEvent() { + this.perfSvc.mark("topsites_first_painted_ts"); + + try { + let topsites_first_painted_ts = this.perfSvc.getMostRecentAbsMarkStartByName("topsites_first_painted_ts"); + + this.props.dispatch(ac.SendToMain({ + type: at.SAVE_SESSION_PERF_DATA, + data: { topsites_first_painted_ts } + })); + } catch (ex) { + // If this failed, it's likely because the `privacy.resistFingerprinting` + // pref is true. We should at least not blow up, and should continue + // to set this._timestampHandled to avoid going through this again. + } + } + + render() { + return React.createElement(TopSites, this.props); + } +} + +const TopSites = props => React.createElement( + "section", + { className: "top-sites" }, + React.createElement( + "h3", + { className: "section-title" }, + React.createElement("span", { className: `icon icon-small-spacer icon-topsites` }), + React.createElement(FormattedMessage, { id: "header_top_sites" }) + ), + React.createElement( + "ul", + { className: "top-sites-list" }, + props.TopSites.rows.map((link, index) => link && React.createElement(TopSite, { + key: link.guid || link.url, + dispatch: props.dispatch, + link: link, + index: index, + intl: props.intl })) + ), + React.createElement(TopSitesEditIntl, props) +); + +class TopSitesEdit extends React.Component { + constructor(props) { + super(props); + this.state = { showEditModal: false }; + this.onEditButtonClick = this.onEditButtonClick.bind(this); + } + onEditButtonClick() { + this.setState({ showEditModal: !this.state.showEditModal }); + const event = this.state.showEditModal ? "TOP_SITES_EDIT_OPEN" : "TOP_SITES_EDIT_CLOSE"; + this.props.dispatch(ac.UserEvent({ + source: TOP_SITES_SOURCE, + event + })); + } + render() { + return React.createElement( + "div", + { className: "edit-topsites-wrapper" }, + React.createElement( + "div", + { className: "edit-topsites-button" }, + React.createElement( + "button", + { + className: "edit", + title: this.props.intl.formatMessage({ id: "edit_topsites_button_label" }), + onClick: this.onEditButtonClick }, + React.createElement(FormattedMessage, { id: "edit_topsites_button_text" }) + ) + ), + this.state.showEditModal && React.createElement( + "div", + { className: "edit-topsites" }, + React.createElement("div", { className: "modal-overlay" }), + React.createElement( + "div", + { className: "modal" }, + React.createElement( + "section", + { className: "edit-topsites-inner-wrapper" }, + React.createElement( + "h3", + { className: "section-title" }, + React.createElement("span", { className: `icon icon-small-spacer icon-topsites` }), + React.createElement(FormattedMessage, { id: "header_top_sites" }) + ), + React.createElement( + "ul", + { className: "top-sites-list" }, + this.props.TopSites.rows.map((link, index) => link && React.createElement(TopSite, { + key: link.guid || link.url, + dispatch: this.props.dispatch, + link: link, + index: index, + intl: this.props.intl, + editMode: true })) + ) + ), + React.createElement( + "section", + { className: "actions" }, + React.createElement( + "button", + { className: "done", onClick: this.onEditButtonClick }, + React.createElement(FormattedMessage, { id: "edit_topsites_done_button" }) + ) + ) + ) + ) + ); + } +} + +const TopSitesEditIntl = injectIntl(TopSitesEdit); + +module.exports = connect(state => ({ TopSites: state.TopSites }))(TopSitesPerfTimer); +module.exports._unconnected = TopSitesPerfTimer; +module.exports.TopSite = TopSite; +module.exports.TopSites = TopSites; +module.exports.TopSitesEdit = TopSitesEdit; + +/***/ }), +/* 11 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +const React = __webpack_require__(0); + +class ContextMenu extends React.Component { + constructor(props) { + super(props); + this.hideContext = this.hideContext.bind(this); + } + hideContext() { + this.props.onUpdate(false); + } + componentWillMount() { + this.hideContext(); + } + componentDidUpdate(prevProps) { + if (this.props.visible && !prevProps.visible) { + setTimeout(() => { + window.addEventListener("click", this.hideContext); + }, 0); + } + if (!this.props.visible && prevProps.visible) { + window.removeEventListener("click", this.hideContext); + } + } + componentWillUnmount() { + window.removeEventListener("click", this.hideContext); + } + render() { + return React.createElement( + "span", + { hidden: !this.props.visible, className: "context-menu" }, + React.createElement( + "ul", + { role: "menu", className: "context-menu-list" }, + this.props.options.map((option, i) => option.type === "separator" ? React.createElement("li", { key: i, className: "separator" }) : React.createElement(ContextMenuItem, { key: i, option: option, hideContext: this.hideContext })) + ) + ); + } +} + +class ContextMenuItem extends React.Component { + constructor(props) { + super(props); + this.onClick = this.onClick.bind(this); + this.onKeyDown = this.onKeyDown.bind(this); + } + onClick() { + this.props.hideContext(); + this.props.option.onClick(); + } + onKeyDown(event) { + const option = this.props.option; + + switch (event.key) { + case "Tab": + // tab goes down in context menu, shift + tab goes up in context menu + // if we're on the last item, one more tab will close the context menu + // similarly, if we're on the first item, one more shift + tab will close it + if (event.shiftKey && option.first || !event.shiftKey && option.last) { + this.props.hideContext(); + } + break; + case "Enter": + this.props.hideContext(); + option.onClick(); + break; + } + } + render() { + const option = this.props.option; + + return React.createElement( + "li", + { role: "menuitem", className: "context-menu-item" }, + React.createElement( + "a", + { onClick: this.onClick, onKeyDown: this.onKeyDown, tabIndex: "0" }, + option.icon && React.createElement("span", { className: `icon icon-spacer icon-${option.icon}` }), + option.label + ) + ); + } +} + +module.exports = ContextMenu; +module.exports.ContextMenu = ContextMenu; +module.exports.ContextMenuItem = ContextMenuItem; + +/***/ }), +/* 12 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var _require = __webpack_require__(1); + +const at = _require.actionTypes, + ac = _require.actionCreators; + +/** + * List of functions that return items that can be included as menu options in a + * LinkMenu. All functions take the site as the first parameter, and optionally + * the index of the site. + */ + +module.exports = { + Separator: () => ({ type: "separator" }), + RemoveBookmark: site => ({ + id: "menu_action_remove_bookmark", + icon: "bookmark-added", + action: ac.SendToMain({ + type: at.DELETE_BOOKMARK_BY_ID, + data: site.bookmarkGuid + }), + userEvent: "BOOKMARK_DELETE" + }), + AddBookmark: site => ({ + id: "menu_action_bookmark", + icon: "bookmark-hollow", + action: ac.SendToMain({ + type: at.BOOKMARK_URL, + data: { url: site.url, title: site.title } + }), + userEvent: "BOOKMARK_ADD" + }), + OpenInNewWindow: site => ({ + id: "menu_action_open_new_window", + icon: "new-window", + action: ac.SendToMain({ + type: at.OPEN_NEW_WINDOW, + data: { url: site.url, referrer: site.referrer } + }), + userEvent: "OPEN_NEW_WINDOW" + }), + OpenInPrivateWindow: site => ({ + id: "menu_action_open_private_window", + icon: "new-window-private", + action: ac.SendToMain({ + type: at.OPEN_PRIVATE_WINDOW, + data: { url: site.url, referrer: site.referrer } + }), + userEvent: "OPEN_PRIVATE_WINDOW" + }), + BlockUrl: (site, index, eventSource) => ({ + id: "menu_action_dismiss", + icon: "dismiss", + action: ac.SendToMain({ + type: at.BLOCK_URL, + data: site.url + }), + impression: ac.ImpressionStats({ + source: eventSource, + block: 0, + incognito: true, + tiles: [{ id: site.guid, pos: index }] + }), + userEvent: "BLOCK" + }), + DeleteUrl: site => ({ + id: "menu_action_delete", + icon: "delete", + action: { + type: at.DIALOG_OPEN, + data: { + onConfirm: [ac.SendToMain({ type: at.DELETE_HISTORY_URL, data: site.url }), ac.UserEvent({ event: "DELETE" })], + body_string_id: ["confirm_history_delete_p1", "confirm_history_delete_notice_p2"], + confirm_button_string_id: "menu_action_delete" + } + }, + userEvent: "DIALOG_OPEN" + }), + PinTopSite: (site, index) => ({ + id: "menu_action_pin", + icon: "pin", + action: ac.SendToMain({ + type: at.TOP_SITES_PIN, + data: { site: { url: site.url }, index } + }), + userEvent: "PIN" + }), + UnpinTopSite: site => ({ + id: "menu_action_unpin", + icon: "unpin", + action: ac.SendToMain({ + type: at.TOP_SITES_UNPIN, + data: { site: { url: site.url } } + }), + userEvent: "UNPIN" + }), + SaveToPocket: (site, index, eventSource) => ({ + id: "menu_action_save_to_pocket", + icon: "pocket", + action: ac.SendToMain({ + type: at.SAVE_TO_POCKET, + data: { site: { url: site.url, title: site.title } } + }), + impression: ac.ImpressionStats({ + source: eventSource, + pocket: 0, + incognito: true, + tiles: [{ id: site.guid, pos: index }] + }), + userEvent: "SAVE_TO_POCKET" + }) +}; + +module.exports.CheckBookmark = site => site.bookmarkGuid ? module.exports.RemoveBookmark(site) : module.exports.AddBookmark(site); +module.exports.CheckPinTopSite = (site, index) => site.isPinned ? module.exports.UnpinTopSite(site) : module.exports.PinTopSite(site, index); + +/***/ }), +/* 13 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +/* globals ContentSearchUIController */ + + +const React = __webpack_require__(0); + +var _require = __webpack_require__(3); + +const connect = _require.connect; + +var _require2 = __webpack_require__(2); + +const FormattedMessage = _require2.FormattedMessage, + injectIntl = _require2.injectIntl; + +var _require3 = __webpack_require__(1); + +const ac = _require3.actionCreators; + + +class Search extends React.Component { + constructor(props) { + super(props); + this.onClick = this.onClick.bind(this); + this.onInputMount = this.onInputMount.bind(this); + } + + handleEvent(event) { + // Also track search events with our own telemetry + if (event.detail.type === "Search") { + this.props.dispatch(ac.UserEvent({ event: "SEARCH" })); + } + } + onClick(event) { + this.controller.search(event); + } + onInputMount(input) { + if (input) { + // The first "newtab" parameter here is called the "healthReportKey" and needs + // to be "newtab" so that BrowserUsageTelemetry.jsm knows to handle events with + // this name, and can add the appropriate telemetry probes for search. Without the + // correct name, certain tests like browser_UsageTelemetry_content.js will fail (See + // github ticket #2348 for more details) + this.controller = new ContentSearchUIController(input, input.parentNode, "newtab", "newtab"); + addEventListener("ContentSearchClient", this); + } else { + this.controller = null; + removeEventListener("ContentSearchClient", this); + } + } + + /* + * Do not change the ID on the input field, as legacy newtab code + * specifically looks for the id 'newtab-search-text' on input fields + * in order to execute searches in various tests + */ + render() { + return React.createElement( + "form", + { className: "search-wrapper" }, + React.createElement( + "label", + { htmlFor: "newtab-search-text", className: "search-label" }, + React.createElement( + "span", + { className: "sr-only" }, + React.createElement(FormattedMessage, { id: "search_web_placeholder" }) + ) + ), + React.createElement("input", { + id: "newtab-search-text", + maxLength: "256", + placeholder: this.props.intl.formatMessage({ id: "search_web_placeholder" }), + ref: this.onInputMount, + title: this.props.intl.formatMessage({ id: "search_web_placeholder" }), + type: "search" }), + React.createElement( + "button", + { + className: "search-button", + onClick: this.onClick, + title: this.props.intl.formatMessage({ id: "search_button" }) }, + React.createElement( + "span", + { className: "sr-only" }, + React.createElement(FormattedMessage, { id: "search_button" }) + ) + ) + ); + } +} + +module.exports = connect()(injectIntl(Search)); +module.exports._unconnected = Search; + +/***/ }), +/* 14 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +const React = __webpack_require__(0); + +var _require = __webpack_require__(3); + +const connect = _require.connect; + +var _require2 = __webpack_require__(2); + +const FormattedMessage = _require2.FormattedMessage; + +var _require3 = __webpack_require__(1); + +const actionTypes = _require3.actionTypes, + ac = _require3.actionCreators; + +/** + * ConfirmDialog component. + * One primary action button, one cancel button. + * + * Content displayed is controlled by `data` prop the component receives. + * Example: + * data: { + * // Any sort of data needed to be passed around by actions. + * payload: site.url, + * // Primary button SendToMain action. + * action: "DELETE_HISTORY_URL", + * // Primary button USerEvent action. + * userEvent: "DELETE", + * // Array of locale ids to display. + * message_body: ["confirm_history_delete_p1", "confirm_history_delete_notice_p2"], + * // Text for primary button. + * confirm_button_string_id: "menu_action_delete" + * }, + */ + +const ConfirmDialog = React.createClass({ + displayName: "ConfirmDialog", + + getDefaultProps() { + return { + visible: false, + data: {} + }; + }, + + _handleCancelBtn() { + this.props.dispatch({ type: actionTypes.DIALOG_CANCEL }); + this.props.dispatch(ac.UserEvent({ event: actionTypes.DIALOG_CANCEL })); + }, + + _handleConfirmBtn() { + this.props.data.onConfirm.forEach(this.props.dispatch); + }, + + _renderModalMessage() { + const message_body = this.props.data.body_string_id; + + if (!message_body) { + return null; + } + + return React.createElement( + "span", + null, + message_body.map(msg => React.createElement( + "p", + { key: msg }, + React.createElement(FormattedMessage, { id: msg }) + )) + ); + }, + + render() { + if (!this.props.visible) { + return null; + } + + return React.createElement( + "div", + { className: "confirmation-dialog" }, + React.createElement("div", { className: "modal-overlay", onClick: this._handleCancelBtn }), + React.createElement( + "div", + { className: "modal", ref: "modal" }, + React.createElement( + "section", + { className: "modal-message" }, + this._renderModalMessage() + ), + React.createElement( + "section", + { className: "actions" }, + React.createElement( + "button", + { ref: "cancelButton", onClick: this._handleCancelBtn }, + React.createElement(FormattedMessage, { id: "topsites_form_cancel_button" }) + ), + React.createElement( + "button", + { ref: "confirmButton", className: "done", onClick: this._handleConfirmBtn }, + React.createElement(FormattedMessage, { id: this.props.data.confirm_button_string_id }) + ) + ) + ) + ); + } +}); + +module.exports = connect(state => state.Dialog)(ConfirmDialog); +module.exports._unconnected = ConfirmDialog; +module.exports.Dialog = ConfirmDialog; + +/***/ }), +/* 15 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +const React = __webpack_require__(0); + +var _require = __webpack_require__(3); + +const connect = _require.connect; + +var _require2 = __webpack_require__(2); + +const FormattedMessage = _require2.FormattedMessage; + +var _require3 = __webpack_require__(1); + +const at = _require3.actionTypes, + ac = _require3.actionCreators; + +/** + * Manual migration component used to start the profile import wizard. + * Message is presented temporarily and will go away if: + * 1. User clicks "No Thanks" + * 2. User completed the data import + * 3. After 3 active days + * 4. User clicks "Cancel" on the import wizard (currently not implemented). + */ + +class ManualMigration extends React.Component { + constructor(props) { + super(props); + this.onLaunchTour = this.onLaunchTour.bind(this); + this.onCancelTour = this.onCancelTour.bind(this); + } + onLaunchTour() { + this.props.dispatch(ac.SendToMain({ type: at.MIGRATION_START })); + this.props.dispatch(ac.UserEvent({ event: at.MIGRATION_START })); + } + + onCancelTour() { + this.props.dispatch(ac.SendToMain({ type: at.MIGRATION_CANCEL })); + this.props.dispatch(ac.UserEvent({ event: at.MIGRATION_CANCEL })); + } + + render() { + return React.createElement( + "div", + { className: "manual-migration-container" }, + React.createElement( + "p", + null, + React.createElement("span", { className: "icon icon-info" }), + React.createElement(FormattedMessage, { id: "manual_migration_explanation" }) + ), + React.createElement( + "div", + { className: "manual-migration-actions actions" }, + React.createElement( + "button", + { onClick: this.onCancelTour }, + React.createElement(FormattedMessage, { id: "manual_migration_cancel_button" }) + ), + React.createElement( + "button", + { className: "done", onClick: this.onLaunchTour }, + React.createElement(FormattedMessage, { id: "manual_migration_import_button" }) + ) + ) + ); + } +} + +module.exports = connect()(ManualMigration); +module.exports._unconnected = ManualMigration; + +/***/ }), +/* 16 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +const React = __webpack_require__(0); + +var _require = __webpack_require__(3); + +const connect = _require.connect; + +var _require2 = __webpack_require__(2); + +const injectIntl = _require2.injectIntl, + FormattedMessage = _require2.FormattedMessage; + +var _require3 = __webpack_require__(1); + +const ac = _require3.actionCreators, + at = _require3.actionTypes; + + +const getFormattedMessage = message => typeof message === "string" ? React.createElement( + "span", + null, + message +) : React.createElement(FormattedMessage, message); + +const PreferencesInput = props => React.createElement( + "section", + null, + React.createElement("input", { type: "checkbox", id: props.prefName, name: props.prefName, checked: props.value, onChange: props.onChange, className: props.className }), + React.createElement( + "label", + { htmlFor: props.prefName }, + getFormattedMessage(props.titleString) + ), + props.descString && React.createElement( + "p", + { className: "prefs-input-description" }, + getFormattedMessage(props.descString) + ) +); + +class PreferencesPane extends React.Component { + constructor(props) { + super(props); + this.state = { visible: false }; + this.handleClickOutside = this.handleClickOutside.bind(this); + this.handlePrefChange = this.handlePrefChange.bind(this); + this.handleSectionChange = this.handleSectionChange.bind(this); + this.togglePane = this.togglePane.bind(this); + } + componentDidMount() { + document.addEventListener("click", this.handleClickOutside); + } + componentWillUnmount() { + document.removeEventListener("click", this.handleClickOutside); + } + handleClickOutside(event) { + // if we are showing the sidebar and there is a click outside, close it. + if (this.state.visible && !this.refs.wrapper.contains(event.target)) { + this.togglePane(); + } + } + handlePrefChange(event) { + const target = event.target; + this.props.dispatch(ac.SetPref(target.name, target.checked)); + } + handleSectionChange(event) { + const target = event.target; + const id = target.name; + const type = target.checked ? at.SECTION_ENABLE : at.SECTION_DISABLE; + this.props.dispatch(ac.SendToMain({ type, data: id })); + } + togglePane() { + this.setState({ visible: !this.state.visible }); + const event = this.state.visible ? "CLOSE_NEWTAB_PREFS" : "OPEN_NEWTAB_PREFS"; + this.props.dispatch(ac.UserEvent({ event })); + } + render() { + const props = this.props; + const prefs = props.Prefs.values; + const sections = props.Sections; + const isVisible = this.state.visible; + return React.createElement( + "div", + { className: "prefs-pane-wrapper", ref: "wrapper" }, + React.createElement( + "div", + { className: "prefs-pane-button" }, + React.createElement("button", { + className: `prefs-button icon ${isVisible ? "icon-dismiss" : "icon-settings"}`, + title: props.intl.formatMessage({ id: isVisible ? "settings_pane_done_button" : "settings_pane_button_label" }), + onClick: this.togglePane }) + ), + React.createElement( + "div", + { className: "prefs-pane" }, + React.createElement( + "div", + { className: `sidebar ${isVisible ? "" : "hidden"}` }, + React.createElement( + "div", + { className: "prefs-modal-inner-wrapper" }, + React.createElement( + "h1", + null, + React.createElement(FormattedMessage, { id: "settings_pane_header" }) + ), + React.createElement( + "p", + null, + React.createElement(FormattedMessage, { id: "settings_pane_body" }) + ), + React.createElement(PreferencesInput, { className: "showSearch", prefName: "showSearch", value: prefs.showSearch, onChange: this.handlePrefChange, + titleString: { id: "settings_pane_search_header" }, descString: { id: "settings_pane_search_body" } }), + React.createElement(PreferencesInput, { className: "showTopSites", prefName: "showTopSites", value: prefs.showTopSites, onChange: this.handlePrefChange, + titleString: { id: "settings_pane_topsites_header" }, descString: { id: "settings_pane_topsites_body" } }), + sections.filter(section => !section.shouldHidePref).map((_ref) => { + let id = _ref.id, + title = _ref.title, + enabled = _ref.enabled, + pref = _ref.pref; + return React.createElement(PreferencesInput, { key: id, className: "showSection", prefName: pref && pref.feed || id, + value: enabled, onChange: pref && pref.feed ? this.handlePrefChange : this.handleSectionChange, + titleString: pref && pref.titleString || title, descString: pref && pref.descString }); + }) + ), + React.createElement( + "section", + { className: "actions" }, + React.createElement( + "button", + { className: "done", onClick: this.togglePane }, + React.createElement(FormattedMessage, { id: "settings_pane_done_button" }) + ) + ) + ) + ) + ); + } +} + +module.exports = connect(state => ({ Prefs: state.Prefs, Sections: state.Sections }))(injectIntl(PreferencesPane)); +module.exports.PreferencesPane = PreferencesPane; +module.exports.PreferencesInput = PreferencesInput; + +/***/ }), +/* 17 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +/* WEBPACK VAR INJECTION */(function(global) { + +var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; + +const React = __webpack_require__(0); + +var _require = __webpack_require__(3); + +const connect = _require.connect; + +var _require2 = __webpack_require__(2); + +const injectIntl = _require2.injectIntl, + FormattedMessage = _require2.FormattedMessage; + +const Card = __webpack_require__(18); +const Topics = __webpack_require__(20); + +var _require3 = __webpack_require__(1); + +const ac = _require3.actionCreators; + + +const VISIBLE = "visible"; +const VISIBILITY_CHANGE_EVENT = "visibilitychange"; + +class Section extends React.Component { + constructor(props) { + super(props); + this.onInfoEnter = this.onInfoEnter.bind(this); + this.onInfoLeave = this.onInfoLeave.bind(this); + this.state = { infoActive: false }; + } + + /** + * Take a truthy value to conditionally change the infoActive state. + */ + _setInfoState(nextActive) { + const infoActive = !!nextActive; + if (infoActive !== this.state.infoActive) { + this.setState({ infoActive }); + } + } + + onInfoEnter() { + // We're getting focus or hover, so info state should be true if not yet. + this._setInfoState(true); + } + + onInfoLeave(event) { + // We currently have an active (true) info state, so keep it true only if we + // have a related event target that is contained "within" the current target + // (section-info-option) as itself or a descendant. Set to false otherwise. + this._setInfoState(event && event.relatedTarget && (event.relatedTarget === event.currentTarget || event.relatedTarget.compareDocumentPosition(event.currentTarget) & Node.DOCUMENT_POSITION_CONTAINS)); + } + + getFormattedMessage(message) { + return typeof message === "string" ? React.createElement( + "span", + null, + message + ) : React.createElement(FormattedMessage, message); + } + + _dispatchImpressionStats() { + const props = this.props; + + const maxCards = 3 * props.maxRows; + props.dispatch(ac.ImpressionStats({ + source: props.eventSource, + tiles: props.rows.slice(0, maxCards).map(link => ({ id: link.guid })) + })); + } + + // This sends an event when a user sees a set of new content. If content + // changes while the page is hidden (i.e. preloaded or on a hidden tab), + // only send the event if the page becomes visible again. + sendImpressionStatsOrAddListener() { + const props = this.props; + + + if (!props.dispatch) { + return; + } + + if (props.document.visibilityState === VISIBLE) { + this._dispatchImpressionStats(); + } else { + // We should only ever send the latest impression stats ping, so remove any + // older listeners. + if (this._onVisibilityChange) { + props.document.removeEventListener(VISIBILITY_CHANGE_EVENT, this._onVisibilityChange); + } + + // When the page becoems visible, send the impression stats ping. + this._onVisibilityChange = () => { + if (props.document.visibilityState === VISIBLE) { + this._dispatchImpressionStats(); + props.document.removeEventListener(VISIBILITY_CHANGE_EVENT, this._onVisibilityChange); + } + }; + props.document.addEventListener(VISIBILITY_CHANGE_EVENT, this._onVisibilityChange); + } + } + + componentDidMount() { + if (this.props.rows.length) { + this.sendImpressionStatsOrAddListener(); + } + } + + componentDidUpdate(prevProps) { + const props = this.props; + + if ( + // Don't send impression stats for the empty state + props.rows.length && + // We only want to send impression stats if the content of the cards has changed + props.rows !== prevProps.rows) { + this.sendImpressionStatsOrAddListener(); + } + } + + render() { + var _props = this.props; + const id = _props.id, + eventSource = _props.eventSource, + title = _props.title, + icon = _props.icon, + rows = _props.rows, + infoOption = _props.infoOption, + emptyState = _props.emptyState, + dispatch = _props.dispatch, + maxRows = _props.maxRows, + contextMenuOptions = _props.contextMenuOptions, + intl = _props.intl; + + const maxCards = 3 * maxRows; + const initialized = rows && rows.length > 0; + const shouldShowTopics = id === "topstories" && this.props.topics && this.props.topics.length > 0 && this.props.read_more_endpoint; + + const infoOptionIconA11yAttrs = { + "aria-haspopup": "true", + "aria-controls": "info-option", + "aria-expanded": this.state.infoActive ? "true" : "false", + "role": "note", + "tabIndex": 0 + }; + + const sectionInfoTitle = intl.formatMessage({ id: "section_info_option" }); + + //
<-- React component + //
<-- HTML5 element + return React.createElement( + "section", + null, + React.createElement( + "div", + { className: "section-top-bar" }, + React.createElement( + "h3", + { className: "section-title" }, + icon && icon.startsWith("moz-extension://") ? React.createElement("span", { className: "icon icon-small-spacer", style: { "background-image": `url('${icon}')` } }) : React.createElement("span", { className: `icon icon-small-spacer icon-${icon || "webextension"}` }), + this.getFormattedMessage(title) + ), + infoOption && React.createElement( + "span", + { className: "section-info-option", + onBlur: this.onInfoLeave, + onFocus: this.onInfoEnter, + onMouseOut: this.onInfoLeave, + onMouseOver: this.onInfoEnter }, + React.createElement("img", _extends({ className: "info-option-icon", title: sectionInfoTitle + }, infoOptionIconA11yAttrs)), + React.createElement( + "div", + { className: "info-option" }, + infoOption.header && React.createElement( + "div", + { className: "info-option-header", role: "heading" }, + this.getFormattedMessage(infoOption.header) + ), + infoOption.body && React.createElement( + "p", + { className: "info-option-body" }, + this.getFormattedMessage(infoOption.body) + ), + infoOption.link && React.createElement( + "a", + { href: infoOption.link.href, target: "_blank", rel: "noopener noreferrer", className: "info-option-link" }, + this.getFormattedMessage(infoOption.link.title || infoOption.link) + ) + ) + ) + ), + React.createElement( + "ul", + { className: "section-list", style: { padding: 0 } }, + rows.slice(0, maxCards).map((link, index) => link && React.createElement(Card, { index: index, dispatch: dispatch, link: link, contextMenuOptions: contextMenuOptions, eventSource: eventSource })) + ), + !initialized && React.createElement( + "div", + { className: "section-empty-state" }, + React.createElement( + "div", + { className: "empty-state" }, + React.createElement("img", { className: `empty-state-icon icon icon-${emptyState.icon}` }), + React.createElement( + "p", + { className: "empty-state-message" }, + this.getFormattedMessage(emptyState.message) + ) + ) + ), + shouldShowTopics && React.createElement(Topics, { topics: this.props.topics, read_more_endpoint: this.props.read_more_endpoint }) + ); + } +} + +Section.defaultProps = { document: global.document }; + +const SectionIntl = injectIntl(Section); + +class Sections extends React.Component { + render() { + const sections = this.props.Sections; + return React.createElement( + "div", + { className: "sections-list" }, + sections.filter(section => section.enabled).map(section => React.createElement(SectionIntl, _extends({ key: section.id }, section, { dispatch: this.props.dispatch }))) + ); + } +} + +module.exports = connect(state => ({ Sections: state.Sections }))(Sections); +module.exports._unconnected = Sections; +module.exports.SectionIntl = SectionIntl; +module.exports._unconnectedSection = Section; +/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(6))) + +/***/ }), +/* 18 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +const React = __webpack_require__(0); +const LinkMenu = __webpack_require__(4); + +var _require = __webpack_require__(2); + +const FormattedMessage = _require.FormattedMessage; + +const cardContextTypes = __webpack_require__(19); + +var _require2 = __webpack_require__(1); + +const ac = _require2.actionCreators, + at = _require2.actionTypes; + +/** + * Card component. + * Cards are found within a Section component and contain information about a link such + * as preview image, page title, page description, and some context about if the page + * was visited, bookmarked, trending etc... + * Each Section can make an unordered list of Cards which will create one instane of + * this class. Each card will then get a context menu which reflects the actions that + * can be done on this Card. + */ + +class Card extends React.Component { + constructor(props) { + super(props); + this.state = { showContextMenu: false, activeCard: null }; + this.onMenuButtonClick = this.onMenuButtonClick.bind(this); + this.onMenuUpdate = this.onMenuUpdate.bind(this); + this.onLinkClick = this.onLinkClick.bind(this); + } + onMenuButtonClick(event) { + event.preventDefault(); + this.setState({ + activeCard: this.props.index, + showContextMenu: true + }); + } + onLinkClick(event) { + event.preventDefault(); + const altKey = event.altKey, + button = event.button, + ctrlKey = event.ctrlKey, + metaKey = event.metaKey, + shiftKey = event.shiftKey; + + this.props.dispatch(ac.SendToMain({ + type: at.OPEN_LINK, + data: Object.assign(this.props.link, { event: { altKey, button, ctrlKey, metaKey, shiftKey } }) + })); + this.props.dispatch(ac.UserEvent({ + event: "CLICK", + source: this.props.eventSource, + action_position: this.props.index + })); + this.props.dispatch(ac.ImpressionStats({ + source: this.props.eventSource, + click: 0, + incognito: true, + tiles: [{ id: this.props.link.guid, pos: this.props.index }] + })); + } + onMenuUpdate(showContextMenu) { + this.setState({ showContextMenu }); + } + render() { + var _props = this.props; + const index = _props.index, + link = _props.link, + dispatch = _props.dispatch, + contextMenuOptions = _props.contextMenuOptions, + eventSource = _props.eventSource; + + const isContextMenuOpen = this.state.showContextMenu && this.state.activeCard === index; + + var _ref = link.type ? cardContextTypes[link.type] : {}; + + const icon = _ref.icon, + intlID = _ref.intlID; + + + return React.createElement( + "li", + { className: `card-outer${isContextMenuOpen ? " active" : ""}` }, + React.createElement( + "a", + { href: link.url, onClick: this.onLinkClick }, + React.createElement( + "div", + { className: "card" }, + link.image && React.createElement("div", { className: "card-preview-image", style: { backgroundImage: `url(${link.image})` } }), + React.createElement( + "div", + { className: `card-details${link.image ? "" : " no-image"}` }, + link.hostname && React.createElement( + "div", + { className: "card-host-name" }, + link.hostname + ), + React.createElement( + "div", + { className: `card-text${link.image ? "" : " no-image"}${link.hostname ? "" : " no-host-name"}${icon ? "" : " no-context"}` }, + React.createElement( + "h4", + { className: "card-title", dir: "auto" }, + link.title + ), + React.createElement( + "p", + { className: "card-description", dir: "auto" }, + link.description + ) + ), + icon && React.createElement( + "div", + { className: "card-context" }, + React.createElement("span", { className: `card-context-icon icon icon-${icon}` }), + React.createElement( + "div", + { className: "card-context-label" }, + React.createElement(FormattedMessage, { id: intlID, defaultMessage: "Visited" }) + ) + ) + ) + ) + ), + React.createElement( + "button", + { className: "context-menu-button icon", + onClick: this.onMenuButtonClick }, + React.createElement( + "span", + { className: "sr-only" }, + `Open context menu for ${link.title}` + ) + ), + React.createElement(LinkMenu, { + dispatch: dispatch, + index: index, + source: eventSource, + onUpdate: this.onMenuUpdate, + options: link.context_menu_options || contextMenuOptions, + site: link, + visible: isContextMenuOpen }) + ); + } +} +module.exports = Card; + +/***/ }), +/* 19 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +module.exports = { + history: { + intlID: "type_label_visited", + icon: "historyItem" + }, + bookmark: { + intlID: "type_label_bookmarked", + icon: "bookmark" + }, + trending: { + intlID: "type_label_recommended", + icon: "trending" + }, + now: { + intlID: "type_label_now", + icon: "now" + } +}; + +/***/ }), +/* 20 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +const React = __webpack_require__(0); + +var _require = __webpack_require__(2); + +const FormattedMessage = _require.FormattedMessage; + + +class Topic extends React.Component { + render() { + var _props = this.props; + const url = _props.url, + name = _props.name; + + return React.createElement( + "li", + null, + React.createElement( + "a", + { key: name, className: "topic-link", href: url }, + name + ) + ); + } +} + +class Topics extends React.Component { + render() { + var _props2 = this.props; + const topics = _props2.topics, + read_more_endpoint = _props2.read_more_endpoint; + + return React.createElement( + "div", + { className: "topic" }, + React.createElement( + "span", + null, + React.createElement(FormattedMessage, { id: "pocket_read_more" }) + ), + React.createElement( + "ul", + null, + topics.map(t => React.createElement(Topic, { key: t.name, url: t.url, name: t.name })) + ), + React.createElement( + "a", + { className: "topic-read-more", href: read_more_endpoint }, + React.createElement(FormattedMessage, { id: "pocket_read_even_more" }), + React.createElement("span", { className: "topic-read-more-logo" }) + ) + ); + } +} + +module.exports = Topics; +module.exports._unconnected = Topics; +module.exports.Topic = Topic; + +/***/ }), +/* 21 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +/* eslint-env mozilla/frame-script */ + +var _require = __webpack_require__(22); + +const createStore = _require.createStore, + combineReducers = _require.combineReducers, + applyMiddleware = _require.applyMiddleware; + +var _require2 = __webpack_require__(1); + +const au = _require2.actionUtils; + + +const MERGE_STORE_ACTION = "NEW_TAB_INITIAL_STATE"; +const OUTGOING_MESSAGE_NAME = "ActivityStream:ContentToMain"; +const INCOMING_MESSAGE_NAME = "ActivityStream:MainToContent"; + +/** + * A higher-order function which returns a reducer that, on MERGE_STORE action, + * will return the action.data object merged into the previous state. + * + * For all other actions, it merely calls mainReducer. + * + * Because we want this to merge the entire state object, it's written as a + * higher order function which takes the main reducer (itself often a call to + * combineReducers) as a parameter. + * + * @param {function} mainReducer reducer to call if action != MERGE_STORE_ACTION + * @return {function} a reducer that, on MERGE_STORE_ACTION action, + * will return the action.data object merged + * into the previous state, and the result + * of calling mainReducer otherwise. + */ +function mergeStateReducer(mainReducer) { + return (prevState, action) => { + if (action.type === MERGE_STORE_ACTION) { + return Object.assign({}, prevState, action.data); + } + + return mainReducer(prevState, action); + }; +} + +/** + * messageMiddleware - Middleware that looks for SentToMain type actions, and sends them if necessary + */ +const messageMiddleware = store => next => action => { + if (au.isSendToMain(action)) { + sendAsyncMessage(OUTGOING_MESSAGE_NAME, action); + } + next(action); +}; + +/** + * initStore - Create a store and listen for incoming actions + * + * @param {object} reducers An object containing Redux reducers + * @return {object} A redux store + */ +module.exports = function initStore(reducers) { + const store = createStore(mergeStateReducer(combineReducers(reducers)), applyMiddleware(messageMiddleware)); + + addMessageListener(INCOMING_MESSAGE_NAME, msg => { + try { + store.dispatch(msg.data); + } catch (ex) { + console.error("Content msg:", msg, "Dispatch error: ", ex); // eslint-disable-line no-console + dump(`Content msg: ${JSON.stringify(msg)}\nDispatch error: ${ex}\n${ex.stack}`); + } + }); + + return store; +}; + +module.exports.MERGE_STORE_ACTION = MERGE_STORE_ACTION; +module.exports.OUTGOING_MESSAGE_NAME = OUTGOING_MESSAGE_NAME; +module.exports.INCOMING_MESSAGE_NAME = INCOMING_MESSAGE_NAME; + +/***/ }), +/* 22 */ +/***/ (function(module, exports) { + +module.exports = Redux; + +/***/ }), +/* 23 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +/* 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/. */ + + +var _require = __webpack_require__(1); + +const at = _require.actionTypes; + + +const TOP_SITES_SHOWMORE_LENGTH = 12; + +const INITIAL_STATE = { + App: { + // Have we received real data from the app yet? + initialized: false, + // The locale of the browser + locale: "", + // Localized strings with defaults + strings: null, + // The version of the system-addon + version: null + }, + Snippets: { initialized: false }, + TopSites: { + // Have we received real data from history yet? + initialized: false, + // The history (and possibly default) links + rows: [] + }, + Prefs: { + initialized: false, + values: {} + }, + Dialog: { + visible: false, + data: {} + }, + Sections: [] +}; + +function App() { + let prevState = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : INITIAL_STATE.App; + let action = arguments[1]; + + switch (action.type) { + case at.INIT: + return Object.assign({}, prevState, action.data || {}, { initialized: true }); + case at.LOCALE_UPDATED: + { + if (!action.data) { + return prevState; + } + var _action$data = action.data; + let locale = _action$data.locale, + strings = _action$data.strings; + + return Object.assign({}, prevState, { + locale, + strings + }); + } + default: + return prevState; + } +} + +/** + * insertPinned - Inserts pinned links in their specified slots + * + * @param {array} a list of links + * @param {array} a list of pinned links + * @return {array} resulting list of links with pinned links inserted + */ +function insertPinned(links, pinned) { + // Remove any pinned links + const pinnedUrls = pinned.map(link => link && link.url); + let newLinks = links.filter(link => link ? !pinnedUrls.includes(link.url) : false); + newLinks = newLinks.map(link => { + if (link && link.isPinned) { + delete link.isPinned; + delete link.pinIndex; + } + return link; + }); + + // Then insert them in their specified location + pinned.forEach((val, index) => { + if (!val) { + return; + } + let link = Object.assign({}, val, { isPinned: true, pinIndex: index }); + if (index > newLinks.length) { + newLinks[index] = link; + } else { + newLinks.splice(index, 0, link); + } + }); + + return newLinks; +} + +function TopSites() { + let prevState = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : INITIAL_STATE.TopSites; + let action = arguments[1]; + + let hasMatch; + let newRows; + let pinned; + switch (action.type) { + case at.TOP_SITES_UPDATED: + if (!action.data) { + return prevState; + } + return Object.assign({}, prevState, { initialized: true, rows: action.data }); + case at.SCREENSHOT_UPDATED: + newRows = prevState.rows.map(row => { + if (row && row.url === action.data.url) { + hasMatch = true; + return Object.assign({}, row, { screenshot: action.data.screenshot }); + } + return row; + }); + return hasMatch ? Object.assign({}, prevState, { rows: newRows }) : prevState; + case at.PLACES_BOOKMARK_ADDED: + if (!action.data) { + return prevState; + } + newRows = prevState.rows.map(site => { + if (site && site.url === action.data.url) { + var _action$data2 = action.data; + const bookmarkGuid = _action$data2.bookmarkGuid, + bookmarkTitle = _action$data2.bookmarkTitle, + lastModified = _action$data2.lastModified; + + return Object.assign({}, site, { bookmarkGuid, bookmarkTitle, bookmarkDateCreated: lastModified }); + } + return site; + }); + return Object.assign({}, prevState, { rows: newRows }); + case at.PLACES_BOOKMARK_REMOVED: + if (!action.data) { + return prevState; + } + newRows = prevState.rows.map(site => { + if (site && site.url === action.data.url) { + const newSite = Object.assign({}, site); + delete newSite.bookmarkGuid; + delete newSite.bookmarkTitle; + delete newSite.bookmarkDateCreated; + return newSite; + } + return site; + }); + return Object.assign({}, prevState, { rows: newRows }); + case at.PLACES_LINK_DELETED: + case at.PLACES_LINK_BLOCKED: + newRows = prevState.rows.filter(val => val && val.url !== action.data.url); + return Object.assign({}, prevState, { rows: newRows }); + case at.PINNED_SITES_UPDATED: + pinned = action.data; + newRows = insertPinned(prevState.rows, pinned).slice(0, TOP_SITES_SHOWMORE_LENGTH); + return Object.assign({}, prevState, { rows: newRows }); + default: + return prevState; + } +} + +function Dialog() { + let prevState = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : INITIAL_STATE.Dialog; + let action = arguments[1]; + + switch (action.type) { + case at.DIALOG_OPEN: + return Object.assign({}, prevState, { visible: true, data: action.data }); + case at.DIALOG_CANCEL: + return Object.assign({}, prevState, { visible: false }); + case at.DELETE_HISTORY_URL: + return Object.assign({}, INITIAL_STATE.Dialog); + default: + return prevState; + } +} + +function Prefs() { + let prevState = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : INITIAL_STATE.Prefs; + let action = arguments[1]; + + let newValues; + switch (action.type) { + case at.PREFS_INITIAL_VALUES: + return Object.assign({}, prevState, { initialized: true, values: action.data }); + case at.PREF_CHANGED: + newValues = Object.assign({}, prevState.values); + newValues[action.data.name] = action.data.value; + return Object.assign({}, prevState, { values: newValues }); + default: + return prevState; + } +} + +function Sections() { + let prevState = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : INITIAL_STATE.Sections; + let action = arguments[1]; + + let hasMatch; + let newState; + switch (action.type) { + case at.SECTION_DEREGISTER: + return prevState.filter(section => section.id !== action.data); + case at.SECTION_REGISTER: + // If section exists in prevState, update it + newState = prevState.map(section => { + if (section && section.id === action.data.id) { + hasMatch = true; + return Object.assign({}, section, action.data); + } + return section; + }); + + // Invariant: Sections array sorted in increasing order of property `order`. + // If section doesn't exist in prevState, create a new section object. If + // the section has an order, insert it at the correct place in the array. + // Otherwise, prepend it and set the order to be minimal. + if (!hasMatch) { + const initialized = action.data.rows && action.data.rows.length > 0; + let order; + let index; + if (prevState.length > 0) { + order = action.data.order || prevState[0].order - 1; + index = newState.findIndex(section => section.order >= order); + } else { + order = action.data.order || 1; + index = 0; + } + const section = Object.assign({ title: "", initialized, rows: [], order, enabled: false }, action.data); + newState.splice(index, 0, section); + } + return newState; + case at.SECTION_UPDATE: + return prevState.map(section => { + if (section && section.id === action.data.id) { + return Object.assign({}, section, action.data); + } + return section; + }); + case at.PLACES_BOOKMARK_ADDED: + if (!action.data) { + return prevState; + } + return prevState.map(section => Object.assign({}, section, { + rows: section.rows.map(item => { + // find the item within the rows that is attempted to be bookmarked + if (item.url === action.data.url) { + var _action$data3 = action.data; + const bookmarkGuid = _action$data3.bookmarkGuid, + bookmarkTitle = _action$data3.bookmarkTitle, + lastModified = _action$data3.lastModified; + + Object.assign(item, { bookmarkGuid, bookmarkTitle, bookmarkDateCreated: lastModified }); + } + return item; + }) + })); + case at.PLACES_BOOKMARK_REMOVED: + if (!action.data) { + return prevState; + } + return prevState.map(section => Object.assign({}, section, { + rows: section.rows.map(item => { + // find the bookmark within the rows that is attempted to be removed + if (item.url === action.data.url) { + const newSite = Object.assign({}, item); + delete newSite.bookmarkGuid; + delete newSite.bookmarkTitle; + delete newSite.bookmarkDateCreated; + return newSite; + } + return item; + }) + })); + case at.PLACES_LINK_DELETED: + case at.PLACES_LINK_BLOCKED: + return prevState.map(section => Object.assign({}, section, { rows: section.rows.filter(site => site.url !== action.data.url) })); + default: + return prevState; + } +} + +function Snippets() { + let prevState = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : INITIAL_STATE.Snippets; + let action = arguments[1]; + + switch (action.type) { + case at.SNIPPETS_DATA: + return Object.assign({}, prevState, { initialized: true }, action.data); + case at.SNIPPETS_RESET: + return INITIAL_STATE.Snippets; + default: + return prevState; + } +} + +var reducers = { TopSites, App, Snippets, Prefs, Dialog, Sections }; +module.exports = { + reducers, + INITIAL_STATE, + insertPinned, + TOP_SITES_SHOWMORE_LENGTH +}; + +/***/ }), +/* 24 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -719,92 +2728,7 @@ module.exports = class DetectUserSessionStart { }; /***/ }), -/* 9 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -/* eslint-env mozilla/frame-script */ - -var _require = __webpack_require__(24); - -const createStore = _require.createStore, - combineReducers = _require.combineReducers, - applyMiddleware = _require.applyMiddleware; - -var _require2 = __webpack_require__(1); - -const au = _require2.actionUtils; - - -const MERGE_STORE_ACTION = "NEW_TAB_INITIAL_STATE"; -const OUTGOING_MESSAGE_NAME = "ActivityStream:ContentToMain"; -const INCOMING_MESSAGE_NAME = "ActivityStream:MainToContent"; - -/** - * A higher-order function which returns a reducer that, on MERGE_STORE action, - * will return the action.data object merged into the previous state. - * - * For all other actions, it merely calls mainReducer. - * - * Because we want this to merge the entire state object, it's written as a - * higher order function which takes the main reducer (itself often a call to - * combineReducers) as a parameter. - * - * @param {function} mainReducer reducer to call if action != MERGE_STORE_ACTION - * @return {function} a reducer that, on MERGE_STORE_ACTION action, - * will return the action.data object merged - * into the previous state, and the result - * of calling mainReducer otherwise. - */ -function mergeStateReducer(mainReducer) { - return (prevState, action) => { - if (action.type === MERGE_STORE_ACTION) { - return Object.assign({}, prevState, action.data); - } - - return mainReducer(prevState, action); - }; -} - -/** - * messageMiddleware - Middleware that looks for SentToMain type actions, and sends them if necessary - */ -const messageMiddleware = store => next => action => { - if (au.isSendToMain(action)) { - sendAsyncMessage(OUTGOING_MESSAGE_NAME, action); - } - next(action); -}; - -/** - * initStore - Create a store and listen for incoming actions - * - * @param {object} reducers An object containing Redux reducers - * @return {object} A redux store - */ -module.exports = function initStore(reducers) { - const store = createStore(mergeStateReducer(combineReducers(reducers)), applyMiddleware(messageMiddleware)); - - addMessageListener(INCOMING_MESSAGE_NAME, msg => { - try { - store.dispatch(msg.data); - } catch (ex) { - console.error("Content msg:", msg, "Dispatch error: ", ex); // eslint-disable-line no-console - dump(`Content msg: ${JSON.stringify(msg)}\nDispatch error: ${ex}\n${ex.stack}`); - } - }); - - return store; -}; - -module.exports.MERGE_STORE_ACTION = MERGE_STORE_ACTION; -module.exports.OUTGOING_MESSAGE_NAME = OUTGOING_MESSAGE_NAME; -module.exports.INCOMING_MESSAGE_NAME = INCOMING_MESSAGE_NAME; - -/***/ }), -/* 10 */ +/* 25 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -1117,1909 +3041,5 @@ module.exports = { }; /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(6))) -/***/ }), -/* 11 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -/* 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/. */ - - -var _require = __webpack_require__(1); - -const at = _require.actionTypes; - - -const TOP_SITES_SHOWMORE_LENGTH = 12; - -const INITIAL_STATE = { - App: { - // Have we received real data from the app yet? - initialized: false, - // The locale of the browser - locale: "", - // Localized strings with defaults - strings: null, - // The version of the system-addon - version: null - }, - Snippets: { initialized: false }, - TopSites: { - // Have we received real data from history yet? - initialized: false, - // The history (and possibly default) links - rows: [] - }, - Prefs: { - initialized: false, - values: {} - }, - Dialog: { - visible: false, - data: {} - }, - Sections: [] -}; - -function App() { - let prevState = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : INITIAL_STATE.App; - let action = arguments[1]; - - switch (action.type) { - case at.INIT: - return Object.assign({}, prevState, action.data || {}, { initialized: true }); - case at.LOCALE_UPDATED: - { - if (!action.data) { - return prevState; - } - var _action$data = action.data; - let locale = _action$data.locale, - strings = _action$data.strings; - - return Object.assign({}, prevState, { - locale, - strings - }); - } - default: - return prevState; - } -} - -/** - * insertPinned - Inserts pinned links in their specified slots - * - * @param {array} a list of links - * @param {array} a list of pinned links - * @return {array} resulting list of links with pinned links inserted - */ -function insertPinned(links, pinned) { - // Remove any pinned links - const pinnedUrls = pinned.map(link => link && link.url); - let newLinks = links.filter(link => link ? !pinnedUrls.includes(link.url) : false); - newLinks = newLinks.map(link => { - if (link && link.isPinned) { - delete link.isPinned; - delete link.pinIndex; - } - return link; - }); - - // Then insert them in their specified location - pinned.forEach((val, index) => { - if (!val) { - return; - } - let link = Object.assign({}, val, { isPinned: true, pinIndex: index }); - if (index > newLinks.length) { - newLinks[index] = link; - } else { - newLinks.splice(index, 0, link); - } - }); - - return newLinks; -} - -function TopSites() { - let prevState = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : INITIAL_STATE.TopSites; - let action = arguments[1]; - - let hasMatch; - let newRows; - let pinned; - switch (action.type) { - case at.TOP_SITES_UPDATED: - if (!action.data) { - return prevState; - } - return Object.assign({}, prevState, { initialized: true, rows: action.data }); - case at.SCREENSHOT_UPDATED: - newRows = prevState.rows.map(row => { - if (row && row.url === action.data.url) { - hasMatch = true; - return Object.assign({}, row, { screenshot: action.data.screenshot }); - } - return row; - }); - return hasMatch ? Object.assign({}, prevState, { rows: newRows }) : prevState; - case at.PLACES_BOOKMARK_ADDED: - if (!action.data) { - return prevState; - } - newRows = prevState.rows.map(site => { - if (site && site.url === action.data.url) { - var _action$data2 = action.data; - const bookmarkGuid = _action$data2.bookmarkGuid, - bookmarkTitle = _action$data2.bookmarkTitle, - lastModified = _action$data2.lastModified; - - return Object.assign({}, site, { bookmarkGuid, bookmarkTitle, bookmarkDateCreated: lastModified }); - } - return site; - }); - return Object.assign({}, prevState, { rows: newRows }); - case at.PLACES_BOOKMARK_REMOVED: - if (!action.data) { - return prevState; - } - newRows = prevState.rows.map(site => { - if (site && site.url === action.data.url) { - const newSite = Object.assign({}, site); - delete newSite.bookmarkGuid; - delete newSite.bookmarkTitle; - delete newSite.bookmarkDateCreated; - return newSite; - } - return site; - }); - return Object.assign({}, prevState, { rows: newRows }); - case at.PLACES_LINK_DELETED: - case at.PLACES_LINK_BLOCKED: - newRows = prevState.rows.filter(val => val && val.url !== action.data.url); - return Object.assign({}, prevState, { rows: newRows }); - case at.PINNED_SITES_UPDATED: - pinned = action.data; - newRows = insertPinned(prevState.rows, pinned).slice(0, TOP_SITES_SHOWMORE_LENGTH); - return Object.assign({}, prevState, { rows: newRows }); - default: - return prevState; - } -} - -function Dialog() { - let prevState = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : INITIAL_STATE.Dialog; - let action = arguments[1]; - - switch (action.type) { - case at.DIALOG_OPEN: - return Object.assign({}, prevState, { visible: true, data: action.data }); - case at.DIALOG_CANCEL: - return Object.assign({}, prevState, { visible: false }); - case at.DELETE_HISTORY_URL: - return Object.assign({}, INITIAL_STATE.Dialog); - default: - return prevState; - } -} - -function Prefs() { - let prevState = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : INITIAL_STATE.Prefs; - let action = arguments[1]; - - let newValues; - switch (action.type) { - case at.PREFS_INITIAL_VALUES: - return Object.assign({}, prevState, { initialized: true, values: action.data }); - case at.PREF_CHANGED: - newValues = Object.assign({}, prevState.values); - newValues[action.data.name] = action.data.value; - return Object.assign({}, prevState, { values: newValues }); - default: - return prevState; - } -} - -function Sections() { - let prevState = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : INITIAL_STATE.Sections; - let action = arguments[1]; - - let hasMatch; - let newState; - switch (action.type) { - case at.SECTION_DEREGISTER: - return prevState.filter(section => section.id !== action.data); - case at.SECTION_REGISTER: - // If section exists in prevState, update it - newState = prevState.map(section => { - if (section && section.id === action.data.id) { - hasMatch = true; - return Object.assign({}, section, action.data); - } - return section; - }); - // If section doesn't exist in prevState, create a new section object and - // append it to the sections state - if (!hasMatch) { - const initialized = action.data.rows && action.data.rows.length > 0; - newState.push(Object.assign({ title: "", initialized, rows: [] }, action.data)); - } - return newState; - case at.SECTION_ROWS_UPDATE: - return prevState.map(section => { - if (section && section.id === action.data.id) { - return Object.assign({}, section, action.data); - } - return section; - }); - case at.PLACES_BOOKMARK_ADDED: - if (!action.data) { - return prevState; - } - return prevState.map(section => Object.assign({}, section, { - rows: section.rows.map(item => { - // find the item within the rows that is attempted to be bookmarked - if (item.url === action.data.url) { - var _action$data3 = action.data; - const bookmarkGuid = _action$data3.bookmarkGuid, - bookmarkTitle = _action$data3.bookmarkTitle, - lastModified = _action$data3.lastModified; - - Object.assign(item, { bookmarkGuid, bookmarkTitle, bookmarkDateCreated: lastModified }); - } - return item; - }) - })); - case at.PLACES_BOOKMARK_REMOVED: - if (!action.data) { - return prevState; - } - return prevState.map(section => Object.assign({}, section, { - rows: section.rows.map(item => { - // find the bookmark within the rows that is attempted to be removed - if (item.url === action.data.url) { - const newSite = Object.assign({}, item); - delete newSite.bookmarkGuid; - delete newSite.bookmarkTitle; - delete newSite.bookmarkDateCreated; - return newSite; - } - return item; - }) - })); - case at.PLACES_LINK_DELETED: - case at.PLACES_LINK_BLOCKED: - return prevState.map(section => Object.assign({}, section, { rows: section.rows.filter(site => site.url !== action.data.url) })); - default: - return prevState; - } -} - -function Snippets() { - let prevState = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : INITIAL_STATE.Snippets; - let action = arguments[1]; - - switch (action.type) { - case at.SNIPPETS_DATA: - return Object.assign({}, prevState, { initialized: true }, action.data); - case at.SNIPPETS_RESET: - return INITIAL_STATE.Snippets; - default: - return prevState; - } -} - -var reducers = { TopSites, App, Snippets, Prefs, Dialog, Sections }; -module.exports = { - reducers, - INITIAL_STATE, - insertPinned, - TOP_SITES_SHOWMORE_LENGTH -}; - -/***/ }), -/* 12 */ -/***/ (function(module, exports) { - -module.exports = ReactDOM; - -/***/ }), -/* 13 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -const React = __webpack_require__(0); -const LinkMenu = __webpack_require__(4); - -var _require = __webpack_require__(2); - -const FormattedMessage = _require.FormattedMessage; - -const cardContextTypes = __webpack_require__(14); - -var _require2 = __webpack_require__(1); - -const ac = _require2.actionCreators, - at = _require2.actionTypes; - -/** - * Card component. - * Cards are found within a Section component and contain information about a link such - * as preview image, page title, page description, and some context about if the page - * was visited, bookmarked, trending etc... - * Each Section can make an unordered list of Cards which will create one instane of - * this class. Each card will then get a context menu which reflects the actions that - * can be done on this Card. - */ - -class Card extends React.Component { - constructor(props) { - super(props); - this.state = { showContextMenu: false, activeCard: null }; - this.onMenuButtonClick = this.onMenuButtonClick.bind(this); - this.onMenuUpdate = this.onMenuUpdate.bind(this); - this.onLinkClick = this.onLinkClick.bind(this); - } - onMenuButtonClick(event) { - event.preventDefault(); - this.setState({ - activeCard: this.props.index, - showContextMenu: true - }); - } - onLinkClick(event) { - event.preventDefault(); - const altKey = event.altKey, - button = event.button, - ctrlKey = event.ctrlKey, - metaKey = event.metaKey, - shiftKey = event.shiftKey; - - this.props.dispatch(ac.SendToMain({ - type: at.OPEN_LINK, - data: Object.assign(this.props.link, { event: { altKey, button, ctrlKey, metaKey, shiftKey } }) - })); - this.props.dispatch(ac.UserEvent({ - event: "CLICK", - source: this.props.eventSource, - action_position: this.props.index - })); - this.props.dispatch(ac.ImpressionStats({ - source: this.props.eventSource, - click: 0, - incognito: true, - tiles: [{ id: this.props.link.guid, pos: this.props.index }] - })); - } - onMenuUpdate(showContextMenu) { - this.setState({ showContextMenu }); - } - render() { - var _props = this.props; - const index = _props.index, - link = _props.link, - dispatch = _props.dispatch, - contextMenuOptions = _props.contextMenuOptions, - eventSource = _props.eventSource; - - const isContextMenuOpen = this.state.showContextMenu && this.state.activeCard === index; - - var _ref = link.type ? cardContextTypes[link.type] : {}; - - const icon = _ref.icon, - intlID = _ref.intlID; - - - return React.createElement( - "li", - { className: `card-outer${isContextMenuOpen ? " active" : ""}` }, - React.createElement( - "a", - { href: link.url, onClick: this.onLinkClick }, - React.createElement( - "div", - { className: "card" }, - link.image && React.createElement("div", { className: "card-preview-image", style: { backgroundImage: `url(${link.image})` } }), - React.createElement( - "div", - { className: `card-details${link.image ? "" : " no-image"}` }, - link.hostname && React.createElement( - "div", - { className: "card-host-name" }, - link.hostname - ), - React.createElement( - "div", - { className: `card-text${link.image ? "" : " no-image"}${link.hostname ? "" : " no-host-name"}${icon ? "" : " no-context"}` }, - React.createElement( - "h4", - { className: "card-title", dir: "auto" }, - link.title - ), - React.createElement( - "p", - { className: "card-description", dir: "auto" }, - link.description - ) - ), - icon && React.createElement( - "div", - { className: "card-context" }, - React.createElement("span", { className: `card-context-icon icon icon-${icon}` }), - React.createElement( - "div", - { className: "card-context-label" }, - React.createElement(FormattedMessage, { id: intlID, defaultMessage: "Visited" }) - ) - ) - ) - ) - ), - React.createElement( - "button", - { className: "context-menu-button", - onClick: this.onMenuButtonClick }, - React.createElement( - "span", - { className: "sr-only" }, - `Open context menu for ${link.title}` - ) - ), - React.createElement(LinkMenu, { - dispatch: dispatch, - index: index, - source: eventSource, - onUpdate: this.onMenuUpdate, - options: link.context_menu_options || contextMenuOptions, - site: link, - visible: isContextMenuOpen }) - ); - } -} -module.exports = Card; - -/***/ }), -/* 14 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -module.exports = { - history: { - intlID: "type_label_visited", - icon: "historyItem" - }, - bookmark: { - intlID: "type_label_bookmarked", - icon: "bookmark" - }, - trending: { - intlID: "type_label_recommended", - icon: "trending" - }, - now: { - intlID: "type_label_now", - icon: "now" - } -}; - -/***/ }), -/* 15 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -const React = __webpack_require__(0); - -var _require = __webpack_require__(3); - -const connect = _require.connect; - -var _require2 = __webpack_require__(2); - -const FormattedMessage = _require2.FormattedMessage; - -var _require3 = __webpack_require__(1); - -const actionTypes = _require3.actionTypes, - ac = _require3.actionCreators; - -/** - * ConfirmDialog component. - * One primary action button, one cancel button. - * - * Content displayed is controlled by `data` prop the component receives. - * Example: - * data: { - * // Any sort of data needed to be passed around by actions. - * payload: site.url, - * // Primary button SendToMain action. - * action: "DELETE_HISTORY_URL", - * // Primary button USerEvent action. - * userEvent: "DELETE", - * // Array of locale ids to display. - * message_body: ["confirm_history_delete_p1", "confirm_history_delete_notice_p2"], - * // Text for primary button. - * confirm_button_string_id: "menu_action_delete" - * }, - */ - -const ConfirmDialog = React.createClass({ - displayName: "ConfirmDialog", - - getDefaultProps() { - return { - visible: false, - data: {} - }; - }, - - _handleCancelBtn() { - this.props.dispatch({ type: actionTypes.DIALOG_CANCEL }); - this.props.dispatch(ac.UserEvent({ event: actionTypes.DIALOG_CANCEL })); - }, - - _handleConfirmBtn() { - this.props.data.onConfirm.forEach(this.props.dispatch); - }, - - _renderModalMessage() { - const message_body = this.props.data.body_string_id; - - if (!message_body) { - return null; - } - - return React.createElement( - "span", - null, - message_body.map(msg => React.createElement( - "p", - { key: msg }, - React.createElement(FormattedMessage, { id: msg }) - )) - ); - }, - - render() { - if (!this.props.visible) { - return null; - } - - return React.createElement( - "div", - { className: "confirmation-dialog" }, - React.createElement("div", { className: "modal-overlay", onClick: this._handleCancelBtn }), - React.createElement( - "div", - { className: "modal", ref: "modal" }, - React.createElement( - "section", - { className: "modal-message" }, - this._renderModalMessage() - ), - React.createElement( - "section", - { className: "actions" }, - React.createElement( - "button", - { ref: "cancelButton", onClick: this._handleCancelBtn }, - React.createElement(FormattedMessage, { id: "topsites_form_cancel_button" }) - ), - React.createElement( - "button", - { ref: "confirmButton", className: "done", onClick: this._handleConfirmBtn }, - React.createElement(FormattedMessage, { id: this.props.data.confirm_button_string_id }) - ) - ) - ) - ); - } -}); - -module.exports = connect(state => state.Dialog)(ConfirmDialog); -module.exports._unconnected = ConfirmDialog; -module.exports.Dialog = ConfirmDialog; - -/***/ }), -/* 16 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -const React = __webpack_require__(0); - -class ContextMenu extends React.Component { - constructor(props) { - super(props); - this.hideContext = this.hideContext.bind(this); - } - hideContext() { - this.props.onUpdate(false); - } - componentWillMount() { - this.hideContext(); - } - componentDidUpdate(prevProps) { - if (this.props.visible && !prevProps.visible) { - setTimeout(() => { - window.addEventListener("click", this.hideContext); - }, 0); - } - if (!this.props.visible && prevProps.visible) { - window.removeEventListener("click", this.hideContext); - } - } - componentWillUnmount() { - window.removeEventListener("click", this.hideContext); - } - render() { - return React.createElement( - "span", - { hidden: !this.props.visible, className: "context-menu" }, - React.createElement( - "ul", - { role: "menu", className: "context-menu-list" }, - this.props.options.map((option, i) => option.type === "separator" ? React.createElement("li", { key: i, className: "separator" }) : React.createElement(ContextMenuItem, { key: i, option: option, hideContext: this.hideContext })) - ) - ); - } -} - -class ContextMenuItem extends React.Component { - constructor(props) { - super(props); - this.onClick = this.onClick.bind(this); - this.onKeyDown = this.onKeyDown.bind(this); - } - onClick() { - this.props.hideContext(); - this.props.option.onClick(); - } - onKeyDown(event) { - const option = this.props.option; - - switch (event.key) { - case "Tab": - // tab goes down in context menu, shift + tab goes up in context menu - // if we're on the last item, one more tab will close the context menu - // similarly, if we're on the first item, one more shift + tab will close it - if (event.shiftKey && option.first || !event.shiftKey && option.last) { - this.props.hideContext(); - } - break; - case "Enter": - this.props.hideContext(); - option.onClick(); - break; - } - } - render() { - const option = this.props.option; - - return React.createElement( - "li", - { role: "menuitem", className: "context-menu-item" }, - React.createElement( - "a", - { onClick: this.onClick, onKeyDown: this.onKeyDown, tabIndex: "0" }, - option.icon && React.createElement("span", { className: `icon icon-spacer icon-${option.icon}` }), - option.label - ) - ); - } -} - -module.exports = ContextMenu; -module.exports.ContextMenu = ContextMenu; -module.exports.ContextMenuItem = ContextMenuItem; - -/***/ }), -/* 17 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -const React = __webpack_require__(0); - -var _require = __webpack_require__(3); - -const connect = _require.connect; - -var _require2 = __webpack_require__(2); - -const FormattedMessage = _require2.FormattedMessage; - -var _require3 = __webpack_require__(1); - -const at = _require3.actionTypes, - ac = _require3.actionCreators; - -/** - * Manual migration component used to start the profile import wizard. - * Message is presented temporarily and will go away if: - * 1. User clicks "No Thanks" - * 2. User completed the data import - * 3. After 3 active days - * 4. User clicks "Cancel" on the import wizard (currently not implemented). - */ - -class ManualMigration extends React.Component { - constructor(props) { - super(props); - this.onLaunchTour = this.onLaunchTour.bind(this); - this.onCancelTour = this.onCancelTour.bind(this); - } - onLaunchTour() { - this.props.dispatch(ac.SendToMain({ type: at.MIGRATION_START })); - this.props.dispatch(ac.UserEvent({ event: at.MIGRATION_START })); - } - - onCancelTour() { - this.props.dispatch(ac.SendToMain({ type: at.MIGRATION_CANCEL })); - this.props.dispatch(ac.UserEvent({ event: at.MIGRATION_CANCEL })); - } - - render() { - return React.createElement( - "div", - { className: "manual-migration-container" }, - React.createElement( - "p", - null, - React.createElement("span", { className: "icon icon-info" }), - React.createElement(FormattedMessage, { id: "manual_migration_explanation" }) - ), - React.createElement( - "div", - { className: "manual-migration-actions actions" }, - React.createElement( - "button", - { onClick: this.onCancelTour }, - React.createElement(FormattedMessage, { id: "manual_migration_cancel_button" }) - ), - React.createElement( - "button", - { className: "done", onClick: this.onLaunchTour }, - React.createElement(FormattedMessage, { id: "manual_migration_import_button" }) - ) - ) - ); - } -} - -module.exports = connect()(ManualMigration); -module.exports._unconnected = ManualMigration; - -/***/ }), -/* 18 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -const React = __webpack_require__(0); - -var _require = __webpack_require__(3); - -const connect = _require.connect; - -var _require2 = __webpack_require__(2); - -const injectIntl = _require2.injectIntl, - FormattedMessage = _require2.FormattedMessage; - -var _require3 = __webpack_require__(1); - -const ac = _require3.actionCreators; - - -const PreferencesInput = props => React.createElement( - "section", - null, - React.createElement("input", { type: "checkbox", id: props.prefName, name: props.prefName, checked: props.value, onChange: props.onChange, className: props.className }), - React.createElement( - "label", - { htmlFor: props.prefName }, - React.createElement(FormattedMessage, { id: props.titleStringId, values: props.titleStringValues }) - ), - props.descStringId && React.createElement( - "p", - { className: "prefs-input-description" }, - React.createElement(FormattedMessage, { id: props.descStringId }) - ) -); - -class PreferencesPane extends React.Component { - constructor(props) { - super(props); - this.state = { visible: false }; - this.handleClickOutside = this.handleClickOutside.bind(this); - this.handleChange = this.handleChange.bind(this); - this.togglePane = this.togglePane.bind(this); - - // TODO This is temporary until sections register their PreferenceInput component automatically - const optionJSON = props.Prefs.values["feeds.section.topstories.options"]; - if (optionJSON) { - try { - this.topStoriesOptions = JSON.parse(optionJSON); - } catch (e) { - console.error("Problem parsing feeds.section.topstories.options", e); // eslint-disable-line no-console - } - } - } - componentDidMount() { - document.addEventListener("click", this.handleClickOutside); - } - componentWillUnmount() { - document.removeEventListener("click", this.handleClickOutside); - } - handleClickOutside(event) { - // if we are showing the sidebar and there is a click outside, close it. - if (this.state.visible && !this.refs.wrapper.contains(event.target)) { - this.togglePane(); - } - } - handleChange(event) { - const target = event.target; - this.props.dispatch(ac.SetPref(target.name, target.checked)); - } - togglePane() { - this.setState({ visible: !this.state.visible }); - const event = this.state.visible ? "CLOSE_NEWTAB_PREFS" : "OPEN_NEWTAB_PREFS"; - this.props.dispatch(ac.UserEvent({ event })); - } - render() { - const props = this.props; - const prefs = props.Prefs.values; - const isVisible = this.state.visible; - return React.createElement( - "div", - { className: "prefs-pane-wrapper", ref: "wrapper" }, - React.createElement( - "div", - { className: "prefs-pane-button" }, - React.createElement("button", { - className: `prefs-button icon ${isVisible ? "icon-dismiss" : "icon-settings"}`, - title: props.intl.formatMessage({ id: isVisible ? "settings_pane_done_button" : "settings_pane_button_label" }), - onClick: this.togglePane }) - ), - React.createElement( - "div", - { className: "prefs-pane" }, - React.createElement( - "div", - { className: `sidebar ${isVisible ? "" : "hidden"}` }, - React.createElement( - "div", - { className: "prefs-modal-inner-wrapper" }, - React.createElement( - "h1", - null, - React.createElement(FormattedMessage, { id: "settings_pane_header" }) - ), - React.createElement( - "p", - null, - React.createElement(FormattedMessage, { id: "settings_pane_body" }) - ), - React.createElement(PreferencesInput, { className: "showSearch", prefName: "showSearch", value: prefs.showSearch, onChange: this.handleChange, - titleStringId: "settings_pane_search_header", descStringId: "settings_pane_search_body" }), - React.createElement(PreferencesInput, { className: "showTopSites", prefName: "showTopSites", value: prefs.showTopSites, onChange: this.handleChange, - titleStringId: "settings_pane_topsites_header", descStringId: "settings_pane_topsites_body" }), - this.topStoriesOptions && !this.topStoriesOptions.hidden && React.createElement(PreferencesInput, { className: "showTopStories", prefName: "feeds.section.topstories", - value: prefs["feeds.section.topstories"], onChange: this.handleChange, - titleStringId: "header_recommended_by", titleStringValues: { provider: this.topStoriesOptions.provider_name }, - descStringId: this.topStoriesOptions.provider_description }) - ), - React.createElement( - "section", - { className: "actions" }, - React.createElement( - "button", - { className: "done", onClick: this.togglePane }, - React.createElement(FormattedMessage, { id: "settings_pane_done_button" }) - ) - ) - ) - ) - ); - } -} - -module.exports = connect(state => ({ Prefs: state.Prefs }))(injectIntl(PreferencesPane)); -module.exports.PreferencesPane = PreferencesPane; -module.exports.PreferencesInput = PreferencesInput; - -/***/ }), -/* 19 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -/* globals ContentSearchUIController */ - - -const React = __webpack_require__(0); - -var _require = __webpack_require__(3); - -const connect = _require.connect; - -var _require2 = __webpack_require__(2); - -const FormattedMessage = _require2.FormattedMessage, - injectIntl = _require2.injectIntl; - -var _require3 = __webpack_require__(1); - -const ac = _require3.actionCreators; - - -class Search extends React.Component { - constructor(props) { - super(props); - this.onClick = this.onClick.bind(this); - this.onInputMount = this.onInputMount.bind(this); - } - - handleEvent(event) { - // Also track search events with our own telemetry - if (event.detail.type === "Search") { - this.props.dispatch(ac.UserEvent({ event: "SEARCH" })); - } - } - onClick(event) { - this.controller.search(event); - } - onInputMount(input) { - if (input) { - // The first "newtab" parameter here is called the "healthReportKey" and needs - // to be "newtab" so that BrowserUsageTelemetry.jsm knows to handle events with - // this name, and can add the appropriate telemetry probes for search. Without the - // correct name, certain tests like browser_UsageTelemetry_content.js will fail (See - // github ticket #2348 for more details) - this.controller = new ContentSearchUIController(input, input.parentNode, "newtab", "newtab"); - addEventListener("ContentSearchClient", this); - } else { - this.controller = null; - removeEventListener("ContentSearchClient", this); - } - } - - /* - * Do not change the ID on the input field, as legacy newtab code - * specifically looks for the id 'newtab-search-text' on input fields - * in order to execute searches in various tests - */ - render() { - return React.createElement( - "form", - { className: "search-wrapper" }, - React.createElement( - "label", - { htmlFor: "newtab-search-text", className: "search-label" }, - React.createElement( - "span", - { className: "sr-only" }, - React.createElement(FormattedMessage, { id: "search_web_placeholder" }) - ) - ), - React.createElement("input", { - id: "newtab-search-text", - maxLength: "256", - placeholder: this.props.intl.formatMessage({ id: "search_web_placeholder" }), - ref: this.onInputMount, - title: this.props.intl.formatMessage({ id: "search_web_placeholder" }), - type: "search" }), - React.createElement( - "button", - { - className: "search-button", - onClick: this.onClick, - title: this.props.intl.formatMessage({ id: "search_button" }) }, - React.createElement( - "span", - { className: "sr-only" }, - React.createElement(FormattedMessage, { id: "search_button" }) - ) - ) - ); - } -} - -module.exports = connect()(injectIntl(Search)); -module.exports._unconnected = Search; - -/***/ }), -/* 20 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -/* WEBPACK VAR INJECTION */(function(global) { - -var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; - -const React = __webpack_require__(0); - -var _require = __webpack_require__(3); - -const connect = _require.connect; - -var _require2 = __webpack_require__(2); - -const injectIntl = _require2.injectIntl, - FormattedMessage = _require2.FormattedMessage; - -const Card = __webpack_require__(13); -const Topics = __webpack_require__(22); - -var _require3 = __webpack_require__(1); - -const ac = _require3.actionCreators; - - -const VISIBLE = "visible"; -const VISIBILITY_CHANGE_EVENT = "visibilitychange"; - -class Section extends React.Component { - constructor(props) { - super(props); - this.onInfoEnter = this.onInfoEnter.bind(this); - this.onInfoLeave = this.onInfoLeave.bind(this); - this.state = { infoActive: false }; - } - - /** - * Take a truthy value to conditionally change the infoActive state. - */ - _setInfoState(nextActive) { - const infoActive = !!nextActive; - if (infoActive !== this.state.infoActive) { - this.setState({ infoActive }); - } - } - - onInfoEnter() { - // We're getting focus or hover, so info state should be true if not yet. - this._setInfoState(true); - } - - onInfoLeave(event) { - // We currently have an active (true) info state, so keep it true only if we - // have a related event target that is contained "within" the current target - // (section-info-option) as itself or a descendant. Set to false otherwise. - this._setInfoState(event && event.relatedTarget && (event.relatedTarget === event.currentTarget || event.relatedTarget.compareDocumentPosition(event.currentTarget) & Node.DOCUMENT_POSITION_CONTAINS)); - } - - getFormattedMessage(message) { - return typeof message === "string" ? React.createElement( - "span", - null, - message - ) : React.createElement(FormattedMessage, message); - } - - _dispatchImpressionStats() { - const props = this.props; - - const maxCards = 3 * props.maxRows; - props.dispatch(ac.ImpressionStats({ - source: props.eventSource, - tiles: props.rows.slice(0, maxCards).map(link => ({ id: link.guid })) - })); - } - - // This sends an event when a user sees a set of new content. If content - // changes while the page is hidden (i.e. preloaded or on a hidden tab), - // only send the event if the page becomes visible again. - sendImpressionStatsOrAddListener() { - const props = this.props; - - - if (!props.dispatch) { - return; - } - - if (props.document.visibilityState === VISIBLE) { - this._dispatchImpressionStats(); - } else { - // We should only ever send the latest impression stats ping, so remove any - // older listeners. - if (this._onVisibilityChange) { - props.document.removeEventListener(VISIBILITY_CHANGE_EVENT, this._onVisibilityChange); - } - - // When the page becoems visible, send the impression stats ping. - this._onVisibilityChange = () => { - if (props.document.visibilityState === VISIBLE) { - this._dispatchImpressionStats(); - props.document.removeEventListener(VISIBILITY_CHANGE_EVENT, this._onVisibilityChange); - } - }; - props.document.addEventListener(VISIBILITY_CHANGE_EVENT, this._onVisibilityChange); - } - } - - componentDidMount() { - if (this.props.rows.length) { - this.sendImpressionStatsOrAddListener(); - } - } - - componentDidUpdate(prevProps) { - const props = this.props; - - if ( - // Don't send impression stats for the empty state - props.rows.length && - // We only want to send impression stats if the content of the cards has changed - props.rows !== prevProps.rows) { - this.sendImpressionStatsOrAddListener(); - } - } - - render() { - var _props = this.props; - const id = _props.id, - eventSource = _props.eventSource, - title = _props.title, - icon = _props.icon, - rows = _props.rows, - infoOption = _props.infoOption, - emptyState = _props.emptyState, - dispatch = _props.dispatch, - maxRows = _props.maxRows, - contextMenuOptions = _props.contextMenuOptions, - intl = _props.intl; - - const maxCards = 3 * maxRows; - const initialized = rows && rows.length > 0; - const shouldShowTopics = id === "TopStories" && this.props.topics && this.props.topics.length > 0 && this.props.read_more_endpoint; - - const infoOptionIconA11yAttrs = { - "aria-haspopup": "true", - "aria-controls": "info-option", - "aria-expanded": this.state.infoActive ? "true" : "false", - "role": "note", - "tabIndex": 0 - }; - - const sectionInfoTitle = intl.formatMessage({ id: "section_info_option" }); - - //
<-- React component - //
<-- HTML5 element - return React.createElement( - "section", - null, - React.createElement( - "div", - { className: "section-top-bar" }, - React.createElement( - "h3", - { className: "section-title" }, - icon && icon.startsWith("moz-extension://") ? React.createElement("span", { className: "icon icon-small-spacer", style: { "background-image": `url('${icon}')` } }) : React.createElement("span", { className: `icon icon-small-spacer icon-${icon || "webextension"}` }), - this.getFormattedMessage(title) - ), - infoOption && React.createElement( - "span", - { className: "section-info-option", - onBlur: this.onInfoLeave, - onFocus: this.onInfoEnter, - onMouseOut: this.onInfoLeave, - onMouseOver: this.onInfoEnter }, - React.createElement("img", _extends({ className: "info-option-icon", title: sectionInfoTitle - }, infoOptionIconA11yAttrs)), - React.createElement( - "div", - { className: "info-option" }, - infoOption.header && React.createElement( - "div", - { className: "info-option-header", role: "heading" }, - this.getFormattedMessage(infoOption.header) - ), - infoOption.body && React.createElement( - "p", - { className: "info-option-body" }, - this.getFormattedMessage(infoOption.body) - ), - infoOption.link && React.createElement( - "a", - { href: infoOption.link.href, target: "_blank", rel: "noopener noreferrer", className: "info-option-link" }, - this.getFormattedMessage(infoOption.link.title || infoOption.link) - ) - ) - ) - ), - React.createElement( - "ul", - { className: "section-list", style: { padding: 0 } }, - rows.slice(0, maxCards).map((link, index) => link && React.createElement(Card, { index: index, dispatch: dispatch, link: link, contextMenuOptions: contextMenuOptions, eventSource: eventSource })) - ), - !initialized && React.createElement( - "div", - { className: "section-empty-state" }, - React.createElement( - "div", - { className: "empty-state" }, - React.createElement("img", { className: `empty-state-icon icon icon-${emptyState.icon}` }), - React.createElement( - "p", - { className: "empty-state-message" }, - this.getFormattedMessage(emptyState.message) - ) - ) - ), - shouldShowTopics && React.createElement(Topics, { topics: this.props.topics, read_more_endpoint: this.props.read_more_endpoint }) - ); - } -} - -Section.defaultProps = { document: global.document }; - -const SectionIntl = injectIntl(Section); - -class Sections extends React.Component { - render() { - const sections = this.props.Sections; - return React.createElement( - "div", - { className: "sections-list" }, - sections.map(section => React.createElement(SectionIntl, _extends({ key: section.id }, section, { dispatch: this.props.dispatch }))) - ); - } -} - -module.exports = connect(state => ({ Sections: state.Sections }))(Sections); -module.exports._unconnected = Sections; -module.exports.SectionIntl = SectionIntl; -module.exports._unconnectedSection = Section; -/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(6))) - -/***/ }), -/* 21 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -const React = __webpack_require__(0); - -var _require = __webpack_require__(3); - -const connect = _require.connect; - -var _require2 = __webpack_require__(2); - -const FormattedMessage = _require2.FormattedMessage, - injectIntl = _require2.injectIntl; - -const LinkMenu = __webpack_require__(4); - -var _require3 = __webpack_require__(1); - -const ac = _require3.actionCreators, - at = _require3.actionTypes; - -var _require4 = __webpack_require__(5); - -const perfSvc = _require4.perfService; - -const TOP_SITES_SOURCE = "TOP_SITES"; -const TOP_SITES_CONTEXT_MENU_OPTIONS = ["CheckPinTopSite", "Separator", "OpenInNewWindow", "OpenInPrivateWindow", "Separator", "BlockUrl", "DeleteUrl"]; - -class TopSite extends React.Component { - constructor(props) { - super(props); - this.state = { showContextMenu: false, activeTile: null }; - this.onLinkClick = this.onLinkClick.bind(this); - this.onMenuButtonClick = this.onMenuButtonClick.bind(this); - this.onMenuUpdate = this.onMenuUpdate.bind(this); - this.onDismissButtonClick = this.onDismissButtonClick.bind(this); - this.onPinButtonClick = this.onPinButtonClick.bind(this); - } - toggleContextMenu(event, index) { - this.setState({ - activeTile: index, - showContextMenu: true - }); - } - userEvent(event) { - this.props.dispatch(ac.UserEvent({ - event, - source: TOP_SITES_SOURCE, - action_position: this.props.index - })); - } - onLinkClick(ev) { - if (this.props.editMode) { - // Ignore clicks if we are in the edit modal. - ev.preventDefault(); - return; - } - this.userEvent("CLICK"); - } - onMenuButtonClick(event) { - event.preventDefault(); - this.toggleContextMenu(event, this.props.index); - } - onMenuUpdate(showContextMenu) { - this.setState({ showContextMenu }); - } - onDismissButtonClick() { - const link = this.props.link; - - if (link.isPinned) { - this.props.dispatch(ac.SendToMain({ - type: at.TOP_SITES_UNPIN, - data: { site: { url: link.url } } - })); - } - this.props.dispatch(ac.SendToMain({ - type: at.BLOCK_URL, - data: link.url - })); - this.userEvent("BLOCK"); - } - onPinButtonClick() { - var _props = this.props; - const link = _props.link, - index = _props.index; - - if (link.isPinned) { - this.props.dispatch(ac.SendToMain({ - type: at.TOP_SITES_UNPIN, - data: { site: { url: link.url } } - })); - this.userEvent("UNPIN"); - } else { - this.props.dispatch(ac.SendToMain({ - type: at.TOP_SITES_PIN, - data: { site: { url: link.url }, index } - })); - this.userEvent("PIN"); - } - } - render() { - var _props2 = this.props; - const link = _props2.link, - index = _props2.index, - dispatch = _props2.dispatch, - editMode = _props2.editMode; - - const isContextMenuOpen = this.state.showContextMenu && this.state.activeTile === index; - const title = link.hostname; - const topSiteOuterClassName = `top-site-outer${isContextMenuOpen ? " active" : ""}`; - const tippyTopIcon = link.tippyTopIcon; - - let imageClassName; - let imageStyle; - if (tippyTopIcon) { - imageClassName = "tippy-top-icon"; - imageStyle = { - backgroundColor: link.backgroundColor, - backgroundImage: `url(${tippyTopIcon})` - }; - } else { - imageClassName = `screenshot${link.screenshot ? " active" : ""}`; - imageStyle = { backgroundImage: link.screenshot ? `url(${link.screenshot})` : "none" }; - } - return React.createElement( - "li", - { className: topSiteOuterClassName, key: link.guid || link.url }, - React.createElement( - "a", - { href: link.url, onClick: this.onLinkClick }, - React.createElement( - "div", - { className: "tile", "aria-hidden": true }, - React.createElement( - "span", - { className: "letter-fallback" }, - title[0] - ), - React.createElement("div", { className: imageClassName, style: imageStyle }) - ), - React.createElement( - "div", - { className: `title ${link.isPinned ? "pinned" : ""}` }, - link.isPinned && React.createElement("div", { className: "icon icon-pin-small" }), - React.createElement( - "span", - { dir: "auto" }, - title - ) - ) - ), - !editMode && React.createElement( - "div", - null, - React.createElement( - "button", - { className: "context-menu-button", onClick: this.onMenuButtonClick }, - React.createElement( - "span", - { className: "sr-only" }, - `Open context menu for ${title}` - ) - ), - React.createElement(LinkMenu, { - dispatch: dispatch, - index: index, - onUpdate: this.onMenuUpdate, - options: TOP_SITES_CONTEXT_MENU_OPTIONS, - site: link, - source: TOP_SITES_SOURCE, - visible: isContextMenuOpen }) - ), - editMode && React.createElement( - "div", - { className: "edit-menu" }, - React.createElement("button", { - className: `icon icon-${link.isPinned ? "unpin" : "pin"}`, - title: this.props.intl.formatMessage({ id: `edit_topsites_${link.isPinned ? "unpin" : "pin"}_button` }), - onClick: this.onPinButtonClick }), - React.createElement("button", { - className: "icon icon-dismiss", - title: this.props.intl.formatMessage({ id: "edit_topsites_dismiss_button" }), - onClick: this.onDismissButtonClick }) - ) - ); - } -} - -TopSite.defaultProps = { editMode: false }; - -/** - * A proxy class that uses double requestAnimationFrame from - * componentDidMount to dispatch a SAVE_SESSION_PERF_DATA to the main procsess - * after the paint. - * - * This uses two callbacks because, after one callback, this part of the tree - * may have rendered but not yet reflowed. This strategy is modeled after - * https://stackoverflow.com/a/34999925 but uses a double rFA because - * we want to get to the closest reliable paint for measuring, and - * setTimeout is often throttled or queued by browsers in ways that could - * make it lag too long. - * - * XXX Should be made more generic by using this.props.children, or potentially - * even split out into a higher-order component to wrap whatever. - * - * @class TopSitesPerfTimer - * @extends {React.Component} - */ -class TopSitesPerfTimer extends React.Component { - constructor(props) { - super(props); - // Just for test dependency injection: - this.perfSvc = this.props.perfSvc || perfSvc; - - this._sendPaintedEvent = this._sendPaintedEvent.bind(this); - this._timestampHandled = false; - } - - componentDidMount() { - this._maybeSendPaintedEvent(); - } - - componentDidUpdate() { - this._maybeSendPaintedEvent(); - } - - /** - * Call the given callback after the upcoming frame paints. - * - * @note Both setTimeout and requestAnimationFrame are throttled when the page - * is hidden, so this callback may get called up to a second or so after the - * requestAnimationFrame "paint" for hidden tabs. - * - * Newtabs hidden while loading will presumably be fairly rare (other than - * preloaded tabs, which we will be filtering out on the server side), so such - * cases should get lost in the noise. - * - * If we decide that it's important to find out when something that's hidden - * has "painted", however, another option is to post a message to this window. - * That should happen even faster than setTimeout, and, at least as of this - * writing, it's not throttled in hidden windows in Firefox. - * - * @param {Function} callback - * - * @returns void - */ - _afterFramePaint(callback) { - requestAnimationFrame(() => setTimeout(callback, 0)); - } - - _maybeSendPaintedEvent() { - // We don't want this to ever happen, but sometimes it does. And when it - // does (typically on the first newtab at startup time calling - // componentDidMount), the paint(s) we care about will be later (eg - // in a subsequent componentDidUpdate). - if (!this.props.TopSites.initialized) { - // XXX should send bad event - return; - } - - // If we've already handled a timestamp, don't do it again - if (this._timestampHandled) { - return; - } - - // And if we haven't, we're doing so now, so remember that. Even if - // something goes wrong in the callback, we can't try again, as we'd be - // sending back the wrong data, and we have to do it here, so that other - // calls to this method while waiting for the next frame won't also try to - // handle handle it. - this._timestampHandled = true; - - this._afterFramePaint(this._sendPaintedEvent); - } - - _sendPaintedEvent() { - this.perfSvc.mark("topsites_first_painted_ts"); - - try { - let topsites_first_painted_ts = this.perfSvc.getMostRecentAbsMarkStartByName("topsites_first_painted_ts"); - - this.props.dispatch(ac.SendToMain({ - type: at.SAVE_SESSION_PERF_DATA, - data: { topsites_first_painted_ts } - })); - } catch (ex) { - // If this failed, it's likely because the `privacy.resistFingerprinting` - // pref is true. We should at least not blow up, and should continue - // to set this._timestampHandled to avoid going through this again. - } - } - - render() { - return React.createElement(TopSites, this.props); - } -} - -const TopSites = props => React.createElement( - "section", - { className: "top-sites" }, - React.createElement( - "h3", - { className: "section-title" }, - React.createElement("span", { className: `icon icon-small-spacer icon-topsites` }), - React.createElement(FormattedMessage, { id: "header_top_sites" }) - ), - React.createElement( - "ul", - { className: "top-sites-list" }, - props.TopSites.rows.map((link, index) => link && React.createElement(TopSite, { - key: link.guid || link.url, - dispatch: props.dispatch, - link: link, - index: index, - intl: props.intl })) - ), - React.createElement(TopSitesEditIntl, props) -); - -class TopSitesEdit extends React.Component { - constructor(props) { - super(props); - this.state = { showEditModal: false }; - this.onEditButtonClick = this.onEditButtonClick.bind(this); - } - onEditButtonClick() { - this.setState({ showEditModal: !this.state.showEditModal }); - const event = this.state.showEditModal ? "TOP_SITES_EDIT_OPEN" : "TOP_SITES_EDIT_CLOSE"; - this.props.dispatch(ac.UserEvent({ - source: TOP_SITES_SOURCE, - event - })); - } - render() { - return React.createElement( - "div", - { className: "edit-topsites-wrapper" }, - React.createElement( - "div", - { className: "edit-topsites-button" }, - React.createElement( - "button", - { - className: "edit", - title: this.props.intl.formatMessage({ id: "edit_topsites_button_label" }), - onClick: this.onEditButtonClick }, - React.createElement(FormattedMessage, { id: "edit_topsites_button_text" }) - ) - ), - this.state.showEditModal && React.createElement( - "div", - { className: "edit-topsites" }, - React.createElement("div", { className: "modal-overlay" }), - React.createElement( - "div", - { className: "modal" }, - React.createElement( - "section", - { className: "edit-topsites-inner-wrapper" }, - React.createElement( - "h3", - { className: "section-title" }, - React.createElement("span", { className: `icon icon-small-spacer icon-topsites` }), - React.createElement(FormattedMessage, { id: "header_top_sites" }) - ), - React.createElement( - "ul", - { className: "top-sites-list" }, - this.props.TopSites.rows.map((link, index) => link && React.createElement(TopSite, { - key: link.guid || link.url, - dispatch: this.props.dispatch, - link: link, - index: index, - intl: this.props.intl, - editMode: true })) - ) - ), - React.createElement( - "section", - { className: "actions" }, - React.createElement( - "button", - { className: "done", onClick: this.onEditButtonClick }, - React.createElement(FormattedMessage, { id: "edit_topsites_done_button" }) - ) - ) - ) - ) - ); - } -} - -const TopSitesEditIntl = injectIntl(TopSitesEdit); - -module.exports = connect(state => ({ TopSites: state.TopSites }))(TopSitesPerfTimer); -module.exports._unconnected = TopSitesPerfTimer; -module.exports.TopSite = TopSite; -module.exports.TopSites = TopSites; -module.exports.TopSitesEdit = TopSitesEdit; - -/***/ }), -/* 22 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -const React = __webpack_require__(0); - -var _require = __webpack_require__(2); - -const FormattedMessage = _require.FormattedMessage; - - -class Topic extends React.Component { - render() { - var _props = this.props; - const url = _props.url, - name = _props.name; - - return React.createElement( - "li", - null, - React.createElement( - "a", - { key: name, className: "topic-link", href: url }, - name - ) - ); - } -} - -class Topics extends React.Component { - render() { - var _props2 = this.props; - const topics = _props2.topics, - read_more_endpoint = _props2.read_more_endpoint; - - return React.createElement( - "div", - { className: "topic" }, - React.createElement( - "span", - null, - React.createElement(FormattedMessage, { id: "pocket_read_more" }) - ), - React.createElement( - "ul", - null, - topics.map(t => React.createElement(Topic, { key: t.name, url: t.url, name: t.name })) - ), - React.createElement( - "a", - { className: "topic-read-more", href: read_more_endpoint }, - React.createElement(FormattedMessage, { id: "pocket_read_even_more" }), - React.createElement("span", { className: "topic-read-more-logo" }) - ) - ); - } -} - -module.exports = Topics; -module.exports._unconnected = Topics; -module.exports.Topic = Topic; - -/***/ }), -/* 23 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -var _require = __webpack_require__(1); - -const at = _require.actionTypes, - ac = _require.actionCreators; - -/** - * List of functions that return items that can be included as menu options in a - * LinkMenu. All functions take the site as the first parameter, and optionally - * the index of the site. - */ - -module.exports = { - Separator: () => ({ type: "separator" }), - RemoveBookmark: site => ({ - id: "menu_action_remove_bookmark", - icon: "bookmark-remove", - action: ac.SendToMain({ - type: at.DELETE_BOOKMARK_BY_ID, - data: site.bookmarkGuid - }), - userEvent: "BOOKMARK_DELETE" - }), - AddBookmark: site => ({ - id: "menu_action_bookmark", - icon: "bookmark", - action: ac.SendToMain({ - type: at.BOOKMARK_URL, - data: { url: site.url, title: site.title } - }), - userEvent: "BOOKMARK_ADD" - }), - OpenInNewWindow: site => ({ - id: "menu_action_open_new_window", - icon: "new-window", - action: ac.SendToMain({ - type: at.OPEN_NEW_WINDOW, - data: { url: site.url, referrer: site.referrer } - }), - userEvent: "OPEN_NEW_WINDOW" - }), - OpenInPrivateWindow: site => ({ - id: "menu_action_open_private_window", - icon: "new-window-private", - action: ac.SendToMain({ - type: at.OPEN_PRIVATE_WINDOW, - data: { url: site.url, referrer: site.referrer } - }), - userEvent: "OPEN_PRIVATE_WINDOW" - }), - BlockUrl: (site, index, eventSource) => ({ - id: "menu_action_dismiss", - icon: "dismiss", - action: ac.SendToMain({ - type: at.BLOCK_URL, - data: site.url - }), - impression: ac.ImpressionStats({ - source: eventSource, - block: 0, - incognito: true, - tiles: [{ id: site.guid, pos: index }] - }), - userEvent: "BLOCK" - }), - DeleteUrl: site => ({ - id: "menu_action_delete", - icon: "delete", - action: { - type: at.DIALOG_OPEN, - data: { - onConfirm: [ac.SendToMain({ type: at.DELETE_HISTORY_URL, data: site.url }), ac.UserEvent({ event: "DELETE" })], - body_string_id: ["confirm_history_delete_p1", "confirm_history_delete_notice_p2"], - confirm_button_string_id: "menu_action_delete" - } - }, - userEvent: "DIALOG_OPEN" - }), - PinTopSite: (site, index) => ({ - id: "menu_action_pin", - icon: "pin", - action: ac.SendToMain({ - type: at.TOP_SITES_PIN, - data: { site: { url: site.url }, index } - }), - userEvent: "PIN" - }), - UnpinTopSite: site => ({ - id: "menu_action_unpin", - icon: "unpin", - action: ac.SendToMain({ - type: at.TOP_SITES_UNPIN, - data: { site: { url: site.url } } - }), - userEvent: "UNPIN" - }), - SaveToPocket: (site, index, eventSource) => ({ - id: "menu_action_save_to_pocket", - icon: "pocket", - action: ac.SendToMain({ - type: at.SAVE_TO_POCKET, - data: { site: { url: site.url, title: site.title } } - }), - impression: ac.ImpressionStats({ - source: eventSource, - pocket: 0, - incognito: true, - tiles: [{ id: site.guid, pos: index }] - }), - userEvent: "SAVE_TO_POCKET" - }) -}; - -module.exports.CheckBookmark = site => site.bookmarkGuid ? module.exports.RemoveBookmark(site) : module.exports.AddBookmark(site); -module.exports.CheckPinTopSite = (site, index) => site.isPinned ? module.exports.UnpinTopSite(site) : module.exports.PinTopSite(site, index); - -/***/ }), -/* 24 */ -/***/ (function(module, exports) { - -module.exports = Redux; - -/***/ }), -/* 25 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -const React = __webpack_require__(0); -const ReactDOM = __webpack_require__(12); -const Base = __webpack_require__(7); - -var _require = __webpack_require__(3); - -const Provider = _require.Provider; - -const initStore = __webpack_require__(9); - -var _require2 = __webpack_require__(11); - -const reducers = _require2.reducers; - -const DetectUserSessionStart = __webpack_require__(8); - -var _require3 = __webpack_require__(10); - -const addSnippetsSubscriber = _require3.addSnippetsSubscriber; - - -new DetectUserSessionStart().sendEventOrAddListener(); - -const store = initStore(reducers); - -ReactDOM.render(React.createElement( - Provider, - { store: store }, - React.createElement(Base, null) -), document.getElementById("root")); - -addSnippetsSubscriber(store); - /***/ }) /******/ ]); \ No newline at end of file diff --git a/browser/extensions/activity-stream/data/content/activity-stream.css b/browser/extensions/activity-stream/data/content/activity-stream.css index 81d7d9432a18..05ffc6957a50 100644 --- a/browser/extensions/activity-stream/data/content/activity-stream.css +++ b/browser/extensions/activity-stream/data/content/activity-stream.css @@ -28,15 +28,17 @@ input { background-size: 16px; background-position: center center; background-repeat: no-repeat; - vertical-align: middle; } + vertical-align: middle; + fill: rgba(12, 12, 13, 0.8); + -moz-context-properties: fill; } .icon.icon-spacer { margin-inline-end: 8px; } .icon.icon-small-spacer { margin-inline-end: 6px; } - .icon.icon-bookmark { - background-image: url("assets/glyph-bookmark-16.svg"); } - .icon.icon-bookmark-remove { - background-image: url("assets/glyph-bookmark-remove-16.svg"); } + .icon.icon-bookmark-added { + background-image: url("chrome://browser/skin/bookmark.svg"); } + .icon.icon-bookmark-hollow { + background-image: url("chrome://browser/skin/bookmark-hollow.svg"); } .icon.icon-delete { background-image: url("assets/glyph-delete-16.svg"); } .icon.icon-dismiss { @@ -46,7 +48,7 @@ input { .icon.icon-new-window { background-image: url("assets/glyph-newWindow-16.svg"); } .icon.icon-new-window-private { - background-image: url("assets/glyph-newWindow-private-16.svg"); } + background-image: url("chrome://browser/skin/privateBrowsing.svg"); } .icon.icon-settings { background-image: url("assets/glyph-settings-16.svg"); } .icon.icon-pin { @@ -60,7 +62,7 @@ input { .icon.icon-trending { background-image: url("assets/glyph-trending-16.svg"); } .icon.icon-now { - background-image: url("assets/glyph-now-16.svg"); } + background-image: url("chrome://browser/skin/history.svg"); } .icon.icon-topsites { background-image: url("assets/glyph-topsites-16.svg"); } .icon.icon-pin-small { @@ -79,7 +81,7 @@ body, height: 100%; } body { - background: #F6F6F8; + background: #EDEDF0; color: #0C0C0D; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Ubuntu', 'Helvetica Neue', sans-serif; font-size: 16px; } @@ -89,10 +91,10 @@ h2 { font-weight: normal; } a { - color: #00AFF7; + color: #008EA4; text-decoration: none; } a:hover { - color: #2bc1ff; } + color: #00C8D7; } .sr-only { position: absolute; @@ -105,7 +107,7 @@ a { border: 0; } .inner-border { - border: 1px solid rgba(0, 0, 0, 0.1); + border: 1px solid #D7D7DB; border-radius: 3px; position: absolute; top: 0; @@ -129,32 +131,32 @@ a { animation: fadeIn 0.2s; } .actions { - border-top: solid 1px rgba(0, 0, 0, 0.1); + border-top: 1px solid #D7D7DB; display: flex; flex-direction: row; margin: 0; padding: 15px 25px; justify-content: flex-start; } .actions button { - background: #FBFBFB; - border: solid 1px #BFBFBF; + background: #F9F9FA; + border: 1px solid #B1B1B3; border-radius: 5px; color: #0C0C0D; cursor: pointer; padding: 10px 30px; } .actions button:hover { - box-shadow: 0 0 0 5px rgba(0, 0, 0, 0.1); + box-shadow: 0 0 0 5px #D7D7DB; transition: box-shadow 150ms; } .actions button.done { - background: #0695F9; - border: solid 1px #1677CF; + background: #0A84FF; + border: solid 1px #0060DF; color: #FFF; margin-inline-start: auto; } .outer-wrapper { display: flex; flex-grow: 1; - padding: 62px 32px 32px; + padding: 40px 32px 32px; height: 100%; } main { @@ -171,10 +173,10 @@ main { main { width: 736px; } } main section { - margin-bottom: 32px; } + margin-bottom: 40px; + position: relative; } .section-title { - color: #6E707E; font-size: 13px; font-weight: bold; text-transform: uppercase; } @@ -239,7 +241,7 @@ main { color: inherit; outline: none; } .top-sites-list .top-site-outer > a.active .tile, .top-sites-list .top-site-outer > a:focus .tile { - box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1), 0 0 0 5px rgba(0, 0, 0, 0.1); + box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1), 0 0 0 5px #D7D7DB; transition: box-shadow 150ms; } .top-sites-list .top-site-outer .context-menu-button { cursor: pointer; @@ -249,13 +251,13 @@ main { width: 27px; height: 27px; background-color: #FFF; - background-image: url("assets/glyph-more-16.svg"); - background-position: 65%; - background-repeat: no-repeat; + background-image: url("chrome://browser/skin/page-action.svg"); + background-position: 55%; background-clip: padding-box; - border: 1px solid rgba(0, 0, 0, 0.2); + border: 1px solid #B1B1B3; border-radius: 100%; - box-shadow: 0 2px 0 rgba(0, 0, 0, 0.1); + box-shadow: 0 2px rgba(12, 12, 13, 0.1); + fill: rgba(12, 12, 13, 0.8); transform: scale(0.25); opacity: 0; transition-property: transform, opacity; @@ -265,7 +267,7 @@ main { transform: scale(1); opacity: 1; } .top-sites-list .top-site-outer:hover .tile, .top-sites-list .top-site-outer:focus .tile, .top-sites-list .top-site-outer.active .tile { - box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1), 0 0 0 5px rgba(0, 0, 0, 0.1); + box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1), 0 0 0 5px #D7D7DB; transition: box-shadow 150ms; } .top-sites-list .top-site-outer:hover .context-menu-button, .top-sites-list .top-site-outer:focus .context-menu-button, .top-sites-list .top-site-outer.active .context-menu-button { transform: scale(1); @@ -275,8 +277,8 @@ main { height: 96px; width: 96px; border-radius: 6px; - box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1); - color: #A0A0A0; + box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1), 0 1px 4px 0 rgba(12, 12, 13, 0.1); + color: #737373; font-weight: 200; font-size: 32px; text-transform: uppercase; @@ -311,12 +313,13 @@ main { background-repeat: no-repeat; } .top-sites-list .top-site-outer .title { font: message-box; - height: 30px; + height: 20px; line-height: 30px; text-align: center; width: 96px; position: relative; } .top-sites-list .top-site-outer .title .icon { + fill: rgba(12, 12, 13, 0.6); offset-inline-start: 0; position: absolute; top: 10px; } @@ -329,9 +332,9 @@ main { padding: 0 13px; } .top-sites-list .top-site-outer .edit-menu { background: #FFF; - border: 1px solid rgba(0, 0, 0, 0.2); + border: 1px solid #B1B1B3; border-radius: 12.5px; - box-shadow: 0 2px 0 rgba(0, 0, 0, 0.1); + box-shadow: 0 2px rgba(12, 12, 13, 0.1); height: 25px; position: absolute; offset-inline-end: -12.5px; @@ -347,13 +350,13 @@ main { opacity: 1; } .top-sites-list .top-site-outer .edit-menu button { border: 0; - border-right: 1px solid rgba(0, 0, 0, 0.2); + border-right: 1px solid #B1B1B3; background-color: #FFF; cursor: pointer; height: 100%; width: 25px; } .top-sites-list .top-site-outer .edit-menu button:hover { - background-color: #FBFBFB; } + background-color: #F9F9FA; } .top-sites-list .top-site-outer .edit-menu button:last-child:dir(ltr) { border-right: 0; } .top-sites-list .top-site-outer .edit-menu button:first-child:dir(rtl) { @@ -362,9 +365,6 @@ main { transform: scale(1); opacity: 1; } -.top-sites { - position: relative; } - .edit-topsites-wrapper .edit-topsites-button { position: absolute; offset-inline-end: 0; @@ -372,13 +372,13 @@ main { .edit-topsites-wrapper .edit-topsites-button button { background: none; border: 0; - color: #A0A0A0; + color: #737373; cursor: pointer; font-size: 12px; padding: 0; } .edit-topsites-wrapper .edit-topsites-button button:focus { - background: #EBEBEB; - border-bottom: dotted 1px #A0A0A0; } + background: #F9F9FA; + border-bottom: dotted 1px #737373; } .edit-topsites-wrapper .modal { offset-inline-start: -31px; @@ -394,16 +394,17 @@ main { position: relative; height: 16px; margin-bottom: 18px; } - .sections-list .section-top-bar .section-title { - float: left; } .sections-list .section-top-bar .section-info-option { - float: right; - margin-top: 14px; } + offset-inline-end: 0; + position: absolute; + top: 0; } .sections-list .section-top-bar .info-option-icon { background-image: url("assets/glyph-info-option-12.svg"); background-size: 12px 12px; background-repeat: no-repeat; background-position: center; + fill: rgba(12, 12, 13, 0.6); + -moz-context-properties: fill; height: 16px; width: 16px; display: inline-block; } @@ -420,10 +421,9 @@ main { z-index: 9999; position: absolute; background: #FFF; - border: solid 1px rgba(0, 0, 0, 0.1); + border: 1px solid #D7D7DB; border-radius: 3px; font-size: 13px; - color: #0C0C0D; line-height: 120%; width: 320px; right: 0; @@ -440,10 +440,9 @@ main { .sections-list .section-top-bar .info-option-link { display: block; margin-top: 12px; - color: #0A84FF; } + color: #008EA4; } .sections-list .section-list { - clear: both; margin: 0; display: grid; grid-template-columns: repeat(auto-fit, 224px); @@ -471,7 +470,7 @@ main { width: 100%; height: 266px; display: flex; - border: solid 1px rgba(0, 0, 0, 0.1); + border: 1px solid #D7D7DB; border-radius: 3px; margin-bottom: 16px; } .sections-list .section-empty-state .empty-state { @@ -481,7 +480,7 @@ main { background-size: 50px 50px; background-repeat: no-repeat; background-position: center; - fill: rgba(160, 160, 160, 0.4); + fill: rgba(12, 12, 13, 0.6); -moz-context-properties: fill; height: 50px; width: 50px; @@ -491,12 +490,12 @@ main { margin-bottom: 0; font-size: 13px; font-weight: 300; - color: #A0A0A0; + color: #737373; text-align: center; } .topic { font-size: 12px; - color: #BFC0C7; + color: #737373; margin-top: 12px; line-height: 1.6; } @media (min-width: 800px) { @@ -534,6 +533,8 @@ main { margin-left: 5px; background-image: url("assets/topic-show-more-12.svg"); background-repeat: no-repeat; + fill: #008EA4; + -moz-context-properties: fill; vertical-align: middle; } .search-wrapper { @@ -545,63 +546,55 @@ main { height: 36px; } .search-wrapper input { border: 0; - box-shadow: 0 1px 0 0 rgba(0, 0, 0, 0.1); - flex-grow: 1; - margin: 0; - outline: none; - padding: 0 12px 0 35px; - height: 100%; - border-radius: 4px 0 0 4px; - padding-inline-start: 35px; } + box-shadow: 0 1px 4px 0 rgba(12, 12, 13, 0.1); + border-radius: 4px; + padding: 0; + padding-inline-end: 36px; + padding-inline-start: 35px; + width: 100%; } .search-wrapper input:focus { - border-color: #0996F8; - box-shadow: 0 0 0 2px #0996F8; + border-color: #0A84FF; + box-shadow: 0 0 0 2px #0A84FF; z-index: 1; } .search-wrapper input:focus + .search-button { z-index: 1; - box-shadow: 0 0 0 2px #0996F8; - background-color: #0996F8; - background-image: url("assets/glyph-forward-16-white.svg"); - color: #FFF; } + background-color: #0A84FF; + background-image: url("chrome://browser/skin/forward.svg"); + fill: #FFF; + -moz-context-properties: fill; } .search-wrapper input[aria-expanded="true"] { - border-radius: 4px 0 0 0; } - .search-wrapper input:dir(rtl) { - border-radius: 0 4px 4px 0; } - .search-wrapper input:dir(rtl)[aria-expanded="true"] { - border-radius: 0 4px 0 0; } + border-radius: 4px 4px 0 0; } .search-wrapper .search-label { background: url("assets/glyph-search-16.svg") no-repeat center center/20px; + fill: rgba(12, 12, 13, 0.6); + -moz-context-properties: fill; position: absolute; - top: 0; offset-inline-start: 0; height: 100%; width: 35px; - display: flex; - align-items: center; - justify-content: center; z-index: 2; } .search-wrapper .search-button { + background: url("chrome://browser/skin/forward.svg") no-repeat center center; border-radius: 0 3px 3px 0; - margin-inline-start: -1px; border: 0; width: 36px; - padding: 0; - box-shadow: 0 1px 0 0 rgba(0, 0, 0, 0.1); - background: #FFF url("assets/glyph-forward-16.svg") no-repeat center center; - background-size: 16px 16px; } + fill: rgba(12, 12, 13, 0.6); + -moz-context-properties: fill; + background-size: 16px 16px; + height: 100%; + offset-inline-end: 0; + position: absolute; } .search-wrapper .search-button:hover { z-index: 1; - box-shadow: 0 1px 0 0 rgba(0, 0, 1, 0.5); - background-color: #0996F8; - background-image: url("assets/glyph-forward-16-white.svg"); - color: #FFF; + background-color: #0A84FF; + fill: #FFF; cursor: pointer; } .search-wrapper .search-button:dir(rtl) { transform: scaleX(-1); } .search-wrapper .contentSearchSuggestionTable { - transform: translate(-2px, 2px); } - .search-wrapper .contentSearchSuggestionTable:dir(rtl) { - transform: translate(2px, 2px); } + border: 0; + box-shadow: 0 0 0 2px #0A84FF; + transform: translateY(2px); } .context-menu { display: block; @@ -612,7 +605,7 @@ main { offset-inline-start: 100%; margin-inline-start: 5px; z-index: 10000; - background: #FBFBFB; + background: #F9F9FA; border-radius: 5px; } .context-menu > ul { margin: 0; @@ -634,7 +627,7 @@ main { display: flex; align-items: center; } .context-menu > ul > li > a:hover, .context-menu > ul > li > a:focus { - background: #2B99FF; + background: #0A84FF; color: #FFF; } .context-menu > ul > li > a:hover a, .context-menu > ul > li > a:focus a { color: #0C0C0D; } @@ -645,21 +638,23 @@ main { font-size: 13px; } .prefs-pane .sidebar { background: #FFF; - border-left: solid 1px rgba(0, 0, 0, 0.1); - box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.08); + border-left: 1px solid #D7D7DB; + box-shadow: 0 1px 4px 0 rgba(12, 12, 13, 0.1); min-height: 100%; offset-inline-end: 0; padding: 40px; position: fixed; top: 0; transition: 0.1s cubic-bezier(0, 0, 0, 1); - transition-property: left, right; + transition-property: transform; width: 400px; z-index: 12000; } .prefs-pane .sidebar.hidden { - offset-inline-end: -400px; } + transform: translateX(100%); } + .prefs-pane .sidebar.hidden:dir(rtl) { + transform: translateX(-100%); } .prefs-pane .sidebar h1 { - border-bottom: solid 1px rgba(0, 0, 0, 0.1); + border-bottom: 1px solid #D7D7DB; font-size: 24px; margin: 0; padding: 20px 0; } @@ -677,8 +672,8 @@ main { font-size: 16px; font-weight: bold; } .prefs-pane .prefs-modal-inner-wrapper section .options { - background: #FBFBFB; - border: solid 1px rgba(0, 0, 0, 0.1); + background: #F9F9FA; + border: 1px solid #D7D7DB; border-radius: 3px; margin: 15px 0; margin-inline-start: 30px; @@ -710,7 +705,7 @@ main { .prefs-pane [type='checkbox']:not(:checked) + label::before, .prefs-pane [type='checkbox']:checked + label::before { background: #FFF; - border: 1px solid #C1C1C1; + border: 1px solid #B1B1B3; border-radius: 3px; content: ''; height: 21px; @@ -728,30 +723,31 @@ main { top: 0; width: 21px; -moz-context-properties: fill, stroke; - fill: #1691D2; + fill: #0A84FF; stroke: none; } .prefs-pane [type='checkbox']:not(:checked) + label::after { opacity: 0; } .prefs-pane [type='checkbox']:checked + label::after { opacity: 1; } .prefs-pane [type='checkbox'] + label:hover::before { - border: 1px solid #1691D2; } + border: 1px solid #0A84FF; } .prefs-pane [type='checkbox']:checked:focus + label::before, .prefs-pane [type='checkbox']:not(:checked):focus + label::before { - border: 1px dotted #1691D2; } + border: 1px dotted #0A84FF; } .prefs-pane-button button { background-color: transparent; border: 0; cursor: pointer; - opacity: 0.7; padding: 15px; position: fixed; right: 15px; top: 15px; z-index: 12001; } .prefs-pane-button button:hover { - background-color: #EBEBEB; } + background-color: #F9F9FA; } + .prefs-pane-button button:active { + background-color: #EDEDF0; } .prefs-pane-button button:dir(rtl) { left: 5px; right: auto; } @@ -779,7 +775,7 @@ main { margin-inline-end: 0; } .modal-overlay { - background: #FBFBFB; + background: #F9F9FA; height: 100%; left: 0; opacity: 0.8; @@ -790,7 +786,7 @@ main { .modal { background: #FFF; - border: solid 1px rgba(0, 0, 0, 0.1); + border: 1px solid #D7D7DB; border-radius: 3px; font-size: 14px; z-index: 11002; } @@ -811,13 +807,13 @@ main { width: 27px; height: 27px; background-color: #FFF; - background-image: url("assets/glyph-more-16.svg"); - background-position: 65%; - background-repeat: no-repeat; + background-image: url("chrome://browser/skin/page-action.svg"); + background-position: 55%; background-clip: padding-box; - border: 1px solid rgba(0, 0, 0, 0.2); + border: 1px solid #B1B1B3; border-radius: 100%; - box-shadow: 0 2px 0 rgba(0, 0, 0, 0.1); + box-shadow: 0 2px rgba(12, 12, 13, 0.1); + fill: rgba(12, 12, 13, 0.8); transform: scale(0.25); opacity: 0; transition-property: transform, opacity; @@ -829,7 +825,7 @@ main { .card-outer .card { height: 100%; border-radius: 3px; - box-shadow: 0 1px 4px 0 rgba(9, 6, 13, 0.1); } + box-shadow: 0 1px 4px 0 rgba(12, 12, 13, 0.1); } .card-outer > a { display: block; color: inherit; @@ -838,28 +834,26 @@ main { position: absolute; width: 224px; } .card-outer > a.active .card, .card-outer > a:focus .card { - box-shadow: 0 0 0 5px rgba(0, 0, 0, 0.1); + box-shadow: 0 0 0 5px #D7D7DB; transition: box-shadow 150ms; } .card-outer > a.active .card-title, .card-outer > a:focus .card-title { - color: #00AFF7; } + color: #008EA4; } .card-outer:hover, .card-outer:focus, .card-outer.active { outline: none; - box-shadow: 0 0 0 5px rgba(0, 0, 0, 0.1); + box-shadow: 0 0 0 5px #D7D7DB; transition: box-shadow 150ms; } .card-outer:hover .context-menu-button, .card-outer:focus .context-menu-button, .card-outer.active .context-menu-button { transform: scale(1); opacity: 1; } .card-outer:hover .card-title, .card-outer:focus .card-title, .card-outer.active .card-title { - color: #00AFF7; } + color: #008EA4; } .card-outer .card-preview-image { position: relative; background-size: cover; background-position: center; background-repeat: no-repeat; height: 122px; - border-bottom-color: rgba(0, 0, 0, 0.1); - border-bottom-style: solid; - border-bottom-width: 1px; + border-bottom: 1px solid #D7D7DB; border-radius: 3px 3px 0 0; } .card-outer .card-details { padding: 15px 16px 12px; } @@ -879,7 +873,7 @@ main { .card-outer .card-text.no-image.no-host-name.no-context { max-height: 230px; } .card-outer .card-host-name { - color: #858585; + color: #737373; font-size: 10px; padding-bottom: 4px; text-transform: uppercase; } @@ -900,11 +894,11 @@ main { bottom: 0; left: 0; right: 0; - color: #A0A0A0; + color: #737373; font-size: 11px; display: flex; } .card-outer .card-context-icon { - opacity: 0.5; + fill: rgba(12, 12, 13, 0.6); font-size: 13px; margin-inline-end: 6px; display: block; } @@ -915,7 +909,9 @@ main { white-space: nowrap; } .manual-migration-container { - background: rgba(215, 215, 219, 0.5); + background: #F9F9FA; + border: 1px solid #D7D7DB; + box-shadow: 0 1px 4px 0 rgba(12, 12, 13, 0.1); font-size: 13px; border-radius: 2px; margin-bottom: 40px; @@ -945,6 +941,7 @@ main { @media (min-width: 544px) { .manual-migration-container .icon { display: block; + fill: rgba(12, 12, 13, 0.6); margin: 0; margin-inline-end: 12px; align-self: center; } } diff --git a/browser/extensions/activity-stream/data/content/assets/glyph-bookmark-16.svg b/browser/extensions/activity-stream/data/content/assets/glyph-bookmark-16.svg deleted file mode 100644 index 8faf0495d65b..000000000000 --- a/browser/extensions/activity-stream/data/content/assets/glyph-bookmark-16.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - diff --git a/browser/extensions/activity-stream/data/content/assets/glyph-bookmark-remove-16.svg b/browser/extensions/activity-stream/data/content/assets/glyph-bookmark-remove-16.svg deleted file mode 100644 index 7dbe9a53f228..000000000000 --- a/browser/extensions/activity-stream/data/content/assets/glyph-bookmark-remove-16.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - diff --git a/browser/extensions/activity-stream/data/content/assets/glyph-delete-16.svg b/browser/extensions/activity-stream/data/content/assets/glyph-delete-16.svg index a309f9efb7cb..d66876aa47d9 100644 --- a/browser/extensions/activity-stream/data/content/assets/glyph-delete-16.svg +++ b/browser/extensions/activity-stream/data/content/assets/glyph-delete-16.svg @@ -1,13 +1 @@ - - - - - - + \ No newline at end of file diff --git a/browser/extensions/activity-stream/data/content/assets/glyph-dismiss-16.svg b/browser/extensions/activity-stream/data/content/assets/glyph-dismiss-16.svg index 94495fe27ca3..074ccb1334dc 100644 --- a/browser/extensions/activity-stream/data/content/assets/glyph-dismiss-16.svg +++ b/browser/extensions/activity-stream/data/content/assets/glyph-dismiss-16.svg @@ -1,13 +1 @@ - - - - - - + \ No newline at end of file diff --git a/browser/extensions/activity-stream/data/content/assets/glyph-forward-16-white.svg b/browser/extensions/activity-stream/data/content/assets/glyph-forward-16-white.svg deleted file mode 100644 index 5bb5b8d535fe..000000000000 --- a/browser/extensions/activity-stream/data/content/assets/glyph-forward-16-white.svg +++ /dev/null @@ -1,7 +0,0 @@ - - Forward - 16 - - - - - diff --git a/browser/extensions/activity-stream/data/content/assets/glyph-forward-16.svg b/browser/extensions/activity-stream/data/content/assets/glyph-forward-16.svg deleted file mode 100644 index 4fb2a7e5c9cd..000000000000 --- a/browser/extensions/activity-stream/data/content/assets/glyph-forward-16.svg +++ /dev/null @@ -1,7 +0,0 @@ - - Forward - 16 - - - - - diff --git a/browser/extensions/activity-stream/data/content/assets/glyph-historyItem-16.svg b/browser/extensions/activity-stream/data/content/assets/glyph-historyItem-16.svg index 05822cec1d34..91c4c6ecc3c5 100644 --- a/browser/extensions/activity-stream/data/content/assets/glyph-historyItem-16.svg +++ b/browser/extensions/activity-stream/data/content/assets/glyph-historyItem-16.svg @@ -1,6 +1 @@ - - - - + \ No newline at end of file diff --git a/browser/extensions/activity-stream/data/content/assets/glyph-info-16.svg b/browser/extensions/activity-stream/data/content/assets/glyph-info-16.svg index e901fe96b6da..9e04ba7a4b74 100644 --- a/browser/extensions/activity-stream/data/content/assets/glyph-info-16.svg +++ b/browser/extensions/activity-stream/data/content/assets/glyph-info-16.svg @@ -1,6 +1 @@ - - - - + \ No newline at end of file diff --git a/browser/extensions/activity-stream/data/content/assets/glyph-info-option-12.svg b/browser/extensions/activity-stream/data/content/assets/glyph-info-option-12.svg index b2eef1230cb5..939525813974 100644 --- a/browser/extensions/activity-stream/data/content/assets/glyph-info-option-12.svg +++ b/browser/extensions/activity-stream/data/content/assets/glyph-info-option-12.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/browser/extensions/activity-stream/data/content/assets/glyph-more-16.svg b/browser/extensions/activity-stream/data/content/assets/glyph-more-16.svg deleted file mode 100644 index 9770ff831b43..000000000000 --- a/browser/extensions/activity-stream/data/content/assets/glyph-more-16.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/browser/extensions/activity-stream/data/content/assets/glyph-newWindow-16.svg b/browser/extensions/activity-stream/data/content/assets/glyph-newWindow-16.svg index 32a552ae5013..3572e8ea800a 100644 --- a/browser/extensions/activity-stream/data/content/assets/glyph-newWindow-16.svg +++ b/browser/extensions/activity-stream/data/content/assets/glyph-newWindow-16.svg @@ -1,13 +1 @@ - - - - - - + \ No newline at end of file diff --git a/browser/extensions/activity-stream/data/content/assets/glyph-newWindow-private-16.svg b/browser/extensions/activity-stream/data/content/assets/glyph-newWindow-private-16.svg deleted file mode 100644 index 1efb98c6947a..000000000000 --- a/browser/extensions/activity-stream/data/content/assets/glyph-newWindow-private-16.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - diff --git a/browser/extensions/activity-stream/data/content/assets/glyph-now-16.svg b/browser/extensions/activity-stream/data/content/assets/glyph-now-16.svg deleted file mode 100644 index 8f1375d2088b..000000000000 --- a/browser/extensions/activity-stream/data/content/assets/glyph-now-16.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - diff --git a/browser/extensions/activity-stream/data/content/assets/glyph-pin-12.svg b/browser/extensions/activity-stream/data/content/assets/glyph-pin-12.svg index c97960d4ac74..d1cafd4e823c 100644 --- a/browser/extensions/activity-stream/data/content/assets/glyph-pin-12.svg +++ b/browser/extensions/activity-stream/data/content/assets/glyph-pin-12.svg @@ -1,8 +1 @@ - - - - + \ No newline at end of file diff --git a/browser/extensions/activity-stream/data/content/assets/glyph-pin-16.svg b/browser/extensions/activity-stream/data/content/assets/glyph-pin-16.svg index 4c063adc2b38..612fda803a12 100644 --- a/browser/extensions/activity-stream/data/content/assets/glyph-pin-16.svg +++ b/browser/extensions/activity-stream/data/content/assets/glyph-pin-16.svg @@ -1,8 +1 @@ - - - - + \ No newline at end of file diff --git a/browser/extensions/activity-stream/data/content/assets/glyph-pocket-16.svg b/browser/extensions/activity-stream/data/content/assets/glyph-pocket-16.svg index aab23f82717f..887943e6dacf 100644 --- a/browser/extensions/activity-stream/data/content/assets/glyph-pocket-16.svg +++ b/browser/extensions/activity-stream/data/content/assets/glyph-pocket-16.svg @@ -1,6 +1 @@ - - - - + \ No newline at end of file diff --git a/browser/extensions/activity-stream/data/content/assets/glyph-search-16.svg b/browser/extensions/activity-stream/data/content/assets/glyph-search-16.svg index 4d5eddbbe081..8ecf81f4f5f7 100644 --- a/browser/extensions/activity-stream/data/content/assets/glyph-search-16.svg +++ b/browser/extensions/activity-stream/data/content/assets/glyph-search-16.svg @@ -1,13 +1 @@ - - - - - - - - + \ No newline at end of file diff --git a/browser/extensions/activity-stream/data/content/assets/glyph-settings-16.svg b/browser/extensions/activity-stream/data/content/assets/glyph-settings-16.svg index 12034f08ea18..0fcbfc5e799d 100644 --- a/browser/extensions/activity-stream/data/content/assets/glyph-settings-16.svg +++ b/browser/extensions/activity-stream/data/content/assets/glyph-settings-16.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/browser/extensions/activity-stream/data/content/assets/glyph-topsites-16.svg b/browser/extensions/activity-stream/data/content/assets/glyph-topsites-16.svg index 5f917c255dc8..147e7c42e9ce 100644 --- a/browser/extensions/activity-stream/data/content/assets/glyph-topsites-16.svg +++ b/browser/extensions/activity-stream/data/content/assets/glyph-topsites-16.svg @@ -1,11 +1 @@ - - - - - - - - - + \ No newline at end of file diff --git a/browser/extensions/activity-stream/data/content/assets/glyph-trending-16.svg b/browser/extensions/activity-stream/data/content/assets/glyph-trending-16.svg index c84c4f0617a7..aee01443701e 100644 --- a/browser/extensions/activity-stream/data/content/assets/glyph-trending-16.svg +++ b/browser/extensions/activity-stream/data/content/assets/glyph-trending-16.svg @@ -1,8 +1 @@ - - - - - - - - + \ No newline at end of file diff --git a/browser/extensions/activity-stream/data/content/assets/glyph-unpin-16.svg b/browser/extensions/activity-stream/data/content/assets/glyph-unpin-16.svg index fa7e978db172..05584647882f 100644 --- a/browser/extensions/activity-stream/data/content/assets/glyph-unpin-16.svg +++ b/browser/extensions/activity-stream/data/content/assets/glyph-unpin-16.svg @@ -1,11 +1 @@ - - - - - - - + \ No newline at end of file diff --git a/browser/extensions/activity-stream/data/content/assets/glyph-webextension-16.svg b/browser/extensions/activity-stream/data/content/assets/glyph-webextension-16.svg index e6427988935e..5d2de736d640 100644 --- a/browser/extensions/activity-stream/data/content/assets/glyph-webextension-16.svg +++ b/browser/extensions/activity-stream/data/content/assets/glyph-webextension-16.svg @@ -1,6 +1 @@ - - - - + \ No newline at end of file diff --git a/browser/extensions/activity-stream/data/content/assets/topic-show-more-12.svg b/browser/extensions/activity-stream/data/content/assets/topic-show-more-12.svg index 7e7639f4e7e6..bc0f25b81a4f 100644 --- a/browser/extensions/activity-stream/data/content/assets/topic-show-more-12.svg +++ b/browser/extensions/activity-stream/data/content/assets/topic-show-more-12.svg @@ -1,12 +1 @@ - - - - Icon / > - Created with Sketch. - - - - - - - \ No newline at end of file + \ No newline at end of file diff --git a/browser/extensions/activity-stream/data/locales.json b/browser/extensions/activity-stream/data/locales.json index 6795b69e28e1..290fd1371866 100644 --- a/browser/extensions/activity-stream/data/locales.json +++ b/browser/extensions/activity-stream/data/locales.json @@ -3,12 +3,19 @@ "newtab_page_title": "Dirica matidi manyen", "default_label_loading": "Tye ka cano…", "header_top_sites": "Kakube maloyo", + "header_stories": "Lok madito", + "header_visit_again": "Lim doki", + "header_bookmarks": "Alamabuk ma cok coki", + "header_recommended_by": "Lami tam obedo {provider}", "header_bookmarks_placeholder": "Pud i pee ki alamabuk.", + "header_stories_from": "ki bot", "type_label_visited": "Kilimo", "type_label_bookmarked": "Kiketo alamabuk", "type_label_synced": "Kiribo ki i nyonyo mukene", + "type_label_recommended": "Ma cuke lamal", "type_label_open": "Tye ayaba", "type_label_topic": "Lok", + "type_label_now": "Kombedi", "menu_action_bookmark": "Alamabuk", "menu_action_remove_bookmark": "Kwany alamabuk", "menu_action_copy_address": "Lok kabedo", @@ -18,12 +25,16 @@ "menu_action_dismiss": "Kwer", "menu_action_delete": "Kwany ki ii gin mukato", "menu_action_pin": "Mwon", + "menu_action_unpin": "War", + "confirm_history_delete_p1": "Imoko ni imito kwanyo nyig jami weng me potbuk man ki i gin mukato mamegi?", + "confirm_history_delete_notice_p2": "Pe ki twero gonyo tic man.", "menu_action_save_to_pocket": "Gwoki i jaba", "search_for_something_with": "Yeny pi {search_term} ki:", "search_button": "Yeny", "search_header": "Yeny me {search_engine_name}", "search_web_placeholder": "Yeny kakube", "search_settings": "Lok ter me yeny", + "section_info_option": "Ngec", "welcome_title": "Wajoli i dirica matidi manyen", "welcome_body": "Firefox bi tic ki kabedo man me nyuto alamabukke mamegi, coc akwana, vidio, ki potbukke ma ilimo cokcoki ma pi gi tego loyo, wek i dok ii gi ma yot.", "welcome_label": "Tye ka kube ki wiye madito mamegi", @@ -40,7 +51,11 @@ "settings_pane_topsites_body": "Nong kakube ma ilimo loyo.", "settings_pane_topsites_options_showmore": "Nyut rek ariyo", "settings_pane_bookmarks_header": "Alamabuk ma cocoki", + "settings_pane_bookmarks_body": "Alamabukke ni ma kicweyo manyen i kabedo acel macek.", "settings_pane_visit_again_header": "Lim Kidoco", + "settings_pane_visit_again_body": "Firefox bi nyuti but gin mukato me yeny mamegi ma itwero mito me poo ikome onyo dok cen iyie.", + "settings_pane_pocketstories_header": "Lok madito", + "settings_pane_pocketstories_body": "Pocket, but jo me Mozilla, bi konyi me kube i jami mabeco loyo ma twero bedo ni pe i nongo.", "settings_pane_done_button": "Otum", "edit_topsites_button_text": "Yubi", "edit_topsites_button_label": "Yub bute pi kakubi ni ma giloyo", @@ -48,13 +63,27 @@ "edit_topsites_showless_button": "Nyut manok", "edit_topsites_done_button": "Otum", "edit_topsites_pin_button": "Mwon kakube man", + "edit_topsites_unpin_button": "War kakube man", "edit_topsites_edit_button": "Yub kakube man", "edit_topsites_dismiss_button": "Kwer kakube man", "edit_topsites_add_button": "Medi", + "topsites_form_add_header": "Kakube maloyo manyen", "topsites_form_edit_header": "Yub Kakube maloyo", + "topsites_form_title_placeholder": "Ket wiye", + "topsites_form_url_placeholder": "Coo onyo mwon URL", "topsites_form_add_button": "Medi", "topsites_form_save_button": "Gwoki", - "topsites_form_cancel_button": "Kwer" + "topsites_form_cancel_button": "Kwer", + "topsites_form_url_validation": "URL ma tye atir mite", + "pocket_read_more": "Lok macuk gi lamal:", + "pocket_read_even_more": "Nen Lok mapol", + "pocket_feedback_header": "Kakube maber loyo, dano makato milion 25 aye oyubo.", + "pocket_feedback_body": "Pocket, but jo me Mozilla, bi konyi me kube i jami mabeco loyo ma twero bedo ni pe i nongo.", + "pocket_send_feedback": "Cwal adwogi", + "topstories_empty_state": "Ityeko weng. Rot doki lacen pi lok madito mapol ki bot {provider}. Pe itwero kuro? Yer lok macuke lamal me nongo lok mabeco mapol ki i but kakube.", + "manual_migration_explanation": "Tem Firefox ki kakube ki alamabuk ni ma imaro loyo ki i layeny mukene.", + "manual_migration_cancel_button": "Pe Apwoyo", + "manual_migration_import_button": "Kel kombedi" }, "af": {}, "an": {}, @@ -227,7 +256,11 @@ "pocket_read_even_more": "Daha çox hekayə gör", "pocket_feedback_header": "25 milyon nəfərin dəstəyi ilə internetin ən yaxşıları.", "pocket_feedback_body": "Pocket, Mozilla ailəsinin üzvü, yüksək keyfiyyətli məzmunları kəşf etməyinizə kömək edəcək.", - "pocket_send_feedback": "Əks-əlaqə göndər" + "pocket_send_feedback": "Əks-əlaqə göndər", + "topstories_empty_state": "Hamısını oxudunuz. Yeni {provider} məqalələri üçün daha sonra təkrar yoxlayın. Gözləyə bilmirsiz? Məşhur mövzu seçərək internetdən daha çox gözəl məqalələr tapın.", + "manual_migration_explanation": "Firefox səyyahını digər səyyahınızdan olan sevimli sayt və əlfəcinlərinizlə yoxlayın.", + "manual_migration_cancel_button": "Xeyr, Təşəkkürlər", + "manual_migration_import_button": "İndi idxal et" }, "be": { "newtab_page_title": "Новая картка", @@ -408,6 +441,7 @@ "header_stories": "শীর্ষ গল্প", "header_visit_again": "পুনরায় ভিজিট করুন", "header_bookmarks": "সাম্প্রতিক বুকমার্ক", + "header_recommended_by": "{provider} দ্বারা সুপারিশকৃত", "header_bookmarks_placeholder": "এখনও কোন বুকমার্ক নেই।", "header_stories_from": "থেকে", "type_label_visited": "পরিদর্শিত", @@ -416,6 +450,7 @@ "type_label_recommended": "ঝোঁক", "type_label_open": "খোলা", "type_label_topic": "টপিক", + "type_label_now": "এখন", "menu_action_bookmark": "বুকমার্ক", "menu_action_remove_bookmark": "বুকমার্ক মুছে দিন", "menu_action_copy_address": "ঠিকানা কপি করুন", @@ -434,6 +469,7 @@ "search_header": "{search_engine_name} খুঁজুন", "search_web_placeholder": "ওয়েবে সন্ধান করুন", "search_settings": "সার্চ সেটিংস বদল করুন", + "section_info_option": "তথ্য", "welcome_title": "নতুন ট্যাবে আপনাকে স্বাগতম", "welcome_body": "আপনার সাথে মিলে এমন বুর্কমার্ক, নিবন্ধ, ভিডিও এবং পাতা যেগুলো আপনি সম্প্রতি ভ্রমণ করেছে তা Firefox এই জায়গায় দেখাবে, যাতে আপনি সেগুলো দ্রুত খুঁজে পান।", "welcome_label": "আপনার হাইলাইট সমূহ চিহ্নিত করুন", @@ -452,7 +488,9 @@ "settings_pane_bookmarks_header": "সাম্প্রতিক বুকমার্ক", "settings_pane_bookmarks_body": "আপনার নতুন করা বুকমার্ক সহজ অবস্থানে রাখা হয়েছে।", "settings_pane_visit_again_header": "পুনরায় ভিজিট করুন", + "settings_pane_visit_again_body": "Firefox আপনার ব্রাউজিং ইতিহাসের এমন একটি অংশ দেখাবে যা আপনি মনে রাখতে চান বা যাতে আবার ফিরে যেতে চান।", "settings_pane_pocketstories_header": "শীর্ষ গল্প", + "settings_pane_pocketstories_body": "Pocket, হচ্ছে Mozilla পরিবারের একটি অংশ, যা আপনাকে ভালো-মানের কন্টেন্টের সাথে যুক্ত করবে যা আপনি অন্য কোথাও পাবেন না।", "settings_pane_done_button": "হয়েছে", "edit_topsites_button_text": "সম্পাদনা", "edit_topsites_button_label": "আপনার টপ সাইট সেকশন কাস্টমাইজ করুন", @@ -467,13 +505,20 @@ "topsites_form_add_header": "নতুন শীর্ষ সাইট", "topsites_form_edit_header": "শীর্ষ সাইট সম্পাদনা করুন", "topsites_form_title_placeholder": "নাম দিন", + "topsites_form_url_placeholder": "টাইপ করুন অথবা পেস্ট করুন URL", "topsites_form_add_button": "যোগ", "topsites_form_save_button": "সংরক্ষণ", "topsites_form_cancel_button": "বাতিল", "topsites_form_url_validation": "কার্যকর URL প্রয়োজন", "pocket_read_more": "জনপ্রিয় বিষয়:", "pocket_read_even_more": "আরও গল্প দেখুন", - "pocket_send_feedback": "প্রতিক্রিয়া জানান" + "pocket_feedback_header": "ওয়েব জগতের সেরা, যা ২.৫ লক্ষ মানুষ রক্ষণাবেক্ষণ করে।", + "pocket_feedback_body": "Pocket, হচ্ছে Mozilla পরিবারের একটি অংশ, যা আপনাকে ভালো-মানের কন্টেন্টের সাথে যুক্ত করবে যা আপনি অন্য কোথাও পাবেন না।", + "pocket_send_feedback": "প্রতিক্রিয়া জানান", + "topstories_empty_state": "কিছু একটা ঠিক নেই। {provider} এর শীর্ষ গল্পগুলো পেতে কিছুক্ষণ পর আবার দেখুন। অপেক্ষা করতে চান না? বিশ্বের সেরা গল্পগুলো পেতে কোন জনপ্রিয় বিষয় নির্বাচন করুন।", + "manual_migration_explanation": "অন্য ব্রাউজার থেকে আপনার পছন্দের সাইট এবং বুকমার্কগুলো নিয়ে Firefox ব্যবহার করুন।", + "manual_migration_cancel_button": "প্রয়োজন নেই", + "manual_migration_import_button": "এখনই ইম্পোর্ট করুন" }, "bn-IN": {}, "br": {}, @@ -564,7 +609,65 @@ "manual_migration_cancel_button": "No, gràcies", "manual_migration_import_button": "Importa-ho ara" }, - "cak": {}, + "cak": { + "newtab_page_title": "K'ak'a' ruwi'", + "default_label_loading": "Tajin nusamajij…", + "header_top_sites": "Utziläj taq Ruxaq K'amaya'l", + "header_stories": "Utziläj taq B'anob'äl", + "header_visit_again": "Titz'et chik", + "header_bookmarks": "K'ak'a' taq Yaketal", + "header_recommended_by": "Chilab'en ruma {provider}", + "header_bookmarks_placeholder": "K'a majani k'o jujun taq ayaketal.", + "header_stories_from": "richin", + "type_label_visited": "Tz'eton", + "type_label_bookmarked": "Yakon retal", + "type_label_synced": "Ximon rik'in jun chik okisaxel", + "type_label_recommended": "Rujawaxik", + "type_label_open": "Tijaq", + "type_label_now": "Wakami", + "menu_action_bookmark": "Yaketal", + "menu_action_remove_bookmark": "Tiyuj el ri yaketal", + "menu_action_copy_address": "Tiwachib'ëx Ochochib'äl", + "menu_action_email_link": "Titaq Ximonel Tzij…", + "menu_action_open_new_window": "Tijaq pa jun K'ak'a' Tzuwäch", + "menu_action_open_private_window": "Tijaq pa jun K'ak'a' Ichinan Tzuwäch", + "menu_action_dismiss": "Tichup ruwäch", + "menu_action_delete": "Tiyuj el pa ri Natab'äl", + "confirm_history_delete_p1": "¿La kan nawajo ye'ayüj el ronojel ri kib'eyal re taq ruxaq re' chi kikojol ri anatab'al?", + "confirm_history_delete_notice_p2": "Man yatikïr ta najäl re b'anïk re'.", + "menu_action_save_to_pocket": "Tiyak pa Pocket", + "search_for_something_with": "Tikanoj {search_term} rik'in:", + "search_button": "Tikanöx", + "search_header": "{search_engine_name} Tikanöx", + "search_web_placeholder": "Tikanöx pa Ajk'amaya'l", + "search_settings": "Tijal Runuk'ulem Kanoxïk", + "section_info_option": "Rutzijol", + "welcome_title": "Ütz apetik pa ri k'ak'a' ruwi'", + "welcome_body": "Firefox xtrokisaj re k'ojlib'äl re' richin xtuk'üt ri taq ruwi', rutzijol, tzuwäch chuqa' taq ruxaq yalan kejqalem ri k'a ja' xe'atz'ët, richin chanin yatikïr yatok jun mul chik.", + "welcome_label": "Tiya' ketal ri Nïm taq K'ojlib'äl", + "time_label_less_than_minute": "<1m", + "time_label_minute": "{number}m", + "time_label_hour": "{number}m", + "time_label_day": "{ajilab'äl}m", + "settings_pane_button_label": "Tawichinaj ri ruxaq richin K'ak'a' Ruwi'", + "settings_pane_header": "K'ak'a' Ruwi' Taq Ajowab'äl", + "settings_pane_body": "Tacha' ri natz'ët toq najäq jun k'ak'a' ruwi'.", + "settings_pane_search_header": "Tikanöx", + "settings_pane_search_body": "Tikanoj ri k'ak'a' taq ruwi' pa ri K'amaya'l.", + "settings_pane_topsites_header": "Utziläj taq ruxaq K'amaya'l", + "settings_pane_topsites_body": "Katok pa ri taq ajk'amaya'l yalan ye'atz'ët.", + "settings_pane_topsites_options_showmore": "Kek'ut pe ka'i' cholaj", + "settings_pane_bookmarks_header": "K'ak'a' taq Yaketal", + "settings_pane_bookmarks_body": "Ri taq awajowab'äl k'a ri xenuk' pa jun utziläj k'ojlib'äl.", + "settings_pane_visit_again_header": "Tab'etz'eta' chik", + "settings_pane_visit_again_body": "Firefox xtuk'ut pe jalajoj taq rub'eyal ri b'anob'äl richin rukusaxik ri k'amaya'l rik'in jub'a' nawajo' nanataj chuqa' yatikir natzu' chik.", + "settings_pane_pocketstories_header": "Utziläj taq B'anob'äl", + "settings_pane_done_button": "Xk'is", + "edit_topsites_button_text": "Tinuk'", + "edit_topsites_button_label": "Tab'ana' runuk'ulem ri kitanaj Nimaläj taq Ruxaq K'amaya'l", + "topsites_form_cancel_button": "Tiq'at", + "manual_migration_import_button": "Tijik' pe" + }, "cs": { "newtab_page_title": "Nový panel", "default_label_loading": "Načítání…", @@ -1163,7 +1266,6 @@ "en-US": { "newtab_page_title": "New Tab", "default_label_loading": "Loading…", - "home_page_title": "{build} Start Page", "header_top_sites": "Top Sites", "header_stories": "Top Stories", "header_visit_again": "Visit Again", @@ -1749,7 +1851,77 @@ "pocket_feedback_body": "Pocket, osana Mozilla perekonnast, aitab sul leida kvaliteetset sisu, mida sa muidu poleks ehk leidnud.", "pocket_send_feedback": "Saada tagasisidet" }, - "eu": {}, + "eu": { + "newtab_page_title": "Fitxa berria", + "default_label_loading": "Kargatzen…", + "header_top_sites": "Gune erabilienak", + "header_visit_again": "Bisitatu berriro", + "header_bookmarks": "Azken laster-markak", + "header_recommended_by": "{provider} hornitzaileak gomendatuta", + "header_bookmarks_placeholder": "Ez daukazu laster-markarik oraindik.", + "type_label_visited": "Bisitatuta", + "type_label_bookmarked": "Laster-marka eginda", + "type_label_synced": "Beste gailu batetik sinkronizatuta", + "type_label_recommended": "Joerak", + "type_label_open": "Ireki", + "type_label_topic": "Gaia", + "type_label_now": "Orain", + "menu_action_bookmark": "Egin laster-marka", + "menu_action_remove_bookmark": "Kendu laster-marka", + "menu_action_copy_address": "Kopiatu helbidea", + "menu_action_email_link": "Bidali lotura postaz…", + "menu_action_open_new_window": "Ireki leiho berri batean", + "menu_action_open_private_window": "Ireki leiho pribatu berrian", + "menu_action_dismiss": "Baztertu", + "menu_action_delete": "Ezabatu historiatik", + "menu_action_pin": "Ainguratu", + "menu_action_unpin": "Desainguratu", + "confirm_history_delete_p1": "Ziur zaude orri honen agerpen guztiak ezabatu nahi dituzula historiatik?", + "confirm_history_delete_notice_p2": "Ekintza hau ezin da desegin.", + "menu_action_save_to_pocket": "Gorde Pocket-en", + "search_for_something_with": "Bilatu {search_term} honekin:", + "search_button": "Bilatu", + "search_header": "{search_engine_name} bilaketa", + "search_web_placeholder": "Bilatu webean", + "search_settings": "Aldatu bilaketa-ezarpenak", + "section_info_option": "Informazioa", + "welcome_title": "Ongi etorri fitxa berrira", + "time_label_less_than_minute": "<1m", + "time_label_minute": "{number}m", + "time_label_hour": "{number}h", + "time_label_day": "{number}d", + "settings_pane_button_label": "Pertsonalizatu fitxa berriaren orria", + "settings_pane_header": "Fitxa berriaren hobespenak", + "settings_pane_body": "Aukeratu fitxa berria irekitzean ikusten duzuna.", + "settings_pane_search_header": "Bilatu", + "settings_pane_search_body": "Bilatu webean zure fitxa berritik.", + "settings_pane_topsites_header": "Gune erabilienak", + "settings_pane_topsites_options_showmore": "Erakutsi bi errenkada", + "settings_pane_bookmarks_header": "Azken laster-markak", + "settings_pane_visit_again_header": "Bisitatu berriro", + "settings_pane_visit_again_body": "Gogoratu edo itzuli nahiko duzun historiaren zatia erakutsiko dizu Firefoxek.", + "settings_pane_pocketstories_body": "Pocket-ek, Mozilla familiakideak, bestela aurkituko ez zenukeen kalitate handiko edukiarekin konektatzen lagunduko dizu.", + "settings_pane_done_button": "Eginda", + "edit_topsites_button_text": "Editatu", + "edit_topsites_button_label": "Pertsonalizatu gune erabilienen atala", + "edit_topsites_showmore_button": "Erakutsi gehiago", + "edit_topsites_showless_button": "Erakutsi gutxiago", + "edit_topsites_done_button": "Eginda", + "edit_topsites_pin_button": "Ainguratu gune hau", + "edit_topsites_unpin_button": "Desainguratu gune hau", + "edit_topsites_edit_button": "Editatu gune hau", + "edit_topsites_dismiss_button": "Baztertu gune hau", + "edit_topsites_add_button": "Gehitu", + "topsites_form_title_placeholder": "Idatzi izenburua", + "topsites_form_url_placeholder": "Idatzi edo itsatsi URLa", + "topsites_form_add_button": "Gehitu", + "topsites_form_save_button": "Gorde", + "topsites_form_cancel_button": "Utzi", + "topsites_form_url_validation": "Baliozko URLa behar da", + "pocket_send_feedback": "Bidali iritzia", + "manual_migration_cancel_button": "Ez, eskerrik asko", + "manual_migration_import_button": "Inportatu orain" + }, "fa": { "newtab_page_title": "زبانه جدید", "default_label_loading": "در حال بارگیری…", @@ -1831,6 +2003,7 @@ "pocket_feedback_header": "بهترین‌های وب، گزینش شده توسط بیش از ۲۵ میلیون نفر.", "pocket_feedback_body": "Pocket، بخشی از خانواده موزیلا، کمک خواهد کرد تا به محتوایی با کیفیت بالا مرتبط شوید که در غیر این صورت ممکن بود پیدا نکنید.", "pocket_send_feedback": "ارسال بازخورد", + "topstories_empty_state": "فعلا تموم شد. بعدا دوباره سر بزن تا مطالب جدید از {provider} ببینی. نمی‌تونی صبر کنی؟ یک موضوع محبوب رو انتخاب کن تا مطالب جالب مرتبط از سراسر دنیا رو پیدا کنی.", "manual_migration_explanation": "فایرفاکس را با سایت‌های مورد علاقه و نشانک‌های خود در سایر مرورگرها امتحان کنید.", "manual_migration_cancel_button": "نه ممنون", "manual_migration_import_button": "هم‌اکنون وارد شوند" @@ -1843,13 +2016,16 @@ "header_stories": "Ykkösjutut", "header_visit_again": "Käy toistekin", "header_bookmarks": "Uusimmat kirjanmerkit", + "header_recommended_by": "Suositukset lähteestä {provider}", "header_bookmarks_placeholder": "Sinulla ei ole vielä kirjanmerkkejä.", + "header_stories_from": "Lähde", "type_label_visited": "Vierailtu", "type_label_bookmarked": "Kirjanmerkki", "type_label_synced": "Synkronoitu toiselta laitteelta", "type_label_recommended": "Pinnalla", "type_label_open": "Avoin", "type_label_topic": "Aihe", + "type_label_now": "Nyt", "menu_action_bookmark": "Lisää kirjanmerkki", "menu_action_remove_bookmark": "Poista kirjanmerkki", "menu_action_copy_address": "Kopioi osoite", @@ -1868,6 +2044,7 @@ "search_header": "{search_engine_name}-haku", "search_web_placeholder": "Verkkohaku", "search_settings": "Muuta hakuasetuksia", + "section_info_option": "Tietoa", "welcome_title": "Tervetuloa uuteen välilehteen", "welcome_body": "Firefox käyttää tätä tilaa näyttämään olennaisimmat kirjanmerkit, artikkelit, videot ja sivut, joita olet katsellut, jotta pääset niihin takaisin nopeasti.", "welcome_label": "Tunnistetaan nostojasi", @@ -1881,11 +2058,14 @@ "settings_pane_search_header": "Haku", "settings_pane_search_body": "Tee verkkohakuja uudesta välilehdestä.", "settings_pane_topsites_header": "Ykkössivustot", + "settings_pane_topsites_body": "Näe eniten vierailemasi sivustot.", "settings_pane_topsites_options_showmore": "Näytä kaksi riviä", "settings_pane_bookmarks_header": "Uusimmat kirjanmerkit", "settings_pane_bookmarks_body": "Uusimmat kirjanmerkkisi, yhdessä kätevässä paikassa.", "settings_pane_visit_again_header": "Käy toistekin", + "settings_pane_visit_again_body": "Firefox näyttää selaushistoriastasi palasia, jotka saatat haluta muistaa tai joissa haluat ehkä käydä.", "settings_pane_pocketstories_header": "Ykkösjutut", + "settings_pane_pocketstories_body": "Pocket, osa Mozilla-perhettä, auttaa löytämään laadukasta sisältöä, jota et ehkä olisi muuten löytänyt.", "settings_pane_done_button": "Valmis", "edit_topsites_button_text": "Muokkaa", "edit_topsites_button_label": "Muokkaa Ykkössivustot-osiota", @@ -1908,7 +2088,12 @@ "pocket_read_more": "Suositut aiheet:", "pocket_read_even_more": "Katso lisää juttuja", "pocket_feedback_header": "Netin parhaat palat, valikoitu yli 25 miljoonan ihmisen voimin.", - "pocket_send_feedback": "Lähetä palautetta" + "pocket_feedback_body": "Pocket, osa Mozilla-perhettä, auttaa löytämään laadukasta sisältöä, jota et ehkä olisi muuten löytänyt.", + "pocket_send_feedback": "Lähetä palautetta", + "topstories_empty_state": "Ei enempää suosituksia juuri nyt. Katso myöhemmin uudestaan lisää ykkösjuttuja lähteestä {provider}. Etkö malta odottaa? Valitse suosittu aihe ja löydä lisää hyviä juttuja ympäri verkkoa.", + "manual_migration_explanation": "Kokeile Firefoxia toisesta selaimesta tuotujen suosikkisivustojesi ja kirjanmerkkiesi kanssa.", + "manual_migration_cancel_button": "Ei kiitos", + "manual_migration_import_button": "Tuo nyt" }, "fr": { "newtab_page_title": "Nouvel onglet", @@ -1917,7 +2102,7 @@ "header_stories": "Articles populaires", "header_visit_again": "Visiter à nouveau", "header_bookmarks": "Marque-pages récents", - "header_recommended_by": "Recommandé par {provider}", + "header_recommended_by": "Recommandations par {provider}", "header_bookmarks_placeholder": "Vous ne possédez aucun marque-page pour l’instant.", "header_stories_from": "par", "type_label_visited": "Visité", @@ -1940,7 +2125,7 @@ "confirm_history_delete_p1": "Voulez-vous vraiment supprimer de l’historique toutes les occurrences de cette page ?", "confirm_history_delete_notice_p2": "Cette action est irréversible.", "menu_action_save_to_pocket": "Enregistrer dans Pocket", - "search_for_something_with": "Recherche pour {search_term} avec :", + "search_for_something_with": "Rechercher {search_term} avec :", "search_button": "Rechercher", "search_header": "Recherche {search_engine_name}", "search_web_placeholder": "Rechercher sur le Web", @@ -2168,6 +2353,7 @@ "header_stories": "Brod nan sgeul", "header_visit_again": "Tadhail a-rithist", "header_bookmarks": "Comharran-lìn o chionn goirid", + "header_recommended_by": "’Ga mholadh le {provider}", "header_bookmarks_placeholder": "Chan eil comharra-lìn sam bith agad fhathast.", "header_stories_from": "o", "type_label_visited": "Na thadhail thu air", @@ -2176,6 +2362,7 @@ "type_label_recommended": "A’ treandadh", "type_label_open": "Fosgailte", "type_label_topic": "Cuspair", + "type_label_now": "An-dràsta", "menu_action_bookmark": "Comharra-lìn", "menu_action_remove_bookmark": "Thoir an comharra-lìn air falbh", "menu_action_copy_address": "Dèan lethbhreac dhen t-seòladh", @@ -2194,6 +2381,7 @@ "search_header": "Lorg le {search_engine_name}", "search_web_placeholder": "Lorg air an lìon", "search_settings": "Atharraich roghainnean an luirg", + "section_info_option": "Fiosrachadh", "welcome_title": "Fàilte gun taba ùr", "welcome_body": "Seallaidh Firefox na comharran-lìn, artaigealan, videothan is duilleagan as iomchaidhe dhut, an fheadhainn air an do thadhail thu o chionn goirid, ach an ruig thu iad gu luath.", "welcome_label": "Ag aithneachadh nan highlights agad", @@ -2238,7 +2426,11 @@ "pocket_read_even_more": "Seall barrachd sgeul", "pocket_feedback_header": "Brod an eadar-lìn, air a dheasachadh le barrachd air 25 millean duine.", "pocket_feedback_body": "Pocket, ball de theaghlach Mozilla, a cheanglas tu ri susbaint fhìor-mhath nach biodh tu air fhaicinn air dòigh eile.", - "pocket_send_feedback": "Dè do bheachd air?" + "pocket_send_feedback": "Dè do bheachd air?", + "topstories_empty_state": "Sin na naidheachdan uile o {provider} an-dràsta ach bidh barrachd ann a dh’aithghearr. No thoir sùil air cuspair air a bheil fèill mhòr is leugh na tha a’ dol mun cuairt air an lìon an-dràsta.", + "manual_migration_explanation": "Feuch Firefox leis na làraichean is comharran-lìn as fhearr leat o bhrabhsair eile.", + "manual_migration_cancel_button": "Chan eil, tapadh leibh", + "manual_migration_import_button": "Ion-phortaich an-dràsta" }, "gl": {}, "gn": {}, @@ -2279,6 +2471,7 @@ "header_stories": "סיפורים מובילים", "header_visit_again": "ביקור חוזר", "header_bookmarks": "סימניות אחרונות", + "header_recommended_by": "מומלץ על ידי {provider}", "header_bookmarks_placeholder": "אין לך סימניות עדיין.", "header_stories_from": "מאת", "type_label_visited": "ביקורים קודמים", @@ -2287,6 +2480,7 @@ "type_label_recommended": "פופולרי", "type_label_open": "פתיחה", "type_label_topic": "נושא", + "type_label_now": "עכשיו", "menu_action_bookmark": "הוספת סימניה", "menu_action_remove_bookmark": "הסרת סימניה", "menu_action_copy_address": "העתקת כתובת", @@ -2305,6 +2499,7 @@ "search_header": "חיפוש ב־{search_engine_name}", "search_web_placeholder": "חיפוש ברשת", "search_settings": "שינוי הגדרות חיפוש", + "section_info_option": "מידע", "welcome_title": "ברוכים הבאים לדף הלשונית החדשה", "welcome_body": "Firefox ישתמש באזור זה כדי להציג את הסימניות הרלוונטיות ביותר, מאמרים, סרטוני וידאו ודפים שביקרת בהם לאחרונה, כך שניתן יהיה לגשת אליהם שוב בקלות.", "welcome_label": "תחומי העניין שלך מזוהים", @@ -2331,11 +2526,11 @@ "edit_topsites_button_label": "התאמת אגף האתרים המובילים שלך", "edit_topsites_showmore_button": "להציג יותר", "edit_topsites_showless_button": "להציג פחות", - "edit_topsites_done_button": "בוצע", + "edit_topsites_done_button": "סיום", "edit_topsites_pin_button": "נעיצת אתר זה", "edit_topsites_unpin_button": "ביטול הצמדת אתר זה", "edit_topsites_edit_button": "עריכת אתר זה", - "edit_topsites_dismiss_button": "התעלמות מאתר זה", + "edit_topsites_dismiss_button": "הסרת אתר זה", "edit_topsites_add_button": "הוספה", "topsites_form_add_header": "אתר מוביל חדש", "topsites_form_edit_header": "עריכת אתר מוביל", @@ -2349,7 +2544,10 @@ "pocket_read_even_more": "צפייה בחדשות נוספות", "pocket_feedback_header": "המיטב מרחבי האינטרנט, נאסף על ידי 25 מיליון אנשים.", "pocket_feedback_body": "Pocket, חלק ממשפחת Mozilla, יסייע לך להתחבר לתוכן באיכות גבוהה שיתכן שלא היה מגיע אליך בדרך אחרת.", - "pocket_send_feedback": "שליחת משוב" + "pocket_send_feedback": "שליחת משוב", + "manual_migration_explanation": "נסו את Firefox עם האתרים המועדפים עליך והסימניות שלך מדפדפן אחר.", + "manual_migration_cancel_button": "לא תודה", + "manual_migration_import_button": "ייבוא כעת" }, "hi-IN": { "newtab_page_title": "नया टैब", @@ -2408,12 +2606,19 @@ "newtab_page_title": "Nova kartica", "default_label_loading": "Učitavanje…", "header_top_sites": "Najbolje stranice", - "header_highlights": "Istaknuto", + "header_stories": "Najbolje priče", + "header_visit_again": "Posjetite ponovno", + "header_bookmarks": "Nedavne zabilješke", + "header_recommended_by": "Preporučeno od {provider}", + "header_bookmarks_placeholder": "Još nemate niti jednu zabilješku.", + "header_stories_from": "od", "type_label_visited": "Posjećeno", "type_label_bookmarked": "Zabilježeno", "type_label_synced": "Sinkronizirano s drugog uređaja", + "type_label_recommended": "Popularno", "type_label_open": "Otvori", "type_label_topic": "Tema", + "type_label_now": "Sada", "menu_action_bookmark": "Zabilježi stranicu", "menu_action_remove_bookmark": "Ukloni zabilješku", "menu_action_copy_address": "Kopiraj adresu", @@ -2422,11 +2627,17 @@ "menu_action_open_private_window": "Otvori u novom privatnom prozoru", "menu_action_dismiss": "Odbaci", "menu_action_delete": "Obriši iz povijesti", + "menu_action_pin": "Zakači", + "menu_action_unpin": "Otkači", + "confirm_history_delete_p1": "Jeste li sigurni da želite obrisati sve primjere ove stranice iz vaše povijesti?", + "confirm_history_delete_notice_p2": "Ova radnja je nepovratna.", + "menu_action_save_to_pocket": "Spremi u Pocket", "search_for_something_with": "Traži {search_term} s:", "search_button": "Traži", "search_header": "{search_engine_name} pretraživanje", "search_web_placeholder": "Pretraži web", "search_settings": "Promijeni postavke pretraživanja", + "section_info_option": "Info", "welcome_title": "Dobro došli u novu karticu", "welcome_body": "Firefox će koristiti ovaj prostor kako bi vam pokazao najbitnije zabilješke, članke, video uratke i stranice koje ste nedavno posjetili, tako da se možete lako vratiti na njih.", "welcome_label": "Identificiranje istaknutog", @@ -2442,8 +2653,12 @@ "settings_pane_topsites_header": "Najbolje stranice", "settings_pane_topsites_body": "Pristupite stranicama koje najčešće posjećujete.", "settings_pane_topsites_options_showmore": "Prikaži dva reda", - "settings_pane_highlights_header": "Istaknuto", - "settings_pane_highlights_body": "Osvrnite se na nedavno posjećene stranice i nove zabilješke.", + "settings_pane_bookmarks_header": "Nedavne zabilješke", + "settings_pane_bookmarks_body": "Vaše novo stvorene zabilješke na jednom praktičnom mjestu.", + "settings_pane_visit_again_header": "Posjetite ponovno", + "settings_pane_visit_again_body": "Firefox će vam prikazati dijelove vaše povijesti pretraživanja koje možda želite zapamtiti ili posjetiti ponovno.", + "settings_pane_pocketstories_header": "Najbolje priče", + "settings_pane_pocketstories_body": "Pocket, dio Mozilla obitelji, povezat će vas sa sadržajem visoke kvalitete koji možda inače ne biste pronašli.", "settings_pane_done_button": "Gotovo", "edit_topsites_button_text": "Uredi", "edit_topsites_button_label": "Prilagodite odjel s najboljim stranicama", @@ -2451,8 +2666,27 @@ "edit_topsites_showless_button": "Prikaži manje", "edit_topsites_done_button": "Gotovo", "edit_topsites_pin_button": "Zakači stranicu", + "edit_topsites_unpin_button": "Otkači ovu stranicu", "edit_topsites_edit_button": "Uredi ovu stranicu", - "edit_topsites_dismiss_button": "Odbaci stranicu" + "edit_topsites_dismiss_button": "Odbaci stranicu", + "edit_topsites_add_button": "Dodaj", + "topsites_form_add_header": "Nova najbolja stranica", + "topsites_form_edit_header": "Uredi najbolju stranicu", + "topsites_form_title_placeholder": "Unesi naslov", + "topsites_form_url_placeholder": "Utipkajte ili zalijepite URL", + "topsites_form_add_button": "Dodaj", + "topsites_form_save_button": "Spremi", + "topsites_form_cancel_button": "Otkaži", + "topsites_form_url_validation": "Potrebno je unijeti ispravan URL", + "pocket_read_more": "Popularne teme:", + "pocket_read_even_more": "Prikaži više priča", + "pocket_feedback_header": "Najbolje od interneta, birano od preko 25 miliona ljudi.", + "pocket_feedback_body": "Pocket, dio Mozilla obitelji, povezat će vas sa sadržajem visoke kvalitete koji možda inače ne biste pronašli.", + "pocket_send_feedback": "Pošaljite povratnu informaciju", + "topstories_empty_state": "Provjerite kasnije za više najpopularnijih priča od {provider}. Ne možete čekati? Odaberite popularne teme kako biste pronašli više kvalitetnih priča s cijelog weba.", + "manual_migration_explanation": "Probajte Firefox s vašim omiljenim stranicama i zabilješkama iz drugog pretraživača.", + "manual_migration_cancel_button": "Ne hvala", + "manual_migration_import_button": "Uvezi sada" }, "hsb": { "newtab_page_title": "Nowy rajtark", @@ -3467,7 +3701,92 @@ "newtab_page_title": "Jauna cilne" }, "mai": {}, - "mk": {}, + "mk": { + "newtab_page_title": "Ново јазиче", + "default_label_loading": "Се вчитува…", + "header_top_sites": "Врвни мрежни места", + "header_stories": "Врвни написи", + "header_visit_again": "Посети повторно", + "header_bookmarks": "Скорешни обележувачи", + "header_recommended_by": "Препорачано од {provider}", + "header_bookmarks_placeholder": "Сѐ уште немате обележувачи.", + "header_stories_from": "од", + "type_label_visited": "Посетени", + "type_label_bookmarked": "Обележани", + "type_label_synced": "Синхронизирани од други уреди", + "type_label_recommended": "Во тренд", + "type_label_open": "Отворени", + "type_label_topic": "Тема", + "type_label_now": "Сега", + "menu_action_bookmark": "Обележувач", + "menu_action_remove_bookmark": "Отстрани обележувач", + "menu_action_copy_address": "Копирај адреса", + "menu_action_email_link": "Испрати врска…", + "menu_action_open_new_window": "Отвори во нов прозорец", + "menu_action_open_private_window": "Отвори во нов приватен прозорец", + "menu_action_dismiss": "Откажи", + "menu_action_delete": "Избриши од историја", + "menu_action_pin": "Прикачи", + "menu_action_unpin": "Откачи", + "confirm_history_delete_p1": "Дали сте сигурни дека сакате да ја избришете оваа страница отсекаде во Вашата историја на прелистување?", + "confirm_history_delete_notice_p2": "Ова дејство не може да се одврати.", + "menu_action_save_to_pocket": "Зачувај во Pocket", + "search_for_something_with": "Пребарај за {search_term} со:", + "search_button": "Барај", + "search_header": "Пребарување со {search_engine_name}", + "search_web_placeholder": "Пребарајте на Интернет", + "search_settings": "Промени поставувања за пребарување", + "section_info_option": "Инфо", + "welcome_title": "Добредојдовте во новото јазиче", + "welcome_body": "Firefox ќе го искористи овој простор за да Ви ги прикаже најрелевантните обележувачи, написи, видеа и страници што сте ги посетиле, за да можете лесно да им се навратите.", + "welcome_label": "Ги откривам Вашите интереси", + "time_label_less_than_minute": "< 1 м", + "time_label_minute": "{number} м", + "time_label_hour": "{number} ч", + "time_label_day": "{number} д", + "settings_pane_button_label": "Прилагодете ја страницата на Вашето Ново јазиче", + "settings_pane_header": "Преференци за Ново јазиче", + "settings_pane_body": "Изберете што ќе гледате кога ќе отворите ново јазиче.", + "settings_pane_search_header": "Пребарување", + "settings_pane_search_body": "Пребарајте низ Интернет од Вашето ново јазиче.", + "settings_pane_topsites_header": "Врвни мрежни места", + "settings_pane_topsites_body": "Пристапете до мрежните места што ги посетувате најмногу.", + "settings_pane_topsites_options_showmore": "Прикажи два реда", + "settings_pane_bookmarks_header": "Скорешни обележувачи", + "settings_pane_bookmarks_body": "Вашите нови обележувачи во едно згодно место.", + "settings_pane_visit_again_header": "Посети повторно", + "settings_pane_visit_again_body": "Firefox ќе прикаже делови од Вашата историја на прелистување кои можеби би сакале да ги запомните или пак да им се навратите.", + "settings_pane_pocketstories_header": "Врвни написи", + "settings_pane_pocketstories_body": "Pocket, дел од семејството на Mozilla, ќе Ви помогне да стигнете до високо-квалитетни содржини кои можеби не би ги откриле на друг начин.", + "settings_pane_done_button": "Готово", + "edit_topsites_button_text": "Уреди", + "edit_topsites_button_label": "Прилагодете ги Вашите Врвни мрежни места", + "edit_topsites_showmore_button": "Прикажи повеќе", + "edit_topsites_showless_button": "Прикажи помалку", + "edit_topsites_done_button": "Готово", + "edit_topsites_pin_button": "Прикачи го ова мрежно место", + "edit_topsites_unpin_button": "Откачи го ова мрежно место", + "edit_topsites_edit_button": "Уреди го ова место", + "edit_topsites_dismiss_button": "Отфрли го ова место", + "edit_topsites_add_button": "Додај", + "topsites_form_add_header": "Ново врвно мрежно место", + "topsites_form_edit_header": "Уреди врвно мрежно место", + "topsites_form_title_placeholder": "Внесете наслов", + "topsites_form_url_placeholder": "Внесете или вметнете URL", + "topsites_form_add_button": "Додај", + "topsites_form_save_button": "Сними", + "topsites_form_cancel_button": "Откажи", + "topsites_form_url_validation": "Потребен е валиден URL", + "pocket_read_more": "Популарни теми:", + "pocket_read_even_more": "Види повеќе написи", + "pocket_feedback_header": "Најдоброто од Интернет, одбрано од повеќе од 25 милиони луѓе.", + "pocket_feedback_body": "Pocket, дел од семејството на Mozilla, ќе Ви помогне да стигнете до високо-квалитетни содржини кои можеби не би ги откриле на друг начин.", + "pocket_send_feedback": "Остави коментар", + "topstories_empty_state": "Имате видено сѐ! Навратете се подоцна за нови содржини од {provider}. Не можете да чекате? Изберете популарна тема и откријте уште одлични содржини ширум Интернет.", + "manual_migration_explanation": "Пробајте го Firefox со Вашите омилени мрежни места и обележувачи од друг прелистувач.", + "manual_migration_cancel_button": "Не, благодарам", + "manual_migration_import_button": "Увези сега" + }, "ml": { "newtab_page_title": "പുതിയ ടാബ്", "default_label_loading": "ലോഡ്ചെയ്യുന്നു…", @@ -3803,12 +4122,19 @@ "newtab_page_title": "नयाँ ट्याब", "default_label_loading": "लोड हुदैँछ...", "header_top_sites": "शीर्ष साइटहरु", - "header_highlights": "विशेषताहरू", + "header_stories": "शीर्ष साइटहरु", + "header_visit_again": "फेरि भ्रमण गर्नुहोस्", + "header_bookmarks": "भर्खरैका पुस्तकचिनोहरु", + "header_recommended_by": "{provider} द्वारा सिफारिस गरिएको", + "header_bookmarks_placeholder": "तपाइँसँग अहिले सम्म कुनै पुस्तकचिनोहरु छैन ।", + "header_stories_from": "बाट", "type_label_visited": "भ्रमण गरिएको", "type_label_bookmarked": "पुस्तकचिनो लागाइएको", "type_label_synced": "अर्को यण्त्रबाट समक्रमण गरिएको", + "type_label_recommended": "प्रचलनमा", "type_label_open": "खोल्नुहोस्", "type_label_topic": "शीर्षक", + "type_label_now": "अहिले", "menu_action_bookmark": "पुस्तकचिनो", "menu_action_remove_bookmark": "पुस्तकचिनो हटाउनुहोस्", "menu_action_copy_address": "ठेगाना प्रतिलिपि गर्नुहोस्", @@ -3817,16 +4143,50 @@ "menu_action_open_private_window": "नयाँ निजी सञ्झ्यालमा खोल्नुहोस्", "menu_action_dismiss": "खारेज गर्नुहोस्", "menu_action_delete": "इतिहासबाट मेट्नुहोस्", + "menu_action_pin": "पिन गर्नुहोस्", + "menu_action_unpin": "अन पिन गर्नुहोस्", + "confirm_history_delete_p1": "के तपाईं पक्का हुनुहुन्छ कि तपाइँ यस पृष्ठको हरेक उदाहरण तपाइँको इतिहासबाट हटाउन चाहनुहुन्छ ?", + "confirm_history_delete_notice_p2": "यो कार्य पूर्ववत गर्न सकिँदैन ।", + "menu_action_save_to_pocket": "Pocketमा बचत गर्नुहोस्", "search_for_something_with": "{search_term} खोज्न प्रयोग गर्नुहोस्:", + "search_button": "खोजी गर्नुहोस्", "search_header": "{search_engine_name} खोजी", "search_web_placeholder": "वेबमा खोज्नुहोस्", "search_settings": "खोजी सेटिङ परिवर्तन गर्नुहोस्", + "section_info_option": "जानकारी", "welcome_title": "नयाँ ट्याबमा स्वागत छ", + "welcome_body": "Firefoxले यस ठाउँको प्रयोग तपाईंको सबैभन्दा सान्दर्भिक पुस्तकचिनो, लेखहरू, भिडियोहरू, र तपाईंले हालै भ्रमण गर्नु भएको पृष्ठहरूलाई राख्न प्रयोग गर्दछ, जसले गर्दा तपाइँ तिनीहरूलाई सजिलै भेटाउन सक्नुहुनेछ ।", "welcome_label": "तपाईँका विशेषताहरु पत्ता लगाउँदै", "time_label_less_than_minute": "< १ मिनेट", "time_label_minute": "{number} मिनेट", "time_label_hour": "{number} घण्टा", - "time_label_day": "{number} दिन" + "time_label_day": "{number} दिन", + "settings_pane_button_label": "तपाईंको नयाँ ट्याब पृष्ठ अनुकूलन गर्नुहोस्", + "settings_pane_header": "नयाँ ट्याब प्राथमिकताहरू", + "settings_pane_body": "तपाईंले नयाँ ट्याब खोल्ने बेलामा के देख्नु चाहनुहुन्छ भन्ने छनौट गर्नुहोस् ।", + "settings_pane_search_header": "खोजी गर्नुहोस्", + "settings_pane_search_body": "तपाईंको नयाँ ट्याबबाट वेबमा खोज्नुहोस् ।", + "settings_pane_topsites_body": "तपाईले धेरै भ्रमण गर्नुभएका वेबसाइटहरूमा पहुँच गर्नुहोस् ।", + "settings_pane_topsites_options_showmore": "दुई पङ्क्तिहरू देखाउनुहोस्", + "settings_pane_bookmarks_header": "भर्खरैका पुस्तकचिनोहरु", + "settings_pane_bookmarks_body": "तपाईंको नयाँ सिर्जना गरिएको पुस्तकचिनोहरुहरू एउटा सजिलो स्थानमा ।", + "settings_pane_visit_again_header": "फेरि भ्रमण गर्नुहोस्", + "settings_pane_pocketstories_header": "शीर्ष कथाहरू", + "settings_pane_done_button": "सम्पन्न भयो", + "edit_topsites_button_text": "सम्पादन गर्नुहोस्", + "edit_topsites_button_label": "तपाईंको शीर्ष साइट खण्ड अनुकूलन गर्नुहोस्", + "edit_topsites_showmore_button": "थप देखाउनुहोस्", + "edit_topsites_showless_button": "थोरै देखाउनुहोस्", + "edit_topsites_done_button": "सम्पन्न भयो", + "edit_topsites_pin_button": "यस साइटलाई पिन गर्नुहोस्", + "edit_topsites_unpin_button": "यस साइटलाई अनपिन गर्नुहोस्", + "edit_topsites_edit_button": "यस साइटलाई सम्पादन गर्नुहोस्", + "edit_topsites_dismiss_button": "यस साइटलाई खारेज गर्नुहोस्", + "edit_topsites_add_button": "थप्नुहोस्", + "topsites_form_add_header": "नयाँ शीर्ष साइट", + "topsites_form_edit_header": "शीर्ष साइट सम्पादन गर्नुहोस्", + "manual_migration_cancel_button": "पर्दैन, धन्यबाद", + "manual_migration_import_button": "अहिले आयात गर्नुहोस्" }, "nl": { "newtab_page_title": "Nieuw tabblad", @@ -3937,7 +4297,7 @@ "menu_action_email_link": "E-postlenke…", "menu_action_open_new_window": "Opne i nytt vindauge", "menu_action_open_private_window": "Opne i eit nytt privat vindauge", - "menu_action_dismiss": "Avslå", + "menu_action_dismiss": "Avvis", "menu_action_delete": "Slett frå historikk", "menu_action_pin": "Fest", "menu_action_unpin": "L:ys", @@ -4193,7 +4553,7 @@ "settings_pane_visit_again_header": "Visite novamente", "settings_pane_visit_again_body": "Firefox irá exibir a você partes do seu histórico de navegação que você pode querer relembrar ou acessar novamente.", "settings_pane_pocketstories_header": "Histórias populares", - "settings_pane_pocketstories_body": "O Pocket, parte da família Mozilla, irá ajudar a conecta-se a conteúdo de alta qualidade que talvez não tenha encontrado de outra forma.", + "settings_pane_pocketstories_body": "O Pocket, parte da família Mozilla, ajudará a conectá-lo a conteúdo de alta qualidade que talvez você não encontre de outra forma.", "settings_pane_done_button": "Concluído", "edit_topsites_button_text": "Editar", "edit_topsites_button_label": "Personalizar a sua seção de sites preferidos", @@ -4216,7 +4576,7 @@ "pocket_read_more": "Tópicos populares:", "pocket_read_even_more": "Ver mais histórias", "pocket_feedback_header": "O melhor da web, com curadoria de mais de 25 milhões de pessoas.", - "pocket_feedback_body": "O Pocket, parte da família Mozilla, irá ajudar a conecta-se a conteúdo de alta qualidade que talvez não tenha encontrado de outra forma.", + "pocket_feedback_body": "O Pocket, parte da família Mozilla, ajudará a conectá-lo a conteúdo de alta qualidade que talvez você não encontre de outra forma.", "pocket_send_feedback": "Enviar feedback", "topstories_empty_state": "Você já viu tudo. Volte mais tarde para mais histórias do {provider}. Não consegue esperar? Escolha um assunto popular para encontrar mais grandes histórias através da web.", "manual_migration_explanation": "Use o Firefox com seus sites favoritos de outro navegador.", @@ -4399,13 +4759,14 @@ "newtab_page_title": "Filă nouă", "default_label_loading": "Se încarcă…", "header_top_sites": "Site-uri de top", - "header_highlights": "Evidențieri", + "header_recommended_by": "Recomandat de {provider}", "header_stories_from": "de la", "type_label_visited": "Vizitate", "type_label_bookmarked": "Însemnat", "type_label_synced": "Sincronizat de pe alt dispozitiv", "type_label_open": "Deschise", "type_label_topic": "Subiect", + "type_label_now": "Acum", "menu_action_bookmark": "Însemnează", "menu_action_remove_bookmark": "Elimină semnul de carte", "menu_action_copy_address": "Copiază adresa", @@ -4414,11 +4775,14 @@ "menu_action_open_private_window": "Deschide într-o fereastră privată nouă", "menu_action_dismiss": "Înlătură", "menu_action_delete": "Șterge din istoric", + "confirm_history_delete_notice_p2": "Această acțiune este ireversibilă.", + "menu_action_save_to_pocket": "Salvează în Pocket", "search_for_something_with": "Caută {search_term} cu: ", "search_button": "Caută", "search_header": "Căutare {search_engine_name}", "search_web_placeholder": "Caută pe web", "search_settings": "Schimbă setările de căutare", + "section_info_option": "Informații", "welcome_title": "Bun venit în noua filă", "welcome_body": "Firefox va folosi acest spațiu pentru a arăta cele mai relevante semne de carte, articole, videouri și pagini vizitate recent pentru a reveni la acestea ușor.", "welcome_label": "Se identifică evidențierile tale", @@ -4434,8 +4798,6 @@ "settings_pane_topsites_header": "Site-uri de top", "settings_pane_topsites_body": "Accesează site-urile pe care le vizitezi mai des.", "settings_pane_topsites_options_showmore": "Arată două rânduri", - "settings_pane_highlights_header": "Evidențieri", - "settings_pane_highlights_body": "Privește înapoi la istoricul de navigare recent și noile semne de carte create.", "settings_pane_done_button": "Gata", "edit_topsites_button_text": "Editează", "edit_topsites_button_label": "Particularizează secțiunea site-urilor de top", @@ -4455,7 +4817,8 @@ "topsites_form_cancel_button": "Renunță", "topsites_form_url_validation": "URL valid necesar", "pocket_read_more": "Subiecte populare:", - "pocket_send_feedback": "Trimite feedback" + "pocket_send_feedback": "Trimite feedback", + "manual_migration_cancel_button": "Nu, mulțumesc" }, "ru": { "newtab_page_title": "Новая вкладка", @@ -4676,8 +5039,8 @@ "settings_pane_button_label": "Prilagodite stran novega zavihka", "settings_pane_header": "Nastavitve novega zavihka", "settings_pane_body": "Izberite, kaj naj se prikaže, ko odprete nov zavihek.", - "settings_pane_search_header": "Išči", - "settings_pane_search_body": "Iščite po spletu s strani novega zavihka.", + "settings_pane_search_header": "Iskanje", + "settings_pane_search_body": "Iščite po spletu z novega zavihka.", "settings_pane_topsites_header": "Glavne strani", "settings_pane_topsites_body": "Priročen dostop do najbolj obiskanih strani.", "settings_pane_topsites_options_showmore": "Prikaži dve vrsti", @@ -4946,6 +5309,7 @@ "header_stories": "முக்கிய கதைகள்", "header_visit_again": "மீண்டும் வருக", "header_bookmarks": "சமீபத்திய புத்தகக்குறிகள்", + "header_recommended_by": "{provider} என்பவரால் பரிந்துரைக்கப்பட்டது", "header_bookmarks_placeholder": "நீங்கள் புத்தகக்குறிகளைக் கொண்டிருக்கவில்லை .", "header_stories_from": "அனுப்பியவர்", "type_label_visited": "பார்த்தவை", @@ -4954,6 +5318,7 @@ "type_label_recommended": "பிரபலமான", "type_label_open": "திற", "type_label_topic": "தலைப்பு", + "type_label_now": "இப்போது", "menu_action_bookmark": "புத்தகக்குறி", "menu_action_remove_bookmark": "புத்தகக்குறியை நீக்கு", "menu_action_copy_address": "முகவரியை நகலெடு", @@ -4972,6 +5337,7 @@ "search_header": "{search_engine_name} தேடுபொறியில் தேடு", "search_web_placeholder": "இணையத்தில் தேடு", "search_settings": "தேடல் அமைவுகளை மாற்று", + "section_info_option": "தகவல்", "welcome_title": "புதிய கீற்றுக்கு வருக", "welcome_body": "உங்களுக்கு மிகவும் பொருத்தமான புத்தகக்குறிகள், கட்டுரைகள், காணொளிகள் மற்றும் சமீபத்தில் பார்வையிட்ட பக்கங்களைக் காண்பிக்க பயர்பாக்ஸ் இந்த இடத்தைப் பயன்படுத்தும், எனவே நீங்கள் அவற்றை எளிதாகத் திரும்பப் பெறலாம்.", "welcome_label": "உங்களின் முக்கியம்சங்களை அடையாளம் காண்கிறோம்", @@ -5016,7 +5382,9 @@ "pocket_read_even_more": "இன்னும் கதைகளைப் பார்க்கவும்", "pocket_feedback_header": "இணையத்தின் சிறந்த செயலி, 250 இலட்ச மக்களால் தேர்ந்தெடுக்கப்பட்டது.", "pocket_feedback_body": "Pocket, ஒரு மொசில்லா குடும்ப உறுப்பினராக, உயர்தர உள்ளடக்கங்களுடன் இணைய உதவுகிறது, இது இல்லையேல் அது சாத்தியமாகது.", - "pocket_send_feedback": "கருத்துகளைத் தெறிவிக்கவும்" + "pocket_send_feedback": "கருத்துகளைத் தெறிவிக்கவும்", + "manual_migration_cancel_button": "பரவாயில்லை", + "manual_migration_import_button": "இப்போது இறக்கு" }, "ta-LK": {}, "te": { @@ -5026,7 +5394,8 @@ "header_stories": "ముఖ్య కథనాలు", "header_visit_again": "మళ్లీ సందర్శించండి", "header_bookmarks": "ఇటీవలి ఇష్టాంశములు", - "header_bookmarks_placeholder": "మీకు ఇంకా బుక్మార్క్లు లేవు.", + "header_recommended_by": "{provider}చే సిఫార్సు చేయబడినది", + "header_bookmarks_placeholder": "మీకు ఇంకా ఎటువంటి ఇష్టాంశాలు లేవు.", "header_stories_from": "నుండి", "type_label_visited": "సందర్శించినవి", "type_label_bookmarked": "ఇష్టాంశము చేయబడినది", @@ -5034,6 +5403,7 @@ "type_label_recommended": "ట్రెండింగ్", "type_label_open": "తెరువు", "type_label_topic": "విషయం", + "type_label_now": "ఇప్పుడు", "menu_action_bookmark": "ఇష్టాంశము", "menu_action_remove_bookmark": "ఇష్టాంశాన్ని తొలగించు", "menu_action_copy_address": "చిరునామా కాపీ చెయ్యండి", @@ -5042,8 +5412,8 @@ "menu_action_open_private_window": "కొత్త వ్యక్తిగత విండోలో తెరువు", "menu_action_dismiss": "విస్మరించు", "menu_action_delete": "చరిత్ర నుంచి తీసివేయి", - "menu_action_pin": "పిన్", - "menu_action_unpin": "పిన్ తీసివేయి", + "menu_action_pin": "పిన్ను", + "menu_action_unpin": "పిన్ను తీసివేయి", "confirm_history_delete_p1": "మీరు మీ చరిత్ర నుండి ఈ పేజీ యొక్క ప్రతి ఉదాహరణకు తొలగించాలనుకుంటున్నారా?", "confirm_history_delete_notice_p2": "ఈ చర్యను రద్దు చేయలేము.", "menu_action_save_to_pocket": "Pocket కి సేవ్ చేయండి", @@ -5052,6 +5422,7 @@ "search_header": "{search_engine_name} శోధన", "search_web_placeholder": "జాలంలో వెతకండి", "search_settings": "శోధన అమరికలు మార్చు", + "section_info_option": "సమాచారం", "welcome_title": "కొత్త ట్యాబుకు స్వాగతం", "welcome_body": "సముచితమైన మీ ఇష్టాంశాలను, వ్యాసాలను, వీడియోలను, ఇంకా మీరు ఇటీవలే చూసిన పేజీలను మీకు తేలిగ్గా అందుబాటులో ఉంచేందుకు Firefox ఈ జాగాని వాడుకుంటుంది.", "welcome_label": "మీ ముఖ్యాంశాలను గుర్తిస్తున్నది", @@ -5080,7 +5451,7 @@ "edit_topsites_showless_button": "కొన్నే చూపించు", "edit_topsites_done_button": "పూర్తయింది", "edit_topsites_pin_button": "ఈ సైటును ఇక్కడ గుచ్చు", - "edit_topsites_unpin_button": "ఈ సైట్ను అన్పిన్ చేయండి", + "edit_topsites_unpin_button": "ఈ సైటుకి పిన్నుని తీసివేయండి", "edit_topsites_edit_button": "ఈ సైటును మార్చు", "edit_topsites_dismiss_button": "ఈ సైటుని తీసివేయి", "edit_topsites_add_button": "జోడించు", @@ -5096,7 +5467,11 @@ "pocket_read_even_more": "మరిన్ని కథలను వీక్షించండి", "pocket_feedback_header": "వెబ్లో అత్యుత్తమమైనది, 25 మిలియన్లకు పైగా ప్రజలు పర్యవేక్షించినవి.", "pocket_feedback_body": "Mozilla కుటుంబం యొక్క Pocket, మీరు కనుగొనలేకపోయే అధిక-నాణ్యత విషయముకి మిమ్మల్ని అనుసంధానించడానికి సహాయపడుతుంది.", - "pocket_send_feedback": "అభిప్రాయాన్ని పంపండి" + "pocket_send_feedback": "అభిప్రాయాన్ని పంపండి", + "topstories_empty_state": "మీరు పట్టుబడ్డారు. {provider} నుండి మరింత అగ్ర కథనాల కోసం తరువాత తనిఖీ చేయండి. వేచి ఉండలేరా? జాలములోని అంతటి నుండి మరింత గొప్ప కథనాలను కనుగొనడానికి ప్రసిద్ధ అంశం ఎంచుకోండి.", + "manual_migration_explanation": "మరొక విహరణి నుండి మీకు ఇష్టమైన సైట్లు మరియు ఇష్టంశాలతో Firefox ను ప్రయత్నించండి.", + "manual_migration_cancel_button": "అడిగినందుకు ధన్యవాదాలు, వద్దు", + "manual_migration_import_button": "ఇప్పుడే దిగుమతి చేయండి" }, "th": { "newtab_page_title": "แท็บใหม่", @@ -5124,7 +5499,7 @@ "menu_action_dismiss": "ยกเลิก", "menu_action_delete": "ลบออกจากประวัติ", "menu_action_pin": "ปักหมุด", - "menu_action_unpin": "ถอดหมุด", + "menu_action_unpin": "ถอนหมุด", "confirm_history_delete_notice_p2": "การกระทำนี้ไม่สามารถเลิกทำได้", "menu_action_save_to_pocket": "บันทึกไปยัง Pocket", "search_for_something_with": "ค้นหาสำหรับ {search_term} ด้วย:", @@ -5159,7 +5534,7 @@ "edit_topsites_showless_button": "แสดงน้อยลง", "edit_topsites_done_button": "เสร็จสิ้น", "edit_topsites_pin_button": "ปักหมุดไซต์นี้", - "edit_topsites_unpin_button": "ถอดหมุดไซต์นี้", + "edit_topsites_unpin_button": "ถอนหมุดไซต์นี้", "edit_topsites_edit_button": "แก้ไขไซต์นี้", "edit_topsites_dismiss_button": "ไม่สนใจไซต์นี้", "edit_topsites_add_button": "เพิ่ม", @@ -5344,7 +5719,7 @@ "menu_action_pin": "Прикріпити", "menu_action_unpin": "Відкріпити", "confirm_history_delete_p1": "Ви справді хочете видалити всі записи про цю сторінку з історії?", - "confirm_history_delete_notice_p2": "Цю дію неможливо відмінити.", + "confirm_history_delete_notice_p2": "Цю дію неможливо скасувати.", "menu_action_save_to_pocket": "Зберегти в Pocket", "search_for_something_with": "Шукати {search_term} з:", "search_button": "Пошук", @@ -5409,6 +5784,7 @@ "header_stories": "بہترین کہانیاں", "header_visit_again": "دوبارہ دورہ کریں", "header_bookmarks": "حالیہ نشانیاں", + "header_recommended_by": "{provider} کی جانب سے تجویز کردہ", "header_stories_from": "من جانب", "type_label_visited": "دورہ شدہ", "type_label_bookmarked": "نشان شدہ", @@ -5416,6 +5792,7 @@ "type_label_recommended": "رجحان سازی", "type_label_open": "کھولیں", "type_label_topic": "عنوان", + "type_label_now": "ابھی", "menu_action_bookmark": "نشانی", "menu_action_remove_bookmark": "نشانى ہٹائيں", "menu_action_copy_address": "پتہ نقل کریں", @@ -5425,6 +5802,7 @@ "menu_action_dismiss": "برخاست کریں", "menu_action_delete": "تاریخ سے حذف کریں", "menu_action_pin": "پن", + "menu_action_unpin": "ان پن", "confirm_history_delete_p1": "کیا آپ کو یقین ہے کہ آپ اس صفحہ کا ہر نمونہ اپنے سابقات سے حذف کرنا چاہتے ہیں؟", "confirm_history_delete_notice_p2": "یہ عمل کلعدم نہیں ہو سکتا۔", "menu_action_save_to_pocket": "Pocket میں محفوظ کریں", @@ -5433,6 +5811,7 @@ "search_header": "{search_engine_name} پر تلاش کریں", "search_web_placeholder": "ويب پر تلاش کريں", "search_settings": "تلاش کی سیٹکگیں تبدیل کریں", + "section_info_option": "معلومات", "welcome_title": "نئے ٹیب میں خوش آمدید", "welcome_body": "اس جگہ کا استعمال کرنے ہوئے Firefox آپکی متعلقہ نشانیاں، عبارات، وڈیوز اور صفحات جن کا حال ہی میں ص آُپ نے دورہ کیا ہے دکھائے گا۔ تاکہ آپ ان تک واپس آسانی سے پہنچ سکیں۔", "welcome_label": "آپکی جھلکیوں کی نشاندہی کر رہا ہے", @@ -5455,6 +5834,7 @@ "edit_topsites_button_text": "تدوین", "edit_topsites_button_label": "اپنی بہترین سائٹس والے حصے کی تخصیص کریں", "edit_topsites_showmore_button": "مزید دکھائیں", + "edit_topsites_showless_button": "کم دکھائیں", "edit_topsites_done_button": "ہوگیا", "edit_topsites_pin_button": "اس سائَٹ کو پن کریں", "edit_topsites_unpin_button": "اس سائٹ کو انپن کریں", @@ -5472,9 +5852,14 @@ "pocket_read_more": "مشہور مضامین:", "pocket_read_even_more": "مزید کہانیاں دیکھیں", "pocket_feedback_body": "Pocket ایک جصہ ہے Mozilla کے خاندان کا،آپ کو اعلی میعار کے مواد سے جڑنے میں مدد دے گا جو شاید آپ بصورت دیگر نہ ڈھونڈ سکتے۔", - "pocket_send_feedback": "جواب الجواب ارسال کریں" + "pocket_send_feedback": "جواب الجواب ارسال کریں", + "manual_migration_cancel_button": "نہیں شکریہ", + "manual_migration_import_button": "ابھی درآمد کری" + }, + "uz": { + "pocket_send_feedback": "Fikr-mulohaza joʻnatish", + "manual_migration_cancel_button": "Yoʻq, kerak emas" }, - "uz": {}, "vi": { "newtab_page_title": "Tab mới", "default_label_loading": "Đang tải…", @@ -5491,6 +5876,7 @@ "type_label_recommended": "Xu hướng", "type_label_open": "Mở", "type_label_topic": "Chủ đề", + "type_label_now": "Bây giờ", "menu_action_bookmark": "Đánh dấu", "menu_action_remove_bookmark": "Xóa đánh dấu", "menu_action_copy_address": "Chép địa chỉ", @@ -5514,9 +5900,12 @@ "time_label_minute": "{number}phút", "time_label_hour": "{number}giờ", "time_label_day": "{number}ngày", + "settings_pane_button_label": "Tùy biến trang Tab mới", "settings_pane_header": "Tùy chỉnh cho tab mới", "settings_pane_body": "Chọn cái bạn muốn tải khi một tab mới được mở ra.", "settings_pane_search_header": "Tìm kiếm", + "settings_pane_topsites_body": "Truy cập vào các trang web mà bạn truy cập vào nhiều nhất.", + "settings_pane_topsites_options_showmore": "Hiển thị hai hàng", "settings_pane_done_button": "Xong", "edit_topsites_button_text": "Chỉnh sửa", "edit_topsites_showmore_button": "Xem thêm", @@ -5540,7 +5929,7 @@ "newtab_page_title": "新标签页", "default_label_loading": "正在载入…", "header_top_sites": "常用网站", - "header_stories": "热门报道", + "header_stories": "热门文章", "header_visit_again": "再次造访", "header_bookmarks": "最近的书签", "header_recommended_by": "{provider} 推荐", @@ -5558,8 +5947,8 @@ "menu_action_copy_address": "复制地址", "menu_action_email_link": "用邮件发送链接…", "menu_action_open_new_window": "在新窗口中打开", - "menu_action_open_private_window": "在新的隐私浏览窗口中打开", - "menu_action_dismiss": "消除", + "menu_action_open_private_window": "在新的隐私窗口中打开", + "menu_action_dismiss": "隐藏", "menu_action_delete": "从历史记录中删除", "menu_action_pin": "固定", "menu_action_unpin": "取消固定", @@ -5581,7 +5970,7 @@ "time_label_day": "{number} 天前", "settings_pane_button_label": "定制您的新标签页", "settings_pane_header": "新标签页选项", - "settings_pane_body": "选择您在新标签页上看到哪些组件。", + "settings_pane_body": "选择打开新标签页时想看到什么。", "settings_pane_search_header": "搜索", "settings_pane_search_body": "从您的新标签页搜索网络。", "settings_pane_topsites_header": "常用网站", @@ -5590,9 +5979,9 @@ "settings_pane_bookmarks_header": "最近的书签", "settings_pane_bookmarks_body": "您最近创建的书签将在此显示。", "settings_pane_visit_again_header": "再次造访", - "settings_pane_visit_again_body": "Firefox 在此显示您可能想记住或再次访问的浏览记录。", + "settings_pane_visit_again_body": "Firefox 在此显示您可能想记住或将再次访问的浏览记录。", "settings_pane_pocketstories_header": "热门报道", - "settings_pane_pocketstories_body": "Pocket,Mozilla 家族的一员,它可以帮助您找到更多不易发现的高品质内容。", + "settings_pane_pocketstories_body": "Pocket 是 Mozilla 家族的一员,可以将您未发现的高品质内容带到眼前。", "settings_pane_done_button": "完成", "edit_topsites_button_text": "编辑", "edit_topsites_button_label": "定制您的“常用网站”区域", @@ -5613,11 +6002,11 @@ "topsites_form_cancel_button": "取消", "topsites_form_url_validation": "需要有效的网址", "pocket_read_more": "热门主题:", - "pocket_read_even_more": "查看更多报道", - "pocket_feedback_header": "超过2500万人构成的互联网。", - "pocket_feedback_body": "Pocket,Mozilla 家族的一员,它可以帮助您找到更多不易发现的高品质内容。", + "pocket_read_even_more": "查看更多文章", + "pocket_feedback_header": "由超过 2500 万人挑选出来的网上精华内容。", + "pocket_feedback_body": "Pocket 是 Mozilla 家族的一员,可以将您未发现的高品质内容带到眼前。", "pocket_send_feedback": "发送反馈", - "topstories_empty_state": "已经都看过了。请过会再来查看 {provider} 提供的热门故事。不想等待?选择一个热门话题,找到网络上的更多好故事。", + "topstories_empty_state": "所有文章都读完啦!晚点再来,{provider} 将推荐更多热门文章。等不及了?选择一个热门话题,找到更多网上的好文章。", "manual_migration_explanation": "将您在其他浏览器中常用的网站和书签导入 Firefox,体验别具一格的浏览器。", "manual_migration_cancel_button": "不用了", "manual_migration_import_button": "立即导入" diff --git a/browser/extensions/activity-stream/lib/ActivityStream.jsm b/browser/extensions/activity-stream/lib/ActivityStream.jsm index dc4b0a1b1d01..42d580e76e00 100644 --- a/browser/extensions/activity-stream/lib/ActivityStream.jsm +++ b/browser/extensions/activity-stream/lib/ActivityStream.jsm @@ -51,15 +51,15 @@ const PREFS_CONFIG = new Map([ api_key_pref: "extensions.pocket.oAuthConsumerKey", // Use the opposite value as what default value the feed would have used hidden: !PREFS_CONFIG.get("feeds.section.topstories").getValue(args), - learn_more_endpoint: "https://getpocket.com/firefox_learnmore?src=ff_newtab", + learn_more_endpoint: "https://getpocket.cdn.mozilla.net/firefox_learnmore?src=ff_newtab", provider_description: "pocket_feedback_body", provider_icon: "pocket", provider_name: "Pocket", - read_more_endpoint: "https://getpocket.com/explore/trending?src=ff_new_tab", - stories_endpoint: `https://getpocket.com/v3/firefox/global-recs?consumer_key=$apiKey&locale_lang=${args.locale}`, + read_more_endpoint: "https://getpocket.cdn.mozilla.net/explore/trending?src=ff_new_tab", + stories_endpoint: `https://getpocket.cdn.mozilla.net/v3/firefox/global-recs?version=2&consumer_key=$apiKey&locale_lang=${args.locale}`, stories_referrer: "https://getpocket.com/recommendations", survey_link: "https://www.surveymonkey.com/r/newtabffx", - topics_endpoint: `https://getpocket.com/v3/firefox/trending-topics?consumer_key=$apiKey&locale_lang=${args.locale}` + topics_endpoint: `https://getpocket.cdn.mozilla.net/v3/firefox/trending-topics?version=2&consumer_key=$apiKey&locale_lang=${args.locale}` }) }], ["migrationExpired", { diff --git a/browser/extensions/activity-stream/lib/ActivityStreamMessageChannel.jsm b/browser/extensions/activity-stream/lib/ActivityStreamMessageChannel.jsm index 35956f4f4754..14ee2182e515 100644 --- a/browser/extensions/activity-stream/lib/ActivityStreamMessageChannel.jsm +++ b/browser/extensions/activity-stream/lib/ActivityStreamMessageChannel.jsm @@ -137,8 +137,10 @@ this.ActivityStreamMessageChannel = class ActivityStreamMessageChannel { // Some pages might have already loaded, so we won't get the usual message for (const {loaded, portID} of this.channel.messagePorts) { + const simulatedMsg = {target: {portID}}; + this.onNewTabInit(simulatedMsg); if (loaded) { - this.onNewTabLoad({target: {portID}}); + this.onNewTabLoad(simulatedMsg); } } } diff --git a/browser/extensions/activity-stream/lib/LocalizationFeed.jsm b/browser/extensions/activity-stream/lib/LocalizationFeed.jsm index 03c9277eae81..297f1cb5ceb8 100644 --- a/browser/extensions/activity-stream/lib/LocalizationFeed.jsm +++ b/browser/extensions/activity-stream/lib/LocalizationFeed.jsm @@ -30,21 +30,18 @@ this.LocalizationFeed = class LocalizationFeed { } updateLocale() { - // Just take the first element in the result array, as it should be - // the best locale - let locale = Services.locale.negotiateLanguages( + // Order locales based on what we have available with fallback always first + const locales = Services.locale.negotiateLanguages( Services.locale.getAppLocalesAsLangTags(), // desired locales Object.keys(this.allStrings), // available locales DEFAULT_LOCALE // fallback - )[0]; + ).reverse(); - let strings = this.allStrings[locale]; - - // Use the default strings for any that are missing - if (locale !== DEFAULT_LOCALE) { - strings = Object.assign({}, this.allStrings[DEFAULT_LOCALE], strings || {}); - } + // Start with default (first) locale then merge in strings of better locales + const strings = Object.assign({}, ...locales.map(l => this.allStrings[l])); + // Use the best (last) locale as the primary locale + const locale = locales.pop(); this.store.dispatch(ac.BroadcastToContent({ type: at.LOCALE_UPDATED, data: { diff --git a/browser/extensions/activity-stream/lib/SectionsManager.jsm b/browser/extensions/activity-stream/lib/SectionsManager.jsm index 0c0ee1b3bbb3..5f101aa65ffd 100644 --- a/browser/extensions/activity-stream/lib/SectionsManager.jsm +++ b/browser/extensions/activity-stream/lib/SectionsManager.jsm @@ -7,21 +7,85 @@ const {utils: Cu} = Components; const {actionCreators: ac, actionTypes: at} = Cu.import("resource://activity-stream/common/Actions.jsm", {}); Cu.import("resource://gre/modules/EventEmitter.jsm"); +/* + * Generators for built in sections, keyed by the pref name for their feed. + * Built in sections may depend on options stored as serialised JSON in the pref + * `${feed_pref_name}.options`. + */ +const BUILT_IN_SECTIONS = { + "feeds.section.topstories": options => ({ + id: "topstories", + pref: { + titleString: {id: "header_recommended_by", values: {provider: options.provider_name}}, + descString: {id: options.provider_description} + }, + shouldHidePref: options.hidden, + eventSource: "TOP_STORIES", + icon: options.provider_icon, + title: {id: "header_recommended_by", values: {provider: options.provider_name}}, + maxRows: 1, + contextMenuOptions: ["CheckBookmark", "SaveToPocket", "Separator", "OpenInNewWindow", "OpenInPrivateWindow", "Separator", "BlockUrl"], + infoOption: { + header: {id: "pocket_feedback_header"}, + body: {id: options.provider_description}, + link: {href: options.survey_link, id: "pocket_send_feedback"} + }, + emptyState: { + message: {id: "topstories_empty_state", values: {provider: options.provider_name}}, + icon: "check" + } + }) +}; + const SectionsManager = { ACTIONS_TO_PROXY: ["SYSTEM_TICK", "NEW_TAB_LOAD"], initialized: false, - sections: new Set(), + sections: new Map(), + init(prefs = {}) { + for (const feedPrefName of Object.keys(BUILT_IN_SECTIONS)) { + const optionsPrefName = `${feedPrefName}.options`; + this.addBuiltInSection(feedPrefName, prefs[optionsPrefName]); + } + this.initialized = true; + this.emit(this.INIT); + }, + addBuiltInSection(feedPrefName, optionsPrefValue = "{}") { + let options; + try { + options = JSON.parse(optionsPrefValue); + } catch (e) { + options = {}; + Cu.reportError("Problem parsing options pref", e); + } + const section = BUILT_IN_SECTIONS[feedPrefName](options); + section.pref.feed = feedPrefName; + this.addSection(section.id, Object.assign(section, {options})); + }, addSection(id, options) { - this.sections.add(id); + this.sections.set(id, options); this.emit(this.ADD_SECTION, id, options); }, removeSection(id) { this.emit(this.REMOVE_SECTION, id); this.sections.delete(id); }, - updateRows(id, rows, shouldBroadcast) { + enableSection(id) { + this.updateSection(id, {enabled: true}, true); + }, + disableSection(id) { + this.updateSection(id, {enabled: false, rows: []}, true); + }, + updateSection(id, options, shouldBroadcast) { if (this.sections.has(id)) { - this.emit(this.UPDATE_ROWS, id, rows, shouldBroadcast); + this.sections.set(id, Object.assign(this.sections.get(id), options)); + this.emit(this.UPDATE_SECTION, id, options, shouldBroadcast); + } + }, + onceInitialized(callback) { + if (this.initialized) { + callback(); + } else { + this.once(this.INIT, callback); } } }; @@ -30,7 +94,7 @@ for (const action of [ "ACTION_DISPATCHED", "ADD_SECTION", "REMOVE_SECTION", - "UPDATE_ROWS", + "UPDATE_SECTION", "INIT", "UNINIT" ]) { @@ -41,17 +105,19 @@ EventEmitter.decorate(SectionsManager); class SectionsFeed { constructor() { + this.init = this.init.bind(this); this.onAddSection = this.onAddSection.bind(this); this.onRemoveSection = this.onRemoveSection.bind(this); - this.onUpdateRows = this.onUpdateRows.bind(this); + this.onUpdateSection = this.onUpdateSection.bind(this); } init() { SectionsManager.on(SectionsManager.ADD_SECTION, this.onAddSection); SectionsManager.on(SectionsManager.REMOVE_SECTION, this.onRemoveSection); - SectionsManager.on(SectionsManager.UPDATE_ROWS, this.onUpdateRows); - SectionsManager.initialized = true; - SectionsManager.emit(SectionsManager.INIT); + SectionsManager.on(SectionsManager.UPDATE_SECTION, this.onUpdateSection); + // Catch any sections that have already been added + SectionsManager.sections.forEach((section, id) => + this.onAddSection(SectionsManager.ADD_SECTION, id, section)); } uninit() { @@ -59,7 +125,7 @@ class SectionsFeed { SectionsManager.emit(SectionsManager.UNINIT); SectionsManager.off(SectionsManager.ADD_SECTION, this.onAddSection); SectionsManager.off(SectionsManager.REMOVE_SECTION, this.onRemoveSection); - SectionsManager.off(SectionsManager.UPDATE_ROWS, this.onUpdateRows); + SectionsManager.off(SectionsManager.UPDATE_SECTION, this.onUpdateSection); } onAddSection(event, id, options) { @@ -72,9 +138,9 @@ class SectionsFeed { this.store.dispatch(ac.BroadcastToContent({type: at.SECTION_DEREGISTER, data: id})); } - onUpdateRows(event, id, rows, shouldBroadcast = false) { - if (rows) { - const action = {type: at.SECTION_ROWS_UPDATE, data: {id, rows}}; + onUpdateSection(event, id, options, shouldBroadcast = false) { + if (options) { + const action = {type: at.SECTION_UPDATE, data: Object.assign(options, {id})}; this.store.dispatch(shouldBroadcast ? ac.BroadcastToContent(action) : action); } } @@ -82,7 +148,22 @@ class SectionsFeed { onAction(action) { switch (action.type) { case at.INIT: - this.init(); + SectionsManager.onceInitialized(this.init); + break; + // Wait for pref values, as some sections have options stored in prefs + case at.PREFS_INITIAL_VALUES: + SectionsManager.init(action.data); + break; + case at.PREF_CHANGED: + if (action.data && action.data.name.match(/^feeds.section.(\S+).options$/i)) { + SectionsManager.addBuiltInSection(action.data.name.slice(0, -8), action.data.value); + } + break; + case at.SECTION_DISABLE: + SectionsManager.disableSection(action.data); + break; + case at.SECTION_ENABLE: + SectionsManager.enableSection(action.data); break; } if (SectionsManager.ACTIONS_TO_PROXY.includes(action.type) && SectionsManager.sections.size > 0) { diff --git a/browser/extensions/activity-stream/common/ShortURL.jsm b/browser/extensions/activity-stream/lib/ShortURL.jsm similarity index 63% rename from browser/extensions/activity-stream/common/ShortURL.jsm rename to browser/extensions/activity-stream/lib/ShortURL.jsm index 9721d330b547..5cd07568b006 100644 --- a/browser/extensions/activity-stream/common/ShortURL.jsm +++ b/browser/extensions/activity-stream/lib/ShortURL.jsm @@ -1,4 +1,24 @@ -Components.utils.importGlobalProperties(["URL"]); +const {utils: Cu} = Components; +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); + +XPCOMUtils.defineLazyServiceGetter(this, "IDNService", "@mozilla.org/network/idn-service;1", "nsIIDNService"); + +Cu.importGlobalProperties(["URL"]); + +/** + * Properly convert internationalized domain names. + * @param {string} host Domain hostname. + * @returns {string} Hostname suitable to be displayed. + */ +function handleIDNHost(hostname) { + try { + return IDNService.convertToDisplayIDN(hostname, {}); + } catch (e) { + // If something goes wrong (e.g. host is an IP address) just fail back + // to the full domain. + return hostname; + } +} /** * shortURL - Creates a short version of a link's url, used for display purposes @@ -20,7 +40,8 @@ this.shortURL = function shortURL(link) { return ""; } const {eTLD} = link; - const hostname = (link.hostname || new URL(link.url).hostname).replace(/^www\./i, ""); + const asciiHost = (link.hostname || new URL(link.url).hostname).replace(/^www\./i, ""); + const hostname = handleIDNHost(asciiHost); // Remove the eTLD (e.g., com, net) and the preceding period from the hostname const eTLDLength = (eTLD || "").length || (hostname.match(/\.com$/) && 3); diff --git a/browser/extensions/activity-stream/lib/TopSitesFeed.jsm b/browser/extensions/activity-stream/lib/TopSitesFeed.jsm index 39414eb2c93a..93c948e77298 100644 --- a/browser/extensions/activity-stream/lib/TopSitesFeed.jsm +++ b/browser/extensions/activity-stream/lib/TopSitesFeed.jsm @@ -10,7 +10,7 @@ const {actionCreators: ac, actionTypes: at} = Cu.import("resource://activity-str const {TippyTopProvider} = Cu.import("resource://activity-stream/lib/TippyTopProvider.jsm", {}); const {insertPinned, TOP_SITES_SHOWMORE_LENGTH} = Cu.import("resource://activity-stream/common/Reducers.jsm", {}); const {Dedupe} = Cu.import("resource://activity-stream/common/Dedupe.jsm", {}); -const {shortURL} = Cu.import("resource://activity-stream/common/ShortURL.jsm", {}); +const {shortURL} = Cu.import("resource://activity-stream/lib/ShortURL.jsm", {}); XPCOMUtils.defineLazyModuleGetter(this, "NewTabUtils", "resource://gre/modules/NewTabUtils.jsm"); @@ -20,6 +20,7 @@ XPCOMUtils.defineLazyModuleGetter(this, "Screenshots", const UPDATE_TIME = 15 * 60 * 1000; // 15 minutes const DEFAULT_SITES_PREF = "default.sites"; const DEFAULT_TOP_SITES = []; +const FRECENCY_THRESHOLD = 100; // 1 visit (skip first-run/one-time pages) this.TopSitesFeed = class TopSitesFeed { constructor() { @@ -62,7 +63,9 @@ this.TopSitesFeed = class TopSitesFeed { if (!frecent) { frecent = []; } else { - frecent = frecent.filter(link => link && link.type !== "affiliate"); + // Get the best history links that pass the frecency threshold + frecent = frecent.filter(link => link && link.type !== "affiliate" && + link.frecency > FRECENCY_THRESHOLD); } // Group together websites that require deduping. @@ -154,6 +157,9 @@ this.TopSitesFeed = class TopSitesFeed { case at.PLACES_HISTORY_CLEARED: this.refresh(); break; + case at.BLOCK_URL: // Topsite blocked, we want to get a new one in. + this.refresh(); + break; case at.PREF_CHANGED: if (action.data.name === DEFAULT_SITES_PREF) { this.refreshDefaults(action.data.value); diff --git a/browser/extensions/activity-stream/lib/TopStoriesFeed.jsm b/browser/extensions/activity-stream/lib/TopStoriesFeed.jsm index d616a9a87630..29acd2e3098e 100644 --- a/browser/extensions/activity-stream/lib/TopStoriesFeed.jsm +++ b/browser/extensions/activity-stream/lib/TopStoriesFeed.jsm @@ -9,26 +9,30 @@ Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/NewTabUtils.jsm"); Cu.importGlobalProperties(["fetch"]); -const {actionCreators: ac, actionTypes: at} = Cu.import("resource://activity-stream/common/Actions.jsm", {}); +const {actionTypes: at} = Cu.import("resource://activity-stream/common/Actions.jsm", {}); const {Prefs} = Cu.import("resource://activity-stream/lib/ActivityStreamPrefs.jsm", {}); -const {shortURL} = Cu.import("resource://activity-stream/common/ShortURL.jsm", {}); +const {shortURL} = Cu.import("resource://activity-stream/lib/ShortURL.jsm", {}); +const {SectionsManager} = Cu.import("resource://activity-stream/lib/SectionsManager.jsm", {}); const STORIES_UPDATE_TIME = 30 * 60 * 1000; // 30 minutes const TOPICS_UPDATE_TIME = 3 * 60 * 60 * 1000; // 3 hours const STORIES_NOW_THRESHOLD = 24 * 60 * 60 * 1000; // 24 hours -const SECTION_ID = "TopStories"; +const SECTION_ID = "topstories"; const FEED_PREF = "feeds.section.topstories"; -const SECTION_OPTIONS_PREF = "feeds.section.topstories.options"; this.TopStoriesFeed = class TopStoriesFeed { init() { - try { - this.storiesLastUpdated = 0; - this.topicsLastUpdated = 0; + this.storiesLastUpdated = 0; + this.topicsLastUpdated = 0; - const prefs = new Prefs(); - const options = JSON.parse(prefs.get(SECTION_OPTIONS_PREF)); + SectionsManager.onceInitialized(this.parseOptions.bind(this)); + } + + parseOptions() { + SectionsManager.enableSection(SECTION_ID); + const options = SectionsManager.sections.get(SECTION_ID).options; + try { const apiKey = this._getApiKeyFromPref(options.api_key_pref); this.stories_endpoint = this._produceFinalEndpointUrl(options.stories_endpoint, apiKey); this.topics_endpoint = this._produceFinalEndpointUrl(options.topics_endpoint, apiKey); @@ -36,30 +40,6 @@ this.TopStoriesFeed = class TopStoriesFeed { this.read_more_endpoint = options.read_more_endpoint; this.stories_referrer = options.stories_referrer; - // TODO https://github.com/mozilla/activity-stream/issues/2902 - const sectionOptions = { - id: SECTION_ID, - eventSource: "TOP_STORIES", - icon: options.provider_icon, - title: {id: "header_recommended_by", values: {provider: options.provider_name}}, - rows: [], - maxRows: 1, - contextMenuOptions: ["CheckBookmark", "SaveToPocket", "Separator", "OpenInNewWindow", "OpenInPrivateWindow", "Separator", "BlockUrl"], - infoOption: { - header: {id: "pocket_feedback_header"}, - body: {id: options.provider_description}, - link: { - href: options.survey_link, - id: "pocket_send_feedback" - } - }, - emptyState: { - message: {id: "topstories_empty_state", values: {provider: options.provider_name}}, - icon: "check" - } - }; - this.store.dispatch(ac.BroadcastToContent({type: at.SECTION_REGISTER, data: sectionOptions})); - this.fetchStories(); this.fetchTopics(); } catch (e) { @@ -68,7 +48,7 @@ this.TopStoriesFeed = class TopStoriesFeed { } uninit() { - this.store.dispatch(ac.BroadcastToContent({type: at.SECTION_DEREGISTER, data: SECTION_ID})); + SectionsManager.disableSection(SECTION_ID); } async fetchStories() { @@ -81,27 +61,26 @@ this.TopStoriesFeed = class TopStoriesFeed { throw new Error(`Stories endpoint returned unexpected status: ${response.status}`); }) .then(body => { - let items = JSON.parse(body).list; + let items = JSON.parse(body).recommendations; items = items - .filter(s => !NewTabUtils.blockedLinks.isBlocked({"url": s.dedupe_url})) + .filter(s => !NewTabUtils.blockedLinks.isBlocked({"url": s.url})) .map(s => ({ "guid": s.id, - "hostname": shortURL(Object.assign({}, s, {url: s.dedupe_url})), + "hostname": shortURL(Object.assign({}, s, {url: s.url})), "type": (Date.now() - (s.published_timestamp * 1000)) <= STORIES_NOW_THRESHOLD ? "now" : "trending", "title": s.title, "description": s.excerpt, "image": this._normalizeUrl(s.image_src), "referrer": this.stories_referrer, - "url": s.dedupe_url, - "eTLD": this._addETLD(s.dedupe_url) + "url": s.url, + "eTLD": this._addETLD(s.url) })); return items; }) .catch(error => Cu.reportError(`Failed to fetch content: ${error.message}`)); if (stories) { - this.dispatchUpdateEvent(this.storiesLastUpdated, - {"type": at.SECTION_ROWS_UPDATE, "data": {"id": SECTION_ID, "rows": stories}}); + this.dispatchUpdateEvent(this.storiesLastUpdated, {rows: stories}); this.storiesLastUpdated = Date.now(); } } @@ -120,19 +99,14 @@ this.TopStoriesFeed = class TopStoriesFeed { .catch(error => Cu.reportError(`Failed to fetch topics: ${error.message}`)); if (topics) { - this.dispatchUpdateEvent(this.topicsLastUpdated, - {"type": at.SECTION_ROWS_UPDATE, "data": {"id": SECTION_ID, "topics": topics, "read_more_endpoint": this.read_more_endpoint}}); + this.dispatchUpdateEvent(this.topicsLastUpdated, {topics, read_more_endpoint: this.read_more_endpoint}); this.topicsLastUpdated = Date.now(); } } } - dispatchUpdateEvent(lastUpdated, evt) { - if (lastUpdated === 0) { - this.store.dispatch(ac.BroadcastToContent(evt)); - } else { - this.store.dispatch(evt); - } + dispatchUpdateEvent(lastUpdated, data) { + SectionsManager.updateSection(SECTION_ID, data, lastUpdated === 0); } _getApiKeyFromPref(apiKeyPref) { @@ -191,11 +165,6 @@ this.TopStoriesFeed = class TopStoriesFeed { this.init(); } break; - case at.PREF_CHANGED: - if (action.data.name === SECTION_OPTIONS_PREF) { - this.init(); - } - break; } } }; @@ -204,5 +173,4 @@ this.STORIES_UPDATE_TIME = STORIES_UPDATE_TIME; this.TOPICS_UPDATE_TIME = TOPICS_UPDATE_TIME; this.SECTION_ID = SECTION_ID; this.FEED_PREF = FEED_PREF; -this.SECTION_OPTIONS_PREF = SECTION_OPTIONS_PREF; -this.EXPORTED_SYMBOLS = ["TopStoriesFeed", "STORIES_UPDATE_TIME", "TOPICS_UPDATE_TIME", "SECTION_ID", "FEED_PREF", "SECTION_OPTIONS_PREF"]; +this.EXPORTED_SYMBOLS = ["TopStoriesFeed", "STORIES_UPDATE_TIME", "TOPICS_UPDATE_TIME", "SECTION_ID", "FEED_PREF"]; diff --git a/browser/extensions/activity-stream/test/functional/mochitest/browser.ini b/browser/extensions/activity-stream/test/functional/mochitest/browser.ini index f30e27fddb48..41eb117c783f 100644 --- a/browser/extensions/activity-stream/test/functional/mochitest/browser.ini +++ b/browser/extensions/activity-stream/test/functional/mochitest/browser.ini @@ -6,4 +6,3 @@ support-files = [browser_as_load_location.js] [browser_as_render.js] [browser_getScreenshots.js] -skip-if=true # issue 2851 diff --git a/browser/extensions/activity-stream/test/functional/mochitest/browser_getScreenshots.js b/browser/extensions/activity-stream/test/functional/mochitest/browser_getScreenshots.js index 17e7bb1a541a..9d6228565a5d 100644 --- a/browser/extensions/activity-stream/test/functional/mochitest/browser_getScreenshots.js +++ b/browser/extensions/activity-stream/test/functional/mochitest/browser_getScreenshots.js @@ -10,8 +10,6 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm"); const TEST_URL = "https://example.com/browser/browser/extensions/activity-stream/test/functional/mochitest/blue_page.html"; const XHTMLNS = "http://www.w3.org/1999/xhtml"; -SpecialPowers.pushPrefEnv({set: [["browser.pagethumbnails.capturing_disabled", false]]}); - XPCOMUtils.defineLazyModuleGetter(this, "Screenshots", "resource://activity-stream/lib/Screenshots.jsm"); function get_pixels_for_data_uri(dataURI, width, height) { @@ -32,6 +30,8 @@ function get_pixels_for_data_uri(dataURI, width, height) { } add_task(async function test_screenshot() { + await SpecialPowers.pushPrefEnv({set: [["browser.pagethumbnails.capturing_disabled", false]]}); + // take a screenshot of a blue page and save it as a data URI const screenshotAsDataURI = await Screenshots.getScreenshotForURL(TEST_URL); let pixels = await get_pixels_for_data_uri(screenshotAsDataURI, 10, 10); diff --git a/browser/extensions/activity-stream/test/unit/common/Reducers.test.js b/browser/extensions/activity-stream/test/unit/common/Reducers.test.js index f599ef45b22b..b8ca8e5a295f 100644 --- a/browser/extensions/activity-stream/test/unit/common/Reducers.test.js +++ b/browser/extensions/activity-stream/test/unit/common/Reducers.test.js @@ -210,7 +210,8 @@ describe("Reducers", () => { id: `foo_bar_${i}`, title: `Foo Bar ${i}`, initialized: false, - rows: [{url: "www.foo.bar"}, {url: "www.other.url"}] + rows: [{url: "www.foo.bar"}, {url: "www.other.url"}], + order: i })); }); @@ -230,6 +231,29 @@ describe("Reducers", () => { const insertedSection = newState.find(section => section.id === "foo_bar_5"); assert.propertyVal(insertedSection, "title", action.data.title); }); + it("should ensure sections are sorted by property `order` (increasing) on SECTION_REGISTER", () => { + let newState = []; + const state = Object.assign([], oldState); + state.forEach(section => { + Object.assign(section, {order: 5 - section.order}); + const action = {type: at.SECTION_REGISTER, data: section}; + newState = Sections(newState, action); + }); + // Should have been inserted into newState in reverse order + assert.deepEqual(newState.map(section => section.id), state.map(section => section.id).reverse()); + const newSection = {id: "new_section", order: 2.5}; + const action = {type: at.SECTION_REGISTER, data: newSection}; + newState = Sections(newState, action); + // Should have been inserted at index 2, between second and third section + assert.equal(newState[2].id, newSection.id); + }); + it("should insert sections without an `order` at the top on SECTION_REGISTER", () => { + const newSection = {id: "new_section"}; + const action = {type: at.SECTION_REGISTER, data: newSection}; + const newState = Sections(oldState, action); + assert.equal(newState[0].id, newSection.id); + assert.ok(newState[0].order < newState[1].order); + }); it("should set newSection.rows === [] if no rows are provided on SECTION_REGISTER", () => { const action = {type: at.SECTION_REGISTER, data: {id: "foo_bar_5", title: "Foo Bar 5"}}; const newState = Sections(oldState, action); @@ -244,17 +268,17 @@ describe("Reducers", () => { const updatedSection = newState.find(section => section.id === "foo_bar_2"); assert.ok(updatedSection && updatedSection.title === NEW_TITLE); }); - it("should have no effect on SECTION_ROWS_UPDATE if the id doesn't exist", () => { - const action = {type: at.SECTION_ROWS_UPDATE, data: {id: "fake_id", data: "fake_data"}}; + it("should have no effect on SECTION_UPDATE if the id doesn't exist", () => { + const action = {type: at.SECTION_UPDATE, data: {id: "fake_id", data: "fake_data"}}; const newState = Sections(oldState, action); assert.deepEqual(oldState, newState); }); - it("should update the section rows with the correct data on SECTION_ROWS_UPDATE", () => { - const FAKE_DATA = ["some", "fake", "data"]; - const action = {type: at.SECTION_ROWS_UPDATE, data: {id: "foo_bar_2", rows: FAKE_DATA}}; + it("should update the section with the correct data on SECTION_UPDATE", () => { + const FAKE_DATA = {rows: ["some", "fake", "data"], foo: "bar"}; + const action = {type: at.SECTION_UPDATE, data: Object.assign(FAKE_DATA, {id: "foo_bar_2"})}; const newState = Sections(oldState, action); const updatedSection = newState.find(section => section.id === "foo_bar_2"); - assert.equal(updatedSection.rows, FAKE_DATA); + assert.include(updatedSection, FAKE_DATA); }); it("should remove blocked and deleted urls from all rows in all sections", () => { const blockAction = {type: at.PLACES_LINK_BLOCKED, data: {url: "www.foo.bar"}}; diff --git a/browser/extensions/activity-stream/test/unit/lib/ActivityStreamMessageChannel.test.js b/browser/extensions/activity-stream/test/unit/lib/ActivityStreamMessageChannel.test.js index 5b2ee793134f..a94c1cdc216d 100644 --- a/browser/extensions/activity-stream/test/unit/lib/ActivityStreamMessageChannel.test.js +++ b/browser/extensions/activity-stream/test/unit/lib/ActivityStreamMessageChannel.test.js @@ -80,6 +80,16 @@ describe("ActivityStreamMessageChannel", () => { mm.createChannel(); assert.notCalled(global.AboutNewTab.override); }); + it("should simluate init for existing ports", () => { + sinon.stub(mm, "onActionFromContent"); + RPmessagePorts.push({loaded: false, portID: "inited"}); + RPmessagePorts.push({loaded: true, portID: "loaded"}); + + mm.createChannel(); + + assert.calledWith(mm.onActionFromContent.firstCall, {type: at.NEW_TAB_INIT}, "inited"); + assert.calledWith(mm.onActionFromContent.secondCall, {type: at.NEW_TAB_INIT}, "loaded"); + }); it("should simluate load for loaded ports", () => { sinon.stub(mm, "onActionFromContent"); RPmessagePorts.push({loaded: true, portID: "foo"}); diff --git a/browser/extensions/activity-stream/test/unit/lib/LocalizationFeed.test.js b/browser/extensions/activity-stream/test/unit/lib/LocalizationFeed.test.js index 8f00921d0ed1..825d791cfd94 100644 --- a/browser/extensions/activity-stream/test/unit/lib/LocalizationFeed.test.js +++ b/browser/extensions/activity-stream/test/unit/lib/LocalizationFeed.test.js @@ -61,7 +61,7 @@ describe("Localization Feed", () => { it("should use strings for other locale", () => { const locale = "it"; sandbox.stub(global.Services.locale, "negotiateLanguages") - .returns([locale]); + .returns([locale, DEFAULT_LOCALE]); feed.updateLocale(); @@ -74,7 +74,7 @@ describe("Localization Feed", () => { it("should use some fallback strings for partial locale", () => { const locale = "ru"; sandbox.stub(global.Services.locale, "negotiateLanguages") - .returns([locale]); + .returns([locale, DEFAULT_LOCALE]); feed.updateLocale(); @@ -87,16 +87,33 @@ describe("Localization Feed", () => { too: TEST_STRINGS[DEFAULT_LOCALE].too }); }); - it("should use all default strings for unknown locale", () => { - const locale = "xyz"; + it("should use multiple fallback strings before default", () => { + const primaryLocale = "ru"; + const secondaryLocale = "it"; sandbox.stub(global.Services.locale, "negotiateLanguages") - .returns([locale]); + .returns([primaryLocale, secondaryLocale, DEFAULT_LOCALE]); + feed.updateLocale(); assert.calledOnce(feed.store.dispatch); const arg = feed.store.dispatch.firstCall.args[0]; assert.propertyVal(arg, "type", at.LOCALE_UPDATED); - assert.propertyVal(arg.data, "locale", locale); + assert.propertyVal(arg.data, "locale", primaryLocale); + assert.deepEqual(arg.data.strings, { + foo: TEST_STRINGS[primaryLocale].foo, + too: TEST_STRINGS[secondaryLocale].too + }); + }); + it("should use all default strings for unknown locale", () => { + sandbox.stub(global.Services.locale, "negotiateLanguages") + .returns([DEFAULT_LOCALE]); + + feed.updateLocale(); + + assert.calledOnce(feed.store.dispatch); + const arg = feed.store.dispatch.firstCall.args[0]; + assert.propertyVal(arg, "type", at.LOCALE_UPDATED); + assert.propertyVal(arg.data, "locale", DEFAULT_LOCALE); assert.deepEqual(arg.data.strings, TEST_STRINGS[DEFAULT_LOCALE]); }); }); diff --git a/browser/extensions/activity-stream/test/unit/lib/SectionsManager.test.js b/browser/extensions/activity-stream/test/unit/lib/SectionsManager.test.js index c01c9c9133bc..606574db61c8 100644 --- a/browser/extensions/activity-stream/test/unit/lib/SectionsManager.test.js +++ b/browser/extensions/activity-stream/test/unit/lib/SectionsManager.test.js @@ -1,20 +1,52 @@ "use strict"; const {SectionsFeed, SectionsManager} = require("lib/SectionsManager.jsm"); -const {EventEmitter} = require("test/unit/utils"); +const {EventEmitter, GlobalOverrider} = require("test/unit/utils"); const {MAIN_MESSAGE_TYPE, CONTENT_MESSAGE_TYPE} = require("common/Actions.jsm"); const FAKE_ID = "FAKE_ID"; const FAKE_OPTIONS = {icon: "FAKE_ICON", title: "FAKE_TITLE"}; const FAKE_ROWS = [{url: "1"}, {url: "2"}, {"url": "3"}]; +let globals; + +beforeEach(() => { + globals = new GlobalOverrider(); +}); + afterEach(() => { + globals.restore(); // Redecorate SectionsManager to remove any listeners that have been added EventEmitter.decorate(SectionsManager); + SectionsManager.init(); }); describe("SectionsManager", () => { - it("should be initialised with .initialized == false", () => { - assert.notOk(SectionsManager.initialized); + describe("#init", () => { + it("should initialise the sections map with the built in sections", () => { + SectionsManager.sections.clear(); + SectionsManager.initialized = false; + SectionsManager.init(); + assert.equal(SectionsManager.sections.size, 1); + assert.ok(SectionsManager.sections.has("topstories")); + }); + it("should set .initialized to true", () => { + SectionsManager.sections.clear(); + SectionsManager.initialized = false; + SectionsManager.init(); + assert.ok(SectionsManager.initialized); + }); + }); + describe("#addBuiltInSection", () => { + it("should not report an error if options is undefined", () => { + globals.sandbox.spy(global.Components.utils, "reportError"); + SectionsManager.addBuiltInSection("feeds.section.topstories", undefined); + assert.notCalled(Components.utils.reportError); + }); + it("should report an error if options is malformed", () => { + globals.sandbox.spy(global.Components.utils, "reportError"); + SectionsManager.addBuiltInSection("feeds.section.topstories", "invalid"); + assert.calledOnce(Components.utils.reportError); + }); }); describe("#addSection", () => { it("should add the id to sections and emit an ADD_SECTION event", () => { @@ -38,29 +70,67 @@ describe("SectionsManager", () => { assert.calledWith(spy, SectionsManager.REMOVE_SECTION, FAKE_ID); }); }); - describe("#updateRows", () => { - it("should emit an UPDATE_ROWS event with correct arguments", () => { + describe("#enableSection", () => { + it("should call updateSection with {enabled: true}", () => { + sinon.spy(SectionsManager, "updateSection"); + SectionsManager.addSection(FAKE_ID, FAKE_OPTIONS); + SectionsManager.enableSection(FAKE_ID); + assert.calledOnce(SectionsManager.updateSection); + assert.calledWith(SectionsManager.updateSection, FAKE_ID, {enabled: true}, true); + SectionsManager.updateSection.restore(); + }); + }); + describe("#disableSection", () => { + it("should call updateSection with {enabled: false, rows: []}", () => { + sinon.spy(SectionsManager, "updateSection"); + SectionsManager.addSection(FAKE_ID, FAKE_OPTIONS); + SectionsManager.disableSection(FAKE_ID); + assert.calledOnce(SectionsManager.updateSection); + assert.calledWith(SectionsManager.updateSection, FAKE_ID, {enabled: false, rows: []}, true); + SectionsManager.updateSection.restore(); + }); + }); + describe("#updateSection", () => { + it("should emit an UPDATE_SECTION event with correct arguments", () => { SectionsManager.addSection(FAKE_ID, FAKE_OPTIONS); const spy = sinon.spy(); - SectionsManager.on(SectionsManager.UPDATE_ROWS, spy); - SectionsManager.updateRows(FAKE_ID, FAKE_ROWS, true); + SectionsManager.on(SectionsManager.UPDATE_SECTION, spy); + SectionsManager.updateSection(FAKE_ID, {rows: FAKE_ROWS}, true); assert.calledOnce(spy); - assert.calledWith(spy, SectionsManager.UPDATE_ROWS, FAKE_ID, FAKE_ROWS, true); + assert.calledWith(spy, SectionsManager.UPDATE_SECTION, FAKE_ID, {rows: FAKE_ROWS}, true); }); it("should do nothing if the section doesn't exist", () => { SectionsManager.removeSection(FAKE_ID); const spy = sinon.spy(); - SectionsManager.on(SectionsManager.UPDATE_ROWS, spy); - SectionsManager.updateRows(FAKE_ID, FAKE_ROWS, true); + SectionsManager.on(SectionsManager.UPDATE_SECTION, spy); + SectionsManager.updateSection(FAKE_ID, {rows: FAKE_ROWS}, true); assert.notCalled(spy); }); }); + describe("#onceInitialized", () => { + it("should call the callback immediately if SectionsManager is initialised", () => { + SectionsManager.initialized = true; + const callback = sinon.spy(); + SectionsManager.onceInitialized(callback); + assert.calledOnce(callback); + }); + it("should bind the callback to .once(INIT) if SectionsManager is not initialised", () => { + SectionsManager.initialized = false; + sinon.spy(SectionsManager, "once"); + const callback = () => {}; + SectionsManager.onceInitialized(callback); + assert.calledOnce(SectionsManager.once); + assert.calledWith(SectionsManager.once, SectionsManager.INIT, callback); + }); + }); }); describe("SectionsFeed", () => { let feed; beforeEach(() => { + SectionsManager.sections.clear(); + SectionsManager.initialized = false; feed = new SectionsFeed(); feed.store = {dispatch: sinon.spy()}; }); @@ -78,17 +148,19 @@ describe("SectionsFeed", () => { for (const [event, listener] of [ [SectionsManager.ADD_SECTION, feed.onAddSection], [SectionsManager.REMOVE_SECTION, feed.onRemoveSection], - [SectionsManager.UPDATE_ROWS, feed.onUpdateRows] + [SectionsManager.UPDATE_SECTION, feed.onUpdateSection] ]) { assert.calledWith(SectionsManager.on, event, listener); } }); - it("should emit an INIT event and set SectionsManager.initialized to true", () => { - const spy = sinon.spy(); - SectionsManager.on(SectionsManager.INIT, spy); + it("should call onAddSection for any already added sections in SectionsManager", () => { + SectionsManager.init(); + assert.ok(SectionsManager.sections.has("topstories")); + const topstories = SectionsManager.sections.get("topstories"); + sinon.spy(feed, "onAddSection"); feed.init(); - assert.calledOnce(spy); - assert.ok(SectionsManager.initialized); + assert.calledOnce(feed.onAddSection); + assert.calledWith(feed.onAddSection, SectionsManager.ADD_SECTION, "topstories", topstories); }); }); describe("#uninit", () => { @@ -100,7 +172,7 @@ describe("SectionsFeed", () => { for (const [event, listener] of [ [SectionsManager.ADD_SECTION, feed.onAddSection], [SectionsManager.REMOVE_SECTION, feed.onRemoveSection], - [SectionsManager.UPDATE_ROWS, feed.onUpdateRows] + [SectionsManager.UPDATE_SECTION, feed.onUpdateSection] ]) { assert.calledWith(SectionsManager.off, event, listener); } @@ -135,21 +207,21 @@ describe("SectionsFeed", () => { assert.equal(action.meta.to, CONTENT_MESSAGE_TYPE); }); }); - describe("#onUpdateRows", () => { + describe("#onUpdateSection", () => { it("should do nothing if no rows are provided", () => { - feed.onUpdateRows(null, FAKE_ID, null); + feed.onUpdateSection(null, FAKE_ID, null); assert.notCalled(feed.store.dispatch); }); - it("should dispatch a SECTION_ROWS_UPDATE action with the correct data", () => { - feed.onUpdateRows(null, FAKE_ID, FAKE_ROWS); + it("should dispatch a SECTION_UPDATE action with the correct data", () => { + feed.onUpdateSection(null, FAKE_ID, {rows: FAKE_ROWS}); const action = feed.store.dispatch.firstCall.args[0]; - assert.equal(action.type, "SECTION_ROWS_UPDATE"); + assert.equal(action.type, "SECTION_UPDATE"); assert.deepEqual(action.data, {id: FAKE_ID, rows: FAKE_ROWS}); // Should be not broadcast by default, so meta should not exist assert.notOk(action.meta); }); it("should broadcast the action only if shouldBroadcast is true", () => { - feed.onUpdateRows(null, FAKE_ID, FAKE_ROWS, true); + feed.onUpdateSection(null, FAKE_ID, {rows: FAKE_ROWS}, true); const action = feed.store.dispatch.firstCall.args[0]; // Should be broadcast assert.equal(action.meta.from, MAIN_MESSAGE_TYPE); @@ -157,18 +229,46 @@ describe("SectionsFeed", () => { }); }); describe("#onAction", () => { - it("should call init() on action INIT", () => { - sinon.spy(feed, "init"); + it("should bind this.init to SectionsManager.INIT on INIT", () => { + sinon.spy(SectionsManager, "once"); feed.onAction({type: "INIT"}); - assert.calledOnce(feed.init); + assert.calledOnce(SectionsManager.once); + assert.calledWith(SectionsManager.once, SectionsManager.INIT, feed.init); + }); + it("should call SectionsManager.init on action PREFS_INITIAL_VALUES", () => { + sinon.spy(SectionsManager, "init"); + feed.onAction({type: "PREFS_INITIAL_VALUES", data: {foo: "bar"}}); + assert.calledOnce(SectionsManager.init); + assert.calledWith(SectionsManager.init, {foo: "bar"}); + }); + it("should call SectionsManager.addBuiltInSection on suitable PREF_CHANGED events", () => { + sinon.spy(SectionsManager, "addBuiltInSection"); + feed.onAction({type: "PREF_CHANGED", data: {name: "feeds.section.topstories.options", value: "foo"}}); + assert.calledOnce(SectionsManager.addBuiltInSection); + assert.calledWith(SectionsManager.addBuiltInSection, "feeds.section.topstories", "foo"); + }); + it("should call SectionsManager.disableSection on SECTION_DISABLE", () => { + sinon.spy(SectionsManager, "disableSection"); + feed.onAction({type: "SECTION_DISABLE", data: 1234}); + assert.calledOnce(SectionsManager.disableSection); + assert.calledWith(SectionsManager.disableSection, 1234); + SectionsManager.disableSection.restore(); + }); + it("should call SectionsManager.enableSection on SECTION_ENABLE", () => { + sinon.spy(SectionsManager, "enableSection"); + feed.onAction({type: "SECTION_ENABLE", data: 1234}); + assert.calledOnce(SectionsManager.enableSection); + assert.calledWith(SectionsManager.enableSection, 1234); + SectionsManager.enableSection.restore(); }); it("should emit a ACTION_DISPATCHED event and forward any action in ACTIONS_TO_PROXY if there are any sections", () => { const spy = sinon.spy(); const allowedActions = SectionsManager.ACTIONS_TO_PROXY; const disallowedActions = ["PREF_CHANGED", "OPEN_PRIVATE_WINDOW"]; + feed.init(); SectionsManager.on(SectionsManager.ACTION_DISPATCHED, spy); // Make sure we start with no sections - no event should be emitted - assert.equal(SectionsManager.sections.size, 0); + SectionsManager.sections.clear(); feed.onAction({type: allowedActions[0]}); assert.notCalled(spy); // Then add a section and check correct behaviour diff --git a/browser/extensions/activity-stream/test/unit/common/ShortUrl.test.js b/browser/extensions/activity-stream/test/unit/lib/ShortUrl.test.js similarity index 72% rename from browser/extensions/activity-stream/test/unit/common/ShortUrl.test.js rename to browser/extensions/activity-stream/test/unit/lib/ShortUrl.test.js index 3316172311ff..3d6d80c49ef2 100644 --- a/browser/extensions/activity-stream/test/unit/common/ShortUrl.test.js +++ b/browser/extensions/activity-stream/test/unit/lib/ShortUrl.test.js @@ -1,6 +1,21 @@ -const {shortURL} = require("common/ShortURL.jsm"); +const {shortURL} = require("lib/ShortURL.jsm"); +const {GlobalOverrider} = require("test/unit/utils"); describe("shortURL", () => { + let globals; + let IDNStub; + + beforeEach(() => { + IDNStub = sinon.stub().callsFake(id => id); + + globals = new GlobalOverrider(); + globals.set("IDNService", {convertToDisplayIDN: IDNStub}); + }); + + afterEach(() => { + globals.restore(); + }); + it("should return a blank string if url and hostname is falsey", () => { assert.equal(shortURL({url: ""}), ""); assert.equal(shortURL({hostname: null}), ""); @@ -10,6 +25,13 @@ describe("shortURL", () => { assert.equal(shortURL({hostname: "com.blah.com", eTLD: "com"}), "com.blah"); }); + it("should call convertToDisplayIDN when calling shortURL", () => { + shortURL({hostname: "com.blah.com", eTLD: "com"}); + + assert.calledOnce(IDNStub); + assert.calledWithExactly(IDNStub, "com.blah.com", {}); + }); + it("should use the hostname, if provided", () => { assert.equal(shortURL({hostname: "foo.com", url: "http://bar.com", eTLD: "com"}), "foo"); }); diff --git a/browser/extensions/activity-stream/test/unit/lib/TopSitesFeed.test.js b/browser/extensions/activity-stream/test/unit/lib/TopSitesFeed.test.js index ab3434fc066e..4adf0f4286d8 100644 --- a/browser/extensions/activity-stream/test/unit/lib/TopSitesFeed.test.js +++ b/browser/extensions/activity-stream/test/unit/lib/TopSitesFeed.test.js @@ -5,7 +5,12 @@ const {FakePrefs, GlobalOverrider} = require("test/unit/utils"); const action = {meta: {fromTarget: {}}}; const {actionCreators: ac, actionTypes: at} = require("common/Actions.jsm"); const {insertPinned, TOP_SITES_SHOWMORE_LENGTH} = require("common/Reducers.jsm"); -const FAKE_LINKS = new Array(TOP_SITES_SHOWMORE_LENGTH).fill(null).map((v, i) => ({url: `http://www.site${i}.com`})); + +const FAKE_FRECENCY = 200; +const FAKE_LINKS = new Array(TOP_SITES_SHOWMORE_LENGTH).fill(null).map((v, i) => ({ + frecency: FAKE_FRECENCY, + url: `http://www.site${i}.com` +})); const FAKE_SCREENSHOT = "data123"; function FakeTippyTopProvider() {} @@ -53,7 +58,7 @@ describe("Top Sites Feed", () => { "common/Reducers.jsm": {insertPinned, TOP_SITES_SHOWMORE_LENGTH}, "lib/Screenshots.jsm": {Screenshots: fakeScreenshot}, "lib/TippyTopProvider.jsm": {TippyTopProvider: FakeTippyTopProvider}, - "common/ShortURL.jsm": {shortURL: shortURLStub} + "lib/ShortURL.jsm": {shortURL: shortURLStub} })); feed = new TopSitesFeed(); feed.store = {dispatch: sinon.spy(), getState() { return {TopSites: {rows: Array(12).fill("site")}}; }}; @@ -106,10 +111,27 @@ describe("Top Sites Feed", () => { assert.deepEqual(result, reference); assert.calledOnce(global.NewTabUtils.activityStreamLinks.getTopSites); }); + it("should filter out low frecency links", async () => { + links = [ + {frecency: FAKE_FRECENCY, url: "https://enough/visited"}, + {frecency: 100, url: "https://visited/once"}, + {frecency: 0, url: "https://unvisited/page"} + ]; + + const result = await feed.getLinksWithDefaults(); + + assert.equal(result[0].url, links[0].url); + assert.notEqual(result[1].url, links[1].url); + assert.notEqual(result[1].url, links[2].url); + }); it("should filter out the defaults that have been blocked", async () => { // make sure we only have one top site, and we block the only default site we have to show const url = "www.myonlytopsite.com"; - const topsite = {url, hostname: shortURLStub({url})}; + const topsite = { + frecency: FAKE_FRECENCY, + hostname: shortURLStub({url}), + url + }; const blockedDefaultSite = {url: "https://foo.com"}; fakeNewTabUtils.activityStreamLinks.getTopSites = () => [topsite]; fakeNewTabUtils.blockedLinks.isBlocked = site => (site.url === blockedDefaultSite.url); @@ -134,7 +156,7 @@ describe("Top Sites Feed", () => { assert.equal(result, site.hostname); }); it("should add defaults if there are are not enough links", async () => { - links = [{url: "foo.com"}]; + links = [{frecency: FAKE_FRECENCY, url: "foo.com"}]; const result = await feed.getLinksWithDefaults(); const reference = [...links, ...DEFAULT_TOP_SITES].map(s => Object.assign({}, s, {hostname: shortURLStub(s)})); @@ -144,7 +166,7 @@ describe("Top Sites Feed", () => { it("should only add defaults up to TOP_SITES_SHOWMORE_LENGTH", async () => { links = []; for (let i = 0; i < TOP_SITES_SHOWMORE_LENGTH - 1; i++) { - links.push({url: `foo${i}.com`}); + links.push({frecency: FAKE_FRECENCY, url: `foo${i}.com`}); } const result = await feed.getLinksWithDefaults(); const reference = [...links, DEFAULT_TOP_SITES[0]].map(s => Object.assign({}, s, {hostname: shortURLStub(s)})); @@ -358,5 +380,10 @@ describe("Top Sites Feed", () => { await feed.onAction({type: at.INIT}); assert.calledOnce(feed.refresh); }); + it("should call refresh on BLOCK_URL action", async () => { + sinon.stub(feed, "refresh"); + await feed.onAction({type: at.BLOCK_URL}); + assert.calledOnce(feed.refresh); + }); }); }); diff --git a/browser/extensions/activity-stream/test/unit/lib/TopStoriesFeed.test.js b/browser/extensions/activity-stream/test/unit/lib/TopStoriesFeed.test.js index dfaccae255b8..6bc5fdb21df4 100644 --- a/browser/extensions/activity-stream/test/unit/lib/TopStoriesFeed.test.js +++ b/browser/extensions/activity-stream/test/unit/lib/TopStoriesFeed.test.js @@ -1,7 +1,7 @@ "use strict"; const injector = require("inject!lib/TopStoriesFeed.jsm"); const {FakePrefs} = require("test/unit/utils"); -const {actionCreators: ac, actionTypes: at} = require("common/Actions.jsm"); +const {actionTypes: at} = require("common/Actions.jsm"); const {GlobalOverrider} = require("test/unit/utils"); describe("Top Stories Feed", () => { @@ -10,34 +10,45 @@ describe("Top Stories Feed", () => { let TOPICS_UPDATE_TIME; let SECTION_ID; let FEED_PREF; - let SECTION_OPTIONS_PREF; let instance; let clock; let globals; + let sectionsManagerStub; let shortURLStub; + const FAKE_OPTIONS = { + "stories_endpoint": "https://somedomain.org/stories?key=$apiKey", + "stories_referrer": "https://somedomain.org/referrer", + "topics_endpoint": "https://somedomain.org/topics?key=$apiKey", + "survey_link": "https://www.surveymonkey.com/r/newtabffx", + "api_key_pref": "apiKeyPref", + "provider_name": "test-provider", + "provider_icon": "provider-icon", + "provider_description": "provider_desc" + }; + beforeEach(() => { - FakePrefs.prototype.prefs["feeds.section.topstories.options"] = `{ - "stories_endpoint": "https://somedomain.org/stories?key=$apiKey", - "stories_referrer": "https://somedomain.org/referrer", - "topics_endpoint": "https://somedomain.org/topics?key=$apiKey", - "survey_link": "https://www.surveymonkey.com/r/newtabffx", - "api_key_pref": "apiKeyPref", - "provider_name": "test-provider", - "provider_icon": "provider-icon", - "provider_description": "provider_desc" - }`; FakePrefs.prototype.prefs.apiKeyPref = "test-api-key"; globals = new GlobalOverrider(); globals.set("Services", {locale: {getRequestedLocale: () => "en-CA"}}); + + sectionsManagerStub = { + onceInitialized: sinon.stub().callsFake(callback => callback()), + enableSection: sinon.spy(), + disableSection: sinon.spy(), + updateSection: sinon.spy(), + sections: new Map([["topstories", {options: FAKE_OPTIONS}]]) + }; + clock = sinon.useFakeTimers(); shortURLStub = sinon.stub().callsFake(site => site.url); - ({TopStoriesFeed, STORIES_UPDATE_TIME, TOPICS_UPDATE_TIME, SECTION_ID, FEED_PREF, SECTION_OPTIONS_PREF} = injector({ + ({TopStoriesFeed, STORIES_UPDATE_TIME, TOPICS_UPDATE_TIME, SECTION_ID, FEED_PREF} = injector({ "lib/ActivityStreamPrefs.jsm": {Prefs: FakePrefs}, - "common/ShortURL.jsm": {shortURL: shortURLStub} + "lib/ShortURL.jsm": {shortURL: shortURLStub}, + "lib/SectionsManager.jsm": {SectionsManager: sectionsManagerStub} })); instance = new TopStoriesFeed(); instance.store = {getState() { return {}; }, dispatch: sinon.spy()}; @@ -52,42 +63,20 @@ describe("Top Stories Feed", () => { it("should create a TopStoriesFeed", () => { assert.instanceOf(instance, TopStoriesFeed); }); - it("should initialize endpoints based on prefs", () => { + it("should bind parseOptions to SectionsManager.onceInitialized", () => { + instance.onAction({type: at.INIT}); + assert.calledOnce(sectionsManagerStub.onceInitialized); + }); + it("should initialize endpoints based on options", () => { instance.onAction({type: at.INIT}); assert.equal("https://somedomain.org/stories?key=test-api-key", instance.stories_endpoint); assert.equal("https://somedomain.org/referrer", instance.stories_referrer); assert.equal("https://somedomain.org/topics?key=test-api-key", instance.topics_endpoint); }); - it("should register section", () => { - const expectedSectionOptions = { - id: SECTION_ID, - eventSource: "TOP_STORIES", - icon: "provider-icon", - title: {id: "header_recommended_by", values: {provider: "test-provider"}}, - rows: [], - maxRows: 1, - contextMenuOptions: ["CheckBookmark", "SaveToPocket", "Separator", "OpenInNewWindow", "OpenInPrivateWindow", "Separator", "BlockUrl"], - infoOption: { - header: {id: "pocket_feedback_header"}, - body: {id: "provider_desc"}, - link: { - href: "https://www.surveymonkey.com/r/newtabffx", - id: "pocket_send_feedback" - } - }, - emptyState: { - message: {id: "topstories_empty_state", values: {provider: "test-provider"}}, - icon: "check" - } - }; - + it("should enable its section", () => { instance.onAction({type: at.INIT}); - assert.calledOnce(instance.store.dispatch); - assert.propertyVal(instance.store.dispatch.firstCall.args[0], "type", at.SECTION_REGISTER); - assert.calledWith(instance.store.dispatch, ac.BroadcastToContent({ - type: at.SECTION_REGISTER, - data: expectedSectionOptions - })); + assert.calledOnce(sectionsManagerStub.enableSection); + assert.calledWith(sectionsManagerStub.enableSection, SECTION_ID); }); it("should fetch stories on init", () => { instance.fetchStories = sinon.spy(); @@ -104,13 +93,13 @@ describe("Top Stories Feed", () => { it("should not fetch if endpoint not configured", () => { let fetchStub = globals.sandbox.stub(); globals.set("fetch", fetchStub); - FakePrefs.prototype.prefs["feeds.section.topstories.options"] = "{}"; + sectionsManagerStub.sections.set("topstories", {options: {}}); instance.init(); assert.notCalled(fetchStub); }); it("should report error for invalid configuration", () => { globals.sandbox.spy(global.Components.utils, "reportError"); - FakePrefs.prototype.prefs["feeds.section.topstories.options"] = "invalid"; + sectionsManagerStub.sections.set("topstories", {options: {api_key_pref: "invalid"}}); instance.init(); assert.called(Components.utils.reportError); @@ -119,31 +108,27 @@ describe("Top Stories Feed", () => { let fakeServices = {prefs: {getCharPref: sinon.spy()}, locale: {getRequestedLocale: sinon.spy()}}; globals.set("Services", fakeServices); globals.sandbox.spy(global.Components.utils, "reportError"); - FakePrefs.prototype.prefs["feeds.section.topstories.options"] = `{ - "stories_endpoint": "https://somedomain.org/stories?key=$apiKey", - "topics_endpoint": "https://somedomain.org/topics?key=$apiKey" - }`; + sectionsManagerStub.sections.set("topstories", { + options: { + "stories_endpoint": "https://somedomain.org/stories?key=$apiKey", + "topics_endpoint": "https://somedomain.org/topics?key=$apiKey" + } + }); instance.init(); assert.called(Components.utils.reportError); }); - it("should deregister section", () => { - instance.onAction({type: at.UNINIT}); - assert.calledOnce(instance.store.dispatch); - assert.calledWith(instance.store.dispatch, ac.BroadcastToContent({ - type: at.SECTION_DEREGISTER, - data: SECTION_ID - })); - }); it("should initialize on FEED_INIT", () => { instance.init = sinon.spy(); instance.onAction({type: at.FEED_INIT, data: FEED_PREF}); assert.calledOnce(instance.init); }); - it("should initialize on PREF_CHANGED", () => { - instance.init = sinon.spy(); - instance.onAction({type: at.PREF_CHANGED, data: {name: SECTION_OPTIONS_PREF}}); - assert.calledOnce(instance.init); + }); + describe("#uninit", () => { + it("should disable its section", () => { + instance.onAction({type: at.UNINIT}); + assert.calledOnce(sectionsManagerStub.disableSection); + assert.calledWith(sectionsManagerStub.disableSection, SECTION_ID); }); }); describe("#fetch", () => { @@ -152,11 +137,11 @@ describe("Top Stories Feed", () => { globals.set("fetch", fetchStub); globals.set("NewTabUtils", {blockedLinks: {isBlocked: globals.sandbox.spy()}}); - const response = `{"list": [{"id" : "1", + const response = `{"recommendations": [{"id" : "1", "title": "title", "excerpt": "description", "image_src": "image-url", - "dedupe_url": "rec-url", + "url": "rec-url", "published_timestamp" : "123" }]}`; const stories = [{ @@ -179,14 +164,12 @@ describe("Top Stories Feed", () => { assert.calledOnce(fetchStub); assert.calledOnce(shortURLStub); assert.calledWithExactly(fetchStub, instance.stories_endpoint); - assert.calledOnce(instance.store.dispatch); - assert.propertyVal(instance.store.dispatch.firstCall.args[0], "type", at.SECTION_ROWS_UPDATE); - assert.deepEqual(instance.store.dispatch.firstCall.args[0].data.id, SECTION_ID); - assert.deepEqual(instance.store.dispatch.firstCall.args[0].data.rows, stories); + assert.calledOnce(sectionsManagerStub.updateSection); + assert.calledWith(sectionsManagerStub.updateSection, SECTION_ID, {rows: stories}); }); - it("should dispatch events", () => { + it("should call SectionsManager.updateSection", () => { instance.dispatchUpdateEvent(123, {}); - assert.calledOnce(instance.store.dispatch); + assert.calledOnce(sectionsManagerStub.updateSection); }); it("should report error for unexpected stories response", async () => { let fetchStub = globals.sandbox.stub(); @@ -199,7 +182,7 @@ describe("Top Stories Feed", () => { assert.calledOnce(fetchStub); assert.calledWithExactly(fetchStub, instance.stories_endpoint); - assert.notCalled(instance.store.dispatch); + assert.notCalled(sectionsManagerStub.updateSection); assert.called(Components.utils.reportError); }); it("should exclude blocked (dismissed) URLs", async () => { @@ -207,15 +190,14 @@ describe("Top Stories Feed", () => { globals.set("fetch", fetchStub); globals.set("NewTabUtils", {blockedLinks: {isBlocked: site => site.url === "blocked"}}); - const response = `{"list": [{"dedupe_url" : "blocked"}, {"dedupe_url" : "not_blocked"}]}`; + const response = `{"recommendations": [{"url" : "blocked"}, {"url" : "not_blocked"}]}`; instance.stories_endpoint = "stories-endpoint"; fetchStub.resolves({ok: true, status: 200, text: () => response}); await instance.fetchStories(); - assert.calledOnce(instance.store.dispatch); - assert.propertyVal(instance.store.dispatch.firstCall.args[0], "type", at.SECTION_ROWS_UPDATE); - assert.equal(instance.store.dispatch.firstCall.args[0].data.rows.length, 1); - assert.equal(instance.store.dispatch.firstCall.args[0].data.rows[0].url, "not_blocked"); + assert.calledOnce(sectionsManagerStub.updateSection); + assert.equal(sectionsManagerStub.updateSection.firstCall.args[1].rows.length, 1); + assert.equal(sectionsManagerStub.updateSection.firstCall.args[1].rows[0].url, "not_blocked"); }); it("should mark stories as new", async () => { let fetchStub = globals.sandbox.stub(); @@ -223,7 +205,7 @@ describe("Top Stories Feed", () => { globals.set("NewTabUtils", {blockedLinks: {isBlocked: globals.sandbox.spy()}}); clock.restore(); const response = JSON.stringify({ - "list": [ + "recommendations": [ {"published_timestamp": Date.now() / 1000}, {"published_timestamp": "0"}, {"published_timestamp": (Date.now() - 2 * 24 * 60 * 60 * 1000) / 1000} @@ -234,12 +216,11 @@ describe("Top Stories Feed", () => { fetchStub.resolves({ok: true, status: 200, text: () => response}); await instance.fetchStories(); - assert.calledOnce(instance.store.dispatch); - assert.propertyVal(instance.store.dispatch.firstCall.args[0], "type", at.SECTION_ROWS_UPDATE); - assert.equal(instance.store.dispatch.firstCall.args[0].data.rows.length, 3); - assert.equal(instance.store.dispatch.firstCall.args[0].data.rows[0].type, "now"); - assert.equal(instance.store.dispatch.firstCall.args[0].data.rows[1].type, "trending"); - assert.equal(instance.store.dispatch.firstCall.args[0].data.rows[2].type, "trending"); + assert.calledOnce(sectionsManagerStub.updateSection); + assert.equal(sectionsManagerStub.updateSection.firstCall.args[1].rows.length, 3); + assert.equal(sectionsManagerStub.updateSection.firstCall.args[1].rows[0].type, "now"); + assert.equal(sectionsManagerStub.updateSection.firstCall.args[1].rows[1].type, "trending"); + assert.equal(sectionsManagerStub.updateSection.firstCall.args[1].rows[2].type, "trending"); }); it("should fetch topics and send event", async () => { let fetchStub = globals.sandbox.stub(); @@ -260,10 +241,8 @@ describe("Top Stories Feed", () => { assert.calledOnce(fetchStub); assert.calledWithExactly(fetchStub, instance.topics_endpoint); - assert.calledOnce(instance.store.dispatch); - assert.propertyVal(instance.store.dispatch.firstCall.args[0], "type", at.SECTION_ROWS_UPDATE); - assert.deepEqual(instance.store.dispatch.firstCall.args[0].data.id, SECTION_ID); - assert.deepEqual(instance.store.dispatch.firstCall.args[0].data.topics, topics); + assert.calledOnce(sectionsManagerStub.updateSection); + assert.calledWithMatch(sectionsManagerStub.updateSection, SECTION_ID, {topics}); }); it("should report error for unexpected topics response", async () => { let fetchStub = globals.sandbox.stub(); @@ -282,6 +261,7 @@ describe("Top Stories Feed", () => { }); describe("#update", () => { it("should fetch stories after update interval", () => { + instance.init(); instance.fetchStories = sinon.spy(); instance.onAction({type: at.SYSTEM_TICK}); assert.notCalled(instance.fetchStories); @@ -291,6 +271,7 @@ describe("Top Stories Feed", () => { assert.calledOnce(instance.fetchStories); }); it("should fetch topics after update interval", () => { + instance.init(); instance.fetchTopics = sinon.spy(); instance.onAction({type: at.SYSTEM_TICK}); assert.notCalled(instance.fetchTopics); diff --git a/browser/extensions/activity-stream/test/unit/utils.js b/browser/extensions/activity-stream/test/unit/utils.js index dbfc54f2ae2d..7095f9c4089a 100644 --- a/browser/extensions/activity-stream/test/unit/utils.js +++ b/browser/extensions/activity-stream/test/unit/utils.js @@ -127,6 +127,7 @@ EventEmitter.decorate = function(objectToDecorate) { let emitter = new EventEmitter(); objectToDecorate.on = emitter.on.bind(emitter); objectToDecorate.off = emitter.off.bind(emitter); + objectToDecorate.once = emitter.once.bind(emitter); objectToDecorate.emit = emitter.emit.bind(emitter); }; EventEmitter.prototype = { @@ -150,6 +151,20 @@ EventEmitter.prototype = { )); } }, + once(event, listener) { + return new Promise(resolve => { + let handler = (_, first, ...rest) => { + this.off(event, handler); + if (listener) { + listener(event, first, ...rest); + } + resolve(first); + }; + + handler._originalListener = listener; + this.on(event, handler); + }); + }, // All arguments to this method will be sent to listeners emit(event, ...args) { if (!this._eventEmitterListeners || !this._eventEmitterListeners.has(event)) { diff --git a/browser/extensions/formautofill/FormAutofillDoorhanger.jsm b/browser/extensions/formautofill/FormAutofillDoorhanger.jsm index c8cb06cb19cc..01e7f92d05ba 100644 --- a/browser/extensions/formautofill/FormAutofillDoorhanger.jsm +++ b/browser/extensions/formautofill/FormAutofillDoorhanger.jsm @@ -26,10 +26,12 @@ FormAutofillUtils.defineLazyLogGetter(this, this.EXPORTED_SYMBOLS[0]); const BUNDLE_URI = "chrome://formautofill/locale/formautofill.properties"; const GetStringFromName = Services.strings.createBundle(BUNDLE_URI).GetStringFromName; let changeAutofillOptsKey = "changeAutofillOptions"; -let viewAutofillOptsKey = "viewAutofillOptionsLink"; -if (AppConstants.platform != "macosx") { +let autofillOptsKey = "autofillOptionsLink"; +let autofillSecurityOptionsKey = "autofillSecurityOptionsLink"; +if (AppConstants.platform == "macosx") { changeAutofillOptsKey += "OSX"; - viewAutofillOptsKey += "OSX"; + autofillOptsKey += "OSX"; + autofillSecurityOptionsKey += "OSX"; } const CONTENT = { @@ -69,6 +71,7 @@ const CONTENT = { update: { notificationId: "autofill-address", message: GetStringFromName("updateAddressMessage"), + linkMessage: GetStringFromName(autofillOptsKey), anchor: { id: "autofill-address-notification-icon", URL: "chrome://formautofill/content/formfill-anchor.svg", @@ -89,6 +92,34 @@ const CONTENT = { popupIconURL: "chrome://formautofill/content/icon-address-update.svg", }, }, + creditCard: { + notificationId: "autofill-credit-card", + message: GetStringFromName("saveCreditCardMessage"), + linkMessage: GetStringFromName(autofillSecurityOptionsKey), + anchor: { + id: "autofill-credit-card-notification-icon", + URL: "chrome://formautofill/content/formfill-anchor.svg", + tooltiptext: GetStringFromName("openAutofillMessagePanel"), + }, + mainAction: { + label: GetStringFromName("saveCreditCardLabel"), + accessKey: "S", + callbackState: "save", + }, + secondaryActions: [{ + label: GetStringFromName("cancelCreditCardLabel"), + accessKey: "D", + callbackState: "cancel", + }, { + label: GetStringFromName("disableCreditCardLabel"), + accessKey: "N", + callbackState: "disable", + }], + options: { + persistWhileVisible: true, + popupIconURL: "chrome://formautofill/content/icon-credit-card.svg", + }, + }, }; let FormAutofillDoorhanger = { @@ -136,8 +167,10 @@ let FormAutofillDoorhanger = { * Target browser element for showing doorhanger. * @param {string} id * The ID of the doorhanger. + * @param {string} message + * The localized string for link title. */ - _appendPrivacyPanelLink(browser, id) { + _appendPrivacyPanelLink(browser, id, message) { let notificationId = id + "-notification"; let chromeDoc = browser.ownerDocument; let notification = chromeDoc.getElementById(notificationId); @@ -148,7 +181,7 @@ let FormAutofillDoorhanger = { privacyLinkElement.className = "text-link"; privacyLinkElement.setAttribute("useoriginprincipal", true); privacyLinkElement.setAttribute("href", "about:preferences#privacy"); - privacyLinkElement.setAttribute("value", GetStringFromName(viewAutofillOptsKey)); + privacyLinkElement.setAttribute("value", message); notificationcontent.appendChild(privacyLinkElement); notification.append(notificationcontent); } @@ -221,13 +254,22 @@ let FormAutofillDoorhanger = { async show(browser, type) { log.debug("show doorhanger with type:", type); return new Promise((resolve) => { - let content = CONTENT[type]; + let { + notificationId, + message, + linkMessage, + anchor, + mainAction, + secondaryActions, + options, + } = CONTENT[type]; + let chromeWin = browser.ownerGlobal; - content.options.eventCallback = (topic) => { + options.eventCallback = (topic) => { log.debug("eventCallback:", topic); if (topic == "removed" || topic == "dismissed") { - this._removeCheckboxListener(browser, content); + this._removeCheckboxListener(browser, {notificationId, options}); return; } @@ -235,23 +277,23 @@ let FormAutofillDoorhanger = { if (topic != "shown") { return; } - this._addCheckboxListener(browser, content); + this._addCheckboxListener(browser, {notificationId, options}); // There's no preferences link or other customization in first time use doorhanger. if (type == "firstTimeUse") { return; } - this._appendPrivacyPanelLink(browser, content.notificationId); + this._appendPrivacyPanelLink(browser, notificationId, linkMessage); }; - this._setAnchor(browser, content.anchor); + this._setAnchor(browser, anchor); chromeWin.PopupNotifications.show( browser, - content.notificationId, - content.message, - content.anchor.id, - ...this._createActions(content.mainAction, content.secondaryActions, resolve), - content.options, + notificationId, + message, + anchor.id, + ...this._createActions(mainAction, secondaryActions, resolve), + options, ); }); }, diff --git a/browser/extensions/formautofill/FormAutofillHandler.jsm b/browser/extensions/formautofill/FormAutofillHandler.jsm index efb5d624642a..935fbb941d32 100644 --- a/browser/extensions/formautofill/FormAutofillHandler.jsm +++ b/browser/extensions/formautofill/FormAutofillHandler.jsm @@ -12,14 +12,13 @@ this.EXPORTED_SYMBOLS = ["FormAutofillHandler"]; const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; +Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://formautofill/FormAutofillUtils.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "FormAutofillHeuristics", "resource://formautofill/FormAutofillHeuristics.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "MasterPassword", - "resource://formautofill/MasterPassword.jsm"); this.log = null; FormAutofillUtils.defineLazyLogGetter(this, this.EXPORTED_SYMBOLS[0]); @@ -284,15 +283,14 @@ FormAutofillHandler.prototype = { // card number. Otherwise, the number can be decrypted with the default // password. if (profile["cc-number-encrypted"]) { - try { - profile["cc-number"] = await MasterPassword.decrypt(profile["cc-number-encrypted"], true); - } catch (e) { - if (e.result == Cr.NS_ERROR_ABORT) { - log.warn("User canceled master password entry"); - return; - } - throw e; + let decrypted = await this._decrypt(profile["cc-number-encrypted"], true); + + if (!decrypted) { + // Early return if the decrypted is empty or undefined + return; } + + profile["cc-number"] = decrypted; } targetSet = this.creditCard; } else if (FormAutofillUtils.isAddressField(focusedDetail.fieldName)) { @@ -392,13 +390,13 @@ FormAutofillHandler.prototype = { * @param {Object} focusedInput * A focused input element for determining credit card or address fields. */ - async previewFormFields(profile, focusedInput) { + previewFormFields(profile, focusedInput) { log.debug("preview profile in autofillFormFields:", profile); // Always show the decrypted credit card number when Master Password is // disabled. - if (profile["cc-number-encrypted"] && !MasterPassword.isEnabled) { - profile["cc-number"] = await MasterPassword.decrypt(profile["cc-number-encrypted"], true); + if (profile["cc-number-decrypted"]) { + profile["cc-number"] = profile["cc-number-decrypted"]; } let fieldDetails = this.getFieldDetailsByElement(focusedInput); @@ -565,11 +563,23 @@ FormAutofillHandler.prototype = { delete data.address; } - if (data.creditCard && !data.creditCard.record["cc-number"]) { - log.debug("No credit card record saving since card number is empty"); + if (data.creditCard && (!data.creditCard.record["cc-number"] || + !FormAutofillUtils.isCCNumber(data.creditCard.record["cc-number"]))) { + log.debug("No credit card record saving since card number is invalid"); delete data.creditCard; } return data; }, + + async _decrypt(cipherText, reauth) { + return new Promise((resolve) => { + Services.cpmm.addMessageListener("FormAutofill:DecryptedString", function getResult(result) { + Services.cpmm.removeMessageListener("FormAutofill:DecryptedString", getResult); + resolve(result.data); + }); + + Services.cpmm.sendAsyncMessage("FormAutofill:GetDecryptedString", {cipherText, reauth}); + }); + }, }; diff --git a/browser/extensions/formautofill/FormAutofillNameUtils.jsm b/browser/extensions/formautofill/FormAutofillNameUtils.jsm index a25f6961a9df..36bd20353d43 100644 --- a/browser/extensions/formautofill/FormAutofillNameUtils.jsm +++ b/browser/extensions/formautofill/FormAutofillNameUtils.jsm @@ -277,7 +277,7 @@ var FormAutofillNameUtils = { }, joinNameParts({given, middle, family}) { - if (this._isCJKName(given) && this._isCJKName(family) && middle == "") { + if (this._isCJKName(given) && this._isCJKName(family) && !middle) { return family + given; } return [given, middle, family].filter(part => part && part.length).join(" "); diff --git a/browser/extensions/formautofill/FormAutofillParent.jsm b/browser/extensions/formautofill/FormAutofillParent.jsm index 6933e4b71f99..8da6ab336e75 100644 --- a/browser/extensions/formautofill/FormAutofillParent.jsm +++ b/browser/extensions/formautofill/FormAutofillParent.jsm @@ -50,7 +50,11 @@ XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow", this.log = null; FormAutofillUtils.defineLazyLogGetter(this, this.EXPORTED_SYMBOLS[0]); -const {ENABLED_AUTOFILL_ADDRESSES_PREF, ENABLED_AUTOFILL_CREDITCARDS_PREF} = FormAutofillUtils; +const { + ENABLED_AUTOFILL_ADDRESSES_PREF, + ENABLED_AUTOFILL_CREDITCARDS_PREF, + CREDITCARDS_COLLECTION_NAME, +} = FormAutofillUtils; function FormAutofillParent() { // Lazily load the storage JSM to avoid disk I/O until absolutely needed. @@ -88,6 +92,7 @@ FormAutofillParent.prototype = { Services.ppmm.addMessageListener("FormAutofill:RemoveAddresses", this); Services.ppmm.addMessageListener("FormAutofill:RemoveCreditCards", this); Services.ppmm.addMessageListener("FormAutofill:OpenPreferences", this); + Services.ppmm.addMessageListener("FormAutofill:GetDecryptedString", this); Services.mm.addMessageListener("FormAutofill:OnFormSubmit", this); // Observing the pref and storage changes @@ -219,6 +224,21 @@ FormAutofillParent.prototype = { case "FormAutofill:OpenPreferences": { const win = RecentWindow.getMostRecentBrowserWindow(); win.openPreferences("panePrivacy", {origin: "autofillFooter"}); + break; + } + case "FormAutofill:GetDecryptedString": { + let {cipherText, reauth} = data; + let string; + try { + string = await MasterPassword.decrypt(cipherText, reauth); + } catch (e) { + if (e.result != Cr.NS_ERROR_ABORT) { + throw e; + } + log.warn("User canceled master password entry"); + } + target.sendAsyncMessage("FormAutofill:DecryptedString", string); + break; } } }, @@ -244,7 +264,8 @@ FormAutofillParent.prototype = { /** * Get the records from profile store and return results back to content - * process. + * process. It will decrypt the credit card number and append + * "cc-number-decrypted" to each record if MasterPassword isn't set. * * @private * @param {string} data.collectionName @@ -263,35 +284,43 @@ FormAutofillParent.prototype = { return; } + let recordsInCollection = collection.getAll(); + if (!info || !info.fieldName || !recordsInCollection.length) { + target.sendAsyncMessage("FormAutofill:Records", recordsInCollection); + return; + } + + let isCCAndMPEnabled = collectionName == CREDITCARDS_COLLECTION_NAME && MasterPassword.isEnabled; + // We don't filter "cc-number" when MasterPassword is set. + if (isCCAndMPEnabled && info.fieldName == "cc-number") { + recordsInCollection = recordsInCollection.filter(record => !!record["cc-number"]); + target.sendAsyncMessage("FormAutofill:Records", recordsInCollection); + return; + } + let records = []; - if (info && info.fieldName && - !(MasterPassword.isEnabled && info.fieldName == "cc-number")) { - if (info.fieldName == "cc-number") { - for (let record of collection.getAll()) { - let number = await MasterPassword.decrypt(record["cc-number-encrypted"]); - if (number.startsWith(searchString)) { - records.push(record); - } - } - } else { - let lcSearchString = searchString.toLowerCase(); - let result = collection.getAll().filter(record => { - // Return true if string is not provided and field exists. - // TODO: We'll need to check if the address is for billing or shipping. - // (Bug 1358941) - let name = record[info.fieldName]; + let lcSearchString = searchString.toLowerCase(); - if (!searchString) { - return !!name; - } - - return name && name.toLowerCase().startsWith(lcSearchString); - }); - - records = result; + for (let record of recordsInCollection) { + let fieldValue = record[info.fieldName]; + if (!fieldValue) { + continue; + } + + // Cache the decrypted "cc-number" in each record for content to preview + // when MasterPassword isn't set. + if (!isCCAndMPEnabled && record["cc-number-encrypted"]) { + record["cc-number-decrypted"] = await MasterPassword.decrypt(record["cc-number-encrypted"]); + } + + // Filter "cc-number" based on the decrypted one. + if (info.fieldName == "cc-number") { + fieldValue = record["cc-number-decrypted"]; + } + + if (!lcSearchString || String(fieldValue).toLowerCase().startsWith(lcSearchString)) { + records.push(record); } - } else { - records = collection.getAll(); } target.sendAsyncMessage("FormAutofill:Records", records); @@ -326,9 +355,7 @@ FormAutofillParent.prototype = { this._updateStatus(); }, - _onFormSubmit(data, target) { - let {address} = data; - + _onAddressSubmit(address, target) { if (address.guid) { // Avoid updating the fields that users don't modify. let originalAddress = this.profileStorage.addresses.get(address.guid); @@ -389,4 +416,38 @@ FormAutofillParent.prototype = { } } }, + + async _onCreditCardSubmit(creditCard, target) { + // We'll show the credit card doorhanger if: + // - User applys autofill and changed + // - User fills form manually + if (creditCard.guid && + Object.keys(creditCard.record).every(key => creditCard.untouchedFields.includes(key))) { + return; + } + + let state = await FormAutofillDoorhanger.show(target, "creditCard"); + if (state == "cancel") { + return; + } + + if (state == "disable") { + Services.prefs.setBoolPref("extensions.formautofill.creditCards.enabled", false); + return; + } + + await this.profileStorage.creditCards.normalizeCCNumberFields(creditCard.record); + this.profileStorage.creditCards.add(creditCard.record); + }, + + _onFormSubmit(data, target) { + let {address, creditCard} = data; + + if (address) { + this._onAddressSubmit(address, target); + } + if (creditCard) { + this._onCreditCardSubmit(creditCard, target); + } + }, }; diff --git a/browser/extensions/formautofill/content/icon-credit-card.svg b/browser/extensions/formautofill/content/icon-credit-card.svg new file mode 100644 index 000000000000..3ba136897641 --- /dev/null +++ b/browser/extensions/formautofill/content/icon-credit-card.svg @@ -0,0 +1,8 @@ + + + + + + diff --git a/browser/extensions/formautofill/locales/en-US/formautofill.properties b/browser/extensions/formautofill/locales/en-US/formautofill.properties index 5d4b933d6459..ace30005944e 100644 --- a/browser/extensions/formautofill/locales/en-US/formautofill.properties +++ b/browser/extensions/formautofill/locales/en-US/formautofill.properties @@ -8,14 +8,20 @@ savedAddresses = Saved Addresses… enableCreditCardAutofill = Autofill credit cards savedCreditCards = Saved Credit Cards… saveAddressesMessage = Firefox now saves addresses so you can fill out forms faster. -viewAutofillOptionsLink = View Form Autofill Options +autofillOptionsLink = Form Autofill Options +autofillSecurityOptionsLink = Form Autofill & Security Options changeAutofillOptions = Change Form Autofill Options -viewAutofillOptionsLinkOSX = View Form Autofill Preferences +autofillOptionsLinkOSX = Form Autofill Preferences +autofillSecurityOptionsLinkOSX = Form Autofill & Security Preferences changeAutofillOptionsOSX = Change Form Autofill Preferences addressesSyncCheckbox = Share addresses with synced devices updateAddressMessage = Would you like to update your address with this new information? createAddressLabel = Create New Address updateAddressLabel = Update Address +saveCreditCardMessage = Would you like Firefox to save this credit card? (Security code will not be saved) +saveCreditCardLabel = Save Credit Card +cancelCreditCardLabel = Don’t Save +disableCreditCardLabel = Never Save credit Cards openAutofillMessagePanel = Open Form Autofill message panel autocompleteFooterOption = Form Autofill Options autocompleteFooterOptionShort = More Options diff --git a/browser/extensions/formautofill/skin/shared/editAddress.css b/browser/extensions/formautofill/skin/shared/editAddress.css index 9279c21f282c..42b8774fabe1 100644 --- a/browser/extensions/formautofill/skin/shared/editAddress.css +++ b/browser/extensions/formautofill/skin/shared/editAddress.css @@ -10,30 +10,31 @@ label > span { input, select { width: calc(50% - 9.5em); + margin: 0; } #given-name-container, #additional-name-container, #address-level1-container, #postal-code-container, -#country-container { +#country-container, +#family-name-container, +#organization-container, +#address-level2-container, +#tel-container { flex: 0 1 50%; } #family-name-container, #organization-container, -#street-address-container, #address-level2-container, -#email-container, #tel-container { - flex: 0 1 100%; + padding-inline-end: 50%; } -#family-name, -#organization, -#address-level2, -#tel { - flex: 0 0 auto; +#street-address-container, +#email-container { + flex: 0 1 100%; } #street-address, @@ -42,7 +43,7 @@ select { } #country-warning-message { - flex: 1; + flex: 1 0 auto; align-items: center; text-align: start; color: #737373; diff --git a/browser/extensions/formautofill/skin/shared/editDialog.css b/browser/extensions/formautofill/skin/shared/editDialog.css index d2a0c6c74559..a6531cd9703a 100644 --- a/browser/extensions/formautofill/skin/shared/editDialog.css +++ b/browser/extensions/formautofill/skin/shared/editDialog.css @@ -17,9 +17,9 @@ form { flex-wrap: wrap; } -label, -p { - margin: 0 0 0.5em; +form > label, +form > p { + margin: 0 0 0.5em !important; } label > span, diff --git a/browser/extensions/formautofill/test/browser/browser.ini b/browser/extensions/formautofill/test/browser/browser.ini index c34bb4471b2b..264da4a2208b 100644 --- a/browser/extensions/formautofill/test/browser/browser.ini +++ b/browser/extensions/formautofill/test/browser/browser.ini @@ -9,6 +9,7 @@ support-files = [browser_autocomplete_marked_back_forward.js] [browser_autocomplete_marked_detached_tab.js] [browser_check_installed.js] +[browser_creditCard_doorhanger.js] [browser_dropdown_layout.js] [browser_editAddressDialog.js] [browser_editCreditCardDialog.js] diff --git a/browser/extensions/formautofill/test/browser/browser_creditCard_doorhanger.js b/browser/extensions/formautofill/test/browser/browser_creditCard_doorhanger.js new file mode 100644 index 000000000000..ccb4036d1bd2 --- /dev/null +++ b/browser/extensions/formautofill/test/browser/browser_creditCard_doorhanger.js @@ -0,0 +1,61 @@ +/* eslint-disable mozilla/no-arbitrary-setTimeout */ +"use strict"; + +add_task(async function test_submit_creditCard_cancel_saving() { + await BrowserTestUtils.withNewTab({gBrowser, url: CREDITCARD_FORM_URL}, + async function(browser) { + let promiseShown = BrowserTestUtils.waitForEvent(PopupNotifications.panel, + "popupshown"); + await ContentTask.spawn(browser, null, async function() { + let form = content.document.getElementById("form"); + let name = form.querySelector("#cc-name"); + name.focus(); + name.setUserInput("User 1"); + + let number = form.querySelector("#cc-number"); + number.setUserInput("1111222233334444"); + + // Wait 1000ms before submission to make sure the input value applied + await new Promise(resolve => setTimeout(resolve, 1000)); + form.querySelector("input[type=submit]").click(); + }); + + await promiseShown; + await clickDoorhangerButton(SECONDARY_BUTTON); + } + ); + + await sleep(1000); + let creditCards = await getCreditCards(); + is(creditCards.length, 0, "No credit card saved"); +}); + +add_task(async function test_submit_creditCard_saved() { + await BrowserTestUtils.withNewTab({gBrowser, url: CREDITCARD_FORM_URL}, + async function(browser) { + let promiseShown = BrowserTestUtils.waitForEvent(PopupNotifications.panel, + "popupshown"); + await ContentTask.spawn(browser, null, async function() { + let form = content.document.getElementById("form"); + let name = form.querySelector("#cc-name"); + name.focus(); + name.setUserInput("User 1"); + + let number = form.querySelector("#cc-number"); + number.setUserInput("1111222233334444"); + + // Wait 1000ms before submission to make sure the input value applied + await new Promise(resolve => setTimeout(resolve, 1000)); + form.querySelector("input[type=submit]").click(); + }); + + await promiseShown; + await clickDoorhangerButton(MAIN_BUTTON); + await TestUtils.topicObserved("formautofill-storage-changed"); + } + ); + + let creditCards = await getCreditCards(); + is(creditCards.length, 1, "Still 1 address in storage"); + is(creditCards[0]["cc-name"], "User 1", "Verify the name field"); +}); diff --git a/browser/extensions/formautofill/test/browser/browser_first_time_use_doorhanger.js b/browser/extensions/formautofill/test/browser/browser_first_time_use_doorhanger.js index f5457b710357..a059109385b6 100644 --- a/browser/extensions/formautofill/test/browser/browser_first_time_use_doorhanger.js +++ b/browser/extensions/formautofill/test/browser/browser_first_time_use_doorhanger.js @@ -32,7 +32,7 @@ add_task(async function test_first_time_save() { let cb = getDoorhangerCheckbox(); ok(cb.hidden, "Sync checkbox should be hidden"); // Open the panel via main button - await clickDoorhangerButton(MAIN_BUTTON_INDEX); + await clickDoorhangerButton(MAIN_BUTTON); let tab = await tabPromise; ok(tab, "Privacy panel opened"); await BrowserTestUtils.removeTab(tab); @@ -111,7 +111,7 @@ add_task(async function test_first_time_save_with_sync_account() { is(SpecialPowers.getBoolPref(SYNC_ADDRESSES_PREF), true, "addresses sync should be enabled after checked"); // Open the panel via main button - await clickDoorhangerButton(MAIN_BUTTON_INDEX); + await clickDoorhangerButton(MAIN_BUTTON); let tab = await tabPromise; ok(tab, "Privacy panel opened"); await BrowserTestUtils.removeTab(tab); diff --git a/browser/extensions/formautofill/test/browser/browser_update_doorhanger.js b/browser/extensions/formautofill/test/browser/browser_update_doorhanger.js index 47a359c4a7dd..66720004e7e7 100644 --- a/browser/extensions/formautofill/test/browser/browser_update_doorhanger.js +++ b/browser/extensions/formautofill/test/browser/browser_update_doorhanger.js @@ -26,7 +26,7 @@ add_task(async function test_update_address() { }); await promiseShown; - await clickDoorhangerButton(MAIN_BUTTON_INDEX); + await clickDoorhangerButton(MAIN_BUTTON); } ); @@ -59,7 +59,7 @@ add_task(async function test_create_new_address() { }); await promiseShown; - await clickDoorhangerButton(SECONDARY_BUTTON_INDEX); + await clickDoorhangerButton(SECONDARY_BUTTON); } ); @@ -92,7 +92,7 @@ add_task(async function test_create_new_address_merge() { }); await promiseShown; - await clickDoorhangerButton(SECONDARY_BUTTON_INDEX); + await clickDoorhangerButton(SECONDARY_BUTTON); } ); @@ -128,7 +128,7 @@ add_task(async function test_submit_untouched_fields() { }); await promiseShown; - await clickDoorhangerButton(MAIN_BUTTON_INDEX); + await clickDoorhangerButton(MAIN_BUTTON); } ); diff --git a/browser/extensions/formautofill/test/browser/head.js b/browser/extensions/formautofill/test/browser/head.js index c5b0edd34e66..40f199398881 100644 --- a/browser/extensions/formautofill/test/browser/head.js +++ b/browser/extensions/formautofill/test/browser/head.js @@ -1,6 +1,6 @@ /* exported MANAGE_ADDRESSES_DIALOG_URL, MANAGE_CREDIT_CARDS_DIALOG_URL, EDIT_ADDRESS_DIALOG_URL, EDIT_CREDIT_CARD_DIALOG_URL, BASE_URL, TEST_ADDRESS_1, TEST_ADDRESS_2, TEST_ADDRESS_3, TEST_ADDRESS_4, TEST_ADDRESS_5, - TEST_CREDIT_CARD_1, TEST_CREDIT_CARD_2, TEST_CREDIT_CARD_3, FORM_URL, + TEST_CREDIT_CARD_1, TEST_CREDIT_CARD_2, TEST_CREDIT_CARD_3, FORM_URL, CREDITCARD_FORM_URL, FTU_PREF, ENABLED_AUTOFILL_ADDRESSES_PREF, ENABLED_AUTOFILL_CREDITCARDS_PREF, SYNC_USERNAME_PREF, SYNC_ADDRESSES_PREF, sleep, expectPopupOpen, openPopupOn, expectPopupClose, closePopup, clickDoorhangerButton, getAddresses, saveAddress, removeAddresses, saveCreditCard, @@ -14,6 +14,8 @@ const EDIT_ADDRESS_DIALOG_URL = "chrome://formautofill/content/editAddress.xhtml const EDIT_CREDIT_CARD_DIALOG_URL = "chrome://formautofill/content/editCreditCard.xhtml"; const BASE_URL = "http://mochi.test:8888/browser/browser/extensions/formautofill/test/browser/"; const FORM_URL = "http://mochi.test:8888/browser/browser/extensions/formautofill/test/browser/autocomplete_basic.html"; +const CREDITCARD_FORM_URL = + "http://mochi.test:8888/browser/browser/extensions/formautofill/test/browser/autocomplete_creditcard_basic.html"; const FTU_PREF = "extensions.formautofill.firstTimeUse"; const ENABLED_AUTOFILL_ADDRESSES_PREF = "extensions.formautofill.addresses.enabled"; const ENABLED_AUTOFILL_CREDITCARDS_PREF = "extensions.formautofill.creditCards.enabled"; @@ -60,7 +62,6 @@ const TEST_ADDRESS_5 = { const TEST_CREDIT_CARD_1 = { "cc-name": "John Doe", "cc-number": "1234567812345678", - // "cc-number-encrypted": "", "cc-exp-month": 4, "cc-exp-year": 2017, }; @@ -78,8 +79,9 @@ const TEST_CREDIT_CARD_3 = { "cc-exp-year": 2000, }; -const MAIN_BUTTON_INDEX = 0; -const SECONDARY_BUTTON_INDEX = 1; +const MAIN_BUTTON = "button"; +const SECONDARY_BUTTON = "secondaryButton"; +const MENU_BUTTON = "menubutton"; function getDisplayedPopupItems(browser, selector = ".autocomplete-richlistitem") { const {autoCompletePopup: {richlistbox: itemsBox}} = browser; @@ -188,25 +190,28 @@ function removeCreditCards(guids) { /** * Clicks the popup notification button and wait for popup hidden. * - * @param {number} buttonIndex Number indicating which button to click. - * See the constants in this file. + * @param {string} button The button type in popup notification. + * @param {number} index The action's index in menu list. */ -async function clickDoorhangerButton(buttonIndex) { +async function clickDoorhangerButton(button, index) { let popuphidden = BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popuphidden"); let notifications = PopupNotifications.panel.childNodes; ok(notifications.length > 0, "at least one notification displayed"); ok(true, notifications.length + " notification(s)"); let notification = notifications[0]; - if (buttonIndex == MAIN_BUTTON_INDEX) { - ok(true, "Triggering main action"); - EventUtils.synthesizeMouseAtCenter(notification.button, {}); - } else if (buttonIndex == SECONDARY_BUTTON_INDEX) { - ok(true, "Triggering secondary action"); - EventUtils.synthesizeMouseAtCenter(notification.secondaryButton, {}); - } else if (notification.childNodes[buttonIndex - 1]) { - ok(true, "Triggering secondary action with index " + buttonIndex); - EventUtils.synthesizeMouseAtCenter(notification.childNodes[buttonIndex - 1], {}); + if (button == MAIN_BUTTON || button == SECONDARY_BUTTON) { + EventUtils.synthesizeMouseAtCenter(notification[button], {}); + } else if (button == MENU_BUTTON) { + // Click the dropmarker arrow and wait for the menu to show up. + let dropdownPromise = + BrowserTestUtils.waitForEvent(notification.menupopup, "popupshown"); + await EventUtils.synthesizeMouseAtCenter(notification.menubutton, {}); + ok(true, "notification menupopup displayed"); + await dropdownPromise; + + let actionMenuItem = notification.querySelectorAll("menuitem")[index]; + await EventUtils.synthesizeMouseAtCenter(actionMenuItem, {}); } await popuphidden; } diff --git a/browser/extensions/formautofill/test/unit/test_autofillFormFields.js b/browser/extensions/formautofill/test/unit/test_autofillFormFields.js index 9ada7016939a..55fb61adc06d 100644 --- a/browser/extensions/formautofill/test/unit/test_autofillFormFields.js +++ b/browser/extensions/formautofill/test/unit/test_autofillFormFields.js @@ -503,6 +503,19 @@ function do_test(testcases, testFn) { let formLike = FormLikeFactory.createFromForm(form); let handler = new FormAutofillHandler(formLike); let promises = []; + // Replace the interal decrypt method with MasterPassword API + handler._decrypt = async (cipherText, reauth) => { + let string; + try { + string = await MasterPassword.decrypt(cipherText, reauth); + } catch (e) { + if (e.result != Cr.NS_ERROR_ABORT) { + throw e; + } + do_print("User canceled master password entry"); + } + return string; + }; handler.collectFormFields(); let handlerInfo = handler[testcase.expectedFillingForm]; diff --git a/browser/extensions/formautofill/test/unit/test_getRecords.js b/browser/extensions/formautofill/test/unit/test_getRecords.js index 0b2984739b5d..09ef2cd6b543 100644 --- a/browser/extensions/formautofill/test/unit/test_getRecords.js +++ b/browser/extensions/formautofill/test/unit/test_getRecords.js @@ -1,5 +1,5 @@ /* - * Test for make sure getRecords can retrieve right collection from storag. + * Test for make sure getRecords can retrieve right collection from storage. */ "use strict"; @@ -166,11 +166,14 @@ add_task(async function test_getRecords_creditCards() { await formAutofillParent.init(); await formAutofillParent.profileStorage.initialize(); let collection = profileStorage.creditCards; + let decryptedCCNumber = [TEST_CREDIT_CARD_1["cc-number"], TEST_CREDIT_CARD_2["cc-number"]]; await collection.normalizeCCNumberFields(TEST_CREDIT_CARD_1); await collection.normalizeCCNumberFields(TEST_CREDIT_CARD_2); - let mockCreditCards = [TEST_CREDIT_CARD_1, TEST_CREDIT_CARD_2]; - sinon.stub(collection, "getAll"); - collection.getAll.returns(mockCreditCards); + sinon.stub(collection, "getAll", () => [Object.assign({}, TEST_CREDIT_CARD_1), Object.assign({}, TEST_CREDIT_CARD_2)]); + let CreditCardsWithDecryptedNumber = [ + Object.assign({}, TEST_CREDIT_CARD_1, {"cc-number-decrypted": decryptedCCNumber[0]}), + Object.assign({}, TEST_CREDIT_CARD_2, {"cc-number-decrypted": decryptedCCNumber[1]}), + ]; let testCases = [ { @@ -180,7 +183,7 @@ add_task(async function test_getRecords_creditCards() { info: {fieldName: "cc-name"}, searchString: "John Doe", }, - expectedResult: [TEST_CREDIT_CARD_1], + expectedResult: CreditCardsWithDecryptedNumber.slice(0, 1), }, { description: "If the search string could match multiple creditCards (without masterpassword)", @@ -189,7 +192,7 @@ add_task(async function test_getRecords_creditCards() { info: {fieldName: "cc-name"}, searchString: "John", }, - expectedResult: [TEST_CREDIT_CARD_1, TEST_CREDIT_CARD_2], + expectedResult: CreditCardsWithDecryptedNumber, }, { description: "If the search string could not match any creditCard (without masterpassword)", @@ -207,7 +210,7 @@ add_task(async function test_getRecords_creditCards() { info: {fieldName: "cc-number"}, searchString: "123", }, - expectedResult: [TEST_CREDIT_CARD_1], + expectedResult: CreditCardsWithDecryptedNumber.slice(0, 1), }, { description: "If the search string could match multiple creditCards (without masterpassword)", @@ -216,7 +219,7 @@ add_task(async function test_getRecords_creditCards() { info: {fieldName: "cc-number"}, searchString: "1", }, - expectedResult: [TEST_CREDIT_CARD_1, TEST_CREDIT_CARD_2], + expectedResult: CreditCardsWithDecryptedNumber, }, { description: "If the search string could match 1 creditCard (with masterpassword)", diff --git a/browser/extensions/formautofill/test/unit/test_transformFields.js b/browser/extensions/formautofill/test/unit/test_transformFields.js index 3f6bc8563102..d7aecfa7a5b3 100644 --- a/browser/extensions/formautofill/test/unit/test_transformFields.js +++ b/browser/extensions/formautofill/test/unit/test_transformFields.js @@ -33,6 +33,18 @@ const ADDRESS_COMPUTE_TESTCASES = [ "name": "Timothy John Berners-Lee", }, }, + { + description: "Has split CJK names", + address: { + "given-name": "德明", + "family-name": "孫", + }, + expectedResult: { + "given-name": "德明", + "family-name": "孫", + "name": "孫德明", + }, + }, // Address { diff --git a/browser/extensions/onboarding/content/onboarding.js b/browser/extensions/onboarding/content/onboarding.js index 3f9465a49396..4d337251516a 100644 --- a/browser/extensions/onboarding/content/onboarding.js +++ b/browser/extensions/onboarding/content/onboarding.js @@ -238,7 +238,7 @@ var onboardingTourset = {
`; return div; diff --git a/browser/extensions/onboarding/locales/en-US/onboarding.properties b/browser/extensions/onboarding/locales/en-US/onboarding.properties index d1322dd1e65f..51edabee4f7e 100644 --- a/browser/extensions/onboarding/locales/en-US/onboarding.properties +++ b/browser/extensions/onboarding/locales/en-US/onboarding.properties @@ -96,7 +96,7 @@ onboarding.tour-library=Library onboarding.tour-library.title=Keep it together. # LOCALIZATION NOTE (onboarding.tour-library.description): This string will be used in the library tour description. %1$S is brandShortName onboarding.tour-library.description=Check out the new %1$S library in the redesigned toolbar. The library puts the things you’ve seen and saved to %1$S - your browsing history, bookmarks, Pocket lists, and synced tabs - in one convenient place. -onboarding.tour-library.button=Show Library in Menu +onboarding.tour-library.button2=Show Library Menu onboarding.notification.onboarding-tour-library.title=Keep it together. # LOCALIZATION NOTE(onboarding.notification.onboarding-tour-library.message): This string will be used in the notification message for the library tour. %S is brandShortName onboarding.notification.onboarding-tour-library.message=The new %S library puts the great things you’ve discovered on the web in one convenient place. diff --git a/devtools/client/framework/about-devtools-toolbox.js b/devtools/client/framework/about-devtools-toolbox.js index cc0d366014c4..870847ca2d44 100644 --- a/devtools/client/framework/about-devtools-toolbox.js +++ b/devtools/client/framework/about-devtools-toolbox.js @@ -31,7 +31,9 @@ AboutURL.prototype = { }, getURIFlags: function (aURI) { - return nsIAboutModule.ALLOW_SCRIPT || nsIAboutModule.ENABLE_INDEXED_DB; + return nsIAboutModule.ALLOW_SCRIPT | + nsIAboutModule.ENABLE_INDEXED_DB | + nsIAboutModule.HIDE_FROM_ABOUTABOUT; } }; diff --git a/devtools/client/locales/en-US/debugger.properties b/devtools/client/locales/en-US/debugger.properties index 9cc6ebd8027a..55839f792d80 100644 --- a/devtools/client/locales/en-US/debugger.properties +++ b/devtools/client/locales/en-US/debugger.properties @@ -17,17 +17,11 @@ collapsePanes=Collapse panes # LOCALIZATION NOTE (copySourceUrl): This is the text that appears in the # context menu to copy the source URL of file open. copySourceUrl=Copy Source Url - -# LOCALIZATION NOTE (copySourceUrl.accesskey): Access key to copy the source URL of a file from -# the context menu. copySourceUrl.accesskey=u # LOCALIZATION NOTE (copyStackTrace): This is the text that appears in the # context menu to copy the stack trace methods, file names and row number. copyStackTrace=Copy Stack Trace - -# LOCALIZATION NOTE (copyStackTrace.accesskey): Access key to copy the stack trace data from -# the context menu. copyStackTrace.accesskey=c # LOCALIZATION NOTE (expandPanes): This is the tooltip for the button @@ -212,50 +206,23 @@ searchPanelVariable=Filter variables (%S) # are displayed in the breakpoints menu item popup. breakpointMenuItem.setConditional=Configure conditional breakpoint breakpointMenuItem.enableSelf=Enable breakpoint +breakpointMenuItem.enableSelf.accesskey=E breakpointMenuItem.disableSelf=Disable breakpoint +breakpointMenuItem.disableSelf.accesskey=D breakpointMenuItem.deleteSelf=Remove breakpoint +breakpointMenuItem.deleteSelf.accesskey=R breakpointMenuItem.enableOthers=Enable others +breakpointMenuItem.enableOthers.accesskey=o breakpointMenuItem.disableOthers=Disable others -breakpointMenuItem.deleteOthers=Remove others -breakpointMenuItem.enableAll=Enable all breakpoints -breakpointMenuItem.disableAll=Disable all breakpoints -breakpointMenuItem.deleteAll=Remove all breakpoints - -# LOCALIZATION NOTE (breakpointMenuItem.deleteSelf.accesskey): Access key to remove the -# currently selected breakpoint from the context menu -breakpointMenuItem.deleteSelf.accesskey=r - -# LOCALIZATION NOTE (breakpointMenuItem.enableSelf.accesskey): Access key to enable the -# currently selected breakpoint from the context menu -breakpointMenuItem.enableSelf.accesskey=e - -# LOCALIZATION NOTE (breakpointMenuItem.disableSelf.accesskey): Access key to disable the -# currently selected breakpoint from the context menu -breakpointMenuItem.disableSelf.accesskey=d - -# LOCALIZATION NOTE (breakpointMenuItem.deleteAll.accesskey): Access key to remove all -# the breakpoints from the context menu -breakpointMenuItem.deleteAll.accesskey=a - -# LOCALIZATION NOTE (breakpointMenuItem.enableAll.accesskey): Access key to enable all -# the breakpoints from the context menu -breakpointMenuItem.enableAll.accesskey=b - -# LOCALIZATION NOTE (breakpointMenuItem.disableAll.accesskey): Access key to disable all -# the breakpoints from the context menu -breakpointMenuItem.disableAll.accesskey=c - -# LOCALIZATION NOTE (breakpointMenuItem.deleteOthers.accesskey): Access key to remove -# other breakpoints from the context menu -breakpointMenuItem.deleteOthers.accesskey=p - -# LOCALIZATION NOTE (breakpointMenuItem.enableOthers.accesskey): Access key to enable -# other breakpoints from the context menu -breakpointMenuItem.enableOthers.accesskey=q - -# LOCALIZATION NOTE (breakpointMenuItem.disableOthers.accesskey): Access key to disable -# other breakpoints from the context menu breakpointMenuItem.disableOthers.accesskey=s +breakpointMenuItem.deleteOthers=Remove others +breakpointMenuItem.deleteOthers.accesskey=h +breakpointMenuItem.enableAll=Enable all breakpoints +breakpointMenuItem.enableAll.accesskey=b +breakpointMenuItem.disableAll=Disable all breakpoints +breakpointMenuItem.disableAll.accesskey=k +breakpointMenuItem.deleteAll=Remove all breakpoints +breakpointMenuItem.deleteAll.accesskey=a # LOCALIZATION NOTE (breakpoints.header): Breakpoints right sidebar pane header. breakpoints.header=Breakpoints @@ -353,17 +320,11 @@ editor.jumpToMappedLocation1=Jump to %S location # LOCALIZATION NOTE (framework.disableGrouping): This is the text that appears in the # context menu to disable framework grouping. framework.disableGrouping=Disable Framework Grouping - -# LOCALIZATION NOTE (framework.disableGrouping.accesskey): Access key to toggle -# framework grouping from the context menu. framework.disableGrouping.accesskey=u # LOCALIZATION NOTE (framework.enableGrouping): This is the text that appears in the # context menu to enable framework grouping. framework.enableGrouping=Enable Framework Grouping - -# LOCALIZATION NOTE (framework.enableGrouping.accesskey): Access key to toggle -# framework grouping from the context menu. framework.enableGrouping.accesskey=u # LOCALIZATION NOTE (generated): Source Map term for a server source location @@ -379,75 +340,48 @@ expressions.placeholder=Add Watch Expression # LOCALIZATION NOTE (sourceTabs.closeTab): Editor source tab context menu item # for closing the selected tab below the mouse. sourceTabs.closeTab=Close tab - -# LOCALIZATION NOTE (sourceTabs.closeTab.accesskey): Access key to close the currently select -# source tab from the editor context menu item. sourceTabs.closeTab.accesskey=c # LOCALIZATION NOTE (sourceTabs.closeOtherTabs): Editor source tab context menu item # for closing the other tabs. sourceTabs.closeOtherTabs=Close others - -# LOCALIZATION NOTE (sourceTabs.closeOtherTabs.accesskey): Access key to close other source tabs -# from the editor context menu. sourceTabs.closeOtherTabs.accesskey=o # LOCALIZATION NOTE (sourceTabs.closeTabsToEnd): Editor source tab context menu item # for closing the tabs to the end (the right for LTR languages) of the selected tab. sourceTabs.closeTabsToEnd=Close tabs to the right - -# LOCALIZATION NOTE (sourceTabs.closeTabsToEnd.accesskey): Access key to close source tabs -# after the selected tab from the editor context menu. sourceTabs.closeTabsToEnd.accesskey=e # LOCALIZATION NOTE (sourceTabs.closeAllTabs): Editor source tab context menu item # for closing all tabs. sourceTabs.closeAllTabs=Close all tabs - -# LOCALIZATION NOTE (sourceTabs.closeAllTabs.accesskey): Access key to close all tabs from the -# editor context menu. sourceTabs.closeAllTabs.accesskey=a # LOCALIZATION NOTE (sourceTabs.revealInTree): Editor source tab context menu item # for revealing source in tree. sourceTabs.revealInTree=Reveal in Tree - -# LOCALIZATION NOTE (sourceTabs.revealInTree.accesskey): Access key to reveal a source in the -# tree from the context menu. sourceTabs.revealInTree.accesskey=r # LOCALIZATION NOTE (sourceTabs.copyLink): Editor source tab context menu item # for copying a link address. sourceTabs.copyLink=Copy Link Address - -# LOCALIZATION NOTE (sourceTabs.copyLink.accesskey): Access key to copy a link addresss from the -# editor context menu. sourceTabs.copyLink.accesskey=l # LOCALIZATION NOTE (sourceTabs.prettyPrint): Editor source tab context menu item # for pretty printing the source. sourceTabs.prettyPrint=Pretty Print Source - -# LOCALIZATION NOTE (sourceTabs.prettyPrint.accesskey): Access key to pretty print a source from -# the editor context menu. sourceTabs.prettyPrint.accesskey=p # LOCALIZATION NOTE (sourceFooter.blackbox): Tooltip text associated # with the blackbox button sourceFooter.blackbox=Blackbox Source +sourceFooter.blackbox.accesskey=B # LOCALIZATION NOTE (sourceFooter.unblackbox): Tooltip text associated # with the blackbox button sourceFooter.unblackbox=Unblackbox Source - -# LOCALIZATION NOTE (sourceFooter.unblackbox.accesskey): Access key to blackbox -# an associated source sourceFooter.unblackbox.accesskey=b -# LOCALIZATION NOTE (sourceFooter.blackbox.accesskey): Access key to blackbox -# an associated source -sourceFooter.blackbox.accesskey=b - # LOCALIZATION NOTE (sourceFooter.blackboxed): Text associated # with a blackboxed source sourceFooter.blackboxed=Blackboxed Source diff --git a/dom/animation/test/mochitest.ini b/dom/animation/test/mochitest.ini index 685c5f5037f5..0f7d570b389b 100644 --- a/dom/animation/test/mochitest.ini +++ b/dom/animation/test/mochitest.ini @@ -81,7 +81,6 @@ support-files = [css-animations/test_event-dispatch.html] [css-animations/test_event-order.html] [css-animations/test_keyframeeffect-getkeyframes.html] -skip-if = stylo [css-animations/test_pseudoElement-get-animations.html] [css-animations/test_setting-effect.html] [css-transitions/test_animation-cancel.html] diff --git a/dom/animation/test/mozilla/file_deferred_start.html b/dom/animation/test/mozilla/file_deferred_start.html index d823be5c8079..6af6455826a7 100644 --- a/dom/animation/test/mozilla/file_deferred_start.html +++ b/dom/animation/test/mozilla/file_deferred_start.html @@ -104,8 +104,15 @@ promise_test(function(t) { div.animate({ transform: [ 'translate(0px)', 'translate(100px)' ] }, { duration: 400 * MS_PER_SEC, delay: -200 * MS_PER_SEC }); - return waitForPaints(); - }).then(() => { + + // TODO: Current waitForPaint() will not wait for MozAfterPaint in this + // case(Bug 1341294), so this waiting code is workaround for it. + // This waitForFrame() uses Promise, but bug 1193394 will be using same + // handling of microtask, so if landed bug 1193394 this test might be + // failure since this promise will resolve in same tick of Element.animate. + return waitForFrame(); + }).then(() => waitForPaints()) + .then(() => { const transformStr = SpecialPowers.DOMWindowUtils.getOMTAStyle(div, 'transform'); const translateX = getTranslateXFromTransform(transformStr); @@ -146,8 +153,12 @@ promise_test(function(t) { 200 * MS_PER_SEC); animation.currentTime = 100 * MS_PER_SEC; animation.playbackRate = 0.1; - return waitForPaints(); - }).then(() => { + + // As the above test case, we should fix bug 1341294 before bug 1193394 + // lands. + return waitForFrame(); + }).then(() => waitForPaints()) + .then(() => { const transformStr = SpecialPowers.DOMWindowUtils.getOMTAStyle(div, 'transform'); const translateX = getTranslateXFromTransform(transformStr); diff --git a/dom/base/Selection.cpp b/dom/base/Selection.cpp index 07f9703eba0a..a7b5b9618110 100644 --- a/dom/base/Selection.cpp +++ b/dom/base/Selection.cpp @@ -786,6 +786,7 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Selection) // we don't want to notify the listeners during JS GC (they could be // in JS!). NS_IMPL_CYCLE_COLLECTION_UNLINK(mSelectionListeners) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mCachedRange) tmp->RemoveAllRanges(); NS_IMPL_CYCLE_COLLECTION_UNLINK(mFrameSelection) NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER @@ -798,6 +799,7 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Selection) } } NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAnchorFocusRange) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCachedRange) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFrameSelection) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSelectionListeners) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END @@ -2290,6 +2292,32 @@ Selection::RemoveAllRanges(ErrorResult& aRv) } } +nsresult +Selection::RemoveAllRangesTemporarily() +{ + if (!mCachedRange) { + // Look for a range which isn't referred by other than this instance. + // If there is, it'll be released by calling Clear(). So, we can reuse it + // when we need to create a range. + for (auto& rangeData : mRanges) { + auto& range = rangeData.mRange; + if (range->GetRefCount() == 1 || + (range->GetRefCount() == 2 && range == mAnchorFocusRange)) { + mCachedRange = range; + break; + } + } + } + + // Then, remove all ranges. + ErrorResult result; + RemoveAllRanges(result); + if (result.Failed()) { + mCachedRange = nullptr; + } + return result.StealNSResult(); +} + /** AddRange adds the specified range to the selection * @param aRange is the range to be added */ @@ -2332,6 +2360,10 @@ Selection::AddRangeInternal(nsRange& aRange, nsIDocument* aDocument, return; } + // If a range is being added, we don't need cached range because Collapse() + // won't use it. + mCachedRange = nullptr; + // AddTableCellRange might flush frame. RefPtr kungFuDeathGrip(this); @@ -2588,7 +2620,9 @@ Selection::Collapse(nsINode& aContainer, uint32_t aOffset, ErrorResult& aRv) // If the old range isn't referred by anybody other than this method, // we should reuse it for reducing the recreation cost. if (oldRange && oldRange->GetRefCount() == 1) { - range = oldRange; + range = Move(oldRange); + } else if (mCachedRange) { + range = Move(mCachedRange); } else { range = new nsRange(container); } @@ -4061,18 +4095,28 @@ Selection::SetBaseAndExtent(nsINode& aAnchorNode, uint32_t aAnchorOffset, endOffset = aAnchorOffset; } - RefPtr newRange; - nsresult rv = nsRange::CreateRange(start, startOffset, end, endOffset, - getter_AddRefs(newRange)); - // CreateRange returns IndexSizeError if any offset is out of bounds. + // If there is cached range, we should reuse it for saving the allocation + // const (and some other cost in nsRange::DoSetRange(). + RefPtr newRange = Move(mCachedRange); + + nsresult rv = NS_OK; + if (newRange) { + rv = newRange->SetStartAndEnd(start, startOffset, end, endOffset); + } else { + rv = nsRange::CreateRange(start, startOffset, end, endOffset, + getter_AddRefs(newRange)); + } + + // nsRange::SetStartAndEnd() and nsRange::CreateRange() returns + // IndexSizeError if any offset is out of bounds. if (NS_FAILED(rv)) { aRv.Throw(rv); return; } - rv = RemoveAllRanges(); - if (NS_FAILED(rv)) { - aRv.Throw(rv); + // Use non-virtual method instead of nsISelection::RemoveAllRanges(). + RemoveAllRanges(aRv); + if (aRv.Failed()) { return; } diff --git a/dom/base/Selection.h b/dom/base/Selection.h index 50377d86dd64..6d40e9a6d753 100644 --- a/dom/base/Selection.h +++ b/dom/base/Selection.h @@ -215,6 +215,14 @@ public: void RemoveRange(nsRange& aRange, mozilla::ErrorResult& aRv); void RemoveAllRanges(mozilla::ErrorResult& aRv); + /** + * RemoveAllRangesTemporarily() is useful if the caller will add one or more + * ranges later. This tries to cache a removing range if it's possible. + * If a range is not referred by anything else this selection, the range + * can be reused later. Otherwise, this works as same as RemoveAllRanges(). + */ + nsresult RemoveAllRangesTemporarily(); + void Stringify(nsAString& aResult); bool ContainsNode(nsINode& aNode, bool aPartlyContained, mozilla::ErrorResult& aRv); @@ -473,6 +481,12 @@ private: AutoTArray mRanges; RefPtr mAnchorFocusRange; + // mCachedRange is set by RemoveAllRangesTemporarily() and used by + // Collapse() and SetBaseAndExtent(). If there is a range which will be + // released by Clear(), RemoveAllRangesTemporarily() stores it with this. + // If Collapse() is called without existing ranges, it'll reuse this range + // for saving the creation cost. + RefPtr mCachedRange; RefPtr mFrameSelection; RefPtr mAutoScrollTimer; FallibleTArray> mSelectionListeners; @@ -552,30 +566,16 @@ public: } // namespace dom inline bool -IsValidSelectionType(RawSelectionType aRawSelectionType) +IsValidRawSelectionType(RawSelectionType aRawSelectionType) { - switch (static_cast(aRawSelectionType)) { - case SelectionType::eNone: - case SelectionType::eNormal: - case SelectionType::eSpellCheck: - case SelectionType::eIMERawClause: - case SelectionType::eIMESelectedRawClause: - case SelectionType::eIMEConvertedClause: - case SelectionType::eIMESelectedClause: - case SelectionType::eAccessibility: - case SelectionType::eFind: - case SelectionType::eURLSecondary: - case SelectionType::eURLStrikeout: - return true; - default: - return false; - } + return aRawSelectionType >= nsISelectionController::SELECTION_NONE && + aRawSelectionType <= nsISelectionController::SELECTION_URLSTRIKEOUT; } inline SelectionType ToSelectionType(RawSelectionType aRawSelectionType) { - if (!IsValidSelectionType(aRawSelectionType)) { + if (!IsValidRawSelectionType(aRawSelectionType)) { return SelectionType::eInvalid; } return static_cast(aRawSelectionType); @@ -584,18 +584,22 @@ ToSelectionType(RawSelectionType aRawSelectionType) inline RawSelectionType ToRawSelectionType(SelectionType aSelectionType) { + MOZ_ASSERT(aSelectionType != SelectionType::eInvalid); return static_cast(aSelectionType); } -inline RawSelectionType ToRawSelectionType(TextRangeType aTextRangeType) +inline RawSelectionType +ToRawSelectionType(TextRangeType aTextRangeType) { return ToRawSelectionType(ToSelectionType(aTextRangeType)); } -inline bool operator &(SelectionType aSelectionType, - RawSelectionType aRawSelectionTypes) +inline SelectionTypeMask +ToSelectionTypeMask(SelectionType aSelectionType) { - return (ToRawSelectionType(aSelectionType) & aRawSelectionTypes) != 0; + MOZ_ASSERT(aSelectionType != SelectionType::eInvalid); + return aSelectionType == SelectionType::eNone ? 0 : + (1 << (static_cast(aSelectionType) - 1)); } } // namespace mozilla diff --git a/dom/base/nsContentUtils.cpp b/dom/base/nsContentUtils.cpp index f9fc428a13a3..6141ffcfafc5 100644 --- a/dom/base/nsContentUtils.cpp +++ b/dom/base/nsContentUtils.cpp @@ -5412,52 +5412,6 @@ nsContentUtils::IsInSameAnonymousTree(const nsINode* aNode, return nodeAsContent->GetBindingParent() == aContent->GetBindingParent(); } -class AnonymousContentDestroyer : public Runnable { -public: - explicit AnonymousContentDestroyer(nsCOMPtr* aContent) - : mozilla::Runnable("AnonymousContentDestroyer") - { - mContent.swap(*aContent); - mParent = mContent->GetParent(); - mDoc = mContent->OwnerDoc(); - } - explicit AnonymousContentDestroyer(nsCOMPtr* aElement) - : mozilla::Runnable("AnonymousContentDestroyer") - { - mContent = aElement->forget(); - mParent = mContent->GetParent(); - mDoc = mContent->OwnerDoc(); - } - NS_IMETHOD Run() override { - mContent->UnbindFromTree(); - return NS_OK; - } -private: - nsCOMPtr mContent; - // Hold strong refs to the parent content and document so that they - // don't die unexpectedly - nsCOMPtr mDoc; - nsCOMPtr mParent; -}; - -/* static */ -void -nsContentUtils::DestroyAnonymousContent(nsCOMPtr* aContent) -{ - if (*aContent) { - AddScriptRunner(new AnonymousContentDestroyer(aContent)); - } -} - -/* static */ -void -nsContentUtils::DestroyAnonymousContent(nsCOMPtr* aElement) -{ - if (*aElement) { - AddScriptRunner(new AnonymousContentDestroyer(aElement)); - } -} - /* static */ void nsContentUtils::NotifyInstalledMenuKeyboardListener(bool aInstalling) diff --git a/dom/base/nsContentUtils.h b/dom/base/nsContentUtils.h index 99187294bbaa..a8c585b783ec 100644 --- a/dom/base/nsContentUtils.h +++ b/dom/base/nsContentUtils.h @@ -1672,12 +1672,6 @@ public: */ static void DestroyMatchString(void* aData); - /** - * Unbinds the content from the tree and nulls it out if it's not null. - */ - static void DestroyAnonymousContent(nsCOMPtr* aContent); - static void DestroyAnonymousContent(nsCOMPtr* aElement); - /* * Notify when the first XUL menu is opened and when the all XUL menus are * closed. At opening, aInstalling should be TRUE, otherwise, it should be diff --git a/dom/base/nsISelectionController.idl b/dom/base/nsISelectionController.idl index 2fbf0aeb8170..dfe73d8c29fb 100644 --- a/dom/base/nsISelectionController.idl +++ b/dom/base/nsISelectionController.idl @@ -26,18 +26,19 @@ interface nsISelectionDisplay; interface nsISelectionController : nsISelectionDisplay { // RawSelectionType values: - const short SELECTION_NONE=0; - const short SELECTION_NORMAL=1; - const short SELECTION_SPELLCHECK=2; - const short SELECTION_IME_RAWINPUT=4; - const short SELECTION_IME_SELECTEDRAWTEXT=8; - const short SELECTION_IME_CONVERTEDTEXT=16; - const short SELECTION_IME_SELECTEDCONVERTEDTEXT=32; - const short SELECTION_ACCESSIBILITY=64; // For accessibility API usage - const short SELECTION_FIND=128; - const short SELECTION_URLSECONDARY=256; - const short SELECTION_URLSTRIKEOUT=512; - const short NUM_SELECTIONTYPES=11; + const short SELECTION_NONE = 0; + const short SELECTION_NORMAL = 1; + const short SELECTION_SPELLCHECK = 2; + const short SELECTION_IME_RAWINPUT = 3; + const short SELECTION_IME_SELECTEDRAWTEXT = 4; + const short SELECTION_IME_CONVERTEDTEXT = 5; + const short SELECTION_IME_SELECTEDCONVERTEDTEXT = 6; + // For accessibility API usage + const short SELECTION_ACCESSIBILITY = 7; + const short SELECTION_FIND = 8; + const short SELECTION_URLSECONDARY = 9; + const short SELECTION_URLSTRIKEOUT = 10; + const short NUM_SELECTIONTYPES = 11; // SelectionRegion values: const short SELECTION_ANCHOR_REGION = 0; @@ -293,7 +294,15 @@ interface nsISelectionController : nsISelectionDisplay namespace mozilla { +// RawSelectionType should be used to store nsISelectionController::SELECTION_*. typedef short RawSelectionType; + +// SelectionTypeMask should be used to store bit-mask of selection types. +// The value can be retrieved with ToSelectionTypeMask() and checking if +// a selection type is in a mask with |SelectionType & SelectionTypeMask|. +typedef uint16_t SelectionTypeMask; + +// SelectionType should be used in internal handling because of type safe. enum class SelectionType : RawSelectionType { eInvalid = -1, @@ -320,16 +329,29 @@ enum : size_t { // kSelectionTypeCount is number of SelectionType. kSelectionTypeCount = nsISelectionController::NUM_SELECTIONTYPES, - // kPresentSelectionTypeCount is number of SelectionType except "none". - kPresentSelectionTypeCount = kSelectionTypeCount - 1 +}; + +// kPresentSelectionTypes is selection types which may be displayed. +// I.e., selection types except eNone. +static const SelectionType kPresentSelectionTypes[] = { + SelectionType::eNormal, + SelectionType::eSpellCheck, + SelectionType::eIMERawClause, + SelectionType::eIMESelectedRawClause, + SelectionType::eIMEConvertedClause, + SelectionType::eIMESelectedClause, + SelectionType::eAccessibility, + SelectionType::eFind, + SelectionType::eURLSecondary, + SelectionType::eURLStrikeout, }; // Please include mozilla/dom/Selection.h for the following APIs. const char* ToChar(SelectionType aSelectionType); +inline bool IsValidRawSelectionType(RawSelectionType aRawSelectionType); inline SelectionType ToSelectionType(RawSelectionType aRawSelectionType); inline RawSelectionType ToRawSelectionType(SelectionType aSelectionType); -inline bool operator &(SelectionType aSelectionType, - RawSelectionType aRawSelectionTypes); +inline SelectionTypeMask ToSelectionTypeMask(SelectionType aSelectionType); } // namespace mozilla %} diff --git a/dom/events/ContentEventHandler.cpp b/dom/events/ContentEventHandler.cpp index 86448f18b4ac..be038ea78c4e 100644 --- a/dom/events/ContentEventHandler.cpp +++ b/dom/events/ContentEventHandler.cpp @@ -3232,7 +3232,7 @@ ContentEventHandler::OnSelectionEvent(WidgetSelectionEvent* aEvent) mSelection->StartBatchChanges(); // Clear selection first before setting - rv = mSelection->RemoveAllRanges(); + rv = mSelection->RemoveAllRangesTemporarily(); // Need to call EndBatchChanges at the end even if call failed if (NS_SUCCEEDED(rv)) { if (aEvent->mReversed) { diff --git a/dom/html/HTMLMediaElement.cpp b/dom/html/HTMLMediaElement.cpp index 82d593e90e43..ce79ac7a0e5b 100644 --- a/dom/html/HTMLMediaElement.cpp +++ b/dom/html/HTMLMediaElement.cpp @@ -2657,7 +2657,7 @@ HTMLMediaElement::SetCurrentTime(double aCurrentTime, ErrorResult& aRv) * on success, and NS_ERROR_FAILURE on failure. */ static nsresult -IsInRanges(dom::TimeRanges& aRanges, +IsInRanges(TimeRanges& aRanges, double aValue, bool& aIsInRanges, int32_t& aIntervalIndex) @@ -2740,13 +2740,13 @@ HTMLMediaElement::Seek(double aTime, } // Clamp the seek target to inside the seekable ranges. - RefPtr seekable = new dom::TimeRanges(ToSupports(OwnerDoc())); media::TimeIntervals seekableIntervals = mDecoder->GetSeekable(); if (seekableIntervals.IsInvalid()) { aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); // This will reject the promise. return promise.forget(); } - seekableIntervals.ToTimeRanges(seekable); + RefPtr seekable = + new TimeRanges(ToSupports(OwnerDoc()), seekableIntervals); uint32_t length = 0; seekable->GetLength(&length); if (!length) { @@ -2865,10 +2865,9 @@ NS_IMETHODIMP HTMLMediaElement::GetDuration(double* aDuration) already_AddRefed HTMLMediaElement::Seekable() const { - RefPtr ranges = new TimeRanges(ToSupports(OwnerDoc())); - if (mDecoder) { - mDecoder->GetSeekable().ToTimeRanges(ranges); - } + media::TimeIntervals seekable = + mDecoder ? mDecoder->GetSeekable() : media::TimeIntervals(); + RefPtr ranges = new TimeRanges(ToSupports(OwnerDoc()), seekable); return ranges.forget(); } @@ -6549,13 +6548,9 @@ HTMLMediaElement::CopyInnerTo(Element* aDest, bool aPreallocateChildren) already_AddRefed HTMLMediaElement::Buffered() const { - RefPtr ranges = new TimeRanges(ToSupports(OwnerDoc())); - if (mDecoder) { - media::TimeIntervals buffered = mDecoder->GetBuffered(); - if (!buffered.IsInvalid()) { - buffered.ToTimeRanges(ranges); - } - } + media::TimeIntervals buffered = + mDecoder ? mDecoder->GetBuffered() : media::TimeIntervals(); + RefPtr ranges = new TimeRanges(ToSupports(OwnerDoc()), buffered); return ranges.forget(); } diff --git a/dom/html/HTMLVideoElement.cpp b/dom/html/HTMLVideoElement.cpp index 716f954dfee4..af19f32ea422 100644 --- a/dom/html/HTMLVideoElement.cpp +++ b/dom/html/HTMLVideoElement.cpp @@ -30,6 +30,7 @@ #include "mozilla/dom/WakeLock.h" #include "mozilla/dom/power/PowerManagerService.h" #include "mozilla/dom/Performance.h" +#include "mozilla/dom/TimeRanges.h" #include "mozilla/dom/VideoPlaybackQuality.h" #include diff --git a/dom/html/TimeRanges.cpp b/dom/html/TimeRanges.cpp index debb81a2fa9a..dceca985d784 100644 --- a/dom/html/TimeRanges.cpp +++ b/dom/html/TimeRanges.cpp @@ -7,6 +7,7 @@ #include "mozilla/dom/TimeRanges.h" #include "mozilla/dom/TimeRangesBinding.h" #include "mozilla/dom/HTMLMediaElement.h" +#include "TimeUnits.h" #include "nsError.h" namespace mozilla { @@ -31,6 +32,35 @@ TimeRanges::TimeRanges(nsISupports* aParent) { } +TimeRanges::TimeRanges(nsISupports* aParent, + const media::TimeIntervals& aTimeIntervals) + : TimeRanges(aParent) +{ + if (aTimeIntervals.IsInvalid()) { + return; + } + for (const media::TimeInterval& interval : aTimeIntervals) { + Add(interval.mStart.ToSeconds(), interval.mEnd.ToSeconds()); + } +} + +TimeRanges::TimeRanges(const media::TimeIntervals& aTimeIntervals) + : TimeRanges(nullptr, aTimeIntervals) +{ +} + +media::TimeIntervals +TimeRanges::ToTimeIntervals() const +{ + media::TimeIntervals t; + for (uint32_t i = 0; i < Length(); i++) { + ErrorResult rv; + t += media::TimeInterval(media::TimeUnit::FromSeconds(Start(i, rv)), + media::TimeUnit::FromSeconds(End(i, rv))); + } + return t; +} + TimeRanges::~TimeRanges() { } @@ -43,7 +73,7 @@ TimeRanges::GetLength(uint32_t* aLength) } double -TimeRanges::Start(uint32_t aIndex, ErrorResult& aRv) +TimeRanges::Start(uint32_t aIndex, ErrorResult& aRv) const { if (aIndex >= mRanges.Length()) { aRv = NS_ERROR_DOM_INDEX_SIZE_ERR; @@ -62,7 +92,7 @@ TimeRanges::Start(uint32_t aIndex, double* aTime) } double -TimeRanges::End(uint32_t aIndex, ErrorResult& aRv) +TimeRanges::End(uint32_t aIndex, ErrorResult& aRv) const { if (aIndex >= mRanges.Length()) { aRv = NS_ERROR_DOM_INDEX_SIZE_ERR; diff --git a/dom/html/TimeRanges.h b/dom/html/TimeRanges.h index 1811346fa3d7..7d12cecdc1dc 100644 --- a/dom/html/TimeRanges.h +++ b/dom/html/TimeRanges.h @@ -13,12 +13,12 @@ #include "nsTArray.h" #include "nsWrapperCache.h" #include "mozilla/ErrorResult.h" +#include "TimeUnits.h" namespace mozilla { + namespace dom { - class TimeRanges; - } // namespace dom namespace dom { @@ -35,6 +35,10 @@ public: TimeRanges(); explicit TimeRanges(nsISupports* aParent); + explicit TimeRanges(const media::TimeIntervals& aTimeIntervals); + TimeRanges(nsISupports* aParent, const media::TimeIntervals& aTimeIntervals); + + media::TimeIntervals ToTimeIntervals() const; void Add(double aStart, double aEnd); @@ -53,7 +57,8 @@ public: // Mutate this TimeRange to be the intersection of this and aOtherRanges. void Intersection(const TimeRanges* aOtherRanges); - virtual JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; + JSObject* WrapObject(JSContext* aCx, + JS::Handle aGivenProto) override; nsISupports* GetParentObject() const; @@ -62,9 +67,9 @@ public: return mRanges.Length(); } - virtual double Start(uint32_t aIndex, ErrorResult& aRv); + double Start(uint32_t aIndex, ErrorResult& aRv) const; - virtual double End(uint32_t aIndex, ErrorResult& aRv); + double End(uint32_t aIndex, ErrorResult& aRv) const; // Shift all values by aOffset seconds. void Shift(double aOffset); diff --git a/dom/html/nsTextEditorState.cpp b/dom/html/nsTextEditorState.cpp index b8c920c65772..002b1bac9853 100644 --- a/dom/html/nsTextEditorState.cpp +++ b/dom/html/nsTextEditorState.cpp @@ -504,10 +504,11 @@ nsTextInputSelectionImpl::SetCaretReadOnly(bool aReadOnly) { RefPtr caret = shell->GetCaret(); if (caret) { - nsISelection* domSel = + Selection* selection = mFrameSelection->GetSelection(SelectionType::eNormal); - if (domSel) + if (selection) { caret->SetCaretReadOnly(aReadOnly); + } return NS_OK; } } @@ -547,10 +548,11 @@ nsTextInputSelectionImpl::SetCaretVisibilityDuringSelection(bool aVisibility) { RefPtr caret = shell->GetCaret(); if (caret) { - nsISelection* domSel = + Selection* selection = mFrameSelection->GetSelection(SelectionType::eNormal); - if (domSel) + if (selection) { caret->SetVisibilityDuringSelection(aVisibility); + } return NS_OK; } } @@ -1338,22 +1340,16 @@ nsTextEditorState::BindToFrame(nsTextControlFrame* aFrame) mSelCon->SetDisplaySelection(nsISelectionController::SELECTION_ON); // Get the caret and make it a selection listener. - RefPtr domSelection; - if (NS_SUCCEEDED(mSelCon->GetSelection(nsISelectionController::SELECTION_NORMAL, - getter_AddRefs(domSelection))) && - domSelection) { - nsCOMPtr selPriv(do_QueryInterface(domSelection)); + // FYI: It's safe to use raw pointer for calling + // Selection::AddSelectionListner() because it only appends the listener + // to its internal array. + Selection* selection = mSelCon->GetSelection(SelectionType::eNormal); + if (selection) { RefPtr caret = shell->GetCaret(); - nsCOMPtr listener; if (caret) { - listener = do_QueryInterface(caret); - if (listener) { - selPriv->AddSelectionListener(listener); - } + selection->AddSelectionListener(caret); } - - selPriv->AddSelectionListener(static_cast - (mTextListener)); + selection->AddSelectionListener(mTextListener); } // If an editor exists from before, prepare it for usage @@ -2208,14 +2204,13 @@ nsTextEditorState::UnbindFromFrame(nsTextControlFrame* aFrame) if (mSelCon) { if (mTextListener) { - RefPtr domSelection; - if (NS_SUCCEEDED(mSelCon->GetSelection(nsISelectionController::SELECTION_NORMAL, - getter_AddRefs(domSelection))) && - domSelection) { - nsCOMPtr selPriv(do_QueryInterface(domSelection)); - - selPriv->RemoveSelectionListener(static_cast - (mTextListener)); + // FYI: It's safe to use raw pointer for calling + // Selection::RemoveSelectionListener() because it only removes the + // listener from its array. + Selection* selection = + mSelCon->GetSelection(SelectionType::eNormal); + if (selection) { + selection->RemoveSelectionListener(mTextListener); } } @@ -2264,9 +2259,9 @@ nsTextEditorState::UnbindFromFrame(nsTextControlFrame* aFrame) // Unbind the anonymous content from the tree. // We actually hold a reference to the content nodes so that // they're not actually destroyed. - nsContentUtils::DestroyAnonymousContent(&rootNode); - nsContentUtils::DestroyAnonymousContent(&mPlaceholderDiv); - nsContentUtils::DestroyAnonymousContent(&mPreviewDiv); + aFrame->DestroyAnonymousContent(rootNode.forget()); + aFrame->DestroyAnonymousContent(mPlaceholderDiv.forget()); + aFrame->DestroyAnonymousContent(mPreviewDiv.forget()); } nsresult @@ -2647,11 +2642,11 @@ nsTextEditorState::SetValue(const nsAString& aValue, const nsAString* aOldValue, { AutoNoJSAPI nojsapi; - nsCOMPtr domSel; - mSelCon->GetSelection(nsISelectionController::SELECTION_NORMAL, - getter_AddRefs(domSel)); - SelectionBatcher selectionBatcher(domSel ? domSel->AsSelection() : - nullptr); + // FYI: It's safe to use raw pointer for selection here because + // SelectionBatcher will grab it with RefPtr. + Selection* selection = + mSelCon->GetSelection(SelectionType::eNormal); + SelectionBatcher selectionBatcher(selection); if (NS_WARN_IF(!weakFrame.IsAlive())) { return true; @@ -2694,10 +2689,12 @@ nsTextEditorState::SetValue(const nsAString& aValue, const nsAString* aOldValue, } } else { AutoDisableUndo disableUndo(textEditor); - if (domSel) { + if (selection) { // Since we don't use undo transaction, we don't need to store // selection state. SetText will set selection to tail. - domSel->RemoveAllRanges(); + // Note that textEditor will collapse selection to the end. + // Therefore, it's safe to use RemoveAllRangesTemporarily() here. + selection->RemoveAllRangesTemporarily(); } textEditor->SetText(newValue); diff --git a/dom/media/Benchmark.cpp b/dom/media/Benchmark.cpp index febae4d227f1..e948b95e2090 100644 --- a/dom/media/Benchmark.cpp +++ b/dom/media/Benchmark.cpp @@ -53,7 +53,7 @@ VP9Benchmark::IsVP9DecodeFast() sHasRunTest = true; RefPtr demuxer = new WebMDemuxer( - new BufferMediaResource(sWebMSample, sizeof(sWebMSample), nullptr)); + new BufferMediaResource(sWebMSample, sizeof(sWebMSample))); RefPtr estimiser = new Benchmark(demuxer, { diff --git a/dom/media/BufferMediaResource.h b/dom/media/BufferMediaResource.h index 2548103ad145..35318957ca3a 100644 --- a/dom/media/BufferMediaResource.h +++ b/dom/media/BufferMediaResource.h @@ -8,7 +8,6 @@ #include "MediaResource.h" #include "nsISeekableStream.h" -#include "nsIPrincipal.h" #include namespace mozilla { @@ -20,13 +19,10 @@ namespace mozilla { class BufferMediaResource : public MediaResource { public: - BufferMediaResource(const uint8_t* aBuffer, - uint32_t aLength, - nsIPrincipal* aPrincipal) + BufferMediaResource(const uint8_t* aBuffer, uint32_t aLength) : mBuffer(aBuffer) , mLength(aLength) , mOffset(0) - , mPrincipal(aPrincipal) { } @@ -36,12 +32,6 @@ protected: } private: - // Get the current principal for the channel - already_AddRefed GetCurrentPrincipal() override - { - nsCOMPtr principal = mPrincipal; - return principal.forget(); - } // These methods are called off the main thread. nsresult ReadAt(int64_t aOffset, char* aBuffer, uint32_t aCount, uint32_t* aBytes) override @@ -87,25 +77,10 @@ private: return NS_OK; } - size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override - { - // Not owned: - // - mBuffer - // - mPrincipal - size_t size = MediaResource::SizeOfExcludingThis(aMallocSizeOf); - return size; - } - - size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override - { - return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); - } - private: const uint8_t * mBuffer; uint32_t mLength; uint32_t mOffset; - nsCOMPtr mPrincipal; }; } // namespace mozilla diff --git a/dom/media/ChannelMediaDecoder.cpp b/dom/media/ChannelMediaDecoder.cpp index cba7c6cddbc9..1a2d0357dee3 100644 --- a/dom/media/ChannelMediaDecoder.cpp +++ b/dom/media/ChannelMediaDecoder.cpp @@ -487,6 +487,22 @@ ChannelMediaDecoder::ShouldThrottleDownload() return stats.mDownloadRate > factor * stats.mPlaybackRate; } +void +ChannelMediaDecoder::AddSizeOfResources(ResourceSizes* aSizes) +{ + MOZ_ASSERT(NS_IsMainThread()); + if (mResource) { + aSizes->mByteSize += mResource->SizeOfIncludingThis(aSizes->mMallocSizeOf); + } +} + +already_AddRefed +ChannelMediaDecoder::GetCurrentPrincipal() +{ + MOZ_ASSERT(NS_IsMainThread()); + return mResource ? mResource->GetCurrentPrincipal() : nullptr; +} + bool ChannelMediaDecoder::IsTransportSeekable() { diff --git a/dom/media/ChannelMediaDecoder.h b/dom/media/ChannelMediaDecoder.h index 641d703209fd..c646b6c67497 100644 --- a/dom/media/ChannelMediaDecoder.h +++ b/dom/media/ChannelMediaDecoder.h @@ -78,6 +78,8 @@ public: bool aIsPrivateBrowsing, nsIStreamListener** aStreamListener); + void AddSizeOfResources(ResourceSizes* aSizes) override; + already_AddRefed GetCurrentPrincipal() override; bool IsTransportSeekable() override; void SetLoadInBackground(bool aLoadInBackground) override; void Suspend() override; diff --git a/dom/media/MediaData.h b/dom/media/MediaData.h index 129b7b952321..5c36beb9285b 100644 --- a/dom/media/MediaData.h +++ b/dom/media/MediaData.h @@ -17,9 +17,7 @@ #include "mozilla/UniquePtr.h" #include "mozilla/UniquePtrExtensions.h" #include "mozilla/gfx/Rect.h" -#include "nsIMemoryReporter.h" -#include "nsRect.h" -#include "nsSize.h" +#include "nsString.h" #include "nsTArray.h" namespace mozilla { diff --git a/dom/media/MediaDecoder.cpp b/dom/media/MediaDecoder.cpp index 118b72fc2643..0896cb018b73 100644 --- a/dom/media/MediaDecoder.cpp +++ b/dom/media/MediaDecoder.cpp @@ -700,15 +700,6 @@ MediaDecoder::GetCurrentTime() return mLogicalPosition; } -already_AddRefed -MediaDecoder::GetCurrentPrincipal() -{ - MOZ_ASSERT(NS_IsMainThread()); - MediaResource* r = GetResource(); - AbstractThread::AutoEnter context(AbstractMainThread()); - return r ? r->GetCurrentPrincipal() : nullptr; -} - void MediaDecoder::OnMetadataUpdate(TimedMetadata&& aMetadata) { @@ -1345,15 +1336,6 @@ MediaDecoder::SizeOfAudioQueue() return 0; } -void MediaDecoder::AddSizeOfResources(ResourceSizes* aSizes) -{ - MOZ_ASSERT(NS_IsMainThread()); - if (GetResource()) { - aSizes->mByteSize += - GetResource()->SizeOfIncludingThis(aSizes->mMallocSizeOf); - } -} - void MediaDecoder::NotifyDataArrivedInternal() { diff --git a/dom/media/MediaDecoder.h b/dom/media/MediaDecoder.h index 624fa4b61f37..56f6799a1bcb 100644 --- a/dom/media/MediaDecoder.h +++ b/dom/media/MediaDecoder.h @@ -121,7 +121,7 @@ public: void NetworkError(); // Return the principal of the current URI being played or downloaded. - virtual already_AddRefed GetCurrentPrincipal(); + virtual already_AddRefed GetCurrentPrincipal() = 0; // Return the time position in the video stream being // played measured in seconds. @@ -275,7 +275,7 @@ private: MozPromiseHolder mCallback; }; - virtual void AddSizeOfResources(ResourceSizes* aSizes); + virtual void AddSizeOfResources(ResourceSizes* aSizes) = 0; VideoFrameContainer* GetVideoFrameContainer() { diff --git a/dom/media/MediaResource.h b/dom/media/MediaResource.h index c3deb43f8501..3816f550c5f9 100644 --- a/dom/media/MediaResource.h +++ b/dom/media/MediaResource.h @@ -160,9 +160,6 @@ public: NS_METHOD_(MozExternalRefCountType) AddRef(void); NS_METHOD_(MozExternalRefCountType) Release(void); - // Get the current principal for the channel - virtual already_AddRefed GetCurrentPrincipal() = 0; - // These methods are called off the main thread. // Read up to aCount bytes from the stream. The read starts at // aOffset in the stream, seeking to that location initially if @@ -219,14 +216,6 @@ public: */ virtual nsresult GetCachedRanges(MediaByteRangeSet& aRanges) = 0; - virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const { - return 0; - } - - virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const { - return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); - } - protected: virtual ~MediaResource() {}; @@ -290,6 +279,9 @@ public: // requests are supported by the connection/server. virtual bool IsTransportSeekable() = 0; + // Get the current principal for the channel + virtual already_AddRefed GetCurrentPrincipal() = 0; + /** * Open the stream. This creates a stream listener and returns it in * aStreamListener; this listener needs to be notified of incoming data. @@ -315,18 +307,17 @@ public: // Returns true if the resource is a live stream. bool IsLiveStream() { return GetLength() == -1; } - size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override + virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const { // Might be useful to track in the future: // - mChannel // - mURI (possibly owned, looks like just a ref from mChannel) // Not owned: // - mCallback - size_t size = MediaResource::SizeOfExcludingThis(aMallocSizeOf); - return size; + return 0; } - size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override + virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const { return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); } diff --git a/dom/media/TimeUnits.h b/dom/media/TimeUnits.h index fd7d13f3d7d2..2e324e0cc533 100644 --- a/dom/media/TimeUnits.h +++ b/dom/media/TimeUnits.h @@ -11,7 +11,6 @@ #include "mozilla/CheckedInt.h" #include "mozilla/FloatingPoint.h" #include "mozilla/Maybe.h" -#include "mozilla/dom/TimeRanges.h" #include "mozilla/TimeStamp.h" namespace mozilla { @@ -19,7 +18,7 @@ namespace media { class TimeIntervals; } // namespace media } // namespace mozilla -// CopyChooser specalization for nsTArray +// CopyChooser specialization for nsTArray template<> struct nsTArray_CopyChooser { @@ -241,34 +240,6 @@ public: } TimeIntervals() = default; - - // Make TimeIntervals interchangeable with dom::TimeRanges. - explicit TimeIntervals(dom::TimeRanges* aRanges) - { - for (uint32_t i = 0; i < aRanges->Length(); i++) { - ErrorResult rv; - *this += - TimeInterval(TimeUnit::FromSeconds(aRanges->Start(i, rv)), - TimeUnit::FromSeconds(aRanges->End(i, rv))); - } - } - TimeIntervals& operator = (dom::TimeRanges* aRanges) - { - *this = TimeIntervals(aRanges); - return *this; - } - - static TimeIntervals FromTimeRanges(dom::TimeRanges* aRanges) - { - return TimeIntervals(aRanges); - } - - void ToTimeRanges(dom::TimeRanges* aRanges) const - { - for (IndexType i = 0; i < Length(); i++) { - aRanges->Add(Start(i).ToSeconds(), End(i).ToSeconds()); - } - } }; } // namespace media diff --git a/dom/media/gtest/MockMediaResource.h b/dom/media/gtest/MockMediaResource.h index d2185013b3b8..cf3c529210fc 100644 --- a/dom/media/gtest/MockMediaResource.h +++ b/dom/media/gtest/MockMediaResource.h @@ -16,10 +16,6 @@ class MockMediaResource : public MediaResource { public: explicit MockMediaResource(const char* aFileName); - already_AddRefed GetCurrentPrincipal() override - { - return nullptr; - } nsresult ReadAt(int64_t aOffset, char* aBuffer, uint32_t aCount, uint32_t* aBytes) override; // Data stored in file, caching recommended. diff --git a/dom/media/gtest/TestAudioCompactor.cpp b/dom/media/gtest/TestAudioCompactor.cpp index 697740df943c..ef52b2238fc2 100644 --- a/dom/media/gtest/TestAudioCompactor.cpp +++ b/dom/media/gtest/TestAudioCompactor.cpp @@ -5,6 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "gtest/gtest.h" #include "AudioCompactor.h" +#include "nsIMemoryReporter.h" using mozilla::AudioCompactor; using mozilla::AudioData; diff --git a/dom/media/gtest/TestIntervalSet.cpp b/dom/media/gtest/TestIntervalSet.cpp index 7e543dfca39f..2a3136c38722 100644 --- a/dom/media/gtest/TestIntervalSet.cpp +++ b/dom/media/gtest/TestIntervalSet.cpp @@ -536,8 +536,7 @@ TEST(IntervalSet, TimeRangesSeconds) i1.Add(media::TimeInterval(media::TimeUnit::FromSeconds(45), media::TimeUnit::FromSeconds(50))); media::TimeIntervals i(i0 + i1); - RefPtr tr = new dom::TimeRanges(); - i.ToTimeRanges(tr); + RefPtr tr = new dom::TimeRanges(i); EXPECT_EQ(tr->Length(), i.Length()); for (dom::TimeRanges::index_type index = 0; index < tr->Length(); index++) { ErrorResult rv; @@ -572,22 +571,13 @@ TEST(IntervalSet, TimeRangesConversion) tr->Add(53, 57); tr->Add(45, 50); - // explicit copy constructor - media::TimeIntervals i1(tr); + // explicit copy constructor and ToTimeIntervals. + media::TimeIntervals i1(tr->ToTimeIntervals()); CheckTimeRanges(tr, i1); - // static FromTimeRanges - media::TimeIntervals i2 = media::TimeIntervals::FromTimeRanges(tr); - CheckTimeRanges(tr, i2); - - media::TimeIntervals i3; - // operator=(TimeRanges*) - i3 = tr; - CheckTimeRanges(tr, i3); - - // operator= test - i1 = tr.get(); - CheckTimeRanges(tr, i1); + // ctor(const TimeIntervals&) + RefPtr tr2 = new dom::TimeRanges(tr->ToTimeIntervals()); + CheckTimeRanges(tr2, i1); } TEST(IntervalSet, TimeRangesMicroseconds) @@ -605,8 +595,7 @@ TEST(IntervalSet, TimeRangesMicroseconds) i1.Add(media::TimeInterval(media::TimeUnit::FromMicroseconds(45), media::TimeUnit::FromMicroseconds(50))); media::TimeIntervals i(i0 + i1); - RefPtr tr = new dom::TimeRanges(); - i.ToTimeRanges(tr); + RefPtr tr = new dom::TimeRanges(i); EXPECT_EQ(tr->Length(), i.Length()); for (dom::TimeRanges::index_type index = 0; index < tr->Length(); index++) { ErrorResult rv; @@ -630,9 +619,8 @@ TEST(IntervalSet, TimeRangesMicroseconds) tr = new dom::TimeRanges(); tr->Add(0, 30); tr->Add(50, std::numeric_limits::infinity()); - media::TimeIntervals i_oo{media::TimeIntervals::FromTimeRanges(tr)}; - RefPtr tr2 = new dom::TimeRanges(); - i_oo.ToTimeRanges(tr2); + media::TimeIntervals i_oo = tr->ToTimeIntervals(); + RefPtr tr2 = new dom::TimeRanges(i_oo); EXPECT_EQ(tr->Length(), tr2->Length()); for (dom::TimeRanges::index_type index = 0; index < tr->Length(); index++) { ErrorResult rv; diff --git a/dom/media/hls/HLSDecoder.cpp b/dom/media/hls/HLSDecoder.cpp index 8597a8a1be4d..0fa916c6ca27 100644 --- a/dom/media/hls/HLSDecoder.cpp +++ b/dom/media/hls/HLSDecoder.cpp @@ -92,6 +92,22 @@ HLSDecoder::Load(nsIChannel* aChannel) return InitializeStateMachine(); } +void +HLSDecoder::AddSizeOfResources(ResourceSizes* aSizes) +{ + MOZ_ASSERT(NS_IsMainThread()); + if (mResource) { + aSizes->mByteSize += mResource->SizeOfIncludingThis(aSizes->mMallocSizeOf); + } +} + +already_AddRefed +HLSDecoder::GetCurrentPrincipal() +{ + MOZ_ASSERT(NS_IsMainThread()); + return mResource ? mResource->GetCurrentPrincipal() : nullptr; +} + nsresult HLSDecoder::Play() { diff --git a/dom/media/hls/HLSDecoder.h b/dom/media/hls/HLSDecoder.h index e0560b63ec23..6153c9c1995e 100644 --- a/dom/media/hls/HLSDecoder.h +++ b/dom/media/hls/HLSDecoder.h @@ -37,6 +37,8 @@ public: void Pause() override; + void AddSizeOfResources(ResourceSizes* aSizes) override; + already_AddRefed GetCurrentPrincipal() override; bool IsTransportSeekable() override { return true; } void Suspend() override; void Resume() override; diff --git a/dom/media/hls/HLSResource.h b/dom/media/hls/HLSResource.h index b42c78cac3fd..f7ce8148c3b2 100644 --- a/dom/media/hls/HLSResource.h +++ b/dom/media/hls/HLSResource.h @@ -59,7 +59,7 @@ public: bool IsDataCachedToEndOfResource(int64_t aOffset) override { UNIMPLEMENTED(); return false; } nsresult ReadFromCache(char* aBuffer, int64_t aOffset, uint32_t aCount) override { UNIMPLEMENTED(); return NS_ERROR_FAILURE; } - already_AddRefed GetCurrentPrincipal() override + already_AddRefed GetCurrentPrincipal() { NS_ASSERTION(NS_IsMainThread(), "Only call on main thread"); @@ -83,23 +83,23 @@ public: void Detach() { mDecoder = nullptr; } + size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const + { + // TODO: track JAVA wrappers. + return 0; + } + + size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const + { + return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); + } + private: friend class HLSResourceCallbacksSupport; void onDataAvailable(); void onError(int aErrorCode); - size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override - { - size_t size = MediaResource::SizeOfExcludingThis(aMallocSizeOf); - return size; - } - - size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override - { - return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); - } - HLSDecoder* mDecoder; nsCOMPtr mChannel; nsCOMPtr mURI; diff --git a/dom/media/mediasource/MediaSourceDecoder.cpp b/dom/media/mediasource/MediaSourceDecoder.cpp index 48355bcaed5d..eb38ca89183a 100644 --- a/dom/media/mediasource/MediaSourceDecoder.cpp +++ b/dom/media/mediasource/MediaSourceDecoder.cpp @@ -60,7 +60,8 @@ MediaSourceDecoder::Load(nsIPrincipal* aPrincipal) MOZ_ASSERT(!GetStateMachine()); AbstractThread::AutoEnter context(AbstractMainThread()); - mResource = new MediaSourceResource(aPrincipal); + mPrincipal = aPrincipal; + mResource = new MediaSourceResource(); nsresult rv = MediaShutdownManager::Instance().Register(this); if (NS_WARN_IF(NS_FAILED(rv))) { @@ -362,6 +363,13 @@ MediaSourceDecoder::NotifyInitDataArrived() } } +already_AddRefed +MediaSourceDecoder::GetCurrentPrincipal() +{ + MOZ_ASSERT(NS_IsMainThread()); + return do_AddRef(mPrincipal); +} + #undef MSE_DEBUG #undef MSE_DEBUGV diff --git a/dom/media/mediasource/MediaSourceDecoder.h b/dom/media/mediasource/MediaSourceDecoder.h index d342f662d9d5..a188a8ae75cd 100644 --- a/dom/media/mediasource/MediaSourceDecoder.h +++ b/dom/media/mediasource/MediaSourceDecoder.h @@ -49,6 +49,8 @@ public: return mDemuxer; } + already_AddRefed GetCurrentPrincipal() override; + bool IsTransportSeekable() override { return true; } // Returns a string describing the state of the MediaSource internal @@ -72,6 +74,7 @@ private: bool IsLiveStream() override final { return !mEnded; } RefPtr mResource; + RefPtr mPrincipal; // The owning MediaSource holds a strong reference to this decoder, and // calls Attach/DetachMediaSource on this decoder to set and clear diff --git a/dom/media/mediasource/MediaSourceResource.h b/dom/media/mediasource/MediaSourceResource.h index ea98a9879453..fc0ecf9f1b5f 100644 --- a/dom/media/mediasource/MediaSourceResource.h +++ b/dom/media/mediasource/MediaSourceResource.h @@ -26,11 +26,10 @@ namespace mozilla { class MediaSourceResource final : public MediaResource { public: - explicit MediaSourceResource(nsIPrincipal* aPrincipal = nullptr) - : mPrincipal(aPrincipal) - , mMonitor("MediaSourceResource") + MediaSourceResource() + : mMonitor("MediaSourceResource") , mEnded(false) - {} + {} nsresult ReadAt(int64_t aOffset, char* aBuffer, uint32_t aCount, uint32_t* aBytes) override { UNIMPLEMENTED(); return NS_ERROR_FAILURE; } bool ShouldCacheReads() override { UNIMPLEMENTED(); return false; } @@ -43,11 +42,6 @@ public: bool IsDataCachedToEndOfResource(int64_t aOffset) override { UNIMPLEMENTED(); return false; } nsresult ReadFromCache(char* aBuffer, int64_t aOffset, uint32_t aCount) override { UNIMPLEMENTED(); return NS_ERROR_FAILURE; } - already_AddRefed GetCurrentPrincipal() override - { - return RefPtr(mPrincipal).forget(); - } - nsresult GetCachedRanges(MediaByteRangeSet& aRanges) override { UNIMPLEMENTED(); @@ -62,18 +56,17 @@ public: } private: - size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override + size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const { - size_t size = MediaResource::SizeOfExcludingThis(aMallocSizeOf); - return size; + // TODO: track source buffers appended to MediaSource. + return 0; } - size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override + size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const { return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); } - RefPtr mPrincipal; Monitor mMonitor; bool mEnded; // protected by mMonitor }; diff --git a/dom/media/mediasource/SourceBuffer.cpp b/dom/media/mediasource/SourceBuffer.cpp index 0c94762e5d8b..17f3451d4898 100644 --- a/dom/media/mediasource/SourceBuffer.cpp +++ b/dom/media/mediasource/SourceBuffer.cpp @@ -114,14 +114,13 @@ SourceBuffer::GetBuffered(ErrorResult& aRv) media::TimeIntervals intersection = mTrackBuffersManager->Buffered(); MSE_DEBUGV("intersection=%s", DumpTimeRanges(intersection).get()); if (mBuffered) { - media::TimeIntervals currentValue(mBuffered); + media::TimeIntervals currentValue(mBuffered->ToTimeIntervals()); rangeChanged = (intersection != currentValue); MSE_DEBUGV("currentValue=%s", DumpTimeRanges(currentValue).get()); } // 5. If intersection ranges does not contain the exact same range information as the current value of this attribute, then update the current value of this attribute to intersection ranges. if (rangeChanged) { - mBuffered = new TimeRanges(ToSupports(this)); - intersection.ToTimeRanges(mBuffered); + mBuffered = new TimeRanges(ToSupports(this), intersection); } // 6. Return the current value of this attribute. return mBuffered; diff --git a/dom/media/mediasource/SourceBufferResource.h b/dom/media/mediasource/SourceBufferResource.h index 70baaf4afb59..d3cf32daf2de 100644 --- a/dom/media/mediasource/SourceBufferResource.h +++ b/dom/media/mediasource/SourceBufferResource.h @@ -31,11 +31,6 @@ class SourceBufferResource final : public MediaResource public: SourceBufferResource(); nsresult Close(); - already_AddRefed GetCurrentPrincipal() override - { - UNIMPLEMENTED(); - return nullptr; - } nsresult ReadAt(int64_t aOffset, char* aBuffer, uint32_t aCount, @@ -83,17 +78,13 @@ public: return NS_OK; } - size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override + size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const { MOZ_ASSERT(OnTaskQueue()); - - size_t size = MediaResource::SizeOfExcludingThis(aMallocSizeOf); - size += mInputBuffer.SizeOfExcludingThis(aMallocSizeOf); - - return size; + return mInputBuffer.SizeOfExcludingThis(aMallocSizeOf); } - size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override + size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const { return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); } diff --git a/dom/media/webaudio/MediaBufferDecoder.cpp b/dom/media/webaudio/MediaBufferDecoder.cpp index e7fa4785f11b..d321f2744812 100644 --- a/dom/media/webaudio/MediaBufferDecoder.cpp +++ b/dom/media/webaudio/MediaBufferDecoder.cpp @@ -173,17 +173,8 @@ MediaDecodeTask::CreateReader() { MOZ_ASSERT(NS_IsMainThread()); - nsPIDOMWindowInner* parent = mDecodeJob.mContext->GetParentObject(); - MOZ_ASSERT(parent); - - nsCOMPtr principal; - nsCOMPtr sop = do_QueryInterface(parent); - if (sop) { - principal = sop->GetPrincipal(); - } - RefPtr resource = - new BufferMediaResource(static_cast(mBuffer), mLength, principal); + new BufferMediaResource(static_cast(mBuffer), mLength); mMainThread = mDecodeJob.mContext->GetOwnerGlobal()->AbstractMainThreadFor(TaskCategory::Other); diff --git a/dom/plugins/test/unit/test_plugin_default_state_xpi.js b/dom/plugins/test/unit/test_plugin_default_state_xpi.js deleted file mode 100644 index c1fd6526df0c..000000000000 --- a/dom/plugins/test/unit/test_plugin_default_state_xpi.js +++ /dev/null @@ -1,109 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ - */ - -Cu.import("resource://gre/modules/Services.jsm"); - -const ADDON_ID = "test-plugin-from-xpi@tests.mozilla.org"; -const XRE_EXTENSIONS_DIR_LIST = "XREExtDL"; -const NS_APP_PLUGINS_DIR_LIST = "APluginsDL"; - -const gPluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost); -var gProfileDir = null; - -function getAddonRoot(profileDir, id) { - let dir = profileDir.clone(); - dir.append("extensions"); - Assert.ok(dir.exists(), "Extensions dir should exist: " + dir.path); - dir.append(id); - return dir; -} - -function run_test() { - allow_all_plugins(); - loadAddonManager(); - gProfileDir = do_get_profile(); - do_register_cleanup(() => shutdownManager()); - run_next_test(); -} - -add_task(async function test_state() { - // Remove test so we will have only one "Test Plug-in" registered. - // xpcshell tests have plugins in per-test profiles, so that's fine. - let file = get_test_plugin(); - file.remove(true); - file = get_test_plugin(true); - file.remove(true); - - Services.prefs.setIntPref("plugin.default.state", Ci.nsIPluginTag.STATE_CLICKTOPLAY); - Services.prefs.setIntPref("plugin.defaultXpi.state", Ci.nsIPluginTag.STATE_ENABLED); - - let success = await installAddon("testaddon.xpi"); - Assert.ok(success, "Should have installed addon."); - let addonDir = getAddonRoot(gProfileDir, ADDON_ID); - - let provider = { - classID: Components.ID("{0af6b2d7-a06c-49b7-babc-636d292b0dbb}"), - QueryInterface: XPCOMUtils.generateQI([Ci.nsIDirectoryServiceProvider, - Ci.nsIDirectoryServiceProvider2]), - - getFile: function (prop, persistant) { - throw Cr.NS_ERROR_FAILURE; - }, - - getFiles: function (prop) { - let result = []; - - switch (prop) { - case XRE_EXTENSIONS_DIR_LIST: - result.push(addonDir); - break; - case NS_APP_PLUGINS_DIR_LIST: - let pluginDir = addonDir.clone(); - pluginDir.append("plugins"); - result.push(pluginDir); - break; - default: - throw Cr.NS_ERROR_FAILURE; - } - - return { - QueryInterface: XPCOMUtils.generateQI([Ci.nsISimpleEnumerator]), - hasMoreElements: () => result.length > 0, - getNext: () => result.shift(), - }; - }, - }; - - let dirSvc = Cc["@mozilla.org/file/directory_service;1"].getService(Ci.nsIProperties); - dirSvc.QueryInterface(Ci.nsIDirectoryService).registerProvider(provider); - dirSvc = null; - - // We installed a non-restartless addon, need to restart the manager. - restartManager(); - gPluginHost.reloadPlugins(); - - Assert.ok(addonDir.exists(), "Addon path should exist: " + addonDir.path); - Assert.ok(addonDir.isDirectory(), "Addon path should be a directory: " + addonDir.path); - let pluginDir = addonDir.clone(); - pluginDir.append("plugins"); - Assert.ok(pluginDir.exists(), "Addon plugins path should exist: " + pluginDir.path); - Assert.ok(pluginDir.isDirectory(), "Addon plugins path should be a directory: " + pluginDir.path); - - let addon = await getAddonByID(ADDON_ID); - Assert.ok(!addon.appDisabled, "Addon should not be appDisabled"); - Assert.ok(addon.isActive, "Addon should be active"); - Assert.ok(addon.isCompatible, "Addon should be compatible"); - Assert.ok(!addon.userDisabled, "Addon should not be user disabled"); - - let testPlugin = get_test_plugintag(); - Assert.notEqual(testPlugin, null, "Test plugin should have been found"); - Assert.equal(testPlugin.enabledState, Ci.nsIPluginTag.STATE_ENABLED, "Test plugin from addon should have state enabled"); - - pluginDir.append(testPlugin.filename); - Assert.ok(pluginDir.exists(), "Plugin file should exist in addon directory: " + pluginDir.path); - - testPlugin = get_test_plugintag("Second Test Plug-in"); - Assert.notEqual(testPlugin, null, "Second test plugin should have been found"); - Assert.equal(testPlugin.enabledState, Ci.nsIPluginTag.STATE_ENABLED, "Second test plugin from addon should have state enabled"); -}); diff --git a/dom/plugins/test/unit/xpcshell.ini b/dom/plugins/test/unit/xpcshell.ini index 602e28039e9c..01685a0de02c 100644 --- a/dom/plugins/test/unit/xpcshell.ini +++ b/dom/plugins/test/unit/xpcshell.ini @@ -25,5 +25,3 @@ reason = plugins are disabled by default in Thunderbird [test_plugin_default_state.js] skip-if = appname == "thunderbird" reason = plugins are disabled by default in Thunderbird -[test_plugin_default_state_xpi.js] -skip-if = no_legacy_extensions diff --git a/dom/svg/SVGUseElement.cpp b/dom/svg/SVGUseElement.cpp index 945ac62c21ba..d6f23f949694 100644 --- a/dom/svg/SVGUseElement.cpp +++ b/dom/svg/SVGUseElement.cpp @@ -17,6 +17,7 @@ #include "nsIURI.h" #include "mozilla/URLExtraData.h" #include "nsSVGEffects.h" +#include "nsSVGUseFrame.h" NS_IMPL_NS_NEW_NAMESPACED_SVG_ELEMENT(Use) @@ -55,13 +56,11 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(SVGUseElement, SVGUseElementBase) nsAutoScriptBlocker scriptBlocker; NS_IMPL_CYCLE_COLLECTION_UNLINK(mOriginal) - tmp->DestroyAnonymousContent(); tmp->UnlinkSource(); NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(SVGUseElement, SVGUseElementBase) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOriginal) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mClone) tmp->mSource.Traverse(&cb); NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END @@ -213,11 +212,9 @@ SVGUseElement::NodeWillBeDestroyed(const nsINode *aNode) //---------------------------------------------------------------------- -nsIContent* +already_AddRefed SVGUseElement::CreateAnonymousContent() { - mClone = nullptr; - if (mSource.get()) { mSource.get()->RemoveMutationObserver(this); } @@ -293,18 +290,17 @@ SVGUseElement::CreateAnonymousContent() do_AddRef(NodePrincipal())); targetContent->AddMutationObserver(this); - mClone = newcontent; #ifdef DEBUG // Our anonymous clone can get restyled by various things // (e.g. SMIL). Reconstructing its frame is OK, though, because // it's going to be our _only_ child in the frame tree, so can't get // mis-ordered with anything. - mClone->SetProperty(nsGkAtoms::restylableAnonymousNode, - reinterpret_cast(true)); + newcontent->SetProperty(nsGkAtoms::restylableAnonymousNode, + reinterpret_cast(true)); #endif // DEBUG - return mClone; + return newcontent.forget(); } nsIURI* @@ -317,16 +313,14 @@ SVGUseElement::GetSourceDocURI() return targetContent->OwnerDoc()->GetDocumentURI(); } -void -SVGUseElement::DestroyAnonymousContent() -{ - nsContentUtils::DestroyAnonymousContent(&mClone); -} - bool SVGUseElement::OurWidthAndHeightAreUsed() const { - return mClone && mClone->IsAnyOfSVGElements(nsGkAtoms::svg, nsGkAtoms::symbol); + auto* frame = GetFrame(); + if (!frame || !frame->GetContentClone()) { + return false; + } + return frame->GetContentClone()->IsAnyOfSVGElements(nsGkAtoms::svg, nsGkAtoms::symbol); } //---------------------------------------------------------------------- @@ -339,15 +333,18 @@ SVGUseElement::SyncWidthOrHeight(nsIAtom* aName) "The clue is in the function name"); NS_ASSERTION(OurWidthAndHeightAreUsed(), "Don't call this"); + auto* frame = GetFrame(); + nsIContent* clone = frame ? frame->GetContentClone() : nullptr; + if (OurWidthAndHeightAreUsed()) { - nsSVGElement *target = static_cast(mClone.get()); + auto* target = static_cast(clone); uint32_t index = *sLengthInfo[ATTR_WIDTH].mName == aName ? ATTR_WIDTH : ATTR_HEIGHT; if (mLengthAttributes[index].IsExplicitlySet()) { target->SetLength(aName, mLengthAttributes[index]); return; } - if (mClone->IsSVGElement(nsGkAtoms::svg)) { + if (clone->IsSVGElement(nsGkAtoms::svg)) { // Our width/height attribute is now no longer explicitly set, so we // need to revert the clone's width/height to the width/height of the // content that's being cloned. @@ -474,6 +471,14 @@ SVGUseElement::GetStringInfo() ArrayLength(sStringInfo)); } +nsSVGUseFrame* +SVGUseElement::GetFrame() const +{ + nsIFrame* frame = GetPrimaryFrame(); + MOZ_ASSERT_IF(frame, frame->IsSVGUseFrame()); + return static_cast(frame); +} + //---------------------------------------------------------------------- // nsIContent methods diff --git a/dom/svg/SVGUseElement.h b/dom/svg/SVGUseElement.h index e3d3f9c23062..88ce5cfeade9 100644 --- a/dom/svg/SVGUseElement.h +++ b/dom/svg/SVGUseElement.h @@ -57,9 +57,7 @@ public: NS_DECL_NSIMUTATIONOBSERVER_NODEWILLBEDESTROYED // for nsSVGUseFrame's nsIAnonymousContentCreator implementation. - nsIContent* CreateAnonymousContent(); - nsIContent* GetAnonymousContent() const { return mClone; } - void DestroyAnonymousContent(); + already_AddRefed CreateAnonymousContent(); // nsSVGElement specializations: virtual gfxMatrix PrependLocalTransformsTo( @@ -98,6 +96,8 @@ protected: SVGUseElement* mContainer; }; + nsSVGUseFrame* GetFrame() const; + virtual LengthAttributesInfo GetLengthInfo() override; virtual StringAttributesInfo GetStringInfo() override; diff --git a/editor/libeditor/EditorBase.cpp b/editor/libeditor/EditorBase.cpp index 942001308327..489e206780f1 100644 --- a/editor/libeditor/EditorBase.cpp +++ b/editor/libeditor/EditorBase.cpp @@ -2889,8 +2889,7 @@ EditorBase::SplitNodeImpl(nsIContent& aExistingRightNode, { // Remember all selection points. AutoTArray savedRanges; - for (size_t i = 0; i < kPresentSelectionTypeCount; ++i) { - SelectionType selectionType(ToSelectionType(1 << i)); + for (SelectionType selectionType : kPresentSelectionTypes) { SavedRange range; range.mSelection = GetSelection(selectionType); if (selectionType == SelectionType::eNormal) { @@ -3038,8 +3037,7 @@ EditorBase::JoinNodesImpl(nsINode* aNodeToKeep, // Remember all selection points. AutoTArray savedRanges; - for (size_t i = 0; i < kPresentSelectionTypeCount; ++i) { - SelectionType selectionType(ToSelectionType(1 << i)); + for (SelectionType selectionType : kPresentSelectionTypes) { SavedRange range; range.mSelection = GetSelection(selectionType); if (selectionType == SelectionType::eNormal) { diff --git a/gfx/layers/wr/WebRenderLayerManager.cpp b/gfx/layers/wr/WebRenderLayerManager.cpp index 670919001df5..fccbdd27de02 100644 --- a/gfx/layers/wr/WebRenderLayerManager.cpp +++ b/gfx/layers/wr/WebRenderLayerManager.cpp @@ -518,6 +518,13 @@ WebRenderLayerManager::GenerateFallbackData(nsDisplayItem* aItem, clippedBounds = itemBounds.Intersect(clip.GetClipRect()); } + // nsDisplayItem::Paint() may refer the variables that come from ComputeVisibility(). + // So we should call ComputeVisibility() before painting. e.g.: nsDisplayBoxShadowInner + // uses mVisibleRegion in Paint() and mVisibleRegion is computed in + // nsDisplayBoxShadowInner::ComputeVisibility(). + nsRegion visibleRegion(clippedBounds); + aItem->ComputeVisibility(aDisplayListBuilder, &visibleRegion); + const int32_t appUnitsPerDevPixel = aItem->Frame()->PresContext()->AppUnitsPerDevPixel(); LayerRect bounds = ViewAs( LayoutDeviceRect::FromAppUnits(clippedBounds, appUnitsPerDevPixel), diff --git a/layout/base/nsFrameManager.cpp b/layout/base/nsFrameManager.cpp index b0849265110a..971d879347a1 100644 --- a/layout/base/nsFrameManager.cpp +++ b/layout/base/nsFrameManager.cpp @@ -681,6 +681,21 @@ nsFrameManager::RestoreFrameState(nsIFrame* aFrame, } } +void +nsFrameManager::DestroyAnonymousContent(already_AddRefed aContent) +{ + nsCOMPtr content = aContent; + if (content) { + // Invoke ClearAllMapsFor before unbinding from the tree. When we unbind, + // we remove the mPrimaryFrame pointer, which is used by the frame + // teardown code to determine whether to invoke ClearAllMapsFor or not. + // These maps will go away when we drop support for the old style system. + ClearAllMapsFor(content); + + content->UnbindFromTree(); + } +} + //---------------------------------------------------------------------- nsFrameManagerBase::UndisplayedMap::UndisplayedMap() diff --git a/layout/base/nsFrameManager.h b/layout/base/nsFrameManager.h index 0a218c82f615..20d241c1e84a 100644 --- a/layout/base/nsFrameManager.h +++ b/layout/base/nsFrameManager.h @@ -199,6 +199,8 @@ public: void RestoreFrameStateFor(nsIFrame* aFrame, nsILayoutHistoryState* aState); + void DestroyAnonymousContent(already_AddRefed aContent); + protected: static nsIContent* ParentForUndisplayedMap(const nsIContent* aContent); diff --git a/layout/forms/nsColorControlFrame.cpp b/layout/forms/nsColorControlFrame.cpp index 7282800b212c..17268e2bd8ce 100644 --- a/layout/forms/nsColorControlFrame.cpp +++ b/layout/forms/nsColorControlFrame.cpp @@ -44,7 +44,7 @@ NS_QUERYFRAME_TAIL_INHERITING(nsHTMLButtonControlFrame) void nsColorControlFrame::DestroyFrom(nsIFrame* aDestructRoot) { nsFormControlFrame::RegUnRegAccessKey(static_cast(this), false); - nsContentUtils::DestroyAnonymousContent(&mColorContent); + DestroyAnonymousContent(mColorContent.forget()); nsHTMLButtonControlFrame::DestroyFrom(aDestructRoot); } diff --git a/layout/forms/nsComboboxControlFrame.cpp b/layout/forms/nsComboboxControlFrame.cpp index bb0923075df5..d1a2c394f8db 100644 --- a/layout/forms/nsComboboxControlFrame.cpp +++ b/layout/forms/nsComboboxControlFrame.cpp @@ -1418,8 +1418,8 @@ nsComboboxControlFrame::DestroyFrom(nsIFrame* aDestructRoot) // Cleanup frames in popup child list mPopupFrames.DestroyFramesFrom(aDestructRoot); - nsContentUtils::DestroyAnonymousContent(&mDisplayContent); - nsContentUtils::DestroyAnonymousContent(&mButtonContent); + DestroyAnonymousContent(mDisplayContent.forget()); + DestroyAnonymousContent(mButtonContent.forget()); nsBlockFrame::DestroyFrom(aDestructRoot); } diff --git a/layout/forms/nsDateTimeControlFrame.cpp b/layout/forms/nsDateTimeControlFrame.cpp index 7a2e31b57389..42ccd6c9fe6d 100644 --- a/layout/forms/nsDateTimeControlFrame.cpp +++ b/layout/forms/nsDateTimeControlFrame.cpp @@ -50,7 +50,7 @@ nsDateTimeControlFrame::nsDateTimeControlFrame(nsStyleContext* aContext) void nsDateTimeControlFrame::DestroyFrom(nsIFrame* aDestructRoot) { - nsContentUtils::DestroyAnonymousContent(&mInputAreaContent); + DestroyAnonymousContent(mInputAreaContent.forget()); nsContainerFrame::DestroyFrom(aDestructRoot); } diff --git a/layout/forms/nsFileControlFrame.cpp b/layout/forms/nsFileControlFrame.cpp index 3d84dfc4b328..06b76249e3de 100644 --- a/layout/forms/nsFileControlFrame.cpp +++ b/layout/forms/nsFileControlFrame.cpp @@ -68,8 +68,8 @@ nsFileControlFrame::DestroyFrom(nsIFrame* aDestructRoot) mMouseListener, false); } - nsContentUtils::DestroyAnonymousContent(&mTextContent); - nsContentUtils::DestroyAnonymousContent(&mBrowseFilesOrDirs); + DestroyAnonymousContent(mTextContent.forget()); + DestroyAnonymousContent(mBrowseFilesOrDirs.forget()); mMouseListener->ForgetFrame(); nsBlockFrame::DestroyFrom(aDestructRoot); diff --git a/layout/forms/nsGfxButtonControlFrame.cpp b/layout/forms/nsGfxButtonControlFrame.cpp index e9fbf6875244..429a8e7a73c6 100644 --- a/layout/forms/nsGfxButtonControlFrame.cpp +++ b/layout/forms/nsGfxButtonControlFrame.cpp @@ -33,7 +33,7 @@ NS_IMPL_FRAMEARENA_HELPERS(nsGfxButtonControlFrame) void nsGfxButtonControlFrame::DestroyFrom(nsIFrame* aDestructRoot) { - nsContentUtils::DestroyAnonymousContent(&mTextContent); + DestroyAnonymousContent(mTextContent.forget()); nsHTMLButtonControlFrame::DestroyFrom(aDestructRoot); } diff --git a/layout/forms/nsMeterFrame.cpp b/layout/forms/nsMeterFrame.cpp index 58e2ba559fd7..fa342db612fd 100644 --- a/layout/forms/nsMeterFrame.cpp +++ b/layout/forms/nsMeterFrame.cpp @@ -55,7 +55,7 @@ nsMeterFrame::DestroyFrom(nsIFrame* aDestructRoot) "nsMeterFrame should not have continuations; if it does we " "need to call RegUnregAccessKey only for the first."); nsFormControlFrame::RegUnRegAccessKey(static_cast(this), false); - nsContentUtils::DestroyAnonymousContent(&mBarDiv); + DestroyAnonymousContent(mBarDiv.forget()); nsContainerFrame::DestroyFrom(aDestructRoot); } diff --git a/layout/forms/nsNumberControlFrame.cpp b/layout/forms/nsNumberControlFrame.cpp index ae9ac25be061..81ad6bfcf283 100644 --- a/layout/forms/nsNumberControlFrame.cpp +++ b/layout/forms/nsNumberControlFrame.cpp @@ -62,7 +62,7 @@ nsNumberControlFrame::DestroyFrom(nsIFrame* aDestructRoot) "nsNumberControlFrame should not have continuations; if it does we " "need to call RegUnregAccessKey only for the first"); nsFormControlFrame::RegUnRegAccessKey(static_cast(this), false); - nsContentUtils::DestroyAnonymousContent(&mOuterWrapper); + DestroyAnonymousContent(mOuterWrapper.forget()); nsContainerFrame::DestroyFrom(aDestructRoot); } diff --git a/layout/forms/nsProgressFrame.cpp b/layout/forms/nsProgressFrame.cpp index 40f564fe9ed6..e215bb822ceb 100644 --- a/layout/forms/nsProgressFrame.cpp +++ b/layout/forms/nsProgressFrame.cpp @@ -54,7 +54,7 @@ nsProgressFrame::DestroyFrom(nsIFrame* aDestructRoot) "nsProgressFrame should not have continuations; if it does we " "need to call RegUnregAccessKey only for the first."); nsFormControlFrame::RegUnRegAccessKey(static_cast(this), false); - nsContentUtils::DestroyAnonymousContent(&mBarDiv); + DestroyAnonymousContent(mBarDiv.forget()); nsContainerFrame::DestroyFrom(aDestructRoot); } diff --git a/layout/forms/nsRangeFrame.cpp b/layout/forms/nsRangeFrame.cpp index 93f7517db8e7..06713085ecc9 100644 --- a/layout/forms/nsRangeFrame.cpp +++ b/layout/forms/nsRangeFrame.cpp @@ -104,9 +104,9 @@ nsRangeFrame::DestroyFrom(nsIFrame* aDestructRoot) mContent->RemoveEventListener(NS_LITERAL_STRING("touchstart"), mDummyTouchListener, false); nsFormControlFrame::RegUnRegAccessKey(static_cast(this), false); - nsContentUtils::DestroyAnonymousContent(&mTrackDiv); - nsContentUtils::DestroyAnonymousContent(&mProgressDiv); - nsContentUtils::DestroyAnonymousContent(&mThumbDiv); + DestroyAnonymousContent(mTrackDiv.forget()); + DestroyAnonymousContent(mProgressDiv.forget()); + DestroyAnonymousContent(mThumbDiv.forget()); nsContainerFrame::DestroyFrom(aDestructRoot); } diff --git a/layout/generic/DetailsFrame.cpp b/layout/generic/DetailsFrame.cpp index f536c3eb4850..399567b7cd3d 100644 --- a/layout/generic/DetailsFrame.cpp +++ b/layout/generic/DetailsFrame.cpp @@ -83,7 +83,7 @@ DetailsFrame::CheckValidMainSummary(const nsFrameList& aFrameList) const void DetailsFrame::DestroyFrom(nsIFrame* aDestructRoot) { - nsContentUtils::DestroyAnonymousContent(&mDefaultSummary); + DestroyAnonymousContent(mDefaultSummary.forget()); nsBlockFrame::DestroyFrom(aDestructRoot); } diff --git a/layout/generic/nsCanvasFrame.cpp b/layout/generic/nsCanvasFrame.cpp index 1803237102eb..b318a45d08f6 100644 --- a/layout/generic/nsCanvasFrame.cpp +++ b/layout/generic/nsCanvasFrame.cpp @@ -153,7 +153,7 @@ nsCanvasFrame::DestroyFrom(nsIFrame* aDestructRoot) content->SetContentNode(clonedElement->AsElement()); } } - nsContentUtils::DestroyAnonymousContent(&mCustomContentContainer); + DestroyAnonymousContent(mCustomContentContainer.forget()); nsContainerFrame::DestroyFrom(aDestructRoot); } diff --git a/layout/generic/nsFrame.cpp b/layout/generic/nsFrame.cpp index 20b11abc4aa9..d3b822245e87 100644 --- a/layout/generic/nsFrame.cpp +++ b/layout/generic/nsFrame.cpp @@ -240,6 +240,13 @@ nsReflowStatus::UpdateTruncated(const ReflowInput& aReflowInput, } } +void +nsIFrame::DestroyAnonymousContent(already_AddRefed aContent) +{ + PresContext()->PresShell()->FrameConstructor() + ->DestroyAnonymousContent(mozilla::Move(aContent)); +} + // Formerly the nsIFrameDebug interface #ifdef DEBUG diff --git a/layout/generic/nsFrameSelection.cpp b/layout/generic/nsFrameSelection.cpp index 3a2927ee986a..110517969faa 100644 --- a/layout/generic/nsFrameSelection.cpp +++ b/layout/generic/nsFrameSelection.cpp @@ -144,56 +144,31 @@ nsPeekOffsetStruct::nsPeekOffsetStruct(nsSelectionAmount aAmount, { } -static int8_t +// Array which contains index of each SelecionType in Selection::mDOMSelections. +// For avoiding using if nor switch to retrieve the index, this needs to have +// -1 for SelectionTypes which won't be created its Selection instance. +static const int8_t kIndexOfSelections[] = { + -1, // SelectionType::eInvalid + -1, // SelectionType::eNone + 0, // SelectionType::eNormal + 1, // SelectionType::eSpellCheck + 2, // SelectionType::eIMERawClause + 3, // SelectionType::eIMESelectedRawClause + 4, // SelectionType::eIMEConvertedClause + 5, // SelectionType::eIMESelectedClause + 6, // SelectionType::eAccessibility + 7, // SelectionType::eFind + 8, // SelectionType::eURLSecondary + 9, // SelectionType::eURLStrikeout +}; + +inline int8_t GetIndexFromSelectionType(SelectionType aSelectionType) { - switch (aSelectionType) { - case SelectionType::eNormal: - return 0; - case SelectionType::eSpellCheck: - return 1; - case SelectionType::eIMERawClause: - return 2; - case SelectionType::eIMESelectedRawClause: - return 3; - case SelectionType::eIMEConvertedClause: - return 4; - case SelectionType::eIMESelectedClause: - return 5; - case SelectionType::eAccessibility: - return 6; - case SelectionType::eFind: - return 7; - case SelectionType::eURLSecondary: - return 8; - case SelectionType::eURLStrikeout: - return 9; - default: - return -1; - } - /* NOTREACHED */ -} - -static SelectionType -GetSelectionTypeFromIndex(int8_t aIndex) -{ - static const SelectionType kSelectionTypes[] = { - SelectionType::eNormal, - SelectionType::eSpellCheck, - SelectionType::eIMERawClause, - SelectionType::eIMESelectedRawClause, - SelectionType::eIMEConvertedClause, - SelectionType::eIMESelectedClause, - SelectionType::eAccessibility, - SelectionType::eFind, - SelectionType::eURLSecondary, - SelectionType::eURLStrikeout - }; - if (NS_WARN_IF(aIndex < 0) || - NS_WARN_IF(static_cast(aIndex) >= ArrayLength(kSelectionTypes))) { - return SelectionType::eNormal; - } - return kSelectionTypes[aIndex]; + // The enum value of eInvalid is -1 and the others are sequential value + // starting from 0. Therefore, |SelectionType + 1| is the index of + // kIndexOfSelections. + return kIndexOfSelections[static_cast(aSelectionType) + 1]; } /* @@ -326,9 +301,9 @@ struct MOZ_RAII AutoPrepareFocusRange nsFrameSelection::nsFrameSelection() { - for (size_t i = 0; i < kPresentSelectionTypeCount; i++){ + for (size_t i = 0; i < ArrayLength(mDomSelections); i++) { mDomSelections[i] = new Selection(this); - mDomSelections[i]->SetType(GetSelectionTypeFromIndex(i)); + mDomSelections[i]->SetType(kPresentSelectionTypes[i]); } mBatching = 0; mChangesDuringBatching = false; @@ -383,7 +358,7 @@ nsFrameSelection::~nsFrameSelection() NS_IMPL_CYCLE_COLLECTION_CLASS(nsFrameSelection) NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsFrameSelection) - for (size_t i = 0; i < kPresentSelectionTypeCount; ++i) { + for (size_t i = 0; i < ArrayLength(tmp->mDomSelections); ++i) { tmp->mDomSelections[i] = nullptr; } @@ -405,7 +380,7 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsFrameSelection) GetMarkedCCGeneration())) { return NS_SUCCESS_INTERRUPTED_TRAVERSE; } - for (size_t i = 0; i < kPresentSelectionTypeCount; ++i) { + for (size_t i = 0; i < ArrayLength(tmp->mDomSelections); ++i) { NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDomSelections[i]) } @@ -1540,11 +1515,11 @@ nsFrameSelection::LookUpSelection(nsIContent *aContent, UniquePtr details; - for (size_t j = 0; j < kPresentSelectionTypeCount; j++) { + for (size_t j = 0; j < ArrayLength(mDomSelections); j++) { if (mDomSelections[j]) { details = mDomSelections[j]->LookUpSelection(aContent, aContentOffset, aContentLength, Move(details), - ToSelectionType(1 << j), + kPresentSelectionTypes[j], aSlowCheck); } } @@ -2969,7 +2944,7 @@ nsFrameSelection::DisconnectFromPresShell() } StopAutoScrollTimer(); - for (size_t i = 0; i < kPresentSelectionTypeCount; i++) { + for (size_t i = 0; i < ArrayLength(mDomSelections); i++) { mDomSelections[i]->Clear(nullptr); } mShell = nullptr; diff --git a/layout/generic/nsFrameSelection.h b/layout/generic/nsFrameSelection.h index 1ad86aa0b0a7..5a2f2c51b63d 100644 --- a/layout/generic/nsFrameSelection.h +++ b/layout/generic/nsFrameSelection.h @@ -703,7 +703,8 @@ private: Selection* aSel); RefPtr - mDomSelections[mozilla::kPresentSelectionTypeCount]; + mDomSelections[ + sizeof(mozilla::kPresentSelectionTypes) / sizeof(mozilla::SelectionType)]; // Table selection support. nsITableCellLayout* GetCellLayout(nsIContent *aCellContent) const; diff --git a/layout/generic/nsGfxScrollFrame.cpp b/layout/generic/nsGfxScrollFrame.cpp index c8b36935190e..0c8f9e8769fb 100644 --- a/layout/generic/nsGfxScrollFrame.cpp +++ b/layout/generic/nsGfxScrollFrame.cpp @@ -4607,10 +4607,10 @@ ScrollFrameHelper::Destroy() } // Unbind any content created in CreateAnonymousContent from the tree - nsContentUtils::DestroyAnonymousContent(&mHScrollbarContent); - nsContentUtils::DestroyAnonymousContent(&mVScrollbarContent); - nsContentUtils::DestroyAnonymousContent(&mScrollCornerContent); - nsContentUtils::DestroyAnonymousContent(&mResizerContent); + mOuter->DestroyAnonymousContent(mHScrollbarContent.forget()); + mOuter->DestroyAnonymousContent(mVScrollbarContent.forget()); + mOuter->DestroyAnonymousContent(mScrollCornerContent.forget()); + mOuter->DestroyAnonymousContent(mResizerContent.forget()); if (mPostedReflowCallback) { mOuter->PresContext()->PresShell()->CancelReflowCallback(this); diff --git a/layout/generic/nsIFrame.h b/layout/generic/nsIFrame.h index da97778289e7..2f550e213993 100644 --- a/layout/generic/nsIFrame.h +++ b/layout/generic/nsIFrame.h @@ -4026,6 +4026,8 @@ public: DisplayItemArray& DisplayItemData() { return mDisplayItemData; } + void DestroyAnonymousContent(already_AddRefed aContent); + protected: /** diff --git a/layout/generic/nsTextFrame.cpp b/layout/generic/nsTextFrame.cpp index eae1dd723bfc..af89e4c7f5cb 100644 --- a/layout/generic/nsTextFrame.cpp +++ b/layout/generic/nsTextFrame.cpp @@ -6022,15 +6022,14 @@ nsTextFrame::ComputeDescentLimitForSelectionUnderline( return aFontMetrics.maxDescent + (lineHeight - aFontMetrics.maxHeight) / 2; } - // Make sure this stays in sync with DrawSelectionDecorations below -static const RawSelectionType kRawSelectionTypesWithDecorations = - nsISelectionController::SELECTION_SPELLCHECK | - nsISelectionController::SELECTION_URLSTRIKEOUT | - nsISelectionController::SELECTION_IME_RAWINPUT | - nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT | - nsISelectionController::SELECTION_IME_CONVERTEDTEXT | - nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT; +static const SelectionTypeMask kSelectionTypesWithDecorations = + ToSelectionTypeMask(SelectionType::eSpellCheck) | + ToSelectionTypeMask(SelectionType::eURLStrikeout) | + ToSelectionTypeMask(SelectionType::eIMERawClause) | + ToSelectionTypeMask(SelectionType::eIMESelectedRawClause) | + ToSelectionTypeMask(SelectionType::eIMEConvertedClause) | + ToSelectionTypeMask(SelectionType::eIMESelectedClause); /* static */ gfxFloat @@ -6109,7 +6108,7 @@ nsTextFrame::PaintDecorationLine(const PaintDecorationLineParams& aParams) } /** - * This, plus kRawSelectionTypesWithDecorations, encapsulates all knowledge + * This, plus kSelectionTypesWithDecorations, encapsulates all knowledge * about drawing text decoration for selections. */ void @@ -6557,7 +6556,7 @@ bool nsTextFrame::PaintTextWithSelectionColors( const PaintTextSelectionParams& aParams, const UniquePtr& aDetails, - RawSelectionType* aAllRawSelectionTypes, + SelectionTypeMask* aAllSelectionTypeMask, const nsCharClipDisplayItem::ClipEdges& aClipEdges) { const gfxTextRun::Range& contentRange = aParams.contentRange; @@ -6573,7 +6572,7 @@ nsTextFrame::PaintTextWithSelectionColors( return false; } - RawSelectionType allRawSelectionTypes = 0; + SelectionTypeMask allSelectionTypeMask = 0; for (uint32_t i = 0; i < contentRange.Length(); ++i) { prevailingSelections[i] = nullptr; } @@ -6585,7 +6584,7 @@ nsTextFrame::PaintTextWithSelectionColors( sdptr->mEnd - int32_t(contentRange.start)); SelectionType selectionType = sdptr->mSelectionType; if (start < end) { - allRawSelectionTypes |= ToRawSelectionType(selectionType); + allSelectionTypeMask |= ToSelectionTypeMask(selectionType); // Ignore selections that don't set colors nscolor foreground, background; if (GetSelectionTextColors(selectionType, *aParams.textPaintStyle, @@ -6604,9 +6603,9 @@ nsTextFrame::PaintTextWithSelectionColors( } } } - *aAllRawSelectionTypes = allRawSelectionTypes; + *aAllSelectionTypeMask = allSelectionTypeMask; - if (!allRawSelectionTypes) { + if (!allSelectionTypeMask) { // Nothing is selected in the given text range. XXX can this still occur? return false; } @@ -6835,8 +6834,8 @@ nsTextFrame::PaintTextWithSelection( return false; } - RawSelectionType allRawSelectionTypes; - if (!PaintTextWithSelectionColors(aParams, details, &allRawSelectionTypes, + SelectionTypeMask allSelectionTypeMask; + if (!PaintTextWithSelectionColors(aParams, details, &allSelectionTypeMask, aClipEdges)) { return false; } @@ -6844,10 +6843,13 @@ nsTextFrame::PaintTextWithSelection( // and paint decorations for any that actually occur in this frame. Paint // higher-numbered selection rawSelectionTypes below lower-numered ones on the // general principal that lower-numbered selections are higher priority. - allRawSelectionTypes &= kRawSelectionTypesWithDecorations; - for (size_t i = kSelectionTypeCount - 1; i >= 1; --i) { - SelectionType selectionType = ToSelectionType(1 << (i - 1)); - if (selectionType & allRawSelectionTypes) { + allSelectionTypeMask &= kSelectionTypesWithDecorations; + MOZ_ASSERT(kPresentSelectionTypes[0] == SelectionType::eNormal, + "The following for loop assumes that the first item of " + "kPresentSelectionTypes is SelectionType::eNormal"); + for (size_t i = ArrayLength(kPresentSelectionTypes) - 1; i >= 1; --i) { + SelectionType selectionType = kPresentSelectionTypes[i]; + if (ToSelectionTypeMask(selectionType) & allSelectionTypeMask) { // There is some selection of this selectionType. Try to paint its // decorations (there might not be any for this type but that's OK, // PaintTextSelectionDecorations will exit early). @@ -7738,7 +7740,9 @@ nsTextFrame::CombineSelectionUnderlineRect(nsPresContext* aPresContext, UniquePtr details = GetSelectionDetails(); for (SelectionDetails* sd = details.get(); sd; sd = sd->mNext.get()) { if (sd->mStart == sd->mEnd || - !(sd->mSelectionType & kRawSelectionTypesWithDecorations) || + sd->mSelectionType == SelectionType::eInvalid || + !(ToSelectionTypeMask(sd->mSelectionType) & + kSelectionTypesWithDecorations) || // URL strikeout does not use underline. sd->mSelectionType == SelectionType::eURLStrikeout) { continue; @@ -7816,7 +7820,7 @@ nsTextFrame::SetSelectedRange(uint32_t aStart, uint32_t aEnd, bool aSelected, // We may need to reflow to recompute the overflow area for // spellchecking or IME underline if their underline is thicker than // the normal decoration line. - if (aSelectionType & kRawSelectionTypesWithDecorations) { + if (ToSelectionTypeMask(aSelectionType) & kSelectionTypesWithDecorations) { bool didHaveOverflowingSelection = (f->GetStateBits() & TEXT_SELECTION_UNDERLINE_OVERFLOWED) != 0; nsRect r(nsPoint(0, 0), GetSize()); diff --git a/layout/generic/nsTextFrame.h b/layout/generic/nsTextFrame.h index 272c4748b508..cff579cf9123 100644 --- a/layout/generic/nsTextFrame.h +++ b/layout/generic/nsTextFrame.h @@ -42,7 +42,7 @@ class SVGContextPaint; class nsTextFrame : public nsFrame { typedef mozilla::LayoutDeviceRect LayoutDeviceRect; - typedef mozilla::RawSelectionType RawSelectionType; + typedef mozilla::SelectionTypeMask SelectionTypeMask; typedef mozilla::SelectionType SelectionType; typedef mozilla::TextRangeStyle TextRangeStyle; typedef mozilla::gfx::DrawTarget DrawTarget; @@ -524,13 +524,13 @@ public: const nsCharClipDisplayItem::ClipEdges& aClipEdges); // helper: paint text with foreground and background colors determined // by selection(s). Also computes a mask of all selection types applying to - // our text, returned in aAllTypes. + // our text, returned in aAllSelectionTypeMask. // Return false if the text was not painted and we should continue with // the fast path. bool PaintTextWithSelectionColors( const PaintTextSelectionParams& aParams, const mozilla::UniquePtr& aDetails, - RawSelectionType* aAllRawSelectionTypes, + SelectionTypeMask* aAllSelectionTypeMask, const nsCharClipDisplayItem::ClipEdges& aClipEdges); // helper: paint text decorations for text selected by aSelectionType void PaintTextSelectionDecorations(const PaintTextSelectionParams& aParams, diff --git a/layout/generic/nsVideoFrame.cpp b/layout/generic/nsVideoFrame.cpp index 0147b691dbbc..44fa2dce45ab 100644 --- a/layout/generic/nsVideoFrame.cpp +++ b/layout/generic/nsVideoFrame.cpp @@ -177,9 +177,9 @@ nsVideoFrame::AppendAnonymousContentTo(nsTArray& aElements, void nsVideoFrame::DestroyFrom(nsIFrame* aDestructRoot) { - nsContentUtils::DestroyAnonymousContent(&mCaptionDiv); - nsContentUtils::DestroyAnonymousContent(&mVideoControls); - nsContentUtils::DestroyAnonymousContent(&mPosterImage); + DestroyAnonymousContent(mCaptionDiv.forget()); + DestroyAnonymousContent(mVideoControls.forget()); + DestroyAnonymousContent(mPosterImage.forget()); nsContainerFrame::DestroyFrom(aDestructRoot); } diff --git a/layout/reftests/generated-content/dynamic-generated-content-inherit-001.html b/layout/reftests/generated-content/dynamic-generated-content-inherit-001.html new file mode 100644 index 000000000000..0bec9c9eafae --- /dev/null +++ b/layout/reftests/generated-content/dynamic-generated-content-inherit-001.html @@ -0,0 +1,25 @@ + + +CSS Test: Dynamic ::before and ::after generation by a style attribute + + + + + + + +

Test passes if you see two PASS lines below:

+
SS
+
PA
+ diff --git a/layout/reftests/generated-content/generated-content-inherit-001-ref.html b/layout/reftests/generated-content/generated-content-inherit-001-ref.html new file mode 100644 index 000000000000..c15156138e6a --- /dev/null +++ b/layout/reftests/generated-content/generated-content-inherit-001-ref.html @@ -0,0 +1,7 @@ + + +CSS Test reference + +

Test passes if you see two PASS lines below:

+
PASS
+
PASS
diff --git a/layout/reftests/generated-content/generated-content-inherit-001.html b/layout/reftests/generated-content/generated-content-inherit-001.html new file mode 100644 index 000000000000..67b9f242df27 --- /dev/null +++ b/layout/reftests/generated-content/generated-content-inherit-001.html @@ -0,0 +1,20 @@ + + +CSS Test: ::before and ::after generated with content: inherit + + + + + + + +

Test passes if you see two PASS lines below:

+
SS
+
PA
diff --git a/layout/reftests/generated-content/reftest.list b/layout/reftests/generated-content/reftest.list index b85820734825..e07a0dd565a5 100644 --- a/layout/reftests/generated-content/reftest.list +++ b/layout/reftests/generated-content/reftest.list @@ -19,3 +19,5 @@ fuzzy-if(OSX==1010,1,10) == table-parts-01.html table-parts-01-ref.html == before-style-sharing.html before-style-sharing-ref.html == transitive-style-invalidation.html transitive-style-invalidation-ref.html == dynamic-content.html dynamic-content-ref.html +== generated-content-inherit-001.html generated-content-inherit-001-ref.html +== dynamic-generated-content-inherit-001.html generated-content-inherit-001-ref.html diff --git a/layout/style/crashtests/1393791.html b/layout/style/crashtests/1393791.html new file mode 100644 index 000000000000..ad045f9a7cd4 --- /dev/null +++ b/layout/style/crashtests/1393791.html @@ -0,0 +1,12 @@ + diff --git a/layout/style/crashtests/crashtests.list b/layout/style/crashtests/crashtests.list index 3a2c1cc355a7..c9487b628118 100644 --- a/layout/style/crashtests/crashtests.list +++ b/layout/style/crashtests/crashtests.list @@ -205,3 +205,4 @@ load 1388234.html load 1391577.html load 1393580.html load 1389645.html +load 1393791.html diff --git a/layout/svg/moz.build b/layout/svg/moz.build index 0b2ef7dddd19..b96cde0bec25 100644 --- a/layout/svg/moz.build +++ b/layout/svg/moz.build @@ -22,6 +22,7 @@ EXPORTS += [ 'nsSVGForeignObjectFrame.h', 'nsSVGImageFrame.h', 'nsSVGIntegrationUtils.h', + 'nsSVGUseFrame.h', 'nsSVGUtils.h', 'SVGImageContext.h', ] diff --git a/layout/svg/nsSVGUseFrame.cpp b/layout/svg/nsSVGUseFrame.cpp index 3172d4a371f0..5ee68c862cf8 100644 --- a/layout/svg/nsSVGUseFrame.cpp +++ b/layout/svg/nsSVGUseFrame.cpp @@ -3,63 +3,15 @@ * 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/. */ -// Keep in (case-insensitive) order: -#include "nsIAnonymousContentCreator.h" -#include "nsSVGEffects.h" -#include "nsSVGGFrame.h" +#include "nsSVGUseFrame.h" +#include "nsContentUtils.h" + #include "mozilla/dom/SVGUseElement.h" #include "nsContentList.h" +#include "nsSVGEffects.h" using namespace mozilla::dom; -class nsSVGUseFrame final - : public nsSVGGFrame - , public nsIAnonymousContentCreator -{ - friend nsIFrame* - NS_NewSVGUseFrame(nsIPresShell* aPresShell, nsStyleContext* aContext); - -protected: - explicit nsSVGUseFrame(nsStyleContext* aContext) - : nsSVGGFrame(aContext, kClassID) - , mHasValidDimensions(true) - {} - -public: - NS_DECL_QUERYFRAME - NS_DECL_FRAMEARENA_HELPERS(nsSVGUseFrame) - - // nsIFrame interface: - virtual void Init(nsIContent* aContent, - nsContainerFrame* aParent, - nsIFrame* aPrevInFlow) override; - - virtual nsresult AttributeChanged(int32_t aNameSpaceID, - nsIAtom* aAttribute, - int32_t aModType) override; - - virtual void DestroyFrom(nsIFrame* aDestructRoot) override; - -#ifdef DEBUG_FRAME_DUMP - virtual nsresult GetFrameName(nsAString& aResult) const override - { - return MakeFrameName(NS_LITERAL_STRING("SVGUse"), aResult); - } -#endif - - // nsSVGDisplayableFrame interface: - virtual void ReflowSVG() override; - virtual void NotifySVGChanged(uint32_t aFlags) override; - - // nsIAnonymousContentCreator - virtual nsresult CreateAnonymousContent(nsTArray& aElements) override; - virtual void AppendAnonymousContentTo(nsTArray& aElements, - uint32_t aFilter) override; - -private: - bool mHasValidDimensions; -}; - //---------------------------------------------------------------------- // Implementation @@ -150,9 +102,8 @@ nsSVGUseFrame::AttributeChanged(int32_t aNameSpaceID, void nsSVGUseFrame::DestroyFrom(nsIFrame* aDestructRoot) { - RefPtr use = static_cast(GetContent()); + DestroyAnonymousContent(mContentClone.forget()); nsSVGGFrame::DestroyFrom(aDestructRoot); - use->DestroyAnonymousContent(); } @@ -217,13 +168,12 @@ nsSVGUseFrame::CreateAnonymousContent(nsTArray& aElements) { SVGUseElement *use = static_cast(GetContent()); - nsIContent* clone = use->CreateAnonymousContent(); + mContentClone = use->CreateAnonymousContent(); nsLayoutUtils::PostRestyleEvent( use, nsRestyleHint(0), nsChangeHint_InvalidateRenderingObservers); - if (!clone) + if (!mContentClone) return NS_ERROR_FAILURE; - if (!aElements.AppendElement(clone)) - return NS_ERROR_OUT_OF_MEMORY; + aElements.AppendElement(mContentClone); return NS_OK; } @@ -231,9 +181,7 @@ void nsSVGUseFrame::AppendAnonymousContentTo(nsTArray& aElements, uint32_t aFilter) { - SVGUseElement *use = static_cast(GetContent()); - nsIContent* clone = use->GetAnonymousContent(); - if (clone) { - aElements.AppendElement(clone); + if (mContentClone) { + aElements.AppendElement(mContentClone); } } diff --git a/layout/svg/nsSVGUseFrame.h b/layout/svg/nsSVGUseFrame.h new file mode 100644 index 000000000000..53322e4af760 --- /dev/null +++ b/layout/svg/nsSVGUseFrame.h @@ -0,0 +1,65 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 __NS_SVGUSEFRAME_H__ +#define __NS_SVGUSEFRAME_H__ + +// Keep in (case-insensitive) order: +#include "nsIAnonymousContentCreator.h" +#include "nsSVGGFrame.h" + +class nsSVGUseFrame final + : public nsSVGGFrame + , public nsIAnonymousContentCreator +{ + friend nsIFrame* NS_NewSVGUseFrame(nsIPresShell* aPresShell, + nsStyleContext* aContext); + +protected: + explicit nsSVGUseFrame(nsStyleContext* aContext) + : nsSVGGFrame(aContext, kClassID) + , mHasValidDimensions(true) + { + } + +public: + NS_DECL_QUERYFRAME + NS_DECL_FRAMEARENA_HELPERS(nsSVGUseFrame) + + // nsIFrame interface: + void Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) override; + + nsresult AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) override; + + void DestroyFrom(nsIFrame* aDestructRoot) override; + +#ifdef DEBUG_FRAME_DUMP + nsresult GetFrameName(nsAString& aResult) const override + { + return MakeFrameName(NS_LITERAL_STRING("SVGUse"), aResult); + } +#endif + + // nsSVGDisplayableFrame interface: + void ReflowSVG() override; + void NotifySVGChanged(uint32_t aFlags) override; + + // nsIAnonymousContentCreator + nsresult CreateAnonymousContent(nsTArray& aElements) override; + void AppendAnonymousContentTo(nsTArray& aElements, + uint32_t aFilter) override; + + nsIContent* GetContentClone() { return mContentClone.get(); } + +private: + bool mHasValidDimensions; + nsCOMPtr mContentClone; +}; + +#endif // __NS_SVGUSEFRAME_H__ diff --git a/layout/xul/nsDocElementBoxFrame.cpp b/layout/xul/nsDocElementBoxFrame.cpp index 525772a8d670..b0df2b86a35b 100644 --- a/layout/xul/nsDocElementBoxFrame.cpp +++ b/layout/xul/nsDocElementBoxFrame.cpp @@ -75,8 +75,8 @@ NS_IMPL_FRAMEARENA_HELPERS(nsDocElementBoxFrame) void nsDocElementBoxFrame::DestroyFrom(nsIFrame* aDestructRoot) { - nsContentUtils::DestroyAnonymousContent(&mPopupgroupContent); - nsContentUtils::DestroyAnonymousContent(&mTooltipContent); + DestroyAnonymousContent(mPopupgroupContent.forget()); + DestroyAnonymousContent(mTooltipContent.forget()); nsBoxFrame::DestroyFrom(aDestructRoot); } diff --git a/mobile/android/app/src/main/res/drawable-hdpi/tab_indicator_background.9.png b/mobile/android/app/src/main/res/drawable-hdpi/tab_indicator_background.9.png deleted file mode 100644 index e225e8ce943c..000000000000 Binary files a/mobile/android/app/src/main/res/drawable-hdpi/tab_indicator_background.9.png and /dev/null differ diff --git a/mobile/android/app/src/main/res/drawable-hdpi/tab_indicator_selected.9.png b/mobile/android/app/src/main/res/drawable-hdpi/tab_indicator_selected.9.png deleted file mode 100644 index 7ebd02f6f76e..000000000000 Binary files a/mobile/android/app/src/main/res/drawable-hdpi/tab_indicator_selected.9.png and /dev/null differ diff --git a/mobile/android/app/src/main/res/drawable-hdpi/tab_indicator_selected_focused.9.png b/mobile/android/app/src/main/res/drawable-hdpi/tab_indicator_selected_focused.9.png deleted file mode 100644 index 272bbaeadecc..000000000000 Binary files a/mobile/android/app/src/main/res/drawable-hdpi/tab_indicator_selected_focused.9.png and /dev/null differ diff --git a/mobile/android/app/src/main/res/drawable-xhdpi/home_tab_menu_strip.9.png b/mobile/android/app/src/main/res/drawable-xhdpi/home_tab_menu_strip.9.png deleted file mode 100644 index 2a20ad421629..000000000000 Binary files a/mobile/android/app/src/main/res/drawable-xhdpi/home_tab_menu_strip.9.png and /dev/null differ diff --git a/mobile/android/app/src/main/res/drawable-xhdpi/tab_indicator_background.9.png b/mobile/android/app/src/main/res/drawable-xhdpi/tab_indicator_background.9.png deleted file mode 100644 index 8b561749af9c..000000000000 Binary files a/mobile/android/app/src/main/res/drawable-xhdpi/tab_indicator_background.9.png and /dev/null differ diff --git a/mobile/android/app/src/main/res/drawable-xhdpi/tab_indicator_selected.9.png b/mobile/android/app/src/main/res/drawable-xhdpi/tab_indicator_selected.9.png deleted file mode 100644 index e78a2ddba67e..000000000000 Binary files a/mobile/android/app/src/main/res/drawable-xhdpi/tab_indicator_selected.9.png and /dev/null differ diff --git a/mobile/android/app/src/main/res/drawable-xhdpi/tab_indicator_selected_focused.9.png b/mobile/android/app/src/main/res/drawable-xhdpi/tab_indicator_selected_focused.9.png deleted file mode 100644 index 3e1fe5560fe2..000000000000 Binary files a/mobile/android/app/src/main/res/drawable-xhdpi/tab_indicator_selected_focused.9.png and /dev/null differ diff --git a/mobile/android/app/src/main/res/layout/tabs_panel_default.xml b/mobile/android/app/src/main/res/layout/tabs_panel_default.xml index 897784d3d56a..ad37e40f2069 100644 --- a/mobile/android/app/src/main/res/layout/tabs_panel_default.xml +++ b/mobile/android/app/src/main/res/layout/tabs_panel_default.xml @@ -31,9 +31,7 @@ android:minWidth="@dimen/tabs_panel_button_width" android:src="@drawable/tabs_panel_nav_back" android:contentDescription="@string/back" - android:background="@drawable/action_bar_button_inverse" - gecko:dividerVerticalPadding="@dimen/tab_panel_divider_vertical_padding" - gecko:rightDivider="@drawable/tab_indicator_divider"/> + android:background="@drawable/action_bar_button_inverse"/> MAX_VISIBLE_TABS && count > MAX_VISIBLE_TABS) { this.count = count; return; @@ -181,7 +181,11 @@ public class TabCounter extends ThemedRelativeLayout { text.setText(formatForDisplay(count)); this.count = count; - // Trigger animation + // Cancel previous animations if necessary. + if (animationSet.isRunning()) { + animationSet.cancel(); + } + // Trigger animations. animationSet.start(); } diff --git a/mobile/android/app/src/photon/res/color/tab_strip_item_title.xml b/mobile/android/app/src/photon/res/color/tab_strip_item_title.xml index f8ff000a8e36..59cd76ccbf87 100644 --- a/mobile/android/app/src/photon/res/color/tab_strip_item_title.xml +++ b/mobile/android/app/src/photon/res/color/tab_strip_item_title.xml @@ -9,13 +9,20 @@ - - + + + + diff --git a/mobile/android/app/src/photon/res/color/url_bar_title.xml b/mobile/android/app/src/photon/res/color/url_bar_title.xml index ac95e2560d91..4ab41fc96f7a 100644 --- a/mobile/android/app/src/photon/res/color/url_bar_title.xml +++ b/mobile/android/app/src/photon/res/color/url_bar_title.xml @@ -9,12 +9,6 @@ - - - - - - diff --git a/mobile/android/app/src/photon/res/color/url_bar_title_highlight.xml b/mobile/android/app/src/photon/res/color/url_bar_title_highlight.xml index 31427ad805de..911c141daf7f 100644 --- a/mobile/android/app/src/photon/res/color/url_bar_title_highlight.xml +++ b/mobile/android/app/src/photon/res/color/url_bar_title_highlight.xml @@ -9,12 +9,6 @@ - - - - - - diff --git a/mobile/android/app/src/photon/res/color/url_bar_title_hint.xml b/mobile/android/app/src/photon/res/color/url_bar_title_hint.xml index 802190206dee..aecea02e45f4 100644 --- a/mobile/android/app/src/photon/res/color/url_bar_title_hint.xml +++ b/mobile/android/app/src/photon/res/color/url_bar_title_hint.xml @@ -9,12 +9,6 @@ - - - - - - diff --git a/mobile/android/app/src/main/res/drawable-hdpi/home_tab_menu_strip.9.png b/mobile/android/app/src/photon/res/drawable-hdpi/home_tab_menu_strip.9.png similarity index 100% rename from mobile/android/app/src/main/res/drawable-hdpi/home_tab_menu_strip.9.png rename to mobile/android/app/src/photon/res/drawable-hdpi/home_tab_menu_strip.9.png diff --git a/mobile/android/app/src/photon/res/drawable-hdpi/ic_globe_nm.png b/mobile/android/app/src/photon/res/drawable-hdpi/ic_globe_nm.png index 38dc8c421088..9b1ae3eeca32 100644 Binary files a/mobile/android/app/src/photon/res/drawable-hdpi/ic_globe_nm.png and b/mobile/android/app/src/photon/res/drawable-hdpi/ic_globe_nm.png differ diff --git a/mobile/android/app/src/photon/res/drawable-hdpi/reading_list_folder.png b/mobile/android/app/src/photon/res/drawable-hdpi/reading_list_folder.png index 41bfac0f0290..b638a3e2205b 100644 Binary files a/mobile/android/app/src/photon/res/drawable-hdpi/reading_list_folder.png and b/mobile/android/app/src/photon/res/drawable-hdpi/reading_list_folder.png differ diff --git a/mobile/android/app/src/photon/res/drawable-hdpi/status_icon_readercache.png b/mobile/android/app/src/photon/res/drawable-hdpi/status_icon_readercache.png index c34d4085d0d9..07da2cbea5e2 100644 Binary files a/mobile/android/app/src/photon/res/drawable-hdpi/status_icon_readercache.png and b/mobile/android/app/src/photon/res/drawable-hdpi/status_icon_readercache.png differ diff --git a/mobile/android/app/src/photon/res/drawable-hdpi/tab_indicator_background.9.png b/mobile/android/app/src/photon/res/drawable-hdpi/tab_indicator_background.9.png new file mode 100644 index 000000000000..7b5cec161219 Binary files /dev/null and b/mobile/android/app/src/photon/res/drawable-hdpi/tab_indicator_background.9.png differ diff --git a/mobile/android/app/src/photon/res/drawable-hdpi/tab_indicator_selected.9.png b/mobile/android/app/src/photon/res/drawable-hdpi/tab_indicator_selected.9.png new file mode 100644 index 000000000000..74e5cffee845 Binary files /dev/null and b/mobile/android/app/src/photon/res/drawable-hdpi/tab_indicator_selected.9.png differ diff --git a/mobile/android/app/src/photon/res/drawable-hdpi/tab_indicator_selected_focused.9.png b/mobile/android/app/src/photon/res/drawable-hdpi/tab_indicator_selected_focused.9.png new file mode 100644 index 000000000000..821f189a3fca Binary files /dev/null and b/mobile/android/app/src/photon/res/drawable-hdpi/tab_indicator_selected_focused.9.png differ diff --git a/mobile/android/app/src/photon/res/drawable-hdpi/tip_addsearch.png b/mobile/android/app/src/photon/res/drawable-hdpi/tip_addsearch.png deleted file mode 100644 index 1dfe86fff443..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-hdpi/tip_addsearch.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi-v11/alert_camera.png b/mobile/android/app/src/photon/res/drawable-mdpi-v11/alert_camera.png deleted file mode 100644 index 109c7c0fe063..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi-v11/alert_camera.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi-v11/alert_download.png b/mobile/android/app/src/photon/res/drawable-mdpi-v11/alert_download.png deleted file mode 100644 index e8fb24a3943d..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi-v11/alert_download.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi-v11/alert_guest.png b/mobile/android/app/src/photon/res/drawable-mdpi-v11/alert_guest.png deleted file mode 100644 index ae7740748625..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi-v11/alert_guest.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi-v11/alert_mic.png b/mobile/android/app/src/photon/res/drawable-mdpi-v11/alert_mic.png deleted file mode 100644 index 667df9566db5..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi-v11/alert_mic.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi-v11/ic_menu_back.png b/mobile/android/app/src/photon/res/drawable-mdpi-v11/ic_menu_back.png deleted file mode 100644 index fc1e7089ceb7..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi-v11/ic_menu_back.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi-v11/ic_menu_bookmark_add.png b/mobile/android/app/src/photon/res/drawable-mdpi-v11/ic_menu_bookmark_add.png deleted file mode 100644 index c5cdb95058fa..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi-v11/ic_menu_bookmark_add.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi-v11/ic_menu_forward.png b/mobile/android/app/src/photon/res/drawable-mdpi-v11/ic_menu_forward.png deleted file mode 100644 index d0b9deed7a5f..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi-v11/ic_menu_forward.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi-v11/ic_menu_reload.png b/mobile/android/app/src/photon/res/drawable-mdpi-v11/ic_menu_reload.png deleted file mode 100644 index 4f46450e7df3..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi-v11/ic_menu_reload.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi-v11/ic_menu_stop.png b/mobile/android/app/src/photon/res/drawable-mdpi-v11/ic_menu_stop.png deleted file mode 100644 index 243b8f48273e..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi-v11/ic_menu_stop.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi-v11/ic_status_logo.png b/mobile/android/app/src/photon/res/drawable-mdpi-v11/ic_status_logo.png deleted file mode 100644 index e963db0e9da3..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi-v11/ic_status_logo.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi-v11/star_blue.png b/mobile/android/app/src/photon/res/drawable-mdpi-v11/star_blue.png deleted file mode 100644 index 7cebcac7ebdc..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi-v11/star_blue.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/ab_add_search_engine.png b/mobile/android/app/src/photon/res/drawable-mdpi/ab_add_search_engine.png deleted file mode 100644 index 041c803ffc40..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/ab_add_search_engine.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/ab_copy.png b/mobile/android/app/src/photon/res/drawable-mdpi/ab_copy.png deleted file mode 100644 index 8b7f29f27164..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/ab_copy.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/ab_cut.png b/mobile/android/app/src/photon/res/drawable-mdpi/ab_cut.png deleted file mode 100644 index cf399dc250d8..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/ab_cut.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/ab_done.png b/mobile/android/app/src/photon/res/drawable-mdpi/ab_done.png deleted file mode 100644 index 3f6f5db59c9b..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/ab_done.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/ab_paste.png b/mobile/android/app/src/photon/res/drawable-mdpi/ab_paste.png deleted file mode 100644 index e810fbf2316f..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/ab_paste.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/ab_search.png b/mobile/android/app/src/photon/res/drawable-mdpi/ab_search.png deleted file mode 100644 index a1b70f916c4b..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/ab_search.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/ab_select_all.png b/mobile/android/app/src/photon/res/drawable-mdpi/ab_select_all.png deleted file mode 100644 index 05752779b628..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/ab_select_all.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/add_folder.png b/mobile/android/app/src/photon/res/drawable-mdpi/add_folder.png deleted file mode 100644 index daa50207d965..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/add_folder.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/alert_camera.png b/mobile/android/app/src/photon/res/drawable-mdpi/alert_camera.png deleted file mode 100644 index a6cceed63231..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/alert_camera.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/alert_download.png b/mobile/android/app/src/photon/res/drawable-mdpi/alert_download.png deleted file mode 100644 index 44c25920af8d..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/alert_download.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/alert_download_animation_1.png b/mobile/android/app/src/photon/res/drawable-mdpi/alert_download_animation_1.png deleted file mode 100644 index 800cfcc91a80..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/alert_download_animation_1.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/alert_download_animation_2.png b/mobile/android/app/src/photon/res/drawable-mdpi/alert_download_animation_2.png deleted file mode 100644 index 1f5fd8c4ebe5..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/alert_download_animation_2.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/alert_download_animation_3.png b/mobile/android/app/src/photon/res/drawable-mdpi/alert_download_animation_3.png deleted file mode 100644 index 5be263a5561d..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/alert_download_animation_3.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/alert_download_animation_4.png b/mobile/android/app/src/photon/res/drawable-mdpi/alert_download_animation_4.png deleted file mode 100644 index 384b34aa1223..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/alert_download_animation_4.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/alert_download_animation_5.png b/mobile/android/app/src/photon/res/drawable-mdpi/alert_download_animation_5.png deleted file mode 100644 index 2e92c7aa3255..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/alert_download_animation_5.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/alert_download_animation_6.png b/mobile/android/app/src/photon/res/drawable-mdpi/alert_download_animation_6.png deleted file mode 100644 index 775d4527bce1..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/alert_download_animation_6.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/alert_guest.png b/mobile/android/app/src/photon/res/drawable-mdpi/alert_guest.png deleted file mode 100644 index ae7740748625..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/alert_guest.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/alert_mic.png b/mobile/android/app/src/photon/res/drawable-mdpi/alert_mic.png deleted file mode 100644 index 667df9566db5..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/alert_mic.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/alert_mic_camera.png b/mobile/android/app/src/photon/res/drawable-mdpi/alert_mic_camera.png deleted file mode 100644 index 249a023beee9..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/alert_mic_camera.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/arrow.png b/mobile/android/app/src/photon/res/drawable-mdpi/arrow.png deleted file mode 100644 index fa30dd58c812..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/arrow.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/arrow_up.png b/mobile/android/app/src/photon/res/drawable-mdpi/arrow_up.png deleted file mode 100644 index fa0b4b6c1b55..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/arrow_up.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/casting.png b/mobile/android/app/src/photon/res/drawable-mdpi/casting.png deleted file mode 100644 index 0f9efcc6e697..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/casting.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/casting_active.png b/mobile/android/app/src/photon/res/drawable-mdpi/casting_active.png deleted file mode 100644 index f5657707a514..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/casting_active.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/cloud.png b/mobile/android/app/src/photon/res/drawable-mdpi/cloud.png deleted file mode 100644 index d45a47feaddc..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/cloud.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/device_desktop.png b/mobile/android/app/src/photon/res/drawable-mdpi/device_desktop.png deleted file mode 100644 index d0d223753db8..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/device_desktop.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/device_mobile.png b/mobile/android/app/src/photon/res/drawable-mdpi/device_mobile.png deleted file mode 100644 index 0e42531c4320..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/device_mobile.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/favicon_globe.png b/mobile/android/app/src/photon/res/drawable-mdpi/favicon_globe.png deleted file mode 100644 index c7da311370fe..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/favicon_globe.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/find_close.png b/mobile/android/app/src/photon/res/drawable-mdpi/find_close.png deleted file mode 100644 index e03da28d2176..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/find_close.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/find_next.png b/mobile/android/app/src/photon/res/drawable-mdpi/find_next.png deleted file mode 100644 index 68cbd30cd569..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/find_next.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/firefox_settings_alert.png b/mobile/android/app/src/photon/res/drawable-mdpi/firefox_settings_alert.png deleted file mode 100644 index 76a110ce0c45..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/firefox_settings_alert.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/flat_icon.png b/mobile/android/app/src/photon/res/drawable-mdpi/flat_icon.png deleted file mode 100644 index e963db0e9da3..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/flat_icon.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/folder_closed.png b/mobile/android/app/src/photon/res/drawable-mdpi/folder_closed.png deleted file mode 100644 index 9a298f7ea84f..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/folder_closed.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/globe_light.png b/mobile/android/app/src/photon/res/drawable-mdpi/globe_light.png deleted file mode 100644 index e2231bc28e4f..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/globe_light.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/home_group_collapsed.png b/mobile/android/app/src/photon/res/drawable-mdpi/home_group_collapsed.png deleted file mode 100644 index c16e3e22b01e..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/home_group_collapsed.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/homepage_banner_firstrun.png b/mobile/android/app/src/photon/res/drawable-mdpi/homepage_banner_firstrun.png deleted file mode 100644 index 0d23ba013737..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/homepage_banner_firstrun.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/ic_action_settings.png b/mobile/android/app/src/photon/res/drawable-mdpi/ic_action_settings.png deleted file mode 100644 index 9c454148613e..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/ic_action_settings.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/ic_cancel_nm.png b/mobile/android/app/src/photon/res/drawable-mdpi/ic_cancel_nm.png deleted file mode 100644 index 916893ec353a..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/ic_cancel_nm.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/ic_cancel_pm.png b/mobile/android/app/src/photon/res/drawable-mdpi/ic_cancel_pm.png deleted file mode 100644 index f6ea04506bd8..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/ic_cancel_pm.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/ic_globe_nm.png b/mobile/android/app/src/photon/res/drawable-mdpi/ic_globe_nm.png deleted file mode 100644 index b33ca7934295..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/ic_globe_nm.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/ic_globe_pm.png b/mobile/android/app/src/photon/res/drawable-mdpi/ic_globe_pm.png deleted file mode 100644 index 2c3934965656..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/ic_globe_pm.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/ic_lock.png b/mobile/android/app/src/photon/res/drawable-mdpi/ic_lock.png deleted file mode 100644 index 0311c2057c1c..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/ic_lock.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/ic_lock_disabled.png b/mobile/android/app/src/photon/res/drawable-mdpi/ic_lock_disabled.png deleted file mode 100644 index 539200158196..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/ic_lock_disabled.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/ic_lock_inactive.png b/mobile/android/app/src/photon/res/drawable-mdpi/ic_lock_inactive.png deleted file mode 100644 index 0fb6af0fb154..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/ic_lock_inactive.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/ic_media_pause.png b/mobile/android/app/src/photon/res/drawable-mdpi/ic_media_pause.png deleted file mode 100644 index d7fc0c0a9476..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/ic_media_pause.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/ic_media_play.png b/mobile/android/app/src/photon/res/drawable-mdpi/ic_media_play.png deleted file mode 100644 index f13172ed3a63..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/ic_media_play.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/ic_menu_share.png b/mobile/android/app/src/photon/res/drawable-mdpi/ic_menu_share.png deleted file mode 100644 index 766a24e0e6ec..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/ic_menu_share.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/ic_mic_nm.png b/mobile/android/app/src/photon/res/drawable-mdpi/ic_mic_nm.png deleted file mode 100644 index 51b91eac4780..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/ic_mic_nm.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/ic_mic_pm.png b/mobile/android/app/src/photon/res/drawable-mdpi/ic_mic_pm.png deleted file mode 100644 index f796ecc167b5..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/ic_mic_pm.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/ic_overflow_nm.png b/mobile/android/app/src/photon/res/drawable-mdpi/ic_overflow_nm.png deleted file mode 100644 index 2b23a3db4fdd..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/ic_overflow_nm.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/ic_overflow_pm.png b/mobile/android/app/src/photon/res/drawable-mdpi/ic_overflow_pm.png deleted file mode 100644 index e204806114fe..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/ic_overflow_pm.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/ic_qrcode_nm.png b/mobile/android/app/src/photon/res/drawable-mdpi/ic_qrcode_nm.png deleted file mode 100644 index 3867a6821574..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/ic_qrcode_nm.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/ic_qrcode_pm.png b/mobile/android/app/src/photon/res/drawable-mdpi/ic_qrcode_pm.png deleted file mode 100644 index 276976fc5f94..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/ic_qrcode_pm.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/ic_readermode_nm.png b/mobile/android/app/src/photon/res/drawable-mdpi/ic_readermode_nm.png deleted file mode 100644 index ea47432219c0..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/ic_readermode_nm.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/ic_readermode_on.png b/mobile/android/app/src/photon/res/drawable-mdpi/ic_readermode_on.png deleted file mode 100644 index 024c00323c54..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/ic_readermode_on.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/ic_readermode_pm.png b/mobile/android/app/src/photon/res/drawable-mdpi/ic_readermode_pm.png deleted file mode 100644 index 79f32505d5bf..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/ic_readermode_pm.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/ic_search_icon.png b/mobile/android/app/src/photon/res/drawable-mdpi/ic_search_icon.png deleted file mode 100644 index 29686b0b7aad..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/ic_search_icon.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/ic_shield_disabled.png b/mobile/android/app/src/photon/res/drawable-mdpi/ic_shield_disabled.png deleted file mode 100644 index b3b8f57e0c5a..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/ic_shield_disabled.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/ic_shield_enabled.png b/mobile/android/app/src/photon/res/drawable-mdpi/ic_shield_enabled.png deleted file mode 100644 index 8c907698e375..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/ic_shield_enabled.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/ic_status_logo.png b/mobile/android/app/src/photon/res/drawable-mdpi/ic_status_logo.png deleted file mode 100644 index a465e0435cd0..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/ic_status_logo.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/ic_url_bar_tab.png b/mobile/android/app/src/photon/res/drawable-mdpi/ic_url_bar_tab.png deleted file mode 100644 index baf0debaee66..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/ic_url_bar_tab.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/ic_warning_major.png b/mobile/android/app/src/photon/res/drawable-mdpi/ic_warning_major.png deleted file mode 100644 index 61a90601b860..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/ic_warning_major.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/ic_warning_minor.png b/mobile/android/app/src/photon/res/drawable-mdpi/ic_warning_minor.png deleted file mode 100644 index bd4bab857c3f..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/ic_warning_minor.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/ic_widget_new_tab.png b/mobile/android/app/src/photon/res/drawable-mdpi/ic_widget_new_tab.png deleted file mode 100644 index 49d6c062ccb0..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/ic_widget_new_tab.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/ic_widget_search.png b/mobile/android/app/src/photon/res/drawable-mdpi/ic_widget_search.png deleted file mode 100644 index ee96e64072f5..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/ic_widget_search.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/icon_bookmarks_empty.png b/mobile/android/app/src/photon/res/drawable-mdpi/icon_bookmarks_empty.png deleted file mode 100644 index 1319229e33bb..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/icon_bookmarks_empty.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/icon_key.png b/mobile/android/app/src/photon/res/drawable-mdpi/icon_key.png deleted file mode 100644 index 134401dd0c1e..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/icon_key.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/icon_most_recent_empty.png b/mobile/android/app/src/photon/res/drawable-mdpi/icon_most_recent_empty.png deleted file mode 100644 index a9a6f8ca1752..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/icon_most_recent_empty.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/icon_openinapp.png b/mobile/android/app/src/photon/res/drawable-mdpi/icon_openinapp.png deleted file mode 100644 index a0939dd4b495..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/icon_openinapp.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/icon_recent.png b/mobile/android/app/src/photon/res/drawable-mdpi/icon_recent.png deleted file mode 100644 index 22dd4203beea..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/icon_recent.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/icon_remote_tabs_empty.png b/mobile/android/app/src/photon/res/drawable-mdpi/icon_remote_tabs_empty.png deleted file mode 100644 index ce03e5ac2025..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/icon_remote_tabs_empty.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/icon_shareplane.png b/mobile/android/app/src/photon/res/drawable-mdpi/icon_shareplane.png deleted file mode 100644 index 4aaffb3a56a8..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/icon_shareplane.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/img_check.png b/mobile/android/app/src/photon/res/drawable-mdpi/img_check.png deleted file mode 100644 index 8198284a13ba..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/img_check.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/location.png b/mobile/android/app/src/photon/res/drawable-mdpi/location.png deleted file mode 100644 index 515e9ae5b980..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/location.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/media_bar_pause.png b/mobile/android/app/src/photon/res/drawable-mdpi/media_bar_pause.png deleted file mode 100644 index 97928dcbcd89..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/media_bar_pause.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/media_bar_play.png b/mobile/android/app/src/photon/res/drawable-mdpi/media_bar_play.png deleted file mode 100644 index f094c3315b2f..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/media_bar_play.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/media_bar_stop.png b/mobile/android/app/src/photon/res/drawable-mdpi/media_bar_stop.png deleted file mode 100644 index 9157544efc4c..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/media_bar_stop.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/menu.png b/mobile/android/app/src/photon/res/drawable-mdpi/menu.png deleted file mode 100644 index 7448bd2e6265..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/menu.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/menu_item_check.png b/mobile/android/app/src/photon/res/drawable-mdpi/menu_item_check.png deleted file mode 100644 index cbbdcc8e208d..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/menu_item_check.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/menu_item_more.png b/mobile/android/app/src/photon/res/drawable-mdpi/menu_item_more.png deleted file mode 100644 index 7f32fce144b0..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/menu_item_more.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/menu_item_uncheck.png b/mobile/android/app/src/photon/res/drawable-mdpi/menu_item_uncheck.png deleted file mode 100644 index 8082d8caaec7..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/menu_item_uncheck.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/network_error.png b/mobile/android/app/src/photon/res/drawable-mdpi/network_error.png deleted file mode 100644 index a69d2b4e3fa8..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/network_error.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/open_in_browser.png b/mobile/android/app/src/photon/res/drawable-mdpi/open_in_browser.png deleted file mode 100644 index 96343eed2aa3..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/open_in_browser.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/orange_check.png b/mobile/android/app/src/photon/res/drawable-mdpi/orange_check.png deleted file mode 100644 index dd23dc9cb398..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/orange_check.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/overlay_bookmark_icon.png b/mobile/android/app/src/photon/res/drawable-mdpi/overlay_bookmark_icon.png deleted file mode 100644 index 5ecb34e20531..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/overlay_bookmark_icon.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/overlay_bookmarked_already_icon.png b/mobile/android/app/src/photon/res/drawable-mdpi/overlay_bookmarked_already_icon.png deleted file mode 100644 index 56aeb98e3bcd..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/overlay_bookmarked_already_icon.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/overlay_check.png b/mobile/android/app/src/photon/res/drawable-mdpi/overlay_check.png deleted file mode 100644 index 7f80d2907a0c..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/overlay_check.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/pause.png b/mobile/android/app/src/photon/res/drawable-mdpi/pause.png deleted file mode 100644 index d8356a2120b8..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/pause.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/phone.png b/mobile/android/app/src/photon/res/drawable-mdpi/phone.png deleted file mode 100644 index 9b08ccfa1c1a..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/phone.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/play.png b/mobile/android/app/src/photon/res/drawable-mdpi/play.png deleted file mode 100644 index fa45c80e0551..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/play.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/push_notification.png b/mobile/android/app/src/photon/res/drawable-mdpi/push_notification.png deleted file mode 100644 index 719771c0156f..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/push_notification.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/reading_list_folder.png b/mobile/android/app/src/photon/res/drawable-mdpi/reading_list_folder.png deleted file mode 100644 index 024c00323c54..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/reading_list_folder.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/search_clear.png b/mobile/android/app/src/photon/res/drawable-mdpi/search_clear.png deleted file mode 100644 index f3d366e21a67..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/search_clear.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/search_history.png b/mobile/android/app/src/photon/res/drawable-mdpi/search_history.png deleted file mode 100644 index 845f3bffec8a..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/search_history.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/search_icon_active.png b/mobile/android/app/src/photon/res/drawable-mdpi/search_icon_active.png deleted file mode 100644 index 83cb3944a72f..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/search_icon_active.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/search_plus.png b/mobile/android/app/src/photon/res/drawable-mdpi/search_plus.png deleted file mode 100644 index 40942108e8b8..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/search_plus.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/settings_notifications.png b/mobile/android/app/src/photon/res/drawable-mdpi/settings_notifications.png deleted file mode 100644 index 143006d7955a..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/settings_notifications.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/shareplane.png b/mobile/android/app/src/photon/res/drawable-mdpi/shareplane.png deleted file mode 100644 index d6eb0b2b2cd6..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/shareplane.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/status_icon_readercache.png b/mobile/android/app/src/photon/res/drawable-mdpi/status_icon_readercache.png deleted file mode 100644 index e2f53ad09a62..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/status_icon_readercache.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/suggestedsites_twitter.png b/mobile/android/app/src/photon/res/drawable-mdpi/suggestedsites_twitter.png deleted file mode 100644 index e8ad9fda306e..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/suggestedsites_twitter.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/switch_button_icon.png b/mobile/android/app/src/photon/res/drawable-mdpi/switch_button_icon.png deleted file mode 100644 index 7141dfd61879..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/switch_button_icon.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/sync_desktop.png b/mobile/android/app/src/photon/res/drawable-mdpi/sync_desktop.png deleted file mode 100644 index bc690792734c..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/sync_desktop.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/sync_desktop_inactive.png b/mobile/android/app/src/photon/res/drawable-mdpi/sync_desktop_inactive.png deleted file mode 100644 index 53e359af89f5..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/sync_desktop_inactive.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/sync_mobile.png b/mobile/android/app/src/photon/res/drawable-mdpi/sync_mobile.png deleted file mode 100644 index 87af05067114..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/sync_mobile.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/sync_mobile_inactive.png b/mobile/android/app/src/photon/res/drawable-mdpi/sync_mobile_inactive.png deleted file mode 100644 index 60373858401a..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/sync_mobile_inactive.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/tab_audio_playing.png b/mobile/android/app/src/photon/res/drawable-mdpi/tab_audio_playing.png deleted file mode 100644 index e3197b48ca59..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/tab_audio_playing.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/tab_close.png b/mobile/android/app/src/photon/res/drawable-mdpi/tab_close.png deleted file mode 100644 index cd488e3ec701..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/tab_close.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/tab_close_active.png b/mobile/android/app/src/photon/res/drawable-mdpi/tab_close_active.png deleted file mode 100644 index 77fd318aa172..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/tab_close_active.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/tab_new.png b/mobile/android/app/src/photon/res/drawable-mdpi/tab_new.png deleted file mode 100644 index 4918e2389bb2..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/tab_new.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/tab_preview_masq.png b/mobile/android/app/src/photon/res/drawable-mdpi/tab_preview_masq.png deleted file mode 100644 index 8b0fb7fb004e..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/tab_preview_masq.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/tabs_normal.png b/mobile/android/app/src/photon/res/drawable-mdpi/tabs_normal.png deleted file mode 100644 index fb60be436899..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/tabs_normal.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/tabs_panel_nav_back.png b/mobile/android/app/src/photon/res/drawable-mdpi/tabs_panel_nav_back.png deleted file mode 100644 index 780a0ec73c5c..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/tabs_panel_nav_back.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/tabs_private.png b/mobile/android/app/src/photon/res/drawable-mdpi/tabs_private.png deleted file mode 100644 index 18b24fc6e512..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/tabs_private.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/tip_addsearch.png b/mobile/android/app/src/photon/res/drawable-mdpi/tip_addsearch.png deleted file mode 100644 index 5acb2a22bcb6..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/tip_addsearch.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-mdpi/undo_button_icon.png b/mobile/android/app/src/photon/res/drawable-mdpi/undo_button_icon.png deleted file mode 100644 index f75eba2a23e8..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-mdpi/undo_button_icon.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-xhdpi/ic_globe_nm.png b/mobile/android/app/src/photon/res/drawable-xhdpi/ic_globe_nm.png index 25e46aa40487..8f60bb305988 100644 Binary files a/mobile/android/app/src/photon/res/drawable-xhdpi/ic_globe_nm.png and b/mobile/android/app/src/photon/res/drawable-xhdpi/ic_globe_nm.png differ diff --git a/mobile/android/app/src/photon/res/drawable-xhdpi/reading_list_folder.png b/mobile/android/app/src/photon/res/drawable-xhdpi/reading_list_folder.png index 95627b4648d2..adb778eb8aaf 100644 Binary files a/mobile/android/app/src/photon/res/drawable-xhdpi/reading_list_folder.png and b/mobile/android/app/src/photon/res/drawable-xhdpi/reading_list_folder.png differ diff --git a/mobile/android/app/src/photon/res/drawable-xhdpi/status_icon_readercache.png b/mobile/android/app/src/photon/res/drawable-xhdpi/status_icon_readercache.png index 135e1cce079f..5d435eaff24f 100644 Binary files a/mobile/android/app/src/photon/res/drawable-xhdpi/status_icon_readercache.png and b/mobile/android/app/src/photon/res/drawable-xhdpi/status_icon_readercache.png differ diff --git a/mobile/android/app/src/photon/res/drawable-xhdpi/tip_addsearch.png b/mobile/android/app/src/photon/res/drawable-xhdpi/tip_addsearch.png deleted file mode 100644 index 3d197baf013b..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-xhdpi/tip_addsearch.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-xxhdpi/ic_globe_nm.png b/mobile/android/app/src/photon/res/drawable-xxhdpi/ic_globe_nm.png index 0a9d90f51b28..aacf7a9efd31 100644 Binary files a/mobile/android/app/src/photon/res/drawable-xxhdpi/ic_globe_nm.png and b/mobile/android/app/src/photon/res/drawable-xxhdpi/ic_globe_nm.png differ diff --git a/mobile/android/app/src/photon/res/drawable-xxhdpi/tip_addsearch.png b/mobile/android/app/src/photon/res/drawable-xxhdpi/tip_addsearch.png deleted file mode 100644 index 4091423b1125..000000000000 Binary files a/mobile/android/app/src/photon/res/drawable-xxhdpi/tip_addsearch.png and /dev/null differ diff --git a/mobile/android/app/src/photon/res/drawable-xxxhdpi/ic_globe_nm.png b/mobile/android/app/src/photon/res/drawable-xxxhdpi/ic_globe_nm.png index 26772257f1b3..40b2cd2f16ca 100644 Binary files a/mobile/android/app/src/photon/res/drawable-xxxhdpi/ic_globe_nm.png and b/mobile/android/app/src/photon/res/drawable-xxxhdpi/ic_globe_nm.png differ diff --git a/mobile/android/app/src/photon/res/drawable-xxxhdpi/reading_list_folder.png b/mobile/android/app/src/photon/res/drawable-xxxhdpi/reading_list_folder.png index c0f812a6e1aa..de101cc91be2 100644 Binary files a/mobile/android/app/src/photon/res/drawable-xxxhdpi/reading_list_folder.png and b/mobile/android/app/src/photon/res/drawable-xxxhdpi/reading_list_folder.png differ diff --git a/mobile/android/app/src/photon/res/drawable-xxxhdpi/status_icon_readercache.png b/mobile/android/app/src/photon/res/drawable-xxxhdpi/status_icon_readercache.png index e513c2771a0f..dfacef9e1b1b 100644 Binary files a/mobile/android/app/src/photon/res/drawable-xxxhdpi/status_icon_readercache.png and b/mobile/android/app/src/photon/res/drawable-xxxhdpi/status_icon_readercache.png differ diff --git a/mobile/android/app/src/photon/res/drawable/tab_strip_item_bg.xml b/mobile/android/app/src/photon/res/drawable/tab_strip_item_bg.xml index b06f897ce47d..6ae383ed754c 100644 --- a/mobile/android/app/src/photon/res/drawable/tab_strip_item_bg.xml +++ b/mobile/android/app/src/photon/res/drawable/tab_strip_item_bg.xml @@ -6,6 +6,7 @@ + - - + - - - - - + + gecko:state_dark="true"/> + gecko:state_dark="true"/> - + + + + + + + + + + + + @@ -57,6 +68,6 @@ - + diff --git a/mobile/android/app/src/photon/res/drawable/tabs_counter_box.xml b/mobile/android/app/src/photon/res/drawable/tabs_counter_box.xml index 530cde37ece0..5f503f5e4367 100644 --- a/mobile/android/app/src/photon/res/drawable/tabs_counter_box.xml +++ b/mobile/android/app/src/photon/res/drawable/tabs_counter_box.xml @@ -5,7 +5,7 @@ diff --git a/mobile/android/app/src/photon/res/drawable/url_bar_title_bg.xml b/mobile/android/app/src/photon/res/drawable/url_bar_title_bg.xml new file mode 100644 index 000000000000..65cd5229a055 --- /dev/null +++ b/mobile/android/app/src/photon/res/drawable/url_bar_title_bg.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + diff --git a/mobile/android/app/src/photon/res/layout/browser_toolbar.xml b/mobile/android/app/src/photon/res/layout/browser_toolbar.xml index 9397bc482183..d86aea72bca2 100644 --- a/mobile/android/app/src/photon/res/layout/browser_toolbar.xml +++ b/mobile/android/app/src/photon/res/layout/browser_toolbar.xml @@ -7,7 +7,7 @@ diff --git a/mobile/android/app/src/photon/res/layout/home_suggestion_prompt.xml b/mobile/android/app/src/photon/res/layout/home_suggestion_prompt.xml index b584ebe4b57b..a7a25096b309 100644 --- a/mobile/android/app/src/photon/res/layout/home_suggestion_prompt.xml +++ b/mobile/android/app/src/photon/res/layout/home_suggestion_prompt.xml @@ -37,6 +37,7 @@ android:minWidth="64dp" android:nextFocusRight="@+id/suggestions_prompt_yes" android:text="@string/button_no" + android:textAllCaps="true" android:textColor="@android:color/white" android:textSize="14sp" android:textStyle="bold"/> @@ -50,6 +51,7 @@ android:gravity="center" android:minWidth="64dp" android:text="@string/button_yes" + android:textAllCaps="true" android:textColor="@android:color/white" android:textSize="14sp" android:textStyle="bold"/> diff --git a/mobile/android/app/src/photon/res/layout/tab_strip_item_view.xml b/mobile/android/app/src/photon/res/layout/tab_strip_item_view.xml index 39bb05eae7f1..cbbcd97e22a8 100644 --- a/mobile/android/app/src/photon/res/layout/tab_strip_item_view.xml +++ b/mobile/android/app/src/photon/res/layout/tab_strip_item_view.xml @@ -13,13 +13,10 @@ diff --git a/mobile/android/app/src/photon/res/layout/tabs_counter.xml b/mobile/android/app/src/photon/res/layout/tabs_counter.xml index b14bfda1f1d4..24033f534575 100644 --- a/mobile/android/app/src/photon/res/layout/tabs_counter.xml +++ b/mobile/android/app/src/photon/res/layout/tabs_counter.xml @@ -28,6 +28,7 @@ @@ -20,22 +21,18 @@ android:id="@+id/site_security" style="@style/UrlBar.SiteIdentity" android:layout_gravity="center_vertical" - android:layout_marginBottom="@dimen/browser_toolbar_site_security_margin_bottom" - android:layout_marginEnd="@dimen/browser_toolbar_site_security_margin_end" - android:layout_marginRight="@dimen/browser_toolbar_site_security_margin_end" - android:background="@android:color/transparent" + android:background="@drawable/url_bar_title_bg" android:contentDescription="@string/site_security" android:src="@drawable/security_mode_icon" - tools:background="#88FF0000" tools:src="@drawable/ic_lock"/> diff --git a/mobile/android/app/src/photon/res/layout/toolbar_edit_layout.xml b/mobile/android/app/src/photon/res/layout/toolbar_edit_layout.xml index 87da6b377c32..f36b3198a168 100644 --- a/mobile/android/app/src/photon/res/layout/toolbar_edit_layout.xml +++ b/mobile/android/app/src/photon/res/layout/toolbar_edit_layout.xml @@ -18,6 +18,8 @@ + android:selectAllOnFocus="true"/> 8dp + @dimen/browser_toolbar_site_security_margin_end diff --git a/mobile/android/app/src/photon/res/values-large/dimens.xml b/mobile/android/app/src/photon/res/values-large/dimens.xml index e303dd98e078..87bb31dc3812 100644 --- a/mobile/android/app/src/photon/res/values-large/dimens.xml +++ b/mobile/android/app/src/photon/res/values-large/dimens.xml @@ -8,18 +8,17 @@ 100dp - - 50dp - @dimen/browser_toolbar_height 16dp @dimen/browser_toolbar_height 4dp 16dp - 56dp + 48dp + + 4dp 4dp 34dp @@ -41,6 +40,5 @@ 8dp - 56dp - + @dimen/browser_toolbar_icon_width diff --git a/mobile/android/app/src/photon/res/values-large/styles.xml b/mobile/android/app/src/photon/res/values-large/styles.xml index bec119a50349..1b807a946b89 100644 --- a/mobile/android/app/src/photon/res/values-large/styles.xml +++ b/mobile/android/app/src/photon/res/values-large/styles.xml @@ -199,6 +199,7 @@ diff --git a/mobile/android/app/src/photon/res/values-v17/styles.xml b/mobile/android/app/src/photon/res/values-v17/styles.xml index 22fd0b8b67c2..aeb93c5264ee 100644 --- a/mobile/android/app/src/photon/res/values-v17/styles.xml +++ b/mobile/android/app/src/photon/res/values-v17/styles.xml @@ -5,6 +5,7 @@ - + + diff --git a/mobile/android/app/src/photon/res/values/colors.xml b/mobile/android/app/src/photon/res/values/colors.xml index e4def506b040..c8c4c9d782ed 100644 --- a/mobile/android/app/src/photon/res/values/colors.xml +++ b/mobile/android/app/src/photon/res/values/colors.xml @@ -10,10 +10,10 @@ #FFFFFF #B2B2B2 #737373 - #B2B2B2 + #737373 #737373 - #E4E4E4 - #737373 + #4D0A84FF + #4DAC39FF #E4E4E4 @@ -39,7 +39,7 @@ #1A272727 #272727 - #00A2FE + #0A84FF #AC39FF #00DCFC #FF1AD9 @@ -60,7 +60,7 @@ #33000000 - #3B3B3B + #272727 #FFFFFF @color/photon_toolbar_text_color @color/photon_toolbar_text_color_private @@ -82,6 +82,7 @@ #16DA00 @color/photon_text_main @color/photon_text_main_private + #80FFFFFF #272727 @@ -91,16 +92,23 @@ #F9F9FA #E4E4E5 #3D3C3D + #272727 #38383D #4C4B51 #3D3C3D - #1A38383D - #1AFFFFFF - #1AFFFFFF - #1AF9F9FA - #1A272727 - #1A272727 + #CCF9F9FA + #99FFFFFF + #66E4E4E4 + #33FFFFFF + + @color/photon_tab_strip_item_lwt_checked + @color/photon_tab_strip_item_lwt_checked_pressed + @color/photon_tab_strip_item_lwt + @color/photon_tab_strip_item_lwt_pressed + + #272727 + #272727 @color/photon_toolbar_bg @@ -283,7 +291,7 @@ #FF919191 #FF919191 - #414146 + #737373 #FCFCFC diff --git a/mobile/android/app/src/photon/res/values/dimens.xml b/mobile/android/app/src/photon/res/values/dimens.xml index 25d75eadb600..7033e7a1e4ec 100644 --- a/mobile/android/app/src/photon/res/values/dimens.xml +++ b/mobile/android/app/src/photon/res/values/dimens.xml @@ -12,17 +12,17 @@ 32dp 56dp - 48dp + 42dp - 4dp + helps drawable xml to set padding: (56 - 42) / 2 --> + 7dp 0dp - 50dp + (4dp). This value should change when the height of the view changes. --> + 52dp @@ -32,6 +32,8 @@ 16dp 2dp + 32dp + 21.33dip @@ -56,7 +58,7 @@ 0dp 1dp 30dp - 6dp + 4dp 56dp 19dp @@ -88,7 +90,6 @@ 0dp - .5dp 1dp -1dp @@ -184,7 +185,7 @@ 5dp - 48dp + @dimen/browser_toolbar_image_button_width 72dp diff --git a/mobile/android/app/src/photon/res/values/styles.xml b/mobile/android/app/src/photon/res/values/styles.xml index 463c33e3eea4..95265fdcf963 100644 --- a/mobile/android/app/src/photon/res/values/styles.xml +++ b/mobile/android/app/src/photon/res/values/styles.xml @@ -465,6 +465,7 @@ + + + + + + + diff --git a/mobile/android/base/java/org/mozilla/gecko/lwt/LightweightThemeDrawable.java b/mobile/android/base/java/org/mozilla/gecko/lwt/LightweightThemeDrawable.java index c0ae6eaedf0a..3e7abc8d2e5e 100644 --- a/mobile/android/base/java/org/mozilla/gecko/lwt/LightweightThemeDrawable.java +++ b/mobile/android/base/java/org/mozilla/gecko/lwt/LightweightThemeDrawable.java @@ -34,6 +34,7 @@ public class LightweightThemeDrawable extends Drawable { private int mStartColor; private int mEndColor; + private boolean mHorizontalGradient; public LightweightThemeDrawable(Resources resources, Bitmap bitmap) { mBitmap = bitmap; @@ -104,15 +105,22 @@ public class LightweightThemeDrawable extends Drawable { mColorPaint.setColorFilter(new PorterDuffColorFilter(filter, PorterDuff.Mode.SRC_OVER)); } + public void setAlpha(final int startAlpha, final int endAlpha) { + // By default we draw a vertical linear gradient. + setAlpha(startAlpha, endAlpha, false); + } + /** * Set the alpha for the linear gradient used with the bitmap's shader. * * @param startAlpha The starting alpha (0..255) value to be applied to the LinearGradient. - * @param startAlpha The ending alpha (0..255) value to be applied to the LinearGradient. + * @param endAlpha The ending alpha (0..255) value to be applied to the LinearGradient. + * @param horizontalGradient Draw gradient in horizontal direction; otherwise in vertical direction. */ - public void setAlpha(int startAlpha, int endAlpha) { + public void setAlpha(final int startAlpha, final int endAlpha, final boolean horizontalGradient) { mStartColor = startAlpha << 24; mEndColor = endAlpha << 24; + mHorizontalGradient = horizontalGradient; initializeBitmapShader(); } @@ -120,10 +128,15 @@ public class LightweightThemeDrawable extends Drawable { // A bitmap-shader to draw the bitmap. // Clamp mode will repeat the last row of pixels. // Hence its better to have an endAlpha of 0 for the linear-gradient. - BitmapShader bitmapShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); + final BitmapShader bitmapShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); // A linear-gradient to specify the opacity of the bitmap. - LinearGradient gradient = new LinearGradient(0, 0, 0, mBitmap.getHeight(), mStartColor, mEndColor, Shader.TileMode.CLAMP); + final LinearGradient gradient; + if (mHorizontalGradient) { + gradient = new LinearGradient(0, 0, mBitmap.getWidth(), 0, mStartColor, mEndColor, Shader.TileMode.CLAMP); + } else { + gradient = new LinearGradient(0, 0, 0, mBitmap.getHeight(), mStartColor, mEndColor, Shader.TileMode.CLAMP); + } // Make a combined shader -- a performance win. // The linear-gradient is the 'SRC' and the bitmap-shader is the 'DST'. diff --git a/mobile/android/base/java/org/mozilla/gecko/toolbar/BrowserToolbar.java b/mobile/android/base/java/org/mozilla/gecko/toolbar/BrowserToolbar.java index 6bc2d02fffea..6907c18b2758 100644 --- a/mobile/android/base/java/org/mozilla/gecko/toolbar/BrowserToolbar.java +++ b/mobile/android/base/java/org/mozilla/gecko/toolbar/BrowserToolbar.java @@ -32,6 +32,7 @@ import org.mozilla.gecko.toolbar.ToolbarDisplayLayout.OnStopListener; import org.mozilla.gecko.toolbar.ToolbarDisplayLayout.OnTitleChangeListener; import org.mozilla.gecko.toolbar.ToolbarDisplayLayout.UpdateFlags; import org.mozilla.gecko.util.Clipboard; +import org.mozilla.gecko.util.HardwareUtils; import org.mozilla.gecko.util.MenuUtils; import org.mozilla.gecko.util.WindowUtil; import org.mozilla.gecko.widget.AnimatedProgressBar; @@ -81,7 +82,9 @@ public abstract class BrowserToolbar extends ThemedRelativeLayout GeckoMenu.ActionItemBarPresenter { private static final String LOGTAG = "GeckoToolbar"; - private static final int LIGHTWEIGHT_THEME_ALPHA = 77; + private static final int LIGHTWEIGHT_THEME_INVERT_ALPHA_START = 204; // 255 - alpha = invert_alpha + private static final int LIGHTWEIGHT_THEME_INVERT_ALPHA_END = 102; + public static final int LIGHTWEIGHT_THEME_INVERT_ALPHA_TABLET = 51; public interface OnActivateListener { public void onActivate(); @@ -908,7 +911,7 @@ public abstract class BrowserToolbar extends ThemedRelativeLayout } final StateListDrawable stateList = new StateListDrawable(); - stateList.addState(PRIVATE_STATE_SET, getColorDrawable(R.color.tabs_tray_grey_pressed)); + stateList.addState(PRIVATE_STATE_SET, getColorDrawable(R.color.photon_browser_toolbar_bg_private)); stateList.addState(EMPTY_STATE_SET, drawable); setBackgroundDrawable(stateList); @@ -937,7 +940,19 @@ public abstract class BrowserToolbar extends ThemedRelativeLayout final LightweightThemeDrawable drawable = theme.getColorDrawable(view, color); if (drawable != null) { - drawable.setAlpha(LIGHTWEIGHT_THEME_ALPHA, LIGHTWEIGHT_THEME_ALPHA); + final int startAlpha, endAlpha; + final boolean horizontalGradient; + + if (HardwareUtils.isTablet()) { + startAlpha = LIGHTWEIGHT_THEME_INVERT_ALPHA_TABLET; + endAlpha = LIGHTWEIGHT_THEME_INVERT_ALPHA_TABLET; + horizontalGradient = false; + } else { + startAlpha = LIGHTWEIGHT_THEME_INVERT_ALPHA_START; + endAlpha = LIGHTWEIGHT_THEME_INVERT_ALPHA_END; + horizontalGradient = true; + } + drawable.setAlpha(startAlpha, endAlpha, horizontalGradient); } return drawable; diff --git a/mobile/android/base/java/org/mozilla/gecko/toolbar/NavButton.java b/mobile/android/base/java/org/mozilla/gecko/toolbar/NavButton.java index 6e0fef996d79..4049fc605fb7 100644 --- a/mobile/android/base/java/org/mozilla/gecko/toolbar/NavButton.java +++ b/mobile/android/base/java/org/mozilla/gecko/toolbar/NavButton.java @@ -58,8 +58,17 @@ abstract class NavButton extends ShapedButton { public void draw(Canvas canvas) { super.draw(canvas); - final double alpha = 255 * (isEnabled() ? 1 : 0.05); - mBorderPaint.setAlpha((int) alpha); + final double alphaRatio; + if (getTheme().isEnabled()) { + // When LightweightTheme is enabled, we don't want to show clear border. + alphaRatio = 0.4; + } else if (isEnabled()) { + alphaRatio = 1; + } else { + // We also use low alpha value to present disabled state. + alphaRatio = 0.05; + } + mBorderPaint.setAlpha((int) (255 * alphaRatio)); // Draw the border on top. canvas.drawPath(mBorderPath, mBorderPaint); diff --git a/python/mozbuild/mozbuild/mozinfo.py b/python/mozbuild/mozbuild/mozinfo.py index 609a3231d430..205a6c743de9 100755 --- a/python/mozbuild/mozbuild/mozinfo.py +++ b/python/mozbuild/mozbuild/mozinfo.py @@ -91,7 +91,7 @@ def build_dict(config, env=os.environ): d['bin_suffix'] = substs.get('BIN_SUFFIX', '') d['addon_signing'] = substs.get('MOZ_ADDON_SIGNING') == '1' d['require_signing'] = substs.get('MOZ_REQUIRE_SIGNING') == '1' - d['no_legacy_extensions'] = substs.get('MOZ_ALLOW_LEGACY_EXTENSIONS') == '0' + d['allow_legacy_extensions'] = substs.get('MOZ_ALLOW_LEGACY_EXTENSIONS') == '1' d['official'] = bool(substs.get('MOZILLA_OFFICIAL')) d['updater'] = substs.get('MOZ_UPDATER') == '1' d['artifact'] = substs.get('MOZ_ARTIFACT_BUILDS') == '1' diff --git a/services/sync/tests/unit/test_clients_engine.js b/services/sync/tests/unit/test_clients_engine.js index 184b4aca638c..8ce700000e24 100644 --- a/services/sync/tests/unit/test_clients_engine.js +++ b/services/sync/tests/unit/test_clients_engine.js @@ -48,6 +48,11 @@ function compareCommands(actual, expected, description) { equal(allIDs.size, actual.length, "all items have unique IDs"); } +async function syncClientsEngine(server) { + engine.lastModified = server.getCollection("foo", "clients").timestamp; + await engine._sync(); +} + add_task(async function setup() { engine = Service.clientsEngine; }); @@ -106,7 +111,7 @@ add_task(async function test_bad_hmac() { _("First sync, client record is uploaded"); equal(engine.lastRecordUpload, 0); check_clients_count(0); - await engine._sync(); + await syncClientsEngine(server); check_clients_count(1); ok(engine.lastRecordUpload > 0); @@ -127,7 +132,7 @@ add_task(async function test_bad_hmac() { ok((await serverKeys.upload(Service.resource(Service.cryptoKeysURL))).success); _("Sync."); - await engine._sync(); + await syncClientsEngine(server); _("Old record " + oldLocalID + " was deleted, new one uploaded."); check_clients_count(1); @@ -142,8 +147,7 @@ add_task(async function test_bad_hmac() { deletedCollections = []; deletedItems = []; check_clients_count(1); - engine.lastModified = server.getCollection("foo", "clients").timestamp; - await engine._sync(); + await syncClientsEngine(server); _("Old record was not deleted, new one uploaded."); equal(deletedCollections.length, 0); @@ -163,8 +167,7 @@ add_task(async function test_bad_hmac() { await uploadNewKeys(); // Sync once to upload a record. - engine.lastModified = server.getCollection("foo", "clients").timestamp; - await engine._sync(); + await syncClientsEngine(server); check_clients_count(1); // Generate and upload new keys, so the old client record is wrong. @@ -181,7 +184,7 @@ add_task(async function test_bad_hmac() { equal(deletedCollections.length, 0); equal(deletedItems.length, 0); - await engine._sync(); + await syncClientsEngine(server); equal(deletedItems.length, 1); check_client_deleted(oldLocalID); check_clients_count(1); @@ -243,8 +246,7 @@ add_task(async function test_full_sync() { _("First sync. 2 records downloaded; our record uploaded."); strictEqual(engine.lastRecordUpload, 0); - engine.lastModified = server.getCollection("foo", "clients").timestamp; - await engine._sync(); + await syncClientsEngine(server); ok(engine.lastRecordUpload > 0); deepEqual(user.collection("clients").keys().sort(), [activeID, deletedID, engine.localID].sort(), @@ -258,8 +260,7 @@ add_task(async function test_full_sync() { let collection = server.getCollection("foo", "clients"); collection.remove(deletedID); // Simulate a timestamp update in info/collections. - engine.lastModified = now; - await engine._sync(); + await syncClientsEngine(server); _("Record should be updated"); ids = await store.getAllIDs(); @@ -295,8 +296,7 @@ add_task(async function test_sync() { _("First sync. Client record is uploaded."); equal(clientWBO(), undefined); equal(engine.lastRecordUpload, 0); - engine.lastModified = server.getCollection("foo", "clients").timestamp; - await engine._sync(); + await syncClientsEngine(server); ok(!!clientWBO().payload); ok(engine.lastRecordUpload > 0); @@ -304,8 +304,7 @@ add_task(async function test_sync() { engine.lastRecordUpload -= MORE_THAN_CLIENTS_TTL_REFRESH; let lastweek = engine.lastRecordUpload; clientWBO().payload = undefined; - engine.lastModified = server.getCollection("foo", "clients").timestamp; - await engine._sync(); + await syncClientsEngine(server); ok(!!clientWBO().payload); ok(engine.lastRecordUpload > lastweek); @@ -316,7 +315,7 @@ add_task(async function test_sync() { _("Time travel one day back, no record uploaded."); engine.lastRecordUpload -= LESS_THAN_CLIENTS_TTL_REFRESH; let yesterday = engine.lastRecordUpload; - await engine._sync(); + await syncClientsEngine(server); equal(clientWBO().payload, undefined); equal(engine.lastRecordUpload, yesterday); @@ -382,8 +381,7 @@ add_task(async function test_last_modified() { let collection = user.collection("clients"); _("Sync to download the record"); - engine.lastModified = server.getCollection("foo", "clients").timestamp; - await engine._sync(); + await syncClientsEngine(server); equal(engine._store._remoteClients[activeID].serverLastModified, now - 10, "last modified in the local record is correctly the server last-modified"); @@ -639,8 +637,7 @@ add_task(async function test_filter_duplicate_names() { _("First sync"); strictEqual(engine.lastRecordUpload, 0); - engine.lastModified = server.getCollection("foo", "clients").timestamp; - await engine._sync(); + await syncClientsEngine(server); ok(engine.lastRecordUpload > 0); deepEqual(user.collection("clients").keys().sort(), [recentID, dupeID, oldID, engine.localID].sort(), @@ -675,7 +672,7 @@ add_task(async function test_filter_duplicate_names() { counts = subject; }); - await engine._sync(); + await syncClientsEngine(server); equal(counts.applied, 0); // We didn't report applying any records. equal(counts.reconciled, 4); // We reported reconcilliation for all records equal(counts.succeeded, 0); @@ -684,7 +681,7 @@ add_task(async function test_filter_duplicate_names() { _("Broadcast logout to all clients"); await engine.sendCommand("logout", []); - await engine._sync(); + await syncClientsEngine(server); let collection = server.getCollection("foo", "clients"); let recentPayload = JSON.parse(JSON.parse(collection.payload(recentID)).ciphertext); @@ -710,7 +707,7 @@ add_task(async function test_filter_duplicate_names() { }), now - 10)); _("Second sync."); - await engine._sync(); + await syncClientsEngine(server); ids = await store.getAllIDs(); deepEqual(Object.keys(ids).sort(), @@ -771,7 +768,7 @@ add_task(async function test_command_sync() { try { _("Syncing."); - await engine._sync(); + await syncClientsEngine(server); _("Checking remote record was downloaded."); let clientRecord = engine._store._remoteClients[remoteId]; @@ -782,7 +779,7 @@ add_task(async function test_command_sync() { await engine.sendCommand("wipeAll", []); let clientCommands = (await engine._readCommands())[remoteId]; equal(clientCommands.length, 1); - await engine._sync(); + await syncClientsEngine(server); _("Checking record was uploaded."); notEqual(clientWBO(engine.localID).payload, undefined); @@ -794,7 +791,7 @@ add_task(async function test_command_sync() { engine._resetClient(); equal(engine.localID, remoteId); _("Performing sync on resetted client."); - await engine._sync(); + await syncClientsEngine(server); notEqual(engine.localCommands, undefined); equal(engine.localCommands.length, 1); @@ -855,7 +852,7 @@ add_task(async function test_clients_not_in_fxa_list() { try { _("Syncing."); - await engine._sync(); + await syncClientsEngine(server); ok(!engine._store._remoteClients[remoteId].stale); ok(engine._store._remoteClients[remoteId2].stale); @@ -1032,12 +1029,11 @@ add_task(async function test_merge_commands() { try { _("First sync. 2 records downloaded."); strictEqual(engine.lastRecordUpload, 0); - engine.lastModified = server.getCollection("foo", "clients").timestamp; - await engine._sync(); + await syncClientsEngine(server); _("Broadcast logout to all clients"); await engine.sendCommand("logout", []); - await engine._sync(); + await syncClientsEngine(server); let collection = server.getCollection("foo", "clients"); let desktopPayload = JSON.parse(JSON.parse(collection.payload(desktopID)).ciphertext); @@ -1085,12 +1081,11 @@ add_task(async function test_duplicate_remote_commands() { try { _("First sync. 1 record downloaded."); strictEqual(engine.lastRecordUpload, 0); - engine.lastModified = server.getCollection("foo", "clients").timestamp; - await engine._sync(); + await syncClientsEngine(server); _("Send tab to client"); await engine.sendCommand("displayURI", ["https://example.com", engine.localID, "Yak Herders Anonymous"]); - await engine._sync(); + await syncClientsEngine(server); _("Simulate the desktop client consuming the command and syncing to the server"); server.insertWBO("foo", "clients", new ServerWBO(desktopID, encryptPayload({ @@ -1104,8 +1099,7 @@ add_task(async function test_duplicate_remote_commands() { _("Send another tab to the desktop client"); await engine.sendCommand("displayURI", ["https://foobar.com", engine.localID, "Foo bar!"], desktopID); - engine.lastModified = server.getCollection("foo", "clients").timestamp; - await engine._sync(); + await syncClientsEngine(server); let collection = server.getCollection("foo", "clients"); let desktopPayload = JSON.parse(JSON.parse(collection.payload(desktopID)).ciphertext); @@ -1159,15 +1153,14 @@ add_task(async function test_upload_after_reboot() { try { _("First sync. 2 records downloaded."); strictEqual(engine.lastRecordUpload, 0); - engine.lastModified = server.getCollection("foo", "clients").timestamp; - await engine._sync(); + await syncClientsEngine(server); _("Send tab to client"); await engine.sendCommand("displayURI", ["https://example.com", engine.localID, "Yak Herders Anonymous"], deviceBID); const oldUploadOutgoing = SyncEngine.prototype._uploadOutgoing; SyncEngine.prototype._uploadOutgoing = async () => engine._onRecordsWritten([], [deviceBID]); - await engine._sync(); + await syncClientsEngine(server); let collection = server.getCollection("foo", "clients"); let deviceBPayload = JSON.parse(JSON.parse(collection.payload(deviceBID)).ciphertext); @@ -1190,7 +1183,7 @@ add_task(async function test_upload_after_reboot() { engine = Service.clientsEngine = new ClientEngine(Service); await engine.initialize(); - await engine._sync(); + await syncClientsEngine(server); deviceBPayload = JSON.parse(JSON.parse(collection.payload(deviceBID)).ciphertext); compareCommands(deviceBPayload.commands, [{ @@ -1263,7 +1256,7 @@ add_task(async function test_keep_cleared_commands_after_reboot() { let commandsProcessed = 0; engine._handleDisplayURIs = (uris) => { commandsProcessed = uris.length }; - await engine._sync(); + await syncClientsEngine(server); await engine.processIncomingCommands(); // Not called by the engine.sync(), gotta call it ourselves equal(commandsProcessed, 2, "We processed 2 commands"); @@ -1306,8 +1299,7 @@ add_task(async function test_keep_cleared_commands_after_reboot() { commandsProcessed = 0; engine._handleDisplayURIs = (uris) => { commandsProcessed = uris.length }; - engine.lastModified = server.getCollection("foo", "clients").timestamp; - await engine._sync(); + await syncClientsEngine(server); await engine.processIncomingCommands(); equal(commandsProcessed, 1, "We processed one command (the other were cleared)"); @@ -1360,8 +1352,7 @@ add_task(async function test_deleted_commands() { try { _("First sync. 2 records downloaded."); - engine.lastModified = server.getCollection("foo", "clients").timestamp; - await engine._sync(); + await syncClientsEngine(server); _("Delete a record on the server."); let collection = server.getCollection("foo", "clients"); @@ -1369,8 +1360,7 @@ add_task(async function test_deleted_commands() { _("Broadcast a command to all clients"); await engine.sendCommand("logout", []); - engine.lastModified = server.getCollection("foo", "clients").timestamp; - await engine._sync(); + await syncClientsEngine(server); deepEqual(collection.keys().sort(), [activeID, engine.localID].sort(), "Should not reupload deleted clients"); @@ -1402,8 +1392,7 @@ add_task(async function test_send_uri_ack() { let fakeSenderID = Utils.makeGUID(); _("Initial sync for empty clients collection"); - engine.lastModified = server.getCollection("foo", "clients").timestamp; - await engine._sync(); + await syncClientsEngine(server); let collection = server.getCollection("foo", "clients"); let ourPayload = JSON.parse(JSON.parse(collection.payload(engine.localID)).ciphertext); ok(ourPayload, "Should upload our client record"); @@ -1417,8 +1406,7 @@ add_task(async function test_send_uri_ack() { server.insertWBO("foo", "clients", new ServerWBO(engine.localID, encryptPayload(ourPayload), now)); _("Sync again"); - engine.lastModified = server.getCollection("foo", "clients").timestamp; - await engine._sync(); + await syncClientsEngine(server); compareCommands(engine.localCommands, [{ command: "displayURI", args: ["https://example.com", fakeSenderID, "Yak Herders Anonymous"], @@ -1431,7 +1419,7 @@ add_task(async function test_send_uri_ack() { }], "Should mark the commands as cleared after processing"); _("Check that the command was removed on the server"); - await engine._sync(); + await syncClientsEngine(server); ourPayload = JSON.parse(JSON.parse(collection.payload(engine.localID)).ciphertext); ok(ourPayload, "Should upload the synced client record"); deepEqual(ourPayload.commands, [], "Should not reupload cleared commands"); @@ -1481,8 +1469,7 @@ add_task(async function test_command_sync() { try { equal(collection.count(), 2, "2 remote records written"); - engine.lastModified = server.getCollection("foo", "clients").timestamp; - await engine._sync(); + await syncClientsEngine(server); equal(collection.count(), 3, "3 remote records written (+1 for the synced local record)"); await engine.sendCommand("wipeAll", []); @@ -1492,7 +1479,7 @@ add_task(async function test_command_sync() { let _notifyCollectionChanged = engineMock.expects("_notifyCollectionChanged") .withArgs(["fxa-" + remoteId, "fxa-" + remoteId2]); _("Syncing."); - await engine._sync(); + await syncClientsEngine(server); _notifyCollectionChanged.verify(); engineMock.restore(); @@ -1545,10 +1532,9 @@ add_task(async function ensureSameFlowIDs() { protocols: ["1.5"] }), Date.now() / 1000)); - engine.lastModified = server.getCollection("foo", "clients").timestamp; - await engine._sync(); + await syncClientsEngine(server); await engine.sendCommand("wipeAll", []); - await engine._sync(); + await syncClientsEngine(server); equal(events.length, 2); // we don't know what the flowID is, but do know it should be the same. equal(events[0].extra.flowID, events[1].extra.flowID); @@ -1560,7 +1546,7 @@ add_task(async function ensureSameFlowIDs() { events.length = 0; let flowID = Utils.makeGUID(); await engine.sendCommand("wipeAll", [], null, { flowID }); - await engine._sync(); + await syncClientsEngine(server); equal(events.length, 2); equal(events[0].extra.flowID, flowID); equal(events[1].extra.flowID, flowID); @@ -1573,7 +1559,7 @@ add_task(async function ensureSameFlowIDs() { // and that it works when something else is in "extra" events.length = 0; await engine.sendCommand("wipeAll", [], null, { reason: "testing" }); - await engine._sync(); + await syncClientsEngine(server); equal(events.length, 2); equal(events[0].extra.flowID, events[1].extra.flowID); equal(events[0].extra.reason, "testing"); @@ -1586,7 +1572,7 @@ add_task(async function ensureSameFlowIDs() { // and when both are specified. events.length = 0; await engine.sendCommand("wipeAll", [], null, { reason: "testing", flowID }); - await engine._sync(); + await syncClientsEngine(server); equal(events.length, 2); equal(events[0].extra.flowID, flowID); equal(events[1].extra.flowID, flowID); @@ -1638,12 +1624,12 @@ add_task(async function test_duplicate_commands_telemetry() { protocols: ["1.5"] }), Date.now() / 1000)); - await engine._sync(); + await syncClientsEngine(server); // Make sure deduping works before syncing await engine.sendURIToClientForDisplay("https://example.com", remoteId, "Example"); await engine.sendURIToClientForDisplay("https://example.com", remoteId, "Example"); equal(events.length, 1); - await engine._sync(); + await syncClientsEngine(server); // And after syncing. await engine.sendURIToClientForDisplay("https://example.com", remoteId, "Example"); equal(events.length, 1); @@ -1680,12 +1666,11 @@ add_task(async function test_other_clients_notified_on_first_sync() { try { engine.lastRecordUpload = 0; _("First sync, should notify other clients"); - engine.lastModified = server.getCollection("foo", "clients").timestamp; - await engine._sync(); + await syncClientsEngine(server); equal(calls, 1); _("Second sync, should not notify other clients"); - await engine._sync(); + await syncClientsEngine(server); equal(calls, 1); } finally { engine.fxAccounts = fxAccounts; diff --git a/servo/Cargo.lock b/servo/Cargo.lock index 5084c077c5c7..3551e3bf1866 100644 --- a/servo/Cargo.lock +++ b/servo/Cargo.lock @@ -3532,7 +3532,7 @@ dependencies = [ [[package]] name = "webrender" version = "0.49.0" -source = "git+https://github.com/servo/webrender#1007a65c6dd1fdfb8b39d57d7faff3cae7b32e0c" +source = "git+https://github.com/servo/webrender#95a4ba0fb673091f7258d929bd2091e629f1c475" dependencies = [ "app_units 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", "bincode 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -3560,7 +3560,7 @@ dependencies = [ [[package]] name = "webrender_api" version = "0.49.0" -source = "git+https://github.com/servo/webrender#1007a65c6dd1fdfb8b39d57d7faff3cae7b32e0c" +source = "git+https://github.com/servo/webrender#95a4ba0fb673091f7258d929bd2091e629f1c475" dependencies = [ "app_units 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", "bincode 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/servo/components/compositing/compositor.rs b/servo/components/compositing/compositor.rs index 89d2fff0c81f..8ff38e772c37 100644 --- a/servo/components/compositing/compositor.rs +++ b/servo/components/compositing/compositor.rs @@ -812,9 +812,9 @@ impl IOCompositor { WindowEvent::ToggleWebRenderDebug(option) => { let mut flags = self.webrender.get_debug_flags(); let flag = match option { - WebRenderDebugOption::Profiler => webrender::renderer::PROFILER_DBG, - WebRenderDebugOption::TextureCacheDebug => webrender::renderer::TEXTURE_CACHE_DBG, - WebRenderDebugOption::RenderTargetDebug => webrender::renderer::RENDER_TARGET_DBG, + WebRenderDebugOption::Profiler => webrender::PROFILER_DBG, + WebRenderDebugOption::TextureCacheDebug => webrender::TEXTURE_CACHE_DBG, + WebRenderDebugOption::RenderTargetDebug => webrender::RENDER_TARGET_DBG, }; flags.toggle(flag); self.webrender.set_debug_flags(flags); diff --git a/servo/components/script/dom/performance.rs b/servo/components/script/dom/performance.rs index bb2c5885bcb0..fdcbfd4a8135 100644 --- a/servo/components/script/dom/performance.rs +++ b/servo/components/script/dom/performance.rs @@ -18,6 +18,7 @@ use dom::window::Window; use dom_struct::dom_struct; use script_thread::{Runnable, ScriptThread}; use std::cell::Cell; +use std::cmp::Ordering; use time; /// Implementation of a list of PerformanceEntry items shared by the @@ -51,6 +52,16 @@ impl PerformanceEntryList { entry_type.as_ref().map_or(true, |type_| *e.entry_type() == *type_) ).map(|e| e.clone()).collect() } + + pub fn get_entries_by_name_and_type(&self, name: Option, entry_type: Option) + -> Vec> { + let mut res = self.entries.iter().filter(|e| + name.as_ref().map_or(true, |name_| *e.name() == *name_) && + entry_type.as_ref().map_or(true, |type_| *e.entry_type() == *type_) + ).map(|e| e.clone()).collect::>>(); + res.sort_by(|a, b| a.start_time().partial_cmp(&b.start_time()).unwrap_or(Ordering::Equal)); + res + } } impl IntoIterator for PerformanceEntryList { @@ -106,7 +117,17 @@ impl Performance { /// observed entry types. pub fn add_observer(&self, observer: &DOMPerformanceObserver, - entry_types: Vec) { + entry_types: Vec, + buffered: bool) { + if buffered { + let entries = self.entries.borrow(); + let mut new_entries = entry_types.iter() + .flat_map(|e| entries.get_entries_by_name_and_type(None, Some(e.clone()))) + .collect::(); + let mut obs_entries = observer.entries(); + obs_entries.append(&mut new_entries); + observer.set_entries(obs_entries); + } let mut observers = self.observers.borrow_mut(); match observers.iter().position(|o| &(*o.observer) == observer) { // If the observer is already in the list, we only update the observed diff --git a/servo/components/script/dom/performanceentry.rs b/servo/components/script/dom/performanceentry.rs index 2c6fef87db01..3242d5d5abd7 100644 --- a/servo/components/script/dom/performanceentry.rs +++ b/servo/components/script/dom/performanceentry.rs @@ -51,6 +51,10 @@ impl PerformanceEntry { pub fn name(&self) -> &DOMString { &self.name } + + pub fn start_time(&self) -> f64 { + self.start_time + } } impl PerformanceEntryMethods for PerformanceEntry { diff --git a/servo/components/script/dom/performanceobserver.rs b/servo/components/script/dom/performanceobserver.rs index 087cb44cd096..56727177a326 100644 --- a/servo/components/script/dom/performanceobserver.rs +++ b/servo/components/script/dom/performanceobserver.rs @@ -89,23 +89,30 @@ impl PerformanceObserver { pub fn entries(&self) -> DOMPerformanceEntryList { self.entries.borrow().clone() } + + pub fn set_entries(&self, entries: DOMPerformanceEntryList) { + *self.entries.borrow_mut() = entries; + } } impl PerformanceObserverMethods for PerformanceObserver { // https://w3c.github.io/performance-timeline/#dom-performanceobserver-observe() fn Observe(&self, options: &PerformanceObserverInit) -> Fallible<()> { + // step 1 // Make sure the client is asking to observe events from allowed entry types. let entry_types = options.entryTypes.iter() .filter(|e| VALID_ENTRY_TYPES.contains(&e.as_ref())) .map(|e| e.clone()) .collect::>(); + // step 2 // There must be at least one valid entry type. if entry_types.is_empty() { return Err((Error::Type("entryTypes cannot be empty".to_string()))); } let performance = self.global().as_window().Performance(); - performance.add_observer(self, entry_types); + // step 3-4-5 + performance.add_observer(self, entry_types, options.buffered); Ok(()) } diff --git a/servo/components/script/dom/webidls/PerformanceObserver.webidl b/servo/components/script/dom/webidls/PerformanceObserver.webidl index a67c52e2730c..4468352e4a91 100644 --- a/servo/components/script/dom/webidls/PerformanceObserver.webidl +++ b/servo/components/script/dom/webidls/PerformanceObserver.webidl @@ -8,6 +8,7 @@ dictionary PerformanceObserverInit { required sequence entryTypes; + boolean buffered = false; }; callback PerformanceObserverCallback = void (PerformanceObserverEntryList entries, PerformanceObserver observer); diff --git a/servo/components/servo/lib.rs b/servo/components/servo/lib.rs index 65244d68b2d6..35cfcef64c62 100644 --- a/servo/components/servo/lib.rs +++ b/servo/components/servo/lib.rs @@ -99,7 +99,7 @@ use std::cmp::max; use std::path::PathBuf; use std::rc::Rc; use std::sync::mpsc::{Sender, channel}; -use webrender::renderer::RendererKind; +use webrender::RendererKind; use webvr::{WebVRThread, WebVRCompositorHandler}; pub use gleam::gl; @@ -176,8 +176,8 @@ impl Servo where Window: WindowMethods + 'static { None }; - let mut debug_flags = webrender::renderer::DebugFlags::empty(); - debug_flags.set(webrender::renderer::PROFILER_DBG, opts.webrender_stats); + let mut debug_flags = webrender::DebugFlags::empty(); + debug_flags.set(webrender::PROFILER_DBG, opts.webrender_stats); webrender::Renderer::new(window.gl(), webrender::RendererOptions { device_pixel_ratio: device_pixel_ratio, diff --git a/servo/components/style/properties/helpers/animated_properties.mako.rs b/servo/components/style/properties/helpers/animated_properties.mako.rs index 2f7aaafc63dd..e3c831c8bc9b 100644 --- a/servo/components/style/properties/helpers/animated_properties.mako.rs +++ b/servo/components/style/properties/helpers/animated_properties.mako.rs @@ -2623,13 +2623,7 @@ where (&SVGLength::Length(ref this), &SVGLength::Length(ref other)) => { Ok(SVGLength::Length(this.animate(other, procedure)?)) }, - _ => { - // FIXME(nox): Is this correct for addition and accumulation? - // I think an error should be returned if it's not - // an interpolation. - let (this_weight, other_weight) = procedure.weights(); - Ok(if this_weight > other_weight { self.clone() } else { other.clone() }) - }, + _ => Err(()), } } } @@ -2649,10 +2643,7 @@ where (&SVGStrokeDashArray::Values(ref this), &SVGStrokeDashArray::Values(ref other)) => { Ok(SVGStrokeDashArray::Values(this.animate(other, procedure)?)) }, - _ => { - let (this_weight, other_weight) = procedure.weights(); - Ok(if this_weight > other_weight { self.clone() } else { other.clone() }) - }, + _ => Err(()), } } } @@ -2684,13 +2675,7 @@ where (&SVGOpacity::Opacity(ref this), &SVGOpacity::Opacity(ref other)) => { Ok(SVGOpacity::Opacity(this.animate(other, procedure)?)) }, - _ => { - // FIXME(nox): Is this correct for addition and accumulation? - // I think an error should be returned if it's not - // an interpolation. - let (this_weight, other_weight) = procedure.weights(); - Ok(if this_weight > other_weight { self.clone() } else { other.clone() }) - }, + _ => Err(()), } } } diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_experiments.js b/toolkit/components/extensions/test/xpcshell/test_ext_experiments.js index 810a0eb67415..d4559c06d0e3 100644 --- a/toolkit/components/extensions/test/xpcshell/test_ext_experiments.js +++ b/toolkit/components/extensions/test/xpcshell/test_ext_experiments.js @@ -5,8 +5,10 @@ XPCOMUtils.defineLazyModuleGetter(this, "AddonManager", "resource://gre/modules/AddonManager.jsm"); +AddonTestUtils.init(this); + add_task(async function setup() { - Services.prefs.setBoolPref("extensions.legacy.enabled", true); + AddonTestUtils.overrideCertDB(); await ExtensionTestUtils.startAddonManager(); }); diff --git a/toolkit/crashreporter/tools/symbolstore.py b/toolkit/crashreporter/tools/symbolstore.py index ed98d522d4ec..bff3fb7d216b 100755 --- a/toolkit/crashreporter/tools/symbolstore.py +++ b/toolkit/crashreporter/tools/symbolstore.py @@ -252,6 +252,54 @@ class GitFileInfo(VCSFileInfo): # prevent extra filesystem activity or process launching. vcsFileInfoCache = {} +if platform.system() == 'Windows': + def normpath(path): + ''' + Normalize a path using `GetFinalPathNameByHandleW` to get the + path with all components in the case they exist in on-disk, so + that making links to a case-sensitive server (hg.mozilla.org) works. + + This function also resolves any symlinks in the path. + ''' + # Return the original path if something fails, which can happen for paths that + # don't exist on this system (like paths from the CRT). + result = path + + ctypes.windll.kernel32.SetErrorMode(ctypes.c_uint(1)) + if not isinstance(path, unicode): + path = unicode(path, sys.getfilesystemencoding()) + handle = ctypes.windll.kernel32.CreateFileW(path, + # GENERIC_READ + 0x80000000, + # FILE_SHARE_READ + 1, + None, + # OPEN_EXISTING + 3, + # FILE_FLAG_BACKUP_SEMANTICS + # This is necessary to open + # directory handles. + 0x02000000, + None) + if handle != -1: + size = ctypes.windll.kernel32.GetFinalPathNameByHandleW(handle, + None, + 0, + 0) + buf = ctypes.create_unicode_buffer(size) + if ctypes.windll.kernel32.GetFinalPathNameByHandleW(handle, + buf, + size, + 0) > 0: + # The return value of GetFinalPathNameByHandleW uses the + # '\\?\' prefix. + result = buf.value.encode(sys.getfilesystemencoding())[4:] + ctypes.windll.kernel32.CloseHandle(handle) + return result +else: + # Just use the os.path version otherwise. + normpath = os.path.normpath + def IsInDir(file, dir): # the lower() is to handle win32+vc8, where # the source filenames come out all lowercase, @@ -333,8 +381,9 @@ def make_file_mapping(install_manifests): manifest.populate_registry(reg) for dst, src in reg: if hasattr(src, 'path'): - abs_dest = os.path.normpath(os.path.join(destination, dst)) - file_mapping[abs_dest] = src.path + # Any paths that get compared to source file names need to go through normpath. + abs_dest = normpath(os.path.join(destination, dst)) + file_mapping[abs_dest] = normpath(src.path) return file_mapping @memoize @@ -402,7 +451,8 @@ class Dumper: self.archs = [''] else: self.archs = ['-a %s' % a for a in archs.split()] - self.srcdirs = [os.path.normpath(self.FixFilenameCase(a)) for a in srcdirs] + # Any paths that get compared to source file names need to go through normpath. + self.srcdirs = [normpath(s) for s in srcdirs] self.copy_debug = copy_debug self.vcsinfo = vcsinfo self.srcsrv = srcsrv @@ -437,10 +487,6 @@ class Dumper: except: return "" - # This is a no-op except on Win32 - def FixFilenameCase(self, file): - return file - # This is a no-op except on Win32 def SourceServerIndexing(self, debug_file, guid, sourceFileStream, vcs_root): return "" @@ -509,7 +555,7 @@ class Dumper: if line.startswith("FILE"): # FILE index filename (x, index, filename) = line.rstrip().split(None, 2) - filename = os.path.normpath(self.FixFilenameCase(filename)) + filename = normpath(filename) # We want original file paths for the source server. sourcepath = filename if filename in self.file_mapping: @@ -581,51 +627,6 @@ class Dumper_Win32(Dumper): return True return False - def FixFilenameCase(self, file): - """Recent versions of Visual C++ put filenames into - PDB files as all lowercase. If the file exists - on the local filesystem, fix it.""" - - # Use a cached version if we have one. - if file in self.fixedFilenameCaseCache: - return self.fixedFilenameCaseCache[file] - - result = file - - ctypes.windll.kernel32.SetErrorMode(ctypes.c_uint(1)) - if not isinstance(file, unicode): - file = unicode(file, sys.getfilesystemencoding()) - handle = ctypes.windll.kernel32.CreateFileW(file, - # GENERIC_READ - 0x80000000, - # FILE_SHARE_READ - 1, - None, - # OPEN_EXISTING - 3, - # FILE_FLAG_BACKUP_SEMANTICS - # This is necessary to open - # directory handles. - 0x02000000, - None) - if handle != -1: - size = ctypes.windll.kernel32.GetFinalPathNameByHandleW(handle, - None, - 0, - 0) - buf = ctypes.create_unicode_buffer(size) - if ctypes.windll.kernel32.GetFinalPathNameByHandleW(handle, - buf, - size, - 0) > 0: - # The return value of GetFinalPathNameByHandleW uses the - # '\\?\' prefix. - result = buf.value.encode(sys.getfilesystemencoding())[4:] - ctypes.windll.kernel32.CloseHandle(handle) - - # Cache the corrected version to avoid future filesystem hits. - self.fixedFilenameCaseCache[file] = result - return result def CopyDebug(self, file, debug_file, guid, code_file, code_id): file = "%s.pdb" % os.path.splitext(file)[0] @@ -865,8 +866,9 @@ to canonical locations in the source repository. Specify parser.error(str(e)) exit(1) file_mapping = make_file_mapping(manifests) - generated_files = {os.path.join(buildconfig.topobjdir, f): f - for (f, _) in get_generated_sources()} + # Any paths that get compared to source file names need to go through normpath. + generated_files = {normpath(os.path.join(buildconfig.topobjdir, f)): f + for (f, _) in get_generated_sources()} _, bucket = get_s3_region_and_bucket() dumper = GetPlatformSpecificDumper(dump_syms=args[0], symbol_path=args[1], diff --git a/toolkit/crashreporter/tools/unit-symbolstore.py b/toolkit/crashreporter/tools/unit-symbolstore.py index 3461456861d3..d5271f327fa6 100644 --- a/toolkit/crashreporter/tools/unit-symbolstore.py +++ b/toolkit/crashreporter/tools/unit-symbolstore.py @@ -21,6 +21,7 @@ from mozpack.manifests import InstallManifest import mozpack.path as mozpath import symbolstore +from symbolstore import normpath # Some simple functions to mock out files that the platform-specific dumpers will accept. # dump_syms itself will not be run (we mock that call out), but we can't override @@ -79,6 +80,11 @@ class HelperMixin(object): if d and not os.path.exists(d): os.makedirs(d) + def make_file(self, path): + self.make_dirs(path) + with open(path, 'wb') as f: + pass + def add_test_files(self, files): for f in files: f = os.path.join(self.test_dir, f) @@ -163,7 +169,6 @@ class TestCopyDebug(HelperMixin, unittest.TestCase): d = symbolstore.Dumper_Win32(dump_syms='dump_syms', symbol_path=self.symbol_dir, copy_debug=True) - d.FixFilenameCase = lambda f: f d.Process(test_file) self.assertTrue(os.path.isfile(os.path.join(self.symbol_dir, code_file, code_id, code_file[:-1] + '_'))) @@ -237,15 +242,13 @@ class TestGeneratedFilePath(HelperMixin, unittest.TestCase): if target_platform() == 'WINNT': - class TestFixFilenameCase(HelperMixin, unittest.TestCase): - def test_fix_filename_case(self): + class TestNormpath(HelperMixin, unittest.TestCase): + def test_normpath(self): # self.test_dir is going to be 8.3 paths... junk = os.path.join(self.test_dir, 'x') with open(junk, 'wb') as o: o.write('x') - d = symbolstore.Dumper_Win32(dump_syms='dump_syms', - symbol_path=self.test_dir) - fixed_dir = os.path.dirname(d.FixFilenameCase(junk)) + fixed_dir = os.path.dirname(normpath(junk)) files = [ 'one\\two.c', 'three\\Four.d', @@ -258,7 +261,7 @@ if target_platform() == 'WINNT': self.make_dirs(full_path) with open(full_path, 'wb') as o: o.write('x') - fixed_path = d.FixFilenameCase(full_path.lower()) + fixed_path = normpath(full_path.lower()) fixed_path = os.path.relpath(fixed_path, fixed_dir) self.assertEqual(rel_path, fixed_path) @@ -323,8 +326,8 @@ class TestInstallManifest(HelperMixin, unittest.TestCase): self.manifest = InstallManifest() self.canonical_mapping = {} for s in ['src1', 'src2']: - srcfile = os.path.join(self.srcdir, s) - objfile = os.path.join(self.objdir, s) + srcfile = normpath(os.path.join(self.srcdir, s)) + objfile = normpath(os.path.join(self.objdir, s)) self.canonical_mapping[objfile] = srcfile self.manifest.add_copy(srcfile, s) self.manifest_file = os.path.join(self.test_dir, 'install-manifest') @@ -400,10 +403,14 @@ class TestFileMapping(HelperMixin, unittest.TestCase): file_mapping = {} dumped_files = [] expected_files = [] + self.make_dirs(os.path.join(self.objdir, 'x', 'y')) for s, o in files: srcfile = os.path.join(self.srcdir, s) - expected_files.append(srcfile) - file_mapping[os.path.join(self.objdir, o)] = srcfile + self.make_file(srcfile) + expected_files.append(normpath(srcfile)) + objfile = os.path.join(self.objdir, o) + self.make_file(objfile) + file_mapping[normpath(objfile)] = normpath(srcfile) dumped_files.append(os.path.join(self.objdir, 'x', 'y', '..', '..', o)) # mock the dump_syms output @@ -479,10 +486,15 @@ class TestFunctional(HelperMixin, unittest.TestCase): def testSymbolstore(self): if self.skip_test: raise unittest.SkipTest('Skipping test in non-Firefox product') + dist_include_manifest = os.path.join(buildconfig.topobjdir, + '_build_manifests/install/dist_include') + dist_include = os.path.join(buildconfig.topobjdir, 'dist/include') output = subprocess.check_output([sys.executable, self.script_path, '--vcs-info', '-s', self.topsrcdir, + '--install-manifest=%s,%s' % (dist_include_manifest, + dist_include), self.dump_syms, self.test_dir, self.target_bin], @@ -493,14 +505,23 @@ class TestFunctional(HelperMixin, unittest.TestCase): symbol_file = os.path.join(self.test_dir, lines[0]) self.assertTrue(os.path.isfile(symbol_file)) symlines = open(symbol_file, 'r').readlines() - file_lines = filter(lambda x: x.startswith('FILE') and 'nsBrowserApp.cpp' in x, symlines) - self.assertTrue(len(file_lines) >= 1, - 'should have nsBrowserApp.cpp FILE line') - filename = file_lines[0].split(None, 2)[2] - - # Skip this check for local git repositories. - if os.path.isdir(mozpath.join(self.topsrcdir, '.hg')): - self.assertEqual('hg:', filename[:3]) + file_lines = [l for l in symlines if l.startswith('FILE')] + def check_hg_path(lines, match): + match_lines = [l for l in file_lines if match in l] + self.assertTrue(len(match_lines) >= 1, + 'should have a FILE line for ' + match) + # Skip this check for local git repositories. + if not os.path.isdir(mozpath.join(self.topsrcdir, '.hg')): + return + for line in match_lines: + filename = line.split(None, 2)[2] + self.assertEqual('hg:', filename[:3]) + # Check that nsBrowserApp.cpp is listed as a FILE line, and that + # it was properly mapped to the source repo. + check_hg_path(file_lines, 'nsBrowserApp.cpp') + # Also check Assertions.h to verify that files from dist/include + # are properly mapped. + check_hg_path(file_lines, 'mfbt/Assertions.h') if __name__ == '__main__': diff --git a/toolkit/mozapps/extensions/internal/XPIProvider.jsm b/toolkit/mozapps/extensions/internal/XPIProvider.jsm index b6614699605b..53be26e488e5 100644 --- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm +++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm @@ -96,8 +96,6 @@ const PREF_ALLOW_NON_MPC = "extensions.allow-non-mpc-extensions"; const PREF_EM_MIN_COMPAT_APP_VERSION = "extensions.minCompatibleAppVersion"; const PREF_EM_MIN_COMPAT_PLATFORM_VERSION = "extensions.minCompatiblePlatformVersion"; -const PREF_CHECKCOMAT_THEMEOVERRIDE = "extensions.checkCompatibility.temporaryThemeOverride_minAppVersion"; - const PREF_EM_HOTFIX_ID = "extensions.hotfix.id"; const OBSOLETE_PREFERENCES = [ @@ -842,20 +840,6 @@ function isUsableAddon(aAddon) { logger.warn(`Add-on ${aAddon.id} is not compatible with target application.`); return false; } - - // XXX Temporary solution to let applications opt-in to make themes safer - // following significant UI changes even if checkCompatibility=false has - // been set, until we get bug 962001. - if (aAddon.type == "theme" && app.id == Services.appinfo.ID) { - try { - let minCompatVersion = Services.prefs.getCharPref(PREF_CHECKCOMAT_THEMEOVERRIDE); - if (minCompatVersion && - Services.vc.compare(minCompatVersion, app.maxVersion) > 0) { - logger.warn(`Theme ${aAddon.id} is not compatible with application version.`); - return false; - } - } catch (e) {} - } } return true; diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_badschema.js b/toolkit/mozapps/extensions/test/xpcshell/test_badschema.js index a336f0273877..36b7f8a9ee2f 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_badschema.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_badschema.js @@ -119,15 +119,17 @@ var theme1 = { // The selected theme var theme2 = { - id: "theme2@tests.mozilla.org", - version: "1.0", - name: "Theme 2", - internalName: "test/1.0", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "2", - maxVersion: "2" - }] + manifest: { + manifest_version: 2, + name: "Theme 2", + version: "1.0", + theme: { images: { headerURL: "example.png" } }, + applications: { + gecko: { + id: "theme2@tests.mozilla.org", + } + } + }, }; const profileDir = gProfD.clone(); @@ -145,34 +147,35 @@ function run_test() { writeInstallRDFForExtension(addon6, profileDir); writeInstallRDFForExtension(addon7, profileDir); writeInstallRDFForExtension(theme1, profileDir); - writeInstallRDFForExtension(theme2, profileDir); + let theme2XPI = createTempWebExtensionFile(theme2); + AddonTestUtils.manuallyInstall(theme2XPI).then(() => { + // Create and configure the HTTP server. + testserver.registerDirectory("/addons/", do_get_file("addons")); - // Create and configure the HTTP server. - testserver.registerDirectory("/addons/", do_get_file("addons")); + // Startup the profile and setup the initial state + startupManager(); - // Startup the profile and setup the initial state - startupManager(); - - AddonManager.getAddonsByIDs(["addon2@tests.mozilla.org", - "addon3@tests.mozilla.org", - "addon4@tests.mozilla.org", - "addon7@tests.mozilla.org", - "theme2@tests.mozilla.org"], function([a2, a3, a4, - a7, t2]) { - // Set up the initial state - a2.userDisabled = true; - a4.userDisabled = true; - a7.userDisabled = true; - t2.userDisabled = false; - a3.findUpdates({ - onUpdateFinished() { - a4.findUpdates({ - onUpdateFinished() { - do_execute_soon(run_test_1); - } - }, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE); - } - }, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE); + AddonManager.getAddonsByIDs(["addon2@tests.mozilla.org", + "addon3@tests.mozilla.org", + "addon4@tests.mozilla.org", + "addon7@tests.mozilla.org", + "theme2@tests.mozilla.org"], function([a2, a3, a4, + a7, t2]) { + // Set up the initial state + a2.userDisabled = true; + a4.userDisabled = true; + a7.userDisabled = true; + t2.userDisabled = false; + a3.findUpdates({ + onUpdateFinished() { + a4.findUpdates({ + onUpdateFinished() { + do_execute_soon(run_test_1); + } + }, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE); + } + }, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE); + }); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_blocklistchange.js b/toolkit/mozapps/extensions/test/xpcshell/test_blocklistchange.js index 77793dfe49fa..08b33e0ee20c 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_blocklistchange.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_blocklistchange.js @@ -23,10 +23,6 @@ // set to true and stay userDisabled. This add-on is not used in tests that // start with add-ons blocked as it would be identical to softblock3 -// softblock5 is a theme. Currently themes just get disabled when they become -// softblocked and have to be manually re-enabled if they become completely -// unblocked (bug 657520) - var Cc = Components.classes; var Ci = Components.interfaces; var Cu = Components.utils; @@ -56,18 +52,6 @@ mapFile("/data/blocklistchange/manual_update.xml", testserver); testserver.registerDirectory("/addons/", do_get_file("addons")); -var default_theme = { - id: "default@tests.mozilla.org", - version: "1.0", - name: "Softblocked add-on", - internalName: "classic/1.0", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "3" - }] -}; - var softblock1_1 = { id: "softblock1@tests.mozilla.org", version: "1.0", @@ -212,45 +196,6 @@ var softblock4_3 = { }] }; -var softblock5_1 = { - id: "softblock5@tests.mozilla.org", - version: "1.0", - name: "Softblocked add-on", - updateURL: "http://localhost:" + gPort + "/data/blocklistchange/addon_update1.rdf", - internalName: "test/1.0", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "3" - }] -}; - -var softblock5_2 = { - id: "softblock5@tests.mozilla.org", - version: "2.0", - name: "Softblocked add-on", - updateURL: "http://localhost:" + gPort + "/data/blocklistchange/addon_update2.rdf", - internalName: "test/1.0", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "3" - }] -}; - -var softblock5_3 = { - id: "softblock5@tests.mozilla.org", - version: "3.0", - name: "Softblocked add-on", - updateURL: "http://localhost:" + gPort + "/data/blocklistchange/addon_update3.rdf", - internalName: "test/1.0", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "3" - }] -}; - var hardblock_1 = { id: "hardblock@tests.mozilla.org", version: "1.0", @@ -327,7 +272,6 @@ const ADDON_IDS = ["softblock1@tests.mozilla.org", "softblock2@tests.mozilla.org", "softblock3@tests.mozilla.org", "softblock4@tests.mozilla.org", - "softblock5@tests.mozilla.org", "hardblock@tests.mozilla.org", "regexpblock@tests.mozilla.org"]; @@ -458,7 +402,7 @@ function Pbackground_update() { // Manually updates the test add-ons to the given version function Pmanual_update(aVersion) { let Pinstalls = []; - for (let name of ["soft1", "soft2", "soft3", "soft4", "soft5", "hard1", "regexp1"]) { + for (let name of ["soft1", "soft2", "soft3", "soft4", "hard1", "regexp1"]) { Pinstalls.push( AddonManager.getInstallForURL( `http://localhost:${gPort}/addons/blocklist_${name}_${aVersion}.xpi`, @@ -536,19 +480,16 @@ function run_test() { } add_task(async function init() { - writeInstallRDFForExtension(default_theme, profileDir); writeInstallRDFForExtension(softblock1_1, profileDir); writeInstallRDFForExtension(softblock2_1, profileDir); writeInstallRDFForExtension(softblock3_1, profileDir); writeInstallRDFForExtension(softblock4_1, profileDir); - writeInstallRDFForExtension(softblock5_1, profileDir); writeInstallRDFForExtension(hardblock_1, profileDir); writeInstallRDFForExtension(regexpblock_1, profileDir); startupManager(); - let [/* s1 */, /* s2 */, /* s3 */, s4, s5, /* h, r */] = await promiseAddonsByIDs(ADDON_IDS); + let [/* s1 */, /* s2 */, /* s3 */, s4, /* h, r */] = await promiseAddonsByIDs(ADDON_IDS); s4.userDisabled = true; - s5.userDisabled = false; }); // Starts with add-ons unblocked and then switches application versions to @@ -558,31 +499,27 @@ add_task(async function run_app_update_test() { await Pload_blocklist("app_update.xml"); await promiseRestartManager(); - let [s1, s2, s3, s4, s5, h, r] = await promiseAddonsByIDs(ADDON_IDS); + let [s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS); check_addon(s1, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); check_addon(s2, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); check_addon(s3, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); check_addon(s4, "1.0", true, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); - check_addon(s5, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); check_addon(h, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); check_addon(r, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); - do_check_eq(Services.prefs.getCharPref("general.skins.selectedSkin"), "test/1.0"); }); add_task(async function app_update_step_2() { await promiseRestartManager("2"); - let [s1, s2, s3, s4, s5, h, r] = await promiseAddonsByIDs(ADDON_IDS); + let [s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS); check_addon(s1, "1.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); check_addon(s2, "1.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); check_addon(s3, "1.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); check_addon(s4, "1.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); - check_addon(s5, "1.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); check_addon(h, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED); check_addon(r, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED); - do_check_eq(Services.prefs.getCharPref("general.skins.selectedSkin"), "classic/1.0"); s2.userDisabled = false; s2.userDisabled = true; @@ -596,35 +533,30 @@ add_task(async function app_update_step_3() { await promiseRestartManager("2.5"); - let [s1, s2, s3, s4, s5, h, r] = await promiseAddonsByIDs(ADDON_IDS); + let [s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS); check_addon(s1, "1.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); check_addon(s2, "1.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); check_addon(s3, "1.0", false, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); check_addon(s4, "1.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); - check_addon(s5, "1.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); check_addon(h, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED); check_addon(r, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED); - do_check_eq(Services.prefs.getCharPref("general.skins.selectedSkin"), "classic/1.0"); }); add_task(async function app_update_step_4() { await promiseRestartManager("1"); - let [s1, s2, s3, s4, s5, h, r] = await promiseAddonsByIDs(ADDON_IDS); + let [s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS); check_addon(s1, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); check_addon(s2, "1.0", true, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); check_addon(s3, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); check_addon(s4, "1.0", true, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); - check_addon(s5, "1.0", true, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); check_addon(h, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); check_addon(r, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); - do_check_eq(Services.prefs.getCharPref("general.skins.selectedSkin"), "classic/1.0"); s1.userDisabled = false; s2.userDisabled = false; - s5.userDisabled = false; }); // Starts with add-ons unblocked and then switches application versions to @@ -633,16 +565,14 @@ add_task(async function app_update_step_4() { add_task(async function run_app_update_schema_test() { await promiseRestartManager(); - let [s1, s2, s3, s4, s5, h, r] = await promiseAddonsByIDs(ADDON_IDS); + let [s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS); check_addon(s1, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); check_addon(s2, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); check_addon(s3, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); check_addon(s4, "1.0", true, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); - check_addon(s5, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); check_addon(h, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); check_addon(r, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); - do_check_eq(Services.prefs.getCharPref("general.skins.selectedSkin"), "test/1.0"); }); add_task(async function update_schema_2() { @@ -652,16 +582,14 @@ add_task(async function update_schema_2() { gAppInfo.version = "2"; startupManager(true); - let [s1, s2, s3, s4, s5, h, r] = await promiseAddonsByIDs(ADDON_IDS); + let [s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS); check_addon(s1, "1.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); check_addon(s2, "1.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); check_addon(s3, "1.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); check_addon(s4, "1.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); - check_addon(s5, "1.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); check_addon(h, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED); check_addon(r, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED); - do_check_eq(Services.prefs.getCharPref("general.skins.selectedSkin"), "classic/1.0"); s2.userDisabled = false; s2.userDisabled = true; @@ -678,16 +606,14 @@ add_task(async function update_schema_3() { gAppInfo.version = "2.5"; startupManager(true); - let [s1, s2, s3, s4, s5, h, r] = await promiseAddonsByIDs(ADDON_IDS); + let [s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS); check_addon(s1, "1.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); check_addon(s2, "1.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); check_addon(s3, "1.0", false, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); check_addon(s4, "1.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); - check_addon(s5, "1.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); check_addon(h, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED); check_addon(r, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED); - do_check_eq(Services.prefs.getCharPref("general.skins.selectedSkin"), "classic/1.0"); }); add_task(async function update_schema_4() { @@ -696,16 +622,14 @@ add_task(async function update_schema_4() { changeXPIDBVersion(100); startupManager(false); - let [s1, s2, s3, s4, s5, h, r] = await promiseAddonsByIDs(ADDON_IDS); + let [s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS); check_addon(s1, "1.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); check_addon(s2, "1.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); check_addon(s3, "1.0", false, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); check_addon(s4, "1.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); - check_addon(s5, "1.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); check_addon(h, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED); check_addon(r, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED); - do_check_eq(Services.prefs.getCharPref("general.skins.selectedSkin"), "classic/1.0"); }); add_task(async function update_schema_5() { @@ -715,20 +639,17 @@ add_task(async function update_schema_5() { gAppInfo.version = "1"; startupManager(true); - let [s1, s2, s3, s4, s5, h, r] = await promiseAddonsByIDs(ADDON_IDS); + let [s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS); check_addon(s1, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); check_addon(s2, "1.0", true, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); check_addon(s3, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); check_addon(s4, "1.0", true, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); - check_addon(s5, "1.0", true, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); check_addon(h, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); check_addon(r, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); - do_check_eq(Services.prefs.getCharPref("general.skins.selectedSkin"), "classic/1.0"); s1.userDisabled = false; s2.userDisabled = false; - s5.userDisabled = false; }); // Starts with add-ons unblocked and then loads new blocklists to change add-ons @@ -737,30 +658,26 @@ add_task(async function run_blocklist_update_test() { await Pload_blocklist("blocklist_update1.xml"); await promiseRestartManager(); - let [s1, s2, s3, s4, s5, h, r] = await promiseAddonsByIDs(ADDON_IDS); + let [s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS); check_addon(s1, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); check_addon(s2, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); check_addon(s3, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); check_addon(s4, "1.0", true, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); - check_addon(s5, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); check_addon(h, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); check_addon(r, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); - do_check_eq(Services.prefs.getCharPref("general.skins.selectedSkin"), "test/1.0"); await Pload_blocklist("blocklist_update2.xml"); await promiseRestartManager(); - [s1, s2, s3, s4, s5, h, r] = await promiseAddonsByIDs(ADDON_IDS); + [s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS); check_addon(s1, "1.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); check_addon(s2, "1.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); check_addon(s3, "1.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); check_addon(s4, "1.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); - check_addon(s5, "1.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); check_addon(h, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED); check_addon(r, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED); - do_check_eq(Services.prefs.getCharPref("general.skins.selectedSkin"), "classic/1.0"); s2.userDisabled = false; s2.userDisabled = true; @@ -773,34 +690,29 @@ add_task(async function run_blocklist_update_test() { await Pload_blocklist("blocklist_update2.xml"); await promiseRestartManager(); - [s1, s2, s3, s4, s5, h, r] = await promiseAddonsByIDs(ADDON_IDS); + [s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS); check_addon(s1, "1.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); check_addon(s2, "1.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); check_addon(s3, "1.0", false, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); check_addon(s4, "1.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); - check_addon(s5, "1.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); check_addon(h, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED); check_addon(r, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED); - do_check_eq(Services.prefs.getCharPref("general.skins.selectedSkin"), "classic/1.0"); await Pload_blocklist("blocklist_update1.xml"); await promiseRestartManager(); - [s1, s2, s3, s4, s5, h, r] = await promiseAddonsByIDs(ADDON_IDS); + [s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS); check_addon(s1, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); check_addon(s2, "1.0", true, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); check_addon(s3, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); check_addon(s4, "1.0", true, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); - check_addon(s5, "1.0", true, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); check_addon(h, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); check_addon(r, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); - do_check_eq(Services.prefs.getCharPref("general.skins.selectedSkin"), "classic/1.0"); s1.userDisabled = false; s2.userDisabled = false; - s5.userDisabled = false; }); // Starts with add-ons unblocked and then new versions are installed outside of @@ -809,16 +721,14 @@ add_task(async function run_addon_change_test() { await Pload_blocklist("addon_change.xml"); await promiseRestartManager(); - let [s1, s2, s3, s4, s5, h, r] = await promiseAddonsByIDs(ADDON_IDS); + let [s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS); check_addon(s1, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); check_addon(s2, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); check_addon(s3, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); check_addon(s4, "1.0", true, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); - check_addon(s5, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); check_addon(h, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); check_addon(r, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); - do_check_eq(Services.prefs.getCharPref("general.skins.selectedSkin"), "test/1.0"); }); add_task(async function run_addon_change_2() { @@ -832,8 +742,6 @@ add_task(async function run_addon_change_2() { setExtensionModifiedTime(getFileForAddon(profileDir, softblock3_2.id), Date.now() + 10000); writeInstallRDFForExtension(softblock4_2, profileDir); setExtensionModifiedTime(getFileForAddon(profileDir, softblock4_2.id), Date.now() + 10000); - writeInstallRDFForExtension(softblock5_2, profileDir); - setExtensionModifiedTime(getFileForAddon(profileDir, softblock5_2.id), Date.now() + 10000); writeInstallRDFForExtension(hardblock_2, profileDir); setExtensionModifiedTime(getFileForAddon(profileDir, hardblock_2.id), Date.now() + 10000); writeInstallRDFForExtension(regexpblock_2, profileDir); @@ -841,16 +749,14 @@ add_task(async function run_addon_change_2() { startupManager(false); - let [s1, s2, s3, s4, s5, h, r] = await promiseAddonsByIDs(ADDON_IDS); + let [s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS); check_addon(s1, "2.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); check_addon(s2, "2.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); check_addon(s3, "2.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); check_addon(s4, "2.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); - check_addon(s5, "2.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); check_addon(h, "2.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED); check_addon(r, "2.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED); - do_check_eq(Services.prefs.getCharPref("general.skins.selectedSkin"), "classic/1.0"); s2.userDisabled = false; s2.userDisabled = true; @@ -872,8 +778,6 @@ add_task(async function run_addon_change_3() { setExtensionModifiedTime(getFileForAddon(profileDir, softblock3_3.id), Date.now() + 20000); writeInstallRDFForExtension(softblock4_3, profileDir); setExtensionModifiedTime(getFileForAddon(profileDir, softblock4_3.id), Date.now() + 20000); - writeInstallRDFForExtension(softblock5_3, profileDir); - setExtensionModifiedTime(getFileForAddon(profileDir, softblock5_3.id), Date.now() + 20000); writeInstallRDFForExtension(hardblock_3, profileDir); setExtensionModifiedTime(getFileForAddon(profileDir, hardblock_3.id), Date.now() + 20000); writeInstallRDFForExtension(regexpblock_3, profileDir); @@ -881,16 +785,14 @@ add_task(async function run_addon_change_3() { startupManager(false); - let [s1, s2, s3, s4, s5, h, r] = await promiseAddonsByIDs(ADDON_IDS); + let [s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS); check_addon(s1, "3.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); check_addon(s2, "3.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); check_addon(s3, "3.0", false, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); check_addon(s4, "3.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); - check_addon(s5, "3.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); check_addon(h, "3.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED); check_addon(r, "3.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED); - do_check_eq(Services.prefs.getCharPref("general.skins.selectedSkin"), "classic/1.0"); }); add_task(async function run_addon_change_4() { @@ -904,8 +806,6 @@ add_task(async function run_addon_change_4() { setExtensionModifiedTime(getFileForAddon(profileDir, softblock3_1.id), Date.now() + 30000); writeInstallRDFForExtension(softblock4_1, profileDir); setExtensionModifiedTime(getFileForAddon(profileDir, softblock4_1.id), Date.now() + 30000); - writeInstallRDFForExtension(softblock5_1, profileDir); - setExtensionModifiedTime(getFileForAddon(profileDir, softblock5_1.id), Date.now() + 30000); writeInstallRDFForExtension(hardblock_1, profileDir); setExtensionModifiedTime(getFileForAddon(profileDir, hardblock_1.id), Date.now() + 30000); writeInstallRDFForExtension(regexpblock_1, profileDir); @@ -913,20 +813,17 @@ add_task(async function run_addon_change_4() { startupManager(false); - let [s1, s2, s3, s4, s5, h, r] = await promiseAddonsByIDs(ADDON_IDS); + let [s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS); check_addon(s1, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); check_addon(s2, "1.0", true, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); check_addon(s3, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); check_addon(s4, "1.0", true, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); - check_addon(s5, "1.0", true, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); check_addon(h, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); check_addon(r, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); - do_check_eq(Services.prefs.getCharPref("general.skins.selectedSkin"), "classic/1.0"); s1.userDisabled = false; s2.userDisabled = false; - s5.userDisabled = false; }); // Starts with add-ons blocked and then new versions are installed outside of @@ -938,7 +835,6 @@ add_task(async function run_addon_change_2_test() { getFileForAddon(profileDir, softblock2_1.id).remove(true); getFileForAddon(profileDir, softblock3_1.id).remove(true); getFileForAddon(profileDir, softblock4_1.id).remove(true); - getFileForAddon(profileDir, softblock5_1.id).remove(true); getFileForAddon(profileDir, hardblock_1.id).remove(true); getFileForAddon(profileDir, regexpblock_1.id).remove(true); @@ -949,13 +845,12 @@ add_task(async function run_addon_change_2_test() { writeInstallRDFForExtension(softblock2_2, profileDir); writeInstallRDFForExtension(softblock3_2, profileDir); writeInstallRDFForExtension(softblock4_2, profileDir); - writeInstallRDFForExtension(softblock5_2, profileDir); writeInstallRDFForExtension(hardblock_2, profileDir); writeInstallRDFForExtension(regexpblock_2, profileDir); startupManager(false); - let [s1, s2, s3, /* s4 */, /* s5 */, h, r] = await promiseAddonsByIDs(ADDON_IDS); + let [s1, s2, s3, /* s4 */, h, r] = await promiseAddonsByIDs(ADDON_IDS); check_addon(s1, "2.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); check_addon(s2, "2.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); @@ -983,8 +878,6 @@ add_task(async function addon_change_2_test_2() { setExtensionModifiedTime(getFileForAddon(profileDir, softblock3_3.id), Date.now() + 10000); writeInstallRDFForExtension(softblock4_3, profileDir); setExtensionModifiedTime(getFileForAddon(profileDir, softblock4_3.id), Date.now() + 10000); - writeInstallRDFForExtension(softblock5_3, profileDir); - setExtensionModifiedTime(getFileForAddon(profileDir, softblock5_3.id), Date.now() + 10000); writeInstallRDFForExtension(hardblock_3, profileDir); setExtensionModifiedTime(getFileForAddon(profileDir, hardblock_3.id), Date.now() + 10000); writeInstallRDFForExtension(regexpblock_3, profileDir); @@ -992,7 +885,7 @@ add_task(async function addon_change_2_test_2() { startupManager(false); - let [s1, s2, s3, /* s4 */, /* s5 */, h, r] = await promiseAddonsByIDs(ADDON_IDS); + let [s1, s2, s3, /* s4 */, h, r] = await promiseAddonsByIDs(ADDON_IDS); check_addon(s1, "3.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); check_addon(s2, "3.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); @@ -1012,8 +905,6 @@ add_task(async function addon_change_2_test_3() { setExtensionModifiedTime(getFileForAddon(profileDir, softblock3_1.id), Date.now() + 20000); writeInstallRDFForExtension(softblock4_1, profileDir); setExtensionModifiedTime(getFileForAddon(profileDir, softblock4_1.id), Date.now() + 20000); - writeInstallRDFForExtension(softblock5_1, profileDir); - setExtensionModifiedTime(getFileForAddon(profileDir, softblock5_1.id), Date.now() + 20000); writeInstallRDFForExtension(hardblock_1, profileDir); setExtensionModifiedTime(getFileForAddon(profileDir, hardblock_1.id), Date.now() + 20000); writeInstallRDFForExtension(regexpblock_1, profileDir); @@ -1021,7 +912,7 @@ add_task(async function addon_change_2_test_3() { startupManager(false); - let [s1, s2, s3, s4, s5, h, r] = await promiseAddonsByIDs(ADDON_IDS); + let [s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS); check_addon(s1, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); check_addon(s2, "1.0", true, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); @@ -1032,7 +923,6 @@ add_task(async function addon_change_2_test_3() { s1.userDisabled = false; s2.userDisabled = false; s4.userDisabled = true; - s5.userDisabled = false; }); // Add-ons are initially unblocked then attempts to upgrade to blocked versions @@ -1040,26 +930,24 @@ add_task(async function addon_change_2_test_3() { add_task(async function run_background_update_test() { await promiseRestartManager(); - let [s1, s2, s3, s4, s5, h, r] = await promiseAddonsByIDs(ADDON_IDS); + let [s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS); check_addon(s1, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); check_addon(s2, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); check_addon(s3, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); check_addon(s4, "1.0", true, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); - check_addon(s5, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); check_addon(h, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); check_addon(r, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); await Pbackground_update(); await promiseRestartManager(); - [s1, s2, s3, s4, s5, h, r] = await promiseAddonsByIDs(ADDON_IDS); + [s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS); check_addon(s1, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); check_addon(s2, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); check_addon(s3, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); check_addon(s4, "1.0", true, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); - check_addon(s5, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); check_addon(h, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); check_addon(r, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); }); @@ -1073,7 +961,6 @@ add_task(async function run_background_update_2_test() { getFileForAddon(profileDir, softblock2_1.id).remove(true); getFileForAddon(profileDir, softblock3_1.id).remove(true); getFileForAddon(profileDir, softblock4_1.id).remove(true); - getFileForAddon(profileDir, softblock5_1.id).remove(true); getFileForAddon(profileDir, hardblock_1.id).remove(true); getFileForAddon(profileDir, regexpblock_1.id).remove(true); @@ -1084,13 +971,12 @@ add_task(async function run_background_update_2_test() { writeInstallRDFForExtension(softblock2_3, profileDir); writeInstallRDFForExtension(softblock3_3, profileDir); writeInstallRDFForExtension(softblock4_3, profileDir); - writeInstallRDFForExtension(softblock5_3, profileDir); writeInstallRDFForExtension(hardblock_3, profileDir); writeInstallRDFForExtension(regexpblock_3, profileDir); startupManager(false); - let [s1, s2, s3, s4, s5, h, r] = await promiseAddonsByIDs(ADDON_IDS); + let [s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS); check_addon(s1, "3.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); check_addon(s2, "3.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); @@ -1109,7 +995,7 @@ add_task(async function run_background_update_2_test() { await Pbackground_update(); await promiseRestartManager(); - [s1, s2, s3, s4, s5, h, r] = await promiseAddonsByIDs(ADDON_IDS); + [s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS); check_addon(s1, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); check_addon(s2, "1.0", true, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); @@ -1120,7 +1006,6 @@ add_task(async function run_background_update_2_test() { s1.userDisabled = false; s2.userDisabled = false; s4.userDisabled = true; - s5.userDisabled = true; }); // Starts with add-ons blocked and then simulates the user upgrading them to @@ -1130,13 +1015,12 @@ add_task(async function run_manual_update_test() { await Pload_blocklist("manual_update.xml"); await promiseRestartManager(); - let [s1, s2, s3, s4, s5, h, r] = await promiseAddonsByIDs(ADDON_IDS); + let [s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS); check_addon(s1, "1.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); check_addon(s2, "1.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); check_addon(s3, "1.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); check_addon(s4, "1.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); - check_addon(s5, "1.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); check_addon(h, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED); check_addon(r, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED); @@ -1151,13 +1035,12 @@ add_task(async function run_manual_update_test() { await Pmanual_update("2"); await promiseRestartManager(); - [s1, s2, s3, s4, s5, h, r] = await promiseAddonsByIDs(ADDON_IDS); + [s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS); check_addon(s1, "2.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); check_addon(s2, "2.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); check_addon(s3, "2.0", false, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); check_addon(s4, "2.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); - check_addon(s5, "2.0", false, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); // Can't manually update to a hardblocked add-on check_addon(h, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED); check_addon(r, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED); @@ -1165,13 +1048,12 @@ add_task(async function run_manual_update_test() { await Pmanual_update("3"); await promiseRestartManager(); - [s1, s2, s3, s4, s5, h, r] = await promiseAddonsByIDs(ADDON_IDS); + [s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS); check_addon(s1, "3.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); check_addon(s2, "3.0", true, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); check_addon(s3, "3.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); check_addon(s4, "3.0", true, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); - check_addon(s5, "3.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); check_addon(h, "3.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); check_addon(r, "3.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); }); @@ -1185,7 +1067,6 @@ add_task(async function run_manual_update_2_test() { getFileForAddon(profileDir, softblock2_1.id).remove(true); getFileForAddon(profileDir, softblock3_1.id).remove(true); getFileForAddon(profileDir, softblock4_1.id).remove(true); - getFileForAddon(profileDir, softblock5_1.id).remove(true); getFileForAddon(profileDir, hardblock_1.id).remove(true); getFileForAddon(profileDir, regexpblock_1.id).remove(true); @@ -1196,13 +1077,12 @@ add_task(async function run_manual_update_2_test() { writeInstallRDFForExtension(softblock2_1, profileDir); writeInstallRDFForExtension(softblock3_1, profileDir); writeInstallRDFForExtension(softblock4_1, profileDir); - writeInstallRDFForExtension(softblock5_1, profileDir); writeInstallRDFForExtension(hardblock_1, profileDir); writeInstallRDFForExtension(regexpblock_1, profileDir); startupManager(false); - let [s1, s2, s3, s4, /* s5 */, h, r] = await promiseAddonsByIDs(ADDON_IDS); + let [s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS); check_addon(s1, "1.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); check_addon(s2, "1.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); @@ -1220,7 +1100,7 @@ add_task(async function run_manual_update_2_test() { await Pmanual_update("2"); await promiseRestartManager(); - [s1, s2, s3, s4, /* s5 */, h, r] = await promiseAddonsByIDs(ADDON_IDS); + [s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS); check_addon(s1, "2.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); check_addon(s2, "2.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); @@ -1234,7 +1114,7 @@ add_task(async function run_manual_update_2_test() { await Pmanual_update("3"); await promiseRestartManager(); - [s1, s2, s3, s4, /* s5 */, h, r] = await promiseAddonsByIDs(ADDON_IDS); + [s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS); check_addon(s1, "3.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); check_addon(s2, "3.0", true, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); @@ -1255,7 +1135,6 @@ add_task(async function run_local_install_test() { getFileForAddon(profileDir, softblock2_1.id).remove(true); getFileForAddon(profileDir, softblock3_1.id).remove(true); getFileForAddon(profileDir, softblock4_1.id).remove(true); - getFileForAddon(profileDir, softblock5_1.id).remove(true); getFileForAddon(profileDir, hardblock_1.id).remove(true); getFileForAddon(profileDir, regexpblock_1.id).remove(true); @@ -1266,7 +1145,6 @@ add_task(async function run_local_install_test() { do_get_file("addons/blocklist_soft2_1.xpi"), do_get_file("addons/blocklist_soft3_1.xpi"), do_get_file("addons/blocklist_soft4_1.xpi"), - do_get_file("addons/blocklist_soft5_1.xpi"), do_get_file("addons/blocklist_hard1_1.xpi"), do_get_file("addons/blocklist_regexp1_1.xpi") ]); @@ -1275,7 +1153,7 @@ add_task(async function run_local_install_test() { // Should have finished all installs without needing to restart do_check_eq(aInstalls.length, 0); - let [s1, s2, s3, /* s4 */, /* s5 */, h, r] = await promiseAddonsByIDs(ADDON_IDS); + let [s1, s2, s3, /* s4 */, h, r] = await promiseAddonsByIDs(ADDON_IDS); check_addon(s1, "1.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); check_addon(s2, "1.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug563256.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug563256.js deleted file mode 100644 index b0dca06b923c..000000000000 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug563256.js +++ /dev/null @@ -1,259 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ - */ - -// This verifies that the themes switch as expected - -const PREF_GENERAL_SKINS_SELECTEDSKIN = "general.skins.selectedSkin"; - -const profileDir = gProfD.clone(); -profileDir.append("extensions"); - -async function run_test() { - do_test_pending(); - createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2"); - - writeInstallRDFForExtension({ - id: "default@tests.mozilla.org", - version: "1.0", - name: "Default", - internalName: "classic/1.0", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "2" - }] - }, profileDir); - - writeInstallRDFForExtension({ - id: "alternate@tests.mozilla.org", - version: "1.0", - name: "Test 1", - type: 4, - internalName: "alternate/1.0", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "2" - }] - }, profileDir); - - await promiseStartupManager(); - - do_check_eq(Services.prefs.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN), "classic/1.0"); - - AddonManager.getAddonsByIDs(["default@tests.mozilla.org", - "alternate@tests.mozilla.org"], function([d, a]) { - do_check_neq(d, null); - do_check_false(d.userDisabled); - do_check_false(d.appDisabled); - do_check_true(d.isActive); - do_check_true(isThemeInAddonsList(profileDir, d.id)); - do_check_false(hasFlag(d.permissions, AddonManager.PERM_CAN_DISABLE)); - do_check_false(hasFlag(d.permissions, AddonManager.PERM_CAN_ENABLE)); - - do_check_neq(a, null); - do_check_true(a.userDisabled); - do_check_false(a.appDisabled); - do_check_false(a.isActive); - do_check_false(isThemeInAddonsList(profileDir, a.id)); - do_check_false(hasFlag(a.permissions, AddonManager.PERM_CAN_DISABLE)); - do_check_true(hasFlag(a.permissions, AddonManager.PERM_CAN_ENABLE)); - - run_test_1(d, a); - }); -} - -function end_test() { - do_execute_soon(do_test_finished); -} - -// Checks switching to a different theme and back again leaves everything the -// same -function run_test_1(d, a) { - a.userDisabled = false; - - do_check_true(d.userDisabled); - do_check_false(d.appDisabled); - do_check_true(d.isActive); - do_check_true(isThemeInAddonsList(profileDir, d.id)); - do_check_false(hasFlag(d.permissions, AddonManager.PERM_CAN_DISABLE)); - do_check_true(hasFlag(d.permissions, AddonManager.PERM_CAN_ENABLE)); - - do_check_false(a.userDisabled); - do_check_false(a.appDisabled); - do_check_false(a.isActive); - do_check_false(isThemeInAddonsList(profileDir, a.id)); - do_check_false(hasFlag(a.permissions, AddonManager.PERM_CAN_DISABLE)); - do_check_false(hasFlag(a.permissions, AddonManager.PERM_CAN_ENABLE)); - - do_check_eq(Services.prefs.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN), "classic/1.0"); - - d.userDisabled = false; - - do_check_false(d.userDisabled); - do_check_false(d.appDisabled); - do_check_true(d.isActive); - do_check_true(isThemeInAddonsList(profileDir, d.id)); - do_check_false(hasFlag(d.permissions, AddonManager.PERM_CAN_DISABLE)); - do_check_false(hasFlag(d.permissions, AddonManager.PERM_CAN_ENABLE)); - - do_check_true(a.userDisabled); - do_check_false(a.appDisabled); - do_check_false(a.isActive); - do_check_false(isThemeInAddonsList(profileDir, a.id)); - do_check_false(hasFlag(a.permissions, AddonManager.PERM_CAN_DISABLE)); - do_check_true(hasFlag(a.permissions, AddonManager.PERM_CAN_ENABLE)); - - do_check_eq(Services.prefs.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN), "classic/1.0"); - - do_execute_soon(run_test_2); -} - -// Tests that after the restart themes can be changed as expected -function run_test_2() { - restartManager(); - AddonManager.getAddonsByIDs(["default@tests.mozilla.org", - "alternate@tests.mozilla.org"], function([d, a]) { - do_check_eq(Services.prefs.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN), "classic/1.0"); - - do_check_neq(d, null); - do_check_false(d.userDisabled); - do_check_false(d.appDisabled); - do_check_true(d.isActive); - do_check_true(isThemeInAddonsList(profileDir, d.id)); - do_check_false(hasFlag(d.permissions, AddonManager.PERM_CAN_DISABLE)); - do_check_false(hasFlag(d.permissions, AddonManager.PERM_CAN_ENABLE)); - - do_check_neq(a, null); - do_check_true(a.userDisabled); - do_check_false(a.appDisabled); - do_check_false(a.isActive); - do_check_false(isThemeInAddonsList(profileDir, a.id)); - do_check_false(hasFlag(a.permissions, AddonManager.PERM_CAN_DISABLE)); - do_check_true(hasFlag(a.permissions, AddonManager.PERM_CAN_ENABLE)); - - do_check_eq(Services.prefs.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN), "classic/1.0"); - - a.userDisabled = false; - - do_check_true(d.userDisabled); - do_check_false(d.appDisabled); - do_check_true(d.isActive); - do_check_true(isThemeInAddonsList(profileDir, d.id)); - do_check_false(hasFlag(d.permissions, AddonManager.PERM_CAN_DISABLE)); - do_check_true(hasFlag(d.permissions, AddonManager.PERM_CAN_ENABLE)); - - do_check_false(a.userDisabled); - do_check_false(a.appDisabled); - do_check_false(a.isActive); - do_check_false(isThemeInAddonsList(profileDir, a.id)); - do_check_false(hasFlag(a.permissions, AddonManager.PERM_CAN_DISABLE)); - do_check_false(hasFlag(a.permissions, AddonManager.PERM_CAN_ENABLE)); - - do_check_eq(Services.prefs.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN), "classic/1.0"); - - d.userDisabled = false; - - do_check_false(d.userDisabled); - do_check_false(d.appDisabled); - do_check_true(d.isActive); - do_check_true(isThemeInAddonsList(profileDir, d.id)); - do_check_false(hasFlag(d.permissions, AddonManager.PERM_CAN_DISABLE)); - do_check_false(hasFlag(d.permissions, AddonManager.PERM_CAN_ENABLE)); - - do_check_true(a.userDisabled); - do_check_false(a.appDisabled); - do_check_false(a.isActive); - do_check_false(isThemeInAddonsList(profileDir, a.id)); - do_check_false(hasFlag(a.permissions, AddonManager.PERM_CAN_DISABLE)); - do_check_true(hasFlag(a.permissions, AddonManager.PERM_CAN_ENABLE)); - - do_check_eq(Services.prefs.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN), "classic/1.0"); - - a.userDisabled = false; - - do_check_true(d.userDisabled); - do_check_false(d.appDisabled); - do_check_true(d.isActive); - do_check_true(isThemeInAddonsList(profileDir, d.id)); - do_check_false(hasFlag(d.permissions, AddonManager.PERM_CAN_DISABLE)); - do_check_true(hasFlag(d.permissions, AddonManager.PERM_CAN_ENABLE)); - - do_check_false(a.userDisabled); - do_check_false(a.appDisabled); - do_check_false(a.isActive); - do_check_false(isThemeInAddonsList(profileDir, a.id)); - do_check_false(hasFlag(a.permissions, AddonManager.PERM_CAN_DISABLE)); - do_check_false(hasFlag(a.permissions, AddonManager.PERM_CAN_ENABLE)); - - do_check_eq(Services.prefs.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN), "classic/1.0"); - - do_execute_soon(check_test_2); - }); -} - -function check_test_2() { - restartManager(); - AddonManager.getAddonsByIDs(["default@tests.mozilla.org", - "alternate@tests.mozilla.org"], callback_soon(function([d, a]) { - do_check_eq(Services.prefs.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN), "alternate/1.0"); - - do_check_true(d.userDisabled); - do_check_false(d.appDisabled); - do_check_false(d.isActive); - do_check_false(isThemeInAddonsList(profileDir, d.id)); - do_check_false(hasFlag(d.permissions, AddonManager.PERM_CAN_DISABLE)); - do_check_true(hasFlag(d.permissions, AddonManager.PERM_CAN_ENABLE)); - - do_check_false(a.userDisabled); - do_check_false(a.appDisabled); - do_check_true(a.isActive); - do_check_true(isThemeInAddonsList(profileDir, a.id)); - do_check_false(hasFlag(a.permissions, AddonManager.PERM_CAN_DISABLE)); - do_check_false(hasFlag(a.permissions, AddonManager.PERM_CAN_ENABLE)); - - d.userDisabled = false; - - do_check_false(d.userDisabled); - do_check_false(d.appDisabled); - do_check_false(d.isActive); - do_check_false(isThemeInAddonsList(profileDir, d.id)); - do_check_false(hasFlag(d.permissions, AddonManager.PERM_CAN_DISABLE)); - do_check_false(hasFlag(d.permissions, AddonManager.PERM_CAN_ENABLE)); - - do_check_true(a.userDisabled); - do_check_false(a.appDisabled); - do_check_true(a.isActive); - do_check_true(isThemeInAddonsList(profileDir, a.id)); - do_check_false(hasFlag(a.permissions, AddonManager.PERM_CAN_DISABLE)); - do_check_true(hasFlag(a.permissions, AddonManager.PERM_CAN_ENABLE)); - - do_check_eq(Services.prefs.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN), "alternate/1.0"); - - restartManager(); - - do_check_eq(Services.prefs.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN), "classic/1.0"); - - AddonManager.getAddonsByIDs(["default@tests.mozilla.org", - "alternate@tests.mozilla.org"], function([d2, a2]) { - do_check_neq(d2, null); - do_check_false(d2.userDisabled); - do_check_false(d2.appDisabled); - do_check_true(d2.isActive); - do_check_true(isThemeInAddonsList(profileDir, d2.id)); - do_check_false(hasFlag(d2.permissions, AddonManager.PERM_CAN_DISABLE)); - do_check_false(hasFlag(d2.permissions, AddonManager.PERM_CAN_ENABLE)); - - do_check_neq(a2, null); - do_check_true(a2.userDisabled); - do_check_false(a2.appDisabled); - do_check_false(a2.isActive); - do_check_false(isThemeInAddonsList(profileDir, a2.id)); - do_check_false(hasFlag(a2.permissions, AddonManager.PERM_CAN_DISABLE)); - do_check_true(hasFlag(a2.permissions, AddonManager.PERM_CAN_ENABLE)); - - end_test(); - }); - })); -} diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_checkCompatibility_themeOverride.js b/toolkit/mozapps/extensions/test/xpcshell/test_checkCompatibility_themeOverride.js deleted file mode 100644 index b6cb13e0879d..000000000000 --- a/toolkit/mozapps/extensions/test/xpcshell/test_checkCompatibility_themeOverride.js +++ /dev/null @@ -1,93 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ - */ - -// This verifies that the (temporary) -// extensions.checkCompatibility.temporaryThemeOverride_minAppVersion -// preference works. - -var ADDONS = [{ - id: "addon1@tests.mozilla.org", - type: 4, - internalName: "theme1/1.0", - version: "1.0", - name: "Test 1", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1.0", - maxVersion: "1.0" - }] -}, { - id: "addon2@tests.mozilla.org", - type: 4, - internalName: "theme2/1.0", - version: "1.0", - name: "Test 2", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "2.0", - maxVersion: "2.0" - }] -}]; - -const profileDir = gProfD.clone(); -profileDir.append("extensions"); - - -function run_test() { - do_test_pending(); - createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "3.0", "1"); - - for (let a of ADDONS) { - writeInstallRDFForExtension(a, profileDir); - } - - startupManager(); - - run_test_1(); -} - -function run_test_1() { - AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", - "addon2@tests.mozilla.org"], - function([a1, a2]) { - - do_check_neq(a1, null); - do_check_false(a1.isActive); - do_check_false(a1.isCompatible); - do_check_true(a1.appDisabled); - - do_check_neq(a2, null); - do_check_false(a2.isActive); - do_check_false(a2.isCompatible); - do_check_true(a1.appDisabled); - - do_execute_soon(run_test_2); - }); -} - -function run_test_2() { - Services.prefs.setCharPref("extensions.checkCompatibility.temporaryThemeOverride_minAppVersion", "2.0"); - if (isNightlyChannel()) - Services.prefs.setBoolPref("extensions.checkCompatibility.nightly", false); - else - Services.prefs.setBoolPref("extensions.checkCompatibility.3.0", false); - restartManager(); - - AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", - "addon2@tests.mozilla.org"], - function([a1, a2]) { - - do_check_neq(a1, null); - do_check_false(a1.isActive); - do_check_false(a1.isCompatible); - do_check_true(a1.appDisabled); - - do_check_neq(a2, null); - do_check_false(a2.isActive); - do_check_false(a2.isCompatible); - do_check_false(a2.appDisabled); - - do_execute_soon(do_test_finished); - }); -} diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_corrupt.js b/toolkit/mozapps/extensions/test/xpcshell/test_corrupt.js index dea5c80c054f..12a4e066d432 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_corrupt.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_corrupt.js @@ -122,15 +122,17 @@ var theme1 = { // The selected theme var theme2 = { - id: "theme2@tests.mozilla.org", - version: "1.0", - name: "Theme 2", - internalName: "test/1.0", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "2", - maxVersion: "2" - }] + manifest: { + manifest_version: 2, + name: "Theme 2", + version: "1.0", + theme: { images: { headerURL: "example.png" } }, + applications: { + gecko: { + id: "theme2@tests.mozilla.org", + }, + }, + }, }; const profileDir = gProfD.clone(); @@ -148,31 +150,32 @@ function run_test() { writeInstallRDFForExtension(addon6, profileDir); writeInstallRDFForExtension(addon7, profileDir); writeInstallRDFForExtension(theme1, profileDir); - writeInstallRDFForExtension(theme2, profileDir); + let theme2XPI = createTempWebExtensionFile(theme2); + AddonTestUtils.manuallyInstall(theme2XPI).then(() => { + // Startup the profile and setup the initial state + startupManager(); - // Startup the profile and setup the initial state - startupManager(); - - AddonManager.getAddonsByIDs(["addon2@tests.mozilla.org", - "addon3@tests.mozilla.org", - "addon4@tests.mozilla.org", - "addon7@tests.mozilla.org", - "theme2@tests.mozilla.org"], function([a2, a3, a4, - a7, t2]) { - // Set up the initial state - a2.userDisabled = true; - a4.userDisabled = true; - a7.userDisabled = true; - t2.userDisabled = false; - a3.findUpdates({ - onUpdateFinished() { - a4.findUpdates({ - onUpdateFinished() { - do_execute_soon(run_test_1); - } - }, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE); - } - }, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE); + AddonManager.getAddonsByIDs(["addon2@tests.mozilla.org", + "addon3@tests.mozilla.org", + "addon4@tests.mozilla.org", + "addon7@tests.mozilla.org", + "theme2@tests.mozilla.org"], function([a2, a3, a4, + a7, t2]) { + // Set up the initial state + a2.userDisabled = true; + a4.userDisabled = true; + a7.userDisabled = true; + t2.userDisabled = false; + a3.findUpdates({ + onUpdateFinished() { + a4.findUpdates({ + onUpdateFinished() { + do_execute_soon(run_test_1); + } + }, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE); + } + }, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE); + }); }); } @@ -327,17 +330,20 @@ function run_test_1() { // Should be correctly recovered do_check_neq(t1_2, null); - do_check_false(t1_2.isActive); - do_check_true(t1_2.userDisabled); + + // Disabled due to bug 1394117 + // do_check_false(t1_2.isActive); + // do_check_true(t1_2.userDisabled); do_check_false(t1_2.appDisabled); do_check_eq(t1_2.pendingOperations, AddonManager.PENDING_NONE); // Should be correctly recovered do_check_neq(t2_2, null); do_check_true(t2_2.isActive); - do_check_false(t2_2.userDisabled); + // Disabled due to bug 1394117 + // do_check_false(t2_2.userDisabled); do_check_false(t2_2.appDisabled); - do_check_eq(t2_2.pendingOperations, AddonManager.PENDING_NONE); + // do_check_eq(t2_2.pendingOperations, AddonManager.PENDING_NONE); Assert.throws(shutdownManager); startupManager(false); @@ -395,14 +401,16 @@ function run_test_1() { do_check_eq(a7_3.pendingOperations, AddonManager.PENDING_NONE); do_check_neq(t1_3, null); - do_check_false(t1_3.isActive); - do_check_true(t1_3.userDisabled); + // Disabled due to bug 1394117 + // do_check_false(t1_3.isActive); + // do_check_true(t1_3.userDisabled); do_check_false(t1_3.appDisabled); do_check_eq(t1_3.pendingOperations, AddonManager.PENDING_NONE); do_check_neq(t2_3, null); - do_check_true(t2_3.isActive); - do_check_false(t2_3.userDisabled); + // Disabled due to bug 1394117 + // do_check_true(t2_3.isActive); + // do_check_false(t2_3.userDisabled); do_check_false(t2_3.appDisabled); do_check_eq(t2_3.pendingOperations, AddonManager.PENDING_NONE); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_corrupt_strictcompat.js b/toolkit/mozapps/extensions/test/xpcshell/test_corrupt_strictcompat.js index 847d3727796b..d453f713d9cd 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_corrupt_strictcompat.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_corrupt_strictcompat.js @@ -123,15 +123,17 @@ var theme1 = { // The selected theme var theme2 = { - id: "theme2@tests.mozilla.org", - version: "1.0", - name: "Theme 2", - internalName: "test/1.0", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "2", - maxVersion: "2" - }] + manifest: { + manifest_version: 2, + name: "Theme 2", + version: "1.0", + theme: { images: { headerURL: "example.png" } }, + applications: { + gecko: { + id: "theme2@tests.mozilla.org", + }, + }, + }, }; const profileDir = gProfD.clone(); @@ -149,31 +151,32 @@ function run_test() { writeInstallRDFForExtension(addon6, profileDir); writeInstallRDFForExtension(addon7, profileDir); writeInstallRDFForExtension(theme1, profileDir); - writeInstallRDFForExtension(theme2, profileDir); + let theme2XPI = createTempWebExtensionFile(theme2); + AddonTestUtils.manuallyInstall(theme2XPI).then(() => { + // Startup the profile and setup the initial state + startupManager(); - // Startup the profile and setup the initial state - startupManager(); - - AddonManager.getAddonsByIDs(["addon2@tests.mozilla.org", - "addon3@tests.mozilla.org", - "addon4@tests.mozilla.org", - "addon7@tests.mozilla.org", - "theme2@tests.mozilla.org"], function([a2, a3, a4, - a7, t2]) { - // Set up the initial state - a2.userDisabled = true; - a4.userDisabled = true; - a7.userDisabled = true; - t2.userDisabled = false; - a3.findUpdates({ - onUpdateFinished() { - a4.findUpdates({ - onUpdateFinished() { - do_execute_soon(run_test_1); - } - }, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE); - } - }, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE); + AddonManager.getAddonsByIDs(["addon2@tests.mozilla.org", + "addon3@tests.mozilla.org", + "addon4@tests.mozilla.org", + "addon7@tests.mozilla.org", + "theme2@tests.mozilla.org"], function([a2, a3, a4, + a7, t2]) { + // Set up the initial state + a2.userDisabled = true; + a4.userDisabled = true; + a7.userDisabled = true; + t2.userDisabled = false; + a3.findUpdates({ + onUpdateFinished() { + a4.findUpdates({ + onUpdateFinished() { + do_execute_soon(run_test_1); + } + }, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE); + } + }, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE); + }); }); } @@ -317,17 +320,19 @@ function run_test_1() { // Should be correctly recovered do_check_neq(t1_2, null); - do_check_false(t1_2.isActive); - do_check_true(t1_2.userDisabled); + // Disabled due to bug 1394117 + // do_check_false(t1_2.isActive); + // do_check_true(t1_2.userDisabled); do_check_false(t1_2.appDisabled); do_check_eq(t1_2.pendingOperations, AddonManager.PENDING_NONE); // Should be correctly recovered do_check_neq(t2_2, null); do_check_true(t2_2.isActive); - do_check_false(t2_2.userDisabled); + // Disabled due to bug 1394117 + // do_check_false(t2_2.userDisabled); do_check_false(t2_2.appDisabled); - do_check_eq(t2_2.pendingOperations, AddonManager.PENDING_NONE); + // do_check_eq(t2_2.pendingOperations, AddonManager.PENDING_NONE); Assert.throws(shutdownManager); startupManager(false); @@ -385,14 +390,16 @@ function run_test_1() { do_check_eq(a7_3.pendingOperations, AddonManager.PENDING_NONE); do_check_neq(t1_3, null); - do_check_false(t1_3.isActive); - do_check_true(t1_3.userDisabled); + // Disabled due to bug 1394117 + // do_check_false(t1_3.isActive); + // do_check_true(t1_3.userDisabled); do_check_false(t1_3.appDisabled); do_check_eq(t1_3.pendingOperations, AddonManager.PENDING_NONE); do_check_neq(t2_3, null); - do_check_true(t2_3.isActive); - do_check_false(t2_3.userDisabled); + // Disabled due to bug 1394117 + // do_check_true(t2_3.isActive); + // do_check_false(t2_3.userDisabled); do_check_false(t2_3.appDisabled); do_check_eq(t2_3.pendingOperations, AddonManager.PENDING_NONE); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_locked.js b/toolkit/mozapps/extensions/test/xpcshell/test_locked.js index c5b06c042530..f83e721514ed 100755 --- a/toolkit/mozapps/extensions/test/xpcshell/test_locked.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_locked.js @@ -120,15 +120,17 @@ var theme1 = { // The selected theme var theme2 = { - id: "theme2@tests.mozilla.org", - version: "1.0", - name: "Theme 2", - internalName: "test/1.0", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "2", - maxVersion: "2" - }] + manifest: { + manifest_version: 2, + name: "Theme 2", + version: "1.0", + theme: { images: { headerURL: "example.png" } }, + applications: { + gecko: { + id: "theme2@tests.mozilla.org", + }, + }, + }, }; const profileDir = gProfD.clone(); @@ -145,7 +147,8 @@ add_task(async function init() { writeInstallRDFForExtension(addon6, profileDir); writeInstallRDFForExtension(addon7, profileDir); writeInstallRDFForExtension(theme1, profileDir); - writeInstallRDFForExtension(theme2, profileDir); + let theme2XPI = createTempWebExtensionFile(theme2); + await AddonTestUtils.manuallyInstall(theme2XPI); // Startup the profile and setup the initial state startupManager(); @@ -155,11 +158,12 @@ add_task(async function init() { let [a2, a3, a4, a7, t2] = await promiseAddonsByIDs(["addon2@tests.mozilla.org", - "addon3@tests.mozilla.org", - "addon4@tests.mozilla.org", - "addon7@tests.mozilla.org", - "theme2@tests.mozilla.org"]); - await new Promise(resolve => { + "addon3@tests.mozilla.org", + "addon4@tests.mozilla.org", + "addon7@tests.mozilla.org", + "theme2@tests.mozilla.org"]); + + await new Promise(resolve => { // Set up the initial state a2.userDisabled = true; a4.userDisabled = true; @@ -246,14 +250,16 @@ add_task(async function run_test_1() { do_check_true(t1.userDisabled); do_check_false(t1.appDisabled); do_check_eq(t1.pendingOperations, AddonManager.PENDING_NONE); - do_check_false(isThemeInAddonsList(profileDir, t1.id)); + // Disabled due to bug 1394117 + // do_check_false(isThemeInAddonsList(profileDir, t1.id)); do_check_neq(t2, null); do_check_true(t2.isActive); do_check_false(t2.userDisabled); do_check_false(t2.appDisabled); do_check_eq(t2.pendingOperations, AddonManager.PENDING_NONE); - do_check_true(isThemeInAddonsList(profileDir, t2.id)); + // Disabled due to bug 1394117 + // do_check_true(isThemeInAddonsList(profileDir, t2.id)); // Open another handle on the JSON DB with as much Unix and Windows locking // as we can to simulate some other process interfering with it @@ -345,19 +351,21 @@ add_task(async function run_test_1() { // Should be correctly recovered do_check_neq(t1, null); - do_check_false(t1.isActive); - do_check_true(t1.userDisabled); + // Disabled due to bug 1394117 + // do_check_false(t1.isActive); + // do_check_true(t1.userDisabled); do_check_false(t1.appDisabled); do_check_eq(t1.pendingOperations, AddonManager.PENDING_NONE); - do_check_false(isThemeInAddonsList(profileDir, t1.id)); + // do_check_false(isThemeInAddonsList(profileDir, t1.id)); // Should be correctly recovered do_check_neq(t2, null); do_check_true(t2.isActive); - do_check_false(t2.userDisabled); + // Disabled due to bug 1394117 + // do_check_false(t2.userDisabled); do_check_false(t2.appDisabled); - do_check_eq(t2.pendingOperations, AddonManager.PENDING_NONE); - do_check_true(isThemeInAddonsList(profileDir, t2.id)); + // do_check_eq(t2.pendingOperations, AddonManager.PENDING_NONE); + // do_check_true(isThemeInAddonsList(profileDir, t2.id)); // Restarting will actually apply changes to extensions.ini which will // then be put into the in-memory database when we next fail to load the @@ -431,18 +439,20 @@ add_task(async function run_test_1() { do_check_eq(a7.pendingOperations, AddonManager.PENDING_NONE); do_check_neq(t1, null); - do_check_false(t1.isActive); - do_check_true(t1.userDisabled); + // Disabled due to bug 1394117 + // do_check_false(t1.isActive); + // do_check_true(t1.userDisabled); do_check_false(t1.appDisabled); do_check_eq(t1.pendingOperations, AddonManager.PENDING_NONE); - do_check_false(isThemeInAddonsList(profileDir, t1.id)); + // do_check_false(isThemeInAddonsList(profileDir, t1.id)); do_check_neq(t2, null); - do_check_true(t2.isActive); - do_check_false(t2.userDisabled); + // Disabled due to bug 1394117 + // do_check_true(t2.isActive); + // do_check_false(t2.userDisabled); do_check_false(t2.appDisabled); do_check_eq(t2.pendingOperations, AddonManager.PENDING_NONE); - do_check_true(isThemeInAddonsList(profileDir, t2.id)); + // do_check_true(isThemeInAddonsList(profileDir, t2.id)); // After allowing access to the original DB things should go back to as // they were previously @@ -518,18 +528,20 @@ add_task(async function run_test_1() { do_check_eq(a7.pendingOperations, AddonManager.PENDING_NONE); do_check_neq(t1, null); - do_check_false(t1.isActive); - do_check_true(t1.userDisabled); + // Disabled due to bug 1394117 + // do_check_false(t1.isActive); + // do_check_true(t1.userDisabled); do_check_false(t1.appDisabled); do_check_eq(t1.pendingOperations, AddonManager.PENDING_NONE); - do_check_false(isThemeInAddonsList(profileDir, t1.id)); + // do_check_false(isThemeInAddonsList(profileDir, t1.id)); do_check_neq(t2, null); - do_check_true(t2.isActive); - do_check_false(t2.userDisabled); + // Disabled due to bug 1394117 + // do_check_true(t2.isActive); + // do_check_false(t2.userDisabled); do_check_false(t2.appDisabled); do_check_eq(t2.pendingOperations, AddonManager.PENDING_NONE); - do_check_true(isThemeInAddonsList(profileDir, t2.id)); + // do_check_true(isThemeInAddonsList(profileDir, t2.id)); try { shutdownManager(); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_locked_strictcompat.js b/toolkit/mozapps/extensions/test/xpcshell/test_locked_strictcompat.js index a85fe4863ed8..ae67c5b32e2b 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_locked_strictcompat.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_locked_strictcompat.js @@ -120,15 +120,17 @@ var theme1 = { // The selected theme var theme2 = { - id: "theme2@tests.mozilla.org", - version: "1.0", - name: "Theme 2", - internalName: "test/1.0", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "2", - maxVersion: "2" - }] + manifest: { + manifest_version: 2, + name: "Theme 2", + version: "1.0", + theme: { images: { headerURL: "example.png" } }, + applications: { + gecko: { + id: "theme2@tests.mozilla.org", + }, + }, + }, }; const profileDir = gProfD.clone(); @@ -145,7 +147,8 @@ add_task(async function init() { writeInstallRDFForExtension(addon6, profileDir); writeInstallRDFForExtension(addon7, profileDir); writeInstallRDFForExtension(theme1, profileDir); - writeInstallRDFForExtension(theme2, profileDir); + let theme2XPI = createTempWebExtensionFile(theme2); + await AddonTestUtils.manuallyInstall(theme2XPI); // Startup the profile and setup the initial state await promiseStartupManager(); @@ -246,14 +249,16 @@ add_task(async function run_test_1() { do_check_true(t1.userDisabled); do_check_false(t1.appDisabled); do_check_eq(t1.pendingOperations, AddonManager.PENDING_NONE); - do_check_false(isThemeInAddonsList(profileDir, t1.id)); + // Disabled due to bug 1394117 + // do_check_false(isThemeInAddonsList(profileDir, t1.id)); do_check_neq(t2, null); do_check_true(t2.isActive); do_check_false(t2.userDisabled); do_check_false(t2.appDisabled); do_check_eq(t2.pendingOperations, AddonManager.PENDING_NONE); - do_check_true(isThemeInAddonsList(profileDir, t2.id)); + // Disabled due to bug 1394117 + // do_check_true(isThemeInAddonsList(profileDir, t2.id)); // Open another handle on the JSON DB with as much Unix and Windows locking // as we can to simulate some other process interfering with it @@ -343,19 +348,21 @@ add_task(async function run_test_1() { // Should be correctly recovered do_check_neq(t1, null); - do_check_false(t1.isActive); - do_check_true(t1.userDisabled); + // Disabled due to bug 1394117 + // do_check_false(t1.isActive); + // do_check_true(t1.userDisabled); do_check_false(t1.appDisabled); do_check_eq(t1.pendingOperations, AddonManager.PENDING_NONE); - do_check_false(isThemeInAddonsList(profileDir, t1.id)); + // do_check_false(isThemeInAddonsList(profileDir, t1.id)); // Should be correctly recovered do_check_neq(t2, null); do_check_true(t2.isActive); - do_check_false(t2.userDisabled); + // Disabled due to bug 1394117 + // do_check_false(t2.userDisabled); do_check_false(t2.appDisabled); - do_check_eq(t2.pendingOperations, AddonManager.PENDING_NONE); - do_check_true(isThemeInAddonsList(profileDir, t2.id)); + // do_check_eq(t2.pendingOperations, AddonManager.PENDING_NONE); + // do_check_true(isThemeInAddonsList(profileDir, t2.id)); // Restarting will actually apply changes to extensions.ini which will // then be put into the in-memory database when we next fail to load the @@ -429,18 +436,20 @@ add_task(async function run_test_1() { do_check_eq(a7.pendingOperations, AddonManager.PENDING_NONE); do_check_neq(t1, null); - do_check_false(t1.isActive); - do_check_true(t1.userDisabled); + // Disabled due to bug 1394117 + // do_check_false(t1.isActive); + // do_check_true(t1.userDisabled); do_check_false(t1.appDisabled); do_check_eq(t1.pendingOperations, AddonManager.PENDING_NONE); - do_check_false(isThemeInAddonsList(profileDir, t1.id)); + // do_check_false(isThemeInAddonsList(profileDir, t1.id)); do_check_neq(t2, null); - do_check_true(t2.isActive); - do_check_false(t2.userDisabled); + // Disabled due to bug 1394117 + // do_check_true(t2.isActive); + // do_check_false(t2.userDisabled); do_check_false(t2.appDisabled); do_check_eq(t2.pendingOperations, AddonManager.PENDING_NONE); - do_check_true(isThemeInAddonsList(profileDir, t2.id)); + // do_check_true(isThemeInAddonsList(profileDir, t2.id)); // After allowing access to the original DB things should go back to as // back how they were before the lock @@ -537,18 +546,20 @@ add_task(async function run_test_1() { do_check_eq(a7.pendingOperations, AddonManager.PENDING_NONE); do_check_neq(t1, null); - do_check_false(t1.isActive); - do_check_true(t1.userDisabled); + // Disabled due to bug 1394117 + // do_check_false(t1.isActive); + // do_check_true(t1.userDisabled); do_check_false(t1.appDisabled); do_check_eq(t1.pendingOperations, AddonManager.PENDING_NONE); - do_check_false(isThemeInAddonsList(profileDir, t1.id)); + // do_check_false(isThemeInAddonsList(profileDir, t1.id)); do_check_neq(t2, null); - do_check_true(t2.isActive); - do_check_false(t2.userDisabled); + // Disabled due to bug 1394117 + // do_check_true(t2.isActive); + // do_check_false(t2.userDisabled); do_check_false(t2.appDisabled); do_check_eq(t2.pendingOperations, AddonManager.PENDING_NONE); - do_check_true(isThemeInAddonsList(profileDir, t2.id)); + // do_check_true(isThemeInAddonsList(profileDir, t2.id)); try { shutdownManager(); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_signed_migrate.js b/toolkit/mozapps/extensions/test/xpcshell/test_signed_migrate.js deleted file mode 100644 index 777f49b8e487..000000000000 --- a/toolkit/mozapps/extensions/test/xpcshell/test_signed_migrate.js +++ /dev/null @@ -1,194 +0,0 @@ -// Enable signature checks for these tests -gUseRealCertChecks = true; -// Disable update security -Services.prefs.setBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, false); -// Allow attempting to show the compatibility UI which should not happen -Services.prefs.setBoolPref("extensions.showMismatchUI", true); - -const DATA = "data/signing_checks/"; -const ADDONS = { - bootstrap: { - unsigned: "unsigned_bootstrap_2.xpi", - badid: "signed_bootstrap_badid_2.xpi", - signed: "signed_bootstrap_2.xpi", - }, - nonbootstrap: { - unsigned: "unsigned_nonbootstrap_2.xpi", - badid: "signed_nonbootstrap_badid_2.xpi", - signed: "signed_nonbootstrap_2.xpi", - } -}; -const ID = "test@tests.mozilla.org"; - -const profileDir = gProfD.clone(); -profileDir.append("extensions"); - -// Override the window watcher -var WindowWatcher = { - sawAddon: false, - - openWindow(parent, url, name, features, args) { - let ids = args.QueryInterface(AM_Ci.nsIVariant); - this.sawAddon = ids.indexOf(ID) >= 0; - }, - - QueryInterface(iid) { - if (iid.equals(AM_Ci.nsIWindowWatcher) - || iid.equals(AM_Ci.nsISupports)) - return this; - - throw Components.results.NS_ERROR_NO_INTERFACE; - } -} - -MockRegistrar.register("@mozilla.org/embedcomp/window-watcher;1", WindowWatcher); - -function resetPrefs() { - Services.prefs.setIntPref("bootstraptest.active_version", -1); - Services.prefs.setIntPref("bootstraptest.installed_version", -1); - Services.prefs.setIntPref("bootstraptest.startup_reason", -1); - Services.prefs.setIntPref("bootstraptest.shutdown_reason", -1); - Services.prefs.setIntPref("bootstraptest.install_reason", -1); - Services.prefs.setIntPref("bootstraptest.uninstall_reason", -1); - Services.prefs.setIntPref("bootstraptest.startup_oldversion", -1); - Services.prefs.setIntPref("bootstraptest.shutdown_newversion", -1); - Services.prefs.setIntPref("bootstraptest.install_oldversion", -1); - Services.prefs.setIntPref("bootstraptest.uninstall_newversion", -1); -} - -function getActiveVersion() { - return Services.prefs.getIntPref("bootstraptest.active_version"); -} - -function run_test() { - createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "4", "4"); - - // Start and stop the manager to initialise everything in the profile before - // actual testing - startupManager(); - shutdownManager(); - resetPrefs(); - - run_next_test(); -} - -// Removes the signedState field from add-ons in the json database to make it -// look like the database was written with an older version of the application -function stripDB() { - let jData = loadJSON(gExtensionsJSON); - jData.schemaVersion--; - - for (let addon of jData.addons) - delete addon.signedState; - - saveJSON(jData, gExtensionsJSON); -} - -async function test_breaking_migrate(addons, test, expectedSignedState) { - // Startup as the old version - gAppInfo.version = "4"; - startupManager(true); - - // Install the signed add-on - await promiseInstallAllFiles([do_get_file(DATA + addons.signed)]); - // Restart to let non-restartless add-ons install fully - await promiseRestartManager(); - await promiseShutdownManager(); - resetPrefs(); - stripDB(); - - // Now replace it with the version to test. Doing this so quickly shouldn't - // trigger the file modification code to detect the change by itself. - manuallyUninstall(profileDir, ID); - manuallyInstall(do_get_file(DATA + addons[test]), profileDir, ID); - - // Update the application - gAppInfo.version = "5"; - await promiseStartupManager(true); - - let addon = await promiseAddonByID(ID); - do_check_neq(addon, null); - do_check_true(addon.appDisabled); - do_check_false(addon.isActive); - do_check_eq(addon.signedState, expectedSignedState); - - // Add-on shouldn't be active - if (addons == ADDONS.bootstrap) - do_check_eq(getActiveVersion(), -1); - else - do_check_false(isExtensionInAddonsList(profileDir, ID)); - - // Should have flagged the change during startup - let changes = AddonManager.getStartupChanges(AddonManager.STARTUP_CHANGE_DISABLED); - do_check_eq(changes.length, 1); - do_check_eq(changes[0], ID); - - // Shouldn't have checked for updates for the add-on - do_check_false(WindowWatcher.sawAddon); - WindowWatcher.sawAddon = false; - - addon.uninstall(); - // Restart to let non-restartless add-ons uninstall fully - await promiseRestartManager(); - await shutdownManager(); - resetPrefs(); -} - -async function test_working_migrate(addons, test, expectedSignedState) { - // Startup as the old version - gAppInfo.version = "4"; - startupManager(true); - - // Install the signed add-on - await promiseInstallAllFiles([do_get_file(DATA + addons.signed)]); - // Restart to let non-restartless add-ons install fully - await promiseRestartManager(); - await promiseShutdownManager(); - resetPrefs(); - stripDB(); - - // Now replace it with the version to test. Doing this so quickly shouldn't - // trigger the file modification code to detect the change by itself. - manuallyUninstall(profileDir, ID); - manuallyInstall(do_get_file(DATA + addons[test]), profileDir, ID); - - // Update the application - gAppInfo.version = "5"; - await promiseStartupManager(true); - - let addon = await promiseAddonByID(ID); - do_check_neq(addon, null); - do_check_false(addon.appDisabled); - do_check_true(addon.isActive); - do_check_eq(addon.signedState, expectedSignedState); - - if (addons == ADDONS.bootstrap) - do_check_eq(getActiveVersion(), 2); - else - do_check_true(isExtensionInAddonsList(profileDir, ID)); - - // Shouldn't have checked for updates for the add-on - do_check_false(WindowWatcher.sawAddon); - WindowWatcher.sawAddon = false; - - addon.uninstall(); - // Restart to let non-restartless add-ons uninstall fully - await promiseRestartManager(); - await shutdownManager(); - resetPrefs(); -} - -add_task(async function() { - await test_breaking_migrate(ADDONS.bootstrap, "unsigned", AddonManager.SIGNEDSTATE_MISSING); - await test_breaking_migrate(ADDONS.nonbootstrap, "unsigned", AddonManager.SIGNEDSTATE_MISSING); -}); - -add_task(async function() { - await test_breaking_migrate(ADDONS.bootstrap, "badid", AddonManager.SIGNEDSTATE_BROKEN); - await test_breaking_migrate(ADDONS.nonbootstrap, "badid", AddonManager.SIGNEDSTATE_BROKEN); -}); - -add_task(async function() { - await test_working_migrate(ADDONS.bootstrap, "signed", AddonManager.SIGNEDSTATE_SIGNED); - await test_working_migrate(ADDONS.nonbootstrap, "signed", AddonManager.SIGNEDSTATE_SIGNED); -}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_theme.js b/toolkit/mozapps/extensions/test/xpcshell/test_theme.js deleted file mode 100644 index 0e3b58833b9a..000000000000 --- a/toolkit/mozapps/extensions/test/xpcshell/test_theme.js +++ /dev/null @@ -1,1138 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ - */ - -Components.utils.import("resource://gre/modules/NetUtil.jsm"); - -// The maximum allowable time since install. If an add-on claims to have been -// installed longer ago than this the the test will fail. -const MAX_INSTALL_TIME = 10000; - -// This verifies that themes behave as expected - -const PREF_GENERAL_SKINS_SELECTEDSKIN = "general.skins.selectedSkin"; - -Components.utils.import("resource://gre/modules/LightweightThemeManager.jsm"); - -const profileDir = gProfD.clone(); -profileDir.append("extensions"); - -// Observer to ensure a "lightweight-theme-styling-update" notification is sent -// when expected -var gLWThemeChanged = false; -var LightweightThemeObserver = { - observe(aSubject, aTopic, aData) { - if (aTopic != "lightweight-theme-styling-update") - return; - - gLWThemeChanged = true; - } -}; - -AM_Cc["@mozilla.org/observer-service;1"] - .getService(Components.interfaces.nsIObserverService) - .addObserver(LightweightThemeObserver, "lightweight-theme-styling-update"); - - -async function run_test() { - do_test_pending(); - createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2"); - - Services.prefs.setCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN, "theme1/1.0"); - writeInstallRDFForExtension({ - id: "theme1@tests.mozilla.org", - version: "1.0", - name: "Test 1", - type: 4, - skinnable: true, - internalName: "theme1/1.0", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "2" - }] - }, profileDir); - - writeInstallRDFForExtension({ - id: "theme2@tests.mozilla.org", - version: "1.0", - name: "Test 1", - skinnable: false, - internalName: "theme2/1.0", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "2" - }] - }, profileDir); - - // We need a default theme for some of these things to work but we have hidden - // the one in the application directory. - writeInstallRDFForExtension({ - id: "default@tests.mozilla.org", - version: "1.0", - name: "Default", - internalName: "classic/1.0", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "2" - }] - }, profileDir); - - await promiseStartupManager(); - // Make sure we only register once despite multiple calls - AddonManager.addInstallListener(InstallListener); - AddonManager.addAddonListener(AddonListener); - AddonManager.addInstallListener(InstallListener); - AddonManager.addAddonListener(AddonListener); - AddonManager.addInstallListener(InstallListener); - - AddonManager.getAddonsByIDs(["default@tests.mozilla.org", - "theme1@tests.mozilla.org", - "theme2@tests.mozilla.org"], - function([d, t1, t2]) { - do_check_neq(d, null); - do_check_false(d.skinnable); - do_check_false(d.foreignInstall); - do_check_eq(d.signedState, undefined); - - do_check_neq(t1, null); - do_check_false(t1.userDisabled); - do_check_false(t1.appDisabled); - do_check_eq(t1.signedState, undefined); - do_check_true(t1.isActive); - do_check_true(t1.skinnable); - do_check_true(t1.foreignInstall); - do_check_eq(t1.screenshots, null); - do_check_true(isThemeInAddonsList(profileDir, t1.id)); - do_check_false(hasFlag(t1.permissions, AddonManager.PERM_CAN_DISABLE)); - do_check_false(hasFlag(t1.permissions, AddonManager.PERM_CAN_ENABLE)); - do_check_eq(t1.operationsRequiringRestart, AddonManager.OP_NEEDS_RESTART_UNINSTALL | - AddonManager.OP_NEEDS_RESTART_DISABLE); - - do_check_neq(t2, null); - do_check_true(t2.userDisabled); - do_check_false(t2.appDisabled); - do_check_eq(t2.signedState, undefined); - do_check_false(t2.isActive); - do_check_false(t2.skinnable); - do_check_true(t2.foreignInstall); - do_check_eq(t2.screenshots, null); - do_check_false(isThemeInAddonsList(profileDir, t2.id)); - do_check_false(hasFlag(t2.permissions, AddonManager.PERM_CAN_DISABLE)); - do_check_true(hasFlag(t2.permissions, AddonManager.PERM_CAN_ENABLE)); - do_check_eq(t2.operationsRequiringRestart, AddonManager.OP_NEEDS_RESTART_ENABLE); - - do_execute_soon(run_test_1); - }); -} - -function end_test() { - do_execute_soon(do_test_finished); -} - -// Checks enabling one theme disables the others -function run_test_1() { - prepare_test({ - "theme1@tests.mozilla.org": [ - "onDisabling" - ], - "theme2@tests.mozilla.org": [ - "onEnabling" - ] - }); - AddonManager.getAddonsByIDs(["theme1@tests.mozilla.org", - "theme2@tests.mozilla.org"], function([t1, t2]) { - t2.userDisabled = false; - - ensure_test_completed(); - do_check_false(hasFlag(t2.permissions, AddonManager.PERM_CAN_DISABLE)); - do_check_false(hasFlag(t2.permissions, AddonManager.PERM_CAN_ENABLE)); - - do_check_true(t1.userDisabled); - do_check_false(hasFlag(t1.permissions, AddonManager.PERM_CAN_DISABLE)); - do_check_true(hasFlag(t1.permissions, AddonManager.PERM_CAN_ENABLE)); - - do_execute_soon(check_test_1); - }); -} - -async function check_test_1() { - await promiseRestartManager(); - - do_check_eq(Services.prefs.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN), "theme2/1.0"); - - AddonManager.getAddonsByIDs(["theme1@tests.mozilla.org", - "theme2@tests.mozilla.org"], function([t1, t2]) { - do_check_neq(t1, null); - do_check_true(t1.userDisabled); - do_check_false(t1.appDisabled); - do_check_false(t1.isActive); - do_check_false(isThemeInAddonsList(profileDir, t1.id)); - do_check_false(hasFlag(t1.permissions, AddonManager.PERM_CAN_DISABLE)); - do_check_true(hasFlag(t1.permissions, AddonManager.PERM_CAN_ENABLE)); - do_check_eq(t1.operationsRequiringRestart, AddonManager.OP_NEEDS_RESTART_ENABLE); - - do_check_neq(t2, null); - do_check_false(t2.userDisabled); - do_check_false(t2.appDisabled); - do_check_true(t2.isActive); - do_check_true(isThemeInAddonsList(profileDir, t2.id)); - do_check_false(hasFlag(t2.permissions, AddonManager.PERM_CAN_DISABLE)); - do_check_false(hasFlag(t2.permissions, AddonManager.PERM_CAN_ENABLE)); - do_check_eq(t2.operationsRequiringRestart, AddonManager.OP_NEEDS_RESTART_UNINSTALL | - AddonManager.OP_NEEDS_RESTART_DISABLE); - do_check_false(gLWThemeChanged); - - do_execute_soon(run_test_2); - }); -} - -// Removing the active theme should fall back to the default (not ideal in this -// case since we don't have the default theme installed) -async function run_test_2() { - var dest = profileDir.clone(); - dest.append(do_get_expected_addon_name("theme2@tests.mozilla.org")); - dest.remove(true); - - await promiseRestartManager(); - - do_check_eq(Services.prefs.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN), "classic/1.0"); - - AddonManager.getAddonsByIDs(["theme1@tests.mozilla.org", - "theme2@tests.mozilla.org"], function([t1, t2]) { - do_check_neq(t1, null); - do_check_true(t1.userDisabled); - do_check_false(t1.appDisabled); - do_check_false(t1.isActive); - do_check_false(isThemeInAddonsList(profileDir, t1.id)); - do_check_false(hasFlag(t1.permissions, AddonManager.PERM_CAN_DISABLE)); - do_check_true(hasFlag(t1.permissions, AddonManager.PERM_CAN_ENABLE)); - - do_check_eq(t2, null); - do_check_false(isThemeInAddonsList(profileDir, "theme2@tests.mozilla.org")); - do_check_false(gLWThemeChanged); - - do_execute_soon(run_test_3); - }); -} - -// Installing a lightweight theme should happen instantly and disable the default theme -function run_test_3() { - writeInstallRDFForExtension({ - id: "theme2@tests.mozilla.org", - version: "1.0", - name: "Test 1", - internalName: "theme2/1.0", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "2" - }] - }, profileDir); - restartManager(); - - prepare_test({ - "1@personas.mozilla.org": [ - ["onInstalling", false], - "onInstalled", - ["onEnabling", false], - "onEnabled" - ], - "default@tests.mozilla.org": [ - ["onDisabling", false], - "onDisabled", - ] - }, [ - "onExternalInstall" - ]); - - LightweightThemeManager.currentTheme = { - id: "1", - version: "1", - name: "Test LW Theme", - description: "A test theme", - author: "Mozilla", - homepageURL: "http://localhost/data/index.html", - headerURL: "http://localhost/data/header.png", - footerURL: "http://localhost/data/footer.png", - previewURL: "http://localhost/data/preview.png", - iconURL: "http://localhost/data/icon.png" - }; - - ensure_test_completed(); - - AddonManager.getAddonByID("1@personas.mozilla.org", function(p1) { - do_check_neq(null, p1); - do_check_eq(p1.name, "Test LW Theme"); - do_check_eq(p1.version, "1"); - do_check_eq(p1.type, "theme"); - do_check_eq(p1.description, "A test theme"); - do_check_eq(p1.creator, "Mozilla"); - do_check_eq(p1.homepageURL, "http://localhost/data/index.html"); - do_check_eq(p1.iconURL, "http://localhost/data/icon.png"); - do_check_eq(p1.screenshots.length, 1); - do_check_eq(p1.screenshots[0], "http://localhost/data/preview.png"); - do_check_false(p1.appDisabled); - do_check_false(p1.userDisabled); - do_check_true(p1.isCompatible); - do_check_true(p1.providesUpdatesSecurely); - do_check_eq(p1.blocklistState, 0); - do_check_true(p1.isActive); - do_check_eq(p1.pendingOperations, 0); - do_check_eq(p1.permissions, AddonManager.PERM_CAN_UNINSTALL | AddonManager.PERM_CAN_DISABLE); - do_check_eq(p1.scope, AddonManager.SCOPE_PROFILE); - do_check_true("isCompatibleWith" in p1); - do_check_true("findUpdates" in p1); - do_check_eq(p1.installDate.getTime(), p1.updateDate.getTime()); - - // Should have been installed sometime in the last few seconds. - let difference = Date.now() - p1.installDate.getTime(); - if (difference > MAX_INSTALL_TIME) - do_throw("Add-on was installed " + difference + "ms ago"); - else if (difference < 0) - do_throw("Add-on was installed " + difference + "ms in the future"); - - AddonManager.getAddonsByTypes(["theme"], function(addons) { - let seen = false; - addons.forEach(function(a) { - if (a.id == "1@personas.mozilla.org") { - seen = true; - } else { - dump("Checking theme " + a.id + "\n"); - do_check_false(a.isActive); - do_check_true(a.userDisabled); - } - }); - do_check_true(seen); - - do_check_true(gLWThemeChanged); - gLWThemeChanged = false; - - do_execute_soon(run_test_4); - }); - }); -} - -// Installing a second lightweight theme should disable the first with no restart -function run_test_4() { - prepare_test({ - "1@personas.mozilla.org": [ - ["onDisabling", false], - "onDisabled", - ], - "2@personas.mozilla.org": [ - ["onInstalling", false], - "onInstalled", - ["onEnabling", false], - "onEnabled" - ] - }, [ - "onExternalInstall" - ]); - - LightweightThemeManager.currentTheme = { - id: "2", - version: "1", - name: "Test LW Theme", - description: "A second test theme", - author: "Mozilla", - homepageURL: "http://localhost/data/index.html", - headerURL: "http://localhost/data/header.png", - footerURL: "http://localhost/data/footer.png", - previewURL: "http://localhost/data/preview.png", - iconURL: "http://localhost/data/icon.png" - }; - - ensure_test_completed(); - - AddonManager.getAddonsByIDs(["1@personas.mozilla.org", - "2@personas.mozilla.org"], function([p1, p2]) { - do_check_neq(null, p2); - do_check_false(p2.appDisabled); - do_check_false(p2.userDisabled); - do_check_true(p2.isActive); - do_check_eq(p2.pendingOperations, 0); - do_check_eq(p2.permissions, AddonManager.PERM_CAN_UNINSTALL | AddonManager.PERM_CAN_DISABLE); - do_check_eq(p2.installDate.getTime(), p2.updateDate.getTime()); - - // Should have been installed sometime in the last few seconds. - let difference = Date.now() - p2.installDate.getTime(); - if (difference > MAX_INSTALL_TIME) - do_throw("Add-on was installed " + difference + "ms ago"); - else if (difference < 0) - do_throw("Add-on was installed " + difference + "ms in the future"); - - do_check_neq(null, p1); - do_check_false(p1.appDisabled); - do_check_true(p1.userDisabled); - do_check_false(p1.isActive); - do_check_eq(p1.pendingOperations, 0); - do_check_eq(p1.permissions, AddonManager.PERM_CAN_UNINSTALL | AddonManager.PERM_CAN_ENABLE); - - AddonManager.getAddonsByTypes(["theme"], function(addons) { - let seen = false; - addons.forEach(function(a) { - if (a.id == "2@personas.mozilla.org") { - seen = true; - } else { - dump("Checking theme " + a.id + "\n"); - do_check_false(a.isActive); - do_check_true(a.userDisabled); - } - }); - do_check_true(seen); - - do_check_true(gLWThemeChanged); - gLWThemeChanged = false; - - do_execute_soon(run_test_5); - }); - }); -} - -// Switching to a custom theme should disable the lightweight theme and require -// a restart. Cancelling that should also be possible. -function run_test_5() { - prepare_test({ - "2@personas.mozilla.org": [ - "onDisabling", - ], - "theme2@tests.mozilla.org": [ - "onEnabling" - ] - }); - - AddonManager.getAddonsByIDs(["2@personas.mozilla.org", - "theme2@tests.mozilla.org"], function([p2, t2]) { - t2.userDisabled = false; - - ensure_test_completed(); - - prepare_test({ - "2@personas.mozilla.org": [ - "onOperationCancelled", - ], - "theme2@tests.mozilla.org": [ - "onOperationCancelled" - ] - }); - - p2.userDisabled = false; - - ensure_test_completed(); - - prepare_test({ - "2@personas.mozilla.org": [ - "onDisabling", - ], - "theme2@tests.mozilla.org": [ - "onEnabling" - ] - }); - - t2.userDisabled = false; - - ensure_test_completed(); - - do_check_false(t2.isActive); - do_check_false(t2.userDisabled); - do_check_true(hasFlag(AddonManager.PENDING_ENABLE, t2.pendingOperations)); - do_check_true(p2.isActive); - do_check_true(p2.userDisabled); - do_check_true(hasFlag(AddonManager.PENDING_DISABLE, p2.pendingOperations)); - do_check_true(hasFlag(AddonManager.PERM_CAN_ENABLE, p2.permissions)); - do_check_false(gLWThemeChanged); - - do_execute_soon(check_test_5); - }); -} - -function check_test_5() { - restartManager(); - - AddonManager.getAddonsByIDs(["2@personas.mozilla.org", - "theme2@tests.mozilla.org"], function([p2, t2]) { - do_check_true(t2.isActive); - do_check_false(t2.userDisabled); - do_check_false(hasFlag(AddonManager.PENDING_ENABLE, t2.pendingOperations)); - do_check_false(p2.isActive); - do_check_true(p2.userDisabled); - do_check_false(hasFlag(AddonManager.PENDING_DISABLE, p2.pendingOperations)); - - do_check_true(gLWThemeChanged); - gLWThemeChanged = false; - - do_execute_soon(run_test_6); - }); -} - -// Switching from a custom theme to a lightweight theme should require a restart -function run_test_6() { - prepare_test({ - "2@personas.mozilla.org": [ - "onEnabling", - ], - "theme2@tests.mozilla.org": [ - "onDisabling" - ] - }); - - AddonManager.getAddonsByIDs(["2@personas.mozilla.org", - "theme2@tests.mozilla.org"], function([p2, t2]) { - p2.userDisabled = false; - - ensure_test_completed(); - - prepare_test({ - "2@personas.mozilla.org": [ - "onOperationCancelled", - ], - "theme2@tests.mozilla.org": [ - "onOperationCancelled" - ] - }); - - t2.userDisabled = false; - - ensure_test_completed(); - - prepare_test({ - "2@personas.mozilla.org": [ - "onEnabling", - ], - "theme2@tests.mozilla.org": [ - "onDisabling" - ] - }); - - p2.userDisabled = false; - - ensure_test_completed(); - - do_check_false(p2.isActive); - do_check_false(p2.userDisabled); - do_check_true(hasFlag(AddonManager.PENDING_ENABLE, p2.pendingOperations)); - do_check_true(t2.isActive); - do_check_true(t2.userDisabled); - do_check_true(hasFlag(AddonManager.PENDING_DISABLE, t2.pendingOperations)); - do_check_false(gLWThemeChanged); - - do_execute_soon(check_test_6); - }); -} - -function check_test_6() { - restartManager(); - - AddonManager.getAddonsByIDs(["2@personas.mozilla.org", - "theme2@tests.mozilla.org"], function([p2, t2]) { - do_check_true(p2.isActive); - do_check_false(p2.userDisabled); - do_check_false(hasFlag(AddonManager.PENDING_ENABLE, p2.pendingOperations)); - do_check_false(t2.isActive); - do_check_true(t2.userDisabled); - do_check_false(hasFlag(AddonManager.PENDING_DISABLE, t2.pendingOperations)); - - do_check_true(gLWThemeChanged); - gLWThemeChanged = false; - - do_execute_soon(run_test_7); - }); -} - -// Uninstalling a lightweight theme should not require a restart -function run_test_7() { - prepare_test({ - "1@personas.mozilla.org": [ - ["onUninstalling", false], - "onUninstalled" - ] - }); - - AddonManager.getAddonByID("1@personas.mozilla.org", function(p1) { - p1.uninstall(); - - ensure_test_completed(); - do_check_eq(LightweightThemeManager.usedThemes.length, 1); - do_check_false(gLWThemeChanged); - - do_execute_soon(run_test_8); - }); -} - -// Uninstalling a lightweight theme in use should not require a restart and it -// should reactivate the default theme -// Also, uninstalling a lightweight theme in use should send a -// "lightweight-theme-styling-update" notification through the observer service -function run_test_8() { - prepare_test({ - "2@personas.mozilla.org": [ - ["onUninstalling", false], - "onUninstalled" - ], - "default@tests.mozilla.org": [ - ["onEnabling", false], - "onEnabled" - ] - }); - - AddonManager.getAddonByID("2@personas.mozilla.org", function(p2) { - p2.uninstall(); - - ensure_test_completed(); - do_check_eq(LightweightThemeManager.usedThemes.length, 0); - - do_check_true(gLWThemeChanged); - gLWThemeChanged = false; - - do_execute_soon(run_test_9); - }); -} - -// Uninstalling a theme not in use should not require a restart -function run_test_9() { - AddonManager.getAddonByID("theme1@tests.mozilla.org", function(t1) { - prepare_test({ - "theme1@tests.mozilla.org": [ - ["onUninstalling", false], - "onUninstalled" - ] - }); - - t1.uninstall(); - - ensure_test_completed(); - - AddonManager.getAddonByID("theme1@tests.mozilla.org", function(newt1) { - do_check_eq(newt1, null); - do_check_false(gLWThemeChanged); - - do_execute_soon(run_test_10); - }); - }); -} - -// Uninstalling a custom theme in use should require a restart -function run_test_10() { - AddonManager.getAddonByID("theme2@tests.mozilla.org", callback_soon(function(oldt2) { - prepare_test({ - "theme2@tests.mozilla.org": [ - "onEnabling", - ], - "default@tests.mozilla.org": [ - "onDisabling" - ] - }); - - oldt2.userDisabled = false; - - ensure_test_completed(); - - restartManager(); - - AddonManager.getAddonsByIDs(["default@tests.mozilla.org", - "theme2@tests.mozilla.org"], function([d, t2]) { - do_check_true(t2.isActive); - do_check_false(t2.userDisabled); - do_check_false(t2.appDisabled); - do_check_false(d.isActive); - do_check_true(d.userDisabled); - do_check_false(d.appDisabled); - - prepare_test({ - "theme2@tests.mozilla.org": [ - "onUninstalling", - ], - "default@tests.mozilla.org": [ - "onEnabling" - ] - }); - - t2.uninstall(); - - ensure_test_completed(); - do_check_false(gLWThemeChanged); - - do_execute_soon(run_test_11); - }); - })); -} - -// Installing a custom theme not in use should not require a restart -function run_test_11() { - restartManager(); - - prepare_test({ }, [ - "onNewInstall" - ]); - - AddonManager.getInstallForFile(do_get_addon("test_theme"), function(install) { - ensure_test_completed(); - - do_check_neq(install, null); - do_check_eq(install.type, "theme"); - do_check_eq(install.version, "1.0"); - do_check_eq(install.name, "Test Theme 1"); - do_check_eq(install.state, AddonManager.STATE_DOWNLOADED); - do_check_true(install.addon.skinnable, true); - do_check_false(hasFlag(install.addon.operationsRequiringRestart, AddonManager.OP_NEEDS_RESTART_INSTALL)); - - prepare_test({ - "theme1@tests.mozilla.org": [ - ["onInstalling", false], - "onInstalled" - ] - }, [ - "onInstallStarted", - "onInstallEnded", - ], check_test_11); - install.install(); - }); -} - -function check_test_11() { - AddonManager.getAddonByID("theme1@tests.mozilla.org", function(t1) { - do_check_neq(t1, null); - var previewSpec = do_get_addon_root_uri(profileDir, "theme1@tests.mozilla.org") + "preview.png"; - do_check_eq(t1.screenshots.length, 1); - do_check_eq(t1.screenshots[0], previewSpec); - do_check_true(t1.skinnable); - do_check_false(gLWThemeChanged); - - do_execute_soon(run_test_12); - }); -} - -// Updating a custom theme not in use should not require a restart -function run_test_12() { - prepare_test({ }, [ - "onNewInstall" - ]); - - AddonManager.getInstallForFile(do_get_addon("test_theme"), function(install) { - ensure_test_completed(); - - do_check_neq(install, null); - do_check_eq(install.type, "theme"); - do_check_eq(install.version, "1.0"); - do_check_eq(install.name, "Test Theme 1"); - do_check_eq(install.state, AddonManager.STATE_DOWNLOADED); - do_check_false(hasFlag(install.addon.operationsRequiringRestart, AddonManager.OP_NEEDS_RESTART_INSTALL)); - - prepare_test({ - "theme1@tests.mozilla.org": [ - ["onInstalling", false], - "onInstalled" - ] - }, [ - "onInstallStarted", - "onInstallEnded", - ], check_test_12); - install.install(); - }); -} - -function check_test_12() { - AddonManager.getAddonByID("theme1@tests.mozilla.org", function(t1) { - do_check_neq(t1, null); - do_check_false(gLWThemeChanged); - - do_execute_soon(run_test_13); - }); -} - -// Updating a custom theme in use should require a restart -function run_test_13() { - AddonManager.getAddonByID("theme1@tests.mozilla.org", callback_soon(function(t1) { - prepare_test({ - "theme1@tests.mozilla.org": [ - "onEnabling", - ], - "default@tests.mozilla.org": [ - "onDisabling" - ] - }); - - t1.userDisabled = false; - ensure_test_completed(); - restartManager(); - - prepare_test({ }, [ - "onNewInstall" - ]); - - AddonManager.getInstallForFile(do_get_addon("test_theme"), function(install) { - ensure_test_completed(); - - do_check_neq(install, null); - do_check_eq(install.type, "theme"); - do_check_eq(install.version, "1.0"); - do_check_eq(install.name, "Test Theme 1"); - do_check_eq(install.state, AddonManager.STATE_DOWNLOADED); - do_check_true(hasFlag(install.addon.operationsRequiringRestart, AddonManager.OP_NEEDS_RESTART_INSTALL)); - - prepare_test({ - "theme1@tests.mozilla.org": [ - "onInstalling", - ] - }, [ - "onInstallStarted", - "onInstallEnded", - ], callback_soon(check_test_13)); - install.install(); - }); - })); -} - -function check_test_13() { - restartManager(); - - AddonManager.getAddonByID("theme1@tests.mozilla.org", callback_soon(function(t1) { - do_check_neq(t1, null); - do_check_true(t1.isActive); - do_check_false(gLWThemeChanged); - t1.uninstall(); - restartManager(); - - do_execute_soon(run_test_14); - })); -} - -// Switching from a lightweight theme to the default theme should not require -// a restart -function run_test_14() { - LightweightThemeManager.currentTheme = { - id: "1", - version: "1", - name: "Test LW Theme", - description: "A test theme", - author: "Mozilla", - homepageURL: "http://localhost/data/index.html", - headerURL: "http://localhost/data/header.png", - footerURL: "http://localhost/data/footer.png", - previewURL: "http://localhost/data/preview.png", - iconURL: "http://localhost/data/icon.png" - }; - - AddonManager.getAddonByID("default@tests.mozilla.org", function(d) { - do_check_true(d.userDisabled); - do_check_false(d.isActive); - - prepare_test({ - "1@personas.mozilla.org": [ - ["onDisabling", false], - "onDisabled" - ], - "default@tests.mozilla.org": [ - ["onEnabling", false], - "onEnabled" - ] - }); - - d.userDisabled = false; - ensure_test_completed(); - - do_check_false(d.userDisabled); - do_check_true(d.isActive); - - do_check_true(gLWThemeChanged); - gLWThemeChanged = false; - - do_execute_soon(run_test_15); - }); -} - -// Upgrading the application with a custom theme in use should not disable it -function run_test_15() { - restartManager(); - - installAllFiles([do_get_addon("test_theme")], function() { - AddonManager.getAddonByID("theme1@tests.mozilla.org", callback_soon(function(t1) { - t1.userDisabled = false; - - restartManager(); - - do_check_eq(Services.prefs.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN), "theme1/1.0"); - AddonManager.getAddonsByIDs(["default@tests.mozilla.org", - "theme1@tests.mozilla.org"], - callback_soon(function([d_2, t1_2]) { - do_check_true(d_2.userDisabled); - do_check_false(d_2.appDisabled); - do_check_false(d_2.isActive); - - do_check_false(t1_2.userDisabled); - do_check_false(t1_2.appDisabled); - do_check_true(t1_2.isActive); - - restartManager("2"); - - do_check_eq(Services.prefs.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN), "theme1/1.0"); - AddonManager.getAddonsByIDs(["default@tests.mozilla.org", - "theme1@tests.mozilla.org"], function([d_3, t1_3]) { - do_check_true(d_3.userDisabled); - do_check_false(d_3.appDisabled); - do_check_false(d_3.isActive); - - do_check_false(t1_3.userDisabled); - do_check_false(t1_3.appDisabled); - do_check_true(t1_3.isActive); - - do_execute_soon(run_test_16); - }); - })); - })); - }); -} - -// Upgrading the application with a custom theme in use should disable it if it -// is no longer compatible -function run_test_16() { - restartManager("3"); - - do_check_eq(Services.prefs.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN), "classic/1.0"); - AddonManager.getAddonsByIDs(["default@tests.mozilla.org", - "theme1@tests.mozilla.org"], function([d, t1]) { - do_check_false(d.userDisabled); - do_check_false(d.appDisabled); - do_check_true(d.isActive); - - do_check_true(t1.userDisabled); - do_check_true(t1.appDisabled); - do_check_false(t1.isActive); - - do_execute_soon(run_test_17); - }); -} - -// Verifies that if the selected theme pref is changed by a different version -// of the application that we correctly reset it when it points to an -// incompatible theme -function run_test_17() { - restartManager("2"); - shutdownManager(); - - Services.prefs.setCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN, "theme1/1.0"); - - restartManager("3"); - - do_check_eq(Services.prefs.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN), "classic/1.0"); - AddonManager.getAddonsByIDs(["default@tests.mozilla.org", - "theme1@tests.mozilla.org"], function([d, t1]) { - do_check_false(d.userDisabled); - do_check_false(d.appDisabled); - do_check_true(d.isActive); - - do_check_true(t1.userDisabled); - do_check_true(t1.appDisabled); - do_check_false(t1.isActive); - - do_execute_soon(run_test_18); - }); -} - -// Disabling the active theme should switch back to the default theme -function run_test_18() { - restartManager(2); - - AddonManager.getAddonByID("theme1@tests.mozilla.org", callback_soon(function(t1) { - t1.userDisabled = false; - - restartManager(); - - AddonManager.getAddonsByIDs(["default@tests.mozilla.org", - "theme1@tests.mozilla.org"], - callback_soon(function([d_2, t1_2]) { - do_check_true(d_2.userDisabled); - do_check_false(d_2.appDisabled); - do_check_false(d_2.isActive); - - do_check_false(t1_2.userDisabled); - do_check_false(t1_2.appDisabled); - do_check_true(t1_2.isActive); - - prepare_test({ - "theme1@tests.mozilla.org": [ - "onDisabling", - ], - "default@tests.mozilla.org": [ - "onEnabling", - ] - }); - t1_2.userDisabled = true; - ensure_test_completed(); - - do_check_false(d_2.userDisabled); - do_check_false(d_2.appDisabled); - do_check_false(d_2.isActive); - - do_check_true(t1_2.userDisabled); - do_check_false(t1_2.appDisabled); - do_check_true(t1_2.isActive); - - restartManager(); - - AddonManager.getAddonsByIDs(["default@tests.mozilla.org", - "theme1@tests.mozilla.org"], function([d_3, t1_3]) { - do_check_false(d_3.userDisabled); - do_check_false(d_3.appDisabled); - do_check_true(d_3.isActive); - - do_check_true(t1_3.userDisabled); - do_check_false(t1_3.appDisabled); - do_check_false(t1_3.isActive); - - do_execute_soon(run_test_19); - }); - })); - })); -} - -// Disabling the active persona should switch back to the default theme -function run_test_19() { - AddonManager.getAddonsByIDs(["default@tests.mozilla.org", - "1@personas.mozilla.org"], function([d, p1]) { - p1.userDisabled = false; - - do_check_true(d.userDisabled); - do_check_false(d.appDisabled); - do_check_false(d.isActive); - - do_check_false(p1.userDisabled); - do_check_false(p1.appDisabled); - do_check_true(p1.isActive); - - prepare_test({ - "1@personas.mozilla.org": [ - ["onDisabling", false], - "onDisabled" - ], - "default@tests.mozilla.org": [ - ["onEnabling", false], - "onEnabled" - ] - }); - p1.userDisabled = true; - ensure_test_completed(); - - do_check_false(d.userDisabled); - do_check_false(d.appDisabled); - do_check_true(d.isActive); - - do_check_true(p1.userDisabled); - do_check_false(p1.appDisabled); - do_check_false(p1.isActive); - - do_execute_soon(run_test_20); - }); -} - -// Tests that you cannot disable the default theme -function run_test_20() { - AddonManager.getAddonByID("default@tests.mozilla.org", function(d) { - do_check_false(d.userDisabled); - do_check_false(d.appDisabled); - do_check_true(d.isActive); - - try { - d.userDisabled = true; - do_throw("Disabling the default theme should throw an exception"); - } catch (e) { - } - - do_execute_soon(run_test_21); - }); -} - -// Tests that cached copies of a lightweight theme have the right permissions -// and pendingOperations during the onEnabling event -function run_test_21() { - AddonManager.getAddonByID("theme1@tests.mozilla.org", callback_soon(function(t1) { - // Switch to a custom theme so we can test pendingOperations properly. - - prepare_test({ - "theme1@tests.mozilla.org": [ - "onEnabling" - ], - "default@tests.mozilla.org": [ - "onDisabling" - ] - }); - - t1.userDisabled = false; - ensure_test_completed(); - - restartManager(); - - AddonManager.getAddonByID("1@personas.mozilla.org", function(p1) { - AddonManager.addAddonListener({ - onEnabling(aAddon) { - do_check_false(hasFlag(aAddon.permissions, AddonManager.PERM_CAN_ENABLE)); - do_check_true(hasFlag(aAddon.pendingOperations, AddonManager.PENDING_ENABLE)); - - do_check_eq(aAddon.permissions, p1.permissions); - do_check_eq(aAddon.pendingOperations, p1.pendingOperations); - } - }); - - prepare_test({ - "1@personas.mozilla.org": [ - "onEnabling" - ], - "theme1@tests.mozilla.org": [ - "onDisabling" - ] - }); - - p1.userDisabled = false; - ensure_test_completed(); - - run_test_22(); - }); - })); -} - -// Detecting a new add-on during the startup file check should not disable an -// active lightweight theme -function run_test_22() { - restartManager(); - - AddonManager.getAddonsByIDs(["default@tests.mozilla.org", - "1@personas.mozilla.org"], function([d, p1]) { - do_check_true(d.userDisabled); - do_check_false(d.appDisabled); - do_check_false(d.isActive); - - do_check_false(p1.userDisabled); - do_check_false(p1.appDisabled); - do_check_true(p1.isActive); - - writeInstallRDFForExtension({ - id: "theme3@tests.mozilla.org", - version: "1.0", - name: "Test 3", - internalName: "theme3/1.0", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "2" - }] - }, profileDir); - - restartManager(); - - AddonManager.getAddonsByIDs(["default@tests.mozilla.org", - "1@personas.mozilla.org"], function([d_2, p1_2]) { - do_check_true(d_2.userDisabled); - do_check_false(d_2.appDisabled); - do_check_false(d_2.isActive); - - do_check_false(p1_2.userDisabled); - do_check_false(p1_2.appDisabled); - do_check_true(p1_2.isActive); - - end_test(); - }); - }); -} diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_undothemeuninstall.js b/toolkit/mozapps/extensions/test/xpcshell/test_undothemeuninstall.js deleted file mode 100644 index 80eb376ac4de..000000000000 --- a/toolkit/mozapps/extensions/test/xpcshell/test_undothemeuninstall.js +++ /dev/null @@ -1,479 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ - */ - -// This verifies that forcing undo for uninstall works for themes -Components.utils.import("resource://gre/modules/LightweightThemeManager.jsm"); - -const PREF_GENERAL_SKINS_SELECTEDSKIN = "general.skins.selectedSkin"; - -var defaultTheme = { - id: "default@tests.mozilla.org", - version: "1.0", - name: "Test 1", - internalName: "classic/1.0", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "1" - }] -}; - -var theme1 = { - id: "theme1@tests.mozilla.org", - version: "1.0", - name: "Test 1", - internalName: "theme1", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "1" - }] -}; - -const profileDir = gProfD.clone(); -profileDir.append("extensions"); - -// Sets up the profile by installing an add-on. -function run_test() { - createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2"); - - startupManager(); - do_register_cleanup(promiseShutdownManager); - - run_next_test(); -} - -add_task(async function checkDefault() { - writeInstallRDFForExtension(defaultTheme, profileDir); - await promiseRestartManager(); - - let d = await promiseAddonByID("default@tests.mozilla.org"); - - do_check_neq(d, null); - do_check_true(d.isActive); - do_check_false(d.userDisabled); - do_check_eq(Services.prefs.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN), "classic/1.0"); -}); - -// Tests that uninstalling an enabled theme offers the option to undo -add_task(async function uninstallEnabledOffersUndo() { - writeInstallRDFForExtension(theme1, profileDir); - - await promiseRestartManager(); - - let t1 = await promiseAddonByID("theme1@tests.mozilla.org"); - - do_check_neq(t1, null); - do_check_true(t1.userDisabled); - - t1.userDisabled = false; - - await promiseRestartManager(); - - let d = null; - [ t1, d ] = await promiseAddonsByIDs(["theme1@tests.mozilla.org", - "default@tests.mozilla.org"]); - do_check_neq(d, null); - do_check_false(d.isActive); - do_check_true(d.userDisabled); - do_check_eq(d.pendingOperations, AddonManager.PENDING_NONE); - - do_check_neq(t1, null); - do_check_true(t1.isActive); - do_check_false(t1.userDisabled); - do_check_eq(t1.pendingOperations, AddonManager.PENDING_NONE); - - do_check_eq(Services.prefs.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN), "theme1"); - - prepare_test({ - "default@tests.mozilla.org": [ - "onEnabling" - ], - "theme1@tests.mozilla.org": [ - "onUninstalling" - ] - }); - t1.uninstall(true); - ensure_test_completed(); - - do_check_neq(d, null); - do_check_false(d.isActive); - do_check_false(d.userDisabled); - do_check_eq(d.pendingOperations, AddonManager.PENDING_ENABLE); - - do_check_true(t1.isActive); - do_check_false(t1.userDisabled); - do_check_true(hasFlag(t1.pendingOperations, AddonManager.PENDING_UNINSTALL)); - - do_check_eq(Services.prefs.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN), "theme1"); - - await promiseRestartManager(); - - [ t1, d ] = await promiseAddonsByIDs(["theme1@tests.mozilla.org", - "default@tests.mozilla.org"]); - do_check_neq(d, null); - do_check_true(d.isActive); - do_check_false(d.userDisabled); - do_check_eq(d.pendingOperations, AddonManager.PENDING_NONE); - - do_check_eq(t1, null); - - do_check_eq(Services.prefs.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN), "classic/1.0"); -}); - -// Tests that uninstalling an enabled theme can be undone -add_task(async function canUndoUninstallEnabled() { - writeInstallRDFForExtension(theme1, profileDir); - - await promiseRestartManager(); - - let t1 = await promiseAddonByID("theme1@tests.mozilla.org"); - - do_check_neq(t1, null); - do_check_true(t1.userDisabled); - - t1.userDisabled = false; - - await promiseRestartManager(); - - let d = null; - [ t1, d ] = await promiseAddonsByIDs(["theme1@tests.mozilla.org", - "default@tests.mozilla.org"]); - - do_check_neq(d, null); - do_check_false(d.isActive); - do_check_true(d.userDisabled); - do_check_eq(d.pendingOperations, AddonManager.PENDING_NONE); - - do_check_neq(t1, null); - do_check_true(t1.isActive); - do_check_false(t1.userDisabled); - do_check_eq(t1.pendingOperations, AddonManager.PENDING_NONE); - - do_check_eq(Services.prefs.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN), "theme1"); - - prepare_test({ - "default@tests.mozilla.org": [ - "onEnabling" - ], - "theme1@tests.mozilla.org": [ - "onUninstalling" - ] - }); - t1.uninstall(true); - ensure_test_completed(); - - do_check_neq(d, null); - do_check_false(d.isActive); - do_check_false(d.userDisabled); - do_check_eq(d.pendingOperations, AddonManager.PENDING_ENABLE); - - do_check_true(t1.isActive); - do_check_false(t1.userDisabled); - do_check_true(hasFlag(t1.pendingOperations, AddonManager.PENDING_UNINSTALL)); - - do_check_eq(Services.prefs.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN), "theme1"); - - prepare_test({ - "default@tests.mozilla.org": [ - "onOperationCancelled" - ], - "theme1@tests.mozilla.org": [ - "onOperationCancelled" - ] - }); - t1.cancelUninstall(); - ensure_test_completed(); - - do_check_neq(d, null); - do_check_false(d.isActive); - do_check_true(d.userDisabled); - do_check_eq(d.pendingOperations, AddonManager.PENDING_NONE); - - do_check_neq(t1, null); - do_check_true(t1.isActive); - do_check_false(t1.userDisabled); - do_check_eq(t1.pendingOperations, AddonManager.PENDING_NONE); - - await promiseRestartManager(); - - [ t1, d ] = await promiseAddonsByIDs(["theme1@tests.mozilla.org", - "default@tests.mozilla.org"]); - - do_check_neq(d, null); - do_check_false(d.isActive); - do_check_true(d.userDisabled); - do_check_eq(d.pendingOperations, AddonManager.PENDING_NONE); - - do_check_neq(t1, null); - do_check_true(t1.isActive); - do_check_false(t1.userDisabled); - do_check_eq(t1.pendingOperations, AddonManager.PENDING_NONE); - - do_check_eq(Services.prefs.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN), "theme1"); - - t1.uninstall(); - await promiseRestartManager(); -}); - -// Tests that uninstalling a disabled theme offers the option to undo -add_task(async function uninstallDisabledOffersUndo() { - writeInstallRDFForExtension(theme1, profileDir); - - await promiseRestartManager(); - - let [ t1, d ] = await promiseAddonsByIDs(["theme1@tests.mozilla.org", - "default@tests.mozilla.org"]); - - do_check_neq(d, null); - do_check_true(d.isActive); - do_check_false(d.userDisabled); - do_check_eq(d.pendingOperations, AddonManager.PENDING_NONE); - - do_check_neq(t1, null); - do_check_false(t1.isActive); - do_check_true(t1.userDisabled); - do_check_eq(t1.pendingOperations, AddonManager.PENDING_NONE); - - do_check_eq(Services.prefs.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN), "classic/1.0"); - - prepare_test({ - "theme1@tests.mozilla.org": [ - "onUninstalling" - ] - }); - t1.uninstall(true); - ensure_test_completed(); - - do_check_neq(d, null); - do_check_true(d.isActive); - do_check_false(d.userDisabled); - do_check_eq(d.pendingOperations, AddonManager.PENDING_NONE); - - do_check_false(t1.isActive); - do_check_true(t1.userDisabled); - do_check_true(hasFlag(t1.pendingOperations, AddonManager.PENDING_UNINSTALL)); - - do_check_eq(Services.prefs.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN), "classic/1.0"); - - await promiseRestartManager(); - - [ t1, d ] = await promiseAddonsByIDs(["theme1@tests.mozilla.org", - "default@tests.mozilla.org"]); - - do_check_neq(d, null); - do_check_true(d.isActive); - do_check_false(d.userDisabled); - do_check_eq(d.pendingOperations, AddonManager.PENDING_NONE); - - do_check_eq(t1, null); - - do_check_eq(Services.prefs.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN), "classic/1.0"); -}); - -// Tests that uninstalling a disabled theme can be undone -add_task(async function canUndoUninstallDisabled() { - writeInstallRDFForExtension(theme1, profileDir); - - await promiseRestartManager(); - - let [ t1, d ] = await promiseAddonsByIDs(["theme1@tests.mozilla.org", - "default@tests.mozilla.org"]); - - do_check_neq(d, null); - do_check_true(d.isActive); - do_check_false(d.userDisabled); - do_check_eq(d.pendingOperations, AddonManager.PENDING_NONE); - - do_check_neq(t1, null); - do_check_false(t1.isActive); - do_check_true(t1.userDisabled); - do_check_eq(t1.pendingOperations, AddonManager.PENDING_NONE); - - do_check_eq(Services.prefs.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN), "classic/1.0"); - - prepare_test({ - "theme1@tests.mozilla.org": [ - "onUninstalling" - ] - }); - t1.uninstall(true); - ensure_test_completed(); - - do_check_neq(d, null); - do_check_true(d.isActive); - do_check_false(d.userDisabled); - do_check_eq(d.pendingOperations, AddonManager.PENDING_NONE); - - do_check_false(t1.isActive); - do_check_true(t1.userDisabled); - do_check_true(hasFlag(t1.pendingOperations, AddonManager.PENDING_UNINSTALL)); - - do_check_eq(Services.prefs.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN), "classic/1.0"); - - prepare_test({ - "theme1@tests.mozilla.org": [ - "onOperationCancelled" - ] - }); - t1.cancelUninstall(); - ensure_test_completed(); - - do_check_neq(d, null); - do_check_true(d.isActive); - do_check_false(d.userDisabled); - do_check_eq(d.pendingOperations, AddonManager.PENDING_NONE); - - do_check_neq(t1, null); - do_check_false(t1.isActive); - do_check_true(t1.userDisabled); - do_check_eq(t1.pendingOperations, AddonManager.PENDING_NONE); - - await promiseRestartManager(); - - [ t1, d ] = await promiseAddonsByIDs(["theme1@tests.mozilla.org", - "default@tests.mozilla.org"]); - - do_check_neq(d, null); - do_check_true(d.isActive); - do_check_false(d.userDisabled); - do_check_eq(d.pendingOperations, AddonManager.PENDING_NONE); - - do_check_neq(t1, null); - do_check_false(t1.isActive); - do_check_true(t1.userDisabled); - do_check_eq(t1.pendingOperations, AddonManager.PENDING_NONE); - - do_check_eq(Services.prefs.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN), "classic/1.0"); - - t1.uninstall(); - await promiseRestartManager(); -}); - -add_task(async function uninstallWebExtensionOffersUndo() { - let { id: addonId } = await promiseInstallWebExtension({ - manifest: { - "author": "Some author", - manifest_version: 2, - name: "Web Extension Name", - version: "1.0", - theme: { images: { headerURL: "example.png" } }, - } - }); - - let [ t1, d ] = await promiseAddonsByIDs([addonId, "default@tests.mozilla.org"]); - - Assert.ok(t1, "Addon should be there"); - Assert.ok(!t1.isActive); - Assert.ok(t1.userDisabled); - Assert.equal(t1.pendingOperations, AddonManager.PENDING_NONE); - - Assert.ok(d, "Addon should be there"); - Assert.ok(d.isActive); - Assert.ok(!d.userDisabled); - Assert.equal(d.pendingOperations, AddonManager.PENDING_NONE); - - Assert.equal(Services.prefs.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN), "classic/1.0"); - - prepare_test({ [addonId]: [ "onUninstalling" ] }); - t1.uninstall(true); - ensure_test_completed(); - - Assert.ok(!t1.isActive); - Assert.ok(t1.userDisabled); - Assert.ok(hasFlag(t1.pendingOperations, AddonManager.PENDING_UNINSTALL)); - - Assert.equal(Services.prefs.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN), "classic/1.0"); - - prepare_test({ - [addonId]: [ - "onOperationCancelled" - ] - }); - t1.cancelUninstall(); - ensure_test_completed(); - - Assert.ok(!t1.isActive); - Assert.ok(t1.userDisabled); - Assert.equal(t1.pendingOperations, AddonManager.PENDING_NONE); - - await promiseRestartManager(); - - [ t1, d ] = await promiseAddonsByIDs([addonId, "default@tests.mozilla.org"]); - - Assert.ok(d); - Assert.ok(d.isActive); - Assert.ok(!d.userDisabled); - Assert.equal(d.pendingOperations, AddonManager.PENDING_NONE); - - Assert.ok(t1); - Assert.ok(!t1.isActive); - Assert.ok(t1.userDisabled); - Assert.equal(t1.pendingOperations, AddonManager.PENDING_NONE); - - Assert.equal(Services.prefs.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN), "classic/1.0"); - - t1.uninstall(); - await promiseRestartManager(); -}); - -// Tests that uninstalling an enabled lightweight theme offers the option to undo -add_task(async function uninstallLWTOffersUndo() { - // skipped since lightweight themes don't support undoable uninstall yet - - /* - LightweightThemeManager.currentTheme = dummyLWTheme("theme1"); - - let [ t1, d ] = yield promiseAddonsByIDs(["theme1@personas.mozilla.org", - "default@tests.mozilla.org"]); - - do_check_neq(d, null); - do_check_false(d.isActive); - do_check_true(d.userDisabled); - do_check_eq(d.pendingOperations, AddonManager.PENDING_NONE); - - do_check_neq(t1, null); - do_check_true(t1.isActive); - do_check_false(t1.userDisabled); - do_check_eq(t1.pendingOperations, AddonManager.PENDING_NONE); - - do_check_eq(Services.prefs.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN), "classic/1.0"); - - prepare_test({ - "default@tests.mozilla.org": [ - "onEnabling" - ], - "theme1@personas.mozilla.org": [ - "onUninstalling" - ] - }); - t1.uninstall(true); - ensure_test_completed(); - - do_check_neq(d, null); - do_check_false(d.isActive); - do_check_false(d.userDisabled); - do_check_eq(d.pendingOperations, AddonManager.PENDING_ENABLE); - - do_check_true(t1.isActive); - do_check_false(t1.userDisabled); - do_check_true(hasFlag(t1.pendingOperations, AddonManager.PENDING_UNINSTALL)); - - do_check_eq(Services.prefs.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN), "classic/1.0"); - - yield promiseRestartManager(); - - [ t1, d ] = yield promiseAddonsByIDs(["theme1@personas.mozilla.org", - "default@tests.mozilla.org"]); - - do_check_neq(d, null); - do_check_true(d.isActive); - do_check_false(d.userDisabled); - do_check_eq(d.pendingOperations, AddonManager.PENDING_NONE); - - do_check_eq(t1, null); - - do_check_eq(Services.prefs.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN), "classic/1.0"); - */ -}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_webextension_theme.js b/toolkit/mozapps/extensions/test/xpcshell/test_webextension_theme.js index 0faa2d2dad59..d0e70fff08b1 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_webextension_theme.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_webextension_theme.js @@ -9,10 +9,12 @@ */ const {LightweightThemeManager} = AM_Cu.import("resource://gre/modules/LightweightThemeManager.jsm", {}); -const THEME_IDS = ["theme1@tests.mozilla.org", "theme3@tests.mozilla.org", - "theme2@personas.mozilla.org", "default@tests.mozilla.org"]; -const REQUIRE_RESTART = { [THEME_IDS[0]]: 1 }; -const DEFAULT_THEME = THEME_IDS[3]; +const THEME_IDS = [ + "theme3@tests.mozilla.org", + "theme2@personas.mozilla.org", + "default@tests.mozilla.org" +]; +const DEFAULT_THEME = THEME_IDS[2]; const profileDir = gProfD.clone(); profileDir.append("extensions"); @@ -23,20 +25,6 @@ var gActiveTheme = null; add_task(async function setup_to_default_browserish_state() { createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2"); - writeInstallRDFForExtension({ - id: THEME_IDS[0], - version: "1.0", - name: "Test 1", - type: 4, - skinnable: true, - internalName: "theme1/1.0", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "2" - }] - }, profileDir); - await promiseWriteWebManifestForExtension({ author: "Some author", manifest_version: 2, @@ -45,11 +33,10 @@ add_task(async function setup_to_default_browserish_state() { theme: { images: { headerURL: "example.png" } }, applications: { gecko: { - id: THEME_IDS[1] + id: THEME_IDS[0] } } }, profileDir); - // We need a default theme for some of these things to work but we have hidden // the one in the application directory. writeInstallRDFForExtension({ @@ -68,7 +55,7 @@ add_task(async function setup_to_default_browserish_state() { // We can add an LWT only after the Addon Manager was started. LightweightThemeManager.currentTheme = { - id: THEME_IDS[2].substr(0, THEME_IDS[2].indexOf("@")), + id: THEME_IDS[1].substr(0, THEME_IDS[1].indexOf("@")), version: "1", name: "Bling", description: "SO MUCH BLING!", @@ -81,24 +68,21 @@ add_task(async function setup_to_default_browserish_state() { accentcolor: Math.random().toString() }; - let [ t1, t2, t3, d ] = await promiseAddonsByIDs(THEME_IDS); + let [ t1, t2, d ] = await promiseAddonsByIDs(THEME_IDS); Assert.ok(t1, "Theme addon should exist"); Assert.ok(t2, "Theme addon should exist"); - Assert.ok(t3, "Theme addon should exist"); Assert.ok(d, "Theme addon should exist"); - t1.userDisabled = t2.userDisabled = t3.userDisabled = true; + t1.userDisabled = t2.userDisabled = true; Assert.ok(!t1.isActive, "Theme should be disabled"); Assert.ok(!t2.isActive, "Theme should be disabled"); - Assert.ok(!t3.isActive, "Theme should be disabled"); Assert.ok(d.isActive, "Default theme should be active"); await promiseRestartManager(); - [ t1, t2, t3, d ] = await promiseAddonsByIDs(THEME_IDS); + [ t1, t2, d ] = await promiseAddonsByIDs(THEME_IDS); Assert.ok(!t1.isActive, "Theme should still be disabled"); Assert.ok(!t2.isActive, "Theme should still be disabled"); - Assert.ok(!t3.isActive, "Theme should still be disabled"); Assert.ok(d.isActive, "Default theme should still be active"); gActiveTheme = d.id; @@ -117,20 +101,21 @@ async function setDisabledStateAndCheck(which, disabled = false) { let themeToDisable = disabled ? which : gActiveTheme; let themeToEnable = disabled ? DEFAULT_THEME : which; - let expectRestart = !!(REQUIRE_RESTART[themeToDisable] || REQUIRE_RESTART[themeToEnable]); let expectedStates = { [themeToDisable]: true, [themeToEnable]: false }; let expectedEvents = { - [themeToDisable]: [ [ "onDisabling", expectRestart ] ], - [themeToEnable]: [ [ "onEnabling", expectRestart ] ] + [themeToDisable]: [ + [ "onDisabling", false ], + [ "onDisabled", false ], + ], + [themeToEnable]: [ + [ "onEnabling", false ], + [ "onEnabled", false ], + ] }; - if (!expectRestart) { - expectedEvents[themeToDisable].push([ "onDisabled", false ]); - expectedEvents[themeToEnable].push([ "onEnabled", false ]); - } // Set the state of the theme to change. let theme = await promiseAddonByID(which); @@ -142,17 +127,10 @@ async function setDisabledStateAndCheck(which, disabled = false) { isDisabled = (theme.id in expectedStates) ? expectedStates[theme.id] : true; Assert.equal(theme.userDisabled, isDisabled, `Theme '${theme.id}' should be ${isDisabled ? "dis" : "en"}abled`); - // Some themes need a restart to get their act together. - if (expectRestart && (theme.id == themeToEnable || theme.id == themeToDisable)) { - let expectedFlag = theme.id == themeToEnable ? AddonManager.PENDING_ENABLE : AddonManager.PENDING_DISABLE; - Assert.ok(hasFlag(theme.pendingOperations, expectedFlag), - "When expecting a restart, the pending operation flags should match"); - } else { - Assert.equal(theme.pendingOperations, AddonManager.PENDING_NONE, - "There should be no pending operations when no restart is expected"); - Assert.equal(theme.isActive, !isDisabled, - `Theme '${theme.id} should be ${isDisabled ? "in" : ""}active`); - } + Assert.equal(theme.pendingOperations, AddonManager.PENDING_NONE, + "There should be no pending operations when no restart is expected"); + Assert.equal(theme.isActive, !isDisabled, + `Theme '${theme.id} should be ${isDisabled ? "in" : ""}active`); } await promiseRestartManager(); @@ -173,55 +151,38 @@ async function setDisabledStateAndCheck(which, disabled = false) { ensure_test_completed(); } -add_task(async function test_complete_themes() { - // Enable the complete theme. +add_task(async function test_WebExtension_themes() { + // Enable the WebExtension theme. await setDisabledStateAndCheck(THEME_IDS[0]); - // Disabling the complete theme should revert to the default theme. + // Disabling WebExtension should revert to the default theme. await setDisabledStateAndCheck(THEME_IDS[0], true); // Enable it again. await setDisabledStateAndCheck(THEME_IDS[0]); - // Enabling a WebExtension theme should disable the active theme. + // Enabling an LWT should disable the active theme. await setDisabledStateAndCheck(THEME_IDS[1]); - // Switching back should disable the WebExtension theme. + // Switching back should disable the LWT. await setDisabledStateAndCheck(THEME_IDS[0]); }); -add_task(async function test_WebExtension_themes() { - // Enable the WebExtension theme. +add_task(async function test_LWTs() { + // Start with enabling an LWT. await setDisabledStateAndCheck(THEME_IDS[1]); - // Disabling WebExtension should revert to the default theme. + // Disabling LWT should revert to the default theme. await setDisabledStateAndCheck(THEME_IDS[1], true); // Enable it again. await setDisabledStateAndCheck(THEME_IDS[1]); - // Enabling an LWT should disable the active theme. - await setDisabledStateAndCheck(THEME_IDS[2]); - - // Switching back should disable the LWT. - await setDisabledStateAndCheck(THEME_IDS[1]); -}); - -add_task(async function test_LWTs() { - // Start with enabling an LWT. - await setDisabledStateAndCheck(THEME_IDS[2]); - - // Disabling LWT should revert to the default theme. - await setDisabledStateAndCheck(THEME_IDS[2], true); - - // Enable it again. - await setDisabledStateAndCheck(THEME_IDS[2]); - // Enabling a WebExtension theme should disable the active theme. - await setDisabledStateAndCheck(THEME_IDS[1]); + await setDisabledStateAndCheck(THEME_IDS[0]); // Switching back should disable the LWT. - await setDisabledStateAndCheck(THEME_IDS[2]); + await setDisabledStateAndCheck(THEME_IDS[1]); }); add_task(async function test_default_theme() { @@ -229,8 +190,49 @@ add_task(async function test_default_theme() { await setDisabledStateAndCheck(DEFAULT_THEME); // Swith to the WebExtension theme. - await setDisabledStateAndCheck(THEME_IDS[1]); + await setDisabledStateAndCheck(THEME_IDS[0]); // Enable it again. await setDisabledStateAndCheck(DEFAULT_THEME); }); + +add_task(async function uninstall_offers_undo() { + const ID = THEME_IDS[0]; + let theme = await promiseAddonByID(ID); + + Assert.ok(theme, "Webextension theme is present"); + Assert.ok(!theme.isActive, "Webextension theme is not active"); + + function promiseAddonEvent(event, id) { + return new Promise(resolve => { + let listener = { + // eslint-disable-next-line object-shorthand + [event]: function(addon) { + if (id) { + Assert.equal(addon.id, id, "Got event for expected addon"); + } + AddonManager.removeAddonListener(listener); + resolve(); + } + }; + AddonManager.addAddonListener(listener); + }); + } + + let uninstallingPromise = promiseAddonEvent("onUninstalling", ID); + theme.uninstall(true); + await uninstallingPromise; + + Assert.ok(hasFlag(theme.pendingOperations, AddonManager.PENDING_UNINSTALL), + "Theme being uninstalled has PENDING_UNINSTALL flag"); + + let cancelPromise = promiseAddonEvent("onOperationCancelled", ID); + theme.cancelUninstall(); + await cancelPromise; + + Assert.equal(theme.pendingOperations, AddonManager.PENDING_NONE, + "PENDING_UNINSTALL flag is cleared when uninstall is canceled"); + + theme.uninstall(); + await promiseRestartManager(); +}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini b/toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini index 94b947fe79d8..cb558f0ad492 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini +++ b/toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini @@ -141,9 +141,6 @@ tags = blocklist run-sequentially = Uses hardcoded ports in xpi files. [test_bug554133.js] [test_bug559800.js] -[test_bug563256.js] -# Bug 676992: test consistently fails on Android -fail-if = os == "android" [test_bug564030.js] [test_bug566626.js] [test_bug567184.js] @@ -170,7 +167,6 @@ tags = blocklist [test_bug757663.js] [test_bug953156.js] [test_checkcompatibility.js] -[test_checkCompatibility_themeOverride.js] [test_childprocess.js] [test_ChromeManifestParser.js] [test_compatoverrides.js] @@ -239,7 +235,7 @@ skip-if = os == "android" run-sequentially = Uses hardcoded ports in xpi files. [test_isDebuggable.js] [test_legacy.js] -skip-if = appname == "thunderbird" +skip-if = !allow_legacy_extensions || appname == "thunderbird" [test_locale.js] [test_locked.js] [test_locked2.js] @@ -271,25 +267,20 @@ skip-if = require_signing run-if = addon_signing [test_signed_inject.js] run-if = addon_signing +# Bug 1394122 +skip-if = true [test_signed_install.js] run-if = addon_signing run-sequentially = Uses hardcoded ports in xpi files. [test_signed_long.js] run-if = addon_signing -[test_signed_migrate.js] -run-if = addon_signing [test_startup.js] # Bug 676992: test consistently fails on Android fail-if = os == "android" [test_syncGUID.js] [test_strictcompatibility.js] [test_targetPlatforms.js] -[test_theme.js] -# Bug 676992: test consistently fails on Android -fail-if = os == "android" [test_types.js] -[test_undothemeuninstall.js] -skip-if = appname == "thunderbird" [test_undouninstall.js] skip-if = os == "win" # Bug 1358846 [test_uninstall.js] diff --git a/toolkit/themes/shared/in-content/common.inc.css b/toolkit/themes/shared/in-content/common.inc.css index 4ee33e3daa90..deca18893d0c 100644 --- a/toolkit/themes/shared/in-content/common.inc.css +++ b/toolkit/themes/shared/in-content/common.inc.css @@ -31,10 +31,10 @@ --in-content-category-background-selected-hover: rgba(12,12,13,0.15); --in-content-category-background-selected-active: rgba(12,12,13,0.2); --in-content-tab-color: #424f5a; - --in-content-link-color: #0a84ff; + --in-content-link-color: #0a8dff; --in-content-link-color-hover: #0060df; - --in-content-link-color-active: #ff9500; - --in-content-link-color-visited: #551a8b; + --in-content-link-color-active: #003eaa; + --in-content-link-color-visited: #0a8dff; --in-content-primary-button-background: #0a84ff; --in-content-primary-button-background-hover: #0060df; --in-content-primary-button-background-active: #003EAA; diff --git a/tools/mach_commands.py b/tools/mach_commands.py index c5588cd620fa..d67027a90f92 100644 --- a/tools/mach_commands.py +++ b/tools/mach_commands.py @@ -256,18 +256,43 @@ class FormatProvider(MachCommandBase): os.rename(temp, target) return target + # List of file extension to consider (should start with dot) + _format_include_extensions = ('.cpp', '.c', '.h') + # File contaning all paths to exclude from formatting + _format_ignore_file = '.clang-format-ignore' + + def _get_clang_format_diff_command(self): + if self.repository.name == 'hg': + args = ["hg", "diff", "-U0", "-r" ".^"] + for dot_extension in self._format_include_extensions: + args += ['--include', 'glob:**{0}'.format(dot_extension)] + args += ['--exclude', 'listfile:{0}'.format(self._format_ignore_file)] + else: + args = ["git", "diff", "--no-color", "-U0", "HEAD", "--"] + for dot_extension in self._format_include_extensions: + args += ['*{0}'.format(dot_extension)] + # git-diff doesn't support an 'exclude-from-files' param, but + # allow to add individual exclude pattern since v1.9, see + # https://git-scm.com/docs/gitglossary#gitglossary-aiddefpathspecapathspec + with open(self._format_ignore_file, 'rb') as exclude_pattern_file: + for pattern in exclude_pattern_file.readlines(): + pattern = pattern.rstrip() + pattern = pattern.replace('.*', '**') + if not pattern or pattern.startswith('#'): + continue # empty or comment + magics = ['exclude'] + if pattern.startswith('^'): + magics += ['top'] + pattern = pattern[1:] + args += [':({0}){1}'.format(','.join(magics), pattern)] + return args + def run_clang_format_diff(self, clang_format_diff, show): # Run clang-format on the diff # Note that this will potentially miss a lot things from subprocess import Popen, PIPE - if self.repository.name == 'hg': - diff_process = Popen(["hg", "diff", "-U0", "-r", ".^", - "--include", "glob:**.c", "--include", "glob:**.cpp", - "--include", "glob:**.h", - "--exclude", "listfile:.clang-format-ignore"], stdout=PIPE) - else: - diff_process = Popen(["git", "diff", "--no-color", "-U0", "HEAD","--","*.c","*.cpp","*.h"], stdout=PIPE) + diff_process = Popen(self._get_clang_format_diff_command(), stdout=PIPE) args = [sys.executable, clang_format_diff, "-p1"] if not show: args.append("-i") @@ -275,8 +300,7 @@ class FormatProvider(MachCommandBase): return cf_process.communicate()[0] def generate_path_list(self, paths): - pathToThirdparty = os.path.join(self.topsrcdir, - ".clang-format-ignore") + pathToThirdparty = os.path.join(self.topsrcdir, self._format_ignore_file) ignored_dir = [] for line in open(pathToThirdparty): # Remove comments and empty lines @@ -286,7 +310,7 @@ class FormatProvider(MachCommandBase): # Generates the list of regexp ignored_dir_re = '(%s)' % '|'.join(ignored_dir) - extensions = ('.cpp', '.c', '.h') + extensions = self._format_include_extensions path_list = [] for f in paths: