forked from mirrors/gecko-dev
		
	Bug 1828673 - Add support for overview page in synced tabs. r=desktop-theme-reviewers,fxview-reviewers,jsudiaman,dao,sfoster,fluent-reviewers,flod
Differential Revision: https://phabricator.services.mozilla.com/D184414
This commit is contained in:
		
							parent
							
								
									b4e65f0c36
								
							
						
					
					
						commit
						ae0fa36393
					
				
					 6 changed files with 201 additions and 95 deletions
				
			
		|  | @ -98,6 +98,9 @@ | ||||||
|           <div> |           <div> | ||||||
|             <view-recentlyclosed slot="recentlyclosed"></view-recentlyclosed> |             <view-recentlyclosed slot="recentlyclosed"></view-recentlyclosed> | ||||||
|           </div> |           </div> | ||||||
|  |           <div> | ||||||
|  |             <view-syncedtabs slot="syncedtabs"></view-syncedtabs> | ||||||
|  |           </div> | ||||||
|         </view-overview> |         </view-overview> | ||||||
|         <view-history name="history"></view-history> |         <view-history name="history"></view-history> | ||||||
|         <view-opentabs name="opentabs"></view-opentabs> |         <view-opentabs name="opentabs"></view-opentabs> | ||||||
|  |  | ||||||
|  | @ -6,6 +6,7 @@ import { | ||||||
|   html, |   html, | ||||||
|   ifDefined, |   ifDefined, | ||||||
|   styleMap, |   styleMap, | ||||||
|  |   classMap, | ||||||
|   when, |   when, | ||||||
| } from "chrome://global/content/vendor/lit.all.mjs"; | } from "chrome://global/content/vendor/lit.all.mjs"; | ||||||
| import { MozLitElement } from "chrome://global/content/lit-utils.mjs"; | import { MozLitElement } from "chrome://global/content/lit-utils.mjs"; | ||||||
|  | @ -213,35 +214,42 @@ export default class FxviewTabList extends MozLitElement { | ||||||
|         role="list" |         role="list" | ||||||
|         @keydown=${this.handleFocusElementInRow} |         @keydown=${this.handleFocusElementInRow} | ||||||
|       > |       > | ||||||
|         ${tabItems.map( |         ${tabItems.map((tabItem, i) => { | ||||||
|           (tabItem, i) => |           let time; | ||||||
|             html` |           if (tabItem.time || tabItem.closedAt) { | ||||||
|               <fxview-tab-row |             let stringTime = (tabItem.time || tabItem.closedAt).toString(); | ||||||
|                 exportparts="secondary-button" |             // Different APIs return time in different units, so we use
 | ||||||
|                 ?active=${i == activeIndex} |             // the length to decide if it's milliseconds or nanoseconds.
 | ||||||
|                 ?compact=${this.compactRows} |             if (stringTime.length === 16) { | ||||||
|                 .hasPopup=${hasPopup} |               time = (tabItem.time || tabItem.closedAt) / 1000; | ||||||
|                 .currentActiveElementId=${currentActiveElementId} |             } else { | ||||||
|                 .dateTimeFormat=${dateTimeFormat} |               time = tabItem.time || tabItem.closedAt; | ||||||
|                 .favicon=${tabItem.icon} |             } | ||||||
|                 .primaryL10nId=${tabItem.primaryL10nId} |           } | ||||||
|                 .primaryL10nArgs=${ifDefined(tabItem.primaryL10nArgs)} |           return html` | ||||||
|                 role="listitem" |             <fxview-tab-row | ||||||
|                 .secondaryL10nId=${tabItem.secondaryL10nId} |               exportparts="secondary-button" | ||||||
|                 .secondaryL10nArgs=${ifDefined(tabItem.secondaryL10nArgs)} |               ?active=${i == activeIndex} | ||||||
|                 .closedId=${ifDefined(tabItem.closedId || tabItem.closedId)} |               ?compact=${this.compactRows} | ||||||
|                 .tabElement=${ifDefined(tabItem.tabElement)} |               .hasPopup=${hasPopup} | ||||||
|                 .time=${(tabItem.time || tabItem.closedAt).toString().length === |               .currentActiveElementId=${currentActiveElementId} | ||||||
|                 16 |               .dateTimeFormat=${dateTimeFormat} | ||||||
|                   ? (tabItem.time || tabItem.closedAt) / 1000 |               .favicon=${tabItem.icon} | ||||||
|                   : tabItem.time || tabItem.closedAt} |               .primaryL10nId=${ifDefined(tabItem.primaryL10nId)} | ||||||
|                 .timeMsPref=${ifDefined(this.timeMsPref)} |               .primaryL10nArgs=${ifDefined(tabItem.primaryL10nArgs)} | ||||||
|                 .title=${tabItem.title} |               role="listitem" | ||||||
|                 .url=${tabItem.url} |               .secondaryL10nId=${ifDefined(tabItem.secondaryL10nId)} | ||||||
|               > |               .secondaryL10nArgs=${ifDefined(tabItem.secondaryL10nArgs)} | ||||||
|               </fxview-tab-row> |               .closedId=${ifDefined(tabItem.closedId || tabItem.closedId)} | ||||||
|             ` |               .tabElement=${ifDefined(tabItem.tabElement)} | ||||||
|         )} |               .time=${ifDefined(time)} | ||||||
|  |               .timeMsPref=${ifDefined(this.timeMsPref)} | ||||||
|  |               .title=${tabItem.title} | ||||||
|  |               .url=${ifDefined(tabItem.url)} | ||||||
|  |             > | ||||||
|  |             </fxview-tab-row> | ||||||
|  |           `;
 | ||||||
|  |         })} | ||||||
|       </div> |       </div> | ||||||
|       <slot name="menu"></slot> |       <slot name="menu"></slot> | ||||||
|     `;
 |     `;
 | ||||||
|  | @ -336,7 +344,7 @@ export class FxviewTabRow extends MozLitElement { | ||||||
| 
 | 
 | ||||||
|   dateFluentId(timestamp, dateTimeFormat, _nowThresholdMs = NOW_THRESHOLD_MS) { |   dateFluentId(timestamp, dateTimeFormat, _nowThresholdMs = NOW_THRESHOLD_MS) { | ||||||
|     if (!timestamp) { |     if (!timestamp) { | ||||||
|       return ""; |       return null; | ||||||
|     } |     } | ||||||
|     if (dateTimeFormat === "relative") { |     if (dateTimeFormat === "relative") { | ||||||
|       const elapsed = Date.now() - timestamp; |       const elapsed = Date.now() - timestamp; | ||||||
|  | @ -447,14 +455,17 @@ export class FxviewTabRow extends MozLitElement { | ||||||
|       /> |       /> | ||||||
|       <link rel="stylesheet" href=${this.constructor.stylesheetUrl} /> |       <link rel="stylesheet" href=${this.constructor.stylesheetUrl} /> | ||||||
|       <a |       <a | ||||||
|         href=${this.url} |         .href=${ifDefined(this.url)} | ||||||
|         class="fxview-tab-row-main" |         class=${classMap({ | ||||||
|  |           "fxview-tab-row-main": true, | ||||||
|  |           "fxview-tab-row-header": !this.url, | ||||||
|  |         })} | ||||||
|         id="fxview-tab-row-main" |         id="fxview-tab-row-main" | ||||||
|         tabindex=${this.active && |         tabindex=${this.active && | ||||||
|         this.currentActiveElementId === "fxview-tab-row-main" |         this.currentActiveElementId === "fxview-tab-row-main" | ||||||
|           ? "0" |           ? "0" | ||||||
|           : "-1"} |           : "-1"} | ||||||
|         data-l10n-id=${this.primaryL10nId} |         data-l10n-id=${ifDefined(this.primaryL10nId)} | ||||||
|         data-l10n-args=${ifDefined(this.primaryL10nArgs)} |         data-l10n-args=${ifDefined(this.primaryL10nArgs)} | ||||||
|         @click=${this.primaryActionHandler} |         @click=${this.primaryActionHandler} | ||||||
|         @keydown=${this.primaryActionHandler} |         @keydown=${this.primaryActionHandler} | ||||||
|  | @ -492,9 +503,9 @@ export class FxviewTabRow extends MozLitElement { | ||||||
|           class="fxview-tab-row-time" |           class="fxview-tab-row-time" | ||||||
|           id="fxview-tab-row-time" |           id="fxview-tab-row-time" | ||||||
|           ?hidden=${this.compact || !timeString} |           ?hidden=${this.compact || !timeString} | ||||||
|           data-timestamp=${this.time} |           data-timestamp=${ifDefined(this.time)} | ||||||
|           data-l10n-id=${ifDefined(timeString)} |           data-l10n-id=${ifDefined(timeString)} | ||||||
|           data-l10n-args=${timeArgs} |           data-l10n-args=${ifDefined(timeArgs)} | ||||||
|         > |         > | ||||||
|         </span> |         </span> | ||||||
|       </a> |       </a> | ||||||
|  |  | ||||||
|  | @ -51,6 +51,18 @@ | ||||||
|   background-color: var(--fxviewtabrow-element-background-active); |   background-color: var(--fxviewtabrow-element-background-active); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | .fxview-tab-row-header { | ||||||
|  |   margin-top: 8px; | ||||||
|  |   cursor: inherit; | ||||||
|  |   font-weight: 600; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .fxview-tab-row-header:hover, | ||||||
|  | .fxview-tab-row-header:hover:active { | ||||||
|  |   color: inherit; | ||||||
|  |   background-color: inherit; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| @media (prefers-contrast) { | @media (prefers-contrast) { | ||||||
|   .fxview-tab-row-main, |   .fxview-tab-row-main, | ||||||
|   .fxview-tab-row-main:hover, |   .fxview-tab-row-main:hover, | ||||||
|  | @ -81,7 +93,7 @@ | ||||||
|   white-space: nowrap; |   white-space: nowrap; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .fxview-tab-row-main:hover .fxview-tab-row-title { | .fxview-tab-row-main:hover:not(.fxview-tab-row-header) .fxview-tab-row-title { | ||||||
|   text-decoration-line: underline; |   text-decoration-line: underline; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -15,7 +15,11 @@ const { TabsSetupFlowManager } = ChromeUtils.importESModule( | ||||||
|   "resource:///modules/firefox-view-tabs-setup-manager.sys.mjs" |   "resource:///modules/firefox-view-tabs-setup-manager.sys.mjs" | ||||||
| ); | ); | ||||||
| 
 | 
 | ||||||
| import { html, ifDefined } from "chrome://global/content/vendor/lit.all.mjs"; | import { | ||||||
|  |   html, | ||||||
|  |   ifDefined, | ||||||
|  |   when, | ||||||
|  | } from "chrome://global/content/vendor/lit.all.mjs"; | ||||||
| import { ViewPage } from "./viewpage.mjs"; | import { ViewPage } from "./viewpage.mjs"; | ||||||
| 
 | 
 | ||||||
| const SYNCED_TABS_CHANGED = "services.sync.tabs.changed"; | const SYNCED_TABS_CHANGED = "services.sync.tabs.changed"; | ||||||
|  | @ -31,7 +35,7 @@ class SyncedTabsInView extends ViewPage { | ||||||
|     this._id = Math.floor(Math.random() * 10e6); |     this._id = Math.floor(Math.random() * 10e6); | ||||||
|     this.currentSyncedTabs = []; |     this.currentSyncedTabs = []; | ||||||
|     if (this.overview) { |     if (this.overview) { | ||||||
|       this.maxTabsLength = 5; |       this.maxTabsLength = 6; // 5 tabs plus the device row
 | ||||||
|     } else { |     } else { | ||||||
|       // Setting maxTabsLength to -1 for no max
 |       // Setting maxTabsLength to -1 for no max
 | ||||||
|       this.maxTabsLength = -1; |       this.maxTabsLength = -1; | ||||||
|  | @ -218,6 +222,7 @@ class SyncedTabsInView extends ViewPage { | ||||||
|         .descriptionLink=${ifDefined(descriptionLink)} |         .descriptionLink=${ifDefined(descriptionLink)} | ||||||
|         class="empty-state synced-tabs" |         class="empty-state synced-tabs" | ||||||
|         ?isSelectedTab=${this.selectedTab} |         ?isSelectedTab=${this.selectedTab} | ||||||
|  |         ?isInnerCard=${this.overview} | ||||||
|         mainImageUrl="${ifDefined(mainImageUrl)}" |         mainImageUrl="${ifDefined(mainImageUrl)}" | ||||||
|         headerIconUrl="${ifDefined(headerIconUrl)}" |         headerIconUrl="${ifDefined(headerIconUrl)}" | ||||||
|       > |       > | ||||||
|  | @ -284,15 +289,74 @@ class SyncedTabsInView extends ViewPage { | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   noDeviceTabsTemplate(deviceName, deviceType) { |   noDeviceTabsTemplate(deviceName, deviceType) { | ||||||
|  |     if (this.overview) { | ||||||
|  |       return html` ${this.deviceTemplate(deviceName, deviceType, [])} | ||||||
|  |         <div | ||||||
|  |           class="blackbox notabs" | ||||||
|  |           data-l10n-id="firefoxview-syncedtabs-device-notabs" | ||||||
|  |         ></div>`; | ||||||
|  |     } | ||||||
|     return html`<card-container>
 |     return html`<card-container>
 | ||||||
|       <h2 slot="header"> |       <h2 slot="header"> | ||||||
|         <div class="icon ${deviceType}" role="presentation"></div> |         <span class="icon ${deviceType}" role="presentation"></span> | ||||||
|         ${deviceName} |         ${deviceName} | ||||||
|       </h2> |       </h2> | ||||||
|       <div slot="main" class="blackbox notabs">No tabs open on this device</div> |       <div | ||||||
|  |         slot="main" | ||||||
|  |         class="blackbox notabs" | ||||||
|  |         data-l10n-id="firefoxview-syncedtabs-device-notabs" | ||||||
|  |       ></div> | ||||||
|     </card-container>`; |     </card-container>`; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   deviceTemplate(deviceName, deviceType, tabs) { | ||||||
|  |     let tabItems = this.getTabItems(tabs); | ||||||
|  |     if (this.overview) { | ||||||
|  |       /* Insert device at the beginning of the tabs array */ | ||||||
|  |       let icon; | ||||||
|  |       switch (deviceType) { | ||||||
|  |         case "phone": | ||||||
|  |           icon = "chrome://browser/skin/device-phone.svg"; | ||||||
|  |           break; | ||||||
|  |         case "desktop": | ||||||
|  |           icon = "chrome://browser/skin/device-desktop.svg"; | ||||||
|  |           break; | ||||||
|  |         case "tablet": | ||||||
|  |           icon = "chrome://browser/skin/device-tablet.svg"; | ||||||
|  |           break; | ||||||
|  |       } | ||||||
|  |       tabItems.unshift({ | ||||||
|  |         icon, | ||||||
|  |         title: deviceName, | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  |     return html`${when( | ||||||
|  |         !this.overview, | ||||||
|  |         () => html`<h2 slot="header">
 | ||||||
|  |           <span class="icon ${deviceType}" role="presentation"></span> | ||||||
|  |           ${deviceName} | ||||||
|  |         </h2>` | ||||||
|  |       )} | ||||||
|  |       <fxview-tab-list | ||||||
|  |         slot="main" | ||||||
|  |         class="syncedtabs" | ||||||
|  |         hasPopup="menu" | ||||||
|  |         .tabItems=${ifDefined(tabItems)} | ||||||
|  |         maxTabsLength=${this.maxTabsLength} | ||||||
|  |         @fxview-tab-list-primary-action=${this.onOpenLink} | ||||||
|  |         @fxview-tab-list-secondary-action=${this.onContextMenu} | ||||||
|  |       > | ||||||
|  |         ${when( | ||||||
|  |           this.overview, | ||||||
|  |           () => html`<h2 slot="header">
 | ||||||
|  |             <span class="icon ${deviceType}" role="presentation"></span> | ||||||
|  |             ${deviceName} | ||||||
|  |           </h2>` | ||||||
|  |         )} | ||||||
|  |         ${this.panelListTemplate()} | ||||||
|  |       </fxview-tab-list>`; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   generateTabList() { |   generateTabList() { | ||||||
|     let renderArray = []; |     let renderArray = []; | ||||||
|     let renderInfo = {}; |     let renderInfo = {}; | ||||||
|  | @ -320,26 +384,25 @@ class SyncedTabsInView extends ViewPage { | ||||||
| 
 | 
 | ||||||
|     for (let id in renderInfo) { |     for (let id in renderInfo) { | ||||||
|       if (renderInfo[id].tabs.length) { |       if (renderInfo[id].tabs.length) { | ||||||
|         renderArray.push(html`<card-container>
 |         if (this.overview) { | ||||||
|           <h2 slot="header"> |           renderArray.push( | ||||||
|             <div |             this.deviceTemplate( | ||||||
|               class="icon ${renderInfo[id].deviceType}" |               renderInfo[id].name, | ||||||
|               role="presentation" |               renderInfo[id].deviceType, | ||||||
|             ></div> |               renderInfo[id].tabs | ||||||
|             ${renderInfo[id].name} |             ) | ||||||
|           </h2> |           ); | ||||||
|           <fxview-tab-list |         } else { | ||||||
|             slot="main" |           renderArray.push( | ||||||
|             class="syncedtabs" |             html`<card-container
 | ||||||
|             hasPopup="menu" |               >${this.deviceTemplate( | ||||||
|             .tabItems=${ifDefined(this.getTabItems(renderInfo[id].tabs))} |                 renderInfo[id].name, | ||||||
|             maxTabsLength=${this.maxTabsLength} |                 renderInfo[id].deviceType, | ||||||
|             @fxview-tab-list-primary-action=${this.onOpenLink} |                 renderInfo[id].tabs | ||||||
|             @fxview-tab-list-secondary-action=${this.onContextMenu} |               )}</card-container | ||||||
|           > |             >` | ||||||
|             ${this.panelListTemplate()} |           ); | ||||||
|           </fxview-tab-list> |         } | ||||||
|         </card-container>`); |  | ||||||
|       } else { |       } else { | ||||||
|         renderArray.push( |         renderArray.push( | ||||||
|           this.noDeviceTabsTemplate( |           this.noDeviceTabsTemplate( | ||||||
|  | @ -351,9 +414,35 @@ class SyncedTabsInView extends ViewPage { | ||||||
|     } |     } | ||||||
|     return renderArray; |     return renderArray; | ||||||
|   } |   } | ||||||
|   render() { |  | ||||||
|     const stateIndex = this._currentSetupStateIndex; |  | ||||||
| 
 | 
 | ||||||
|  |   generateCardContent() { | ||||||
|  |     switch (this._currentSetupStateIndex) { | ||||||
|  |       case 0 /* error-state */: | ||||||
|  |         if (this.errorState) { | ||||||
|  |           return this.generateMessageCard({ error: true }); | ||||||
|  |         } | ||||||
|  |         break; | ||||||
|  |       case 1 /* not-signed-in */: | ||||||
|  |         if (Services.prefs.prefHasUserValue("services.sync.lastversion")) { | ||||||
|  |           // If this pref is set, the user has signed out of sync.
 | ||||||
|  |           // This path is also taken if we are disconnected from sync. See bug 1784055
 | ||||||
|  |           return this.generateMessageCard({ | ||||||
|  |             error: true, | ||||||
|  |             errorState: "signed-out", | ||||||
|  |           }); | ||||||
|  |         } | ||||||
|  |         return this.generateMessageCard({ action: "sign-in" }); | ||||||
|  |       case 2 /* connect-secondary-device*/: | ||||||
|  |         return this.generateMessageCard({ action: "add-device" }); | ||||||
|  |       case 3 /* disabled-tab-sync */: | ||||||
|  |         return this.generateMessageCard({ action: "sync-tabs-disabled" }); | ||||||
|  |       case 4 /* synced-tabs-loaded*/: | ||||||
|  |         return this.generateTabList(); | ||||||
|  |     } | ||||||
|  |     return html``; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   render() { | ||||||
|     this.open = |     this.open = | ||||||
|       !TabsSetupFlowManager.isTabSyncSetupComplete || |       !TabsSetupFlowManager.isTabSyncSetupComplete || | ||||||
|       Services.prefs.getBoolPref(UI_OPEN_STATE, true); |       Services.prefs.getBoolPref(UI_OPEN_STATE, true); | ||||||
|  | @ -376,34 +465,23 @@ class SyncedTabsInView extends ViewPage { | ||||||
|       </div>`); |       </div>`); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     switch (stateIndex) { |     if (this.overview) { | ||||||
|       case 0 /* error-state */: |       renderArray.push( | ||||||
|         if (this.errorState) { |         html`<card-container
 | ||||||
|           renderArray.push(this.generateMessageCard({ error: true })); |           preserveCollapseState | ||||||
|         } |           viewAllPage=${this._currentSetupStateIndex == 4 ? "syncedtabs" : null} | ||||||
|         break; |         > | ||||||
|       case 1 /* not-signed-in */: |           > | ||||||
|         if (Services.prefs.prefHasUserValue("services.sync.lastversion")) { |           <h2 | ||||||
|           // If this pref is set, the user has signed out of sync.
 |             slot="header" | ||||||
|           // This path is also taken if we are disconnected from sync. See bug 1784055
 |             data-l10n-id="firefoxview-synced-tabs-header" | ||||||
|           renderArray.push( |             class="overview-header" | ||||||
|             this.generateMessageCard({ error: true, errorState: "signed-out" }) |           ></h2> | ||||||
|           ); |           <div slot="main">${this.generateCardContent()}</div> | ||||||
|         } else { |         </card-container>` | ||||||
|           renderArray.push(this.generateMessageCard({ action: "sign-in" })); |       ); | ||||||
|         } |     } else { | ||||||
|         break; |       renderArray.push(this.generateCardContent()); | ||||||
|       case 2 /* connect-secondary-device*/: |  | ||||||
|         renderArray.push(this.generateMessageCard({ action: "add-device" })); |  | ||||||
|         break; |  | ||||||
|       case 3 /* disabled-tab-sync */: |  | ||||||
|         renderArray.push( |  | ||||||
|           this.generateMessageCard({ action: "sync-tabs-disabled" }) |  | ||||||
|         ); |  | ||||||
|         break; |  | ||||||
|       case 4 /* synced-tabs-loaded*/: |  | ||||||
|         renderArray = renderArray.concat(this.generateTabList()); |  | ||||||
|         break; |  | ||||||
|     } |     } | ||||||
|     return renderArray; |     return renderArray; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  | @ -74,17 +74,17 @@ You'll need to pass along some of the following properties: | ||||||
| * `hasPopup` (**Optional**): The optional aria-haspopup attribute for the secondary action, if required | * `hasPopup` (**Optional**): The optional aria-haspopup attribute for the secondary action, if required | ||||||
| * `maxTabsLength` (**Optional**): The max number of tabs you want to display in the tabs list. The default value will be `25` if no max value is given. You may use any negative number such as `-1` to indicate no max. | * `maxTabsLength` (**Optional**): The max number of tabs you want to display in the tabs list. The default value will be `25` if no max value is given. You may use any negative number such as `-1` to indicate no max. | ||||||
| * `tabItems` (**Required**): An array of tab data such as History nodes, Bookmark nodes, Synced Tabs, etc. | * `tabItems` (**Required**): An array of tab data such as History nodes, Bookmark nodes, Synced Tabs, etc. | ||||||
|     * The component is expecting to receive the following properties within each `tabItems` object (you may need to do some normalizing for this): |     * The component is expecting to receive the following properties within each `tabItems` object (you may need to do some normalizing for this). If you just pass a title and an icon, it creates a header row that is not clickable. | ||||||
|         * `icon` (**Required**) - The location string for the favicon. Will fallback to default favicon if none is found. |         * `icon` (**Required**) - The location string for the favicon. Will fallback to default favicon if none is found. | ||||||
|         * `primaryL10nId` (**Required**) - The l10n id to be used for the primary action element. This fluent string should ONLY define a `.title` attribute to describe the link element in each row. |         * `primaryL10nId` (**Optional**) - The l10n id to be used for the primary action element. This fluent string should ONLY define a `.title` attribute to describe the link element in each row. | ||||||
|         * `primaryL10nArgs` (**Optional**) - The l10n args you can optionally pass for the primary action element |         * `primaryL10nArgs` (**Optional**) - The l10n args you can optionally pass for the primary action element | ||||||
|         * `secondaryL10nId` (**Optional**) -  The l10n id to be used for the secondary action button. This fluent string should ONLY define a `.title` attribute to describe the secondary button in each row. |         * `secondaryL10nId` (**Optional**) -  The l10n id to be used for the secondary action button. This fluent string should ONLY define a `.title` attribute to describe the secondary button in each row. | ||||||
|         * `secondaryL10nArgs` (**Optional**) - The l10n args you can optionally pass for the secondary action button |         * `secondaryL10nArgs` (**Optional**) - The l10n args you can optionally pass for the secondary action button | ||||||
|         * `tabElement` (**Optional**) - The MozTabbrowserTab element for the tab item. |         * `tabElement` (**Optional**) - The MozTabbrowserTab element for the tab item. | ||||||
|         * `tabid` (**Optional**) - Optional property expected for Recently Closed tab data |         * `tabid` (**Optional**) - Optional property expected for Recently Closed tab data | ||||||
|         * `time` (**Required**) - The time in milliseconds for expected last interaction with the tab (Ex: `lastUsed` for SyncedTabs tabs, `closedAt` for RecentlyClosed tabs, etc.) |         * `time` (**Optional**) - The time in milliseconds for expected last interaction with the tab (Ex: `lastUsed` for SyncedTabs tabs, `closedAt` for RecentlyClosed tabs, etc.) | ||||||
|         * `title` (**Required**) - The title for the tab |         * `title` (**Required**) - The title for the tab | ||||||
|         * `url` (**Required**) - The full URL for the tab |         * `url` (**Optional**) - The full URL for the tab | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| ### Notes | ### Notes | ||||||
|  |  | ||||||
|  | @ -230,4 +230,6 @@ firefoxview-recentlyclosed-empty-header = Closed a tab too soon? | ||||||
| firefoxview-recentlyclosed-empty-description = Here you’ll find the tabs you recently closed, so you can reopen any of them quickly. | firefoxview-recentlyclosed-empty-description = Here you’ll find the tabs you recently closed, so you can reopen any of them quickly. | ||||||
| firefoxview-recentlyclosed-empty-description-two = To find tabs from longer ago, view your <a data-l10n-name="history-url">browsing history</a>. | firefoxview-recentlyclosed-empty-description-two = To find tabs from longer ago, view your <a data-l10n-name="history-url">browsing history</a>. | ||||||
| 
 | 
 | ||||||
| ## | ## This message is displayed below the name of another connected device when it doesn't have any open tabs. | ||||||
|  | 
 | ||||||
|  | firefoxview-syncedtabs-device-notabs = No tabs open on this device | ||||||
|  |  | ||||||
		Loading…
	
		Reference in a new issue
	
	 Mike Kaply
						Mike Kaply