mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-10-27 17:47:34 +09:00
[BASILISK] Remove FxA Synced Tabs Sidebar
This commit is contained in:
parent
b8e9664f47
commit
98ec471861
24 changed files with 0 additions and 2200 deletions
|
|
@ -195,9 +195,6 @@
|
||||||
key="key_gotoHistory"
|
key="key_gotoHistory"
|
||||||
observes="viewHistorySidebar"
|
observes="viewHistorySidebar"
|
||||||
label="&historyButton.label;"/>
|
label="&historyButton.label;"/>
|
||||||
<menuitem id="menu_tabsSidebar"
|
|
||||||
observes="viewTabsSidebar"
|
|
||||||
label="&syncedTabs.sidebar.label;"/>
|
|
||||||
</menupopup>
|
</menupopup>
|
||||||
</menu>
|
</menu>
|
||||||
<menuseparator/>
|
<menuseparator/>
|
||||||
|
|
|
||||||
|
|
@ -169,10 +169,6 @@
|
||||||
<broadcaster id="sync-setup-state"/>
|
<broadcaster id="sync-setup-state"/>
|
||||||
<broadcaster id="sync-syncnow-state" hidden="true"/>
|
<broadcaster id="sync-syncnow-state" hidden="true"/>
|
||||||
<broadcaster id="sync-reauth-state" hidden="true"/>
|
<broadcaster id="sync-reauth-state" hidden="true"/>
|
||||||
<broadcaster id="viewTabsSidebar" autoCheck="false" sidebartitle="&syncedTabs.sidebar.label;"
|
|
||||||
type="checkbox" group="sidebar"
|
|
||||||
sidebarurl="chrome://browser/content/syncedtabs/sidebar.xhtml"
|
|
||||||
oncommand="SidebarUI.toggle('viewTabsSidebar');"/>
|
|
||||||
<broadcaster id="workOfflineMenuitemState"/>
|
<broadcaster id="workOfflineMenuitemState"/>
|
||||||
|
|
||||||
<broadcaster id="devtoolsMenuBroadcaster_ErrorConsole"
|
<broadcaster id="devtoolsMenuBroadcaster_ErrorConsole"
|
||||||
|
|
|
||||||
|
|
@ -390,59 +390,6 @@
|
||||||
|
|
||||||
<tooltip id="dynamic-shortcut-tooltip"
|
<tooltip id="dynamic-shortcut-tooltip"
|
||||||
onpopupshowing="UpdateDynamicShortcutTooltipText(this);"/>
|
onpopupshowing="UpdateDynamicShortcutTooltipText(this);"/>
|
||||||
|
|
||||||
<menupopup id="SyncedTabsSidebarContext">
|
|
||||||
<menuitem label="&syncedTabs.context.open.label;"
|
|
||||||
accesskey="&syncedTabs.context.open.accesskey;"
|
|
||||||
id="syncedTabsOpenSelected" where="current"/>
|
|
||||||
<menuitem label="&syncedTabs.context.openInNewTab.label;"
|
|
||||||
accesskey="&syncedTabs.context.openInNewTab.accesskey;"
|
|
||||||
id="syncedTabsOpenSelectedInTab" where="tab"/>
|
|
||||||
<menuitem label="&syncedTabs.context.openInNewWindow.label;"
|
|
||||||
accesskey="&syncedTabs.context.openInNewWindow.accesskey;"
|
|
||||||
id="syncedTabsOpenSelectedInWindow" where="window"/>
|
|
||||||
<menuitem label="&syncedTabs.context.openInNewPrivateWindow.label;"
|
|
||||||
accesskey="&syncedTabs.context.openInNewPrivateWindow.accesskey;"
|
|
||||||
id="syncedTabsOpenSelectedInPrivateWindow" where="window" private="true"/>
|
|
||||||
<menuseparator/>
|
|
||||||
<menuitem label="&syncedTabs.context.bookmarkSingleTab.label;"
|
|
||||||
accesskey="&syncedTabs.context.bookmarkSingleTab.accesskey;"
|
|
||||||
id="syncedTabsBookmarkSelected"/>
|
|
||||||
<menuitem label="&syncedTabs.context.copy.label;"
|
|
||||||
accesskey="&syncedTabs.context.copy.accesskey;"
|
|
||||||
id="syncedTabsCopySelected"/>
|
|
||||||
<menuseparator/>
|
|
||||||
<menuitem label="&syncSyncNowItem.label;"
|
|
||||||
accesskey="&syncSyncNowItem.accesskey;"
|
|
||||||
id="syncedTabsRefresh"/>
|
|
||||||
</menupopup>
|
|
||||||
<menupopup id="SyncedTabsSidebarTabsFilterContext"
|
|
||||||
class="textbox-contextmenu">
|
|
||||||
<menuitem label="&undoCmd.label;"
|
|
||||||
accesskey="&undoCmd.accesskey;"
|
|
||||||
cmd="cmd_undo"/>
|
|
||||||
<menuseparator/>
|
|
||||||
<menuitem label="&cutCmd.label;"
|
|
||||||
accesskey="&cutCmd.accesskey;"
|
|
||||||
cmd="cmd_cut"/>
|
|
||||||
<menuitem label="©Cmd.label;"
|
|
||||||
accesskey="©Cmd.accesskey;"
|
|
||||||
cmd="cmd_copy"/>
|
|
||||||
<menuitem label="&pasteCmd.label;"
|
|
||||||
accesskey="&pasteCmd.accesskey;"
|
|
||||||
cmd="cmd_paste"/>
|
|
||||||
<menuitem label="&deleteCmd.label;"
|
|
||||||
accesskey="&deleteCmd.accesskey;"
|
|
||||||
cmd="cmd_delete"/>
|
|
||||||
<menuseparator/>
|
|
||||||
<menuitem label="&selectAllCmd.label;"
|
|
||||||
accesskey="&selectAllCmd.accesskey;"
|
|
||||||
cmd="cmd_selectAll"/>
|
|
||||||
<menuseparator/>
|
|
||||||
<menuitem label="&syncSyncNowItem.label;"
|
|
||||||
accesskey="&syncSyncNowItem.accesskey;"
|
|
||||||
id="syncedTabsRefreshFilter"/>
|
|
||||||
</menupopup>
|
|
||||||
</popupset>
|
</popupset>
|
||||||
|
|
||||||
#ifdef CAN_DRAW_IN_TITLEBAR
|
#ifdef CAN_DRAW_IN_TITLEBAR
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@ DIRS += [
|
||||||
'sessionstore',
|
'sessionstore',
|
||||||
'shell',
|
'shell',
|
||||||
'selfsupport',
|
'selfsupport',
|
||||||
'syncedtabs',
|
|
||||||
'translation',
|
'translation',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,45 +0,0 @@
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
||||||
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
|
|
||||||
|
|
||||||
this.EXPORTED_SYMBOLS = [
|
|
||||||
"EventEmitter"
|
|
||||||
];
|
|
||||||
|
|
||||||
// Simple event emitter abstraction for storage objects to use.
|
|
||||||
function EventEmitter () {
|
|
||||||
this._events = new Map();
|
|
||||||
}
|
|
||||||
|
|
||||||
EventEmitter.prototype = {
|
|
||||||
on(event, listener) {
|
|
||||||
if (this._events.has(event)) {
|
|
||||||
this._events.get(event).add(listener);
|
|
||||||
} else {
|
|
||||||
this._events.set(event, new Set([listener]));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
off(event, listener) {
|
|
||||||
if (!this._events.has(event)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this._events.get(event).delete(listener);
|
|
||||||
},
|
|
||||||
emit(event, ...args) {
|
|
||||||
if (!this._events.has(event)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (let listener of this._events.get(event).values()) {
|
|
||||||
try {
|
|
||||||
listener.apply(this, args);
|
|
||||||
} catch (e) {
|
|
||||||
Cu.reportError(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
@ -1,169 +0,0 @@
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
||||||
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
|
|
||||||
|
|
||||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
|
||||||
Cu.import("resource://gre/modules/Services.jsm");
|
|
||||||
|
|
||||||
Cu.import("resource:///modules/syncedtabs/SyncedTabsDeckStore.js");
|
|
||||||
Cu.import("resource:///modules/syncedtabs/SyncedTabsDeckView.js");
|
|
||||||
Cu.import("resource:///modules/syncedtabs/SyncedTabsListStore.js");
|
|
||||||
Cu.import("resource:///modules/syncedtabs/TabListComponent.js");
|
|
||||||
Cu.import("resource:///modules/syncedtabs/TabListView.js");
|
|
||||||
let { getChromeWindow } = Cu.import("resource:///modules/syncedtabs/util.js", {});
|
|
||||||
|
|
||||||
XPCOMUtils.defineLazyGetter(this, "FxAccountsCommon", function () {
|
|
||||||
return Components.utils.import("resource://gre/modules/FxAccountsCommon.js", {});
|
|
||||||
});
|
|
||||||
|
|
||||||
let log = Cu.import("resource://gre/modules/Log.jsm", {})
|
|
||||||
.Log.repository.getLogger("Sync.RemoteTabs");
|
|
||||||
|
|
||||||
this.EXPORTED_SYMBOLS = [
|
|
||||||
"SyncedTabsDeckComponent"
|
|
||||||
];
|
|
||||||
|
|
||||||
/* SyncedTabsDeckComponent
|
|
||||||
* This component instantiates views and storage objects as well as defines
|
|
||||||
* behaviors that will be passed down to the views. This helps keep the views
|
|
||||||
* isolated and easier to test.
|
|
||||||
*/
|
|
||||||
|
|
||||||
function SyncedTabsDeckComponent({
|
|
||||||
window, SyncedTabs, fxAccounts, deckStore, listStore, listComponent, DeckView, getChromeWindowMock,
|
|
||||||
}) {
|
|
||||||
this._window = window;
|
|
||||||
this._SyncedTabs = SyncedTabs;
|
|
||||||
this._fxAccounts = fxAccounts;
|
|
||||||
this._DeckView = DeckView || SyncedTabsDeckView;
|
|
||||||
// used to stub during tests
|
|
||||||
this._getChromeWindow = getChromeWindowMock || getChromeWindow;
|
|
||||||
|
|
||||||
this._deckStore = deckStore || new SyncedTabsDeckStore();
|
|
||||||
this._syncedTabsListStore = listStore || new SyncedTabsListStore(SyncedTabs);
|
|
||||||
this.tabListComponent = listComponent || new TabListComponent({
|
|
||||||
window: this._window,
|
|
||||||
store: this._syncedTabsListStore,
|
|
||||||
View: TabListView,
|
|
||||||
SyncedTabs: SyncedTabs,
|
|
||||||
clipboardHelper: Cc["@mozilla.org/widget/clipboardhelper;1"]
|
|
||||||
.getService(Ci.nsIClipboardHelper),
|
|
||||||
getChromeWindow: this._getChromeWindow,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
SyncedTabsDeckComponent.prototype = {
|
|
||||||
PANELS: {
|
|
||||||
TABS_CONTAINER: "tabs-container",
|
|
||||||
TABS_FETCHING: "tabs-fetching",
|
|
||||||
NOT_AUTHED_INFO: "notAuthedInfo",
|
|
||||||
SINGLE_DEVICE_INFO: "singleDeviceInfo",
|
|
||||||
TABS_DISABLED: "tabs-disabled",
|
|
||||||
},
|
|
||||||
|
|
||||||
get container() {
|
|
||||||
return this._deckView ? this._deckView.container : null;
|
|
||||||
},
|
|
||||||
|
|
||||||
init() {
|
|
||||||
Services.obs.addObserver(this, this._SyncedTabs.TOPIC_TABS_CHANGED, false);
|
|
||||||
Services.obs.addObserver(this, FxAccountsCommon.ONLOGIN_NOTIFICATION, false);
|
|
||||||
|
|
||||||
// Go ahead and trigger sync
|
|
||||||
this._SyncedTabs.syncTabs()
|
|
||||||
.catch(Cu.reportError);
|
|
||||||
|
|
||||||
this._deckView = new this._DeckView(this._window, this.tabListComponent, {
|
|
||||||
onAndroidClick: event => this.openAndroidLink(event),
|
|
||||||
oniOSClick: event => this.openiOSLink(event),
|
|
||||||
onSyncPrefClick: event => this.openSyncPrefs(event)
|
|
||||||
});
|
|
||||||
|
|
||||||
this._deckStore.on("change", state => this._deckView.render(state));
|
|
||||||
// Trigger the initial rendering of the deck view
|
|
||||||
// Object.values only in nightly
|
|
||||||
this._deckStore.setPanels(Object.keys(this.PANELS).map(k => this.PANELS[k]));
|
|
||||||
// Set the initial panel to display
|
|
||||||
this.updatePanel();
|
|
||||||
},
|
|
||||||
|
|
||||||
uninit() {
|
|
||||||
Services.obs.removeObserver(this, this._SyncedTabs.TOPIC_TABS_CHANGED);
|
|
||||||
Services.obs.removeObserver(this, FxAccountsCommon.ONLOGIN_NOTIFICATION);
|
|
||||||
this._deckView.destroy();
|
|
||||||
},
|
|
||||||
|
|
||||||
observe(subject, topic, data) {
|
|
||||||
switch (topic) {
|
|
||||||
case this._SyncedTabs.TOPIC_TABS_CHANGED:
|
|
||||||
this._syncedTabsListStore.getData();
|
|
||||||
this.updatePanel();
|
|
||||||
break;
|
|
||||||
case FxAccountsCommon.ONLOGIN_NOTIFICATION:
|
|
||||||
this.updatePanel();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// There's no good way to mock fxAccounts in browser tests where it's already
|
|
||||||
// been instantiated, so we have this method for stubbing.
|
|
||||||
_accountStatus() {
|
|
||||||
return this._fxAccounts.accountStatus();
|
|
||||||
},
|
|
||||||
|
|
||||||
getPanelStatus() {
|
|
||||||
return this._accountStatus().then(exists => {
|
|
||||||
if (!exists) {
|
|
||||||
return this.PANELS.NOT_AUTHED_INFO;
|
|
||||||
}
|
|
||||||
if (!this._SyncedTabs.isConfiguredToSyncTabs) {
|
|
||||||
return this.PANELS.TABS_DISABLED;
|
|
||||||
}
|
|
||||||
if (!this._SyncedTabs.hasSyncedThisSession) {
|
|
||||||
return this.PANELS.TABS_FETCHING;
|
|
||||||
}
|
|
||||||
return this._SyncedTabs.getTabClients().then(clients => {
|
|
||||||
if (clients.length) {
|
|
||||||
return this.PANELS.TABS_CONTAINER;
|
|
||||||
}
|
|
||||||
return this.PANELS.SINGLE_DEVICE_INFO;
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
Cu.reportError(err);
|
|
||||||
return this.PANELS.NOT_AUTHED_INFO;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
updatePanel() {
|
|
||||||
// return promise for tests
|
|
||||||
return this.getPanelStatus()
|
|
||||||
.then(panelId => this._deckStore.selectPanel(panelId))
|
|
||||||
.catch(Cu.reportError);
|
|
||||||
},
|
|
||||||
|
|
||||||
openAndroidLink(event) {
|
|
||||||
let href = Services.prefs.getCharPref("identity.mobilepromo.android") + "synced-tabs-sidebar";
|
|
||||||
this._openUrl(href, event);
|
|
||||||
},
|
|
||||||
|
|
||||||
openiOSLink(event) {
|
|
||||||
let href = Services.prefs.getCharPref("identity.mobilepromo.ios") + "synced-tabs-sidebar";
|
|
||||||
this._openUrl(href, event);
|
|
||||||
},
|
|
||||||
|
|
||||||
_openUrl(url, event) {
|
|
||||||
this._window.openUILink(url, event);
|
|
||||||
},
|
|
||||||
|
|
||||||
openSyncPrefs() {
|
|
||||||
this._getChromeWindow(this._window).gSyncUI.openSetup(null, "tabs-sidebar");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
@ -1,60 +0,0 @@
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
||||||
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
|
|
||||||
|
|
||||||
let { EventEmitter } = Cu.import("resource:///modules/syncedtabs/EventEmitter.jsm", {});
|
|
||||||
|
|
||||||
this.EXPORTED_SYMBOLS = [
|
|
||||||
"SyncedTabsDeckStore"
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* SyncedTabsDeckStore
|
|
||||||
*
|
|
||||||
* This store keeps track of the deck view state, including the panels and which
|
|
||||||
* one is selected. The view listens for change events on the store, which are
|
|
||||||
* triggered whenever the state changes. If it's a small change, the state
|
|
||||||
* will have `isUpdatable` set to true so the view can skip rerendering the whole
|
|
||||||
* DOM.
|
|
||||||
*/
|
|
||||||
function SyncedTabsDeckStore() {
|
|
||||||
EventEmitter.call(this);
|
|
||||||
this._panels = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
Object.assign(SyncedTabsDeckStore.prototype, EventEmitter.prototype, {
|
|
||||||
_change(isUpdatable = false) {
|
|
||||||
let panels = this._panels.map(panel => {
|
|
||||||
return {id: panel, selected: panel === this._selectedPanel};
|
|
||||||
});
|
|
||||||
this.emit("change", {panels, isUpdatable: isUpdatable});
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the selected panelId and triggers a change event.
|
|
||||||
* @param {String} panelId - ID of the panel to select.
|
|
||||||
*/
|
|
||||||
selectPanel(panelId) {
|
|
||||||
if (this._panels.indexOf(panelId) === -1 || this._selectedPanel === panelId) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this._selectedPanel = panelId;
|
|
||||||
this._change(true);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the set of panels in the deck and trigger a change event.
|
|
||||||
* @param {Array} panels - an array of IDs for each panel in the deck.
|
|
||||||
*/
|
|
||||||
setPanels(panels) {
|
|
||||||
if (panels === this._panels) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this._panels = panels || [];
|
|
||||||
this._change();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
@ -1,116 +0,0 @@
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
||||||
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
|
|
||||||
|
|
||||||
let { getChromeWindow } = Cu.import("resource:///modules/syncedtabs/util.js", {});
|
|
||||||
|
|
||||||
let log = Cu.import("resource://gre/modules/Log.jsm", {})
|
|
||||||
.Log.repository.getLogger("Sync.RemoteTabs");
|
|
||||||
|
|
||||||
this.EXPORTED_SYMBOLS = [
|
|
||||||
"SyncedTabsDeckView"
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* SyncedTabsDeckView
|
|
||||||
*
|
|
||||||
* Instances of SyncedTabsDeckView render DOM nodes from a given state.
|
|
||||||
* No state is kept internaly and the DOM will completely
|
|
||||||
* rerender unless the state flags `isUpdatable`, which helps
|
|
||||||
* make small changes without the overhead of a full rerender.
|
|
||||||
*/
|
|
||||||
const SyncedTabsDeckView = function (window, tabListComponent, props) {
|
|
||||||
this.props = props;
|
|
||||||
|
|
||||||
this._window = window;
|
|
||||||
this._doc = window.document;
|
|
||||||
|
|
||||||
this._tabListComponent = tabListComponent;
|
|
||||||
this._deckTemplate = this._doc.getElementById("deck-template");
|
|
||||||
this.container = this._doc.createElement("div");
|
|
||||||
};
|
|
||||||
|
|
||||||
SyncedTabsDeckView.prototype = {
|
|
||||||
render(state) {
|
|
||||||
if (state.isUpdatable) {
|
|
||||||
this.update(state);
|
|
||||||
} else {
|
|
||||||
this.create(state);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
create(state) {
|
|
||||||
let deck = this._doc.importNode(this._deckTemplate.content, true).firstElementChild;
|
|
||||||
this._clearChilden();
|
|
||||||
|
|
||||||
let tabListWrapper = this._doc.createElement("div");
|
|
||||||
tabListWrapper.className = "tabs-container sync-state";
|
|
||||||
this._tabListComponent.init();
|
|
||||||
tabListWrapper.appendChild(this._tabListComponent.container);
|
|
||||||
deck.appendChild(tabListWrapper);
|
|
||||||
this.container.appendChild(deck);
|
|
||||||
|
|
||||||
this._generateDevicePromo();
|
|
||||||
|
|
||||||
this._attachListeners();
|
|
||||||
this.update(state);
|
|
||||||
},
|
|
||||||
|
|
||||||
_getBrowserBundle() {
|
|
||||||
return getChromeWindow(this._window).document.getElementById("bundle_browser");
|
|
||||||
},
|
|
||||||
|
|
||||||
_generateDevicePromo() {
|
|
||||||
let bundle = this._getBrowserBundle();
|
|
||||||
let formatArgs = ["android", "ios"].map(os => {
|
|
||||||
let link = this._doc.createElement("a");
|
|
||||||
link.textContent = bundle.getString(`appMenuRemoteTabs.mobilePromo.${os}`);
|
|
||||||
link.className = `${os}-link text-link`;
|
|
||||||
link.setAttribute("href", "#");
|
|
||||||
return link.outerHTML;
|
|
||||||
});
|
|
||||||
// Put it all together...
|
|
||||||
let contents = bundle.getFormattedString("appMenuRemoteTabs.mobilePromo.text2", formatArgs);
|
|
||||||
this.container.querySelector(".device-promo").innerHTML = contents;
|
|
||||||
},
|
|
||||||
|
|
||||||
destroy() {
|
|
||||||
this._tabListComponent.uninit();
|
|
||||||
this.container.remove();
|
|
||||||
},
|
|
||||||
|
|
||||||
update(state) {
|
|
||||||
// Note that we may also want to update elements that are outside of the
|
|
||||||
// deck, so use the document to find the class names rather than our
|
|
||||||
// container.
|
|
||||||
for (let panel of state.panels) {
|
|
||||||
if (panel.selected) {
|
|
||||||
Array.prototype.map.call(this._doc.getElementsByClassName(panel.id),
|
|
||||||
item => item.classList.add("selected"));
|
|
||||||
} else {
|
|
||||||
Array.prototype.map.call(this._doc.getElementsByClassName(panel.id),
|
|
||||||
item => item.classList.remove("selected"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
_clearChilden() {
|
|
||||||
while (this.container.firstChild) {
|
|
||||||
this.container.removeChild(this.container.firstChild);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
_attachListeners() {
|
|
||||||
this.container.querySelector(".android-link").addEventListener("click", this.props.onAndroidClick);
|
|
||||||
this.container.querySelector(".ios-link").addEventListener("click", this.props.oniOSClick);
|
|
||||||
let syncPrefLinks = this.container.querySelectorAll(".sync-prefs");
|
|
||||||
for (let link of syncPrefLinks) {
|
|
||||||
link.addEventListener("click", this.props.onSyncPrefClick);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
@ -1,235 +0,0 @@
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
||||||
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
|
|
||||||
|
|
||||||
let { EventEmitter } = Cu.import("resource:///modules/syncedtabs/EventEmitter.jsm", {});
|
|
||||||
|
|
||||||
this.EXPORTED_SYMBOLS = [
|
|
||||||
"SyncedTabsListStore"
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* SyncedTabsListStore
|
|
||||||
*
|
|
||||||
* Instances of this store encapsulate all of the state associated with a synced tabs list view.
|
|
||||||
* The state includes the clients, their tabs, the row that is currently selected,
|
|
||||||
* and the filtered query.
|
|
||||||
*/
|
|
||||||
function SyncedTabsListStore(SyncedTabs) {
|
|
||||||
EventEmitter.call(this);
|
|
||||||
this._SyncedTabs = SyncedTabs;
|
|
||||||
this.data = [];
|
|
||||||
this._closedClients = {};
|
|
||||||
this._selectedRow = [-1, -1];
|
|
||||||
this.filter = "";
|
|
||||||
this.inputFocused = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Object.assign(SyncedTabsListStore.prototype, EventEmitter.prototype, {
|
|
||||||
// This internal method triggers the "change" event that views
|
|
||||||
// listen for. It denormalizes the state so that it's easier for
|
|
||||||
// the view to deal with. updateType hints to the view what
|
|
||||||
// actually needs to be rerendered or just updated, and can be
|
|
||||||
// empty (to (re)render everything), "searchbox" (to rerender just the tab list),
|
|
||||||
// or "all" (to skip rendering and just update all attributes of existing nodes).
|
|
||||||
_change(updateType) {
|
|
||||||
let selectedParent = this._selectedRow[0];
|
|
||||||
let selectedChild = this._selectedRow[1];
|
|
||||||
let rowSelected = false;
|
|
||||||
// clone the data so that consumers can't mutate internal storage
|
|
||||||
let data = Cu.cloneInto(this.data, {});
|
|
||||||
let tabCount = 0;
|
|
||||||
|
|
||||||
data.forEach((client, index) => {
|
|
||||||
client.closed = !!this._closedClients[client.id];
|
|
||||||
|
|
||||||
if (rowSelected || selectedParent < 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (this.filter) {
|
|
||||||
if (selectedParent < tabCount + client.tabs.length) {
|
|
||||||
client.tabs[selectedParent - tabCount].selected = true;
|
|
||||||
client.tabs[selectedParent - tabCount].focused = !this.inputFocused;
|
|
||||||
rowSelected = true;
|
|
||||||
} else {
|
|
||||||
tabCount += client.tabs.length;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (selectedParent === index && selectedChild === -1) {
|
|
||||||
client.selected = true;
|
|
||||||
client.focused = !this.inputFocused;
|
|
||||||
rowSelected = true;
|
|
||||||
} else if (selectedParent === index) {
|
|
||||||
client.tabs[selectedChild].selected = true;
|
|
||||||
client.tabs[selectedChild].focused = !this.inputFocused;
|
|
||||||
rowSelected = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// If this were React the view would be smart enough
|
|
||||||
// to not re-render the whole list unless necessary. But it's
|
|
||||||
// not, so updateType is a hint to the view of what actually
|
|
||||||
// needs to be rerendered.
|
|
||||||
this.emit("change", {
|
|
||||||
clients: data,
|
|
||||||
canUpdateAll: updateType === "all",
|
|
||||||
canUpdateInput: updateType === "searchbox",
|
|
||||||
filter: this.filter,
|
|
||||||
inputFocused: this.inputFocused
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Moves the row selection from a child to its parent,
|
|
||||||
* which occurs when the parent of a selected row closes.
|
|
||||||
*/
|
|
||||||
_selectParentRow() {
|
|
||||||
this._selectedRow[1] = -1;
|
|
||||||
},
|
|
||||||
|
|
||||||
_toggleBranch(id, closed) {
|
|
||||||
this._closedClients[id] = closed;
|
|
||||||
if (this._closedClients[id]) {
|
|
||||||
this._selectParentRow();
|
|
||||||
}
|
|
||||||
this._change("all");
|
|
||||||
},
|
|
||||||
|
|
||||||
_isOpen(client) {
|
|
||||||
return !this._closedClients[client.id];
|
|
||||||
},
|
|
||||||
|
|
||||||
moveSelectionDown() {
|
|
||||||
let branchRow = this._selectedRow[0];
|
|
||||||
let childRow = this._selectedRow[1];
|
|
||||||
let branch = this.data[branchRow];
|
|
||||||
|
|
||||||
if (this.filter) {
|
|
||||||
this.selectRow(branchRow + 1);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (branchRow < 0) {
|
|
||||||
this.selectRow(0, -1);
|
|
||||||
} else if ((!branch.tabs.length || childRow >= branch.tabs.length - 1 || !this._isOpen(branch)) && branchRow < this.data.length) {
|
|
||||||
this.selectRow(branchRow + 1, -1);
|
|
||||||
} else if (childRow < branch.tabs.length) {
|
|
||||||
this.selectRow(branchRow, childRow + 1);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
moveSelectionUp() {
|
|
||||||
let branchRow = this._selectedRow[0];
|
|
||||||
let childRow = this._selectedRow[1];
|
|
||||||
|
|
||||||
if (this.filter) {
|
|
||||||
this.selectRow(branchRow - 1);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (branchRow < 0) {
|
|
||||||
this.selectRow(0, -1);
|
|
||||||
} else if (childRow < 0 && branchRow > 0) {
|
|
||||||
let prevBranch = this.data[branchRow - 1];
|
|
||||||
let newChildRow = this._isOpen(prevBranch) ? prevBranch.tabs.length - 1 : -1;
|
|
||||||
this.selectRow(branchRow - 1, newChildRow);
|
|
||||||
} else if (childRow >= 0) {
|
|
||||||
this.selectRow(branchRow, childRow - 1);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// Selects a row and makes sure the selection is within bounds
|
|
||||||
selectRow(parent, child) {
|
|
||||||
let maxParentRow = this.filter ? this._tabCount() : this.data.length;
|
|
||||||
let parentRow = parent;
|
|
||||||
if (parent <= -1) {
|
|
||||||
parentRow = 0;
|
|
||||||
} else if (parent >= maxParentRow) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let childRow = child;
|
|
||||||
if (parentRow === -1 || this.filter || typeof child === "undefined" || child < -1) {
|
|
||||||
childRow = -1;
|
|
||||||
} else if (child >= this.data[parentRow].tabs.length) {
|
|
||||||
childRow = this.data[parentRow].tabs.length - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this._selectedRow[0] === parentRow && this._selectedRow[1] === childRow) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._selectedRow = [parentRow, childRow];
|
|
||||||
this.inputFocused = false;
|
|
||||||
this._change("all");
|
|
||||||
},
|
|
||||||
|
|
||||||
_tabCount() {
|
|
||||||
return this.data.reduce((prev, curr) => curr.tabs.length + prev, 0);
|
|
||||||
},
|
|
||||||
|
|
||||||
toggleBranch(id) {
|
|
||||||
this._toggleBranch(id, !this._closedClients[id]);
|
|
||||||
},
|
|
||||||
|
|
||||||
closeBranch(id) {
|
|
||||||
this._toggleBranch(id, true);
|
|
||||||
},
|
|
||||||
|
|
||||||
openBranch(id) {
|
|
||||||
this._toggleBranch(id, false);
|
|
||||||
},
|
|
||||||
|
|
||||||
focusInput() {
|
|
||||||
this.inputFocused = true;
|
|
||||||
// A change type of "all" updates rather than rebuilds, which is what we
|
|
||||||
// want here - only the selection/focus has changed.
|
|
||||||
this._change("all");
|
|
||||||
},
|
|
||||||
|
|
||||||
blurInput() {
|
|
||||||
this.inputFocused = false;
|
|
||||||
// A change type of "all" updates rather than rebuilds, which is what we
|
|
||||||
// want here - only the selection/focus has changed.
|
|
||||||
this._change("all");
|
|
||||||
},
|
|
||||||
|
|
||||||
clearFilter() {
|
|
||||||
this.filter = "";
|
|
||||||
this._selectedRow = [-1, -1];
|
|
||||||
return this.getData();
|
|
||||||
},
|
|
||||||
|
|
||||||
// Fetches data from the SyncedTabs module and triggers
|
|
||||||
// and update
|
|
||||||
getData(filter) {
|
|
||||||
let updateType;
|
|
||||||
let hasFilter = typeof filter !== "undefined";
|
|
||||||
if (hasFilter) {
|
|
||||||
this.filter = filter;
|
|
||||||
this._selectedRow = [-1, -1];
|
|
||||||
|
|
||||||
// When a filter is specified we tell the view that only the list
|
|
||||||
// needs to be rerendered so that it doesn't disrupt the input
|
|
||||||
// field's focus.
|
|
||||||
updateType = "searchbox";
|
|
||||||
}
|
|
||||||
|
|
||||||
// return promise for tests
|
|
||||||
return this._SyncedTabs.getTabClients(this.filter)
|
|
||||||
.then(result => {
|
|
||||||
if (!hasFilter) {
|
|
||||||
// Only sort clients and tabs if we're rendering the whole list.
|
|
||||||
this._SyncedTabs.sortTabClientsByLastUsed(result);
|
|
||||||
}
|
|
||||||
this.data = result;
|
|
||||||
this._change(updateType);
|
|
||||||
})
|
|
||||||
.catch(Cu.reportError);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
@ -1,138 +0,0 @@
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
||||||
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
|
|
||||||
|
|
||||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
|
||||||
|
|
||||||
let log = Cu.import("resource://gre/modules/Log.jsm", {})
|
|
||||||
.Log.repository.getLogger("Sync.RemoteTabs");
|
|
||||||
|
|
||||||
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUIUtils",
|
|
||||||
"resource:///modules/PlacesUIUtils.jsm");
|
|
||||||
|
|
||||||
this.EXPORTED_SYMBOLS = [
|
|
||||||
"TabListComponent"
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* TabListComponent
|
|
||||||
*
|
|
||||||
* The purpose of this component is to compose the view, state, and actions.
|
|
||||||
* It defines high level actions that act on the state and passes them to the
|
|
||||||
* view for it to trigger during user interaction. It also subscribes the view
|
|
||||||
* to state changes so it can rerender.
|
|
||||||
*/
|
|
||||||
|
|
||||||
function TabListComponent({window, store, View, SyncedTabs, clipboardHelper,
|
|
||||||
getChromeWindow}) {
|
|
||||||
this._window = window;
|
|
||||||
this._store = store;
|
|
||||||
this._View = View;
|
|
||||||
this._clipboardHelper = clipboardHelper;
|
|
||||||
this._getChromeWindow = getChromeWindow;
|
|
||||||
// used to trigger Sync from context menu
|
|
||||||
this._SyncedTabs = SyncedTabs;
|
|
||||||
}
|
|
||||||
|
|
||||||
TabListComponent.prototype = {
|
|
||||||
get container() {
|
|
||||||
return this._view.container;
|
|
||||||
},
|
|
||||||
|
|
||||||
init() {
|
|
||||||
log.debug("Initializing TabListComponent");
|
|
||||||
|
|
||||||
this._view = new this._View(this._window, {
|
|
||||||
onSelectRow: (...args) => this.onSelectRow(...args),
|
|
||||||
onOpenTab: (...args) => this.onOpenTab(...args),
|
|
||||||
onOpenTabs: (...args) => this.onOpenTabs(...args),
|
|
||||||
onMoveSelectionDown: (...args) => this.onMoveSelectionDown(...args),
|
|
||||||
onMoveSelectionUp: (...args) => this.onMoveSelectionUp(...args),
|
|
||||||
onToggleBranch: (...args) => this.onToggleBranch(...args),
|
|
||||||
onBookmarkTab: (...args) => this.onBookmarkTab(...args),
|
|
||||||
onCopyTabLocation: (...args) => this.onCopyTabLocation(...args),
|
|
||||||
onSyncRefresh: (...args) => this.onSyncRefresh(...args),
|
|
||||||
onFilter: (...args) => this.onFilter(...args),
|
|
||||||
onClearFilter: (...args) => this.onClearFilter(...args),
|
|
||||||
onFilterFocus: (...args) => this.onFilterFocus(...args),
|
|
||||||
onFilterBlur: (...args) => this.onFilterBlur(...args)
|
|
||||||
});
|
|
||||||
|
|
||||||
this._store.on("change", state => this._view.render(state));
|
|
||||||
this._view.render({clients: []});
|
|
||||||
// get what's already available...
|
|
||||||
this._store.getData();
|
|
||||||
this._store.focusInput();
|
|
||||||
},
|
|
||||||
|
|
||||||
uninit() {
|
|
||||||
this._view.destroy();
|
|
||||||
},
|
|
||||||
|
|
||||||
onFilter(query) {
|
|
||||||
this._store.getData(query);
|
|
||||||
},
|
|
||||||
|
|
||||||
onClearFilter() {
|
|
||||||
this._store.clearFilter();
|
|
||||||
},
|
|
||||||
|
|
||||||
onFilterFocus() {
|
|
||||||
this._store.focusInput();
|
|
||||||
},
|
|
||||||
|
|
||||||
onFilterBlur() {
|
|
||||||
this._store.blurInput();
|
|
||||||
},
|
|
||||||
|
|
||||||
onSelectRow(position) {
|
|
||||||
this._store.selectRow(position[0], position[1]);
|
|
||||||
},
|
|
||||||
|
|
||||||
onMoveSelectionDown() {
|
|
||||||
this._store.moveSelectionDown();
|
|
||||||
},
|
|
||||||
|
|
||||||
onMoveSelectionUp() {
|
|
||||||
this._store.moveSelectionUp();
|
|
||||||
},
|
|
||||||
|
|
||||||
onToggleBranch(id) {
|
|
||||||
this._store.toggleBranch(id);
|
|
||||||
},
|
|
||||||
|
|
||||||
onBookmarkTab(uri, title) {
|
|
||||||
this._window.top.PlacesCommandHook
|
|
||||||
.bookmarkLink(this._window.top.PlacesUtils.bookmarksMenuFolderId, uri, title)
|
|
||||||
.catch(Cu.reportError);
|
|
||||||
},
|
|
||||||
|
|
||||||
onOpenTab(url, where, params) {
|
|
||||||
this._window.openUILinkIn(url, where, params);
|
|
||||||
},
|
|
||||||
|
|
||||||
onOpenTabs(urls, where) {
|
|
||||||
if (!PlacesUIUtils.confirmOpenInTabs(urls.length, this._window)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (where == "window") {
|
|
||||||
this._window.openDialog(this._window.getBrowserURL(), "_blank",
|
|
||||||
"chrome,dialog=no,all", urls.join("|"));
|
|
||||||
} else {
|
|
||||||
let loadInBackground = where == "tabshifted" ? true : false;
|
|
||||||
this._getChromeWindow(this._window).gBrowser.loadTabs(urls, loadInBackground, false);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
onCopyTabLocation(url) {
|
|
||||||
this._clipboardHelper.copyString(url);
|
|
||||||
},
|
|
||||||
|
|
||||||
onSyncRefresh() {
|
|
||||||
this._SyncedTabs.syncTabs(true);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
@ -1,568 +0,0 @@
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
||||||
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
|
|
||||||
|
|
||||||
Cu.import("resource://gre/modules/Services.jsm");
|
|
||||||
|
|
||||||
let { getChromeWindow } = Cu.import("resource:///modules/syncedtabs/util.js", {});
|
|
||||||
|
|
||||||
let log = Cu.import("resource://gre/modules/Log.jsm", {})
|
|
||||||
.Log.repository.getLogger("Sync.RemoteTabs");
|
|
||||||
|
|
||||||
this.EXPORTED_SYMBOLS = [
|
|
||||||
"TabListView"
|
|
||||||
];
|
|
||||||
|
|
||||||
function getContextMenu(window) {
|
|
||||||
return getChromeWindow(window).document.getElementById("SyncedTabsSidebarContext");
|
|
||||||
}
|
|
||||||
|
|
||||||
function getTabsFilterContextMenu(window) {
|
|
||||||
return getChromeWindow(window).document.getElementById("SyncedTabsSidebarTabsFilterContext");
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* TabListView
|
|
||||||
*
|
|
||||||
* Given a state, this object will render the corresponding DOM.
|
|
||||||
* It maintains no state of it's own. It listens for DOM events
|
|
||||||
* and triggers actions that may cause the state to change and
|
|
||||||
* ultimately the view to rerender.
|
|
||||||
*/
|
|
||||||
function TabListView(window, props) {
|
|
||||||
this.props = props;
|
|
||||||
|
|
||||||
this._window = window;
|
|
||||||
this._doc = this._window.document;
|
|
||||||
|
|
||||||
this._tabsContainerTemplate = this._doc.getElementById("tabs-container-template");
|
|
||||||
this._clientTemplate = this._doc.getElementById("client-template");
|
|
||||||
this._emptyClientTemplate = this._doc.getElementById("empty-client-template");
|
|
||||||
this._tabTemplate = this._doc.getElementById("tab-template");
|
|
||||||
this.tabsFilter = this._doc.querySelector(".tabsFilter");
|
|
||||||
this.clearFilter = this._doc.querySelector(".textbox-search-clear");
|
|
||||||
this.searchBox = this._doc.querySelector(".search-box");
|
|
||||||
this.searchIcon = this._doc.querySelector(".textbox-search-icon");
|
|
||||||
|
|
||||||
this.container = this._doc.createElement("div");
|
|
||||||
|
|
||||||
this._attachFixedListeners();
|
|
||||||
|
|
||||||
this._setupContextMenu();
|
|
||||||
}
|
|
||||||
|
|
||||||
TabListView.prototype = {
|
|
||||||
render(state) {
|
|
||||||
// Don't rerender anything; just update attributes, e.g. selection
|
|
||||||
if (state.canUpdateAll) {
|
|
||||||
this._update(state);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Rerender the tab list
|
|
||||||
if (state.canUpdateInput) {
|
|
||||||
this._updateSearchBox(state);
|
|
||||||
this._createList(state);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Create the world anew
|
|
||||||
this._create(state);
|
|
||||||
},
|
|
||||||
|
|
||||||
// Create the initial DOM from templates
|
|
||||||
_create(state) {
|
|
||||||
let wrapper = this._doc.importNode(this._tabsContainerTemplate.content, true).firstElementChild;
|
|
||||||
this._clearChilden();
|
|
||||||
this.container.appendChild(wrapper);
|
|
||||||
|
|
||||||
this.list = this.container.querySelector(".list");
|
|
||||||
|
|
||||||
this._createList(state);
|
|
||||||
this._updateSearchBox(state);
|
|
||||||
|
|
||||||
this._attachListListeners();
|
|
||||||
},
|
|
||||||
|
|
||||||
_createList(state) {
|
|
||||||
this._clearChilden(this.list);
|
|
||||||
for (let client of state.clients) {
|
|
||||||
if (state.filter) {
|
|
||||||
this._renderFilteredClient(client);
|
|
||||||
} else {
|
|
||||||
this._renderClient(client);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (this.list.firstChild) {
|
|
||||||
const firstTab = this.list.firstChild.querySelector(".item.tab:first-child .item-title");
|
|
||||||
if (firstTab) {
|
|
||||||
firstTab.setAttribute("tabindex", 2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
destroy() {
|
|
||||||
this._teardownContextMenu();
|
|
||||||
this.container.remove();
|
|
||||||
},
|
|
||||||
|
|
||||||
_update(state) {
|
|
||||||
this._updateSearchBox(state);
|
|
||||||
for (let client of state.clients) {
|
|
||||||
let clientNode = this._doc.getElementById("item-" + client.id);
|
|
||||||
if (clientNode) {
|
|
||||||
this._updateClient(client, clientNode);
|
|
||||||
}
|
|
||||||
|
|
||||||
client.tabs.forEach((tab, index) => {
|
|
||||||
let tabNode = this._doc.getElementById('tab-' + client.id + '-' + index);
|
|
||||||
this._updateTab(tab, tabNode, index);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// Client rows are hidden when the list is filtered
|
|
||||||
_renderFilteredClient(client, filter) {
|
|
||||||
client.tabs.forEach((tab, index) => {
|
|
||||||
let node = this._renderTab(client, tab, index);
|
|
||||||
this.list.appendChild(node);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
_renderClient(client) {
|
|
||||||
let itemNode = client.tabs.length ?
|
|
||||||
this._createClient(client) :
|
|
||||||
this._createEmptyClient(client);
|
|
||||||
|
|
||||||
this._updateClient(client, itemNode);
|
|
||||||
|
|
||||||
let tabsList = itemNode.querySelector(".item-tabs-list");
|
|
||||||
client.tabs.forEach((tab, index) => {
|
|
||||||
let node = this._renderTab(client, tab, index);
|
|
||||||
tabsList.appendChild(node);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.list.appendChild(itemNode);
|
|
||||||
return itemNode;
|
|
||||||
},
|
|
||||||
|
|
||||||
_renderTab(client, tab, index) {
|
|
||||||
let itemNode = this._createTab(tab);
|
|
||||||
this._updateTab(tab, itemNode, index);
|
|
||||||
return itemNode;
|
|
||||||
},
|
|
||||||
|
|
||||||
_createClient(item) {
|
|
||||||
return this._doc.importNode(this._clientTemplate.content, true).firstElementChild;
|
|
||||||
},
|
|
||||||
|
|
||||||
_createEmptyClient(item) {
|
|
||||||
return this._doc.importNode(this._emptyClientTemplate.content, true).firstElementChild;
|
|
||||||
},
|
|
||||||
|
|
||||||
_createTab(item) {
|
|
||||||
return this._doc.importNode(this._tabTemplate.content, true).firstElementChild;
|
|
||||||
},
|
|
||||||
|
|
||||||
_clearChilden(node) {
|
|
||||||
let parent = node || this.container;
|
|
||||||
while (parent.firstChild) {
|
|
||||||
parent.removeChild(parent.firstChild);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// These listeners are attached only once, when we initialize the view
|
|
||||||
_attachFixedListeners() {
|
|
||||||
this.tabsFilter.addEventListener("input", this.onFilter.bind(this));
|
|
||||||
this.tabsFilter.addEventListener("focus", this.onFilterFocus.bind(this));
|
|
||||||
this.tabsFilter.addEventListener("blur", this.onFilterBlur.bind(this));
|
|
||||||
this.clearFilter.addEventListener("click", this.onClearFilter.bind(this));
|
|
||||||
this.searchIcon.addEventListener("click", this.onFilterFocus.bind(this));
|
|
||||||
},
|
|
||||||
|
|
||||||
// These listeners have to be re-created every time since we re-create the list
|
|
||||||
_attachListListeners() {
|
|
||||||
this.list.addEventListener("click", this.onClick.bind(this));
|
|
||||||
this.list.addEventListener("mouseup", this.onMouseUp.bind(this));
|
|
||||||
this.list.addEventListener("keydown", this.onKeyDown.bind(this));
|
|
||||||
},
|
|
||||||
|
|
||||||
_updateSearchBox(state) {
|
|
||||||
if (state.filter) {
|
|
||||||
this.searchBox.classList.add("filtered");
|
|
||||||
} else {
|
|
||||||
this.searchBox.classList.remove("filtered");
|
|
||||||
}
|
|
||||||
this.tabsFilter.value = state.filter;
|
|
||||||
if (state.inputFocused) {
|
|
||||||
this.searchBox.setAttribute("focused", true);
|
|
||||||
this.tabsFilter.focus();
|
|
||||||
} else {
|
|
||||||
this.searchBox.removeAttribute("focused");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the element representing an item, ensuring it's in sync with the
|
|
||||||
* underlying data.
|
|
||||||
* @param {client} item - Item to use as a source.
|
|
||||||
* @param {Element} itemNode - Element to update.
|
|
||||||
*/
|
|
||||||
_updateClient(item, itemNode) {
|
|
||||||
itemNode.setAttribute("id", "item-" + item.id);
|
|
||||||
let lastSync = new Date(item.lastModified);
|
|
||||||
let lastSyncTitle = getChromeWindow(this._window).gSyncUI.formatLastSyncDate(lastSync);
|
|
||||||
itemNode.setAttribute("title", lastSyncTitle);
|
|
||||||
if (item.closed) {
|
|
||||||
itemNode.classList.add("closed");
|
|
||||||
} else {
|
|
||||||
itemNode.classList.remove("closed");
|
|
||||||
}
|
|
||||||
if (item.selected) {
|
|
||||||
itemNode.classList.add("selected");
|
|
||||||
} else {
|
|
||||||
itemNode.classList.remove("selected");
|
|
||||||
}
|
|
||||||
if (item.isMobile) {
|
|
||||||
itemNode.classList.add("device-image-mobile");
|
|
||||||
} else {
|
|
||||||
itemNode.classList.add("device-image-desktop");
|
|
||||||
}
|
|
||||||
if (item.focused) {
|
|
||||||
itemNode.focus();
|
|
||||||
}
|
|
||||||
itemNode.dataset.id = item.id;
|
|
||||||
itemNode.querySelector(".item-title").textContent = item.name;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the element representing a tab, ensuring it's in sync with the
|
|
||||||
* underlying data.
|
|
||||||
* @param {tab} item - Item to use as a source.
|
|
||||||
* @param {Element} itemNode - Element to update.
|
|
||||||
*/
|
|
||||||
_updateTab(item, itemNode, index) {
|
|
||||||
itemNode.setAttribute("title", `${item.title}\n${item.url}`);
|
|
||||||
itemNode.setAttribute("id", "tab-" + item.client + '-' + index);
|
|
||||||
if (item.selected) {
|
|
||||||
itemNode.classList.add("selected");
|
|
||||||
} else {
|
|
||||||
itemNode.classList.remove("selected");
|
|
||||||
}
|
|
||||||
if (item.focused) {
|
|
||||||
itemNode.focus();
|
|
||||||
}
|
|
||||||
itemNode.dataset.url = item.url;
|
|
||||||
|
|
||||||
itemNode.querySelector(".item-title").textContent = item.title;
|
|
||||||
|
|
||||||
if (item.icon) {
|
|
||||||
let icon = itemNode.querySelector(".item-icon-container");
|
|
||||||
icon.style.backgroundImage = "url(" + item.icon + ")";
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
onMouseUp(event) {
|
|
||||||
if (event.which == 2) { // Middle click
|
|
||||||
this.onClick(event);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
onClick(event) {
|
|
||||||
let itemNode = this._findParentItemNode(event.target);
|
|
||||||
if (!itemNode) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (itemNode.classList.contains("tab")) {
|
|
||||||
let url = itemNode.dataset.url;
|
|
||||||
if (url) {
|
|
||||||
this.onOpenSelected(url, event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Middle click on a client
|
|
||||||
if (itemNode.classList.contains("client")) {
|
|
||||||
let where = getChromeWindow(this._window).whereToOpenLink(event);
|
|
||||||
if (where != "current") {
|
|
||||||
const tabs = itemNode.querySelector(".item-tabs-list").childNodes;
|
|
||||||
const urls = [...tabs].map(tab => tab.dataset.url);
|
|
||||||
this.props.onOpenTabs(urls, where);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.target.classList.contains("item-twisty-container")
|
|
||||||
&& event.which != 2) {
|
|
||||||
this.props.onToggleBranch(itemNode.dataset.id);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let position = this._getSelectionPosition(itemNode);
|
|
||||||
this.props.onSelectRow(position);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle a keydown event on the list box.
|
|
||||||
* @param {Event} event - Triggering event.
|
|
||||||
*/
|
|
||||||
onKeyDown(event) {
|
|
||||||
if (event.keyCode == this._window.KeyEvent.DOM_VK_DOWN) {
|
|
||||||
event.preventDefault();
|
|
||||||
this.props.onMoveSelectionDown();
|
|
||||||
} else if (event.keyCode == this._window.KeyEvent.DOM_VK_UP) {
|
|
||||||
event.preventDefault();
|
|
||||||
this.props.onMoveSelectionUp();
|
|
||||||
} else if (event.keyCode == this._window.KeyEvent.DOM_VK_RETURN) {
|
|
||||||
let selectedNode = this.container.querySelector('.item.selected');
|
|
||||||
if (selectedNode.dataset.url) {
|
|
||||||
this.onOpenSelected(selectedNode.dataset.url, event);
|
|
||||||
} else if (selectedNode) {
|
|
||||||
this.props.onToggleBranch(selectedNode.dataset.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
onBookmarkTab() {
|
|
||||||
let item = this._getSelectedTabNode();
|
|
||||||
if (item) {
|
|
||||||
let title = item.querySelector(".item-title").textContent;
|
|
||||||
this.props.onBookmarkTab(item.dataset.url, title);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
onCopyTabLocation() {
|
|
||||||
let item = this._getSelectedTabNode();
|
|
||||||
if (item) {
|
|
||||||
this.props.onCopyTabLocation(item.dataset.url);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
onOpenSelected(url, event) {
|
|
||||||
let where = getChromeWindow(this._window).whereToOpenLink(event);
|
|
||||||
this.props.onOpenTab(url, where, {});
|
|
||||||
},
|
|
||||||
|
|
||||||
onOpenSelectedFromContextMenu(event) {
|
|
||||||
let item = this._getSelectedTabNode();
|
|
||||||
if (item) {
|
|
||||||
let where = event.target.getAttribute("where");
|
|
||||||
let params = {
|
|
||||||
private: event.target.hasAttribute("private"),
|
|
||||||
};
|
|
||||||
this.props.onOpenTab(item.dataset.url, where, params);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
onFilter(event) {
|
|
||||||
let query = event.target.value;
|
|
||||||
if (query) {
|
|
||||||
this.props.onFilter(query);
|
|
||||||
} else {
|
|
||||||
this.props.onClearFilter();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
onClearFilter() {
|
|
||||||
this.props.onClearFilter();
|
|
||||||
},
|
|
||||||
|
|
||||||
onFilterFocus() {
|
|
||||||
this.props.onFilterFocus();
|
|
||||||
},
|
|
||||||
onFilterBlur() {
|
|
||||||
this.props.onFilterBlur();
|
|
||||||
},
|
|
||||||
|
|
||||||
_getSelectedTabNode() {
|
|
||||||
let item = this.container.querySelector('.item.selected');
|
|
||||||
if (this._isTab(item) && item.dataset.url) {
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
|
|
||||||
// Set up the custom context menu
|
|
||||||
_setupContextMenu() {
|
|
||||||
Services.els.addSystemEventListener(this._window, "contextmenu", this, false);
|
|
||||||
for (let getMenu of [getContextMenu, getTabsFilterContextMenu]) {
|
|
||||||
let menu = getMenu(this._window);
|
|
||||||
menu.addEventListener("popupshowing", this, true);
|
|
||||||
menu.addEventListener("command", this, true);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
_teardownContextMenu() {
|
|
||||||
// Tear down context menu
|
|
||||||
Services.els.removeSystemEventListener(this._window, "contextmenu", this, false);
|
|
||||||
for (let getMenu of [getContextMenu, getTabsFilterContextMenu]) {
|
|
||||||
let menu = getMenu(this._window);
|
|
||||||
menu.removeEventListener("popupshowing", this, true);
|
|
||||||
menu.removeEventListener("command", this, true);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
handleEvent(event) {
|
|
||||||
switch (event.type) {
|
|
||||||
case "contextmenu":
|
|
||||||
this.handleContextMenu(event);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "popupshowing": {
|
|
||||||
if (event.target.getAttribute("id") == "SyncedTabsSidebarTabsFilterContext") {
|
|
||||||
this.handleTabsFilterContextMenuShown(event);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case "command": {
|
|
||||||
let menu = event.target.closest("menupopup");
|
|
||||||
switch (menu.getAttribute("id")) {
|
|
||||||
case "SyncedTabsSidebarContext":
|
|
||||||
this.handleContentContextMenuCommand(event);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "SyncedTabsSidebarTabsFilterContext":
|
|
||||||
this.handleTabsFilterContextMenuCommand(event);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
handleTabsFilterContextMenuShown(event) {
|
|
||||||
let document = event.target.ownerDocument;
|
|
||||||
let focusedElement = document.commandDispatcher.focusedElement;
|
|
||||||
if (focusedElement != this.tabsFilter) {
|
|
||||||
this.tabsFilter.focus();
|
|
||||||
}
|
|
||||||
for (let item of event.target.children) {
|
|
||||||
if (!item.hasAttribute("cmd")) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let command = item.getAttribute("cmd");
|
|
||||||
let controller = document.commandDispatcher.getControllerForCommand(command);
|
|
||||||
if (controller.isCommandEnabled(command)) {
|
|
||||||
item.removeAttribute("disabled");
|
|
||||||
} else {
|
|
||||||
item.setAttribute("disabled", "true");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
handleContentContextMenuCommand(event) {
|
|
||||||
let id = event.target.getAttribute("id");
|
|
||||||
switch (id) {
|
|
||||||
case "syncedTabsOpenSelected":
|
|
||||||
case "syncedTabsOpenSelectedInTab":
|
|
||||||
case "syncedTabsOpenSelectedInWindow":
|
|
||||||
case "syncedTabsOpenSelectedInPrivateWindow":
|
|
||||||
this.onOpenSelectedFromContextMenu(event);
|
|
||||||
break;
|
|
||||||
case "syncedTabsBookmarkSelected":
|
|
||||||
this.onBookmarkTab();
|
|
||||||
break;
|
|
||||||
case "syncedTabsCopySelected":
|
|
||||||
this.onCopyTabLocation();
|
|
||||||
break;
|
|
||||||
case "syncedTabsRefresh":
|
|
||||||
case "syncedTabsRefreshFilter":
|
|
||||||
this.props.onSyncRefresh();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
handleTabsFilterContextMenuCommand(event) {
|
|
||||||
let command = event.target.getAttribute("cmd");
|
|
||||||
let dispatcher = getChromeWindow(this._window).document.commandDispatcher;
|
|
||||||
let controller = dispatcher.focusedElement.controllers.getControllerForCommand(command);
|
|
||||||
controller.doCommand(command);
|
|
||||||
},
|
|
||||||
|
|
||||||
handleContextMenu(event) {
|
|
||||||
let menu;
|
|
||||||
|
|
||||||
if (event.target == this.tabsFilter) {
|
|
||||||
menu = getTabsFilterContextMenu(this._window);
|
|
||||||
} else {
|
|
||||||
let itemNode = this._findParentItemNode(event.target);
|
|
||||||
if (itemNode) {
|
|
||||||
let position = this._getSelectionPosition(itemNode);
|
|
||||||
this.props.onSelectRow(position);
|
|
||||||
}
|
|
||||||
menu = getContextMenu(this._window);
|
|
||||||
this.adjustContextMenu(menu);
|
|
||||||
}
|
|
||||||
|
|
||||||
menu.openPopupAtScreen(event.screenX, event.screenY, true, event);
|
|
||||||
},
|
|
||||||
|
|
||||||
adjustContextMenu(menu) {
|
|
||||||
let item = this.container.querySelector('.item.selected');
|
|
||||||
let showTabOptions = this._isTab(item);
|
|
||||||
|
|
||||||
let el = menu.firstChild;
|
|
||||||
|
|
||||||
while (el) {
|
|
||||||
if (showTabOptions || el.getAttribute("id") === "syncedTabsRefresh") {
|
|
||||||
el.hidden = false;
|
|
||||||
} else {
|
|
||||||
el.hidden = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
el = el.nextSibling;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Find the parent item element, from a given child element.
|
|
||||||
* @param {Element} node - Child element.
|
|
||||||
* @return {Element} Element for the item, or null if not found.
|
|
||||||
*/
|
|
||||||
_findParentItemNode(node) {
|
|
||||||
while (node && node !== this.list && node !== this._doc.documentElement &&
|
|
||||||
!node.classList.contains("item")) {
|
|
||||||
node = node.parentNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (node !== this.list && node !== this._doc.documentElement) {
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
|
|
||||||
_findParentBranchNode(node) {
|
|
||||||
while (node && !node.classList.contains("list") && node !== this._doc.documentElement &&
|
|
||||||
!node.parentNode.classList.contains("list")) {
|
|
||||||
node = node.parentNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (node !== this.list && node !== this._doc.documentElement) {
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
|
|
||||||
_getSelectionPosition(itemNode) {
|
|
||||||
let parent = this._findParentBranchNode(itemNode);
|
|
||||||
let parentPosition = this._indexOfNode(parent.parentNode, parent);
|
|
||||||
let childPosition = -1;
|
|
||||||
// if the node is not a client, find its position within the parent
|
|
||||||
if (parent !== itemNode) {
|
|
||||||
childPosition = this._indexOfNode(itemNode.parentNode, itemNode);
|
|
||||||
}
|
|
||||||
return [parentPosition, childPosition];
|
|
||||||
},
|
|
||||||
|
|
||||||
_indexOfNode(parent, child) {
|
|
||||||
return Array.prototype.indexOf.call(parent.childNodes, child);
|
|
||||||
},
|
|
||||||
|
|
||||||
_isTab(item) {
|
|
||||||
return item && item.classList.contains("tab");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
# 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/.
|
|
||||||
|
|
||||||
browser.jar:
|
|
||||||
content/browser/syncedtabs/sidebar.xhtml
|
|
||||||
content/browser/syncedtabs/sidebar.js
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
# 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/.
|
|
||||||
|
|
||||||
JAR_MANIFESTS += ['jar.mn']
|
|
||||||
|
|
||||||
EXTRA_JS_MODULES.syncedtabs += [
|
|
||||||
'EventEmitter.jsm',
|
|
||||||
'SyncedTabsDeckComponent.js',
|
|
||||||
'SyncedTabsDeckStore.js',
|
|
||||||
'SyncedTabsDeckView.js',
|
|
||||||
'SyncedTabsListStore.js',
|
|
||||||
'TabListComponent.js',
|
|
||||||
'TabListView.js',
|
|
||||||
'util.js',
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
||||||
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
|
|
||||||
|
|
||||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
|
||||||
Cu.import("resource://services-sync/SyncedTabs.jsm");
|
|
||||||
Cu.import("resource:///modules/syncedtabs/SyncedTabsDeckComponent.js");
|
|
||||||
|
|
||||||
XPCOMUtils.defineLazyModuleGetter(this, "fxAccounts",
|
|
||||||
"resource://gre/modules/FxAccounts.jsm");
|
|
||||||
|
|
||||||
this.syncedTabsDeckComponent = new SyncedTabsDeckComponent({window, SyncedTabs, fxAccounts});
|
|
||||||
|
|
||||||
let onLoaded = () => {
|
|
||||||
syncedTabsDeckComponent.init();
|
|
||||||
document.getElementById("template-container").appendChild(syncedTabsDeckComponent.container);
|
|
||||||
};
|
|
||||||
|
|
||||||
let onUnloaded = () => {
|
|
||||||
removeEventListener("DOMContentLoaded", onLoaded);
|
|
||||||
removeEventListener("unload", onUnloaded);
|
|
||||||
syncedTabsDeckComponent.uninit();
|
|
||||||
};
|
|
||||||
|
|
||||||
addEventListener("DOMContentLoaded", onLoaded);
|
|
||||||
addEventListener("unload", onUnloaded);
|
|
||||||
|
|
@ -1,114 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!-- 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/. -->
|
|
||||||
|
|
||||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
|
||||||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd" [
|
|
||||||
<!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd">
|
|
||||||
%browserDTD;
|
|
||||||
<!ENTITY % globalDTD
|
|
||||||
SYSTEM "chrome://global/locale/global.dtd">
|
|
||||||
%globalDTD;
|
|
||||||
<!ENTITY % syncBrandDTD
|
|
||||||
SYSTEM "chrome://browser/locale/syncBrand.dtd">
|
|
||||||
%syncBrandDTD;
|
|
||||||
]>
|
|
||||||
<html xmlns="http://www.w3.org/1999/xhtml"
|
|
||||||
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
|
||||||
<head>
|
|
||||||
<script src="chrome://browser/content/syncedtabs/sidebar.js" type="application/javascript;version=1.8"></script>
|
|
||||||
<script type="application/javascript" src="chrome://browser/content/utilityOverlay.js"/>
|
|
||||||
|
|
||||||
<link rel="stylesheet" type="text/css" media="all" href="chrome://browser/skin/syncedtabs/sidebar.css"/>
|
|
||||||
<link rel="stylesheet" type="text/css" media="all" href="chrome://global/skin/"/>
|
|
||||||
<link rel="stylesheet" type="text/css" media="all" href="chrome://global/skin/textbox.css"/>
|
|
||||||
<link rel="stylesheet" type="text/css" media="all" href="chrome://browser/content/browser.css"/>
|
|
||||||
<title>&syncedTabs.sidebar.label;</title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body dir="&locale.dir;" role="application">
|
|
||||||
<template id="client-template">
|
|
||||||
<div class="item client" role="option" tabindex="-1">
|
|
||||||
<div class="item-title-container">
|
|
||||||
<div class="item-twisty-container"></div>
|
|
||||||
<div class="item-icon-container"></div>
|
|
||||||
<p class="item-title"></p>
|
|
||||||
</div>
|
|
||||||
<div class="item-tabs-list"></div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<template id="empty-client-template">
|
|
||||||
<div class="item empty client" role="option" tabindex="-1">
|
|
||||||
<div class="item-title-container">
|
|
||||||
<div class="item-twisty-container"></div>
|
|
||||||
<div class="item-icon-container"></div>
|
|
||||||
<p class="item-title"></p>
|
|
||||||
</div>
|
|
||||||
<div class="item-tabs-list">
|
|
||||||
<div class="item empty" role="option" tabindex="-1">
|
|
||||||
<div class="item-title-container">
|
|
||||||
<div class="item-icon-container"></div>
|
|
||||||
<p class="item-title">&syncedTabs.sidebar.notabs.label;</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<template id="tab-template">
|
|
||||||
<div class="item tab" role="option" tabindex="-1">
|
|
||||||
<div class="item-title-container">
|
|
||||||
<div class="item-icon-container"></div>
|
|
||||||
<p class="item-title"></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template id="tabs-container-template">
|
|
||||||
<div class="tabs-container">
|
|
||||||
<div class="list" role="listbox"></div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template id="deck-template">
|
|
||||||
<div class="deck">
|
|
||||||
<div class="tabs-fetching sync-state">
|
|
||||||
<!-- Show intentionally blank panel, see bug 1239845 -->
|
|
||||||
</div>
|
|
||||||
<div class="notAuthedInfo sync-state">
|
|
||||||
<p>&syncedTabs.sidebar.notsignedin.label;</p>
|
|
||||||
<p><a href="#" class="sync-prefs text-link">&fxaSignIn.label;</a></p>
|
|
||||||
</div>
|
|
||||||
<div class="singleDeviceInfo sync-state">
|
|
||||||
<p>&syncedTabs.sidebar.noclients.title;</p>
|
|
||||||
<p>&syncedTabs.sidebar.noclients.subtitle;</p>
|
|
||||||
<p class="device-promo" fxAccountsBrand="&syncBrand.fxAccount.label;"></p>
|
|
||||||
</div>
|
|
||||||
<div class="tabs-disabled sync-state">
|
|
||||||
<p>&syncedTabs.sidebar.tabsnotsyncing.label;</p>
|
|
||||||
<p><a href="#" class="sync-prefs text-link">&syncedTabs.sidebar.openprefs.label;</a></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<div class="content-container">
|
|
||||||
<!-- the non-scrollable header -->
|
|
||||||
<div class="content-header">
|
|
||||||
<div class="sidebar-search-container tabs-container sync-state">
|
|
||||||
<div class="search-box compact">
|
|
||||||
<div class="textbox-input-box">
|
|
||||||
<input type="text" class="tabsFilter textbox-input" tabindex="1"/>
|
|
||||||
<div class="textbox-search-icons">
|
|
||||||
<a class="textbox-search-clear"></a>
|
|
||||||
<a class="textbox-search-icon"></a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- the scrollable content area where our templates are inserted -->
|
|
||||||
<div id="template-container" class="content-scrollable" tabindex="-1">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
||||||
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
|
|
||||||
|
|
||||||
this.EXPORTED_SYMBOLS = [
|
|
||||||
"getChromeWindow"
|
|
||||||
];
|
|
||||||
|
|
||||||
// Get the chrome (ie, browser) window hosting this content.
|
|
||||||
function getChromeWindow(window) {
|
|
||||||
return window
|
|
||||||
.QueryInterface(Ci.nsIInterfaceRequestor)
|
|
||||||
.getInterface(Ci.nsIWebNavigation)
|
|
||||||
.QueryInterface(Ci.nsIDocShellTreeItem)
|
|
||||||
.rootTreeItem
|
|
||||||
.QueryInterface(Ci.nsIInterfaceRequestor)
|
|
||||||
.getInterface(Ci.nsIDOMWindow)
|
|
||||||
.wrappedJSObject;
|
|
||||||
}
|
|
||||||
|
|
@ -726,31 +726,6 @@ you can use these alternative items. Otherwise, their values should be empty. -
|
||||||
<!-- LOCALIZATION NOTE (syncTabsMenu3.label): This appears in the history menu -->
|
<!-- LOCALIZATION NOTE (syncTabsMenu3.label): This appears in the history menu -->
|
||||||
<!ENTITY syncTabsMenu3.label "Synced Tabs">
|
<!ENTITY syncTabsMenu3.label "Synced Tabs">
|
||||||
|
|
||||||
<!ENTITY syncedTabs.sidebar.label "Synced Tabs">
|
|
||||||
<!ENTITY syncedTabs.sidebar.noclients.label "Sign in to Firefox from your other devices to view their tabs here.">
|
|
||||||
<!ENTITY syncedTabs.sidebar.noclients.title "No synced tabs… yet!">
|
|
||||||
<!ENTITY syncedTabs.sidebar.noclients.subtitle "Want to see your tabs from other devices here?">
|
|
||||||
<!ENTITY syncedTabs.sidebar.notsignedin.label "Sign in to view a list of tabs from your other devices.">
|
|
||||||
<!ENTITY syncedTabs.sidebar.notabs.label "No open tabs">
|
|
||||||
<!ENTITY syncedTabs.sidebar.openprefs.label "Open &syncBrand.shortName.label; Preferences">
|
|
||||||
<!-- LOCALIZATION NOTE (syncedTabs.sidebar.tabsnotsyncing.label): This is shown
|
|
||||||
when Sync is configured but syncing tabs is disabled. -->
|
|
||||||
<!ENTITY syncedTabs.sidebar.tabsnotsyncing.label "Turn on tab syncing to view a list of tabs from your other devices.">
|
|
||||||
|
|
||||||
<!ENTITY syncedTabs.context.open.label "Open">
|
|
||||||
<!ENTITY syncedTabs.context.open.accesskey "O">
|
|
||||||
<!ENTITY syncedTabs.context.openInNewTab.label "Open in a New Tab">
|
|
||||||
<!ENTITY syncedTabs.context.openInNewTab.accesskey "w">
|
|
||||||
<!ENTITY syncedTabs.context.openInNewWindow.label "Open in a New Window">
|
|
||||||
<!ENTITY syncedTabs.context.openInNewWindow.accesskey "N">
|
|
||||||
<!ENTITY syncedTabs.context.openInNewPrivateWindow.label "Open in a New Private Window">
|
|
||||||
<!ENTITY syncedTabs.context.openInNewPrivateWindow.accesskey "P">
|
|
||||||
<!ENTITY syncedTabs.context.bookmarkSingleTab.label "Bookmark This Tab…">
|
|
||||||
<!ENTITY syncedTabs.context.bookmarkSingleTab.accesskey "B">
|
|
||||||
<!ENTITY syncedTabs.context.copy.label "Copy">
|
|
||||||
<!ENTITY syncedTabs.context.copy.accesskey "C">
|
|
||||||
|
|
||||||
|
|
||||||
<!ENTITY syncBrand.shortName.label "Sync">
|
<!ENTITY syncBrand.shortName.label "Sync">
|
||||||
|
|
||||||
<!ENTITY syncSignIn.label "Sign In To &syncBrand.shortName.label;…">
|
<!ENTITY syncSignIn.label "Sign In To &syncBrand.shortName.label;…">
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ browser.jar:
|
||||||
skin/classic/browser/sanitizeDialog.css
|
skin/classic/browser/sanitizeDialog.css
|
||||||
skin/classic/browser/aboutSessionRestore-window-icon.png
|
skin/classic/browser/aboutSessionRestore-window-icon.png
|
||||||
skin/classic/browser/aboutSyncTabs.css
|
skin/classic/browser/aboutSyncTabs.css
|
||||||
* skin/classic/browser/syncedtabs/sidebar.css (syncedtabs/sidebar.css)
|
|
||||||
skin/classic/browser/actionicon-tab.png
|
skin/classic/browser/actionicon-tab.png
|
||||||
* skin/classic/browser/browser.css
|
* skin/classic/browser/browser.css
|
||||||
* skin/classic/browser/devedition.css
|
* skin/classic/browser/devedition.css
|
||||||
|
|
|
||||||
|
|
@ -1,69 +0,0 @@
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
||||||
|
|
||||||
%include ../../shared/syncedtabs/sidebar.inc.css
|
|
||||||
|
|
||||||
/* These styles are intended to mimic XUL trees and the XUL search box. */
|
|
||||||
|
|
||||||
html {
|
|
||||||
border: 1px solid ThreeDShadow;
|
|
||||||
background-color: -moz-Field;
|
|
||||||
color: -moz-FieldText;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item {
|
|
||||||
padding-inline-end: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item-title {
|
|
||||||
margin: 1px 0 0;
|
|
||||||
margin-inline-end: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.search-box {
|
|
||||||
-moz-appearance: textfield;
|
|
||||||
cursor: text;
|
|
||||||
margin: 2px 4px;
|
|
||||||
border: 2px solid;
|
|
||||||
-moz-border-top-colors: ThreeDShadow ThreeDDarkShadow;
|
|
||||||
-moz-border-right-colors: ThreeDHighlight ThreeDLightShadow;
|
|
||||||
-moz-border-bottom-colors: ThreeDHighlight ThreeDLightShadow;
|
|
||||||
-moz-border-left-colors: ThreeDShadow ThreeDDarkShadow;
|
|
||||||
padding: 2px 2px 3px;
|
|
||||||
padding-inline-start: 4px;
|
|
||||||
background-color: -moz-Field;
|
|
||||||
color: -moz-FieldText;
|
|
||||||
}
|
|
||||||
|
|
||||||
.textbox-search-clear {
|
|
||||||
background-image: url(moz-icon://stock/gtk-clear?size=menu);
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.textbox-search-icon {
|
|
||||||
background-image: url(moz-icon://stock/gtk-find?size=menu);
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.textbox-search-icon[searchbutton]:not([disabled]) ,
|
|
||||||
.textbox-search-clear:not([disabled]) {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item.client .item-twisty-container {
|
|
||||||
-moz-appearance: treetwistyopen;
|
|
||||||
margin-top: 3px;
|
|
||||||
margin-left: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item.client.closed .item-twisty-container {
|
|
||||||
-moz-appearance: treetwisty;
|
|
||||||
}
|
|
||||||
|
|
@ -8,7 +8,6 @@ browser.jar:
|
||||||
skin/classic/browser/sanitizeDialog.css
|
skin/classic/browser/sanitizeDialog.css
|
||||||
skin/classic/browser/aboutSessionRestore-window-icon.png
|
skin/classic/browser/aboutSessionRestore-window-icon.png
|
||||||
skin/classic/browser/aboutSyncTabs.css
|
skin/classic/browser/aboutSyncTabs.css
|
||||||
* skin/classic/browser/syncedtabs/sidebar.css (syncedtabs/sidebar.css)
|
|
||||||
skin/classic/browser/actionicon-tab.png
|
skin/classic/browser/actionicon-tab.png
|
||||||
skin/classic/browser/actionicon-tab@2x.png
|
skin/classic/browser/actionicon-tab@2x.png
|
||||||
* skin/classic/browser/browser.css
|
* skin/classic/browser/browser.css
|
||||||
|
|
|
||||||
|
|
@ -1,154 +0,0 @@
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
||||||
|
|
||||||
%include ../../shared/syncedtabs/sidebar.inc.css
|
|
||||||
|
|
||||||
/* These styles are intended to mimic XUL trees and the XUL search box. */
|
|
||||||
|
|
||||||
.content-container {
|
|
||||||
-moz-appearance: -moz-mac-source-list;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item {
|
|
||||||
color: -moz-DialogText;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item-title-container {
|
|
||||||
box-sizing: border-box;
|
|
||||||
align-items: center;
|
|
||||||
height: 24px;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item.selected > .item-title-container {
|
|
||||||
color: HighlightText;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item.selected > .item-title-container {
|
|
||||||
-moz-appearance: -moz-mac-source-list-selection;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item.selected:focus > .item-title-container {
|
|
||||||
-moz-appearance: -moz-mac-active-source-list-selection;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item.client .item-twisty-container {
|
|
||||||
min-width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
background-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-expanded");
|
|
||||||
}
|
|
||||||
|
|
||||||
@media not all and (-moz-mac-yosemite-theme) {
|
|
||||||
.item.client.selected .item-twisty-container {
|
|
||||||
background-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-expanded-inverted");
|
|
||||||
}
|
|
||||||
|
|
||||||
.item.client.selected.closed .item-twisty-container {
|
|
||||||
background-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-collapsed-inverted");
|
|
||||||
}
|
|
||||||
|
|
||||||
.item.client.selected .item-twisty-container:dir(rtl) {
|
|
||||||
background-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-expanded-inverted");
|
|
||||||
}
|
|
||||||
|
|
||||||
.item.client.selected.closed .item-twisty-container:dir(rtl) {
|
|
||||||
background-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-collapsed-inverted-rtl");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.item.client.closed .item-twisty-container {
|
|
||||||
background-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-collapsed");
|
|
||||||
}
|
|
||||||
|
|
||||||
.item.client.selected:focus .item-twisty-container {
|
|
||||||
background-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-expanded-inverted");
|
|
||||||
}
|
|
||||||
|
|
||||||
.item.client.selected.closed:focus .item-twisty-container {
|
|
||||||
background-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-collapsed-inverted");
|
|
||||||
}
|
|
||||||
|
|
||||||
.item.client .item-twisty-container:dir(rtl) {
|
|
||||||
background-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-expanded");
|
|
||||||
}
|
|
||||||
|
|
||||||
.item.client.closed .item-twisty-container:dir(rtl) {
|
|
||||||
background-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-collapsed-rtl");
|
|
||||||
}
|
|
||||||
|
|
||||||
.item.client.selected:focus .item-twisty-container:dir(rtl) {
|
|
||||||
background-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-expanded-inverted");
|
|
||||||
}
|
|
||||||
|
|
||||||
.item.client.selected.closed:focus .item-twisty-container:dir(rtl) {
|
|
||||||
background-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-collapsed-inverted-rtl");
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (-moz-mac-yosemite-theme) {
|
|
||||||
.item.selected > .item-title-container {
|
|
||||||
color: -moz-dialogtext;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item.selected:focus > .item-title-container {
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-search-container {
|
|
||||||
border-bottom: 1px solid #bdbdbd;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-box {
|
|
||||||
-moz-appearance: searchfield;
|
|
||||||
padding: 1px;
|
|
||||||
font-size: 12px;
|
|
||||||
cursor: text;
|
|
||||||
margin: 4px 8px 10px;
|
|
||||||
border-width: 3px;
|
|
||||||
border-style: solid;
|
|
||||||
border-color: currentcolor;
|
|
||||||
border-image: none;
|
|
||||||
-moz-border-top-colors: transparent #888 #000;
|
|
||||||
-moz-border-right-colors: transparent #FFF #000;
|
|
||||||
-moz-border-bottom-colors: transparent #FFF #000;
|
|
||||||
-moz-border-left-colors: transparent #888 #000;
|
|
||||||
border-top-right-radius: 2px;
|
|
||||||
border-bottom-left-radius: 2px;
|
|
||||||
background-color: #FFF;
|
|
||||||
color: #000;
|
|
||||||
-moz-user-select: text;
|
|
||||||
text-shadow: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-box.compact > .textbox-input-box > .textbox-search-icons > .textbox-search-clear {
|
|
||||||
background-image: url(chrome://global/skin/icons/searchfield-cancel.svg);
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-size: 11px 11px;
|
|
||||||
width: 11px;
|
|
||||||
height: 11px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-box.compact > .textbox-input-box > .textbox-search-icons > .textbox-search-icon {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-box[focused="true"] {
|
|
||||||
-moz-border-top-colors: -moz-mac-focusring -moz-mac-focusring #000000;
|
|
||||||
-moz-border-right-colors: -moz-mac-focusring -moz-mac-focusring #000000;
|
|
||||||
-moz-border-bottom-colors: -moz-mac-focusring -moz-mac-focusring #000000;
|
|
||||||
-moz-border-left-colors: -moz-mac-focusring -moz-mac-focusring #000000;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-box.compact {
|
|
||||||
padding: 0px;
|
|
||||||
/* font size is in px because the XUL it was copied from uses px */
|
|
||||||
font-size: 11px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.textbox-search-clear,
|
|
||||||
.textbox-search-icon {
|
|
||||||
margin-top: 1px;
|
|
||||||
}
|
|
||||||
|
|
@ -1,234 +0,0 @@
|
||||||
% 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/.
|
|
||||||
|
|
||||||
/* These styles are intended to mimic XUL trees and the XUL search box. */
|
|
||||||
|
|
||||||
html {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
height: 100%;
|
|
||||||
margin: 0;
|
|
||||||
font: message-box;
|
|
||||||
color: #333333;
|
|
||||||
-moz-user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* The content-container holds the non-scrollable header and the scrollable
|
|
||||||
content area.
|
|
||||||
*/
|
|
||||||
.content-container {
|
|
||||||
display: flex;
|
|
||||||
flex-flow: column;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* The content header is not scrollable */
|
|
||||||
.content-header {
|
|
||||||
flex: 0 1 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* The main content area is scrollable and fills the rest of the area */
|
|
||||||
.content-scrollable {
|
|
||||||
flex: 1 1 auto;
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.emptyListInfo {
|
|
||||||
cursor: default;
|
|
||||||
padding: 3em 1em;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.list,
|
|
||||||
.item-tabs-list {
|
|
||||||
display: flex;
|
|
||||||
flex-flow: column;
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item.client {
|
|
||||||
opacity: 1;
|
|
||||||
max-height: unset;
|
|
||||||
display: unset;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item.client.closed .item-tabs-list {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item {
|
|
||||||
display: inline-block;
|
|
||||||
opacity: 1;
|
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
outline: none;
|
|
||||||
color: -moz-FieldText;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item.selected > .item-title-container {
|
|
||||||
background-color: -moz-cellhighlight;
|
|
||||||
color: -moz-cellhighlighttext;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item.selected:focus > .item-title-container {
|
|
||||||
background-color: Highlight;
|
|
||||||
color: HighlightText;
|
|
||||||
}
|
|
||||||
|
|
||||||
.client .item.tab > .item-title-container {
|
|
||||||
padding-inline-start: 35px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item.tab > .item-title-container {
|
|
||||||
padding-inline-start: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item.client.device-image-desktop > .item-title-container > .item-icon-container {
|
|
||||||
background-image: url("chrome://browser/skin/sync-desktopIcon.svg#icon");
|
|
||||||
}
|
|
||||||
|
|
||||||
.item.client.device-image-desktop.selected:focus > .item-title-container > .item-icon-container {
|
|
||||||
background-image: url("chrome://browser/skin/sync-desktopIcon.svg#icon-inverted");
|
|
||||||
}
|
|
||||||
|
|
||||||
.item.client.device-image-mobile > .item-title-container > .item-icon-container {
|
|
||||||
background-image: url("chrome://browser/skin/sync-mobileIcon.svg#icon");
|
|
||||||
}
|
|
||||||
|
|
||||||
.item.client.device-image-mobile.selected:focus > .item-title-container > .item-icon-container {
|
|
||||||
background-image: url("chrome://browser/skin/sync-mobileIcon.svg#icon-inverted");
|
|
||||||
}
|
|
||||||
|
|
||||||
.item.tab > .item-title-container > .item-icon-container {
|
|
||||||
background-image: url("chrome://mozapps/skin/places/defaultFavicon.png");
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-resolution: 1.1dppx) {
|
|
||||||
.item.tab > .item-title-container > .item-icon-container {
|
|
||||||
background-image: url("chrome://mozapps/skin/places/defaultFavicon@2x.png");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.item-icon-container {
|
|
||||||
min-width: 16px;
|
|
||||||
max-width: 16px;
|
|
||||||
min-height: 16px;
|
|
||||||
max-height: 16px;
|
|
||||||
margin-right: 5px;
|
|
||||||
margin-left: 5px;
|
|
||||||
background-size: 16px 16px;
|
|
||||||
background-size: contain;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-position: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item-title-container {
|
|
||||||
display: flex;
|
|
||||||
flex-flow: row;
|
|
||||||
overflow: hidden;
|
|
||||||
flex-grow: 1;
|
|
||||||
padding: 1px 0px 1px 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item-title {
|
|
||||||
flex-grow: 1;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
margin: 0px;
|
|
||||||
line-height: 1.3;
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item[hidden] {
|
|
||||||
opacity: 0;
|
|
||||||
max-height: 0;
|
|
||||||
transition: opacity 150ms ease-in-out, max-height 150ms ease-in-out 150ms;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item.empty .item-title-container {
|
|
||||||
color: #aeaeae;
|
|
||||||
}
|
|
||||||
|
|
||||||
.client .item.empty > .item-title-container {
|
|
||||||
padding-inline-start: 35px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-input-box {
|
|
||||||
display: flex;
|
|
||||||
flex-flow: row nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.textbox-input-box {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tabsFilter {
|
|
||||||
flex: 1;
|
|
||||||
/* min-width of anything to override the implicit "-moz-min-content" value.
|
|
||||||
0px is safe as the sidebar itself has a constrained size meaning we will
|
|
||||||
never actually hit this minimum
|
|
||||||
*/
|
|
||||||
min-width: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sync-state > p {
|
|
||||||
padding-inline-end: 10px;
|
|
||||||
padding-inline-start: 10px;
|
|
||||||
color: #888;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-link {
|
|
||||||
color: rgb(0, 149, 221);
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-link:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-link,
|
|
||||||
.text-link:focus {
|
|
||||||
margin: 0px;
|
|
||||||
padding: 0px;
|
|
||||||
border: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.deck .sync-state {
|
|
||||||
display: none;
|
|
||||||
opacity: 0;
|
|
||||||
transition: opacity 1.5s;
|
|
||||||
border-top: 1px solid #bdbdbd;
|
|
||||||
}
|
|
||||||
|
|
||||||
.deck .sync-state.tabs-container {
|
|
||||||
border-top: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.deck .sync-state.selected {
|
|
||||||
display: unset;
|
|
||||||
opacity: 100;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-search-container.tabs-container:not(.selected) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.textbox-search-clear:not([disabled]) {
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
|
|
||||||
.textbox-search-icons .textbox-search-clear,
|
|
||||||
.filtered .textbox-search-icons .textbox-search-icon {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.filtered .textbox-search-icons .textbox-search-clear {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
@ -8,7 +8,6 @@ browser.jar:
|
||||||
skin/classic/browser/sanitizeDialog.css
|
skin/classic/browser/sanitizeDialog.css
|
||||||
skin/classic/browser/aboutSessionRestore-window-icon.png
|
skin/classic/browser/aboutSessionRestore-window-icon.png
|
||||||
skin/classic/browser/aboutSyncTabs.css
|
skin/classic/browser/aboutSyncTabs.css
|
||||||
* skin/classic/browser/syncedtabs/sidebar.css (syncedtabs/sidebar.css)
|
|
||||||
skin/classic/browser/actionicon-tab.png
|
skin/classic/browser/actionicon-tab.png
|
||||||
skin/classic/browser/actionicon-tab@2x.png
|
skin/classic/browser/actionicon-tab@2x.png
|
||||||
skin/classic/browser/actionicon-tab-XPVista7.png
|
skin/classic/browser/actionicon-tab-XPVista7.png
|
||||||
|
|
|
||||||
|
|
@ -1,132 +0,0 @@
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
||||||
|
|
||||||
%include ../../shared/syncedtabs/sidebar.inc.css
|
|
||||||
|
|
||||||
/* These styles are intended to mimic XUL trees and the XUL search box. */
|
|
||||||
|
|
||||||
html {
|
|
||||||
background-color: #EEF3FA;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item {
|
|
||||||
padding-inline-end: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item-title {
|
|
||||||
margin: 1px 0 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item-title {
|
|
||||||
margin-inline-end: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-box {
|
|
||||||
-moz-appearance: textfield;
|
|
||||||
cursor: text;
|
|
||||||
margin: 2px 4px;
|
|
||||||
padding: 2px 2px 3px;
|
|
||||||
padding-inline-start: 4px;
|
|
||||||
color: -moz-FieldText;
|
|
||||||
}
|
|
||||||
|
|
||||||
.textbox-search-icon {
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
background-image: url(chrome://global/skin/icons/Search-glass.png);
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.textbox-search-icon:-moz-locale-dir(rtl) {
|
|
||||||
transform: scaleX(-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.textbox-search-icon[searchbutton]:not([disabled]) {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.textbox-search-clear {
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
background-image: url(chrome://global/skin/icons/Search-close.png);
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
}
|
|
||||||
|
|
||||||
.textbox-search-clear:not([disabled]) {
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
|
|
||||||
.textbox-search-icon:not([disabled]) {
|
|
||||||
cursor: text;
|
|
||||||
}
|
|
||||||
|
|
||||||
.textbox-search-clear:not([disabled]):hover ,
|
|
||||||
.textbox-search-icon:not([disabled]):hover {
|
|
||||||
background-position: -16px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.textbox-search-clear:not([disabled]):hover:active ,
|
|
||||||
.textbox-search-icon:not([disabled]):hover:active {
|
|
||||||
background-position: -32px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.client .item.tab > .item-title-container {
|
|
||||||
padding-inline-start: 26px;
|
|
||||||
}
|
|
||||||
.item.tab > .item-title-container {
|
|
||||||
padding-inline-start: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item-icon-container {
|
|
||||||
min-width: 16px;
|
|
||||||
max-width: 16px;
|
|
||||||
min-height: 16px;
|
|
||||||
max-height: 16px;
|
|
||||||
margin-right: 5px;
|
|
||||||
background-size: 16px 16px;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-position: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item-twisty-container {
|
|
||||||
background-size: contain;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-position: center;
|
|
||||||
padding-top: 5px;
|
|
||||||
min-width: 9px; /* The image's width is 9 pixels */
|
|
||||||
height: 9px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item.client .item-twisty-container {
|
|
||||||
background-image: url("chrome://global/skin/tree/twisty.svg#open");
|
|
||||||
}
|
|
||||||
|
|
||||||
.item.client.closed .item-twisty-container {
|
|
||||||
background-image: url("chrome://global/skin/tree/twisty.svg#clsd");
|
|
||||||
}
|
|
||||||
|
|
||||||
.item.client .item-twisty-container:hover {
|
|
||||||
background-image: url("chrome://global/skin/tree/twisty.svg#open-hover");
|
|
||||||
}
|
|
||||||
|
|
||||||
.item.client.closed .item-twisty-container:hover {
|
|
||||||
background-image: url("chrome://global/skin/tree/twisty.svg#clsd-hover");
|
|
||||||
}
|
|
||||||
|
|
||||||
.item.client .item-twisty-container:dir(rtl) {
|
|
||||||
background-image: url("chrome://global/skin/tree/twisty.svg#open-rtl");
|
|
||||||
}
|
|
||||||
|
|
||||||
.item.client.closed .item-twisty-container:dir(rtl) {
|
|
||||||
background-image: url("chrome://global/skin/tree/twisty.svg#clsd-rtl");
|
|
||||||
}
|
|
||||||
|
|
||||||
.item.client .item-twisty-container:hover:dir(rtl) {
|
|
||||||
background-image: url("chrome://global/skin/tree/twisty.svg#open-hover-rtl");
|
|
||||||
}
|
|
||||||
|
|
||||||
.item.client.closed .item-twisty-container:hover:dir(rtl) {
|
|
||||||
background-image: url("chrome://global/skin/tree/twisty.svg#clsd-hover-rtl");
|
|
||||||
}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue