Merge remote-tracking branch 'origin/master' into custom

This commit is contained in:
Roy Tam 2019-04-19 09:42:27 +08:00
commit 838b5e4e48
569 changed files with 1183 additions and 62621 deletions

View file

@ -92,10 +92,16 @@ static RedirEntry kRedirMap[] = {
"welcomeback", "chrome://browser/content/aboutWelcomeBack.xhtml",
nsIAboutModule::ALLOW_SCRIPT
},
#ifdef MOZ_SERVICES_SYNC
{
"sync-progress", "chrome://browser/content/sync/progress.xhtml",
nsIAboutModule::ALLOW_SCRIPT
},
{
"sync-tabs", "chrome://browser/content/sync/aboutSyncTabs.xul",
nsIAboutModule::ALLOW_SCRIPT
},
#endif
{ "home", "chrome://browser/content/abouthome/aboutHome.xhtml",
nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
nsIAboutModule::URI_MUST_LOAD_IN_CHILD |
@ -122,10 +128,6 @@ static RedirEntry kRedirMap[] = {
nsIAboutModule::ALLOW_SCRIPT
},
#endif
{
"accounts", "chrome://browser/content/aboutaccounts/aboutaccounts.xhtml",
nsIAboutModule::ALLOW_SCRIPT
},
{
"reader", "chrome://global/content/reader/aboutReader.html",
nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |

View file

@ -97,12 +97,14 @@ static const mozilla::Module::ContractIDEntry kBrowserContracts[] = {
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "searchreset", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "sessionrestore", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "welcomeback", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
#ifdef MOZ_SERVICES_SYNC
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "sync-tabs", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "sync-progress", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
#endif
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "home", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "newtab", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "preferences", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "downloads", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "accounts", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
#ifdef MOZ_SERVICES_HEALTHREPORT
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "healthreport", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
#endif

View file

@ -200,7 +200,6 @@ var CustomizableUIInternal = {
"find-button",
"preferences-button",
"add-ons-button",
"sync-button",
];
if (!AppConstants.MOZ_DEV_EDITION) {

View file

@ -287,144 +287,6 @@ const CustomizableWidgets = [
onViewHiding: function(aEvent) {
log.debug("History view is being hidden!");
}
}, {
id: "sync-button",
label: "remotetabs-panelmenu.label",
tooltiptext: "remotetabs-panelmenu.tooltiptext2",
type: "view",
viewId: "PanelUI-remotetabs",
defaultArea: CustomizableUI.AREA_PANEL,
deckIndices: {
DECKINDEX_TABS: 0,
DECKINDEX_TABSDISABLED: 1,
DECKINDEX_FETCHING: 2,
DECKINDEX_NOCLIENTS: 3,
},
onCreated(aNode) {
// Add an observer to the button so we get the animation during sync.
// (Note the observer sets many attributes, including label and
// tooltiptext, but we only want the 'syncstatus' attribute for the
// animation)
let doc = aNode.ownerDocument;
let obnode = doc.createElementNS(kNSXUL, "observes");
obnode.setAttribute("element", "sync-status");
obnode.setAttribute("attribute", "syncstatus");
aNode.appendChild(obnode);
},
setDeckIndex(index) {
let deck = this._tabsList.ownerDocument.getElementById("PanelUI-remotetabs-deck");
// We call setAttribute instead of relying on the XBL property setter due
// to things going wrong when we try and set the index before the XBL
// binding has been created - see bug 1241851 for the gory details.
deck.setAttribute("selectedIndex", index);
},
_showTabsPromise: Promise.resolve(),
// Update the tab list after any existing in-flight updates are complete.
_showTabs() {
this._showTabsPromise = this._showTabsPromise.then(() => {
return this.__showTabs();
});
},
// Return a new promise to update the tab list.
__showTabs() {
let doc = this._tabsList.ownerDocument;
return SyncedTabs.getTabClients().then(clients => {
// The view may have been hidden while the promise was resolving.
if (!this._tabsList) {
return;
}
if (clients.length === 0 && !SyncedTabs.hasSyncedThisSession) {
// the "fetching tabs" deck is being shown - let's leave it there.
// When that first sync completes we'll be notified and update.
return;
}
if (clients.length === 0) {
this.setDeckIndex(this.deckIndices.DECKINDEX_NOCLIENTS);
return;
}
this.setDeckIndex(this.deckIndices.DECKINDEX_TABS);
this._clearTabList();
SyncedTabs.sortTabClientsByLastUsed(clients, 50 /* maxTabs */);
let fragment = doc.createDocumentFragment();
for (let client of clients) {
// add a menu separator for all clients other than the first.
if (fragment.lastChild) {
let separator = doc.createElementNS(kNSXUL, "menuseparator");
fragment.appendChild(separator);
}
this._appendClient(client, fragment);
}
this._tabsList.appendChild(fragment);
}).catch(err => {
Cu.reportError(err);
}).then(() => {
// an observer for tests.
Services.obs.notifyObservers(null, "synced-tabs-menu:test:tabs-updated", null);
});
},
_clearTabList () {
let list = this._tabsList;
while (list.lastChild) {
list.lastChild.remove();
}
},
_showNoClientMessage() {
this._appendMessageLabel("notabslabel");
},
_appendMessageLabel(messageAttr, appendTo = null) {
if (!appendTo) {
appendTo = this._tabsList;
}
let message = this._tabsList.getAttribute(messageAttr);
let doc = this._tabsList.ownerDocument;
let messageLabel = doc.createElementNS(kNSXUL, "label");
messageLabel.textContent = message;
appendTo.appendChild(messageLabel);
return messageLabel;
},
_appendClient: function (client, attachFragment) {
let doc = attachFragment.ownerDocument;
// Create the element for the remote client.
let clientItem = doc.createElementNS(kNSXUL, "label");
clientItem.setAttribute("itemtype", "client");
let window = doc.defaultView;
clientItem.setAttribute("tooltiptext",
window.gSyncUI.formatLastSyncDate(new Date(client.lastModified)));
clientItem.textContent = client.name;
attachFragment.appendChild(clientItem);
if (client.tabs.length == 0) {
let label = this._appendMessageLabel("notabsforclientlabel", attachFragment);
label.setAttribute("class", "PanelUI-remotetabs-notabsforclient-label");
} else {
for (let tab of client.tabs) {
let tabEnt = this._createTabElement(doc, tab);
attachFragment.appendChild(tabEnt);
}
}
},
_createTabElement(doc, tabInfo) {
let item = doc.createElementNS(kNSXUL, "toolbarbutton");
let tooltipText = (tabInfo.title ? tabInfo.title + "\n" : "") + tabInfo.url;
item.setAttribute("itemtype", "tab");
item.setAttribute("class", "subviewbutton");
item.setAttribute("targetURI", tabInfo.url);
item.setAttribute("label", tabInfo.title != "" ? tabInfo.title : tabInfo.url);
item.setAttribute("image", tabInfo.icon);
item.setAttribute("tooltiptext", tooltipText);
// We need to use "click" instead of "command" here so openUILink
// respects different buttons (eg, to open in a new tab).
item.addEventListener("click", e => {
doc.defaultView.openUILink(tabInfo.url, e);
CustomizableUI.hidePanelForNode(item);
});
return item;
},
}, {
id: "privatebrowsing-button",
shortcutId: "key_privatebrowsing",

View file

@ -20,27 +20,6 @@
oncommand="gMenuButtonUpdateBadge.onMenuPanelCommand(event);"
wrap="true"
hidden="true"/>
<hbox id="PanelUI-footer-fxa">
<hbox id="PanelUI-fxa-status"
defaultlabel="&fxaSignIn.label;"
signedinTooltiptext="&syncSettings.label;"
tooltiptext="&syncSettings.label;"
errorlabel="&fxaSignInError.label;"
unverifiedlabel="&fxaUnverified.label;"
settingslabel="&syncSettings.label;"
onclick="if (event.which == 1) gFxAccounts.onMenuPanelCommand();">
<image id="PanelUI-fxa-avatar"/>
<toolbarbutton id="PanelUI-fxa-label"
fxabrandname="&syncBrand.fxAccount.label;"/>
</hbox>
<toolbarseparator/>
<toolbarbutton id="PanelUI-fxa-icon"
oncommand="gSyncUI.doSync();"
closemenu="none">
<observes element="sync-status" attribute="syncstatus"/>
<observes element="sync-status" attribute="tooltiptext"/>
</toolbarbutton>
</hbox>
<hbox id="PanelUI-footer-inner">
<toolbarbutton id="PanelUI-customize" label="&appMenuCustomize.label;"
@ -103,95 +82,6 @@
oncommand="PlacesCommandHook.showPlacesOrganizer('History'); CustomizableUI.hidePanelForNode(this);"/>
</panelview>
<panelview id="PanelUI-remotetabs" flex="1" class="PanelUI-subView">
<label value="&appMenuRemoteTabs.label;" class="panel-subview-header"/>
<vbox class="panel-subview-body">
<!-- this widget has 3 boxes in the body, but only 1 is ever visible -->
<!-- When Sync is ready to sync -->
<vbox id="PanelUI-remotetabs-main" observes="sync-syncnow-state">
<vbox id="PanelUI-remotetabs-buttons">
<toolbarbutton id="PanelUI-remotetabs-view-sidebar"
class="subviewbutton"
oncommand="BrowserOpenSyncTabs();"
label="&appMenuRemoteTabs.sidebar.label;"/>
<toolbarbutton id="PanelUI-remotetabs-syncnow"
observes="sync-status"
class="subviewbutton"
oncommand="gSyncUI.doSync();"
closemenu="none"/>
<menuseparator id="PanelUI-remotetabs-separator"/>
</vbox>
<deck id="PanelUI-remotetabs-deck">
<!-- Sync is ready to Sync and the "tabs" engine is enabled -->
<vbox id="PanelUI-remotetabs-tabspane">
<vbox id="PanelUI-remotetabs-tabslist"
notabsforclientlabel="&appMenuRemoteTabs.notabs.label;"
/>
</vbox>
<!-- Sync is ready to Sync but the "tabs" engine isn't enabled-->
<hbox id="PanelUI-remotetabs-tabsdisabledpane" pack="center" flex="1">
<vbox class="PanelUI-remotetabs-instruction-box">
<hbox pack="center">
<image class="fxaSyncIllustration" alt=""/>
</hbox>
<label class="PanelUI-remotetabs-instruction-label">&appMenuRemoteTabs.tabsnotsyncing.label;</label>
<hbox pack="center">
<toolbarbutton class="PanelUI-remotetabs-prefs-button"
label="&appMenuRemoteTabs.openprefs.label;"
oncommand="gSyncUI.openSetup(null, 'synced-tabs');"/>
</hbox>
</vbox>
</hbox>
<!-- Sync is ready to Sync but we are still fetching the tabs to show -->
<vbox id="PanelUI-remotetabs-fetching">
<!-- Show intentionally blank panel, see bug 1239845 -->
</vbox>
<!-- Sync has only 1 (ie, this) device connected -->
<hbox id="PanelUI-remotetabs-nodevicespane" pack="center" flex="1">
<vbox class="PanelUI-remotetabs-instruction-box">
<hbox pack="center">
<image class="fxaSyncIllustration" alt=""/>
</hbox>
<label class="PanelUI-remotetabs-instruction-title">&appMenuRemoteTabs.noclients.title;</label>
<label class="PanelUI-remotetabs-instruction-label">&appMenuRemoteTabs.noclients.subtitle;</label>
<!-- The inner HTML for PanelUI-remotetabs-mobile-promo is built at runtime -->
<label id="PanelUI-remotetabs-mobile-promo" fxAccountsBrand="&syncBrand.fxAccount.label;"/>
</vbox>
</hbox>
</deck>
</vbox>
<!-- a box to ensure contained boxes are centered horizonally -->
<hbox pack="center" flex="1">
<!-- When Sync is not configured -->
<vbox id="PanelUI-remotetabs-setupsync"
flex="1"
align="center"
class="PanelUI-remotetabs-instruction-box"
observes="sync-setup-state">
<image class="fxaSyncIllustration" alt=""/>
<label class="PanelUI-remotetabs-instruction-label">&appMenuRemoteTabs.notsignedin.label;</label>
<toolbarbutton class="PanelUI-remotetabs-prefs-button"
label="&appMenuRemoteTabs.signin.label;"
oncommand="gSyncUI.openSetup(null, 'synced-tabs');"/>
</vbox>
<!-- When Sync needs re-authentication. This uses the exact same messaging
as "Sync is not configured" but remains a separate box so we get
the goodness of observing broadcasters to manage the hidden states -->
<vbox id="PanelUI-remotetabs-reauthsync"
flex="1"
align="center"
class="PanelUI-remotetabs-instruction-box"
observes="sync-reauth-state">
<image class="fxaSyncIllustration" alt=""/>
<label class="PanelUI-remotetabs-instruction-label">&appMenuRemoteTabs.notsignedin.label;</label>
<toolbarbutton class="PanelUI-remotetabs-prefs-button"
label="&appMenuRemoteTabs.signin.label;"
oncommand="gSyncUI.openSetup(null, 'synced-tabs');"/>
</vbox>
</hbox>
</vbox>
</panelview>
<panelview id="PanelUI-bookmarks" flex="1" class="PanelUI-subView">
<label value="&bookmarksMenu.label;" class="panel-subview-header"/>
<vbox class="panel-subview-body">

View file

@ -9,7 +9,6 @@ DIRS += [
]
EXTRA_JS_MODULES += [
'CustomizableUI.jsm',
'CustomizableWidgets.jsm',
'CustomizeMode.jsm',
'DragPositionManager.jsm',
@ -17,5 +16,9 @@ EXTRA_JS_MODULES += [
'ScrollbarSampler.jsm',
]
EXTRA_PP_JS_MODULES += [
'CustomizableUI.jsm',
]
if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('windows', 'cocoa'):
DEFINES['CAN_DRAW_IN_TITLEBAR'] = 1

View file

@ -20,13 +20,15 @@ DIRS += [
'sessionstore',
'shell',
'selfsupport',
'syncedtabs',
'translation',
]
if CONFIG['MOZ_WEBEXTENSIONS']:
DIRS += ['webextensions']
if CONFIG['MOZ_SERVICES_SYNC']:
DIRS += ['sync']
DIRS += ['build']
XPIDL_SOURCES += [
@ -39,6 +41,9 @@ XPIDL_MODULE = 'browsercompsbase'
EXTRA_COMPONENTS += [
'BrowserComponents.manifest',
'nsBrowserContentHandler.js',
]
EXTRA_PP_COMPONENTS += [
'nsBrowserGlue.js',
]

View file

@ -143,6 +143,7 @@ BrowserGlue.prototype = {
Services.prefs.savePrefFile(null);
},
#ifdef MOZ_SERVICES_SYNC
_setSyncAutoconnectDelay: function BG__setSyncAutoconnectDelay() {
// Assume that a non-zero value for services.sync.autoconnectDelay should override
if (Services.prefs.prefHasUserValue("services.sync.autoconnectDelay")) {
@ -164,6 +165,7 @@ BrowserGlue.prototype = {
Cu.import("resource://services-sync/main.js");
Weave.Service.scheduler.delayedAutoConnect(delay);
},
#endif
// nsIObserver implementation
observe: function BG_observe(subject, topic, data) {
@ -210,18 +212,14 @@ BrowserGlue.prototype = {
this._setPrefToSaveSession();
}
break;
#ifdef MOZ_SERVICES_SYNC
case "weave:service:ready":
this._setSyncAutoconnectDelay();
break;
case "fxaccounts:onverified":
this._showSyncStartedDoorhanger();
break;
case "fxaccounts:device_disconnected":
this._onDeviceDisconnected();
break;
case "weave:engine:clients:display-uris":
this._onDisplaySyncURIs(subject);
break;
case "weave:engine:clients:display-uri":
this._onDisplaySyncURI(subject);
break;
#endif
case "session-save":
this._setPrefToSaveSession(true);
subject.QueryInterface(Ci.nsISupportsPRBool);
@ -428,10 +426,10 @@ BrowserGlue.prototype = {
os.addObserver(this, "browser-lastwindow-close-requested", false);
os.addObserver(this, "browser-lastwindow-close-granted", false);
}
#ifdef MOZ_SERVICES_SYNC
os.addObserver(this, "weave:service:ready", false);
os.addObserver(this, "fxaccounts:onverified", false);
os.addObserver(this, "fxaccounts:device_disconnected", false);
os.addObserver(this, "weave:engine:clients:display-uris", false);
os.addObserver(this, "weave:engine:clients:display-uri", false);
#endif
os.addObserver(this, "session-save", false);
os.addObserver(this, "places-init-complete", false);
this._isPlacesInitObserver = true;
@ -479,10 +477,10 @@ BrowserGlue.prototype = {
os.removeObserver(this, "browser-lastwindow-close-requested");
os.removeObserver(this, "browser-lastwindow-close-granted");
}
#ifdef MOZ_SERVICES_SYNC
os.removeObserver(this, "weave:service:ready");
os.removeObserver(this, "fxaccounts:onverified");
os.removeObserver(this, "fxaccounts:device_disconnected");
os.removeObserver(this, "weave:engine:clients:display-uris");
os.removeObserver(this, "weave:engine:clients:display-uri");
#endif
os.removeObserver(this, "session-save");
if (this._bookmarksBackupIdleTime) {
this._idleService.removeIdleObserver(this, this._bookmarksBackupIdleTime);
@ -2274,90 +2272,29 @@ BrowserGlue.prototype = {
chromeWindow.openPreferences(...args);
},
#ifdef MOZ_SERVICES_SYNC
/**
* Called as an observer when Sync's "display URIs" notification is fired.
* Called as an observer when Sync's "display URI" notification is fired.
*
* We open the received URIs in background tabs.
* We open the received URI in a background tab.
*
* Eventually, this will likely be replaced by a more robust tab syncing
* feature. This functionality is considered somewhat evil by UX because it
* opens a new tab automatically without any prompting. However, it is a
* lesser evil than sending a tab to a specific device (from e.g. Fennec)
* and having nothing happen on the receiving end.
*/
_onDisplaySyncURIs: function _onDisplaySyncURIs(data) {
_onDisplaySyncURI: function _onDisplaySyncURI(data) {
try {
let tabbrowser = RecentWindow.getMostRecentBrowserWindow({private: false}).gBrowser;
// The payload is wrapped weirdly because of how Sync does notifications.
const URIs = data.wrappedJSObject.object;
const findWindow = () => RecentWindow.getMostRecentBrowserWindow({private: false});
// win can be null, but it's ok, we'll assign it later in openTab()
let win = findWindow();
const openTab = URI => {
let tab;
if (!win) {
Services.appShell.hiddenDOMWindow.open(URI.uri);
win = findWindow();
tab = win.gBrowser.tabs[0];
} else {
tab = win.gBrowser.addTab(URI.uri);
}
tab.setAttribute("attention", true);
return tab;
};
const firstTab = openTab(URIs[0]);
URIs.slice(1).forEach(URI => openTab(URI));
let title, body;
const deviceName = Weave.Service.clientsEngine.getClientName(URIs[0].clientId);
const bundle = Services.strings.createBundle("chrome://browser/locale/accounts.properties");
if (URIs.length == 1) {
// Due to bug 1305895, tabs from iOS may not have device information, so
// we have separate strings to handle those cases. (See Also
// unnamedTabsArrivingNotificationNoDevice.body below)
if (deviceName) {
title = bundle.formatStringFromName("tabArrivingNotificationWithDevice.title", [deviceName], 1);
} else {
title = bundle.GetStringFromName("tabArrivingNotification.title");
}
// Use the page URL as the body. We strip the fragment and query to
// reduce size, and also format it the same way that the url bar would.
body = URIs[0].uri.replace(/[?#].*$/, "");
if (win.gURLBar) {
body = win.gURLBar.trimValue(body);
}
} else {
title = bundle.GetStringFromName("tabsArrivingNotification.title");
const allSameDevice = URIs.every(URI => URI.clientId == URIs[0].clientId);
const unknownDevice = allSameDevice && !deviceName;
let tabArrivingBody;
if (unknownDevice) {
tabArrivingBody = "unnamedTabsArrivingNotificationNoDevice.body";
} else if (allSameDevice) {
tabArrivingBody = "unnamedTabsArrivingNotification2.body";
} else {
tabArrivingBody = "unnamedTabsArrivingNotificationMultiple2.body"
}
body = bundle.GetStringFromName(tabArrivingBody);
body = PluralForm.get(URIs.length, body);
body = body.replace("#1", URIs.length);
body = body.replace("#2", deviceName);
}
const clickCallback = (subject, topic, data) => {
if (topic == "alertclickcallback") {
win.gBrowser.selectedTab = firstTab;
}
}
// Specify an icon because on Windows no icon is shown at the moment
let imageURL;
if (AppConstants.platform == "win") {
imageURL = "chrome://branding/content/icon64.png";
}
AlertsService.showAlertNotification(imageURL, title, body, true, null, clickCallback);
tabbrowser.addTab(data.wrappedJSObject.object.uri);
} catch (ex) {
Cu.reportError("Error displaying tab(s) received by Sync: " + ex);
Cu.reportError("Error displaying tab received by Sync: " + ex);
}
},
#endif
_onDeviceDisconnected() {
let bundle = Services.strings.createBundle("chrome://browser/locale/accounts.properties");

View file

@ -1418,9 +1418,9 @@ this.PlacesUIUtils = {
},
shouldShowTabsFromOtherComputersMenuitem: function() {
let weaveOK = Weave.Status.checkSetup() != Weave.CLIENT_NOT_CONFIGURED &&
Weave.Svc.Prefs.get("firstSync", "") != "notReady";
return weaveOK;
#ifdef MOZ_SERVICES_SYNC
// Weave code to enable menu item
#endif
},
/**

View file

@ -6,6 +6,6 @@
JAR_MANIFESTS += ['jar.mn']
EXTRA_JS_MODULES += [
EXTRA_PP_JS_MODULES += [
'PlacesUIUtils.jsm',
]

View file

@ -3,7 +3,7 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
browser.jar:
content/browser/preferences/in-content/preferences.js
* content/browser/preferences/in-content/preferences.js
* content/browser/preferences/in-content/preferences.xul
content/browser/preferences/in-content/subdialogs.js
@ -13,6 +13,8 @@ browser.jar:
content/browser/preferences/in-content/advanced.js
content/browser/preferences/in-content/applications.js
* content/browser/preferences/in-content/content.js
#ifdef MOZ_SERVICES_SYNC
content/browser/preferences/in-content/sync.js
#endif
* content/browser/preferences/in-content/security.js
content/browser/preferences/in-content/search.js

View file

@ -65,7 +65,9 @@ function init_all() {
register_module("paneAdvanced", gAdvancedPane);
register_module("paneApplications", gApplicationsPane);
register_module("paneContent", gContentPane);
#ifdef MOZ_SERVICES_SYNC
register_module("paneSync", gSyncPane);
#endif
register_module("paneSecurity", gSecurityPane);
let categories = document.getElementById("categories");

View file

@ -23,8 +23,10 @@
<!ENTITY % privacyDTD SYSTEM "chrome://browser/locale/preferences/privacy.dtd">
<!ENTITY % tabsDTD SYSTEM "chrome://browser/locale/preferences/tabs.dtd">
<!ENTITY % searchDTD SYSTEM "chrome://browser/locale/preferences/search.dtd">
#ifdef MOZ_SERVICES_SYNC
<!ENTITY % syncBrandDTD SYSTEM "chrome://browser/locale/syncBrand.dtd">
<!ENTITY % syncDTD SYSTEM "chrome://browser/locale/preferences/sync.dtd">
#endif
<!ENTITY % securityDTD SYSTEM
"chrome://browser/locale/preferences/security.dtd">
<!ENTITY % containersDTD SYSTEM
@ -43,8 +45,10 @@
%privacyDTD;
%tabsDTD;
%searchDTD;
#ifdef MOZ_SERVICES_SYNC
%syncBrandDTD;
%syncDTD;
#endif
%securityDTD;
%containersDTD;
%sanitizeDTD;
@ -150,6 +154,7 @@
<label class="category-name" flex="1">&paneSecurity.title;</label>
</richlistitem>
#ifdef MOZ_SERVICES_SYNC
<richlistitem id="category-sync"
class="category"
value="paneSync"
@ -159,6 +164,7 @@
<image class="category-icon"/>
<label class="category-name" flex="1">&paneSync.title;</label>
</richlistitem>
#endif
<richlistitem id="category-advanced"
class="category"
@ -188,7 +194,9 @@
#include applications.xul
#include content.xul
#include security.xul
#ifdef MOZ_SERVICES_SYNC
#include sync.xul
#endif
</prefpane>
</vbox>

View file

@ -1,32 +1,16 @@
/* 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/. */
* 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/. */
Components.utils.import("resource://services-sync/main.js");
Components.utils.import("resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyGetter(this, "FxAccountsCommon", function () {
return Components.utils.import("resource://gre/modules/FxAccountsCommon.js", {});
});
XPCOMUtils.defineLazyModuleGetter(this, "fxAccounts",
"resource://gre/modules/FxAccounts.jsm");
const PAGE_NO_ACCOUNT = 0;
const PAGE_HAS_ACCOUNT = 1;
const PAGE_NEEDS_UPDATE = 2;
const FXA_PAGE_LOGGED_OUT = 3;
const FXA_PAGE_LOGGED_IN = 4;
// Indexes into the "login status" deck.
// We are in a successful verified state - everything should work!
const FXA_LOGIN_VERIFIED = 0;
// We have logged in to an unverified account.
const FXA_LOGIN_UNVERIFIED = 1;
// We are logged in locally, but the server rejected our credentials.
const FXA_LOGIN_FAILED = 2;
var gSyncPane = {
_stringBundle: null,
prefArray: ["engine.bookmarks", "engine.passwords", "engine.prefs",
"engine.tabs", "engine.history"],
@ -45,13 +29,11 @@ var gSyncPane = {
needsUpdate: function () {
this.page = PAGE_NEEDS_UPDATE;
let label = document.getElementById("loginError");
label.textContent = Weave.Utils.getErrorString(Weave.Status.login);
label.value = Weave.Utils.getErrorString(Weave.Status.login);
label.className = "error";
},
init: function () {
this._setupEventListeners();
// If the Service hasn't finished initializing, wait for it.
let xps = Components.classes["@mozilla.org/weave/service;1"]
.getService(Components.interfaces.nsISupports)
@ -62,10 +44,6 @@ var gSyncPane = {
return;
}
// it may take some time before we can determine what provider to use
// and the state of that provider, so show the "please wait" page.
this._showLoadPage(xps);
let onUnload = function () {
window.removeEventListener("unload", onUnload, false);
try {
@ -85,247 +63,50 @@ var gSyncPane = {
xps.ensureLoaded();
},
_showLoadPage: function (xps) {
let username;
try {
username = Services.prefs.getCharPref("services.sync.username");
} catch (e) {}
if (!username) {
this.page = FXA_PAGE_LOGGED_OUT;
} else if (xps.fxAccountsEnabled) {
// Use cached values while we wait for the up-to-date values
let cachedComputerName;
try {
cachedComputerName = Services.prefs.getCharPref("services.sync.client.name");
}
catch (e) {
cachedComputerName = "";
}
document.getElementById("fxaEmailAddress1").textContent = username;
this._populateComputerName(cachedComputerName);
this.page = FXA_PAGE_LOGGED_IN;
} else { // Old Sync
this.page = PAGE_HAS_ACCOUNT;
}
},
_init: function () {
let topics = ["weave:service:login:error",
"weave:service:login:finish",
"weave:service:start-over:finish",
"weave:service:start-over",
"weave:service:setup-complete",
"weave:service:logout:finish",
FxAccountsCommon.ONVERIFIED_NOTIFICATION,
FxAccountsCommon.ONLOGIN_NOTIFICATION,
FxAccountsCommon.ON_PROFILE_CHANGE_NOTIFICATION,
];
"weave:service:logout:finish"];
// Add the observers now and remove them on unload
// XXXzpao This should use Services.obs.* but Weave's Obs does nice handling
//XXXzpao This should use Services.obs.* but Weave's Obs does nice handling
// of `this`. Fix in a followup. (bug 583347)
topics.forEach(function (topic) {
Weave.Svc.Obs.add(topic, this.updateWeavePrefs, this);
}, this);
window.addEventListener("unload", function() {
topics.forEach(function (topic) {
Weave.Svc.Obs.remove(topic, this.updateWeavePrefs, this);
}, gSyncPane);
}, false);
XPCOMUtils.defineLazyGetter(this, '_stringBundle', () => {
return Services.strings.createBundle("chrome://browser/locale/preferences/preferences.properties");
});
XPCOMUtils.defineLazyGetter(this, '_accountsStringBundle', () => {
return Services.strings.createBundle("chrome://browser/locale/accounts.properties");
});
let url = Services.prefs.getCharPref("identity.mobilepromo.android") + "sync-preferences";
document.getElementById("fxaMobilePromo-android").setAttribute("href", url);
document.getElementById("fxaMobilePromo-android-hasFxaAccount").setAttribute("href", url);
url = Services.prefs.getCharPref("identity.mobilepromo.ios") + "sync-preferences";
document.getElementById("fxaMobilePromo-ios").setAttribute("href", url);
document.getElementById("fxaMobilePromo-ios-hasFxaAccount").setAttribute("href", url);
document.getElementById("tosPP-small-ToS").setAttribute("href", gSyncUtils.tosURL);
document.getElementById("tosPP-normal-ToS").setAttribute("href", gSyncUtils.tosURL);
document.getElementById("tosPP-small-PP").setAttribute("href", gSyncUtils.privacyPolicyURL);
document.getElementById("tosPP-normal-PP").setAttribute("href", gSyncUtils.privacyPolicyURL);
fxAccounts.promiseAccountsManageURI(this._getEntryPoint()).then(url => {
document.getElementById("verifiedManage").setAttribute("href", url);
});
this._stringBundle =
Services.strings.createBundle("chrome://browser/locale/preferences/preferences.properties");
this.updateWeavePrefs();
this._initProfileImageUI();
},
_toggleComputerNameControls: function(editMode) {
let textbox = document.getElementById("fxaSyncComputerName");
textbox.disabled = !editMode;
document.getElementById("fxaChangeDeviceName").hidden = editMode;
document.getElementById("fxaCancelChangeDeviceName").hidden = !editMode;
document.getElementById("fxaSaveChangeDeviceName").hidden = !editMode;
},
_focusComputerNameTextbox: function() {
let textbox = document.getElementById("fxaSyncComputerName");
let valLength = textbox.value.length;
textbox.focus();
textbox.setSelectionRange(valLength, valLength);
},
_blurComputerNameTextbox: function() {
document.getElementById("fxaSyncComputerName").blur();
},
_focusAfterComputerNameTextbox: function() {
// Focus the most appropriate element that's *not* the "computer name" box.
Services.focus.moveFocus(window,
document.getElementById("fxaSyncComputerName"),
Services.focus.MOVEFOCUS_FORWARD, 0);
},
_updateComputerNameValue: function(save) {
if (save) {
let textbox = document.getElementById("fxaSyncComputerName");
Weave.Service.clientsEngine.localName = textbox.value;
}
this._populateComputerName(Weave.Service.clientsEngine.localName);
},
_setupEventListeners: function() {
function setEventListener(aId, aEventType, aCallback)
{
document.getElementById(aId)
.addEventListener(aEventType, aCallback.bind(gSyncPane));
}
setEventListener("noAccountSetup", "click", function (aEvent) {
aEvent.stopPropagation();
gSyncPane.openSetup(null);
});
setEventListener("noAccountPair", "click", function (aEvent) {
aEvent.stopPropagation();
gSyncPane.openSetup('pair');
});
setEventListener("syncChangePassword", "command",
() => gSyncUtils.changePassword());
setEventListener("syncResetPassphrase", "command",
() => gSyncUtils.resetPassphrase());
setEventListener("syncReset", "command", gSyncPane.resetSync);
setEventListener("syncAddDeviceLabel", "click", function () {
gSyncPane.openAddDevice();
return false;
});
setEventListener("syncEnginesList", "select", function () {
if (this.selectedCount)
this.clearSelection();
});
setEventListener("syncComputerName", "change", function (e) {
gSyncUtils.changeName(e.target);
});
setEventListener("fxaChangeDeviceName", "command", function () {
this._toggleComputerNameControls(true);
this._focusComputerNameTextbox();
});
setEventListener("fxaCancelChangeDeviceName", "command", function () {
// We explicitly blur the textbox because of bug 75324, then after
// changing the state of the buttons, force focus to whatever the focus
// manager thinks should be next (which on the mac, depends on an OSX
// keyboard access preference)
this._blurComputerNameTextbox();
this._toggleComputerNameControls(false);
this._updateComputerNameValue(false);
this._focusAfterComputerNameTextbox();
});
setEventListener("fxaSaveChangeDeviceName", "command", function () {
// Work around bug 75324 - see above.
this._blurComputerNameTextbox();
this._toggleComputerNameControls(false);
this._updateComputerNameValue(true);
this._focusAfterComputerNameTextbox();
});
setEventListener("unlinkDevice", "click", function () {
gSyncPane.startOver(true);
return false;
});
setEventListener("loginErrorUpdatePass", "click", function () {
gSyncPane.updatePass();
return false;
});
setEventListener("loginErrorResetPass", "click", function () {
gSyncPane.resetPass();
return false;
});
setEventListener("loginErrorStartOver", "click", function () {
gSyncPane.startOver(true);
return false;
});
setEventListener("noFxaSignUp", "command", function () {
gSyncPane.signUp();
return false;
});
setEventListener("noFxaSignIn", "command", function () {
gSyncPane.signIn();
return false;
});
setEventListener("fxaUnlinkButton", "command", function () {
gSyncPane.unlinkFirefoxAccount(true);
});
setEventListener("verifyFxaAccount", "command",
gSyncPane.verifyFirefoxAccount);
setEventListener("unverifiedUnlinkFxaAccount", "command", function () {
/* no warning as account can't have previously synced */
gSyncPane.unlinkFirefoxAccount(false);
});
setEventListener("rejectReSignIn", "command",
gSyncPane.reSignIn);
setEventListener("rejectUnlinkFxaAccount", "command", function () {
gSyncPane.unlinkFirefoxAccount(true);
});
setEventListener("fxaSyncComputerName", "keypress", function (e) {
if (e.keyCode == KeyEvent.DOM_VK_RETURN) {
document.getElementById("fxaSaveChangeDeviceName").click();
} else if (e.keyCode == KeyEvent.DOM_VK_ESCAPE) {
document.getElementById("fxaCancelChangeDeviceName").click();
}
});
},
_initProfileImageUI: function () {
try {
if (Services.prefs.getBoolPref("identity.fxaccounts.profile_image.enabled")) {
document.getElementById("fxaProfileImage").hidden = false;
}
} catch (e) { }
document.getElementById("weavePrefsDeck").setAttribute("hidden", "");
},
updateWeavePrefs: function () {
let service = Components.classes["@mozilla.org/weave/service;1"]
.getService(Components.interfaces.nsISupports)
.wrappedJSObject;
if (Weave.Status.service == Weave.CLIENT_NOT_CONFIGURED ||
Weave.Svc.Prefs.get("firstSync", "") == "notReady") {
this.page = PAGE_NO_ACCOUNT;
// else: sync was previously configured for the legacy provider, so we
// make the "old" panels available.
} else if (Weave.Status.login == Weave.LOGIN_FAILED_INVALID_PASSPHRASE ||
Weave.Status.login == Weave.LOGIN_FAILED_LOGIN_REJECTED) {
this.needsUpdate();
} else {
this.page = PAGE_HAS_ACCOUNT;
document.getElementById("accountName").textContent = Weave.Service.identity.account;
document.getElementById("accountName").value = Weave.Service.identity.account;
document.getElementById("syncComputerName").value = Weave.Service.clientsEngine.localName;
document.getElementById("tosPP-normal").hidden = this._usingCustomServer;
document.getElementById("tosPP").hidden = this._usingCustomServer;
}
},
startOver: function (showDialog) {
if (showDialog) {
let flags = Services.prompt.BUTTON_POS_0 * Services.prompt.BUTTON_TITLE_IS_STRING +
Services.prompt.BUTTON_POS_1 * Services.prompt.BUTTON_TITLE_CANCEL +
Services.prompt.BUTTON_POS_1 * Services.prompt.BUTTON_TITLE_CANCEL +
Services.prompt.BUTTON_POS_1_DEFAULT;
let buttonChoice =
Services.prompt.confirmEx(window,
@ -336,8 +117,9 @@ var gSyncPane = {
null, null, null, {});
// If the user selects cancel, just bail
if (buttonChoice == 1)
if (buttonChoice == 1) {
return;
}
}
Weave.Service.startOver();
@ -345,33 +127,19 @@ var gSyncPane = {
},
updatePass: function () {
if (Weave.Status.login == Weave.LOGIN_FAILED_LOGIN_REJECTED)
if (Weave.Status.login == Weave.LOGIN_FAILED_LOGIN_REJECTED) {
gSyncUtils.changePassword();
else
} else {
gSyncUtils.updatePassphrase();
}
},
resetPass: function () {
if (Weave.Status.login == Weave.LOGIN_FAILED_LOGIN_REJECTED)
if (Weave.Status.login == Weave.LOGIN_FAILED_LOGIN_REJECTED) {
gSyncUtils.resetPassword();
else
} else {
gSyncUtils.resetPassphrase();
},
_getEntryPoint: function () {
let params = new URLSearchParams(document.URL.split("#")[0].split("?")[1] || "");
return params.get("entrypoint") || "preferences";
},
_openAboutAccounts: function(action) {
let entryPoint = this._getEntryPoint();
let params = new URLSearchParams();
if (action) {
params.set("action", action);
}
params.set("entrypoint", entryPoint);
this.replaceTabWithUrl("about:accounts?" + params);
},
/**
@ -384,163 +152,16 @@ var gSyncPane = {
* "reset" -- reset sync
*/
openSetup: function (wizardType) {
let service = Components.classes["@mozilla.org/weave/service;1"]
.getService(Components.interfaces.nsISupports)
.wrappedJSObject;
if (service.fxAccountsEnabled) {
this._openAboutAccounts();
let win = Services.wm.getMostRecentWindow("Weave:AccountSetup");
if (win) {
win.focus();
} else {
let win = Services.wm.getMostRecentWindow("Weave:AccountSetup");
if (win)
win.focus();
else {
window.openDialog("chrome://browser/content/sync/setup.xul",
"weaveSetup", "centerscreen,chrome,resizable=no",
wizardType);
}
window.openDialog("chrome://browser/content/sync/setup.xul",
"weaveSetup", "centerscreen,chrome,resizable=no",
wizardType);
}
},
openContentInBrowser: function(url, options) {
let win = Services.wm.getMostRecentWindow("navigator:browser");
if (!win) {
// no window to use, so use _openLink to create a new one. We don't
// always use that as it prefers to open a new window rather than use
// an existing one.
gSyncUtils._openLink(url);
return;
}
win.switchToTabHavingURI(url, true, options);
},
// Replace the current tab with the specified URL.
replaceTabWithUrl(url) {
// Get the <browser> element hosting us.
let browser = window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShell)
.chromeEventHandler;
// And tell it to load our URL.
browser.loadURI(url);
},
signUp: function() {
this._openAboutAccounts("signup");
},
signIn: function() {
this._openAboutAccounts("signin");
},
reSignIn: function() {
this._openAboutAccounts("reauth");
},
clickOrSpaceOrEnterPressed: function(event) {
// Note: charCode is deprecated, but 'char' not yet implemented.
// Replace charCode with char when implemented, see Bug 680830
return ((event.type == "click" && event.button == 0) ||
(event.type == "keypress" &&
(event.charCode == KeyEvent.DOM_VK_SPACE || event.keyCode == KeyEvent.DOM_VK_RETURN)));
},
openChangeProfileImage: function(event) {
if (this.clickOrSpaceOrEnterPressed(event)) {
fxAccounts.promiseAccountsChangeProfileURI(this._getEntryPoint(), "avatar")
.then(url => {
this.openContentInBrowser(url, {
replaceQueryString: true
});
});
// Prevent page from scrolling on the space key.
event.preventDefault();
}
},
openManageFirefoxAccount: function(event) {
if (this.clickOrSpaceOrEnterPressed(event)) {
this.manageFirefoxAccount();
// Prevent page from scrolling on the space key.
event.preventDefault();
}
},
manageFirefoxAccount: function() {
fxAccounts.promiseAccountsManageURI(this._getEntryPoint())
.then(url => {
this.openContentInBrowser(url, {
replaceQueryString: true
});
});
},
verifyFirefoxAccount: function() {
let showVerifyNotification = (data) => {
let isError = !data;
let maybeNot = isError ? "Not" : "";
let sb = this._accountsStringBundle;
let title = sb.GetStringFromName("verification" + maybeNot + "SentTitle");
let email = !isError && data ? data.email : "";
let body = sb.formatStringFromName("verification" + maybeNot + "SentBody", [email], 1);
new Notification(title, { body })
}
let onError = () => {
showVerifyNotification();
};
let onSuccess = data => {
if (data) {
showVerifyNotification(data);
} else {
onError();
}
};
fxAccounts.resendVerificationEmail()
.then(fxAccounts.getSignedInUser, onError)
.then(onSuccess, onError);
},
openOldSyncSupportPage: function() {
let url = Services.urlFormatter.formatURLPref("app.support.baseURL") + "old-sync";
this.openContentInBrowser(url);
},
unlinkFirefoxAccount: function(confirm) {
if (confirm) {
// We use a string bundle shared with aboutAccounts.
let sb = Services.strings.createBundle("chrome://browser/locale/syncSetup.properties");
let disconnectLabel = sb.GetStringFromName("disconnect.label");
let title = sb.GetStringFromName("disconnect.verify.title");
let body = sb.GetStringFromName("disconnect.verify.bodyHeading") +
"\n\n" +
sb.GetStringFromName("disconnect.verify.bodyText");
let ps = Services.prompt;
let buttonFlags = (ps.BUTTON_POS_0 * ps.BUTTON_TITLE_IS_STRING) +
(ps.BUTTON_POS_1 * ps.BUTTON_TITLE_CANCEL) +
ps.BUTTON_POS_1_DEFAULT;
let factory = Cc["@mozilla.org/prompter;1"]
.getService(Ci.nsIPromptFactory);
let prompt = factory.getPrompt(window, Ci.nsIPrompt);
let bag = prompt.QueryInterface(Ci.nsIWritablePropertyBag2);
bag.setPropertyAsBool("allowTabModal", true);
let pressed = prompt.confirmEx(title, body, buttonFlags,
disconnectLabel, null, null, null, {});
if (pressed != 0) { // 0 is the "continue" button
return;
}
}
fxAccounts.signOut().then(() => {
this.updateWeavePrefs();
});
},
openQuotaDialog: function () {
let win = Services.wm.getMostRecentWindow("Sync:ViewQuota");
if (win) {
@ -552,27 +173,21 @@ var gSyncPane = {
},
openAddDevice: function () {
if (!Weave.Utils.ensureMPUnlocked())
if (!Weave.Utils.ensureMPUnlocked()) {
return;
}
let win = Services.wm.getMostRecentWindow("Sync:AddDevice");
if (win)
if (win) {
win.focus();
else
} else {
window.openDialog("chrome://browser/content/sync/addDevice.xul",
"syncAddDevice", "centerscreen,chrome,resizable=no");
}
},
resetSync: function () {
this.openSetup("reset");
},
_populateComputerName(value) {
let textbox = document.getElementById("fxaSyncComputerName");
if (!textbox.hasAttribute("placeholder")) {
textbox.setAttribute("placeholder",
Weave.Utils.getDefaultDeviceName());
}
textbox.value = value;
},
};

View file

@ -1,35 +1,22 @@
# 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/.
<!-- Sync panel -->
<preferences id="syncEnginePrefs" hidden="true" data-category="paneSync">
<preference id="engine.addons"
name="services.sync.engine.addons"
type="bool"/>
<preference id="engine.bookmarks"
name="services.sync.engine.bookmarks"
type="bool"/>
<preference id="engine.history"
name="services.sync.engine.history"
type="bool"/>
<preference id="engine.tabs"
name="services.sync.engine.tabs"
type="bool"/>
<preference id="engine.prefs"
name="services.sync.engine.prefs"
type="bool"/>
<preference id="engine.passwords"
name="services.sync.engine.passwords"
type="bool"/>
</preferences>
<!-- 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/. -->
<script type="application/javascript"
src="chrome://browser/content/preferences/in-content/sync.js"/>
<script type="application/javascript"
src="chrome://browser/content/sync/utils.js"/>
<preferences>
<!-- <preference id="engine.addons" name="services.sync.engine.addons" type="bool"/> -->
<preference id="engine.bookmarks" name="services.sync.engine.bookmarks" type="bool"/>
<preference id="engine.history" name="services.sync.engine.history" type="bool"/>
<preference id="engine.tabs" name="services.sync.engine.tabs" type="bool"/>
<preference id="engine.prefs" name="services.sync.engine.prefs" type="bool"/>
<preference id="engine.passwords" name="services.sync.engine.passwords" type="bool"/>
</preferences>
<hbox id="header-sync"
class="header"
hidden="true"
@ -39,22 +26,19 @@
</hbox>
<deck id="weavePrefsDeck" data-category="paneSync" hidden="true">
<!-- These panels are for the "legacy" sync provider -->
<vbox id="noAccount" align="center">
<spacer flex="1"/>
<description id="syncDesc">
&weaveDesc.label;
</description>
<separator/>
<label id="noAccountSetup" class="text-link">
&setupButton.label;
</label>
<vbox id="pairDevice">
<separator/>
<label id="noAccountPair" class="text-link">
&pairDevice.label;
</label>
</vbox>
<label class="text-link"
onclick="event.stopPropagation(); gSyncPane.openSetup(null);"
value="&setupButton.label;"/>
<separator/>
<label class="text-link"
onclick="event.stopPropagation(); gSyncPane.openSetup('pair');"
value="&pairDevice.label;"/>
<spacer flex="3"/>
</vbox>
@ -63,7 +47,7 @@
<!-- label is set to account name -->
<caption id="accountCaption" align="center">
<image id="accountCaptionImage"/>
<label id="accountName"/>
<label id="accountName" value=""/>
</caption>
<hbox>
@ -71,28 +55,39 @@
label="&manageAccount.label;"
accesskey="&manageAccount.accesskey;">
<menupopup>
<menuitem id="syncViewQuota" label="&viewQuota.label;"
<menuitem label="&viewQuota.label;"
oncommand="gSyncPane.openQuotaDialog();"/>
<menuseparator/>
<menuitem id="syncChangePassword" label="&changePassword2.label;"/>
<menuitem id="syncResetPassphrase" label="&myRecoveryKey.label;"/>
<menuseparator/>
<menuitem id="syncReset" label="&resetSync2.label;"/>
<menuitem label="&changePassword2.label;"
oncommand="gSyncUtils.changePassword();"/>
<menuitem label="&myRecoveryKey.label;"
oncommand="gSyncUtils.resetPassphrase();"/>
<menuseparator/>
<menuitem label="&resetSync2.label;"
oncommand="gSyncPane.resetSync();"/>
</menupopup>
</button>
</hbox>
<hbox>
<label id="syncAddDeviceLabel"
class="text-link">
&pairDevice.label;
</label>
class="text-link"
onclick="gSyncPane.openAddDevice(); return false;"
value="&pairDevice.label;"/>
</hbox>
<vbox>
<label>&syncMy.label;</label>
<label value="&syncMy.label;" />
<richlistbox id="syncEnginesList"
orient="vertical">
orient="vertical"
onselect="if (this.selectedCount) this.clearSelection();">
<!--
<richlistitem>
<checkbox label="&engine.addons.label;"
accesskey="&engine.addons.accesskey;"
preference="engine.addons"/>
</richlistitem>
-->
<richlistitem>
<checkbox label="&engine.bookmarks.label;"
accesskey="&engine.bookmarks.accesskey;"
@ -130,228 +125,42 @@
</columns>
<rows>
<row align="center">
<label control="syncComputerName">
&syncDeviceName.label;
</label>
<textbox id="syncComputerName"/>
<label value="&syncDeviceName.label;"
accesskey="&syncDeviceName.accesskey;"
control="syncComputerName"/>
<textbox id="syncComputerName"
onchange="gSyncUtils.changeName(this)"/>
</row>
</rows>
</grid>
<hbox>
<label id="unlinkDevice" class="text-link">
&unlinkDevice.label;
</label>
<label class="text-link"
onclick="gSyncPane.startOver(true); return false;"
value="&unlinkDevice.label;"/>
</hbox>
</groupbox>
<vbox id="tosPP-normal">
<label id="tosPP-normal-ToS" class="text-link">
&prefs.tosLink.label;
</label>
<label id="tosPP-normal-PP" class="text-link">
&prefs.ppLink.label;
</label>
</vbox>
<hbox id="tosPP" pack="center">
<label class="text-link"
onclick="event.stopPropagation();gSyncUtils.openToS();"
value="&prefs.tosLink.label;"/>
<label class="text-link"
onclick="event.stopPropagation();gSyncUtils.openPrivacyPolicy();"
value="&prefs.ppLink.label;"/>
</hbox>
</vbox>
<vbox id="needsUpdate" align="center" pack="center">
<hbox>
<label id="loginError"/>
<label id="loginErrorUpdatePass" class="text-link">
&updatePass.label;
</label>
<label id="loginErrorResetPass" class="text-link">
&resetPass.label;
</label>
<label id="loginError" value=""/>
<label class="text-link"
onclick="gSyncPane.updatePass(); return false;"
value="&updatePass.label;"/>
<label class="text-link"
onclick="gSyncPane.resetPass(); return false;"
value="&resetPass.label;"/>
</hbox>
<label id="loginErrorStartOver" class="text-link">
&unlinkDevice.label;
</label>
</vbox>
<!-- These panels are for the Firefox Accounts identity provider -->
<vbox id="noFxaAccount">
<hbox>
<vbox id="fxaContentWrapper">
<groupbox id="noFxaGroup">
<vbox>
<label id="noFxaCaption">&signedOut.caption;</label>
<description id="noFxaDescription" flex="1">&signedOut.description;</description>
<hbox class="fxaAccountBox">
<vbox>
<image class="fxaFirefoxLogo"/>
</vbox>
<vbox flex="1">
<label id="signedOutAccountBoxTitle">&signedOut.accountBox.title;</label>
<hbox class="fxaAccountBoxButtons">
<button id="noFxaSignUp" label="&signedOut.accountBox.create;" accesskey="&signedOut.accountBox.create.accesskey;"></button>
<button id="noFxaSignIn" label="&signedOut.accountBox.signin;" accesskey="&signedOut.accountBox.signin.accesskey;"></button>
</hbox>
</vbox>
</hbox>
</vbox>
</groupbox>
</vbox>
<vbox>
<image class="fxaSyncIllustration"/>
</vbox>
</hbox>
<label class="fxaMobilePromo">
&mobilePromo3.start;<!-- We put these comments to avoid inserting white spaces
--><label id="fxaMobilePromo-android"
class="androidLink text-link"><!--
-->&mobilePromo3.androidLink;</label><!--
-->&mobilePromo3.iOSBefore;<!--
--><label id="fxaMobilePromo-ios"
class="iOSLink text-link"><!--
-->&mobilePromo3.iOSLink;</label><!--
-->&mobilePromo3.end;
</label>
</vbox>
<vbox id="hasFxaAccount">
<hbox>
<vbox id="fxaContentWrapper">
<groupbox id="fxaGroup">
<caption><label>&syncBrand.fxAccount.label;</label></caption>
<deck id="fxaLoginStatus">
<!-- logged in and verified and all is good -->
<hbox id="fxaLoginVerified" class="fxaAccountBox">
<vbox align="center" pack="center">
<image id="fxaProfileImage" class="actionable"
role="button"
onclick="gSyncPane.openChangeProfileImage(event);" hidden="true"
onkeypress="gSyncPane.openChangeProfileImage(event);"
tooltiptext="&profilePicture.tooltip;"/>
</vbox>
<vbox flex="1" pack="center">
<label id="fxaDisplayName" hidden="true"/>
<label id="fxaEmailAddress1"/>
<hbox class="fxaAccountBoxButtons">
<button id="fxaUnlinkButton" label="&disconnect.label;" accesskey="&disconnect.accesskey;"/>
<html:a id="verifiedManage" target="_blank"
accesskey="&verifiedManage.accesskey;"
onkeypress="gSyncPane.openManageFirefoxAccount(event);"><!--
-->&verifiedManage.label;</html:a>
</hbox>
</vbox>
</hbox>
<!-- logged in to an unverified account -->
<hbox id="fxaLoginUnverified" class="fxaAccountBox">
<vbox>
<image id="fxaProfileImage"/>
</vbox>
<vbox flex="1">
<hbox>
<vbox><image id="fxaLoginRejectedWarning"/></vbox>
<description flex="1">
&signedInUnverified.beforename.label;
<label id="fxaEmailAddress2"/>
&signedInUnverified.aftername.label;
</description>
</hbox>
<hbox class="fxaAccountBoxButtons">
<button id="verifyFxaAccount" accesskey="&verify.accesskey;">&verify.label;</button>
<button id="unverifiedUnlinkFxaAccount" accesskey="&forget.accesskey;">&forget.label;</button>
</hbox>
</vbox>
</hbox>
<!-- logged in locally but server rejected credentials -->
<hbox id="fxaLoginRejected" class="fxaAccountBox">
<vbox>
<image id="fxaProfileImage"/>
</vbox>
<vbox flex="1">
<hbox>
<vbox><image id="fxaLoginRejectedWarning"/></vbox>
<description flex="1">
&signedInLoginFailure.beforename.label;
<label id="fxaEmailAddress3"/>
&signedInLoginFailure.aftername.label;
</description>
</hbox>
<hbox class="fxaAccountBoxButtons">
<button id="rejectReSignIn" accessky="&signIn.accesskey;">&signIn.label;</button>
<button id="rejectUnlinkFxaAccount" accesskey="&forget.accesskey;">&forget.label;</button>
</hbox>
</vbox>
</hbox>
</deck>
</groupbox>
<groupbox id="syncOptions">
<caption><label>&signedIn.engines.label;</label></caption>
<hbox id="fxaSyncEngines">
<vbox align="start" flex="1">
<checkbox label="&engine.tabs.label;"
accesskey="&engine.tabs.accesskey;"
preference="engine.tabs"/>
<checkbox label="&engine.bookmarks.label;"
accesskey="&engine.bookmarks.accesskey;"
preference="engine.bookmarks"/>
<checkbox label="&engine.passwords.label;"
accesskey="&engine.passwords.accesskey;"
preference="engine.passwords"/>
</vbox>
<vbox align="start" flex="1">
<checkbox label="&engine.history.label;"
accesskey="&engine.history.accesskey;"
preference="engine.history"/>
<checkbox label="&engine.addons.label;"
accesskey="&engine.addons.accesskey;"
preference="engine.addons"/>
<checkbox label="&engine.prefs.label;"
accesskey="&engine.prefs.accesskey;"
preference="engine.prefs"/>
</vbox>
<spacer/>
</hbox>
</groupbox>
</vbox>
<vbox>
<image class="fxaSyncIllustration"/>
</vbox>
</hbox>
<groupbox>
<caption>
<label control="fxaSyncComputerName">
&fxaSyncDeviceName.label;
</label>
</caption>
<hbox id="fxaDeviceName">
<textbox id="fxaSyncComputerName" disabled="true"/>
<hbox>
<button id="fxaChangeDeviceName"
label="&changeSyncDeviceName.label;"
accesskey="&changeSyncDeviceName.accesskey;"/>
<button id="fxaCancelChangeDeviceName"
label="&cancelChangeSyncDeviceName.label;"
accesskey="&cancelChangeSyncDeviceName.accesskey;"
hidden="true"/>
<button id="fxaSaveChangeDeviceName"
label="&saveChangeSyncDeviceName.label;"
accesskey="&saveChangeSyncDeviceName.accesskey;"
hidden="true"/>
</hbox>
</hbox>
</groupbox>
<label class="fxaMobilePromo">
&mobilePromo3.start;<!-- We put these comments to avoid inserting white spaces
--><label class="androidLink text-link" id="fxaMobilePromo-android-hasFxaAccount"><!--
-->&mobilePromo3.androidLink;</label><!--
-->&mobilePromo3.iOSBefore;<!--
--><label class="iOSLink text-link" id="fxaMobilePromo-ios-hasFxaAccount"><!--
-->&mobilePromo3.iOSLink;</label><!--
-->&mobilePromo3.end;
</label>
<vbox id="tosPP-small" align="start">
<label id="tosPP-small-ToS" class="text-link">
&prefs.tosLink.label;
</label>
<label id="tosPP-small-PP" class="text-link">
&fxaPrivacyNotice.link.label;
</label>
</vbox>
<label class="text-link"
onclick="gSyncPane.startOver(true); return false;"
value="&unlinkDevice.label;"/>
</vbox>
</deck>

View file

@ -0,0 +1,46 @@
<?xml version="1.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/. -->
<bindings id="tabBindings"
xmlns="http://www.mozilla.org/xbl"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:xbl="http://www.mozilla.org/xbl">
<binding id="tab-listing" extends="chrome://global/content/bindings/richlistbox.xml#richlistitem">
<content>
<xul:hbox flex="1">
<xul:vbox pack="start">
<xul:image class="tabIcon"
xbl:inherits="src=icon"/>
</xul:vbox>
<xul:vbox pack="start" flex="1">
<xul:label xbl:inherits="value=title,selected"
crop="end" flex="1" class="title"/>
<xul:label xbl:inherits="value=url,selected"
crop="end" flex="1" class="url"/>
</xul:vbox>
</xul:hbox>
</content>
<handlers>
<handler event="dblclick" button="0">
<![CDATA[
RemoteTabViewer.openSelected();
]]>
</handler>
</handlers>
</binding>
<binding id="client-listing" extends="chrome://global/content/bindings/richlistbox.xml#richlistitem">
<content>
<xul:hbox pack="start" align="center" onfocus="event.target.blur()" onselect="return false;">
<xul:image/>
<xul:label xbl:inherits="value=clientName"
class="clientName"
crop="center" flex="1"/>
</xul:hbox>
</content>
</binding>
</bindings>

View file

@ -0,0 +1,11 @@
/* 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/. */
richlistitem[type="tab"] {
-moz-binding: url(chrome://browser/content/sync/aboutSyncTabs-bindings.xml#tab-listing);
}
richlistitem[type="client"] {
-moz-binding: url(chrome://browser/content/sync/aboutSyncTabs-bindings.xml#client-listing);
}

View file

@ -0,0 +1,313 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
var Cu = Components.utils;
Cu.import("resource://services-common/utils.js");
Cu.import("resource://services-sync/main.js");
Cu.import("resource:///modules/PlacesUIUtils.jsm");
Cu.import("resource://gre/modules/PlacesUtils.jsm", this);
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
var RemoteTabViewer = {
_tabsList: null,
init: function () {
Services.obs.addObserver(this, "weave:service:login:finish", false);
Services.obs.addObserver(this, "weave:engine:sync:finish", false);
this._tabsList = document.getElementById("tabsList");
this.buildList(true);
},
uninit: function () {
Services.obs.removeObserver(this, "weave:service:login:finish");
Services.obs.removeObserver(this, "weave:engine:sync:finish");
},
createItem: function(attrs) {
let item = document.createElement("richlistitem");
// Copy the attributes from the argument into the item
for (let attr in attrs) {
item.setAttribute(attr, attrs[attr]);
}
if (attrs["type"] == "tab") {
item.label = attrs.title != "" ? attrs.title : attrs.url;
}
return item;
},
filterTabs: function(event) {
let val = event.target.value.toLowerCase();
let numTabs = this._tabsList.getRowCount();
let clientTabs = 0;
let currentClient = null;
for (let i = 0; i < numTabs; i++) {
let item = this._tabsList.getItemAtIndex(i);
let hide = false;
if (item.getAttribute("type") == "tab") {
if (!item.getAttribute("url").toLowerCase().includes(val) &&
!item.getAttribute("title").toLowerCase().includes(val)) {
hide = true;
} else {
clientTabs++;
}
}
else if (item.getAttribute("type") == "client") {
if (currentClient) {
if (clientTabs == 0) {
currentClient.hidden = true;
}
}
currentClient = item;
clientTabs = 0;
}
item.hidden = hide;
}
if (clientTabs == 0) {
currentClient.hidden = true;
}
},
openSelected: function() {
let items = this._tabsList.selectedItems;
let urls = [];
for (let i = 0;i < items.length;i++) {
if (items[i].getAttribute("type") == "tab") {
urls.push(items[i].getAttribute("url"));
let index = this._tabsList.getIndexOfItem(items[i]);
this._tabsList.removeItemAt(index);
}
}
if (urls.length) {
getTopWin().gBrowser.loadTabs(urls);
this._tabsList.clearSelection();
}
},
bookmarkSingleTab: function() {
let item = this._tabsList.selectedItems[0];
let uri = Weave.Utils.makeURI(item.getAttribute("url"));
let title = item.getAttribute("title");
PlacesUIUtils.showBookmarkDialog({ action: "add"
, type: "bookmark"
, uri: uri
, title: title
, hiddenRows: [ "description"
, "location"
, "loadInSidebar"
, "keyword" ]
}, window.top);
},
bookmarkSelectedTabs: function() {
let items = this._tabsList.selectedItems;
let URIs = [];
for (let i = 0;i < items.length;i++) {
if (items[i].getAttribute("type") == "tab") {
let uri = Weave.Utils.makeURI(items[i].getAttribute("url"));
if (!uri) {
continue;
}
URIs.push(uri);
}
}
if (URIs.length) {
PlacesUIUtils.showBookmarkDialog({ action: "add"
, type: "folder"
, URIList: URIs
, hiddenRows: [ "description" ]
}, window.top);
}
},
getIcon: function (iconUri, defaultIcon) {
try {
let iconURI = Weave.Utils.makeURI(iconUri);
return PlacesUtils.favicons.getFaviconLinkForIcon(iconURI).spec;
} catch (ex) {
// Do nothing.
}
// Just give the provided default icon or the system's default.
return defaultIcon || PlacesUtils.favicons.defaultFavicon.spec;
},
_waitingForBuildList: false,
_buildListRequested: false,
buildList: function (force) {
if (this._waitingForBuildList) {
this._buildListRequested = true;
return;
}
this._waitingForBuildList = true;
this._buildListRequested = false;
this._clearTabList();
if (Weave.Service.isLoggedIn && this._refetchTabs(force)) {
this._generateWeaveTabList();
} else {
//XXXzpao We should say something about not being logged in & not having data
// or tell the appropriate condition. (bug 583344)
}
function complete() {
this._waitingForBuildList = false;
if (this._buildListRequested) {
CommonUtils.nextTick(this.buildList, this);
}
}
complete();
},
_clearTabList: function () {
let list = this._tabsList;
// Clear out existing richlistitems
let count = list.getRowCount();
if (count > 0) {
for (let i = count - 1; i >= 0; i--) {
list.removeItemAt(i);
}
}
},
_generateWeaveTabList: function () {
let engine = Weave.Service.engineManager.get("tabs");
let list = this._tabsList;
let seenURLs = new Set();
let localURLs = engine.getOpenURLs();
for (let [guid, client] in Iterator(engine.getAllClients())) {
// Create the client node, but don't add it in-case we don't show any tabs
let appendClient = true;
client.tabs.forEach(function({title, urlHistory, icon}) {
let url = urlHistory[0];
if (!url || localURLs.has(url) || seenURLs.has(url)) {
return;
}
seenURLs.add(url);
if (appendClient) {
let attrs = {
type: "client",
clientName: client.clientName,
class: Weave.Service.clientsEngine.isMobile(client.id) ? "mobile" : "desktop"
};
let clientEnt = this.createItem(attrs);
list.appendChild(clientEnt);
appendClient = false;
clientEnt.disabled = true;
}
let attrs = {
type: "tab",
title: title || url,
url: url,
icon: this.getIcon(icon),
}
let tab = this.createItem(attrs);
list.appendChild(tab);
}, this);
}
},
adjustContextMenu: function(event) {
let mode = "all";
switch (this._tabsList.selectedItems.length) {
case 0:
break;
case 1:
mode = "single"
break;
default:
mode = "multiple";
break;
}
let menu = document.getElementById("tabListContext");
let el = menu.firstChild;
while (el) {
let showFor = el.getAttribute("showFor");
if (showFor) {
el.hidden = showFor != mode && showFor != "all";
}
el = el.nextSibling;
}
},
_refetchTabs: function(force) {
if (!force) {
// Don't bother refetching tabs if we already did so recently
let lastFetch = 0;
try {
lastFetch = Services.prefs.getIntPref("services.sync.lastTabFetch");
}
catch (e) {
/* Just use the default value of 0 */
}
let now = Math.floor(Date.now() / 1000);
if (now - lastFetch < 30) {
return false;
}
}
// if Clients hasn't synced yet this session, we need to sync it as well.
if (Weave.Service.clientsEngine.lastSync == 0) {
Weave.Service.clientsEngine.sync();
}
// Force a sync only for the tabs engine
let engine = Weave.Service.engineManager.get("tabs");
engine.lastModified = null;
engine.sync();
Services.prefs.setIntPref("services.sync.lastTabFetch",
Math.floor(Date.now() / 1000));
return true;
},
observe: function(subject, topic, data) {
switch (topic) {
case "weave:service:login:finish":
this.buildList(true);
break;
case "weave:engine:sync:finish":
if (subject == "tabs") {
this.buildList(false);
}
break;
}
},
handleClick: function(event) {
if (event.target.getAttribute("type") != "tab") {
return;
}
if (event.button == 1) {
let url = event.target.getAttribute("url");
openUILink(url, event);
let index = this._tabsList.getIndexOfItem(event.target);
this._tabsList.removeItemAt(index);
}
}
}

View file

@ -0,0 +1,68 @@
<?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/. -->
<?xml-stylesheet href="chrome://browser/skin/" type="text/css"?>
<?xml-stylesheet href="chrome://browser/skin/aboutSyncTabs.css" type="text/css"?>
<?xml-stylesheet href="chrome://browser/content/sync/aboutSyncTabs.css" type="text/css"?>
<!DOCTYPE window [
<!ENTITY % aboutSyncTabsDTD SYSTEM "chrome://browser/locale/aboutSyncTabs.dtd">
%aboutSyncTabsDTD;
]>
<window id="tabs-display"
onload="RemoteTabViewer.init()"
onunload="RemoteTabViewer.uninit()"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:html="http://www.w3.org/1999/xhtml"
title="&tabs.otherDevices.label;">
<script type="application/javascript;version=1.8" src="chrome://browser/content/sync/aboutSyncTabs.js"/>
<script type="application/javascript" src="chrome://browser/content/utilityOverlay.js"/>
<html:head>
<html:link rel="icon" href="chrome://browser/skin/sync-16.png"/>
</html:head>
<popupset id="contextmenus">
<menupopup id="tabListContext">
<menuitem label="&tabs.context.openTab.label;"
accesskey="&tabs.context.openTab.accesskey;"
oncommand="RemoteTabViewer.openSelected()"
showFor="single"/>
<menuitem label="&tabs.context.bookmarkSingleTab.label;"
accesskey="&tabs.context.bookmarkSingleTab.accesskey;"
oncommand="RemoteTabViewer.bookmarkSingleTab(event)"
showFor="single"/>
<menuitem label="&tabs.context.openMultipleTabs.label;"
accesskey="&tabs.context.openMultipleTabs.accesskey;"
oncommand="RemoteTabViewer.openSelected()"
showFor="multiple"/>
<menuitem label="&tabs.context.bookmarkMultipleTabs.label;"
accesskey="&tabs.context.bookmarkMultipleTabs.accesskey;"
oncommand="RemoteTabViewer.bookmarkSelectedTabs()"
showFor="multiple"/>
<menuseparator/>
<menuitem label="&tabs.context.refreshList.label;"
accesskey="&tabs.context.refreshList.accesskey;"
oncommand="RemoteTabViewer.buildList()"
showFor="all"/>
</menupopup>
</popupset>
<richlistbox context="tabListContext" id="tabsList" seltype="multiple"
align="center" flex="1"
onclick="RemoteTabViewer.handleClick(event)"
oncontextmenu="RemoteTabViewer.adjustContextMenu(event)">
<hbox id="headers" align="center">
<label id="tabsListHeading"
value="&tabs.otherDevices.label;"/>
<spacer flex="1"/>
<textbox type="search"
emptytext="&tabs.searchText.label;"
oncommand="RemoteTabViewer.filterTabs(event)"/>
</hbox>
</richlistbox>
</window>

View file

@ -0,0 +1,157 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
var Ci = Components.interfaces;
var Cc = Components.classes;
var Cu = Components.utils;
Cu.import("resource://services-sync/main.js");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
const PIN_PART_LENGTH = 4;
const ADD_DEVICE_PAGE = 0;
const SYNC_KEY_PAGE = 1;
const DEVICE_CONNECTED_PAGE = 2;
var gSyncAddDevice = {
init: function init() {
this.pin1.setAttribute("maxlength", PIN_PART_LENGTH);
this.pin2.setAttribute("maxlength", PIN_PART_LENGTH);
this.pin3.setAttribute("maxlength", PIN_PART_LENGTH);
this.nextFocusEl = {pin1: this.pin2,
pin2: this.pin3,
pin3: this.wizard.getButton("next")};
this.throbber = document.getElementById("pairDeviceThrobber");
this.errorRow = document.getElementById("errorRow");
// Kick off a sync. That way the server will have the most recent data from
// this computer and it will show up immediately on the new device.
Weave.Service.scheduler.scheduleNextSync(0);
},
onPageShow: function onPageShow() {
this.wizard.getButton("back").hidden = true;
switch (this.wizard.pageIndex) {
case ADD_DEVICE_PAGE:
this.onTextBoxInput();
this.wizard.canRewind = false;
this.wizard.getButton("next").hidden = false;
this.pin1.focus();
break;
case SYNC_KEY_PAGE:
this.wizard.canAdvance = false;
this.wizard.canRewind = true;
this.wizard.getButton("back").hidden = false;
this.wizard.getButton("next").hidden = true;
document.getElementById("weavePassphrase").value =
Weave.Utils.hyphenatePassphrase(Weave.Service.identity.syncKey);
break;
case DEVICE_CONNECTED_PAGE:
this.wizard.canAdvance = true;
this.wizard.canRewind = false;
this.wizard.getButton("cancel").hidden = true;
break;
}
},
onWizardAdvance: function onWizardAdvance() {
switch (this.wizard.pageIndex) {
case ADD_DEVICE_PAGE:
this.startTransfer();
return false;
case DEVICE_CONNECTED_PAGE:
window.close();
return false;
}
return true;
},
startTransfer: function startTransfer() {
this.errorRow.hidden = true;
// When onAbort is called, Weave may already be gone.
const JPAKE_ERROR_USERABORT = Weave.JPAKE_ERROR_USERABORT;
let self = this;
let jpakeclient = this._jpakeclient = new Weave.JPAKEClient({
onPaired: function onPaired() {
let credentials = {account: Weave.Service.identity.account,
password: Weave.Service.identity.basicPassword,
synckey: Weave.Service.identity.syncKey,
serverURL: Weave.Service.serverURL};
jpakeclient.sendAndComplete(credentials);
},
onComplete: function onComplete() {
delete self._jpakeclient;
self.wizard.pageIndex = DEVICE_CONNECTED_PAGE;
// Schedule a Sync for soonish to fetch the data uploaded by the
// device with which we just paired.
Weave.Service.scheduler.scheduleNextSync(Weave.Service.scheduler.activeInterval);
},
onAbort: function onAbort(error) {
delete self._jpakeclient;
// Aborted by user, ignore.
if (error == JPAKE_ERROR_USERABORT) {
return;
}
self.errorRow.hidden = false;
self.throbber.hidden = true;
self.pin1.value = self.pin2.value = self.pin3.value = "";
self.pin1.disabled = self.pin2.disabled = self.pin3.disabled = false;
self.pin1.focus();
}
});
this.throbber.hidden = false;
this.pin1.disabled = this.pin2.disabled = this.pin3.disabled = true;
this.wizard.canAdvance = false;
let pin = this.pin1.value + this.pin2.value + this.pin3.value;
let expectDelay = false;
jpakeclient.pairWithPIN(pin, expectDelay);
},
onWizardBack: function onWizardBack() {
if (this.wizard.pageIndex != SYNC_KEY_PAGE)
return true;
this.wizard.pageIndex = ADD_DEVICE_PAGE;
return false;
},
onWizardCancel: function onWizardCancel() {
if (this._jpakeclient) {
this._jpakeclient.abort();
delete this._jpakeclient;
}
return true;
},
onTextBoxInput: function onTextBoxInput(textbox) {
if (textbox && textbox.value.length == PIN_PART_LENGTH)
this.nextFocusEl[textbox.id].focus();
this.wizard.canAdvance = (this.pin1.value.length == PIN_PART_LENGTH
&& this.pin2.value.length == PIN_PART_LENGTH
&& this.pin3.value.length == PIN_PART_LENGTH);
},
goToSyncKeyPage: function goToSyncKeyPage() {
this.wizard.pageIndex = SYNC_KEY_PAGE;
}
};
// onWizardAdvance() and onPageShow() are run before init() so we'll set
// these up as lazy getters.
["wizard", "pin1", "pin2", "pin3"].forEach(function (id) {
XPCOMUtils.defineLazyGetter(gSyncAddDevice, id, function() {
return document.getElementById(id);
});
});

View file

@ -0,0 +1,129 @@
<?xml version="1.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/. -->
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<?xml-stylesheet href="chrome://browser/skin/syncSetup.css" type="text/css"?>
<?xml-stylesheet href="chrome://browser/skin/syncCommon.css" type="text/css"?>
<!DOCTYPE window [
<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
<!ENTITY % syncBrandDTD SYSTEM "chrome://browser/locale/syncBrand.dtd">
<!ENTITY % syncSetupDTD SYSTEM "chrome://browser/locale/syncSetup.dtd">
%brandDTD;
%syncBrandDTD;
%syncSetupDTD;
]>
<wizard xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:html="http://www.w3.org/1999/xhtml"
id="wizard"
title="&pairDevice.title.label;"
windowtype="Sync:AddDevice"
persist="screenX screenY"
onwizardnext="return gSyncAddDevice.onWizardAdvance();"
onwizardback="return gSyncAddDevice.onWizardBack();"
onwizardcancel="gSyncAddDevice.onWizardCancel();"
onload="gSyncAddDevice.init();">
<script type="application/javascript"
src="chrome://browser/content/sync/addDevice.js"/>
<script type="application/javascript"
src="chrome://browser/content/sync/utils.js"/>
<script type="application/javascript"
src="chrome://browser/content/utilityOverlay.js"/>
<script type="application/javascript"
src="chrome://global/content/printUtils.js"/>
<wizardpage id="addDevicePage"
label="&pairDevice.title.label;"
onpageshow="gSyncAddDevice.onPageShow();">
<description>
&pairDevice.dialog.description.label;
<label class="text-link"
value="&addDevice.showMeHow.label;"
href="http://www.palemoon.org/sync/help/easy-setup.shtml"/>
</description>
<separator class="groove-thin"/>
<description>
&addDevice.dialog.enterCode.label;
</description>
<separator class="groove-thin"/>
<vbox align="center">
<textbox id="pin1"
class="pin"
oninput="gSyncAddDevice.onTextBoxInput(this);"
onfocus="this.select();"
/>
<textbox id="pin2"
class="pin"
oninput="gSyncAddDevice.onTextBoxInput(this);"
onfocus="this.select();"
/>
<textbox id="pin3"
class="pin"
oninput="gSyncAddDevice.onTextBoxInput(this);"
onfocus="this.select();"
/>
</vbox>
<separator class="groove-thin"/>
<vbox id="pairDeviceThrobber" align="center" hidden="true">
<image/>
</vbox>
<hbox id="errorRow" pack="center" hidden="true">
<image class="statusIcon" status="error"/>
<label class="status"
value="&addDevice.dialog.tryAgain.label;"/>
</hbox>
<spacer flex="3"/>
<label class="text-link"
value="&addDevice.dontHaveDevice.label;"
onclick="gSyncAddDevice.goToSyncKeyPage();"/>
</wizardpage>
<!-- Need a non-empty label here, otherwise we get a default label on Mac -->
<wizardpage id="syncKeyPage"
label=" "
onpageshow="gSyncAddDevice.onPageShow();">
<description>
&addDevice.dialog.recoveryKey.label;
</description>
<spacer/>
<groupbox>
<label value="&recoveryKeyEntry.label;"
accesskey="&recoveryKeyEntry.accesskey;"
control="weavePassphrase"/>
<textbox id="weavePassphrase"
readonly="true"/>
</groupbox>
<groupbox align="center">
<description>&recoveryKeyBackup.description;</description>
<hbox>
<button id="printSyncKeyButton"
label="&button.syncKeyBackup.print.label;"
accesskey="&button.syncKeyBackup.print.accesskey;"
oncommand="gSyncUtils.passphrasePrint('weavePassphrase');"/>
<button id="saveSyncKeyButton"
label="&button.syncKeyBackup.save.label;"
accesskey="&button.syncKeyBackup.save.accesskey;"
oncommand="gSyncUtils.passphraseSave('weavePassphrase');"/>
</hbox>
</groupbox>
</wizardpage>
<wizardpage id="deviceConnectedPage"
label="&addDevice.dialog.connected.label;"
onpageshow="gSyncAddDevice.onPageShow();">
<vbox align="center">
<image id="successPageIcon"/>
</vbox>
<separator/>
<description class="normal">
&addDevice.dialog.successful.label;
</description>
</wizardpage>
</wizard>

View file

@ -0,0 +1,234 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
var Ci = Components.interfaces;
var Cc = Components.classes;
Components.utils.import("resource://services-sync/main.js");
Components.utils.import("resource://gre/modules/Services.jsm");
var Change = {
_dialog: null,
_dialogType: null,
_status: null,
_statusIcon: null,
_firstBox: null,
_secondBox: null,
get _passphraseBox() {
delete this._passphraseBox;
return this._passphraseBox = document.getElementById("passphraseBox");
},
get _currentPasswordInvalid() {
return Weave.Status.login == Weave.LOGIN_FAILED_LOGIN_REJECTED;
},
get _updatingPassphrase() {
return this._dialogType == "UpdatePassphrase";
},
onLoad: function Change_onLoad() {
/* Load labels */
let introText = document.getElementById("introText");
let introText2 = document.getElementById("introText2");
let warningText = document.getElementById("warningText");
// load some other elements & info from the window
this._dialog = document.getElementById("change-dialog");
this._dialogType = window.arguments[0];
this._duringSetup = window.arguments[1];
this._status = document.getElementById("status");
this._statusIcon = document.getElementById("statusIcon");
this._statusRow = document.getElementById("statusRow");
this._firstBox = document.getElementById("textBox1");
this._secondBox = document.getElementById("textBox2");
this._dialog.getButton("finish").disabled = true;
this._dialog.getButton("back").hidden = true;
this._stringBundle =
Services.strings.createBundle("chrome://browser/locale/syncGenericChange.properties");
switch (this._dialogType) {
case "UpdatePassphrase":
case "ResetPassphrase":
document.getElementById("textBox1Row").hidden = true;
document.getElementById("textBox2Row").hidden = true;
document.getElementById("passphraseLabel").value
= this._str("new.recoverykey.label");
document.getElementById("passphraseSpacer").hidden = false;
if (this._updatingPassphrase) {
document.getElementById("passphraseHelpBox").hidden = false;
document.title = this._str("new.recoverykey.title");
introText.textContent = this._str("new.recoverykey.introText");
this._dialog.getButton("finish").label
= this._str("new.recoverykey.acceptButton");
}
else {
document.getElementById("generatePassphraseButton").hidden = false;
document.getElementById("passphraseBackupButtons").hidden = false;
let pp = Weave.Service.identity.syncKey;
if (Weave.Utils.isPassphrase(pp))
pp = Weave.Utils.hyphenatePassphrase(pp);
this._passphraseBox.value = pp;
this._passphraseBox.focus();
document.title = this._str("change.recoverykey.title");
introText.textContent = this._str("change.synckey.introText2");
warningText.textContent = this._str("change.recoverykey.warningText");
this._dialog.getButton("finish").label
= this._str("change.recoverykey.acceptButton");
if (this._duringSetup) {
this._dialog.getButton("finish").disabled = false;
}
}
break;
case "ChangePassword":
document.getElementById("passphraseRow").hidden = true;
let box1label = document.getElementById("textBox1Label");
let box2label = document.getElementById("textBox2Label");
box1label.value = this._str("new.password.label");
if (this._currentPasswordInvalid) {
document.title = this._str("new.password.title");
introText.textContent = this._str("new.password.introText");
this._dialog.getButton("finish").label
= this._str("new.password.acceptButton");
document.getElementById("textBox2Row").hidden = true;
}
else {
document.title = this._str("change.password.title");
box2label.value = this._str("new.password.confirm");
introText.textContent = this._str("change.password3.introText");
warningText.textContent = this._str("change.password.warningText");
this._dialog.getButton("finish").label
= this._str("change.password.acceptButton");
}
break;
}
document.getElementById("change-page")
.setAttribute("label", document.title);
},
_clearStatus: function _clearStatus() {
this._status.value = "";
this._statusIcon.removeAttribute("status");
},
_updateStatus: function Change__updateStatus(str, state) {
this._updateStatusWithString(this._str(str), state);
},
_updateStatusWithString: function Change__updateStatusWithString(string, state) {
this._statusRow.hidden = false;
this._status.value = string;
this._statusIcon.setAttribute("status", state);
let error = state == "error";
this._dialog.getButton("cancel").disabled = !error;
this._dialog.getButton("finish").disabled = !error;
document.getElementById("printSyncKeyButton").disabled = !error;
document.getElementById("saveSyncKeyButton").disabled = !error;
if (state == "success")
window.setTimeout(window.close, 1500);
},
onDialogAccept: function() {
switch (this._dialogType) {
case "UpdatePassphrase":
case "ResetPassphrase":
return this.doChangePassphrase();
break;
case "ChangePassword":
return this.doChangePassword();
break;
}
},
doGeneratePassphrase: function () {
let passphrase = Weave.Utils.generatePassphrase();
this._passphraseBox.value = Weave.Utils.hyphenatePassphrase(passphrase);
this._dialog.getButton("finish").disabled = false;
},
doChangePassphrase: function Change_doChangePassphrase() {
let pp = Weave.Utils.normalizePassphrase(this._passphraseBox.value);
if (this._updatingPassphrase) {
Weave.Service.identity.syncKey = pp;
if (Weave.Service.login()) {
this._updateStatus("change.recoverykey.success", "success");
Weave.Service.persistLogin();
Weave.Service.scheduler.delayedAutoConnect(0);
}
else {
this._updateStatus("new.passphrase.status.incorrect", "error");
}
}
else {
this._updateStatus("change.recoverykey.label", "active");
if (Weave.Service.changePassphrase(pp))
this._updateStatus("change.recoverykey.success", "success");
else
this._updateStatus("change.recoverykey.error", "error");
}
return false;
},
doChangePassword: function Change_doChangePassword() {
if (this._currentPasswordInvalid) {
Weave.Service.identity.basicPassword = this._firstBox.value;
if (Weave.Service.login()) {
this._updateStatus("change.password.status.success", "success");
Weave.Service.persistLogin();
}
else {
this._updateStatus("new.password.status.incorrect", "error");
}
}
else {
this._updateStatus("change.password.status.active", "active");
if (Weave.Service.changePassword(this._firstBox.value))
this._updateStatus("change.password.status.success", "success");
else
this._updateStatus("change.password.status.error", "error");
}
return false;
},
validate: function (event) {
let valid = false;
let errorString = "";
if (this._dialogType == "ChangePassword") {
if (this._currentPasswordInvalid)
[valid, errorString] = gSyncUtils.validatePassword(this._firstBox);
else
[valid, errorString] = gSyncUtils.validatePassword(this._firstBox, this._secondBox);
}
else {
//Pale Moon: Enforce minimum length of 8 for allowed custom passphrase
//and don't restrict it to "out of sync" situations only. People who
//go to this page generally know what they are doing ;)
valid = this._passphraseBox.value.length >= 8;
}
if (errorString == "")
this._clearStatus();
else
this._updateStatusWithString(errorString, "error");
this._statusRow.hidden = valid;
this._dialog.getButton("finish").disabled = !valid;
},
_str: function Change__string(str) {
return this._stringBundle.GetStringFromName(str);
}
};

View file

@ -0,0 +1,123 @@
<?xml version="1.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/. -->
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<?xml-stylesheet href="chrome://browser/skin/syncSetup.css" type="text/css"?>
<?xml-stylesheet href="chrome://browser/skin/syncCommon.css" type="text/css"?>
<!DOCTYPE window [
<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
<!ENTITY % syncBrandDTD SYSTEM "chrome://browser/locale/syncBrand.dtd">
<!ENTITY % syncSetupDTD SYSTEM "chrome://browser/locale/syncSetup.dtd">
%brandDTD;
%syncBrandDTD;
%syncSetupDTD;
]>
<wizard xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:html="http://www.w3.org/1999/xhtml"
id="change-dialog"
windowtype="Weave:ChangeSomething"
persist="screenX screenY"
onwizardnext="Change.onLoad()"
onwizardfinish="return Change.onDialogAccept();">
<script type="application/javascript"
src="chrome://browser/content/sync/genericChange.js"/>
<script type="application/javascript"
src="chrome://browser/content/sync/utils.js"/>
<script type="application/javascript"
src="chrome://global/content/printUtils.js"/>
<wizardpage id="change-page"
label="">
<description id="introText">
</description>
<separator class="thin"/>
<groupbox>
<grid>
<columns>
<column align="right"/>
<column flex="3"/>
<column flex="1"/>
</columns>
<rows>
<row id="textBox1Row" align="center">
<label id="textBox1Label" control="textBox1"/>
<textbox id="textBox1" type="password" oninput="Change.validate()"/>
<spacer/>
</row>
<row id="textBox2Row" align="center">
<label id="textBox2Label" control="textBox2"/>
<textbox id="textBox2" type="password" oninput="Change.validate()"/>
<spacer/>
</row>
</rows>
</grid>
<vbox id="passphraseRow">
<hbox flex="1">
<label id="passphraseLabel" control="passphraseBox"/>
<spacer flex="1"/>
<label id="generatePassphraseButton"
hidden="true"
value="&syncGenerateNewKey.label;"
class="text-link inline-link"
onclick="event.stopPropagation();
Change.doGeneratePassphrase();"/>
</hbox>
<textbox id="passphraseBox"
flex="1"
onfocus="this.select()"
oninput="Change.validate()"/>
</vbox>
<vbox id="feedback" pack="center">
<hbox id="statusRow" align="center">
<image id="statusIcon" class="statusIcon"/>
<label id="status" class="status" value=" "/>
</hbox>
</vbox>
</groupbox>
<separator class="thin"/>
<hbox id="passphraseBackupButtons"
hidden="true"
pack="center">
<button id="printSyncKeyButton"
label="&button.syncKeyBackup.print.label;"
accesskey="&button.syncKeyBackup.print.accesskey;"
oncommand="gSyncUtils.passphrasePrint('passphraseBox');"/>
<button id="saveSyncKeyButton"
label="&button.syncKeyBackup.save.label;"
accesskey="&button.syncKeyBackup.save.accesskey;"
oncommand="gSyncUtils.passphraseSave('passphraseBox');"/>
</hbox>
<vbox id="passphraseHelpBox"
hidden="true">
<description>
&existingRecoveryKey.description;
<label class="text-link"
href="http://www.palemoon.org/sync/help/recoverykey.shtml">
&addDevice.showMeHow.label;
</label>
</description>
</vbox>
<spacer id="passphraseSpacer"
flex="1"
hidden="true"/>
<description id="warningText" class="data">
</description>
<spacer flex="1"/>
</wizardpage>
</wizard>

View file

@ -0,0 +1,22 @@
# 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/sync/aboutSyncTabs.xul
content/browser/sync/aboutSyncTabs.js
content/browser/sync/aboutSyncTabs.css
content/browser/sync/aboutSyncTabs-bindings.xml
content/browser/sync/setup.xul
content/browser/sync/addDevice.js
content/browser/sync/addDevice.xul
content/browser/sync/setup.js
content/browser/sync/genericChange.xul
content/browser/sync/genericChange.js
content/browser/sync/key.xhtml
content/browser/sync/notification.xml
content/browser/sync/quota.xul
content/browser/sync/quota.js
content/browser/sync/utils.js
content/browser/sync/progress.js
content/browser/sync/progress.xhtml

View file

@ -0,0 +1,54 @@
<?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 [
<!ENTITY % htmlDTD PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd">
%htmlDTD;
<!ENTITY % syncBrandDTD SYSTEM "chrome://browser/locale/syncBrand.dtd">
%syncBrandDTD;
<!ENTITY % syncKeyDTD SYSTEM "chrome://browser/locale/syncKey.dtd">
%syncKeyDTD;
<!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd" >
%globalDTD;
]>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>&syncKey.page.title;</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<meta name="robots" content="noindex"/>
<style type="text/css">
#synckey { font-size: 150% }
footer { font-size: 70% }
/* Bug 575675: Need to have an a:visited rule in a chrome document. */
a:visited { color: purple; }
</style>
</head>
<body dir="&locale.dir;">
<h1>&syncKey.page.title;</h1>
<p id="synckey" dir="ltr">SYNCKEY</p>
<p>&syncKey.page.description2;</p>
<div id="column1">
<h2>&syncKey.keepItSecret.heading;</h2>
<p>&syncKey.keepItSecret.description;</p>
</div>
<div id="column2">
<h2>&syncKey.keepItSafe.heading;</h2>
<p><em>&syncKey.keepItSafe1.description;</em>&syncKey.keepItSafe2.description;<em>&syncKey.keepItSafe3.description;</em>&syncKey.keepItSafe4a.description;</p>
</div>
<p>&syncKey.findOutMore1.label;<a href="http://www.palemoon.org/sync/">http://www.palemoon.org/sync/</a>&syncKey.findOutMore2.label;</p>
<footer>
&syncKey.footer1.label;<a id="tosLink" href="termsURL">termsURL</a>&syncKey.footer2.label;<a id="ppLink" href="privacyURL">privacyURL</a>&syncKey.footer3.label;
</footer>
</body>
</html>

View file

@ -1,7 +1,8 @@
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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
JAR_MANIFESTS += ['jar.mn']

View file

@ -0,0 +1,129 @@
<?xml version="1.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/. -->
<!DOCTYPE bindings [
<!ENTITY % notificationDTD SYSTEM "chrome://global/locale/notification.dtd">
%notificationDTD;
]>
<bindings id="notificationBindings"
xmlns="http://www.mozilla.org/xbl"
xmlns:xbl="http://www.mozilla.org/xbl"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<binding id="notificationbox" extends="chrome://global/content/bindings/notification.xml#notificationbox">
<content>
<xul:vbox xbl:inherits="hidden=notificationshidden">
<xul:spacer/>
<children includes="notification"/>
</xul:vbox>
<children/>
</content>
<implementation>
<constructor><![CDATA[
let temp = {};
Cu.import("resource://services-common/observers.js", temp);
temp.Observers.add("weave:notification:added", this.onNotificationAdded, this);
temp.Observers.add("weave:notification:removed", this.onNotificationRemoved, this);
for each (var notification in Weave.Notifications.notifications)
this._appendNotification(notification);
]]></constructor>
<destructor><![CDATA[
let temp = {};
Cu.import("resource://services-common/observers.js", temp);
temp.Observers.remove("weave:notification:added", this.onNotificationAdded, this);
temp.Observers.remove("weave:notification:removed", this.onNotificationRemoved, this);
]]></destructor>
<method name="onNotificationAdded">
<parameter name="subject"/>
<parameter name="data"/>
<body><![CDATA[
this._appendNotification(subject);
]]></body>
</method>
<method name="onNotificationRemoved">
<parameter name="subject"/>
<parameter name="data"/>
<body><![CDATA[
// If the view of the notification hasn't been removed yet, remove it.
var notifications = this.allNotifications;
for each (var notification in notifications) {
if (notification.notification == subject) {
notification.close();
break;
}
}
]]></body>
</method>
<method name="_appendNotification">
<parameter name="notification"/>
<body><![CDATA[
var node = this.appendNotification(notification.description,
notification.title,
notification.iconURL,
notification.priority,
notification.buttons);
node.notification = notification;
]]></body>
</method>
</implementation>
</binding>
<binding id="notification" extends="chrome://global/content/bindings/notification.xml#notification">
<content>
<xul:hbox class="notification-inner outset" flex="1" xbl:inherits="type">
<xul:toolbarbutton ondblclick="event.stopPropagation();"
class="messageCloseButton close-icon tabbable"
xbl:inherits="hidden=hideclose"
tooltiptext="&closeNotification.tooltip;"
oncommand="document.getBindingParent(this).close()"/>
<xul:hbox anonid="details" align="center" flex="1">
<xul:image anonid="messageImage" class="messageImage" xbl:inherits="src=image,type"/>
<xul:description anonid="messageText" class="messageText" xbl:inherits="xbl:text=label"/>
<!-- The children are the buttons defined by the notification. -->
<xul:hbox oncommand="document.getBindingParent(this)._doButtonCommand(event);">
<children/>
</xul:hbox>
</xul:hbox>
</xul:hbox>
</content>
<implementation>
<!-- Note: this used to be a field, but for some reason it kept getting
- reset to its default value for TabNotification elements.
- As a property, that doesn't happen, even though the property stores
- its value in a JS property |_notification| that is not defined
- in XBL as a field or property. Maybe this is wrong, but it works.
-->
<property name="notification"
onget="return this._notification"
onset="this._notification = val; return val;"/>
<method name="close">
<body><![CDATA[
Weave.Notifications.remove(this.notification);
// We should be able to call the base class's close method here
// to remove the notification element from the notification box,
// but we can't because of bug 373652, so instead we copied its code
// and execute it below.
var control = this.control;
if (control)
control.removeNotification(this);
else
this.hidden = true;
]]></body>
</method>
</implementation>
</binding>
</bindings>

View file

@ -0,0 +1,71 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://services-sync/main.js");
var gProgressBar;
var gCounter = 0;
function onLoad(event) {
Services.obs.addObserver(onEngineSync, "weave:engine:sync:finish", false);
Services.obs.addObserver(onEngineSync, "weave:engine:sync:error", false);
Services.obs.addObserver(onServiceSync, "weave:service:sync:finish", false);
Services.obs.addObserver(onServiceSync, "weave:service:sync:error", false);
gProgressBar = document.getElementById('uploadProgressBar');
if (Services.prefs.getPrefType("services.sync.firstSync") != Ci.nsIPrefBranch.PREF_INVALID) {
gProgressBar.hidden = false;
}
else {
gProgressBar.hidden = true;
}
}
function onUnload(event) {
cleanUpObservers();
}
function cleanUpObservers() {
try {
Services.obs.removeObserver(onEngineSync, "weave:engine:sync:finish");
Services.obs.removeObserver(onEngineSync, "weave:engine:sync:error");
Services.obs.removeObserver(onServiceSync, "weave:service:sync:finish");
Services.obs.removeObserver(onServiceSync, "weave:service:sync:error");
}
catch (e) {
// may be double called by unload & exit. Ignore.
}
}
function onEngineSync(subject, topic, data) {
// The Clients engine syncs first. At this point we don't necessarily know
// yet how many engines will be enabled, so we'll ignore the Clients engine
// and evaluate how many engines are enabled when the first "real" engine
// syncs.
if (data == "clients") {
return;
}
if (!gCounter &&
Services.prefs.getPrefType("services.sync.firstSync") != Ci.nsIPrefBranch.PREF_INVALID) {
gProgressBar.max = Weave.Service.engineManager.getEnabled().length;
}
gCounter += 1;
gProgressBar.setAttribute("value", gCounter);
}
function onServiceSync(subject, topic, data) {
// To address the case where 0 engines are synced, we will fill the
// progress bar so the user knows that the sync has finished.
gProgressBar.setAttribute("value", gProgressBar.max);
cleanUpObservers();
}
function closeTab() {
window.close();
}

View file

@ -0,0 +1,55 @@
<?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 [
<!ENTITY % htmlDTD
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"DTD/xhtml1-strict.dtd">
%htmlDTD;
<!ENTITY % syncProgressDTD
SYSTEM "chrome://browser/locale/syncProgress.dtd">
%syncProgressDTD;
<!ENTITY % syncSetupDTD
SYSTEM "chrome://browser/locale/syncSetup.dtd">
%syncSetupDTD;
<!ENTITY % globalDTD
SYSTEM "chrome://global/locale/global.dtd">
%globalDTD;
]>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>&syncProgress.pageTitle;</title>
<link rel="stylesheet" type="text/css" media="all"
href="chrome://browser/skin/syncProgress.css"/>
<link rel="icon" type="image/png" id="favicon"
href="chrome://browser/skin/sync-16.png"/>
<script type="text/javascript;version=1.8"
src="chrome://browser/content/sync/progress.js"/>
</head>
<body onload="onLoad(event)" onunload="onUnload(event)" dir="&locale.dir;">
<title>&setup.successPage.title;</title>
<div id="floatingBox" class="main-content">
<div id="title">
<h1>&setup.successPage.title;</h1>
</div>
<div id="successLogo">
<img id="brandSyncLogo" src="chrome://browser/skin/sync-128.png" alt="&syncProgress.logoAltText;" />
</div>
<div id="loadingText">
<p id="blurb">&syncProgress.textBlurb; </p>
</div>
<div id="progressBar">
<progress id="uploadProgressBar" value="0"/>
</div>
<div id="bottomRow">
<button id="closeButton" onclick="closeTab()">&syncProgress.closeButton; </button>
</div>
</div>
</body>
</html>

View file

@ -0,0 +1,247 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const Ci = Components.interfaces;
const Cc = Components.classes;
const Cr = Components.results;
const Cu = Components.utils;
Cu.import("resource://services-sync/main.js");
Cu.import("resource://gre/modules/DownloadUtils.jsm");
var gSyncQuota = {
init: function init() {
this.bundle = document.getElementById("quotaStrings");
let caption = document.getElementById("treeCaption");
caption.firstChild.nodeValue = this.bundle.getString("quota.treeCaption.label");
gUsageTreeView.init();
this.tree = document.getElementById("usageTree");
this.tree.view = gUsageTreeView;
this.loadData();
},
loadData: function loadData() {
this._usage_req = Weave.Service.getStorageInfo(Weave.INFO_COLLECTION_USAGE,
function (error, usage) {
delete gSyncQuota._usage_req;
// displayUsageData handles null values, so no need to check 'error'.
gUsageTreeView.displayUsageData(usage);
});
let usageLabel = document.getElementById("usageLabel");
let bundle = this.bundle;
this._quota_req = Weave.Service.getStorageInfo(Weave.INFO_QUOTA,
function (error, quota) {
delete gSyncQuota._quota_req;
if (error) {
usageLabel.value = bundle.getString("quota.usageError.label");
return;
}
let used = gSyncQuota.convertKB(quota[0]);
if (!quota[1]) {
// No quota on the server.
usageLabel.value = bundle.getFormattedString(
"quota.usageNoQuota.label", used);
return;
}
let percent = Math.round(100 * quota[0] / quota[1]);
let total = gSyncQuota.convertKB(quota[1]);
usageLabel.value = bundle.getFormattedString(
"quota.usagePercentage.label", [percent].concat(used).concat(total));
});
},
onCancel: function onCancel() {
if (this._usage_req) {
this._usage_req.abort();
}
if (this._quota_req) {
this._quota_req.abort();
}
return true;
},
onAccept: function onAccept() {
let engines = gUsageTreeView.getEnginesToDisable();
for each (let engine in engines) {
Weave.Service.engineManager.get(engine).enabled = false;
}
if (engines.length) {
// The 'Weave' object will disappear once the window closes.
let Service = Weave.Service;
Weave.Utils.nextTick(function() { Service.sync(); });
}
return this.onCancel();
},
convertKB: function convertKB(value) {
return DownloadUtils.convertByteUnits(value * 1024);
}
};
var gUsageTreeView = {
_ignored: {keys: true,
meta: true,
clients: true},
/*
* Internal data structures underlaying the tree.
*/
_collections: [],
_byname: {},
init: function init() {
let retrievingLabel = gSyncQuota.bundle.getString("quota.retrieving.label");
for each (let engine in Weave.Service.engineManager.getEnabled()) {
if (this._ignored[engine.name])
continue;
// Some engines use the same pref, which means they can only be turned on
// and off together. We need to combine them here as well.
let existing = this._byname[engine.prefName];
if (existing) {
existing.engines.push(engine.name);
continue;
}
let obj = {name: engine.prefName,
title: this._collectionTitle(engine),
engines: [engine.name],
enabled: true,
sizeLabel: retrievingLabel};
this._collections.push(obj);
this._byname[engine.prefName] = obj;
}
},
_collectionTitle: function _collectionTitle(engine) {
try {
return gSyncQuota.bundle.getString(
"collection." + engine.prefName + ".label");
} catch (ex) {
return engine.Name;
}
},
/*
* Process the quota information as returned by info/collection_usage.
*/
displayUsageData: function displayUsageData(data) {
for each (let coll in this._collections) {
coll.size = 0;
// If we couldn't retrieve any data, just blank out the label.
if (!data) {
coll.sizeLabel = "";
continue;
}
for each (let engineName in coll.engines)
coll.size += data[engineName] || 0;
let sizeLabel = "";
sizeLabel = gSyncQuota.bundle.getFormattedString(
"quota.sizeValueUnit.label", gSyncQuota.convertKB(coll.size));
coll.sizeLabel = sizeLabel;
}
let sizeColumn = this.treeBox.columns.getNamedColumn("size");
this.treeBox.invalidateColumn(sizeColumn);
},
/*
* Handle click events on the tree.
*/
onTreeClick: function onTreeClick(event) {
if (event.button == 2)
return;
let cell = this.treeBox.getCellAt(event.clientX, event.clientY);
if (cell.col && cell.col.id == "enabled")
this.toggle(cell.row);
},
/*
* Toggle enabled state of an engine.
*/
toggle: function toggle(row) {
// Update the tree
let collection = this._collections[row];
collection.enabled = !collection.enabled;
this.treeBox.invalidateRow(row);
},
/*
* Return a list of engines (or rather their pref names) that should be
* disabled.
*/
getEnginesToDisable: function getEnginesToDisable() {
// Tycho: return [coll.name for each (coll in this._collections) if (!coll.enabled)];
let engines = [];
for each (let coll in this._collections) {
if (!coll.enabled) {
engines.push(coll.name);
}
}
return engines;
},
// nsITreeView
get rowCount() {
return this._collections.length;
},
getRowProperties: function(index) { return ""; },
getCellProperties: function(row, col) { return ""; },
getColumnProperties: function(col) { return ""; },
isContainer: function(index) { return false; },
isContainerOpen: function(index) { return false; },
isContainerEmpty: function(index) { return false; },
isSeparator: function(index) { return false; },
isSorted: function() { return false; },
canDrop: function(index, orientation, dataTransfer) { return false; },
drop: function(row, orientation, dataTransfer) {},
getParentIndex: function(rowIndex) {},
hasNextSibling: function(rowIndex, afterIndex) { return false; },
getLevel: function(index) { return 0; },
getImageSrc: function(row, col) {},
getCellValue: function(row, col) {
return this._collections[row].enabled;
},
getCellText: function getCellText(row, col) {
let collection = this._collections[row];
switch (col.id) {
case "collection":
return collection.title;
case "size":
return collection.sizeLabel;
default:
return "";
}
},
setTree: function setTree(tree) {
this.treeBox = tree;
},
toggleOpenState: function(index) {},
cycleHeader: function(col) {},
selectionChanged: function() {},
cycleCell: function(row, col) {},
isEditable: function(row, col) { return false; },
isSelectable: function (row, col) { return false; },
setCellValue: function(row, col, value) {},
setCellText: function(row, col, value) {},
performAction: function(action) {},
performActionOnRow: function(action, row) {},
performActionOnCell: function(action, row, col) {}
};

View file

@ -0,0 +1,65 @@
<?xml version="1.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/. -->
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<?xml-stylesheet href="chrome://browser/skin/syncQuota.css"?>
<!DOCTYPE dialog [
<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
<!ENTITY % syncBrandDTD SYSTEM "chrome://browser/locale/syncBrand.dtd">
<!ENTITY % syncQuotaDTD SYSTEM "chrome://browser/locale/syncQuota.dtd">
%brandDTD;
%syncBrandDTD;
%syncQuotaDTD;
]>
<dialog id="quotaDialog"
windowtype="Sync:ViewQuota"
persist="screenX screenY width height"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:html="http://www.w3.org/1999/xhtml"
onload="gSyncQuota.init()"
buttons="accept,cancel"
title="&quota.dialogTitle.label;"
ondialogcancel="return gSyncQuota.onCancel();"
ondialogaccept="return gSyncQuota.onAccept();">
<script type="application/javascript"
src="chrome://browser/content/sync/quota.js"/>
<stringbundleset id="stringbundleset">
<stringbundle id="quotaStrings"
src="chrome://browser/locale/syncQuota.properties"/>
</stringbundleset>
<vbox flex="1">
<label id="usageLabel"
value="&quota.retrievingInfo.label;"/>
<separator/>
<tree id="usageTree"
seltype="single"
hidecolumnpicker="true"
onclick="gUsageTreeView.onTreeClick(event);"
flex="1">
<treecols>
<treecol id="enabled"
type="checkbox"
fixed="true"/>
<splitter class="tree-splitter"/>
<treecol id="collection"
label="&quota.typeColumn.label;"
flex="1"/>
<splitter class="tree-splitter"/>
<treecol id="size"
label="&quota.sizeColumn.label;"
flex="1"/>
</treecols>
<treechildren flex="1"/>
</tree>
<separator/>
<description id="treeCaption"> </description>
</vbox>
</dialog>

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,491 @@
<?xml version="1.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/. -->
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<?xml-stylesheet href="chrome://browser/skin/syncSetup.css" type="text/css"?>
<?xml-stylesheet href="chrome://browser/skin/syncCommon.css" type="text/css"?>
<!DOCTYPE window [
<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
<!ENTITY % syncBrandDTD SYSTEM "chrome://browser/locale/syncBrand.dtd">
<!ENTITY % syncSetupDTD SYSTEM "chrome://browser/locale/syncSetup.dtd">
%brandDTD;
%syncBrandDTD;
%syncSetupDTD;
]>
<wizard id="wizard"
title="&accountSetupTitle.label;"
windowtype="Weave:AccountSetup"
persist="screenX screenY"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:html="http://www.w3.org/1999/xhtml"
onwizardnext="return gSyncSetup.onWizardAdvance()"
onwizardback="return gSyncSetup.onWizardBack()"
onwizardcancel="gSyncSetup.onWizardCancel()"
onload="gSyncSetup.init()">
<script type="application/javascript"
src="chrome://browser/content/sync/setup.js"/>
<script type="application/javascript"
src="chrome://browser/content/sync/utils.js"/>
<script type="application/javascript"
src="chrome://browser/content/utilityOverlay.js"/>
<script type="application/javascript"
src="chrome://global/content/printUtils.js"/>
<wizardpage id="addDevicePage"
label="&pairDevice.title.label;"
onpageshow="gSyncSetup.onPageShow()">
<description>
&pairDevice.dialog.description.label;
<label class="text-link"
value="&addDevice.showMeHow.label;"
href="http://www.palemoon.org/sync/help/easy-setup.shtml"/>
</description>
<separator class="groove-thin"/>
<description>
&addDevice.dialog.enterCode.label;
</description>
<separator class="groove-thin"/>
<vbox align="center">
<textbox id="pin1"
class="pin"
oninput="gSyncSetup.onPINInput(this);"
onfocus="this.select();"
/>
<textbox id="pin2"
class="pin"
oninput="gSyncSetup.onPINInput(this);"
onfocus="this.select();"
/>
<textbox id="pin3"
class="pin"
oninput="gSyncSetup.onPINInput(this);"
onfocus="this.select();"
/>
</vbox>
<separator class="groove-thin"/>
<vbox id="pairDeviceThrobber" align="center" hidden="true">
<image/>
</vbox>
<hbox id="pairDeviceErrorRow" pack="center" hidden="true">
<image class="statusIcon" status="error"/>
<label class="status"
value="&addDevice.dialog.tryAgain.label;"/>
</hbox>
</wizardpage>
<wizardpage id="pickSetupType"
label="&syncBrand.fullName.label;"
onpageshow="gSyncSetup.onPageShow()">
<vbox align="center" flex="1">
<description style="padding: 0 7em;">
&setup.pickSetupType.description2;
</description>
<spacer flex="3"/>
<button id="newAccount"
class="accountChoiceButton"
label="&button.createNewAccount.label;"
oncommand="gSyncSetup.startNewAccountSetup()"
align="center"/>
<spacer flex="1"/>
</vbox>
<separator class="groove"/>
<vbox align="center" flex="1">
<spacer flex="1"/>
<button id="existingAccount"
class="accountChoiceButton"
label="&button.haveAccount.label;"
oncommand="gSyncSetup.useExistingAccount()"/>
<spacer flex="3"/>
</vbox>
</wizardpage>
<wizardpage label="&setup.newAccountDetailsPage.title.label;"
id="newAccountStart"
onextra1="gSyncSetup.onSyncOptions()"
onpageshow="gSyncSetup.onPageShow();">
<grid>
<columns>
<column/>
<column class="inputColumn" flex="1"/>
</columns>
<rows>
<row id="emailRow" align="center">
<label value="&setup.emailAddress.label;"
accesskey="&setup.emailAddress.accesskey;"
control="weaveEmail"/>
<textbox id="weaveEmail"
oninput="gSyncSetup.onEmailInput()"/>
</row>
<row id="emailFeedbackRow" align="center" hidden="true">
<spacer/>
<hbox>
<image class="statusIcon"/>
<label class="status" value=" "/>
</hbox>
</row>
<row id="passwordRow" align="center">
<label value="&setup.choosePassword.label;"
accesskey="&setup.choosePassword.accesskey;"
control="weavePassword"/>
<textbox id="weavePassword"
type="password"
onchange="gSyncSetup.onPasswordChange()"/>
</row>
<row id="confirmRow" align="center">
<label value="&setup.confirmPassword.label;"
accesskey="&setup.confirmPassword.accesskey;"
control="weavePasswordConfirm"/>
<textbox id="weavePasswordConfirm"
type="password"
onchange="gSyncSetup.onPasswordChange()"/>
</row>
<row id="passwordFeedbackRow" align="center" hidden="true">
<spacer/>
<hbox>
<image class="statusIcon"/>
<label class="status" value=" "/>
</hbox>
</row>
<row align="center">
<label control="server"
value="&server.label;"/>
<menulist id="server"
oncommand="gSyncSetup.onServerCommand()"
oninput="gSyncSetup.onServerInput()">
<menupopup>
<menuitem label="&serverType.default.label;"
value="main"/>
<menuitem label="&serverType.custom2.label;"
value="custom"/>
</menupopup>
</menulist>
</row>
<row id="serverFeedbackRow" align="center" hidden="true">
<spacer/>
<hbox>
<image class="statusIcon"/>
<label class="status" value=" "/>
</hbox>
</row>
<row id="TOSRow" align="center">
<spacer/>
<hbox align="center">
<checkbox id="tos"
accesskey="&setup.tosAgree1.accesskey;"
oncommand="this.focus(); gSyncSetup.checkFields();"/>
<description id="tosDesc"
flex="1"
onclick="document.getElementById('tos').focus();
document.getElementById('tos').click()">
&setup.tosAgree1.label;
<label class="text-link inline-link"
onclick="event.stopPropagation();gSyncUtils.openToS();">
&setup.tosLink.label;
</label>
&setup.tosAgree2.label;
<label class="text-link inline-link"
onclick="event.stopPropagation();gSyncUtils.openPrivacyPolicy();">
&setup.ppLink.label;
</label>
&setup.tosAgree3.label;
</description>
</hbox>
</row>
</rows>
</grid>
<spacer flex="1"/>
<vbox flex="1" align="center">
<browser height="150"
width="500"
id="captcha"
type="content"
disablehistory="true"/>
<spacer flex="1"/>
<hbox id="captchaFeedback">
<image class="statusIcon"/>
<label class="status" value=" "/>
</hbox>
</vbox>
</wizardpage>
<wizardpage id="addDevice"
label="&pairDevice.title.label;"
onextra1="gSyncSetup.onSyncOptions()"
onpageshow="gSyncSetup.onPageShow()">
<description>
&pairDevice.setup.description.label;
<label class="text-link"
value="&addDevice.showMeHow.label;"
href="http://www.palemoon.org/sync/help/easy-setup.shtml"/>
</description>
<label value="&addDevice.setup.enterCode.label;"
control="easySetupPIN1"/>
<spacer flex="1"/>
<vbox align="center" flex="1">
<textbox id="easySetupPIN1"
class="pin"
value=""
readonly="true"
/>
<textbox id="easySetupPIN2"
class="pin"
value=""
readonly="true"
/>
<textbox id="easySetupPIN3"
class="pin"
value=""
readonly="true"
/>
</vbox>
<spacer flex="3"/>
<label class="text-link"
value="&addDevice.dontHaveDevice.label;"
onclick="gSyncSetup.manualSetup();"/>
</wizardpage>
<wizardpage id="existingAccount"
label="&setup.signInPage.title.label;"
onextra1="gSyncSetup.onSyncOptions()"
onpageshow="gSyncSetup.onPageShow()">
<grid>
<columns>
<column/>
<column class="inputColumn" flex="1"/>
</columns>
<rows>
<row id="existingAccountRow" align="center">
<label id="existingAccountLabel"
value="&signIn.account2.label;"
accesskey="&signIn.account2.accesskey;"
control="existingAccount"/>
<textbox id="existingAccountName"
oninput="gSyncSetup.checkFields(event)"
onchange="gSyncSetup.checkFields(event)"/>
</row>
<row id="existingPasswordRow" align="center">
<label id="existingPasswordLabel"
value="&signIn.password.label;"
accesskey="&signIn.password.accesskey;"
control="existingPassword"/>
<textbox id="existingPassword"
type="password"
onkeyup="gSyncSetup.checkFields(event)"
onchange="gSyncSetup.checkFields(event)"/>
</row>
<row id="existingPasswordFeedbackRow" align="center" hidden="true">
<spacer/>
<hbox>
<image class="statusIcon"/>
<label class="status" value=" "/>
</hbox>
</row>
<row align="center">
<spacer/>
<label class="text-link"
value="&resetPassword.label;"
onclick="gSyncUtils.resetPassword(); return false;"/>
</row>
<row align="center">
<label control="existingServer"
value="&server.label;"/>
<menulist id="existingServer"
oncommand="gSyncSetup.onExistingServerCommand()"
oninput="gSyncSetup.onExistingServerInput()">
<menupopup>
<menuitem label="&serverType.default.label;"
value="main"/>
<menuitem label="&serverType.custom2.label;"
value="custom"/>
</menupopup>
</menulist>
</row>
<row id="existingServerFeedbackRow" align="center" hidden="true">
<spacer/>
<hbox>
<image class="statusIcon"/>
<vbox>
<label class="status" value=" "/>
</vbox>
</hbox>
</row>
</rows>
</grid>
<groupbox>
<label id="existingPassphraseLabel"
value="&signIn.recoveryKey.label;"
accesskey="&signIn.recoveryKey.accesskey;"
control="existingPassphrase"/>
<textbox id="existingPassphrase"
oninput="gSyncSetup.checkFields()"/>
<hbox id="login-throbber" hidden="true">
<image/>
<label value="&verifying.label;"/>
</hbox>
<vbox align="left" id="existingPassphraseFeedbackRow" hidden="true">
<hbox>
<image class="statusIcon"/>
<label class="status" value=" "/>
</hbox>
</vbox>
</groupbox>
<vbox id="passphraseHelpBox">
<description>
&existingRecoveryKey.description;
<label class="text-link"
href="http://www.palemoon.org/sync/help/recoverykey.shtml">
&addDevice.showMeHow.label;
</label>
<spacer id="passphraseHelpSpacer"/>
<label class="text-link"
onclick="gSyncSetup.resetPassphrase(); return false;">
&resetSyncKey.label;
</label>
</description>
</vbox>
</wizardpage>
<wizardpage id="syncOptionsPage"
label="&setup.optionsPage.title;"
onpageshow="gSyncSetup.onPageShow()">
<groupbox id="syncOptions">
<grid>
<columns>
<column/>
<column flex="1" style="-moz-margin-end: 2px"/>
</columns>
<rows>
<row align="center">
<label value="&syncDeviceName.label;"
accesskey="&syncDeviceName.accesskey;"
control="syncComputerName"/>
<textbox id="syncComputerName" flex="1"
onchange="gSyncUtils.changeName(this)"/>
</row>
<row>
<label value="&syncMy.label;" />
<vbox>
<checkbox label="&engine.addons.label;"
accesskey="&engine.addons.accesskey;"
id="engine.addons"
checked="false"
hidden="true"/>
<checkbox label="&engine.bookmarks.label;"
accesskey="&engine.bookmarks.accesskey;"
id="engine.bookmarks"
checked="true"/>
<checkbox label="&engine.passwords.label;"
accesskey="&engine.passwords.accesskey;"
id="engine.passwords"
checked="true"/>
<checkbox label="&engine.prefs.label;"
accesskey="&engine.prefs.accesskey;"
id="engine.prefs"
checked="true"/>
<checkbox label="&engine.history.label;"
accesskey="&engine.history.accesskey;"
id="engine.history"
checked="true"/>
<checkbox label="&engine.tabs.label;"
accesskey="&engine.tabs.accesskey;"
id="engine.tabs"
checked="true"/>
</vbox>
</row>
</rows>
</grid>
</groupbox>
<groupbox id="mergeOptions">
<radiogroup id="mergeChoiceRadio" pack="start">
<grid>
<columns>
<column/>
<column flex="1"/>
</columns>
<rows flex="1">
<row align="center">
<radio id="resetClient"
class="mergeChoiceButton"
aria-labelledby="resetClientLabel"/>
<label id="resetClientLabel" control="resetClient">
<html:strong>&choice2.merge.recommended.label;</html:strong>
&choice2a.merge.main.label;
</label>
</row>
<row align="center">
<radio id="wipeClient"
class="mergeChoiceButton"
aria-labelledby="wipeClientLabel"/>
<label id="wipeClientLabel"
control="wipeClient">
&choice2a.client.main.label;
</label>
</row>
<row align="center">
<radio id="wipeRemote"
class="mergeChoiceButton"
aria-labelledby="wipeRemoteLabel"/>
<label id="wipeRemoteLabel"
control="wipeRemote">
&choice2a.server.main.label;
</label>
</row>
</rows>
</grid>
</radiogroup>
</groupbox>
</wizardpage>
<wizardpage id="syncOptionsConfirm"
label="&setup.optionsConfirmPage.title;"
onpageshow="gSyncSetup.onPageShow()">
<deck id="chosenActionDeck">
<vbox id="chosenActionMerge" class="confirm">
<description class="normal">
&confirm.merge2.label;
</description>
</vbox>
<vbox id="chosenActionWipeClient" class="confirm">
<description class="normal">
&confirm.client3.label;
</description>
<separator class="thin"/>
<vbox id="dataList">
<label class="data indent" id="bookmarkCount"/>
<label class="data indent" id="historyCount"/>
<label class="data indent" id="passwordCount"/>
<label class="data indent" id="addonCount"/>
<label class="data indent" id="prefsWipe"
value="&engine.prefs.label;"/>
</vbox>
<separator class="thin"/>
<description class="normal">
&confirm.client2.moreinfo.label;
</description>
</vbox>
<vbox id="chosenActionWipeServer" class="confirm">
<description class="normal">
&confirm.server2.label;
</description>
<separator class="thin"/>
<vbox id="clientList">
</vbox>
</vbox>
</deck>
</wizardpage>
<!-- In terms of the wizard flow shown to the user, the 'syncOptionsConfirm'
page above is not the last wizard page. To prevent the wizard binding from
assuming that it is, we're inserting this dummy page here. This also means
that the wizard needs to always be closed manually via wizardFinish(). -->
<wizardpage>
</wizardpage>
</wizard>

View file

@ -0,0 +1,218 @@
/* 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/. */
// Equivalent to 0o600 permissions; used for saved Sync Recovery Key.
// This constant can be replaced when the equivalent values are available to
// chrome JS; see Bug 433295 and Bug 757351.
const PERMISSIONS_RWUSR = 0x180;
// Weave should always exist before before this file gets included.
var gSyncUtils = {
get bundle() {
delete this.bundle;
return this.bundle = Services.strings.createBundle("chrome://browser/locale/syncSetup.properties");
},
// opens in a new window if we're in a modal prefwindow world, in a new tab otherwise
_openLink: function (url) {
let thisDocEl = document.documentElement,
openerDocEl = window.opener && window.opener.document.documentElement;
if (thisDocEl.id == "accountSetup" && window.opener &&
openerDocEl.id == "BrowserPreferences" && !openerDocEl.instantApply)
openUILinkIn(url, "window");
else if (thisDocEl.id == "BrowserPreferences" && !thisDocEl.instantApply)
openUILinkIn(url, "window");
else if (document.documentElement.id == "change-dialog")
Services.wm.getMostRecentWindow("navigator:browser")
.openUILinkIn(url, "tab");
else
openUILinkIn(url, "tab");
},
changeName: function changeName(input) {
// Make sure to update to a modified name, e.g., empty-string -> default
Weave.Service.clientsEngine.localName = input.value;
input.value = Weave.Service.clientsEngine.localName;
},
openChange: function openChange(type, duringSetup) {
// Just re-show the dialog if it's already open
let openedDialog = Services.wm.getMostRecentWindow("Sync:" + type);
if (openedDialog != null) {
openedDialog.focus();
return;
}
// Open up the change dialog
let changeXUL = "chrome://browser/content/sync/genericChange.xul";
let changeOpt = "centerscreen,chrome,resizable=no";
Services.ww.activeWindow.openDialog(changeXUL, "", changeOpt,
type, duringSetup);
},
changePassword: function () {
if (Weave.Utils.ensureMPUnlocked())
this.openChange("ChangePassword");
},
resetPassphrase: function (duringSetup) {
if (Weave.Utils.ensureMPUnlocked())
this.openChange("ResetPassphrase", duringSetup);
},
updatePassphrase: function () {
if (Weave.Utils.ensureMPUnlocked())
this.openChange("UpdatePassphrase");
},
resetPassword: function () {
this._openLink(Weave.Service.pwResetURL);
},
openToS: function () {
this._openLink(Weave.Svc.Prefs.get("termsURL"));
},
openPrivacyPolicy: function () {
this._openLink(Weave.Svc.Prefs.get("privacyURL"));
},
openFirstSyncProgressPage: function () {
this._openLink("about:sync-progress");
},
/**
* Prepare an invisible iframe with the passphrase backup document.
* Used by both the print and saving methods.
*
* @param elid : ID of the form element containing the passphrase.
* @param callback : Function called once the iframe has loaded.
*/
_preparePPiframe: function(elid, callback) {
let pp = document.getElementById(elid).value;
// Create an invisible iframe whose contents we can print.
let iframe = document.createElement("iframe");
iframe.setAttribute("src", "chrome://browser/content/sync/key.xhtml");
iframe.collapsed = true;
document.documentElement.appendChild(iframe);
iframe.contentWindow.addEventListener("load", function() {
iframe.contentWindow.removeEventListener("load", arguments.callee, false);
// Insert the Sync Key into the page.
let el = iframe.contentDocument.getElementById("synckey");
el.firstChild.nodeValue = pp;
// Insert the TOS and Privacy Policy URLs into the page.
let termsURL = Weave.Svc.Prefs.get("termsURL");
el = iframe.contentDocument.getElementById("tosLink");
el.setAttribute("href", termsURL);
el.firstChild.nodeValue = termsURL;
let privacyURL = Weave.Svc.Prefs.get("privacyURL");
el = iframe.contentDocument.getElementById("ppLink");
el.setAttribute("href", privacyURL);
el.firstChild.nodeValue = privacyURL;
callback(iframe);
}, false);
},
/**
* Print passphrase backup document.
*
* @param elid : ID of the form element containing the passphrase.
*/
passphrasePrint: function(elid) {
this._preparePPiframe(elid, function(iframe) {
let webBrowserPrint = iframe.contentWindow
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebBrowserPrint);
let printSettings = PrintUtils.getPrintSettings();
// Display no header/footer decoration except for the date.
printSettings.headerStrLeft
= printSettings.headerStrCenter
= printSettings.headerStrRight
= printSettings.footerStrLeft
= printSettings.footerStrCenter = "";
printSettings.footerStrRight = "&D";
try {
webBrowserPrint.print(printSettings, null);
} catch (ex) {
// print()'s return codes are expressed as exceptions. Ignore.
}
});
},
/**
* Save passphrase backup document to disk as HTML file.
*
* @param elid : ID of the form element containing the passphrase.
*/
passphraseSave: function(elid) {
let dialogTitle = this.bundle.GetStringFromName("save.recoverykey.title");
let defaultSaveName = this.bundle.GetStringFromName("save.recoverykey.defaultfilename");
this._preparePPiframe(elid, function(iframe) {
let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
let fpCallback = function fpCallback_done(aResult) {
if (aResult == Ci.nsIFilePicker.returnOK ||
aResult == Ci.nsIFilePicker.returnReplace) {
let stream = Cc["@mozilla.org/network/file-output-stream;1"].
createInstance(Ci.nsIFileOutputStream);
stream.init(fp.file, -1, PERMISSIONS_RWUSR, 0);
let serializer = new XMLSerializer();
let output = serializer.serializeToString(iframe.contentDocument);
output = output.replace(/<!DOCTYPE (.|\n)*?]>/,
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" ' +
'"DTD/xhtml1-strict.dtd">');
output = Weave.Utils.encodeUTF8(output);
stream.write(output, output.length);
}
};
fp.init(window, dialogTitle, Ci.nsIFilePicker.modeSave);
fp.appendFilters(Ci.nsIFilePicker.filterHTML);
fp.defaultString = defaultSaveName;
fp.open(fpCallback);
return false;
});
},
/**
* validatePassword
*
* @param el1 : the first textbox element in the form
* @param el2 : the second textbox element, if omitted it's an update form
*
* returns [valid, errorString]
*/
validatePassword: function (el1, el2) {
let valid = false;
let val1 = el1.value;
let val2 = el2 ? el2.value : "";
let error = "";
if (!el2)
valid = val1.length >= Weave.MIN_PASS_LENGTH;
else if (val1 && val1 == Weave.Service.identity.username)
error = "change.password.pwSameAsUsername";
else if (val1 && val1 == Weave.Service.identity.account)
error = "change.password.pwSameAsEmail";
else if (val1 && val1 == Weave.Service.identity.basicPassword)
error = "change.password.pwSameAsPassword";
else if (val1 && val2) {
if (val1 == val2 && val1.length >= Weave.MIN_PASS_LENGTH)
valid = true;
else if (val1.length < Weave.MIN_PASS_LENGTH)
error = "change.password.tooShort";
else if (val1 != val2)
error = "change.password.mismatch";
}
let errorString = error ? Weave.Utils.getErrorString(error) : "";
return [valid, errorString];
}
};

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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',
]

View file

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

View file

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

View file

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