Add Pale Moon

This commit is contained in:
NTD 2018-02-02 03:35:06 -05:00 committed by Roy Tam
commit fe8028fa2e
1173 changed files with 143053 additions and 934 deletions

View file

@ -0,0 +1,46 @@
# nsBrowserContentHandler.js
component {5d0ce354-df01-421a-83fb-7ead0990c24e} nsBrowserContentHandler.js application={8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}
contract @mozilla.org/browser/clh;1 {5d0ce354-df01-421a-83fb-7ead0990c24e} application={8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}
component {47cd0651-b1be-4a0f-b5c4-10e5a573ef71} nsBrowserContentHandler.js application={8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}
contract @mozilla.org/browser/final-clh;1 {47cd0651-b1be-4a0f-b5c4-10e5a573ef71} application={8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}
contract @mozilla.org/uriloader/content-handler;1?type=text/html {5d0ce354-df01-421a-83fb-7ead0990c24e} application={8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}
contract @mozilla.org/uriloader/content-handler;1?type=application/vnd.mozilla.xul+xml {5d0ce354-df01-421a-83fb-7ead0990c24e} application={8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}
contract @mozilla.org/uriloader/content-handler;1?type=image/svg+xml {5d0ce354-df01-421a-83fb-7ead0990c24e} application={8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}
contract @mozilla.org/uriloader/content-handler;1?type=text/rdf {5d0ce354-df01-421a-83fb-7ead0990c24e} application={8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}
contract @mozilla.org/uriloader/content-handler;1?type=text/xml {5d0ce354-df01-421a-83fb-7ead0990c24e} application={8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}
contract @mozilla.org/uriloader/content-handler;1?type=application/xhtml+xml {5d0ce354-df01-421a-83fb-7ead0990c24e} application={8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}
contract @mozilla.org/uriloader/content-handler;1?type=text/css {5d0ce354-df01-421a-83fb-7ead0990c24e} application={8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}
contract @mozilla.org/uriloader/content-handler;1?type=text/plain {5d0ce354-df01-421a-83fb-7ead0990c24e} application={8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}
contract @mozilla.org/uriloader/content-handler;1?type=image/gif {5d0ce354-df01-421a-83fb-7ead0990c24e} application={8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}
contract @mozilla.org/uriloader/content-handler;1?type=image/jpeg {5d0ce354-df01-421a-83fb-7ead0990c24e} application={8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}
contract @mozilla.org/uriloader/content-handler;1?type=image/jpg {5d0ce354-df01-421a-83fb-7ead0990c24e} application={8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}
contract @mozilla.org/uriloader/content-handler;1?type=image/png {5d0ce354-df01-421a-83fb-7ead0990c24e} application={8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}
contract @mozilla.org/uriloader/content-handler;1?type=image/bmp {5d0ce354-df01-421a-83fb-7ead0990c24e} application={8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}
contract @mozilla.org/uriloader/content-handler;1?type=image/x-icon {5d0ce354-df01-421a-83fb-7ead0990c24e} application={8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}
contract @mozilla.org/uriloader/content-handler;1?type=image/vnd.microsoft.icon {5d0ce354-df01-421a-83fb-7ead0990c24e} application={8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}
contract @mozilla.org/uriloader/content-handler;1?type=image/webp {5d0ce354-df01-421a-83fb-7ead0990c24e} application={8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}
contract @mozilla.org/uriloader/content-handler;1?type=application/http-index-format {5d0ce354-df01-421a-83fb-7ead0990c24e} application={8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}
category command-line-handler m-browser @mozilla.org/browser/clh;1 application={8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}
category command-line-handler x-default @mozilla.org/browser/final-clh;1 application={8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}
category command-line-validator b-browser @mozilla.org/browser/clh;1 application={8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}
# nsBrowserGlue.js
# WebappRT doesn't need these instructions, and they don't necessarily work
# with it, but it does use a GRE directory that the GRE shares with Firefox,
# so in order to prevent the instructions from being processed for WebappRT,
# we need to restrict them to the applications that depend on them, i.e.:
#
# b2g: {3c2e2abc-06d4-11e1-ac3b-374f68613e61}
# browser: {8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}
# mobile/android: {aa3c5121-dab2-40e2-81ca-7ea25febc110}
# mobile/xul: {a23983c0-fd0e-11dc-95ff-0800200c9a66}
#
# In theory we should do this for all these instructions, but in practice it is
# sufficient to do it for the app-startup one, and the file is simpler that way.
component {eab9012e-5f74-4cbc-b2b5-a590235513cc} nsBrowserGlue.js
contract @mozilla.org/browser/browserglue;1 {eab9012e-5f74-4cbc-b2b5-a590235513cc}
category app-startup nsBrowserGlue service,@mozilla.org/browser/browserglue;1 application={3c2e2abc-06d4-11e1-ac3b-374f68613e61} application={8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4} application={aa3c5121-dab2-40e2-81ca-7ea25febc110} application={a23983c0-fd0e-11dc-95ff-0800200c9a66}
component {d8903bf6-68d5-4e97-bcd1-e4d3012f721a} nsBrowserGlue.js
contract @mozilla.org/content-permission/prompt;1 {d8903bf6-68d5-4e97-bcd1-e4d3012f721a}

View file

@ -0,0 +1,188 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// See also: docshell/base/nsAboutRedirector.cpp
#include "AboutRedirector.h"
#include "nsNetUtil.h"
#include "nsIScriptSecurityManager.h"
#include "mozilla/ArrayUtils.h"
#include "nsDOMString.h"
namespace mozilla {
namespace browser {
NS_IMPL_ISUPPORTS(AboutRedirector, nsIAboutModule)
struct RedirEntry {
const char* id;
const char* url;
uint32_t flags;
const char* idbOriginPostfix;
};
/*
Entries which do not have URI_SAFE_FOR_UNTRUSTED_CONTENT will run with chrome
privileges. This is potentially dangerous. Please use
URI_SAFE_FOR_UNTRUSTED_CONTENT in the third argument to each map item below
unless your about: page really needs chrome privileges. Security review is
required before adding new map entries without
URI_SAFE_FOR_UNTRUSTED_CONTENT.
*/
static RedirEntry kRedirMap[] = {
{ "certerror", "chrome://browser/content/certerror/aboutCertError.xhtml",
nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
nsIAboutModule::ALLOW_SCRIPT |
nsIAboutModule::HIDE_FROM_ABOUTABOUT },
{ "feeds", "chrome://browser/content/feeds/subscribe.xhtml",
nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
nsIAboutModule::ALLOW_SCRIPT |
nsIAboutModule::HIDE_FROM_ABOUTABOUT },
{ "privatebrowsing", "chrome://browser/content/aboutPrivateBrowsing.xhtml",
nsIAboutModule::ALLOW_SCRIPT },
{ "rights",
#ifdef MOZ_OFFICIAL_BRANDING
"chrome://global/content/aboutRights.xhtml",
#else
"chrome://global/content/aboutRights-unbranded.xhtml",
#endif
nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
nsIAboutModule::MAKE_LINKABLE |
nsIAboutModule::ALLOW_SCRIPT },
{ "palemoon", "chrome://global/content/palemoon.xhtml",
nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
nsIAboutModule::HIDE_FROM_ABOUTABOUT },
{ "logopage", "chrome://global/content/logopage.xhtml",
nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
nsIAboutModule::HIDE_FROM_ABOUTABOUT },
{ "robots", "chrome://browser/content/aboutRobots.xhtml",
nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
nsIAboutModule::ALLOW_SCRIPT },
{ "sessionrestore", "chrome://browser/content/aboutSessionRestore.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::MAKE_LINKABLE |
nsIAboutModule::ALLOW_SCRIPT },
{ "newtab", "chrome://browser/content/newtab/newTab.xul",
nsIAboutModule::ALLOW_SCRIPT },
{ "permissions", "chrome://browser/content/preferences/aboutPermissions.xul",
nsIAboutModule::ALLOW_SCRIPT },
{ "downloads", "chrome://browser/content/downloads/contentAreaDownloadsView.xul",
nsIAboutModule::ALLOW_SCRIPT },
};
static const int kRedirTotal = ArrayLength(kRedirMap);
static nsAutoCString
GetAboutModuleName(nsIURI *aURI)
{
nsAutoCString path;
aURI->GetPath(path);
int32_t f = path.FindChar('#');
if (f >= 0)
path.SetLength(f);
f = path.FindChar('?');
if (f >= 0)
path.SetLength(f);
ToLowerCase(path);
return path;
}
NS_IMETHODIMP
AboutRedirector::NewChannel(nsIURI* aURI,
nsILoadInfo* aLoadInfo,
nsIChannel** result)
{
NS_ENSURE_ARG_POINTER(aURI);
NS_ASSERTION(result, "must not be null");
nsAutoCString path = GetAboutModuleName(aURI);
nsresult rv;
nsCOMPtr<nsIIOService> ioService = do_GetIOService(&rv);
NS_ENSURE_SUCCESS(rv, rv);
for (int i = 0; i < kRedirTotal; i++) {
if (!strcmp(path.get(), kRedirMap[i].id)) {
nsCOMPtr<nsIChannel> tempChannel;
rv = ioService->NewChannel(nsDependentCString(kRedirMap[i].url),
nullptr, nullptr, getter_AddRefs(tempChannel));
NS_ENSURE_SUCCESS(rv, rv);
tempChannel->SetOriginalURI(aURI);
NS_ADDREF(*result = tempChannel);
return rv;
}
}
return NS_ERROR_ILLEGAL_VALUE;
}
NS_IMETHODIMP
AboutRedirector::GetURIFlags(nsIURI *aURI, uint32_t *result)
{
NS_ENSURE_ARG_POINTER(aURI);
nsAutoCString name = GetAboutModuleName(aURI);
for (int i = 0; i < kRedirTotal; i++) {
if (name.Equals(kRedirMap[i].id)) {
*result = kRedirMap[i].flags;
return NS_OK;
}
}
return NS_ERROR_ILLEGAL_VALUE;
}
NS_IMETHODIMP
AboutRedirector::GetIndexedDBOriginPostfix(nsIURI *aURI, nsAString &result)
{
NS_ENSURE_ARG_POINTER(aURI);
nsAutoCString name = GetAboutModuleName(aURI);
for (int i = 0; i < kRedirTotal; i++) {
if (name.Equals(kRedirMap[i].id)) {
const char* postfix = kRedirMap[i].idbOriginPostfix;
if (!postfix) {
break;
}
result.AssignASCII(postfix);
return NS_OK;
}
}
SetDOMStringToNull(result);
return NS_ERROR_ILLEGAL_VALUE;
}
nsresult
AboutRedirector::Create(nsISupports *aOuter, REFNSIID aIID, void **result)
{
AboutRedirector* about = new AboutRedirector();
if (about == nullptr)
return NS_ERROR_OUT_OF_MEMORY;
NS_ADDREF(about);
nsresult rv = about->QueryInterface(aIID, result);
NS_RELEASE(about);
return rv;
}
} // namespace browser
} // namespace mozilla

View file

@ -0,0 +1,32 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef AboutRedirector_h__
#define AboutRedirector_h__
#include "nsIAboutModule.h"
namespace mozilla {
namespace browser {
class AboutRedirector : public nsIAboutModule
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIABOUTMODULE
AboutRedirector() {}
static nsresult
Create(nsISupports *aOuter, REFNSIID aIID, void **aResult);
protected:
virtual ~AboutRedirector() {}
};
} // namespace browser
} // namespace mozilla
#endif // AboutRedirector_h__

View file

@ -0,0 +1,19 @@
# -*- 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/.
EXPORTS.mozilla.browser += [
'AboutRedirector.h',
]
SOURCES += [
'AboutRedirector.cpp',
]
FINAL_LIBRARY = 'browsercomps'
LOCAL_INCLUDES += [
'../build',
]

View file

@ -0,0 +1,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/.
include $(topsrcdir)/config/rules.mk
# Ensure that we don't embed a manifest referencing the CRT.
EMBED_MANIFEST_AT =

View file

@ -0,0 +1,36 @@
# -*- 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/.
EXPORTS += [
'nsBrowserCompsCID.h',
]
SOURCES += [
'nsModule.cpp',
]
XPCOMBinaryComponent('browsercomps')
LOCAL_INCLUDES += [
'../about',
'../dirprovider',
'../feeds',
'../migration',
'../shell',
]
if CONFIG['OS_ARCH'] == 'WINNT':
OS_LIBS += [
'ole32',
'shell32',
'shlwapi',
'version',
]
# Mac: Need to link with CoreFoundation for Mac Migrators (PList reading code)
# GTK2: Need to link with glib for GNOME shell service
if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('cocoa', 'gtk2', 'gtk3'):
OS_LIBS += CONFIG['TK_LIBS']

View file

@ -0,0 +1,43 @@
/* 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/. */
/////////////////////////////////////////////////////////////////////////////
#ifdef XP_WIN
#define NS_WINIEHISTORYENUMERATOR_CID \
{ 0x93480624, 0x806e, 0x4756, { 0xb7, 0xcb, 0x0f, 0xb7, 0xdd, 0x74, 0x6a, 0x8f } }
#define NS_IEHISTORYENUMERATOR_CONTRACTID \
"@mozilla.org/profile/migrator/iehistoryenumerator;1"
#endif
#define NS_SHELLSERVICE_CID \
{ 0x63c7b9f4, 0xcc8, 0x43f8, { 0xb6, 0x66, 0xa, 0x66, 0x16, 0x55, 0xcb, 0x73 } }
#define NS_SHELLSERVICE_CONTRACTID \
"@mozilla.org/browser/shell-service;1"
#define NS_RDF_FORWARDPROXY_INFER_DATASOURCE_CID \
{ 0x7a024bcf, 0xedd5, 0x4d9a, { 0x86, 0x14, 0xd4, 0x4b, 0xe1, 0xda, 0xda, 0xd3 } }
#define NS_FEEDSNIFFER_CID \
{ 0x6893e69, 0x71d8, 0x4b23, { 0x81, 0xeb, 0x80, 0x31, 0x4d, 0xaf, 0x3e, 0x66 } }
#define NS_FEEDSNIFFER_CONTRACTID \
"@mozilla.org/browser/feeds/sniffer;1"
#define NS_ABOUTFEEDS_CID \
{ 0x12ff56ec, 0x58be, 0x402c, { 0xb0, 0x57, 0x1, 0xf9, 0x61, 0xde, 0x96, 0x9b } }
// 136e2c4d-c5a4-477c-b131-d93d7d704f64
#define NS_PRIVATE_BROWSING_SERVICE_WRAPPER_CID \
{ 0x136e2c4d, 0xc5a4, 0x477c, { 0xb1, 0x31, 0xd9, 0x3d, 0x7d, 0x70, 0x4f, 0x64 } }
// 7e4bb6ad-2fc4-4dc6-89ef-23e8e5ccf980
#define NS_BROWSER_ABOUT_REDIRECTOR_CID \
{ 0x7e4bb6ad, 0x2fc4, 0x4dc6, { 0x89, 0xef, 0x23, 0xe8, 0xe5, 0xcc, 0xf9, 0x80 } }
// {6DEB193C-F87D-4078-BC78-5E64655B4D62}
#define NS_BROWSERDIRECTORYPROVIDER_CID \
{ 0x6deb193c, 0xf87d, 0x4078, { 0xbc, 0x78, 0x5e, 0x64, 0x65, 0x5b, 0x4d, 0x62 } }

View file

@ -0,0 +1,126 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/ModuleUtils.h"
#include "nsBrowserCompsCID.h"
#include "DirectoryProvider.h"
#if defined(XP_WIN)
#include "nsWindowsShellService.h"
#elif defined(XP_MACOSX)
#include "nsMacShellService.h"
#elif defined(MOZ_WIDGET_GTK)
#include "nsGNOMEShellService.h"
#endif
#if defined(XP_WIN)
#include "nsIEHistoryEnumerator.h"
#endif
#include "rdf.h"
#include "nsFeedSniffer.h"
#include "AboutRedirector.h"
#include "nsIAboutModule.h"
#include "nsNetCID.h"
using namespace mozilla::browser;
/////////////////////////////////////////////////////////////////////////////
NS_GENERIC_FACTORY_CONSTRUCTOR(DirectoryProvider)
#if defined(XP_WIN)
NS_GENERIC_FACTORY_CONSTRUCTOR(nsWindowsShellService)
#elif defined(XP_MACOSX)
NS_GENERIC_FACTORY_CONSTRUCTOR(nsMacShellService)
#elif defined(MOZ_WIDGET_GTK)
NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsGNOMEShellService, Init)
#endif
#if defined(XP_WIN)
NS_GENERIC_FACTORY_CONSTRUCTOR(nsIEHistoryEnumerator)
#endif
NS_GENERIC_FACTORY_CONSTRUCTOR(nsFeedSniffer)
NS_DEFINE_NAMED_CID(NS_BROWSERDIRECTORYPROVIDER_CID);
#if defined(XP_WIN)
NS_DEFINE_NAMED_CID(NS_SHELLSERVICE_CID);
#elif defined(MOZ_WIDGET_GTK)
NS_DEFINE_NAMED_CID(NS_SHELLSERVICE_CID);
#endif
NS_DEFINE_NAMED_CID(NS_FEEDSNIFFER_CID);
NS_DEFINE_NAMED_CID(NS_BROWSER_ABOUT_REDIRECTOR_CID);
#if defined(XP_WIN)
NS_DEFINE_NAMED_CID(NS_WINIEHISTORYENUMERATOR_CID);
#elif defined(XP_MACOSX)
NS_DEFINE_NAMED_CID(NS_SHELLSERVICE_CID);
#endif
static const mozilla::Module::CIDEntry kBrowserCIDs[] = {
{ &kNS_BROWSERDIRECTORYPROVIDER_CID, false, nullptr, DirectoryProviderConstructor },
#if defined(XP_WIN)
{ &kNS_SHELLSERVICE_CID, false, nullptr, nsWindowsShellServiceConstructor },
#elif defined(MOZ_WIDGET_GTK)
{ &kNS_SHELLSERVICE_CID, false, nullptr, nsGNOMEShellServiceConstructor },
#endif
{ &kNS_FEEDSNIFFER_CID, false, nullptr, nsFeedSnifferConstructor },
{ &kNS_BROWSER_ABOUT_REDIRECTOR_CID, false, nullptr, AboutRedirector::Create },
#if defined(XP_WIN)
{ &kNS_WINIEHISTORYENUMERATOR_CID, false, nullptr, nsIEHistoryEnumeratorConstructor },
#elif defined(XP_MACOSX)
{ &kNS_SHELLSERVICE_CID, false, nullptr, nsMacShellServiceConstructor },
#endif
{ nullptr }
};
static const mozilla::Module::ContractIDEntry kBrowserContracts[] = {
{ NS_BROWSERDIRECTORYPROVIDER_CONTRACTID, &kNS_BROWSERDIRECTORYPROVIDER_CID },
#if defined(XP_WIN)
{ NS_SHELLSERVICE_CONTRACTID, &kNS_SHELLSERVICE_CID },
#elif defined(MOZ_WIDGET_GTK)
{ NS_SHELLSERVICE_CONTRACTID, &kNS_SHELLSERVICE_CID },
#endif
{ NS_FEEDSNIFFER_CONTRACTID, &kNS_FEEDSNIFFER_CID },
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "certerror", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "socialerror", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "feeds", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "privatebrowsing", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "rights", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "palemoon", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "logopage", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "robots", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "sessionrestore", &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 "permissions", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "downloads", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
#if defined(XP_WIN)
{ NS_IEHISTORYENUMERATOR_CONTRACTID, &kNS_WINIEHISTORYENUMERATOR_CID },
#elif defined(XP_MACOSX)
{ NS_SHELLSERVICE_CONTRACTID, &kNS_SHELLSERVICE_CID },
#endif
{ nullptr }
};
static const mozilla::Module::CategoryEntry kBrowserCategories[] = {
{ XPCOM_DIRECTORY_PROVIDER_CATEGORY, "browser-directory-provider", NS_BROWSERDIRECTORYPROVIDER_CONTRACTID },
{ NS_CONTENT_SNIFFER_CATEGORY, "Feed Sniffer", NS_FEEDSNIFFER_CONTRACTID },
{ nullptr }
};
static const mozilla::Module kBrowserModule = {
mozilla::Module::kVersion,
kBrowserCIDs,
kBrowserContracts,
kBrowserCategories
};
NSMODULE_DEFN(nsBrowserCompsModule) = &kBrowserModule;

View file

@ -0,0 +1,17 @@
/* 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/. */
/* Logical CSS rules belong here, but presentation & theming rules
should live in the CSS of the appropriate theme */
#technicalContentText {
overflow: auto;
white-space: pre-wrap;
}
.expander[hidden],
.expander[hidden] + *,
.expander[collapsed] + * {
display: none;
}

View file

@ -0,0 +1,247 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html [
<!ENTITY % htmlDTD
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"DTD/xhtml1-strict.dtd">
%htmlDTD;
<!ENTITY % globalDTD
SYSTEM "chrome://global/locale/global.dtd">
%globalDTD;
<!ENTITY % certerrorDTD
SYSTEM "chrome://browser/locale/aboutCertError.dtd">
%certerrorDTD;
]>
<!-- 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/. -->
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>&certerror.pagetitle;</title>
<link rel="stylesheet" href="chrome://browser/skin/aboutCertError.css" type="text/css" media="all" />
<link rel="stylesheet" href="chrome://browser/content/certerror/aboutCertError.css" type="text/css" media="all" />
<!-- This page currently uses the same favicon as neterror.xhtml.
If the location of the favicon is changed for both pages, the
FAVICON_ERRORPAGE_URL symbol in toolkit/components/places/src/nsFaviconService.h
should be updated. If this page starts using a different favicon
than neterror.xhtml nsFaviconService->SetAndLoadFaviconForPage
should be updated to ignore this one as well. -->
<link rel="icon" type="image/png" id="favicon" href="chrome://global/skin/icons/warning-16.png"/>
<script type="application/javascript"><![CDATA[
// Error url MUST be formatted like this:
// about:certerror?e=error&u=url&d=desc
// Note that this file uses document.documentURI to get
// the URL (with the format from above). This is because
// document.location.href gets the current URI off the docshell,
// which is the URL displayed in the location bar, i.e.
// the URI that the user attempted to load.
function getCSSClass()
{
var url = document.documentURI;
var matches = url.match(/s\=([^&]+)\&/);
// s is optional, if no match just return nothing
if (!matches || matches.length < 2)
return "";
// parenthetical match is the second entry
return decodeURIComponent(matches[1]);
}
function getDescription()
{
var url = document.documentURI;
var desc = url.search(/d\=/);
// desc == -1 if not found; if so, return an empty string
// instead of what would turn out to be portions of the URI
if (desc == -1)
return "";
return decodeURIComponent(url.slice(desc + 2));
}
function initPage()
{
// Replace the "#1" string in the intro with the hostname. Trickier
// than it might seem since we want to preserve the <b> tags, but
// not allow for any injection by just using innerHTML. Instead,
// just find the right target text node.
var intro = document.getElementById('introContentP1');
function replaceWithHost(node) {
if (node.textContent == "#1")
node.textContent = location.host;
else
for(var i = 0; i < node.childNodes.length; i++)
replaceWithHost(node.childNodes[i]);
};
replaceWithHost(intro);
if (getCSSClass() == "expertBadCert") {
toggle('technicalContent');
toggle('expertContent');
}
// Disallow overrides if this is a Strict-Transport-Security
// host and the cert is bad (STS Spec section 7.3) or if the
// certerror is in a frame (bug 633691).
if (getCSSClass() == "badStsCert" || window != top)
document.getElementById("expertContent").setAttribute("hidden", "true");
var tech = document.getElementById("technicalContentText");
if (tech)
tech.textContent = getDescription();
addDomainErrorLink();
}
/* In the case of SSL error pages about domain mismatch, see if
we can hyperlink the user to the correct site. We don't want
to do this generically since it allows MitM attacks to redirect
users to a site under attacker control, but in certain cases
it is safe (and helpful!) to do so. Bug 402210
*/
function addDomainErrorLink() {
// Rather than textContent, we need to treat description as HTML
var sd = document.getElementById("technicalContentText");
if (sd) {
var desc = getDescription();
// sanitize description text - see bug 441169
// First, find the index of the <a> tag we care about, being careful not to
// use an over-greedy regex
var re = /<a id="cert_domain_link" title="([^"]+)">/;
var result = re.exec(desc);
if(!result)
return;
// Remove sd's existing children
sd.textContent = "";
// Everything up to the link should be text content
sd.appendChild(document.createTextNode(desc.slice(0, result.index)));
// Now create the link itself
var anchorEl = document.createElement("a");
anchorEl.setAttribute("id", "cert_domain_link");
anchorEl.setAttribute("title", result[1]);
anchorEl.appendChild(document.createTextNode(result[1]));
sd.appendChild(anchorEl);
// Finally, append text for anything after the closing </a>
sd.appendChild(document.createTextNode(desc.slice(desc.indexOf("</a>") + "</a>".length)));
}
var link = document.getElementById('cert_domain_link');
if (!link)
return;
var okHost = link.getAttribute("title");
var thisHost = document.location.hostname;
var proto = document.location.protocol;
// If okHost is a wildcard domain ("*.example.com") let's
// use "www" instead. "*.example.com" isn't going to
// get anyone anywhere useful. bug 432491
okHost = okHost.replace(/^\*\./, "www.");
/* case #1:
* example.com uses an invalid security certificate.
*
* The certificate is only valid for www.example.com
*
* Make sure to include the "." ahead of thisHost so that
* a MitM attack on paypal.com doesn't hyperlink to "notpaypal.com"
*
* We'd normally just use a RegExp here except that we lack a
* library function to escape them properly (bug 248062), and
* domain names are famous for having '.' characters in them,
* which would allow spurious and possibly hostile matches.
*/
if (endsWith(okHost, "." + thisHost))
link.href = proto + okHost;
/* case #2:
* browser.garage.maemo.org uses an invalid security certificate.
*
* The certificate is only valid for garage.maemo.org
*/
if (endsWith(thisHost, "." + okHost))
link.href = proto + okHost;
// If we set a link, meaning there's something helpful for
// the user here, expand the section by default
if (link.href && getCSSClass() != "expertBadCert")
toggle("technicalContent");
}
function endsWith(haystack, needle) {
return haystack.slice(-needle.length) == needle;
}
function toggle(id) {
var el = document.getElementById(id);
if (el.getAttribute("collapsed"))
el.removeAttribute("collapsed");
else
el.setAttribute("collapsed", true);
}
]]></script>
</head>
<body dir="&locale.dir;">
<!-- PAGE CONTAINER (for styling purposes only) -->
<div id="errorPageContainer">
<!-- Error Title -->
<div id="errorTitle">
<h1 id="errorTitleText">&certerror.longpagetitle;</h1>
</div>
<!-- LONG CONTENT (the section most likely to require scrolling) -->
<div id="errorLongContent">
<div id="introContent">
<p id="introContentP1">&certerror.introPara1;</p>
<p>&certerror.introPara2;</p>
</div>
<div id="whatShouldIDoContent">
<h2>&certerror.whatShouldIDo.heading;</h2>
<div id="whatShouldIDoContentText">
<p>&certerror.whatShouldIDo.content;</p>
<button id='getMeOutOfHereButton'>&certerror.getMeOutOfHere.label;</button>
</div>
</div>
<!-- The following sections can be unhidden by default by setting the
"browser.xul.error_pages.expert_bad_cert" pref to true -->
<h2 id="technicalContent" class="expander" collapsed="true">
<button onclick="toggle('technicalContent');">&certerror.technical.heading;</button>
</h2>
<p id="technicalContentText"/>
<h2 id="expertContent" class="expander" collapsed="true">
<button onclick="toggle('expertContent');">&certerror.expert.heading;</button>
</h2>
<div>
<p>&certerror.expert.content;</p>
<p>&certerror.expert.contentPara2;</p>
<button id='exceptionDialogButton'>&certerror.addException.label;</button>
</div>
</div>
</div>
<!--
- Note: It is important to run the script this way, instead of using
- an onload handler. This is because error pages are loaded as
- LOAD_BACKGROUND, which means that onload handlers will not be executed.
-->
<script type="application/javascript">initPage();</script>
</body>
</html>

View file

@ -0,0 +1,7 @@
# 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/certerror/aboutCertError.xhtml (content/aboutCertError.xhtml)
content/browser/certerror/aboutCertError.css (content/aboutCertError.css)

View file

@ -0,0 +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/.
JAR_MANIFESTS += ['jar.mn']

View file

@ -0,0 +1,318 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsIDirectoryService.h"
#include "DirectoryProvider.h"
#include "nsIFile.h"
#include "nsISimpleEnumerator.h"
#include "nsIPrefService.h"
#include "nsIPrefBranch.h"
#include "nsArrayEnumerator.h"
#include "nsEnumeratorUtils.h"
#include "nsAppDirectoryServiceDefs.h"
#include "nsDirectoryServiceDefs.h"
#include "nsCategoryManagerUtils.h"
#include "nsComponentManagerUtils.h"
#include "nsCOMArray.h"
#include "nsDirectoryServiceUtils.h"
#include "mozilla/ModuleUtils.h"
#include "nsServiceManagerUtils.h"
#include "nsStringAPI.h"
#include "nsXULAppAPI.h"
#include "nsIPrefLocalizedString.h"
namespace mozilla {
namespace browser {
NS_IMPL_ISUPPORTS(DirectoryProvider,
nsIDirectoryServiceProvider,
nsIDirectoryServiceProvider2)
NS_IMETHODIMP
DirectoryProvider::GetFile(const char *aKey, bool *aPersist, nsIFile* *aResult)
{
nsresult rv;
*aResult = nullptr;
// NOTE: This function can be reentrant through the NS_GetSpecialDirectory
// call, so be careful not to cause infinite recursion.
nsCOMPtr<nsIFile> file;
char const* leafName = nullptr;
if (!strcmp(aKey, NS_APP_BOOKMARKS_50_FILE)) {
leafName = "bookmarks.html";
nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));
if (prefs) {
nsCString path;
rv = prefs->GetCharPref("browser.bookmarks.file", getter_Copies(path));
if (NS_SUCCEEDED(rv)) {
NS_NewNativeLocalFile(path, true, getter_AddRefs(file));
}
}
}
else {
return NS_ERROR_FAILURE;
}
nsDependentCString leafstr(leafName);
nsCOMPtr<nsIFile> parentDir;
if (file) {
rv = file->GetParent(getter_AddRefs(parentDir));
if (NS_FAILED(rv))
return rv;
}
else {
rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(parentDir));
if (NS_FAILED(rv))
return rv;
rv = parentDir->Clone(getter_AddRefs(file));
if (NS_FAILED(rv))
return rv;
file->AppendNative(leafstr);
}
*aPersist = true;
NS_ADDREF(*aResult = file);
return NS_OK;
}
static void
AppendFileKey(const char *key, nsIProperties* aDirSvc,
nsCOMArray<nsIFile> &array)
{
nsCOMPtr<nsIFile> file;
nsresult rv = aDirSvc->Get(key, NS_GET_IID(nsIFile), getter_AddRefs(file));
if (NS_FAILED(rv))
return;
bool exists;
rv = file->Exists(&exists);
if (NS_FAILED(rv) || !exists)
return;
array.AppendObject(file);
}
// Appends the distribution-specific search engine directories to the
// array. The directory structure is as follows:
// appdir/
// \- distribution/
// \- searchplugins/
// |- common/
// \- locale/
// |- <locale 1>/
// ...
// \- <locale N>/
// common engines are loaded for all locales. If there is no locale
// directory for the current locale, there is a pref:
// "distribution.searchplugins.defaultLocale"
// which specifies a default locale to use.
static void
AppendDistroSearchDirs(nsIProperties* aDirSvc, nsCOMArray<nsIFile> &array)
{
nsCOMPtr<nsIFile> searchPlugins;
nsresult rv = aDirSvc->Get(XRE_APP_DISTRIBUTION_DIR,
NS_GET_IID(nsIFile),
getter_AddRefs(searchPlugins));
if (NS_FAILED(rv))
return;
searchPlugins->AppendNative(NS_LITERAL_CSTRING("searchplugins"));
bool exists;
rv = searchPlugins->Exists(&exists);
if (NS_FAILED(rv) || !exists)
return;
nsCOMPtr<nsIFile> commonPlugins;
rv = searchPlugins->Clone(getter_AddRefs(commonPlugins));
if (NS_SUCCEEDED(rv)) {
commonPlugins->AppendNative(NS_LITERAL_CSTRING("common"));
rv = commonPlugins->Exists(&exists);
if (NS_SUCCEEDED(rv) && exists)
array.AppendObject(commonPlugins);
}
nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));
if (prefs) {
nsCOMPtr<nsIFile> localePlugins;
rv = searchPlugins->Clone(getter_AddRefs(localePlugins));
if (NS_FAILED(rv))
return;
localePlugins->AppendNative(NS_LITERAL_CSTRING("locale"));
nsCString locale;
nsCOMPtr<nsIPrefLocalizedString> prefString;
rv = prefs->GetComplexValue("general.useragent.locale",
NS_GET_IID(nsIPrefLocalizedString),
getter_AddRefs(prefString));
if (NS_SUCCEEDED(rv)) {
nsAutoString wLocale;
prefString->GetData(getter_Copies(wLocale));
CopyUTF16toUTF8(wLocale, locale);
} else {
rv = prefs->GetCharPref("general.useragent.locale", getter_Copies(locale));
}
if (NS_SUCCEEDED(rv)) {
nsCOMPtr<nsIFile> curLocalePlugins;
rv = localePlugins->Clone(getter_AddRefs(curLocalePlugins));
if (NS_SUCCEEDED(rv)) {
curLocalePlugins->AppendNative(locale);
rv = curLocalePlugins->Exists(&exists);
if (NS_SUCCEEDED(rv) && exists) {
array.AppendObject(curLocalePlugins);
return; // all done
}
}
}
// we didn't append the locale dir - try the default one
nsCString defLocale;
rv = prefs->GetCharPref("distribution.searchplugins.defaultLocale",
getter_Copies(defLocale));
if (NS_SUCCEEDED(rv)) {
nsCOMPtr<nsIFile> defLocalePlugins;
rv = localePlugins->Clone(getter_AddRefs(defLocalePlugins));
if (NS_SUCCEEDED(rv)) {
defLocalePlugins->AppendNative(defLocale);
rv = defLocalePlugins->Exists(&exists);
if (NS_SUCCEEDED(rv) && exists)
array.AppendObject(defLocalePlugins);
}
}
}
}
NS_IMETHODIMP
DirectoryProvider::GetFiles(const char *aKey, nsISimpleEnumerator* *aResult)
{
nsresult rv;
if (!strcmp(aKey, NS_APP_SEARCH_DIR_LIST)) {
nsCOMPtr<nsIProperties> dirSvc
(do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID));
if (!dirSvc)
return NS_ERROR_FAILURE;
nsCOMArray<nsIFile> baseFiles;
/**
* We want to preserve the following order, since the search service loads
* engines in first-loaded-wins order.
* - extension search plugin locations (prepended below using
* NS_NewUnionEnumerator)
* - distro search plugin locations
* - user search plugin locations (profile)
* - app search plugin location (shipped engines)
*/
AppendDistroSearchDirs(dirSvc, baseFiles);
AppendFileKey(NS_APP_USER_SEARCH_DIR, dirSvc, baseFiles);
AppendFileKey(NS_APP_SEARCH_DIR, dirSvc, baseFiles);
nsCOMPtr<nsISimpleEnumerator> baseEnum;
rv = NS_NewArrayEnumerator(getter_AddRefs(baseEnum), baseFiles);
if (NS_FAILED(rv))
return rv;
nsCOMPtr<nsISimpleEnumerator> list;
rv = dirSvc->Get(XRE_EXTENSIONS_DIR_LIST,
NS_GET_IID(nsISimpleEnumerator), getter_AddRefs(list));
if (NS_FAILED(rv))
return rv;
static char const *const kAppendSPlugins[] = {"searchplugins", nullptr};
nsCOMPtr<nsISimpleEnumerator> extEnum =
new AppendingEnumerator(list, kAppendSPlugins);
if (!extEnum)
return NS_ERROR_OUT_OF_MEMORY;
return NS_NewUnionEnumerator(aResult, extEnum, baseEnum);
}
return NS_ERROR_FAILURE;
}
NS_IMPL_ISUPPORTS(DirectoryProvider::AppendingEnumerator, nsISimpleEnumerator)
NS_IMETHODIMP
DirectoryProvider::AppendingEnumerator::HasMoreElements(bool *aResult)
{
*aResult = mNext ? true : false;
return NS_OK;
}
NS_IMETHODIMP
DirectoryProvider::AppendingEnumerator::GetNext(nsISupports* *aResult)
{
if (aResult)
NS_ADDREF(*aResult = mNext);
mNext = nullptr;
nsresult rv;
// Ignore all errors
bool more;
while (NS_SUCCEEDED(mBase->HasMoreElements(&more)) && more) {
nsCOMPtr<nsISupports> nextbasesupp;
mBase->GetNext(getter_AddRefs(nextbasesupp));
nsCOMPtr<nsIFile> nextbase(do_QueryInterface(nextbasesupp));
if (!nextbase)
continue;
nextbase->Clone(getter_AddRefs(mNext));
if (!mNext)
continue;
char const *const * i = mAppendList;
while (*i) {
mNext->AppendNative(nsDependentCString(*i));
++i;
}
bool exists;
rv = mNext->Exists(&exists);
if (NS_SUCCEEDED(rv) && exists)
break;
mNext = nullptr;
}
return NS_OK;
}
DirectoryProvider::AppendingEnumerator::AppendingEnumerator
(nsISimpleEnumerator* aBase,
char const *const *aAppendList) :
mBase(aBase),
mAppendList(aAppendList)
{
// Initialize mNext to begin.
GetNext(nullptr);
}
} // namespace browser
} // namespace mozilla

View file

@ -0,0 +1,51 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef DirectoryProvider_h__
#define DirectoryProvider_h__
#include "nsIDirectoryService.h"
#include "nsComponentManagerUtils.h"
#include "nsISimpleEnumerator.h"
#include "nsIFile.h"
#include "mozilla/Attributes.h"
#define NS_BROWSERDIRECTORYPROVIDER_CONTRACTID \
"@mozilla.org/browser/directory-provider;1"
namespace mozilla {
namespace browser {
class DirectoryProvider final : public nsIDirectoryServiceProvider2
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIDIRECTORYSERVICEPROVIDER
NS_DECL_NSIDIRECTORYSERVICEPROVIDER2
private:
~DirectoryProvider() {}
class AppendingEnumerator final : public nsISimpleEnumerator
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSISIMPLEENUMERATOR
AppendingEnumerator(nsISimpleEnumerator* aBase,
char const *const *aAppendList);
private:
~AppendingEnumerator() {}
nsCOMPtr<nsISimpleEnumerator> mBase;
char const *const *const mAppendList;
nsCOMPtr<nsIFile> mNext;
};
};
} // namespace browser
} // namespace mozilla
#endif // DirectoryProvider_h__

View file

@ -0,0 +1,19 @@
# -*- 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/.
EXPORTS.mozilla.browser += [
'DirectoryProvider.h',
]
SOURCES += [
'DirectoryProvider.cpp',
]
FINAL_LIBRARY = 'browsercomps'
LOCAL_INCLUDES += [
'../build'
]

View file

@ -0,0 +1,355 @@
/* 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/. */
this.EXPORTED_SYMBOLS = [ "DistributionCustomizer" ];
const Ci = Components.interfaces;
const Cc = Components.classes;
const Cr = Components.results;
const Cu = Components.utils;
const DISTRIBUTION_CUSTOMIZATION_COMPLETE_TOPIC =
"distribution-customization-complete";
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
"resource://gre/modules/PlacesUtils.jsm");
this.DistributionCustomizer = function DistributionCustomizer() {
let dirSvc = Cc["@mozilla.org/file/directory_service;1"].
getService(Ci.nsIProperties);
let iniFile = dirSvc.get("XREExeF", Ci.nsIFile);
iniFile.leafName = "distribution";
iniFile.append("distribution.ini");
if (iniFile.exists())
this._iniFile = iniFile;
}
DistributionCustomizer.prototype = {
_iniFile: null,
get _ini() {
let ini = Cc["@mozilla.org/xpcom/ini-parser-factory;1"].
getService(Ci.nsIINIParserFactory).
createINIParser(this._iniFile);
this.__defineGetter__("_ini", function() ini);
return this._ini;
},
get _locale() {
let locale;
try {
locale = this._prefs.getCharPref("general.useragent.locale");
}
catch (e) {
locale = "en-US";
}
this.__defineGetter__("_locale", function() locale);
return this._locale;
},
get _prefSvc() {
let svc = Cc["@mozilla.org/preferences-service;1"].
getService(Ci.nsIPrefService);
this.__defineGetter__("_prefSvc", function() svc);
return this._prefSvc;
},
get _prefs() {
let branch = this._prefSvc.getBranch(null);
this.__defineGetter__("_prefs", function() branch);
return this._prefs;
},
get _ioSvc() {
let svc = Cc["@mozilla.org/network/io-service;1"].
getService(Ci.nsIIOService);
this.__defineGetter__("_ioSvc", function() svc);
return this._ioSvc;
},
_makeURI: function DIST__makeURI(spec) {
return this._ioSvc.newURI(spec, null, null);
},
_parseBookmarksSection:
function DIST_parseBookmarksSection(parentId, section) {
let keys = [];
for (let i in enumerate(this._ini.getKeys(section)))
keys.push(i);
keys.sort();
let items = {};
let defaultItemId = -1;
let maxItemId = -1;
for (let i = 0; i < keys.length; i++) {
let m = /^item\.(\d+)\.(\w+)\.?(\w*)/.exec(keys[i]);
if (m) {
let [foo, iid, iprop, ilocale] = m;
iid = parseInt(iid);
if (ilocale)
continue;
if (!items[iid])
items[iid] = {};
if (keys.indexOf(keys[i] + "." + this._locale) >= 0) {
items[iid][iprop] = this._ini.getString(section, keys[i] + "." +
this._locale);
} else {
items[iid][iprop] = this._ini.getString(section, keys[i]);
}
if (iprop == "type" && items[iid]["type"] == "default")
defaultItemId = iid;
if (maxItemId < iid)
maxItemId = iid;
} else {
dump("Key did not match: " + keys[i] + "\n");
}
}
let prependIndex = 0;
for (let iid = 0; iid <= maxItemId; iid++) {
if (!items[iid])
continue;
let index = PlacesUtils.bookmarks.DEFAULT_INDEX;
let newId;
switch (items[iid]["type"]) {
case "default":
break;
case "folder":
if (iid < defaultItemId)
index = prependIndex++;
newId = PlacesUtils.bookmarks.createFolder(parentId,
items[iid]["title"],
index);
this._parseBookmarksSection(newId, "BookmarksFolder-" +
items[iid]["folderId"]);
if (items[iid]["description"])
PlacesUtils.annotations.setItemAnnotation(newId,
"bookmarkProperties/description",
items[iid]["description"], 0,
PlacesUtils.annotations.EXPIRE_NEVER);
break;
case "separator":
if (iid < defaultItemId)
index = prependIndex++;
PlacesUtils.bookmarks.insertSeparator(parentId, index);
break;
case "livemark":
if (iid < defaultItemId)
index = prependIndex++;
// Don't bother updating the livemark contents on creation.
PlacesUtils.livemarks.addLivemark({ title: items[iid]["title"]
, parentId: parentId
, index: index
, feedURI: this._makeURI(items[iid]["feedLink"])
, siteURI: this._makeURI(items[iid]["siteLink"])
}).then(null, Cu.reportError);
break;
case "bookmark":
default:
if (iid < defaultItemId)
index = prependIndex++;
newId = PlacesUtils.bookmarks.insertBookmark(parentId,
this._makeURI(items[iid]["link"]),
index, items[iid]["title"]);
if (items[iid]["description"])
PlacesUtils.annotations.setItemAnnotation(newId,
"bookmarkProperties/description",
items[iid]["description"], 0,
PlacesUtils.annotations.EXPIRE_NEVER);
break;
}
}
},
_customizationsApplied: false,
applyCustomizations: function DIST_applyCustomizations() {
this._customizationsApplied = true;
if (!this._iniFile)
return this._checkCustomizationComplete();
// nsPrefService loads very early. Reload prefs so we can set
// distribution defaults during the prefservice:after-app-defaults
// notification (see applyPrefDefaults below)
this._prefSvc.QueryInterface(Ci.nsIObserver);
this._prefSvc.observe(null, "reload-default-prefs", null);
},
_bookmarksApplied: false,
applyBookmarks: function DIST_applyBookmarks() {
this._bookmarksApplied = true;
if (!this._iniFile)
return this._checkCustomizationComplete();
let sections = enumToObject(this._ini.getSections());
// The global section, and several of its fields, is required
// (we also check here to be consistent with applyPrefDefaults below)
if (!sections["Global"])
return this._checkCustomizationComplete();
let globalPrefs = enumToObject(this._ini.getKeys("Global"));
if (!(globalPrefs["id"] && globalPrefs["version"] && globalPrefs["about"]))
return this._checkCustomizationComplete();
let bmProcessedPref;
try {
bmProcessedPref = this._ini.getString("Global",
"bookmarks.initialized.pref");
}
catch (e) {
bmProcessedPref = "distribution." +
this._ini.getString("Global", "id") + ".bookmarksProcessed";
}
let bmProcessed = false;
try {
bmProcessed = this._prefs.getBoolPref(bmProcessedPref);
}
catch (e) {}
if (!bmProcessed) {
if (sections["BookmarksMenu"])
this._parseBookmarksSection(PlacesUtils.bookmarksMenuFolderId,
"BookmarksMenu");
if (sections["BookmarksToolbar"])
this._parseBookmarksSection(PlacesUtils.toolbarFolderId,
"BookmarksToolbar");
this._prefs.setBoolPref(bmProcessedPref, true);
}
return this._checkCustomizationComplete();
},
_prefDefaultsApplied: false,
applyPrefDefaults: function DIST_applyPrefDefaults() {
this._prefDefaultsApplied = true;
if (!this._iniFile)
return this._checkCustomizationComplete();
let sections = enumToObject(this._ini.getSections());
// The global section, and several of its fields, is required
if (!sections["Global"])
return this._checkCustomizationComplete();
let globalPrefs = enumToObject(this._ini.getKeys("Global"));
if (!(globalPrefs["id"] && globalPrefs["version"] && globalPrefs["about"]))
return this._checkCustomizationComplete();
let defaults = this._prefSvc.getDefaultBranch(null);
// Global really contains info we set as prefs. They're only
// separate because they are "special" (read: required)
defaults.setCharPref("distribution.id", this._ini.getString("Global", "id"));
defaults.setCharPref("distribution.version",
this._ini.getString("Global", "version"));
let partnerAbout = Cc["@mozilla.org/supports-string;1"].
createInstance(Ci.nsISupportsString);
try {
if (globalPrefs["about." + this._locale]) {
partnerAbout.data = this._ini.getString("Global", "about." + this._locale);
} else {
partnerAbout.data = this._ini.getString("Global", "about");
}
defaults.setComplexValue("distribution.about",
Ci.nsISupportsString, partnerAbout);
} catch (e) {
/* ignore bad prefs due to bug 895473 and move on */
Cu.reportError(e);
}
if (sections["Preferences"]) {
for (let key in enumerate(this._ini.getKeys("Preferences"))) {
try {
let value = eval(this._ini.getString("Preferences", key));
switch (typeof value) {
case "boolean":
defaults.setBoolPref(key, value);
break;
case "number":
defaults.setIntPref(key, value);
break;
case "string":
defaults.setCharPref(key, value);
break;
case "undefined":
defaults.setCharPref(key, value);
break;
}
} catch (e) { /* ignore bad prefs and move on */ }
}
}
// We eval() the localizable prefs as well (even though they'll
// always get set as a string) to keep the INI format consistent:
// string prefs always need to be in quotes
let localizedStr = Cc["@mozilla.org/pref-localizedstring;1"].
createInstance(Ci.nsIPrefLocalizedString);
if (sections["LocalizablePreferences"]) {
for (let key in enumerate(this._ini.getKeys("LocalizablePreferences"))) {
try {
let value = eval(this._ini.getString("LocalizablePreferences", key));
value = value.replace("%LOCALE%", this._locale, "g");
localizedStr.data = "data:text/plain," + key + "=" + value;
defaults.setComplexValue(key, Ci.nsIPrefLocalizedString, localizedStr);
} catch (e) { /* ignore bad prefs and move on */ }
}
}
if (sections["LocalizablePreferences-" + this._locale]) {
for (let key in enumerate(this._ini.getKeys("LocalizablePreferences-" + this._locale))) {
try {
let value = eval(this._ini.getString("LocalizablePreferences-" + this._locale, key));
localizedStr.data = "data:text/plain," + key + "=" + value;
defaults.setComplexValue(key, Ci.nsIPrefLocalizedString, localizedStr);
} catch (e) { /* ignore bad prefs and move on */ }
}
}
return this._checkCustomizationComplete();
},
_checkCustomizationComplete: function DIST__checkCustomizationComplete() {
let prefDefaultsApplied = this._prefDefaultsApplied || !this._iniFile;
if (this._customizationsApplied && this._bookmarksApplied &&
prefDefaultsApplied) {
let os = Cc["@mozilla.org/observer-service;1"].
getService(Ci.nsIObserverService);
os.notifyObservers(null, DISTRIBUTION_CUSTOMIZATION_COMPLETE_TOPIC, null);
}
}
};
function enumerate(UTF8Enumerator) {
while (UTF8Enumerator.hasMore())
yield UTF8Enumerator.getNext();
}
function enumToObject(UTF8Enumerator) {
let ret = {};
for (let i in enumerate(UTF8Enumerator))
ret[i] = 1;
return ret;
}

View file

@ -0,0 +1,4 @@
component {49507fe5-2cee-4824-b6a3-e999150ce9b8} DownloadsStartup.js
contract @mozilla.org/browser/downloadsstartup;1 {49507fe5-2cee-4824-b6a3-e999150ce9b8}
category profile-after-change DownloadsStartup @mozilla.org/browser/downloadsstartup;1
component {4d99321e-d156-455b-81f7-e7aa2308134f} DownloadsUI.js

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,76 @@
/* -*- Mode: js2; js2-basic-offset: 2; indent-tabs-mode: nil; -*- */
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* The contents of this file were copied almost entirely from
* toolkit/identity/LogUtils.jsm. Until we've got a more generalized logging
* mechanism for toolkit, I think this is going to be how we roll.
*/
"use strict";
this.EXPORTED_SYMBOLS = ["DownloadsLogger"];
const PREF_DEBUG = "browser.download.debug";
const Cu = Components.utils;
const Ci = Components.interfaces;
const Cc = Components.classes;
const Cr = Components.results;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
this.DownloadsLogger = {
_generateLogMessage: function _generateLogMessage(args) {
// create a string representation of a list of arbitrary things
let strings = [];
for (let arg of args) {
if (typeof arg === 'string') {
strings.push(arg);
} else if (arg === undefined) {
strings.push('undefined');
} else if (arg === null) {
strings.push('null');
} else {
try {
strings.push(JSON.stringify(arg, null, 2));
} catch(err) {
strings.push("<<something>>");
}
}
};
return 'Downloads: ' + strings.join(' ');
},
/**
* log() - utility function to print a list of arbitrary things
*
* Enable with about:config pref browser.download.debug
*/
log: function DL_log(...args) {
let output = this._generateLogMessage(args);
dump(output + "\n");
// Additionally, make the output visible in the Error Console
Services.console.logStringMessage(output);
},
/**
* reportError() - report an error through component utils as well as
* our log function
*/
reportError: function DL_reportError(...aArgs) {
// Report the error in the browser
let output = this._generateLogMessage(aArgs);
Cu.reportError(output);
dump("ERROR:" + output + "\n");
for (let frame = Components.stack.caller; frame; frame = frame.caller) {
dump("\t" + frame + "\n");
}
}
};

View file

@ -0,0 +1,278 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* This component listens to notifications for startup, shutdown and session
* restore, controlling which downloads should be loaded from the database.
*
* To avoid affecting startup performance, this component monitors the current
* session restore state, but defers the actual downloads data manipulation
* until the Download Manager service is loaded.
*/
"use strict";
////////////////////////////////////////////////////////////////////////////////
//// Globals
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
const Cr = Components.results;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DownloadsCommon",
"resource:///modules/DownloadsCommon.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "gSessionStartup",
"@mozilla.org/browser/sessionstartup;1",
"nsISessionStartup");
const kObservedTopics = [
"sessionstore-windows-restored",
"sessionstore-browser-state-restored",
"download-manager-initialized",
"download-manager-change-retention",
"last-pb-context-exited",
"browser-lastwindow-close-granted",
"quit-application",
"profile-change-teardown",
];
/**
* CID of our implementation of nsIDownloadManagerUI.
*/
const kDownloadsUICid = Components.ID("{4d99321e-d156-455b-81f7-e7aa2308134f}");
/**
* Contract ID of the service implementing nsIDownloadManagerUI.
*/
const kDownloadsUIContractId = "@mozilla.org/download-manager-ui;1";
/**
* CID of the JavaScript implementation of nsITransfer.
*/
const kTransferCid = Components.ID("{1b4c85df-cbdd-4bb6-b04e-613caece083c}");
/**
* Contract ID of the service implementing nsITransfer.
*/
const kTransferContractId = "@mozilla.org/transfer;1";
////////////////////////////////////////////////////////////////////////////////
//// DownloadsStartup
function DownloadsStartup() { }
DownloadsStartup.prototype = {
classID: Components.ID("{49507fe5-2cee-4824-b6a3-e999150ce9b8}"),
_xpcom_factory: XPCOMUtils.generateSingletonFactory(DownloadsStartup),
//////////////////////////////////////////////////////////////////////////////
//// nsISupports
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
Ci.nsISupportsWeakReference]),
//////////////////////////////////////////////////////////////////////////////
//// nsIObserver
observe: function DS_observe(aSubject, aTopic, aData)
{
switch (aTopic) {
case "profile-after-change":
// Override Toolkit's nsIDownloadManagerUI implementation with our own.
// This must be done at application startup and not in the manifest to
// ensure that our implementation overrides the original one.
Components.manager.QueryInterface(Ci.nsIComponentRegistrar)
.registerFactory(kDownloadsUICid, "",
kDownloadsUIContractId, null);
Components.manager.QueryInterface(Ci.nsIComponentRegistrar)
.registerFactory(kTransferCid, "",
kTransferContractId, null);
break;
case "sessionstore-windows-restored":
case "sessionstore-browser-state-restored":
// Unless there is no saved session, there is a chance that we are
// starting up after a restart or a crash. We should check the disk
// database to see if there are completed downloads to recover and show
// in the panel, in addition to in-progress downloads.
if (gSessionStartup.sessionType != Ci.nsISessionStartup.NO_SESSION) {
this._restoringSession = true;
}
this._ensureDataLoaded();
break;
case "download-manager-initialized":
// Don't initialize the JavaScript data and user interface layer if we
// are initializing the Download Manager service during shutdown.
if (this._shuttingDown) {
break;
}
// Start receiving events for active and new downloads before we return
// from this observer function. We can't defer the execution of this
// step, to ensure that we don't lose events raised in the meantime.
DownloadsCommon.initializeAllDataLinks(
aSubject.QueryInterface(Ci.nsIDownloadManager));
this._downloadsServiceInitialized = true;
// Since this notification is generated during the getService call and
// we need to get the Download Manager service ourselves, we must post
// the handler on the event queue to be executed later.
Services.tm.mainThread.dispatch(this._ensureDataLoaded.bind(this),
Ci.nsIThread.DISPATCH_NORMAL);
break;
case "download-manager-change-retention":
// If we're using the Downloads Panel, we override the retention
// preference to always retain downloads on completion.
if (!DownloadsCommon.useToolkitUI) {
aSubject.QueryInterface(Ci.nsISupportsPRInt32).data = 2;
}
break;
case "browser-lastwindow-close-granted":
// When using the panel interface, downloads that are already completed
// should be removed when the last full browser window is closed. This
// event is invoked only if the application is not shutting down yet.
// If the Download Manager service is not initialized, we don't want to
// initialize it just to clean up completed downloads, because they can
// be present only in case there was a browser crash or restart.
if (this._downloadsServiceInitialized &&
!DownloadsCommon.useToolkitUI) {
Services.downloads.cleanUp();
}
break;
case "last-pb-context-exited":
// Similar to the above notification, but for private downloads.
if (this._downloadsServiceInitialized &&
!DownloadsCommon.useToolkitUI) {
Services.downloads.cleanUpPrivate();
}
break;
case "quit-application":
// When the application is shutting down, we must free all resources in
// addition to cleaning up completed downloads. If the Download Manager
// service is not initialized, we don't want to initialize it just to
// clean up completed downloads, because they can be present only in
// case there was a browser crash or restart.
this._shuttingDown = true;
if (!this._downloadsServiceInitialized) {
break;
}
DownloadsCommon.terminateAllDataLinks();
// When using the panel interface, downloads that are already completed
// should be removed when quitting the application.
if (!DownloadsCommon.useToolkitUI && aData != "restart") {
this._cleanupOnShutdown = true;
}
break;
case "profile-change-teardown":
// If we need to clean up, we must do it synchronously after all the
// "quit-application" listeners are invoked, so that the Download
// Manager service has a chance to pause or cancel in-progress downloads
// before we remove completed downloads from the list. Note that, since
// "quit-application" was invoked, we've already exited Private Browsing
// Mode, thus we are always working on the disk database.
if (this._cleanupOnShutdown) {
Services.downloads.cleanUp();
}
if (!DownloadsCommon.useToolkitUI) {
// If we got this far, that means that we finished our first session
// with the Downloads Panel without crashing. This means that we don't
// have to force displaying only active downloads on the next startup
// now.
this._firstSessionCompleted = true;
}
break;
}
},
//////////////////////////////////////////////////////////////////////////////
//// Private
/**
* Indicates whether we're restoring a previous session. This is used by
* _recoverAllDownloads to determine whether or not we should load and
* display all downloads data, or restrict it to only the active downloads.
*/
_restoringSession: false,
/**
* Indicates whether the Download Manager service has been initialized. This
* flag is required because we want to avoid accessing the service immediately
* at browser startup. The service will start when the user first requests a
* download, or some time after browser startup.
*/
_downloadsServiceInitialized: false,
/**
* True while we are processing the "quit-application" event, and later.
*/
_shuttingDown: false,
/**
* True during shutdown if we need to remove completed downloads.
*/
_cleanupOnShutdown: false,
/**
* True if we should display all downloads, as opposed to just active
* downloads. We decide to display all downloads if we're restoring a session,
* or if we're using the Downloads Panel anytime after the first session with
* it has completed.
*/
get _recoverAllDownloads() {
return this._restoringSession ||
(!DownloadsCommon.useToolkitUI && this._firstSessionCompleted);
},
/**
* True if we've ever completed a session with the Downloads Panel enabled.
*/
get _firstSessionCompleted() {
return Services.prefs
.getBoolPref("browser.download.panel.firstSessionCompleted");
},
set _firstSessionCompleted(aValue) {
Services.prefs.setBoolPref("browser.download.panel.firstSessionCompleted",
aValue);
return aValue;
},
/**
* Ensures that persistent download data is reloaded at the appropriate time.
*/
_ensureDataLoaded: function DS_ensureDataLoaded()
{
if (!this._downloadsServiceInitialized) {
return;
}
// If the previous session has been already restored, then we ensure that
// all the downloads are loaded. Otherwise, we only ensure that the active
// downloads from the previous session are loaded.
DownloadsCommon.ensureAllPersistentDataLoaded(!this._recoverAllDownloads);
}
};
////////////////////////////////////////////////////////////////////////////////
//// Module
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([DownloadsStartup]);

View file

@ -0,0 +1,177 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80 filetype=javascript: */
/* 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/. */
/**
* Handles the download progress indicator in the taskbar.
*/
"use strict";
this.EXPORTED_SYMBOLS = [
"DownloadsTaskbar",
];
////////////////////////////////////////////////////////////////////////////////
//// Globals
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
const Cr = Components.results;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
"resource://gre/modules/Downloads.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow",
"resource:///modules/RecentWindow.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Services",
"resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyGetter(this, "gWinTaskbar", function () {
if (!("@mozilla.org/windows-taskbar;1" in Cc)) {
return null;
}
let winTaskbar = Cc["@mozilla.org/windows-taskbar;1"]
.getService(Ci.nsIWinTaskbar);
return winTaskbar.available && winTaskbar;
});
XPCOMUtils.defineLazyGetter(this, "gMacTaskbarProgress", function () {
return ("@mozilla.org/widget/macdocksupport;1" in Cc) &&
Cc["@mozilla.org/widget/macdocksupport;1"]
.getService(Ci.nsITaskbarProgress);
});
////////////////////////////////////////////////////////////////////////////////
//// DownloadsTaskbar
/**
* Handles the download progress indicator in the taskbar.
*/
this.DownloadsTaskbar = {
/**
* Underlying DownloadSummary providing the aggregate download information, or
* null if the indicator has never been initialized.
*/
_summary: null,
/**
* nsITaskbarProgress object to which download information is dispatched.
* This can be null if the indicator has never been initialized or if the
* indicator is currently hidden on Windows.
*/
_taskbarProgress: null,
/**
* This method is called after a new browser window is opened, and ensures
* that the download progress indicator is displayed in the taskbar.
*
* On Windows, the indicator is attached to the first browser window that
* calls this method. When the window is closed, the indicator is moved to
* another browser window, if available, in no particular order. When there
* are no browser windows visible, the indicator is hidden.
*
* On Mac OS X, the indicator is initialized globally when this method is
* called for the first time. Subsequent calls have no effect.
*
* @param aBrowserWindow
* nsIDOMWindow object of the newly opened browser window to which the
* indicator may be attached.
*/
registerIndicator(aBrowserWindow) {
if (!this._taskbarProgress) {
if (gMacTaskbarProgress) {
// On Mac OS X, we have to register the global indicator only once.
this._taskbarProgress = gMacTaskbarProgress;
// Free the XPCOM reference on shutdown, to prevent detecting a leak.
Services.obs.addObserver(() => {
this._taskbarProgress = null;
gMacTaskbarProgress = null;
}, "quit-application-granted", false);
} else if (gWinTaskbar) {
// On Windows, the indicator is currently hidden because we have no
// previous browser window, thus we should attach the indicator now.
this._attachIndicator(aBrowserWindow);
} else {
// The taskbar indicator is not available on this platform.
return;
}
}
// Ensure that the DownloadSummary object will be created asynchronously.
if (!this._summary) {
Downloads.getSummary(Downloads.ALL).then(summary => {
// In case the method is re-entered, we simply ignore redundant
// invocations of the callback, instead of keeping separate state.
if (this._summary) {
return;
}
this._summary = summary;
return this._summary.addView(this);
}).then(null, Cu.reportError);
}
},
/**
* On Windows, attaches the taskbar indicator to the specified browser window.
*/
_attachIndicator(aWindow) {
// Activate the indicator on the specified window.
let docShell = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShellTreeItem).treeOwner
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIXULWindow).docShell;
this._taskbarProgress = gWinTaskbar.getTaskbarProgress(docShell);
// If the DownloadSummary object has already been created, we should update
// the state of the new indicator, otherwise it will be updated as soon as
// the DownloadSummary view is registered.
if (this._summary) {
this.onSummaryChanged();
}
aWindow.addEventListener("unload", () => {
// Locate another browser window, excluding the one being closed.
let browserWindow = RecentWindow.getMostRecentBrowserWindow();
if (browserWindow) {
// Move the progress indicator to the other browser window.
this._attachIndicator(browserWindow);
} else {
// The last browser window has been closed. We remove the reference to
// the taskbar progress object so that the indicator will be registered
// again on the next browser window that is opened.
this._taskbarProgress = null;
}
}, false);
},
//////////////////////////////////////////////////////////////////////////////
//// DownloadSummary view
onSummaryChanged() {
// If the last browser window has been closed, we have no indicator any more.
if (!this._taskbarProgress) {
return;
}
if (this._summary.allHaveStopped || this._summary.progressTotalBytes == 0) {
this._taskbarProgress.setProgressState(
Ci.nsITaskbarProgress.STATE_NO_PROGRESS, 0, 0);
} else {
// For a brief moment before completion, some download components may
// report more transferred bytes than the total number of bytes. Thus,
// ensure that we never break the expectations of the progress indicator.
let progressCurrentBytes = Math.min(this._summary.progressTotalBytes,
this._summary.progressCurrentBytes);
this._taskbarProgress.setProgressState(
Ci.nsITaskbarProgress.STATE_NORMAL,
progressCurrentBytes,
this._summary.progressTotalBytes);
}
},
};

View file

@ -0,0 +1,151 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* This component implements the nsIDownloadManagerUI interface and opens the
* downloads panel in the most recent browser window when requested.
*
* If a specific preference is set, this component transparently forwards all
* calls to the original implementation in Toolkit, that shows the window UI.
*/
"use strict";
////////////////////////////////////////////////////////////////////////////////
//// Globals
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
const Cr = Components.results;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DownloadsCommon",
"resource:///modules/DownloadsCommon.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "gBrowserGlue",
"@mozilla.org/browser/browserglue;1",
"nsIBrowserGlue");
XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow",
"resource:///modules/RecentWindow.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
"resource://gre/modules/PrivateBrowsingUtils.jsm");
////////////////////////////////////////////////////////////////////////////////
//// DownloadsUI
function DownloadsUI()
{
XPCOMUtils.defineLazyGetter(this, "_toolkitUI", function () {
// Create Toolkit's nsIDownloadManagerUI implementation.
return Components.classesByID["{7dfdf0d1-aff6-4a34-bad1-d0fe74601642}"]
.getService(Ci.nsIDownloadManagerUI);
});
}
DownloadsUI.prototype = {
classID: Components.ID("{4d99321e-d156-455b-81f7-e7aa2308134f}"),
_xpcom_factory: XPCOMUtils.generateSingletonFactory(DownloadsUI),
//////////////////////////////////////////////////////////////////////////////
//// nsISupports
QueryInterface: XPCOMUtils.generateQI([Ci.nsIDownloadManagerUI]),
//////////////////////////////////////////////////////////////////////////////
//// nsIDownloadManagerUI
show: function DUI_show(aWindowContext, aDownload, aReason, aUsePrivateUI)
{
if (DownloadsCommon.useToolkitUI && !PrivateBrowsingUtils.isWindowPrivate(aWindowContext)) {
this._toolkitUI.show(aWindowContext, aDownload, aReason, aUsePrivateUI);
return;
}
if (!aReason) {
aReason = Ci.nsIDownloadManagerUI.REASON_USER_INTERACTED;
}
if (aReason == Ci.nsIDownloadManagerUI.REASON_NEW_DOWNLOAD) {
const kMinimized = Ci.nsIDOMChromeWindow.STATE_MINIMIZED;
let browserWin = gBrowserGlue.getMostRecentBrowserWindow();
if (!browserWin || browserWin.windowState == kMinimized) {
this._showDownloadManagerUI(aWindowContext, aUsePrivateUI);
}
else {
// If the indicator is visible, then new download notifications are
// already handled by the panel service.
browserWin.DownloadsButton.checkIsVisible(function(isVisible) {
if (!isVisible) {
this._showDownloadManagerUI(aWindowContext, aUsePrivateUI);
}
}.bind(this));
}
} else {
this._showDownloadManagerUI(aWindowContext, aUsePrivateUI);
}
},
get visible()
{
// If we're still using the toolkit downloads manager, delegate the call
// to it. Otherwise, return true for now, until we decide on how we want
// to indicate that a new download has started if a browser window is
// not available or minimized.
return DownloadsCommon.useToolkitUI ? this._toolkitUI.visible : true;
},
getAttention: function DUI_getAttention()
{
if (DownloadsCommon.useToolkitUI) {
this._toolkitUI.getAttention();
}
},
/**
* Helper function that opens the download manager UI.
*/
_showDownloadManagerUI:
function DUI_showDownloadManagerUI(aWindowContext, aUsePrivateUI)
{
// If we weren't given a window context, try to find a browser window
// to use as our parent - and if that doesn't work, error out and give up.
let parentWindow = aWindowContext;
if (!parentWindow) {
parentWindow = RecentWindow.getMostRecentBrowserWindow({ private: !!aUsePrivateUI });
if (!parentWindow) {
Components.utils.reportError(
"Couldn't find a browser window to open the Places Downloads View " +
"from.");
return;
}
}
// If window is private then show it in a tab.
if (PrivateBrowsingUtils.isWindowPrivate(parentWindow)) {
parentWindow.openUILinkIn("about:downloads", "tab");
return;
} else {
let organizer = Services.wm.getMostRecentWindow("Places:Organizer");
if (!organizer) {
parentWindow.openDialog("chrome://browser/content/places/places.xul",
"", "chrome,toolbar=yes,dialog=no,resizable",
"Downloads");
} else {
organizer.PlacesOrganizer.selectLeftPaneQuery("Downloads");
organizer.focus();
}
}
}
};
////////////////////////////////////////////////////////////////////////////////
//// Module
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([DownloadsUI]);

View file

@ -0,0 +1,56 @@
/* 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/. */
/**
* The downloads richlistbox may list thousands of items, and it turns out
* XBL binding attachment, and even more so detachment, is a performance hog.
* This hack makes sure we don't apply any binding to inactive items (inactive
* items are history downloads that haven't been in the visible area).
* We can do this because the richlistbox implementation does not interact
* much with the richlistitem binding. However, this may turn out to have
* some side effects (see bug 828111 for the details).
*
* We might be able to do away with this workaround once bug 653881 is fixed.
*/
richlistitem.download {
-moz-binding: none;
}
richlistitem.download[active] {
-moz-binding: url('chrome://browser/content/downloads/download.xml#download-full-ui');
}
richlistitem.download[active]:-moz-any([state="-1"],/* Starting (initial) */
[state="0"], /* Downloading */
[state="4"], /* Paused */
[state="5"], /* Starting (queued) */
[state="7"]) /* Scanning */
{
-moz-binding: url('chrome://browser/content/downloads/download.xml#download-in-progress-full-ui');
}
.download-state:not( [state="0"] /* Downloading */)
.downloadPauseMenuItem,
.download-state:not( [state="4"] /* Paused */)
.downloadResumeMenuItem,
.download-state:not(:-moz-any([state="2"], /* Failed */
[state="4"]) /* Paused */)
.downloadCancelMenuItem,
.download-state[state]:not(:-moz-any([state="1"], /* Finished */
[state="2"], /* Failed */
[state="3"], /* Canceled */
[state="6"], /* Blocked (parental) */
[state="8"], /* Blocked (dirty) */
[state="9"]) /* Blocked (policy) */)
.downloadRemoveFromHistoryMenuItem,
.download-state:not(:-moz-any([state="-1"],/* Starting (initial) */
[state="0"], /* Downloading */
[state="1"], /* Finished */
[state="4"], /* Paused */
[state="5"]) /* Starting (queued) */)
.downloadShowMenuItem,
.download-state[state="7"] /* Scanning */ .downloadCommandsSeparator
{
display: none;
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,119 @@
<?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://browser/content/downloads/allDownloadsViewOverlay.css"?>
<?xml-stylesheet href="chrome://browser/skin/downloads/allDownloadsViewOverlay.css"?>
<!DOCTYPE overlay [
<!ENTITY % downloadsDTD SYSTEM "chrome://browser/locale/downloads/downloads.dtd">
%downloadsDTD;
]>
<!-- This overlay provides a downloads view that lists both session downloads,
using the DownloadsView API, and history downloads, using places queries.
The view also implements a command controller and a context menu for
managing the downloads list. In order to use this view:
1. Apply this overlay to your window.
2. Insert in all the overlay entry-points, namely:
<richlistbox id="downloadsRichListBox"/>
<commandset id="downloadCommands"/>
<menupopup id="downloadsContextMenu"/>
3. Make sure your window has the editMenuOverlay overlay applied,
because the view implements cmd_copy and cmd_delete.
4. Make sure your window has the globalOverlay.js script loaded.
5. To initialize the view
let view = new DownloadsPlacesView(document.getElementById("downloadsRichListBox"));
// This is what the Places Library uses. It could be tweaked a bit as long as the
// transition-type is set correctly
view.place = "place:transition=7&sort=4";
-->
<overlay id="downloadsViewOverlay"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script type="application/javascript"
src="chrome://browser/content/downloads/allDownloadsViewOverlay.js"/>
<script type="application/javascript"
src="chrome://global/content/contentAreaUtils.js"/>
<richlistbox flex="1"
seltype="multiple"
id="downloadsRichListBox" context="downloadsContextMenu"
onscroll="return this._placesView.onScroll();"
onkeypress="return this._placesView.onKeyPress(event);"
ondblclick="return this._placesView.onDoubleClick(event);"
oncontextmenu="return this._placesView.onContextMenu(event);"
ondragstart="this._placesView.onDragStart(event);"
ondragover="this._placesView.onDragOver(event);"
ondrop="this._placesView.onDrop(event);"
onfocus="goUpdateDownloadCommands();"
onselect="this._placesView.onSelect();"
onblur="goUpdateDownloadCommands();"/>
<commandset id="downloadCommands"
commandupdater="true"
events="focus,select,contextmenu"
oncommandupdate="goUpdateDownloadCommands();">
<command id="downloadsCmd_pauseResume"
oncommand="goDoCommand('downloadsCmd_pauseResume')"/>
<command id="downloadsCmd_cancel"
oncommand="goDoCommand('downloadsCmd_cancel')"/>
<command id="downloadsCmd_open"
oncommand="goDoCommand('downloadsCmd_open')"/>
<command id="downloadsCmd_show"
oncommand="goDoCommand('downloadsCmd_show')"/>
<command id="downloadsCmd_retry"
oncommand="goDoCommand('downloadsCmd_retry')"/>
<command id="downloadsCmd_openReferrer"
oncommand="goDoCommand('downloadsCmd_openReferrer')"/>
<command id="downloadsCmd_clearDownloads"
oncommand="goDoCommand('downloadsCmd_clearDownloads')"/>
</commandset>
<menupopup id="downloadsContextMenu" class="download-state">
<menuitem command="downloadsCmd_pauseResume"
class="downloadPauseMenuItem"
label="&cmd.pause.label;"
accesskey="&cmd.pause.accesskey;"/>
<menuitem command="downloadsCmd_pauseResume"
class="downloadResumeMenuItem"
label="&cmd.resume.label;"
accesskey="&cmd.resume.accesskey;"/>
<menuitem command="downloadsCmd_cancel"
class="downloadCancelMenuItem"
label="&cmd.cancel.label;"
accesskey="&cmd.cancel.accesskey;"/>
<menuitem command="cmd_delete"
class="downloadRemoveFromHistoryMenuItem"
label="&cmd.removeFromHistory.label;"
accesskey="&cmd.removeFromHistory.accesskey;"/>
<menuitem command="downloadsCmd_show"
class="downloadShowMenuItem"
#ifdef XP_MACOSX
label="&cmd.showMac.label;"
accesskey="&cmd.showMac.accesskey;"
#else
label="&cmd.show.label;"
accesskey="&cmd.show.accesskey;"
#endif
/>
<menuseparator class="downloadCommandsSeparator"/>
<menuitem command="downloadsCmd_openReferrer"
label="&cmd.goToDownloadPage.label;"
accesskey="&cmd.goToDownloadPage.accesskey;"/>
<menuitem command="cmd_copy"
label="&cmd.copyDownloadLink.label;"
accesskey="&cmd.copyDownloadLink.accesskey;"/>
<menuseparator/>
<menuitem command="downloadsCmd_clearDownloads"
label="&cmd.clearDownloads.label;"
accesskey="&cmd.clearDownloads.accesskey;"/>
</menupopup>
</overlay>

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/. */
#downloadsListEmptyDescription {
display: none;
}
#downloadsRichListBox:empty + #downloadsListEmptyDescription {
display: -moz-box;
}

View file

@ -0,0 +1,15 @@
/* 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/. */
Components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
let ContentAreaDownloadsView = {
init: function CADV_init() {
let view = new DownloadsPlacesView(document.getElementById("downloadsRichListBox"));
// Do not display the Places downloads in private windows
if (!PrivateBrowsingUtils.isWindowPrivate(window)) {
view.place = "place:transition=7&sort=4";
}
}
};

View file

@ -0,0 +1,45 @@
<?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/"?>
<?xml-stylesheet href="chrome://browser/content/downloads/contentAreaDownloadsView.css"?>
<?xml-stylesheet href="chrome://browser/skin/downloads/contentAreaDownloadsView.css"?>
<?xul-overlay href="chrome://browser/content/downloads/allDownloadsViewOverlay.xul"?>
<?xul-overlay href="chrome://global/content/editMenuOverlay.xul"?>
<!DOCTYPE window [
<!ENTITY % downloadsDTD SYSTEM "chrome://browser/locale/downloads/downloads.dtd">
%downloadsDTD;
]>
<window id="contentAreaDownloadsView"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
title="&downloads.title;"
onload="ContentAreaDownloadsView.init();">
<script type="application/javascript"
src="chrome://global/content/globalOverlay.js"/>
<script type="application/javascript"
src="chrome://browser/content/downloads/contentAreaDownloadsView.js"/>
<commandset id="editMenuCommands"/>
<keyset id="editMenuKeys">
#ifdef XP_MACOSX
<key id="key_delete2" keycode="VK_BACK" command="cmd_delete"/>
#endif
</keyset>
<stack flex="1">
<richlistbox id="downloadsRichListBox"/>
<description id="downloadsListEmptyDescription"
value="&downloadsListEmpty.label;"/>
</stack>
<commandset id="downloadCommands"/>
<menupopup id="downloadsContextMenu"/>
</window>

View file

@ -0,0 +1,45 @@
/* 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.download button {
/* These buttons should never get focus, as that would "disable"
the downloads view controller (it's only used when the richlistbox
is focused). */
-moz-user-focus: none;
}
/*** Visibility of controls inside download items ***/
.download-state:-moz-any( [state="6"], /* Blocked (parental) */
[state="8"], /* Blocked (dirty) */
[state="9"]) /* Blocked (policy) */
> .downloadTypeIcon:not(.blockedIcon),
.download-state:not(:-moz-any([state="6"], /* Blocked (parental) */
[state="8"], /* Blocked (dirty) */
[state="9"]) /* Blocked (policy) */)
> .downloadTypeIcon.blockedIcon,
.download-state:not(:-moz-any([state="-1"],/* Starting (initial) */
[state="5"], /* Starting (queued) */
[state="0"], /* Downloading */
[state="4"], /* Paused */
[state="7"]) /* Scanning */)
> vbox > .downloadProgress,
.download-state:not(:-moz-any([state="-1"],/* Starting (initial) */
[state="5"], /* Starting (queued) */
[state="0"], /* Downloading */
[state="4"]) /* Paused */)
> .downloadCancel,
.download-state[state]:not(:-moz-any([state="2"], /* Failed */
[state="3"]) /* Canceled */)
> .downloadRetry,
.download-state:not( [state="1"] /* Finished */)
> .downloadShow
{
display: none;
}

View file

@ -0,0 +1,188 @@
<?xml version="1.0"?>
<!-- -*- Mode: HTML; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- -->
<!-- vim: set ts=2 et sw=2 tw=80: -->
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this file,
- You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!DOCTYPE bindings SYSTEM "chrome://browser/locale/downloads/downloads.dtd">
<bindings id="downloadBindings"
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="download"
extends="chrome://global/content/bindings/richlistbox.xml#richlistitem">
<content orient="horizontal"
align="center"
onclick="DownloadsView.onDownloadClick(event);">
<xul:image class="downloadTypeIcon"
validate="always"
xbl:inherits="src=image"/>
<xul:image class="downloadTypeIcon blockedIcon"/>
<xul:vbox pack="center"
flex="1"
class="downloadContainer"
style="width: &downloadDetails.width;">
<!-- We're letting localizers put a min-width in here primarily
because of the downloads summary at the bottom of the list of
download items. An element in the summary has the same min-width
on a description, and we don't want the panel to change size if the
summary isn't being displayed, so we ensure that items share the
same minimum width.
-->
<xul:description class="downloadDisplayName"
crop="center"
style="min-width: &downloadsSummary.minWidth2;"
xbl:inherits="value=displayName,tooltiptext=displayName"/>
<xul:progressmeter anonid="progressmeter"
class="downloadProgress"
min="0"
max="100"
xbl:inherits="mode=progressmode,value=progress"/>
<xul:description class="downloadDetails"
crop="end"
xbl:inherits="value=status,tooltiptext=statusTip"/>
</xul:vbox>
<xul:stack>
<xul:button class="downloadButton downloadCancel"
tooltiptext="&cmd.cancel.label;"
oncommand="DownloadsView.onDownloadCommand(event, 'downloadsCmd_cancel');"/>
<xul:button class="downloadButton downloadRetry"
tooltiptext="&cmd.retry.label;"
oncommand="DownloadsView.onDownloadCommand(event, 'downloadsCmd_retry');"/>
<xul:button class="downloadButton downloadShow"
#ifdef XP_MACOSX
tooltiptext="&cmd.showMac.label;"
#else
tooltiptext="&cmd.show.label;"
#endif
oncommand="DownloadsView.onDownloadCommand(event, 'downloadsCmd_show');"/>
</xul:stack>
</content>
</binding>
<binding id="download-in-progress"
extends="chrome://global/content/bindings/richlistbox.xml#richlistitem">
<content orient="horizontal"
align="center"
onclick="DownloadsView.onDownloadClick(event);">
<xul:image class="downloadTypeIcon"
validate="always"
xbl:inherits="src=image"/>
<xul:image class="downloadTypeIcon blockedIcon"/>
<xul:vbox pack="center"
flex="1"
class="downloadContainer"
style="width: &downloadDetails.width;">
<xul:description class="downloadDisplayName"
crop="center"
style="min-width: &downloadsSummary.minWidth2;"
xbl:inherits="value=displayName,tooltiptext=extendedDisplayNameTip"/>
<xul:progressmeter anonid="progressmeter"
class="downloadProgress"
min="0"
max="100"
xbl:inherits="mode=progressmode,value=progress"/>
<xul:description class="downloadDetails"
crop="end"
xbl:inherits="value=status,tooltiptext=statusTip"/>
</xul:vbox>
<xul:stack>
<xul:button class="downloadButton downloadCancel"
tooltiptext="&cmd.cancel.label;"
oncommand="DownloadsView.onDownloadCommand(event, 'downloadsCmd_cancel');"/>
<xul:button class="downloadButton downloadRetry"
tooltiptext="&cmd.retry.label;"
oncommand="DownloadsView.onDownloadCommand(event, 'downloadsCmd_retry');"/>
<xul:button class="downloadButton downloadShow"
tooltiptext="&cmd.show.label;"
oncommand="DownloadsView.onDownloadCommand(event, 'downloadsCmd_show');"/>
</xul:stack>
</content>
</binding>
<binding id="download-full-ui"
extends="chrome://global/content/bindings/richlistbox.xml#richlistitem">
<resources>
<stylesheet src="chrome://browser/content/downloads/download.css"/>
</resources>
<content orient="horizontal" align="center">
<xul:image class="downloadTypeIcon"
validate="always"
xbl:inherits="src=image"/>
<xul:image class="downloadTypeIcon blockedIcon"/>
<xul:vbox pack="center" flex="1">
<xul:description class="downloadDisplayName"
crop="center"
xbl:inherits="value=displayName,tooltiptext=displayName"/>
<xul:progressmeter anonid="progressmeter"
class="downloadProgress"
min="0"
max="100"
xbl:inherits="mode=progressmode,value=progress"/>
<xul:description class="downloadDetails"
style="width: &downloadDetails.width;"
crop="end"
xbl:inherits="value=status,tooltiptext=statusTip"/>
</xul:vbox>
<xul:button class="downloadButton downloadCancel"
tooltiptext="&cmd.cancel.label;"
oncommand="goDoCommand('downloadsCmd_cancel')"/>
<xul:button class="downloadButton downloadRetry"
tooltiptext="&cmd.retry.label;"
oncommand="goDoCommand('downloadsCmd_retry')"/>
<xul:button class="downloadButton downloadShow"
#ifdef XP_MACOSX
tooltiptext="&cmd.showMac.label;"
#else
tooltiptext="&cmd.show.label;"
#endif
oncommand="goDoCommand('downloadsCmd_show')"/>
</content>
</binding>
<binding id="download-in-progress-full-ui"
extends="chrome://global/content/bindings/richlistbox.xml#richlistitem">
<resources>
<stylesheet src="chrome://browser/content/downloads/download.css"/>
</resources>
<content orient="horizontal" align="center">
<xul:image class="downloadTypeIcon"
validate="always"
xbl:inherits="src=image"/>
<xul:image class="downloadTypeIcon blockedIcon"/>
<xul:vbox pack="center" flex="1">
<xul:description class="downloadDisplayName"
crop="end"
xbl:inherits="value=extendedDisplayName,tooltiptext=extendedDisplayNameTip"/>
<xul:progressmeter anonid="progressmeter"
class="downloadProgress"
min="0"
max="100"
xbl:inherits="mode=progressmode,value=progress"/>
<xul:description class="downloadDetails"
style="width: &downloadDetails.width;"
crop="end"
xbl:inherits="value=status,tooltiptext=statusTip"/>
</xul:vbox>
<xul:button class="downloadButton downloadCancel"
tooltiptext="&cmd.cancel.label;"
oncommand="goDoCommand('downloadsCmd_cancel')"/>
<xul:button class="downloadButton downloadRetry"
tooltiptext="&cmd.retry.label;"
oncommand="goDoCommand('downloadsCmd_retry')"/>
<xul:button class="downloadButton downloadShow"
tooltiptext="&cmd.show.label;"
oncommand="goDoCommand('downloadsCmd_show')"/>
</content>
</binding>
</bindings>

View file

@ -0,0 +1,132 @@
/* 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/. */
/*** Download items ***/
richlistitem[type="download"] {
-moz-binding: url('chrome://browser/content/downloads/download.xml#download');
}
richlistitem[type="download"]:-moz-any([state="-1"],/* Starting (initial) */
[state="0"], /* Downloading */
[state="4"], /* Paused */
[state="5"], /* Starting (queued) */
[state="7"]) /* Scanning */
{
-moz-binding: url('chrome://browser/content/downloads/download.xml#download-in-progress');
}
richlistitem[type="download"]:not([selected]) button {
/* Only focus buttons in the selected item. */
-moz-user-focus: none;
}
/*** Visibility of controls inside download items ***/
.download-state:-moz-any( [state="6"], /* Blocked (parental) */
[state="8"], /* Blocked (dirty) */
[state="9"]) /* Blocked (policy) */
.downloadTypeIcon:not(.blockedIcon),
.download-state:not(:-moz-any([state="6"], /* Blocked (parental) */
[state="8"], /* Blocked (dirty) */
[state="9"]) /* Blocked (policy) */)
.downloadTypeIcon.blockedIcon,
.download-state:not(:-moz-any([state="-1"],/* Starting (initial) */
[state="0"], /* Downloading */
[state="4"], /* Paused */
[state="5"], /* Starting (queued) */
[state="7"]) /* Scanning */)
.downloadProgress,
.download-state:not( [state="0"] /* Downloading */)
.downloadPauseMenuItem,
.download-state:not( [state="4"] /* Paused */)
.downloadResumeMenuItem,
.download-state:not(:-moz-any([state="2"], /* Failed */
[state="4"]) /* Paused */)
.downloadCancelMenuItem,
.download-state:not(:-moz-any([state="1"], /* Finished */
[state="2"], /* Failed */
[state="3"], /* Canceled */
[state="6"], /* Blocked (parental) */
[state="8"], /* Blocked (dirty) */
[state="9"]) /* Blocked (policy) */)
.downloadRemoveFromHistoryMenuItem,
.download-state:not(:-moz-any([state="-1"],/* Starting (initial) */
[state="0"], /* Downloading */
[state="1"], /* Finished */
[state="4"], /* Paused */
[state="5"]) /* Starting (queued) */)
.downloadShowMenuItem,
.download-state[state="7"] /* Scanning */ .downloadCommandsSeparator
{
display: none;
}
/*** Visibility of download buttons and indicator controls. ***/
.download-state:not(:-moz-any([state="-1"],/* Starting (initial) */
[state="0"], /* Downloading */
[state="4"], /* Paused */
[state="5"]) /* Starting (queued) */)
.downloadCancel,
.download-state:not(:-moz-any([state="2"], /* Failed */
[state="3"]) /* Canceled */)
.downloadRetry,
.download-state:not( [state="1"] /* Finished */)
.downloadShow,
#downloads-indicator:-moz-any([progress],
[counter],
[paused]) #downloads-indicator-icon,
#downloads-indicator:not(:-moz-any([progress],
[counter],
[paused]))
#downloads-indicator-progress-area
{
visibility: hidden;
}
.download-state[state="1"]:not([exists]) .downloadShow
{
display: none;
}
#downloadsSummary:not([inprogress]) > vbox > #downloadsSummaryProgress,
#downloadsSummary:not([inprogress]) > vbox > #downloadsSummaryDetails,
#downloadsFooter[showingsummary] > #downloadsHistory,
#downloadsFooter:not([showingsummary]) > #downloadsSummary
{
display: none;
}
/* Hacks for toolbar full and text modes, until bug 573329 removes them */
toolbar[mode="text"] > #downloads-indicator {
display: -moz-box;
-moz-box-orient: vertical;
-moz-box-pack: center;
}
toolbar[mode="text"] > #downloads-indicator > .toolbarbutton-text {
-moz-box-ordinal-group: 1;
}
toolbar[mode="text"] > #downloads-indicator > .toolbarbutton-icon {
display: -moz-box;
-moz-box-ordinal-group: 2;
visibility: collapse;
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,142 @@
<?xml version="1.0"?>
# -*- Mode: HTML; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
# vim: set ts=2 et sw=2 tw=80:
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
# You can obtain one at http://mozilla.org/MPL/2.0/.
<?xml-stylesheet href="chrome://browser/content/downloads/downloads.css"?>
<?xml-stylesheet href="chrome://browser/skin/downloads/downloads.css"?>
<!DOCTYPE overlay SYSTEM "chrome://browser/locale/downloads/downloads.dtd">
<overlay xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
id="downloadsOverlay">
<commandset>
<command id="downloadsCmd_doDefault"
oncommand="goDoCommand('downloadsCmd_doDefault')"/>
<command id="downloadsCmd_pauseResume"
oncommand="goDoCommand('downloadsCmd_pauseResume')"/>
<command id="downloadsCmd_cancel"
oncommand="goDoCommand('downloadsCmd_cancel')"/>
<command id="downloadsCmd_open"
oncommand="goDoCommand('downloadsCmd_open')"/>
<command id="downloadsCmd_show"
oncommand="goDoCommand('downloadsCmd_show')"/>
<command id="downloadsCmd_retry"
oncommand="goDoCommand('downloadsCmd_retry')"/>
<command id="downloadsCmd_openReferrer"
oncommand="goDoCommand('downloadsCmd_openReferrer')"/>
<command id="downloadsCmd_copyLocation"
oncommand="goDoCommand('downloadsCmd_copyLocation')"/>
<command id="downloadsCmd_clearList"
oncommand="goDoCommand('downloadsCmd_clearList')"/>
</commandset>
<popupset>
<!-- The panel has level="top" to ensure that it is never hidden by the
taskbar on Windows. See bug 672365. For accessibility to screen
readers, we use a label on the panel instead of the anchor because the
panel can also be displayed without an anchor. -->
<panel id="downloadsPanel"
aria-label="&downloads.title;"
role="group"
type="arrow"
orient="vertical"
level="top"
consumeoutsideclicks="true"
onpopupshown="DownloadsPanel.onPopupShown(event);"
onpopuphidden="DownloadsPanel.onPopupHidden(event);">
<!-- The following popup menu should be a child of the panel element,
otherwise flickering may occur when the cursor is moved over the area
of a disabled menu item that overlaps the panel. See bug 492960. -->
<menupopup id="downloadsContextMenu"
class="download-state">
<menuitem command="downloadsCmd_pauseResume"
class="downloadPauseMenuItem"
label="&cmd.pause.label;"
accesskey="&cmd.pause.accesskey;"/>
<menuitem command="downloadsCmd_pauseResume"
class="downloadResumeMenuItem"
label="&cmd.resume.label;"
accesskey="&cmd.resume.accesskey;"/>
<menuitem command="downloadsCmd_cancel"
class="downloadCancelMenuItem"
label="&cmd.cancel.label;"
accesskey="&cmd.cancel.accesskey;"/>
<menuitem command="cmd_delete"
class="downloadRemoveFromHistoryMenuItem"
label="&cmd.removeFromHistory.label;"
accesskey="&cmd.removeFromHistory.accesskey;"/>
<menuitem command="downloadsCmd_show"
class="downloadShowMenuItem"
#ifdef XP_MACOSX
label="&cmd.showMac.label;"
accesskey="&cmd.showMac.accesskey;"
#else
label="&cmd.show.label;"
accesskey="&cmd.show.accesskey;"
#endif
/>
<menuseparator class="downloadCommandsSeparator"/>
<menuitem command="downloadsCmd_openReferrer"
label="&cmd.goToDownloadPage.label;"
accesskey="&cmd.goToDownloadPage.accesskey;"/>
<menuitem command="downloadsCmd_copyLocation"
label="&cmd.copyDownloadLink.label;"
accesskey="&cmd.copyDownloadLink.accesskey;"/>
<menuseparator/>
<menuitem command="downloadsCmd_clearList"
label="&cmd.clearList.label;"
accesskey="&cmd.clearList.accesskey;"/>
</menupopup>
<richlistbox id="downloadsListBox"
class="plain"
flex="1"
context="downloadsContextMenu"
onmouseover="DownloadsView.onDownloadMouseOver(event);"
onmouseout="DownloadsView.onDownloadMouseOut(event);"
oncontextmenu="DownloadsView.onDownloadContextMenu(event);"
ondragstart="DownloadsView.onDownloadDragStart(event);"/>
<description id="emptyDownloads"
mousethrough="always">
&downloadsPanelEmpty.label;
</description>
<vbox id="downloadsFooter">
<hbox id="downloadsSummary"
align="center"
orient="horizontal"
onkeydown="DownloadsSummary.onKeyDown(event);"
onclick="DownloadsSummary.onClick(event);">
<image class="downloadTypeIcon" />
<vbox>
<description id="downloadsSummaryDescription"
style="min-width: &downloadsSummary.minWidth2;"/>
<progressmeter id="downloadsSummaryProgress"
class="downloadProgress"
min="0"
max="100"
mode="normal" />
<description id="downloadsSummaryDetails"
style="width: &downloadDetails.width;"
crop="end"/>
</vbox>
</hbox>
<button id="downloadsHistory"
class="plain"
label="&downloadsHistory.label;"
accesskey="&downloadsHistory.accesskey;"
oncommand="DownloadsPanel.showDownloadsHistory();"/>
</vbox>
</panel>
</popupset>
</overlay>

View file

@ -0,0 +1,594 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* Handles the indicator that displays the progress of ongoing downloads, which
* is also used as the anchor for the downloads panel.
*
* This module includes the following constructors and global objects:
*
* DownloadsButton
* Main entry point for the downloads indicator. Depending on how the toolbars
* have been customized, this object determines if we should show a fully
* functional indicator, a placeholder used during customization and in the
* customization palette, or a neutral view as a temporary anchor for the
* downloads panel.
*
* DownloadsIndicatorView
* Builds and updates the actual downloads status widget, responding to changes
* in the global status data, or provides a neutral view if the indicator is
* removed from the toolbars and only used as a temporary anchor. In addition,
* handles the user interaction events raised by the widget.
*/
"use strict";
////////////////////////////////////////////////////////////////////////////////
//// DownloadsButton
/**
* Main entry point for the downloads indicator. Depending on how the toolbars
* have been customized, this object determines if we should show a fully
* functional indicator, a placeholder used during customization and in the
* customization palette, or a neutral view as a temporary anchor for the
* downloads panel.
*/
const DownloadsButton = {
/**
* Location of the indicator overlay.
*/
get kIndicatorOverlay()
"chrome://browser/content/downloads/indicatorOverlay.xul",
/**
* Returns a reference to the downloads button position placeholder, or null
* if not available because it has been removed from the toolbars.
*/
get _placeholder()
{
return document.getElementById("downloads-button");
},
/**
* This function is called asynchronously just after window initialization.
*
* NOTE: This function should limit the input/output it performs to improve
* startup time, and in particular should not cause the Download Manager
* service to start.
*/
initializeIndicator: function DB_initializeIndicator()
{
this._update();
},
/**
* Indicates whether toolbar customization is in progress.
*/
_customizing: false,
/**
* This function is called when toolbar customization starts.
*
* During customization, we never show the actual download progress indication
* or the event notifications, but we show a neutral placeholder. The neutral
* placeholder is an ordinary button defined in the browser window that can be
* moved freely between the toolbars and the customization palette.
*/
customizeStart: function DB_customizeStart()
{
// Hide the indicator and prevent it to be displayed as a temporary anchor
// during customization, even if requested using the getAnchor method.
this._customizing = true;
this._anchorRequested = false;
let indicator = DownloadsIndicatorView.indicator;
if (indicator) {
indicator.collapsed = true;
}
let placeholder = this._placeholder;
if (placeholder) {
placeholder.collapsed = false;
}
},
/**
* This function is called when toolbar customization ends.
*/
customizeDone: function DB_customizeDone()
{
this._customizing = false;
this._update();
},
/**
* This function is called during initialization or when toolbar customization
* ends. It determines if we should enable or disable the object that keeps
* the indicator updated, and ensures that the placeholder is hidden unless it
* has been moved to the customization palette.
*
* NOTE: This function is also called on startup, thus it should limit the
* input/output it performs, and in particular should not cause the
* Download Manager service to start.
*/
_update: function DB_update() {
this._updatePositionInternal();
if (!DownloadsCommon.useToolkitUI) {
DownloadsIndicatorView.ensureInitialized();
} else {
DownloadsIndicatorView.ensureTerminated();
}
},
/**
* Determines the position where the indicator should appear, and moves its
* associated element to the new position. This does not happen if the
* indicator is currently being used as the anchor for the panel, to ensure
* that the panel doesn't flicker because we move the DOM element to which
* it's anchored.
*/
updatePosition: function DB_updatePosition()
{
if (!this._anchorRequested) {
this._updatePositionInternal();
}
},
/**
* Determines the position where the indicator should appear, and moves its
* associated element to the new position.
*
* @return Anchor element, or null if the indicator is not visible.
*/
_updatePositionInternal: function DB_updatePositionInternal()
{
let indicator = DownloadsIndicatorView.indicator;
if (!indicator) {
// Exit now if the indicator overlay isn't loaded yet.
return null;
}
let placeholder = this._placeholder;
if (!placeholder) {
// The placeholder has been removed from the browser window.
indicator.collapsed = true;
// Move the indicator to a safe position on the toolbar, since otherwise
// it may break the merge of adjacent items, like back/forward + urlbar.
indicator.parentNode.appendChild(indicator);
return null;
}
// Position the indicator where the placeholder is located. We should
// update the position even if the placeholder is located on an invisible
// toolbar, because the toolbar may be displayed later.
placeholder.parentNode.insertBefore(indicator, placeholder);
placeholder.collapsed = true;
indicator.collapsed = false;
indicator.open = this._anchorRequested;
// Determine if the placeholder is located on an invisible toolbar.
if (!isElementVisible(placeholder.parentNode)) {
return null;
}
return DownloadsIndicatorView.indicatorAnchor;
},
/**
* Checks whether the indicator is, or will soon be visible in the browser
* window.
*
* @param aCallback
* Called once the indicator overlay has loaded. Gets a boolean
* argument representing the indicator visibility.
*/
checkIsVisible: function DB_checkIsVisible(aCallback)
{
function DB_CEV_callback() {
if (!this._placeholder) {
aCallback(false);
} else {
let element = DownloadsIndicatorView.indicator || this._placeholder;
aCallback(isElementVisible(element.parentNode));
}
}
DownloadsOverlayLoader.ensureOverlayLoaded(this.kIndicatorOverlay,
DB_CEV_callback.bind(this));
},
/**
* Indicates whether we should try and show the indicator temporarily as an
* anchor for the panel, even if the indicator would be hidden by default.
*/
_anchorRequested: false,
/**
* Ensures that there is an anchor available for the panel.
*
* @param aCallback
* Called when the anchor is available, passing the element where the
* panel should be anchored, or null if an anchor is not available (for
* example because both the tab bar and the navigation bar are hidden).
*/
getAnchor: function DB_getAnchor(aCallback)
{
// Do not allow anchoring the panel to the element while customizing.
if (this._customizing) {
aCallback(null);
return;
}
function DB_GA_callback() {
this._anchorRequested = true;
aCallback(this._updatePositionInternal());
}
DownloadsOverlayLoader.ensureOverlayLoaded(this.kIndicatorOverlay,
DB_GA_callback.bind(this));
},
/**
* Allows the temporary anchor to be hidden.
*/
releaseAnchor: function DB_releaseAnchor()
{
this._anchorRequested = false;
this._updatePositionInternal();
},
get _tabsToolbar()
{
delete this._tabsToolbar;
return this._tabsToolbar = document.getElementById("TabsToolbar");
},
get _navBar()
{
delete this._navBar;
return this._navBar = document.getElementById("nav-bar");
}
};
////////////////////////////////////////////////////////////////////////////////
//// DownloadsIndicatorView
/**
* Builds and updates the actual downloads status widget, responding to changes
* in the global status data, or provides a neutral view if the indicator is
* removed from the toolbars and only used as a temporary anchor. In addition,
* handles the user interaction events raised by the widget.
*/
const DownloadsIndicatorView = {
/**
* True when the view is connected with the underlying downloads data.
*/
_initialized: false,
/**
* True when the user interface elements required to display the indicator
* have finished loading in the browser window, and can be referenced.
*/
_operational: false,
/**
* Prepares the downloads indicator to be displayed.
*/
ensureInitialized: function DIV_ensureInitialized()
{
if (this._initialized) {
return;
}
this._initialized = true;
window.addEventListener("unload", this.onWindowUnload, false);
DownloadsCommon.getIndicatorData(window).addView(this);
},
/**
* Frees the internal resources related to the indicator.
*/
ensureTerminated: function DIV_ensureTerminated()
{
if (!this._initialized) {
return;
}
this._initialized = false;
window.removeEventListener("unload", this.onWindowUnload, false);
DownloadsCommon.getIndicatorData(window).removeView(this);
// Reset the view properties, so that a neutral indicator is displayed if we
// are visible only temporarily as an anchor.
this.counter = "";
this.percentComplete = 0;
this.paused = false;
this.attention = false;
},
/**
* Ensures that the user interface elements required to display the indicator
* are loaded, then invokes the given callback.
*/
_ensureOperational: function DIV_ensureOperational(aCallback)
{
if (this._operational) {
aCallback();
return;
}
function DIV_EO_callback() {
this._operational = true;
// If the view is initialized, we need to update the elements now that
// they are finally available in the document.
if (this._initialized) {
DownloadsCommon.getIndicatorData(window).refreshView(this);
}
aCallback();
}
DownloadsOverlayLoader.ensureOverlayLoaded(
DownloadsButton.kIndicatorOverlay,
DIV_EO_callback.bind(this));
},
//////////////////////////////////////////////////////////////////////////////
//// Direct control functions
/**
* Set while we are waiting for a notification to fade out.
*/
_notificationTimeout: null,
/**
* If the status indicator is visible in its assigned position, shows for a
* brief time a visual notification of a relevant event, like a new download.
*
* @param aType
* Set to "start" for new downloads, "finish" for completed downloads.
*/
showEventNotification: function DIV_showEventNotification(aType)
{
if (!this._initialized) {
return;
}
if (!DownloadsCommon.animateNotifications) {
return;
}
// No need to show visual notification if the panel is visible.
if (DownloadsPanel.isPanelShowing) {
return;
}
function DIV_SEN_callback() {
if (this._notificationTimeout) {
clearTimeout(this._notificationTimeout);
}
// Now that the overlay is loaded, place the indicator in its final
// position.
DownloadsButton.updatePosition();
let indicator = this.indicator;
indicator.setAttribute("notification", aType);
this._notificationTimeout = setTimeout(
function () indicator.removeAttribute("notification"), 1000);
}
this._ensureOperational(DIV_SEN_callback.bind(this));
},
//////////////////////////////////////////////////////////////////////////////
//// Callback functions from DownloadsIndicatorData
/**
* Indicates whether the indicator should be shown because there are some
* downloads to be displayed.
*/
set hasDownloads(aValue)
{
if (this._hasDownloads != aValue) {
this._hasDownloads = aValue;
// If there is at least one download, ensure that the view elements are
// loaded before determining the position of the downloads button.
if (aValue) {
this._ensureOperational(function() DownloadsButton.updatePosition());
} else {
DownloadsButton.updatePosition();
}
}
return aValue;
},
get hasDownloads()
{
return this._hasDownloads;
},
_hasDownloads: false,
/**
* Status text displayed in the indicator. If this is set to an empty value,
* then the small downloads icon is displayed instead of the text.
*/
set counter(aValue)
{
if (!this._operational) {
return this._counter;
}
if (this._counter !== aValue) {
this._counter = aValue;
if (this._counter)
this.indicator.setAttribute("counter", "true");
else
this.indicator.removeAttribute("counter");
// We have to set the attribute instead of using the property because the
// XBL binding isn't applied if the element is invisible for any reason.
this._indicatorCounter.setAttribute("value", aValue);
}
return aValue;
},
_counter: null,
/**
* Progress indication to display, from 0 to 100, or -1 if unknown. The
* progress bar is hidden if the current progress is unknown and no status
* text is set in the "counter" property.
*/
set percentComplete(aValue)
{
if (!this._operational) {
return this._percentComplete;
}
if (this._percentComplete !== aValue) {
this._percentComplete = aValue;
if (this._percentComplete >= 0)
this.indicator.setAttribute("progress", "true");
else
this.indicator.removeAttribute("progress");
// We have to set the attribute instead of using the property because the
// XBL binding isn't applied if the element is invisible for any reason.
this._indicatorProgress.setAttribute("value", Math.max(aValue, 0));
}
return aValue;
},
_percentComplete: null,
/**
* Indicates whether the progress won't advance because of a paused state.
* Setting this property forces a paused progress bar to be displayed, even if
* the current progress information is unavailable.
*/
set paused(aValue)
{
if (!this._operational) {
return this._paused;
}
if (this._paused != aValue) {
this._paused = aValue;
if (this._paused) {
this.indicator.setAttribute("paused", "true")
} else {
this.indicator.removeAttribute("paused");
}
}
return aValue;
},
_paused: false,
/**
* Set when the indicator should draw user attention to itself.
*/
set attention(aValue)
{
if (!this._operational) {
return this._attention;
}
if (this._attention != aValue) {
this._attention = aValue;
if (aValue) {
this.indicator.setAttribute("attention", "true");
} else {
this.indicator.removeAttribute("attention");
}
}
return aValue;
},
_attention: false,
//////////////////////////////////////////////////////////////////////////////
//// User interface event functions
onWindowUnload: function DIV_onWindowUnload()
{
// This function is registered as an event listener, we can't use "this".
DownloadsIndicatorView.ensureTerminated();
},
onCommand: function DIV_onCommand(aEvent)
{
if (DownloadsCommon.useToolkitUI) {
// The panel won't suppress attention for us, we need to clear now.
DownloadsCommon.getIndicatorData(window).attention = false;
BrowserDownloadsUI();
} else {
DownloadsPanel.showPanel();
}
aEvent.stopPropagation();
},
onDragOver: function DIV_onDragOver(aEvent)
{
browserDragAndDrop.dragOver(aEvent);
},
onDrop: function DIV_onDrop(aEvent)
{
let dt = aEvent.dataTransfer;
// If dragged item is from our source, do not try to
// redownload already downloaded file.
if (dt.mozGetDataAt("application/x-moz-file", 0))
return;
let name = {};
let url = browserDragAndDrop.drop(aEvent, name);
if (url) {
if (url.startsWith("about:")) {
return;
}
let sourceDoc = dt.mozSourceNode ? dt.mozSourceNode.ownerDocument : document;
saveURL(url, name.value, null, true, true, null, sourceDoc);
aEvent.preventDefault();
}
},
/**
* Returns a reference to the main indicator element, or null if the element
* is not present in the browser window yet.
*/
get indicator()
{
let indicator = document.getElementById("downloads-indicator");
if (!indicator) {
return null;
}
// Once the element is loaded, it will never be unloaded.
delete this.indicator;
return this.indicator = indicator;
},
get indicatorAnchor()
{
delete this.indicatorAnchor;
return this.indicatorAnchor =
document.getElementById("downloads-indicator-anchor");
},
get _indicatorCounter()
{
delete this._indicatorCounter;
return this._indicatorCounter =
document.getElementById("downloads-indicator-counter");
},
get _indicatorProgress()
{
delete this._indicatorProgress;
return this._indicatorProgress =
document.getElementById("downloads-indicator-progress");
}
};

View file

@ -0,0 +1,60 @@
<?xml version="1.0"?>
<!-- -*- Mode: HTML; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- -->
<!-- vim: set ts=2 et sw=2 tw=80: -->
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this file,
- You can obtain one at http://mozilla.org/MPL/2.0/. -->
<?xml-stylesheet href="chrome://browser/content/downloads/downloads.css"?>
<?xml-stylesheet href="chrome://browser/skin/downloads/downloads.css"?>
<!DOCTYPE overlay [
<!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd" >
%browserDTD;
<!ENTITY % downloadsDTD SYSTEM "chrome://browser/locale/downloads/downloads.dtd" >
%downloadsDTD;
]>
<overlay xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
id="indicatorOverlay">
<popupset>
<!-- The downloads indicator is placed in its final toolbar location
programmatically, and can be shown temporarily even when its
placeholder is removed from the toolbars. Its initial location within
the document must not be a toolbar or the toolbar palette, otherwise the
toolbar handling code could remove it from the document. -->
<toolbarbutton id="downloads-indicator"
class="toolbarbutton-1 chromeclass-toolbar-additional"
tooltiptext="&downloads.tooltip;"
collapsed="true"
oncommand="DownloadsIndicatorView.onCommand(event);"
ondrop="DownloadsIndicatorView.onDrop(event);"
ondragover="DownloadsIndicatorView.onDragOver(event);"
ondragenter="DownloadsIndicatorView.onDragOver(event);"
ondragleave="DownloadsIndicatorView.onDragLeave(event);"
skipintoolbarset="true">
<!-- The panel's anchor area is smaller than the outer button, but must
always be visible and must not move or resize when the indicator
state changes, otherwise the panel could change its position or lose
its arrow unexpectedly. -->
<stack id="downloads-indicator-anchor"
class="toolbarbutton-icon">
<vbox id="downloads-indicator-progress-area"
pack="center">
<description id="downloads-indicator-counter"/>
<progressmeter id="downloads-indicator-progress"
class="plain"
min="0"
max="100"/>
</vbox>
<vbox id="downloads-indicator-icon"/>
<vbox id="downloads-indicator-notification"/>
</stack>
<label class="toolbarbutton-text" crop="right" flex="1"
value="&downloads.label;"/>
</toolbarbutton>
</popupset>
</overlay>

View file

@ -0,0 +1,18 @@
# 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/downloads/download.xml (content/download.xml)
content/browser/downloads/download.css (content/download.css)
content/browser/downloads/downloads.css (content/downloads.css)
* content/browser/downloads/downloads.js (content/downloads.js)
* content/browser/downloads/downloadsOverlay.xul (content/downloadsOverlay.xul)
content/browser/downloads/indicator.js (content/indicator.js)
content/browser/downloads/indicatorOverlay.xul (content/indicatorOverlay.xul)
* content/browser/downloads/allDownloadsViewOverlay.xul (content/allDownloadsViewOverlay.xul)
content/browser/downloads/allDownloadsViewOverlay.js (content/allDownloadsViewOverlay.js)
content/browser/downloads/allDownloadsViewOverlay.css (content/allDownloadsViewOverlay.css)
* content/browser/downloads/contentAreaDownloadsView.xul (content/contentAreaDownloadsView.xul)
content/browser/downloads/contentAreaDownloadsView.js (content/contentAreaDownloadsView.js)
content/browser/downloads/contentAreaDownloadsView.css (content/contentAreaDownloadsView.css)

View file

@ -0,0 +1,19 @@
# -*- 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/.
JAR_MANIFESTS += ['jar.mn']
EXTRA_COMPONENTS += [
'BrowserDownloads.manifest',
'DownloadsStartup.js',
'DownloadsUI.js',
]
EXTRA_JS_MODULES += [
'DownloadsCommon.jsm',
'DownloadsLogger.jsm',
'DownloadsTaskbar.jsm',
]

View file

@ -0,0 +1,28 @@
# WebappRT doesn't need these instructions, and they don't necessarily work
# with it, but it does use a GRE directory that the GRE shares with Firefox,
# so in order to prevent the instructions from being processed for WebappRT,
# we need to restrict them to the applications that depend on them, i.e.:
#
# b2g: {3c2e2abc-06d4-11e1-ac3b-374f68613e61}
# browser: {8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}
# mobile/android: {aa3c5121-dab2-40e2-81ca-7ea25febc110}
# mobile/xul: {a23983c0-fd0e-11dc-95ff-0800200c9a66}
#
# In theory we should do this for all these instructions, but in practice it is
# sufficient to do it for the app-startup one, and the file is simpler that way.
component {229fa115-9412-4d32-baf3-2fc407f76fb1} FeedConverter.js
contract @mozilla.org/streamconv;1?from=application/vnd.mozilla.maybe.feed&to=*/* {229fa115-9412-4d32-baf3-2fc407f76fb1}
contract @mozilla.org/streamconv;1?from=application/vnd.mozilla.maybe.video.feed&to=*/* {229fa115-9412-4d32-baf3-2fc407f76fb1}
contract @mozilla.org/streamconv;1?from=application/vnd.mozilla.maybe.audio.feed&to=*/* {229fa115-9412-4d32-baf3-2fc407f76fb1}
component {2376201c-bbc6-472f-9b62-7548040a61c6} FeedConverter.js
contract @mozilla.org/browser/feeds/result-service;1 {2376201c-bbc6-472f-9b62-7548040a61c6}
component {4f91ef2e-57ba-472e-ab7a-b4999e42d6c0} FeedConverter.js
contract @mozilla.org/network/protocol;1?name=feed {4f91ef2e-57ba-472e-ab7a-b4999e42d6c0}
component {1c31ed79-accd-4b94-b517-06e0c81999d5} FeedConverter.js
contract @mozilla.org/network/protocol;1?name=pcast {1c31ed79-accd-4b94-b517-06e0c81999d5}
component {49bb6593-3aff-4eb3-a068-2712c28bd58e} FeedWriter.js
contract @mozilla.org/browser/feeds/result-writer;1 {49bb6593-3aff-4eb3-a068-2712c28bd58e}
component {792a7e82-06a0-437c-af63-b2d12e808acc} WebContentConverter.js
contract @mozilla.org/embeddor.implemented/web-content-handler-registrar;1 {792a7e82-06a0-437c-af63-b2d12e808acc}
category app-startup WebContentConverter service,@mozilla.org/embeddor.implemented/web-content-handler-registrar;1 application={3c2e2abc-06d4-11e1-ac3b-374f68613e61} application={8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4} application={aa3c5121-dab2-40e2-81ca-7ea25febc110} application={a23983c0-fd0e-11dc-95ff-0800200c9a66}

View file

@ -0,0 +1,591 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
Components.utils.import("resource://gre/modules/debug.js");
Components.utils.import("resource://gre/modules/Services.jsm");
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
function LOG(str) {
dump("*** " + str + "\n");
}
const FS_CONTRACTID = "@mozilla.org/browser/feeds/result-service;1";
const FPH_CONTRACTID = "@mozilla.org/network/protocol;1?name=feed";
const PCPH_CONTRACTID = "@mozilla.org/network/protocol;1?name=pcast";
const TYPE_MAYBE_FEED = "application/vnd.mozilla.maybe.feed";
const TYPE_MAYBE_VIDEO_FEED = "application/vnd.mozilla.maybe.video.feed";
const TYPE_MAYBE_AUDIO_FEED = "application/vnd.mozilla.maybe.audio.feed";
const TYPE_ANY = "*/*";
const PREF_SELECTED_APP = "browser.feeds.handlers.application";
const PREF_SELECTED_WEB = "browser.feeds.handlers.webservice";
const PREF_SELECTED_ACTION = "browser.feeds.handler";
const PREF_SELECTED_READER = "browser.feeds.handler.default";
const PREF_VIDEO_SELECTED_APP = "browser.videoFeeds.handlers.application";
const PREF_VIDEO_SELECTED_WEB = "browser.videoFeeds.handlers.webservice";
const PREF_VIDEO_SELECTED_ACTION = "browser.videoFeeds.handler";
const PREF_VIDEO_SELECTED_READER = "browser.videoFeeds.handler.default";
const PREF_AUDIO_SELECTED_APP = "browser.audioFeeds.handlers.application";
const PREF_AUDIO_SELECTED_WEB = "browser.audioFeeds.handlers.webservice";
const PREF_AUDIO_SELECTED_ACTION = "browser.audioFeeds.handler";
const PREF_AUDIO_SELECTED_READER = "browser.audioFeeds.handler.default";
function getPrefAppForType(t) {
switch (t) {
case Ci.nsIFeed.TYPE_VIDEO:
return PREF_VIDEO_SELECTED_APP;
case Ci.nsIFeed.TYPE_AUDIO:
return PREF_AUDIO_SELECTED_APP;
default:
return PREF_SELECTED_APP;
}
}
function getPrefWebForType(t) {
switch (t) {
case Ci.nsIFeed.TYPE_VIDEO:
return PREF_VIDEO_SELECTED_WEB;
case Ci.nsIFeed.TYPE_AUDIO:
return PREF_AUDIO_SELECTED_WEB;
default:
return PREF_SELECTED_WEB;
}
}
function getPrefActionForType(t) {
switch (t) {
case Ci.nsIFeed.TYPE_VIDEO:
return PREF_VIDEO_SELECTED_ACTION;
case Ci.nsIFeed.TYPE_AUDIO:
return PREF_AUDIO_SELECTED_ACTION;
default:
return PREF_SELECTED_ACTION;
}
}
function getPrefReaderForType(t) {
switch (t) {
case Ci.nsIFeed.TYPE_VIDEO:
return PREF_VIDEO_SELECTED_READER;
case Ci.nsIFeed.TYPE_AUDIO:
return PREF_AUDIO_SELECTED_READER;
default:
return PREF_SELECTED_READER;
}
}
function safeGetCharPref(pref, defaultValue) {
var prefs =
Cc["@mozilla.org/preferences-service;1"].
getService(Ci.nsIPrefBranch);
try {
return prefs.getCharPref(pref);
}
catch (e) {
}
return defaultValue;
}
function FeedConverter() {
}
FeedConverter.prototype = {
classID: Components.ID("{229fa115-9412-4d32-baf3-2fc407f76fb1}"),
/**
* This is the downloaded text data for the feed.
*/
_data: null,
/**
* This is the object listening to the conversion, which is ultimately the
* docshell for the load.
*/
_listener: null,
/**
* Records if the feed was sniffed
*/
_sniffed: false,
/**
* See nsIStreamConverter.idl
*/
convert: function FC_convert(sourceStream, sourceType, destinationType,
context) {
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
},
/**
* See nsIStreamConverter.idl
*/
asyncConvertData: function FC_asyncConvertData(sourceType, destinationType,
listener, context) {
this._listener = listener;
},
/**
* Whether or not the preview page is being forced.
*/
_forcePreviewPage: false,
/**
* Release our references to various things once we're done using them.
*/
_releaseHandles: function FC__releaseHandles() {
this._listener = null;
this._request = null;
this._processor = null;
},
/**
* See nsIFeedResultListener.idl
*/
handleResult: function FC_handleResult(result) {
// Feeds come in various content types, which our feed sniffer coerces to
// the maybe.feed type. However, feeds are used as a transport for
// different data types, e.g. news/blogs (traditional feed), video/audio
// (podcasts) and photos (photocasts, photostreams). Each of these is
// different in that there's a different class of application suitable for
// handling feeds of that type, but without a content-type differentiation
// it is difficult for us to disambiguate.
//
// The other problem is that if the user specifies an auto-action handler
// for one feed application, the fact that the content type is shared means
// that all other applications will auto-load with that handler too,
// regardless of the content-type.
//
// This means that content-type alone is not enough to determine whether
// or not a feed should be auto-handled. This means that for feeds we need
// to always use this stream converter, even when an auto-action is
// specified, not the basic one provided by WebContentConverter. This
// converter needs to consume all of the data and parse it, and based on
// that determination make a judgment about type.
//
// Since there are no content types for this content, and I'm not going to
// invent any, the upshot is that while a user can set an auto-handler for
// generic feed content, the system will prevent them from setting an auto-
// handler for other stream types. In those cases, the user will always see
// the preview page and have to select a handler. We can guess and show
// a client handler, but will not be able to show web handlers for those
// types.
//
// If this is just a feed, not some kind of specialized application, then
// auto-handlers can be set and we should obey them.
try {
var feedService =
Cc["@mozilla.org/browser/feeds/result-service;1"].
getService(Ci.nsIFeedResultService);
if (!this._forcePreviewPage && result.doc) {
var feed = result.doc.QueryInterface(Ci.nsIFeed);
var handler = safeGetCharPref(getPrefActionForType(feed.type), "ask");
if (handler != "ask") {
if (handler == "reader")
handler = safeGetCharPref(getPrefReaderForType(feed.type), "bookmarks");
switch (handler) {
case "web":
var wccr =
Cc["@mozilla.org/embeddor.implemented/web-content-handler-registrar;1"].
getService(Ci.nsIWebContentConverterService);
if ((feed.type == Ci.nsIFeed.TYPE_FEED &&
wccr.getAutoHandler(TYPE_MAYBE_FEED)) ||
(feed.type == Ci.nsIFeed.TYPE_VIDEO &&
wccr.getAutoHandler(TYPE_MAYBE_VIDEO_FEED)) ||
(feed.type == Ci.nsIFeed.TYPE_AUDIO &&
wccr.getAutoHandler(TYPE_MAYBE_AUDIO_FEED))) {
wccr.loadPreferredHandler(this._request);
return;
}
break;
default:
LOG("unexpected handler: " + handler);
// fall through -- let feed service handle error
case "bookmarks":
case "client":
try {
var title = feed.title ? feed.title.plainText() : "";
var desc = feed.subtitle ? feed.subtitle.plainText() : "";
feedService.addToClientReader(result.uri.spec, title, desc, feed.type);
return;
} catch(ex) { /* fallback to preview mode */ }
}
}
}
var ios =
Cc["@mozilla.org/network/io-service;1"].
getService(Ci.nsIIOService);
var chromeChannel;
// handling a redirect, hence forwarding the loadInfo from the old channel
// to the newchannel.
var oldChannel = this._request.QueryInterface(Ci.nsIChannel);
var loadInfo = oldChannel.loadInfo;
// If there was no automatic handler, or this was a podcast,
// photostream or some other kind of application, show the preview page
// if the parser returned a document.
if (result.doc) {
// Store the result in the result service so that the display
// page can access it.
feedService.addFeedResult(result);
// Now load the actual XUL document.
var aboutFeedsURI = ios.newURI("about:feeds", null, null);
chromeChannel = ios.newChannelFromURIWithLoadInfo(aboutFeedsURI, loadInfo);
chromeChannel.originalURI = result.uri;
chromeChannel.owner =
Services.scriptSecurityManager.getNoAppCodebasePrincipal(aboutFeedsURI);
} else {
chromeChannel = ios.newChannelFromURIWithLoadInfo(result.uri, loadInfo);
}
chromeChannel.loadGroup = this._request.loadGroup;
chromeChannel.asyncOpen(this._listener, null);
}
finally {
this._releaseHandles();
}
},
/**
* See nsIStreamListener.idl
*/
onDataAvailable: function FC_onDataAvailable(request, context, inputStream,
sourceOffset, count) {
if (this._processor)
this._processor.onDataAvailable(request, context, inputStream,
sourceOffset, count);
},
/**
* See nsIRequestObserver.idl
*/
onStartRequest: function FC_onStartRequest(request, context) {
var channel = request.QueryInterface(Ci.nsIChannel);
// Check for a header that tells us there was no sniffing
// The value doesn't matter.
try {
var httpChannel = channel.QueryInterface(Ci.nsIHttpChannel);
// Make sure to check requestSucceeded before the potentially-throwing
// getResponseHeader.
if (!httpChannel.requestSucceeded) {
// Just give up, but don't forget to cancel the channel first!
request.cancel(Cr.NS_BINDING_ABORTED);
return;
}
var noSniff = httpChannel.getResponseHeader("X-Moz-Is-Feed");
}
catch (ex) {
this._sniffed = true;
}
this._request = request;
// Save and reset the forced state bit early, in case there's some kind of
// error.
var feedService =
Cc["@mozilla.org/browser/feeds/result-service;1"].
getService(Ci.nsIFeedResultService);
this._forcePreviewPage = feedService.forcePreviewPage;
feedService.forcePreviewPage = false;
// Parse feed data as it comes in
this._processor =
Cc["@mozilla.org/feed-processor;1"].
createInstance(Ci.nsIFeedProcessor);
this._processor.listener = this;
this._processor.parseAsync(null, channel.URI);
this._processor.onStartRequest(request, context);
},
/**
* See nsIRequestObserver.idl
*/
onStopRequest: function FC_onStopRequest(request, context, status) {
if (this._processor)
this._processor.onStopRequest(request, context, status);
},
/**
* See nsISupports.idl
*/
QueryInterface: function FC_QueryInterface(iid) {
if (iid.equals(Ci.nsIFeedResultListener) ||
iid.equals(Ci.nsIStreamConverter) ||
iid.equals(Ci.nsIStreamListener) ||
iid.equals(Ci.nsIRequestObserver)||
iid.equals(Ci.nsISupports))
return this;
throw Cr.NS_ERROR_NO_INTERFACE;
},
};
/**
* Keeps parsed FeedResults around for use elsewhere in the UI after the stream
* converter completes.
*/
function FeedResultService() {
}
FeedResultService.prototype = {
classID: Components.ID("{2376201c-bbc6-472f-9b62-7548040a61c6}"),
/**
* A URI spec -> [nsIFeedResult] hash. We have to keep a list as the
* value in case the same URI is requested concurrently.
*/
_results: { },
/**
* See nsIFeedResultService.idl
*/
forcePreviewPage: false,
/**
* See nsIFeedResultService.idl
*/
addToClientReader: function FRS_addToClientReader(spec, title, subtitle, feedType) {
var prefs =
Cc["@mozilla.org/preferences-service;1"].
getService(Ci.nsIPrefBranch);
var handler = safeGetCharPref(getPrefActionForType(feedType), "bookmarks");
if (handler == "ask" || handler == "reader")
handler = safeGetCharPref(getPrefReaderForType(feedType), "bookmarks");
switch (handler) {
case "client":
var clientApp = prefs.getComplexValue(getPrefAppForType(feedType), Ci.nsILocalFile);
// For the benefit of applications that might know how to deal with more
// URLs than just feeds, send feed: URLs in the following format:
//
// http urls: replace scheme with feed, e.g.
// http://foo.com/index.rdf -> feed://foo.com/index.rdf
// other urls: prepend feed: scheme, e.g.
// https://foo.com/index.rdf -> feed:https://foo.com/index.rdf
var ios =
Cc["@mozilla.org/network/io-service;1"].
getService(Ci.nsIIOService);
var feedURI = ios.newURI(spec, null, null);
if (feedURI.schemeIs("http")) {
feedURI.scheme = "feed";
spec = feedURI.spec;
}
else
spec = "feed:" + spec;
// Retrieving the shell service might fail on some systems, most
// notably systems where GNOME is not installed.
try {
var ss =
Cc["@mozilla.org/browser/shell-service;1"].
getService(Ci.nsIShellService);
ss.openApplicationWithURI(clientApp, spec);
} catch(e) {
// If we couldn't use the shell service, fallback to using a
// nsIProcess instance
var p =
Cc["@mozilla.org/process/util;1"].
createInstance(Ci.nsIProcess);
p.init(clientApp);
p.run(false, [spec], 1);
}
break;
default:
// "web" should have been handled elsewhere
LOG("unexpected handler: " + handler);
// fall through
case "bookmarks":
var wm =
Cc["@mozilla.org/appshell/window-mediator;1"].
getService(Ci.nsIWindowMediator);
var topWindow = wm.getMostRecentWindow("navigator:browser");
topWindow.PlacesCommandHook.addLiveBookmark(spec, title, subtitle);
break;
}
},
/**
* See nsIFeedResultService.idl
*/
addFeedResult: function FRS_addFeedResult(feedResult) {
NS_ASSERT(feedResult.uri != null, "null URI!");
NS_ASSERT(feedResult.uri != null, "null feedResult!");
var spec = feedResult.uri.spec;
if(!this._results[spec])
this._results[spec] = [];
this._results[spec].push(feedResult);
},
/**
* See nsIFeedResultService.idl
*/
getFeedResult: function RFS_getFeedResult(uri) {
NS_ASSERT(uri != null, "null URI!");
var resultList = this._results[uri.spec];
for (var i in resultList) {
if (resultList[i].uri == uri)
return resultList[i];
}
return null;
},
/**
* See nsIFeedResultService.idl
*/
removeFeedResult: function FRS_removeFeedResult(uri) {
NS_ASSERT(uri != null, "null URI!");
var resultList = this._results[uri.spec];
if (!resultList)
return;
var deletions = 0;
for (var i = 0; i < resultList.length; ++i) {
if (resultList[i].uri == uri) {
delete resultList[i];
++deletions;
}
}
// send the holes to the end
resultList.sort();
// and trim the list
resultList.splice(resultList.length - deletions, deletions);
if (resultList.length == 0)
delete this._results[uri.spec];
},
createInstance: function FRS_createInstance(outer, iid) {
if (outer != null)
throw Cr.NS_ERROR_NO_AGGREGATION;
return this.QueryInterface(iid);
},
QueryInterface: function FRS_QueryInterface(iid) {
if (iid.equals(Ci.nsIFeedResultService) ||
iid.equals(Ci.nsIFactory) ||
iid.equals(Ci.nsISupports))
return this;
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
},
};
/**
* A protocol handler that attempts to deal with the variant forms of feed:
* URIs that are actually either http or https.
*/
function GenericProtocolHandler() {
}
GenericProtocolHandler.prototype = {
_init: function GPH_init(scheme) {
var ios =
Cc["@mozilla.org/network/io-service;1"].
getService(Ci.nsIIOService);
this._http = ios.getProtocolHandler("http");
this._scheme = scheme;
},
get scheme() {
return this._scheme;
},
get protocolFlags() {
return this._http.protocolFlags;
},
get defaultPort() {
return this._http.defaultPort;
},
allowPort: function GPH_allowPort(port, scheme) {
return this._http.allowPort(port, scheme);
},
newURI: function GPH_newURI(spec, originalCharset, baseURI) {
// Feed URIs can be either nested URIs of the form feed:realURI (in which
// case we create a nested URI for the realURI) or feed://example.com, in
// which case we create a nested URI for the real protocol which is http.
var scheme = this._scheme + ":";
if (spec.substr(0, scheme.length) != scheme)
throw Cr.NS_ERROR_MALFORMED_URI;
var prefix = spec.substr(scheme.length, 2) == "//" ? "http:" : "";
var inner = Cc["@mozilla.org/network/io-service;1"].
getService(Ci.nsIIOService).newURI(spec.replace(scheme, prefix),
originalCharset, baseURI);
var netutil = Cc["@mozilla.org/network/util;1"].getService(Ci.nsINetUtil);
const URI_INHERITS_SECURITY_CONTEXT = Ci.nsIProtocolHandler
.URI_INHERITS_SECURITY_CONTEXT;
if (netutil.URIChainHasFlags(inner, URI_INHERITS_SECURITY_CONTEXT))
throw Cr.NS_ERROR_MALFORMED_URI;
var uri = netutil.newSimpleNestedURI(inner);
uri.spec = inner.spec.replace(prefix, scheme);
return uri;
},
newChannel2: function GPH_newChannel(aUri, aLoadInfo) {
var inner = aUri.QueryInterface(Ci.nsINestedURI).innerURI;
var channel = Cc["@mozilla.org/network/io-service;1"].
getService(Ci.nsIIOService).
newChannelFromURIWithLoadInfo(inner, aLoadInfo);
if (channel instanceof Components.interfaces.nsIHttpChannel)
// Set this so we know this is supposed to be a feed
channel.setRequestHeader("X-Moz-Is-Feed", "1", false);
channel.originalURI = aUri;
return channel;
},
QueryInterface: function GPH_QueryInterface(iid) {
if (iid.equals(Ci.nsIProtocolHandler) ||
iid.equals(Ci.nsISupports))
return this;
throw Cr.NS_ERROR_NO_INTERFACE;
}
};
function FeedProtocolHandler() {
this._init('feed');
}
FeedProtocolHandler.prototype = new GenericProtocolHandler();
FeedProtocolHandler.prototype.classID = Components.ID("{4f91ef2e-57ba-472e-ab7a-b4999e42d6c0}");
function PodCastProtocolHandler() {
this._init('pcast');
}
PodCastProtocolHandler.prototype = new GenericProtocolHandler();
PodCastProtocolHandler.prototype.classID = Components.ID("{1c31ed79-accd-4b94-b517-06e0c81999d5}");
var components = [FeedConverter,
FeedResultService,
FeedProtocolHandler,
PodCastProtocolHandler];
this.NSGetFactory = XPCOMUtils.generateNSGetFactory(components);

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,932 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
Components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
function LOG(str) {
dump("*** " + str + "\n");
}
const WCCR_CONTRACTID = "@mozilla.org/embeddor.implemented/web-content-handler-registrar;1";
const WCCR_CLASSID = Components.ID("{792a7e82-06a0-437c-af63-b2d12e808acc}");
const WCC_CLASSID = Components.ID("{db7ebf28-cc40-415f-8a51-1b111851df1e}");
const WCC_CLASSNAME = "Web Service Handler";
const TYPE_MAYBE_FEED = "application/vnd.mozilla.maybe.feed";
const TYPE_ANY = "*/*";
const TYPE_BLACKLIST = [
"application/x-www-form-urlencoded",
"application/xhtml+xml",
"application/xml",
"application/mathml+xml",
"application/xslt+xml",
"application/x-xpinstall",
"image/gif",
"image/jpg",
"image/jpeg",
"image/png",
"image/x-png",
"image/webp",
#ifdef MOZ_JXR
"image/jxr",
"image/vnd.ms-photo",
#endif
"image/svg+xml",
"image/bmp",
"image/x-ms-bmp",
"image/icon",
"image/x-icon",
"image/vnd.microsoft.icon",
"multipart/x-mixed-replace",
"multipart/form-data",
"text/cache-manifest",
"text/css",
"text/xsl",
"text/html",
"text/ping",
"text/plain",
"text/xml",
"text/javascript", // To prevent malicious intent blocking scripting.
"text/ecmascript"];
const PREF_CONTENTHANDLERS_AUTO = "browser.contentHandlers.auto.";
const PREF_CONTENTHANDLERS_BRANCH = "browser.contentHandlers.types.";
const PREF_SELECTED_WEB = "browser.feeds.handlers.webservice";
const PREF_SELECTED_ACTION = "browser.feeds.handler";
const PREF_SELECTED_READER = "browser.feeds.handler.default";
const PREF_HANDLER_EXTERNAL_PREFIX = "network.protocol-handler.external";
const PREF_ALLOW_DIFFERENT_HOST = "goanna.handlerService.allowRegisterFromDifferentHost";
const STRING_BUNDLE_URI = "chrome://browser/locale/feeds/subscribe.properties";
const NS_ERROR_MODULE_DOM = 2152923136;
const NS_ERROR_DOM_SYNTAX_ERR = NS_ERROR_MODULE_DOM + 12;
function WebContentConverter() {
}
WebContentConverter.prototype = {
convert: function WCC_convert() { },
asyncConvertData: function WCC_asyncConvertData() { },
onDataAvailable: function WCC_onDataAvailable() { },
onStopRequest: function WCC_onStopRequest() { },
onStartRequest: function WCC_onStartRequest(request, context) {
var wccr =
Cc[WCCR_CONTRACTID].
getService(Ci.nsIWebContentConverterService);
wccr.loadPreferredHandler(request);
},
QueryInterface: function WCC_QueryInterface(iid) {
if (iid.equals(Ci.nsIStreamConverter) ||
iid.equals(Ci.nsIStreamListener) ||
iid.equals(Ci.nsISupports))
return this;
throw Cr.NS_ERROR_NO_INTERFACE;
}
};
var WebContentConverterFactory = {
createInstance: function WCCF_createInstance(outer, iid) {
if (outer != null)
throw Cr.NS_ERROR_NO_AGGREGATION;
return new WebContentConverter().QueryInterface(iid);
},
QueryInterface: function WCC_QueryInterface(iid) {
if (iid.equals(Ci.nsIFactory) ||
iid.equals(Ci.nsISupports))
return this;
throw Cr.NS_ERROR_NO_INTERFACE;
}
};
function ServiceInfo(contentType, uri, name) {
this._contentType = contentType;
this._uri = uri;
this._name = name;
}
ServiceInfo.prototype = {
/**
* See nsIHandlerApp
*/
get name() {
return this._name;
},
/**
* See nsIHandlerApp
*/
equals: function SI_equals(aHandlerApp) {
if (!aHandlerApp)
throw Cr.NS_ERROR_NULL_POINTER;
if (aHandlerApp instanceof Ci.nsIWebContentHandlerInfo &&
aHandlerApp.contentType == this.contentType &&
aHandlerApp.uri == this.uri)
return true;
return false;
},
/**
* See nsIWebContentHandlerInfo
*/
get contentType() {
return this._contentType;
},
/**
* See nsIWebContentHandlerInfo
*/
get uri() {
return this._uri;
},
/**
* See nsIWebContentHandlerInfo
*/
getHandlerURI: function SI_getHandlerURI(uri) {
return this._uri.replace(/%s/gi, encodeURIComponent(uri));
},
QueryInterface: function SI_QueryInterface(iid) {
if (iid.equals(Ci.nsIWebContentHandlerInfo) ||
iid.equals(Ci.nsISupports))
return this;
throw Cr.NS_ERROR_NO_INTERFACE;
}
};
function WebContentConverterRegistrar() {
this._contentTypes = { };
this._autoHandleContentTypes = { };
}
WebContentConverterRegistrar.prototype = {
get stringBundle() {
var sb = Cc["@mozilla.org/intl/stringbundle;1"].
getService(Ci.nsIStringBundleService).
createBundle(STRING_BUNDLE_URI);
delete WebContentConverterRegistrar.prototype.stringBundle;
return WebContentConverterRegistrar.prototype.stringBundle = sb;
},
_getFormattedString: function WCCR__getFormattedString(key, params) {
return this.stringBundle.formatStringFromName(key, params, params.length);
},
_getString: function WCCR_getString(key) {
return this.stringBundle.GetStringFromName(key);
},
/**
* See nsIWebContentConverterService
*/
getAutoHandler:
function WCCR_getAutoHandler(contentType) {
contentType = this._resolveContentType(contentType);
if (contentType in this._autoHandleContentTypes)
return this._autoHandleContentTypes[contentType];
return null;
},
/**
* See nsIWebContentConverterService
*/
setAutoHandler:
function WCCR_setAutoHandler(contentType, handler) {
if (handler && !this._typeIsRegistered(contentType, handler.uri))
throw Cr.NS_ERROR_NOT_AVAILABLE;
contentType = this._resolveContentType(contentType);
this._setAutoHandler(contentType, handler);
var ps =
Cc["@mozilla.org/preferences-service;1"].
getService(Ci.nsIPrefService);
var autoBranch = ps.getBranch(PREF_CONTENTHANDLERS_AUTO);
if (handler)
autoBranch.setCharPref(contentType, handler.uri);
else if (autoBranch.prefHasUserValue(contentType))
autoBranch.clearUserPref(contentType);
ps.savePrefFile(null);
},
/**
* Update the internal data structure (not persistent)
*/
_setAutoHandler:
function WCCR__setAutoHandler(contentType, handler) {
if (handler)
this._autoHandleContentTypes[contentType] = handler;
else if (contentType in this._autoHandleContentTypes)
delete this._autoHandleContentTypes[contentType];
},
/**
* See nsIWebContentConverterService
*/
getWebContentHandlerByURI:
function WCCR_getWebContentHandlerByURI(contentType, uri) {
var handlers = this.getContentHandlers(contentType, { });
for (var i = 0; i < handlers.length; ++i) {
if (handlers[i].uri == uri)
return handlers[i];
}
return null;
},
/**
* See nsIWebContentConverterService
*/
loadPreferredHandler:
function WCCR_loadPreferredHandler(request) {
var channel = request.QueryInterface(Ci.nsIChannel);
var contentType = this._resolveContentType(channel.contentType);
var handler = this.getAutoHandler(contentType);
if (handler) {
request.cancel(Cr.NS_ERROR_FAILURE);
var webNavigation =
channel.notificationCallbacks.getInterface(Ci.nsIWebNavigation);
webNavigation.loadURI(handler.getHandlerURI(channel.URI.spec),
Ci.nsIWebNavigation.LOAD_FLAGS_NONE,
null, null, null);
}
},
/**
* See nsIWebContentConverterService
*/
removeProtocolHandler:
function WCCR_removeProtocolHandler(aProtocol, aURITemplate) {
var eps = Cc["@mozilla.org/uriloader/external-protocol-service;1"].
getService(Ci.nsIExternalProtocolService);
var handlerInfo = eps.getProtocolHandlerInfo(aProtocol);
var handlers = handlerInfo.possibleApplicationHandlers;
for (let i = 0; i < handlers.length; i++) {
try { // We only want to test web handlers
let handler = handlers.queryElementAt(i, Ci.nsIWebHandlerApp);
if (handler.uriTemplate == aURITemplate) {
handlers.removeElementAt(i);
var hs = Cc["@mozilla.org/uriloader/handler-service;1"].
getService(Ci.nsIHandlerService);
hs.store(handlerInfo);
return;
}
} catch (e) { /* it wasn't a web handler */ }
}
},
/**
* See nsIWebContentConverterService
*/
removeContentHandler:
function WCCR_removeContentHandler(contentType, uri) {
function notURI(serviceInfo) {
return serviceInfo.uri != uri;
}
if (contentType in this._contentTypes) {
this._contentTypes[contentType] =
this._contentTypes[contentType].filter(notURI);
}
},
/**
*
*/
_mappings: {
"application/rss+xml": TYPE_MAYBE_FEED,
"application/atom+xml": TYPE_MAYBE_FEED,
},
/**
* These are types for which there is a separate content converter aside
* from our built in generic one. We should not automatically register
* a factory for creating a converter for these types.
*/
_blockedTypes: {
"application/vnd.mozilla.maybe.feed": true,
},
/**
* Determines the "internal" content type based on the _mappings.
* @param contentType
* @returns The resolved contentType value.
*/
_resolveContentType:
function WCCR__resolveContentType(contentType) {
if (contentType in this._mappings)
return this._mappings[contentType];
return contentType;
},
_makeURI: function(aURL, aOriginCharset, aBaseURI) {
var ioService = Components.classes["@mozilla.org/network/io-service;1"]
.getService(Components.interfaces.nsIIOService);
return ioService.newURI(aURL, aOriginCharset, aBaseURI);
},
_checkAndGetURI:
function WCCR_checkAndGetURI(aURIString, aContentWindow)
{
try {
let baseURI = aContentWindow.document.baseURIObject;
var uri = this._makeURI(aURIString, null, baseURI);
} catch (ex) {
// not supposed to throw according to spec
return;
}
// For security reasons we reject non-http(s) urls (see bug 354316),
// we may need to revise this once we support more content types
// XXX this should be a "security exception" according to spec, but that
// isn't defined yet.
if (uri.scheme != "http" && uri.scheme != "https")
throw("Permission denied to add " + uri.spec + " as a content or protocol handler");
// We also reject handlers registered from a different host (see bug 402287)
// The pref allows us to test the feature
var pb = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
if (!pb.getBoolPref(PREF_ALLOW_DIFFERENT_HOST) &&
(!["http:", "https:"].includes(aContentWindow.location.protocol) ||
aContentWindow.location.hostname != uri.host)) {
throw("Permission denied to add " + uri.spec + " as a content or protocol handler");
}
// If the uri doesn't contain '%s', it won't be a good handler
if (uri.spec.indexOf("%s") < 0)
throw NS_ERROR_DOM_SYNTAX_ERR;
return uri;
},
/**
* Determines if a web handler is already registered.
*
* @param aProtocol
* The scheme of the web handler we are checking for.
* @param aURITemplate
* The URI template that the handler uses to handle the protocol.
* @return true if it is already registered, false otherwise.
*/
_protocolHandlerRegistered:
function WCCR_protocolHandlerRegistered(aProtocol, aURITemplate) {
var eps = Cc["@mozilla.org/uriloader/external-protocol-service;1"].
getService(Ci.nsIExternalProtocolService);
var handlerInfo = eps.getProtocolHandlerInfo(aProtocol);
var handlers = handlerInfo.possibleApplicationHandlers;
for (let i = 0; i < handlers.length; i++) {
try { // We only want to test web handlers
let handler = handlers.queryElementAt(i, Ci.nsIWebHandlerApp);
if (handler.uriTemplate == aURITemplate)
return true;
} catch (e) { /* it wasn't a web handler */ }
}
return false;
},
/**
* See nsIWebContentHandlerRegistrar
*/
registerProtocolHandler:
function WCCR_registerProtocolHandler(aProtocol, aURIString, aTitle, aContentWindow) {
LOG("registerProtocolHandler(" + aProtocol + "," + aURIString + "," + aTitle + ")");
var uri = this._checkAndGetURI(aURIString, aContentWindow);
// If the protocol handler is already registered, just return early.
if (this._protocolHandlerRegistered(aProtocol, uri.spec)) {
return;
}
var browserWindow = this._getBrowserWindowForContentWindow(aContentWindow);
if (PrivateBrowsingUtils.isWindowPrivate(browserWindow)) {
// Inside the private browsing mode, we don't want to alert the user to save
// a protocol handler. We log it to the error console so that web developers
// would have some way to tell what's going wrong.
Cc["@mozilla.org/consoleservice;1"].
getService(Ci.nsIConsoleService).
logStringMessage("Web page denied access to register a protocol handler inside private browsing mode");
return;
}
// First, check to make sure this isn't already handled internally (we don't
// want to let them take over, say "chrome").
var ios = Cc["@mozilla.org/network/io-service;1"].
getService(Ci.nsIIOService);
var handler = ios.getProtocolHandler(aProtocol);
if (!(handler instanceof Ci.nsIExternalProtocolHandler)) {
// This is handled internally, so we don't want them to register
// XXX this should be a "security exception" according to spec, but that
// isn't defined yet.
throw("Permission denied to add " + aURIString + "as a protocol handler");
}
// check if it is in the black list
var pb = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
var allowed;
try {
allowed = pb.getBoolPref(PREF_HANDLER_EXTERNAL_PREFIX + "." + aProtocol);
}
catch (e) {
allowed = pb.getBoolPref(PREF_HANDLER_EXTERNAL_PREFIX + "-default");
}
if (!allowed) {
// XXX this should be a "security exception" according to spec
throw("Not allowed to register a protocol handler for " + aProtocol);
}
// Now Ask the user and provide the proper callback
var message = this._getFormattedString("addProtocolHandler",
[aTitle, uri.host, aProtocol]);
var notificationIcon = uri.prePath + "/favicon.ico";
var notificationValue = "Protocol Registration: " + aProtocol;
var addButton = {
label: this._getString("addProtocolHandlerAddButton"),
accessKey: this._getString("addHandlerAddButtonAccesskey"),
protocolInfo: { protocol: aProtocol, uri: uri.spec, name: aTitle },
callback:
function WCCR_addProtocolHandlerButtonCallback(aNotification, aButtonInfo) {
var protocol = aButtonInfo.protocolInfo.protocol;
var uri = aButtonInfo.protocolInfo.uri;
var name = aButtonInfo.protocolInfo.name;
var handler = Cc["@mozilla.org/uriloader/web-handler-app;1"].
createInstance(Ci.nsIWebHandlerApp);
handler.name = name;
handler.uriTemplate = uri;
var eps = Cc["@mozilla.org/uriloader/external-protocol-service;1"].
getService(Ci.nsIExternalProtocolService);
var handlerInfo = eps.getProtocolHandlerInfo(protocol);
handlerInfo.possibleApplicationHandlers.appendElement(handler, false);
// Since the user has agreed to add a new handler, chances are good
// that the next time they see a handler of this type, they're going
// to want to use it. Reset the handlerInfo to ask before the next
// use.
handlerInfo.alwaysAskBeforeHandling = true;
var hs = Cc["@mozilla.org/uriloader/handler-service;1"].
getService(Ci.nsIHandlerService);
hs.store(handlerInfo);
}
};
var browserElement = this._getBrowserForContentWindow(browserWindow, aContentWindow);
var notificationBox = browserWindow.gBrowser.getNotificationBox(browserElement);
notificationBox.appendNotification(message,
notificationValue,
notificationIcon,
notificationBox.PRIORITY_INFO_LOW,
[addButton]);
},
/**
* See nsIWebContentHandlerRegistrar
* If a DOM window is provided, then the request came from content, so we
* prompt the user to confirm the registration.
*/
registerContentHandler:
function WCCR_registerContentHandler(aContentType, aURIString, aTitle, aContentWindow) {
LOG("registerContentHandler(" + aContentType + "," + aURIString + "," + aTitle + ")");
// Check against the type blacklist.
// XXX this should be a "security exception" according to spec, but that
// isn't defined yet.
var contentType = this._resolveContentType(aContentType);
for (let blacklistType of TYPE_BLACKLIST) {
if (contentType == blacklistType) {
console.error("Unable to register content handler for prohibited MIME type %s.", contentType);
return;
}
}
if (aContentWindow) {
var uri = this._checkAndGetURI(aURIString, aContentWindow);
var browserWindow = this._getBrowserWindowForContentWindow(aContentWindow);
var browserElement = this._getBrowserForContentWindow(browserWindow, aContentWindow);
var notificationBox = browserWindow.gBrowser.getNotificationBox(browserElement);
this._appendFeedReaderNotification(uri, aTitle, notificationBox);
}
else
this._registerContentHandler(contentType, aURIString, aTitle);
},
/**
* Returns the browser chrome window in which the content window is in
*/
_getBrowserWindowForContentWindow:
function WCCR__getBrowserWindowForContentWindow(aContentWindow) {
return aContentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShellTreeItem)
.rootTreeItem
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindow)
.wrappedJSObject;
},
/**
* Returns the <xul:browser> element associated with the given content
* window.
*
* @param aBrowserWindow
* The browser window in which the content window is in.
* @param aContentWindow
* The content window. It's possible to pass a child content window
* (i.e. the content window of a frame/iframe).
*/
_getBrowserForContentWindow:
function WCCR__getBrowserForContentWindow(aBrowserWindow, aContentWindow) {
// This depends on pseudo APIs of browser.js and tabbrowser.xml
aContentWindow = aContentWindow.top;
var browsers = aBrowserWindow.gBrowser.browsers;
for (var i = 0; i < browsers.length; ++i) {
if (browsers[i].contentWindow == aContentWindow)
return browsers[i];
}
},
/**
* Appends a notifcation for the given feed reader details.
*
* The notification could be either a pseudo-dialog which lets
* the user to add the feed reader:
* [ [icon] Add %feed-reader-name% (%feed-reader-host%) as a Feed Reader? (Add) [x] ]
*
* or a simple message for the case where the feed reader is already registered:
* [ [icon] %feed-reader-name% is already registered as a Feed Reader [x] ]
*
* A new notification isn't appended if the given notificationbox has a
* notification for the same feed reader.
*
* @param aURI
* The url of the feed reader as a nsIURI object
* @param aName
* The feed reader name as it was passed to registerContentHandler
* @param aNotificationBox
* The notification box to which a notification might be appended
* @return true if a notification has been appended, false otherwise.
*/
_appendFeedReaderNotification:
function WCCR__appendFeedReaderNotification(aURI, aName, aNotificationBox) {
var uriSpec = aURI.spec;
var notificationValue = "feed reader notification: " + uriSpec;
var notificationIcon = aURI.prePath + "/favicon.ico";
// Don't append a new notification if the notificationbox
// has a notification for the given feed reader already
if (aNotificationBox.getNotificationWithValue(notificationValue))
return false;
var buttons, message;
if (this.getWebContentHandlerByURI(TYPE_MAYBE_FEED, uriSpec))
message = this._getFormattedString("handlerRegistered", [aName]);
else {
message = this._getFormattedString("addHandler", [aName, aURI.host]);
var self = this;
var addButton = {
_outer: self,
label: self._getString("addHandlerAddButton"),
accessKey: self._getString("addHandlerAddButtonAccesskey"),
feedReaderInfo: { uri: uriSpec, name: aName },
/* static */
callback:
function WCCR__addFeedReaderButtonCallback(aNotification, aButtonInfo) {
var uri = aButtonInfo.feedReaderInfo.uri;
var name = aButtonInfo.feedReaderInfo.name;
var outer = aButtonInfo._outer;
// The reader could have been added from another window mean while
if (!outer.getWebContentHandlerByURI(TYPE_MAYBE_FEED, uri))
outer._registerContentHandler(TYPE_MAYBE_FEED, uri, name);
// avoid reference cycles
aButtonInfo._outer = null;
return false;
}
};
buttons = [addButton];
}
aNotificationBox.appendNotification(message,
notificationValue,
notificationIcon,
aNotificationBox.PRIORITY_INFO_LOW,
buttons);
return true;
},
/**
* Save Web Content Handler metadata to persistent preferences.
* @param contentType
* The content Type being handled
* @param uri
* The uri of the web service
* @param title
* The human readable name of the web service
*
* This data is stored under:
*
* browser.contentHandlers.type0 = content/type
* browser.contentHandlers.uri0 = http://www.foo.com/q=%s
* browser.contentHandlers.title0 = Foo 2.0alphr
*/
_saveContentHandlerToPrefs:
function WCCR__saveContentHandlerToPrefs(contentType, uri, title) {
var ps =
Cc["@mozilla.org/preferences-service;1"].
getService(Ci.nsIPrefService);
var i = 0;
var typeBranch = null;
while (true) {
typeBranch =
ps.getBranch(PREF_CONTENTHANDLERS_BRANCH + i + ".");
try {
typeBranch.getCharPref("type");
++i;
}
catch (e) {
// No more handlers
break;
}
}
if (typeBranch) {
typeBranch.setCharPref("type", contentType);
var pls =
Cc["@mozilla.org/pref-localizedstring;1"].
createInstance(Ci.nsIPrefLocalizedString);
pls.data = uri;
typeBranch.setComplexValue("uri", Ci.nsIPrefLocalizedString, pls);
pls.data = title;
typeBranch.setComplexValue("title", Ci.nsIPrefLocalizedString, pls);
ps.savePrefFile(null);
}
},
/**
* Determines if there is a type with a particular uri registered for the
* specified content type already.
* @param contentType
* The content type that the uri handles
* @param uri
* The uri of the
*/
_typeIsRegistered: function WCCR__typeIsRegistered(contentType, uri) {
if (!(contentType in this._contentTypes))
return false;
var services = this._contentTypes[contentType];
for (var i = 0; i < services.length; ++i) {
// This uri has already been registered
if (services[i].uri == uri)
return true;
}
return false;
},
/**
* Gets a stream converter contract id for the specified content type.
* @param contentType
* The source content type for the conversion.
* @returns A contract id to construct a converter to convert between the
* contentType and *\/*.
*/
_getConverterContractID: function WCCR__getConverterContractID(contentType) {
const template = "@mozilla.org/streamconv;1?from=%s&to=*/*";
return template.replace(/%s/, contentType);
},
/**
* Register a web service handler for a content type.
*
* @param contentType
* the content type being handled
* @param uri
* the URI of the web service
* @param title
* the human readable name of the web service
*/
_registerContentHandler:
function WCCR__registerContentHandler(contentType, uri, title) {
this._updateContentTypeHandlerMap(contentType, uri, title);
this._saveContentHandlerToPrefs(contentType, uri, title);
if (contentType == TYPE_MAYBE_FEED) {
// Make the new handler the last-selected reader in the preview page
// and make sure the preview page is shown the next time a feed is visited
var pb = Cc["@mozilla.org/preferences-service;1"].
getService(Ci.nsIPrefService).getBranch(null);
pb.setCharPref(PREF_SELECTED_READER, "web");
var supportsString =
Cc["@mozilla.org/supports-string;1"].
createInstance(Ci.nsISupportsString);
supportsString.data = uri;
pb.setComplexValue(PREF_SELECTED_WEB, Ci.nsISupportsString,
supportsString);
pb.setCharPref(PREF_SELECTED_ACTION, "ask");
this._setAutoHandler(TYPE_MAYBE_FEED, null);
}
},
/**
* Update the content type -> handler map. This mapping is not persisted, use
* registerContentHandler or _saveContentHandlerToPrefs for that purpose.
* @param contentType
* The content Type being handled
* @param uri
* The uri of the web service
* @param title
* The human readable name of the web service
*/
_updateContentTypeHandlerMap:
function WCCR__updateContentTypeHandlerMap(contentType, uri, title) {
if (!(contentType in this._contentTypes))
this._contentTypes[contentType] = [];
// Avoid adding duplicates
if (this._typeIsRegistered(contentType, uri))
return;
this._contentTypes[contentType].push(new ServiceInfo(contentType, uri, title));
if (!(contentType in this._blockedTypes)) {
var converterContractID = this._getConverterContractID(contentType);
var cr = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
cr.registerFactory(WCC_CLASSID, WCC_CLASSNAME, converterContractID,
WebContentConverterFactory);
}
},
/**
* See nsIWebContentConverterService
*/
getContentHandlers:
function WCCR_getContentHandlers(contentType, countRef) {
countRef.value = 0;
if (!(contentType in this._contentTypes))
return [];
var handlers = this._contentTypes[contentType];
countRef.value = handlers.length;
return handlers;
},
/**
* See nsIWebContentConverterService
*/
resetHandlersForType:
function WCCR_resetHandlersForType(contentType) {
// currently unused within the tree, so only useful for extensions; previous
// impl. was buggy (and even infinite-looped!), so I argue that this is a
// definite improvement
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
},
/**
* Registers a handler from the settings on a preferences branch.
*
* @param branch
* an nsIPrefBranch containing "type", "uri", and "title" preferences
* corresponding to the content handler to be registered
*/
_registerContentHandlerWithBranch: function(branch) {
/**
* Since we support up to six predefined readers, we need to handle gaps
* better, since the first branch with user-added values will be .6
*
* How we deal with that is to check to see if there's no prefs in the
* branch and stop cycling once that's true. This doesn't fix the case
* where a user manually removes a reader, but that's not supported yet!
*/
var vals = branch.getChildList("");
if (vals.length == 0)
return;
try {
var type = branch.getCharPref("type");
var uri = branch.getComplexValue("uri", Ci.nsIPrefLocalizedString).data;
var title = branch.getComplexValue("title",
Ci.nsIPrefLocalizedString).data;
this._updateContentTypeHandlerMap(type, uri, title);
}
catch(ex) {
// do nothing, the next branch might have values
}
},
/**
* Load the auto handler, content handler and protocol tables from
* preferences.
*/
_init: function WCCR__init() {
var ps =
Cc["@mozilla.org/preferences-service;1"].
getService(Ci.nsIPrefService);
var kids = ps.getBranch(PREF_CONTENTHANDLERS_BRANCH)
.getChildList("");
// first get the numbers of the providers by getting all ###.uri prefs
var nums = [];
for (var i = 0; i < kids.length; i++) {
var match = /^(\d+)\.uri$/.exec(kids[i]);
if (!match)
continue;
else
nums.push(match[1]);
}
// sort them, to get them back in order
nums.sort(function(a, b) {return a - b;});
// now register them
for (var i = 0; i < nums.length; i++) {
var branch = ps.getBranch(PREF_CONTENTHANDLERS_BRANCH + nums[i] + ".");
this._registerContentHandlerWithBranch(branch);
}
// We need to do this _after_ registering all of the available handlers,
// so that getWebContentHandlerByURI can return successfully.
try {
var autoBranch = ps.getBranch(PREF_CONTENTHANDLERS_AUTO);
var childPrefs = autoBranch.getChildList("");
for (var i = 0; i < childPrefs.length; ++i) {
var type = childPrefs[i];
var uri = autoBranch.getCharPref(type);
if (uri) {
var handler = this.getWebContentHandlerByURI(type, uri);
this._setAutoHandler(type, handler);
}
}
}
catch (e) {
// No auto branch yet, that's fine
//LOG("WCCR.init: There is no auto branch, benign");
}
},
/**
* See nsIObserver
*/
observe: function WCCR_observe(subject, topic, data) {
var os =
Cc["@mozilla.org/observer-service;1"].
getService(Ci.nsIObserverService);
switch (topic) {
case "app-startup":
os.addObserver(this, "browser-ui-startup-complete", false);
break;
case "browser-ui-startup-complete":
os.removeObserver(this, "browser-ui-startup-complete");
this._init();
break;
}
},
/**
* See nsIFactory
*/
createInstance: function WCCR_createInstance(outer, iid) {
if (outer != null)
throw Cr.NS_ERROR_NO_AGGREGATION;
return this.QueryInterface(iid);
},
classID: WCCR_CLASSID,
/**
* See nsISupports
*/
QueryInterface: XPCOMUtils.generateQI(
[Ci.nsIWebContentConverterService,
Ci.nsIWebContentHandlerRegistrar,
Ci.nsIObserver,
Ci.nsIFactory]),
_xpcom_categories: [{
category: "app-startup",
service: true
}]
};
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([WebContentConverterRegistrar]);

View file

@ -0,0 +1,7 @@
/* 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/. */
#feedSubscribeLine {
-moz-binding: url(subscribe.xml#feedreaderUI);
}

View file

@ -0,0 +1,23 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
var SubscribeHandler = {
/**
* The nsIFeedWriter object that produces the UI
*/
_feedWriter: null,
init: function SH_init() {
this._feedWriter = new BrowserFeedWriter();
},
writeContent: function SH_writeContent() {
this._feedWriter.writeContent();
},
uninit: function SH_uninit() {
this._feedWriter.close();
}
};

View file

@ -0,0 +1,65 @@
<?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 % globalDTD
SYSTEM "chrome://global/locale/global.dtd">
%globalDTD;
<!ENTITY % feedDTD
SYSTEM "chrome://browser/locale/feeds/subscribe.dtd">
%feedDTD;
]>
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<html id="feedHandler"
xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>&feedPage.title;</title>
<link rel="stylesheet"
href="chrome://browser/skin/feeds/subscribe.css"
type="text/css"
media="all"/>
<link rel="stylesheet"
href="chrome://browser/content/feeds/subscribe.css"
type="text/css"
media="all"/>
<script type="application/javascript"
src="chrome://browser/content/feeds/subscribe.js"/>
</head>
<body onload="SubscribeHandler.writeContent();" onunload="SubscribeHandler.uninit();">
<div id="feedHeaderContainer">
<div id="feedHeader" dir="&locale.dir;">
<div id="feedIntroText">
<p id="feedSubscriptionInfo1" />
<p id="feedSubscriptionInfo2" />
</div>
<div id="feedSubscribeLine"></div>
</div>
</div>
<script type="application/javascript">
SubscribeHandler.init();
</script>
<div id="feedBody">
<div id="feedTitle">
<a id="feedTitleLink">
<img id="feedTitleImage"/>
</a>
<div id="feedTitleContainer">
<h1 id="feedTitleText"/>
<h2 id="feedSubtitleText"/>
</div>
</div>
<div id="feedContent"/>
</div>
</body>
</html>

View file

@ -0,0 +1,40 @@
<?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 bindings [
<!ENTITY % feedDTD
SYSTEM "chrome://browser/locale/feeds/subscribe.dtd">
%feedDTD;
]>
<bindings id="feedBindings"
xmlns="http://www.mozilla.org/xbl"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<binding id="feedreaderUI" bindToUntrustedContent="true">
<content>
<xul:vbox>
<xul:hbox align="center">
<xul:description anonid="subscribeUsingDescription" class="subscribeUsingDescription"/>
<xul:menulist anonid="handlersMenuList" class="handlersMenuList" aria-labelledby="subscribeUsingDescription">
<xul:menupopup anonid="handlersMenuPopup" class="handlersMenuPopup">
<xul:menuitem anonid="liveBookmarksMenuItem" label="&feedLiveBookmarks;" class="menuitem-iconic liveBookmarksMenuItem" image="chrome://browser/skin/page-livemarks.png" selected="true"/>
<xul:menuseparator/>
</xul:menupopup>
</xul:menulist>
</xul:hbox>
<xul:hbox>
<xul:checkbox anonid="alwaysUse" class="alwaysUse" checked="false"/>
</xul:hbox>
<xul:hbox align="center">
<xul:spacer flex="1"/>
<xul:button label="&feedSubscribeNow;" anonid="subscribeButton" class="subscribeButton"/>
</xul:hbox>
</xul:vbox>
</content>
<resources>
<stylesheet src="chrome://browser/skin/feeds/subscribe-ui.css"/>
</resources>
</binding>
</bindings>

View file

@ -0,0 +1,9 @@
# 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/feeds/subscribe.xhtml (content/subscribe.xhtml)
content/browser/feeds/subscribe.js (content/subscribe.js)
content/browser/feeds/subscribe.xml (content/subscribe.xml)
content/browser/feeds/subscribe.css (content/subscribe.css)

View file

@ -0,0 +1,39 @@
# -*- 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/.
JAR_MANIFESTS += ['jar.mn']
XPIDL_SOURCES += [
'nsIFeedResultService.idl',
'nsIWebContentConverterRegistrar.idl',
]
XPIDL_MODULE = 'browser-feeds'
SOURCES += [
'nsFeedSniffer.cpp',
]
EXTRA_COMPONENTS += [
'BrowserFeeds.manifest',
'FeedConverter.js',
]
EXTRA_PP_COMPONENTS += [
'FeedWriter.js',
'WebContentConverter.js',
]
FINAL_LIBRARY = 'browsercomps'
for var in ('MOZ_APP_NAME', 'MOZ_MACBUNDLE_NAME'):
DEFINES[var] = CONFIG[var]
LOCAL_INCLUDES += [
'../build',
]
FAIL_ON_WARNINGS = True

View file

@ -0,0 +1,363 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsFeedSniffer.h"
#include "nsNetCID.h"
#include "nsXPCOM.h"
#include "nsCOMPtr.h"
#include "nsStringStream.h"
#include "nsBrowserCompsCID.h"
#include "nsICategoryManager.h"
#include "nsIServiceManager.h"
#include "nsComponentManagerUtils.h"
#include "nsServiceManagerUtils.h"
#include "nsIStreamConverterService.h"
#include "nsIStreamConverter.h"
#include "nsIStreamListener.h"
#include "nsIHttpChannel.h"
#include "nsIMIMEHeaderParam.h"
#include "nsMimeTypes.h"
#include "nsIURI.h"
#include <algorithm>
#define TYPE_ATOM "application/atom+xml"
#define TYPE_RSS "application/rss+xml"
#define TYPE_MAYBE_FEED "application/vnd.mozilla.maybe.feed"
#define NS_RDF "http://www.w3.org/1999/02/22-rdf-syntax-ns#"
#define NS_RSS "http://purl.org/rss/1.0/"
#define MAX_BYTES 512u
NS_IMPL_ISUPPORTS(nsFeedSniffer,
nsIContentSniffer,
nsIStreamListener,
nsIRequestObserver)
nsresult
nsFeedSniffer::ConvertEncodedData(nsIRequest* request,
const uint8_t* data,
uint32_t length)
{
nsresult rv = NS_OK;
mDecodedData = "";
nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(request));
if (!httpChannel)
return NS_ERROR_NO_INTERFACE;
nsAutoCString contentEncoding;
httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("Content-Encoding"),
contentEncoding);
if (!contentEncoding.IsEmpty()) {
nsCOMPtr<nsIStreamConverterService> converterService(do_GetService(NS_STREAMCONVERTERSERVICE_CONTRACTID));
if (converterService) {
ToLowerCase(contentEncoding);
nsCOMPtr<nsIStreamListener> converter;
rv = converterService->AsyncConvertData(contentEncoding.get(),
"uncompressed", this, nullptr,
getter_AddRefs(converter));
NS_ENSURE_SUCCESS(rv, rv);
converter->OnStartRequest(request, nullptr);
nsCOMPtr<nsIStringInputStream> rawStream =
do_CreateInstance(NS_STRINGINPUTSTREAM_CONTRACTID);
if (!rawStream)
return NS_ERROR_FAILURE;
rv = rawStream->SetData((const char*)data, length);
NS_ENSURE_SUCCESS(rv, rv);
rv = converter->OnDataAvailable(request, nullptr, rawStream, 0, length);
NS_ENSURE_SUCCESS(rv, rv);
converter->OnStopRequest(request, nullptr, NS_OK);
}
}
return rv;
}
template<int N>
static bool
StringBeginsWithLowercaseLiteral(nsAString& aString,
const char (&aSubstring)[N])
{
return StringHead(aString, N).LowerCaseEqualsLiteral(aSubstring);
}
bool
HasAttachmentDisposition(nsIHttpChannel* httpChannel)
{
if (!httpChannel)
return false;
uint32_t disp;
nsresult rv = httpChannel->GetContentDisposition(&disp);
if (NS_SUCCEEDED(rv) && disp == nsIChannel::DISPOSITION_ATTACHMENT)
return true;
return false;
}
/**
* @return the first occurrence of a character within a string buffer,
* or nullptr if not found
*/
static const char*
FindChar(char c, const char *begin, const char *end)
{
for (; begin < end; ++begin) {
if (*begin == c)
return begin;
}
return nullptr;
}
/**
*
* Determine if a substring is the "documentElement" in the document.
*
* All of our sniffed substrings: <rss, <feed, <rdf:RDF must be the "document"
* element within the XML DOM, i.e. the root container element. Otherwise,
* it's possible that someone embedded one of these tags inside a document of
* another type, e.g. a HTML document, and we don't want to show the preview
* page if the document isn't actually a feed.
*
* @param start
* The beginning of the data being sniffed
* @param end
* The end of the data being sniffed, right before the substring that
* was found.
* @returns true if the found substring is the documentElement, false
* otherwise.
*/
static bool
IsDocumentElement(const char *start, const char* end)
{
// For every tag in the buffer, check to see if it's a PI, Doctype or
// comment, our desired substring or something invalid.
while ( (start = FindChar('<', start, end)) ) {
++start;
if (start >= end)
return false;
// Check to see if the character following the '<' is either '?' or '!'
// (processing instruction or doctype or comment)... these are valid nodes
// to have in the prologue.
if (*start != '?' && *start != '!')
return false;
// Now advance the iterator until the '>' (We do this because we don't want
// to sniff indicator substrings that are embedded within other nodes, e.g.
// comments: <!-- <rdf:RDF .. > -->
start = FindChar('>', start, end);
if (!start)
return false;
++start;
}
return true;
}
/**
* Determines whether or not a string exists as the root element in an XML data
* string buffer.
* @param dataString
* The data being sniffed
* @param substring
* The substring being tested for existence and root-ness.
* @returns true if the substring exists and is the documentElement, false
* otherwise.
*/
static bool
ContainsTopLevelSubstring(nsACString& dataString, const char *substring)
{
int32_t offset = dataString.Find(substring);
if (offset == -1)
return false;
const char *begin = dataString.BeginReading();
// Only do the validation when we find the substring.
return IsDocumentElement(begin, begin + offset);
}
NS_IMETHODIMP
nsFeedSniffer::GetMIMETypeFromContent(nsIRequest* request,
const uint8_t* data,
uint32_t length,
nsACString& sniffedType)
{
nsCOMPtr<nsIHttpChannel> channel(do_QueryInterface(request));
if (!channel)
return NS_ERROR_NO_INTERFACE;
// Check that this is a GET request, since you can't subscribe to a POST...
nsAutoCString method;
channel->GetRequestMethod(method);
if (!method.EqualsLiteral("GET")) {
sniffedType.Truncate();
return NS_OK;
}
// We need to find out if this is a load of a view-source document. In this
// case we do not want to override the content type, since the source display
// does not need to be converted from feed format to XUL. More importantly,
// we don't want to change the content type from something
// nsContentDLF::CreateInstance knows about (e.g. application/xml, text/html
// etc) to something that only the application fe knows about (maybe.feed)
// thus deactivating syntax highlighting.
nsCOMPtr<nsIURI> originalURI;
channel->GetOriginalURI(getter_AddRefs(originalURI));
nsAutoCString scheme;
originalURI->GetScheme(scheme);
if (scheme.EqualsLiteral("view-source")) {
sniffedType.Truncate();
return NS_OK;
}
// Check the Content-Type to see if it is set correctly. If it is set to
// something specific that we think is a reliable indication of a feed, don't
// bother sniffing since we assume the site maintainer knows what they're
// doing.
nsAutoCString contentType;
channel->GetContentType(contentType);
bool noSniff = contentType.EqualsLiteral(TYPE_RSS) ||
contentType.EqualsLiteral(TYPE_ATOM);
// Check to see if this was a feed request from the location bar or from
// the feed: protocol. This is also a reliable indication.
// The value of the header doesn't matter.
if (!noSniff) {
nsAutoCString sniffHeader;
nsresult foundHeader =
channel->GetRequestHeader(NS_LITERAL_CSTRING("X-Moz-Is-Feed"),
sniffHeader);
noSniff = NS_SUCCEEDED(foundHeader);
}
if (noSniff) {
// check for an attachment after we have a likely feed.
if(HasAttachmentDisposition(channel)) {
sniffedType.Truncate();
return NS_OK;
}
// set the feed header as a response header, since we have good metadata
// telling us that the feed is supposed to be RSS or Atom
channel->SetResponseHeader(NS_LITERAL_CSTRING("X-Moz-Is-Feed"),
NS_LITERAL_CSTRING("1"), false);
sniffedType.AssignLiteral(TYPE_MAYBE_FEED);
return NS_OK;
}
// Don't sniff arbitrary types. Limit sniffing to situations that
// we think can reasonably arise.
if (!contentType.EqualsLiteral(TEXT_HTML) &&
!contentType.EqualsLiteral(APPLICATION_OCTET_STREAM) &&
// Same criterion as XMLHttpRequest. Should we be checking for "+xml"
// and check for text/xml and application/xml by hand instead?
contentType.Find("xml") == -1) {
sniffedType.Truncate();
return NS_OK;
}
// Now we need to potentially decompress data served with
// Content-Encoding: gzip
nsresult rv = ConvertEncodedData(request, data, length);
if (NS_FAILED(rv))
return rv;
// We cap the number of bytes to scan at MAX_BYTES to prevent picking up
// false positives by accidentally reading document content, e.g. a "how to
// make a feed" page.
const char* testData;
if (mDecodedData.IsEmpty()) {
testData = (const char*)data;
length = std::min(length, MAX_BYTES);
} else {
testData = mDecodedData.get();
length = std::min(mDecodedData.Length(), MAX_BYTES);
}
// The strategy here is based on that described in:
// http://blogs.msdn.com/rssteam/articles/PublishersGuide.aspx
// for interoperarbility purposes.
// Thus begins the actual sniffing.
nsDependentCSubstring dataString((const char*)testData, length);
bool isFeed = false;
// RSS 0.91/0.92/2.0
isFeed = ContainsTopLevelSubstring(dataString, "<rss");
// Atom 1.0
if (!isFeed)
isFeed = ContainsTopLevelSubstring(dataString, "<feed");
// RSS 1.0
if (!isFeed) {
isFeed = ContainsTopLevelSubstring(dataString, "<rdf:RDF") &&
dataString.Find(NS_RDF) != -1 &&
dataString.Find(NS_RSS) != -1;
}
// If we sniffed a feed, coerce our internal type
if (isFeed && !HasAttachmentDisposition(channel))
sniffedType.AssignLiteral(TYPE_MAYBE_FEED);
else
sniffedType.Truncate();
return NS_OK;
}
NS_IMETHODIMP
nsFeedSniffer::OnStartRequest(nsIRequest* request, nsISupports* context)
{
return NS_OK;
}
NS_METHOD
nsFeedSniffer::AppendSegmentToString(nsIInputStream* inputStream,
void* closure,
const char* rawSegment,
uint32_t toOffset,
uint32_t count,
uint32_t* writeCount)
{
nsCString* decodedData = static_cast<nsCString*>(closure);
decodedData->Append(rawSegment, count);
*writeCount = count;
return NS_OK;
}
NS_IMETHODIMP
nsFeedSniffer::OnDataAvailable(nsIRequest* request, nsISupports* context,
nsIInputStream* stream, uint64_t offset,
uint32_t count)
{
uint32_t read;
return stream->ReadSegments(AppendSegmentToString, &mDecodedData, count,
&read);
}
NS_IMETHODIMP
nsFeedSniffer::OnStopRequest(nsIRequest* request, nsISupports* context,
nsresult status)
{
return NS_OK;
}

View file

@ -0,0 +1,37 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsIContentSniffer.h"
#include "nsIStreamListener.h"
#include "nsStringAPI.h"
#include "mozilla/Attributes.h"
class nsFeedSniffer final : public nsIContentSniffer,
nsIStreamListener
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSICONTENTSNIFFER
NS_DECL_NSIREQUESTOBSERVER
NS_DECL_NSISTREAMLISTENER
static NS_METHOD AppendSegmentToString(nsIInputStream* inputStream,
void* closure,
const char* rawSegment,
uint32_t toOffset,
uint32_t count,
uint32_t* writeCount);
protected:
~nsFeedSniffer() {}
nsresult ConvertEncodedData(nsIRequest* request, const uint8_t* data,
uint32_t length);
private:
nsCString mDecodedData;
};

View file

@ -0,0 +1,66 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsISupports.idl"
interface nsIURI;
interface nsIRequest;
interface nsIFeedResult;
/**
* nsIFeedResultService provides a globally-accessible object for retrieving
* the results of feed processing.
*/
[scriptable, uuid(950a829e-c20e-4dc3-b447-f8b753ae54da)]
interface nsIFeedResultService : nsISupports
{
/**
* When set to true, forces the preview page to be displayed, regardless
* of the user's preferences.
*/
attribute boolean forcePreviewPage;
/**
* Adds a URI to the user's specified external feed handler, or live
* bookmarks.
* @param uri
* The uri of the feed to add.
* @param title
* The title of the feed to add.
* @param subtitle
* The subtitle of the feed to add.
* @param feedType
* The nsIFeed type of the feed. See nsIFeed.idl
*/
void addToClientReader(in AUTF8String uri,
in AString title,
in AString subtitle,
in unsigned long feedType);
/**
* Registers a Feed Result object with a globally accessible service
* so that it can be accessed by a singleton method outside the usual
* flow of control in document loading.
*
* @param feedResult
* An object implementing nsIFeedResult representing the feed.
*/
void addFeedResult(in nsIFeedResult feedResult);
/**
* Gets a Feed Handler object registered using addFeedResult.
*
* @param uri
* The URI of the feed a handler is being requested for
*/
nsIFeedResult getFeedResult(in nsIURI uri);
/**
* Unregisters a Feed Handler object registered using addFeedResult.
* @param uri
* The feed URI the handler was registered under. This must be
* the same *instance* the feed was registered under.
*/
void removeFeedResult(in nsIURI uri);
};

View file

@ -0,0 +1,117 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsIMIMEInfo.idl"
#include "nsIWebContentHandlerRegistrar.idl"
interface nsIRequest;
[scriptable, uuid(eb361098-5158-4b21-8f98-50b445f1f0b2)]
interface nsIWebContentHandlerInfo : nsIHandlerApp
{
/**
* The content type handled by the handler
*/
readonly attribute AString contentType;
/**
* The uri of the handler, with an embedded %s where the URI of the loaded
* document will be encoded.
*/
readonly attribute AString uri;
/**
* Gets the service URL Spec, with the loading document URI encoded in it.
* @param uri
* The URI of the document being loaded
* @returns The URI of the service with the loading document URI encoded in
* it.
*/
AString getHandlerURI(in AString uri);
};
[scriptable, uuid(de7cc06e-e778-45cb-b7db-7a114e1e75b1)]
interface nsIWebContentConverterService : nsIWebContentHandlerRegistrar
{
/**
* Specifies the handler to be used to automatically handle all links of a
* certain content type from now on.
* @param contentType
* The content type to automatically load with the specified handler
* @param handler
* A web service handler. If this is null, no automatic action is
* performed and the user must choose.
* @throws NS_ERROR_NOT_AVAILABLE if the service refered to by |handler| is
* not already registered.
*/
void setAutoHandler(in AString contentType, in nsIWebContentHandlerInfo handler);
/**
* Gets the auto handler specified for a particular content type
* @param contentType
* The content type to look up an auto handler for.
* @returns The web service handler that will automatically handle all
* documents of the specified type. null if there is no automatic
* handler. (Handlers may be registered, just none of them specified
* as "automatic").
*/
nsIWebContentHandlerInfo getAutoHandler(in AString contentType);
/**
* Gets a web handler for the specified service URI
* @param contentType
* The content type of the service being located
* @param uri
* The service URI of the handler to locate.
* @returns A web service handler that uses the specified uri.
*/
nsIWebContentHandlerInfo getWebContentHandlerByURI(in AString contentType,
in AString uri);
/**
* Loads the preferred handler when content of a registered type is about
* to be loaded.
* @param request
* The nsIRequest for the load of the content
*/
void loadPreferredHandler(in nsIRequest request);
/**
* Removes a registered protocol handler
* @param protocol
* The protocol scheme to remove a service handler for
* @param uri
* The uri of the service handler to remove
*/
void removeProtocolHandler(in AString protocol, in AString uri);
/**
* Removes a registered content handler
* @param contentType
* The content type to remove a service handler for
* @param uri
* The uri of the service handler to remove
*/
void removeContentHandler(in AString contentType, in AString uri);
/**
* Gets the list of content handlers for a particular type.
* @param contentType
* The content type to get handlers for
* @returns An array of nsIWebContentHandlerInfo objects
*/
void getContentHandlers(in AString contentType,
[optional] out unsigned long count,
[retval,array,size_is(count)] out nsIWebContentHandlerInfo handlers);
/**
* Resets the list of available content handlers to the default set from
* the distribution.
* @param contentType
* The content type to reset handlers for
*/
void resetHandlersForType(in AString contentType);
};

View file

@ -0,0 +1,818 @@
/* 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;
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
const APPLICATION_CID = Components.ID("fe74cf80-aa2d-11db-abbd-0800200c9a66");
const APPLICATION_CONTRACTID = "@mozilla.org/fuel/application;1";
//=================================================
// Singleton that holds services and utilities
var Utilities = {
get bookmarks() {
let bookmarks = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].
getService(Ci.nsINavBookmarksService);
this.__defineGetter__("bookmarks", function() bookmarks);
return this.bookmarks;
},
get bookmarksObserver() {
let bookmarksObserver = new BookmarksObserver();
this.__defineGetter__("bookmarksObserver", function() bookmarksObserver);
return this.bookmarksObserver;
},
get annotations() {
let annotations = Cc["@mozilla.org/browser/annotation-service;1"].
getService(Ci.nsIAnnotationService);
this.__defineGetter__("annotations", function() annotations);
return this.annotations;
},
get history() {
let history = Cc["@mozilla.org/browser/nav-history-service;1"].
getService(Ci.nsINavHistoryService);
this.__defineGetter__("history", function() history);
return this.history;
},
get windowMediator() {
let windowMediator = Cc["@mozilla.org/appshell/window-mediator;1"].
getService(Ci.nsIWindowMediator);
this.__defineGetter__("windowMediator", function() windowMediator);
return this.windowMediator;
},
makeURI: function fuelutil_makeURI(aSpec) {
if (!aSpec)
return null;
var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
return ios.newURI(aSpec, null, null);
},
free: function fuelutil_free() {
delete this.bookmarks;
delete this.bookmarksObserver;
delete this.annotations;
delete this.history;
delete this.windowMediator;
}
};
//=================================================
// Window implementation
var fuelWindowMap = new WeakMap();
function getWindow(aWindow) {
let fuelWindow = fuelWindowMap.get(aWindow);
if (!fuelWindow) {
fuelWindow = new Window(aWindow);
fuelWindowMap.set(aWindow, fuelWindow);
}
return fuelWindow;
}
// Don't call new Window() directly; use getWindow instead.
function Window(aWindow) {
this._window = aWindow;
this._events = new Events();
this._watch("TabOpen");
this._watch("TabMove");
this._watch("TabClose");
this._watch("TabSelect");
}
Window.prototype = {
get events() {
return this._events;
},
get _tabbrowser() {
return this._window.getBrowser();
},
/*
* Helper used to setup event handlers on the XBL element. Note that the events
* are actually dispatched to tabs, so we capture them.
*/
_watch: function win_watch(aType) {
this._tabbrowser.tabContainer.addEventListener(aType, this,
/* useCapture = */ true);
},
handleEvent: function win_handleEvent(aEvent) {
this._events.dispatch(aEvent.type, getBrowserTab(this, aEvent.originalTarget.linkedBrowser));
},
get tabs() {
var tabs = [];
var browsers = this._tabbrowser.browsers;
for (var i=0; i<browsers.length; i++)
tabs.push(getBrowserTab(this, browsers[i]));
return tabs;
},
get activeTab() {
return getBrowserTab(this, this._tabbrowser.selectedBrowser);
},
open: function win_open(aURI) {
return getBrowserTab(this, this._tabbrowser.addTab(aURI.spec).linkedBrowser);
},
QueryInterface: XPCOMUtils.generateQI([Ci.fuelIWindow])
};
//=================================================
// BrowserTab implementation
var fuelBrowserTabMap = new WeakMap();
function getBrowserTab(aFUELWindow, aBrowser) {
let fuelBrowserTab = fuelBrowserTabMap.get(aBrowser);
if (!fuelBrowserTab) {
fuelBrowserTab = new BrowserTab(aFUELWindow, aBrowser);
fuelBrowserTabMap.set(aBrowser, fuelBrowserTab);
}
else {
// This tab may have moved to another window, so make sure its cached
// window is up-to-date.
fuelBrowserTab._window = aFUELWindow;
}
return fuelBrowserTab;
}
// Don't call new BrowserTab() directly; call getBrowserTab instead.
function BrowserTab(aFUELWindow, aBrowser) {
this._window = aFUELWindow;
this._browser = aBrowser;
this._events = new Events();
this._watch("load");
}
BrowserTab.prototype = {
get _tabbrowser() {
return this._window._tabbrowser;
},
get uri() {
return this._browser.currentURI;
},
get index() {
var tabs = this._tabbrowser.tabs;
for (var i=0; i<tabs.length; i++) {
if (tabs[i].linkedBrowser == this._browser)
return i;
}
return -1;
},
get events() {
return this._events;
},
get window() {
return this._window;
},
get document() {
return this._browser.contentDocument;
},
/*
* Helper used to setup event handlers on the XBL element
*/
_watch: function bt_watch(aType) {
this._browser.addEventListener(aType, this,
/* useCapture = */ true);
},
handleEvent: function bt_handleEvent(aEvent) {
if (aEvent.type == "load") {
if (!(aEvent.originalTarget instanceof Ci.nsIDOMDocument))
return;
if (aEvent.originalTarget.defaultView instanceof Ci.nsIDOMWindow &&
aEvent.originalTarget.defaultView.frameElement)
return;
}
this._events.dispatch(aEvent.type, this);
},
/*
* Helper used to determine the index offset of the browsertab
*/
_getTab: function bt_gettab() {
var tabs = this._tabbrowser.tabs;
return tabs[this.index] || null;
},
load: function bt_load(aURI) {
this._browser.loadURI(aURI.spec, null, null);
},
focus: function bt_focus() {
this._tabbrowser.selectedTab = this._getTab();
this._tabbrowser.focus();
},
close: function bt_close() {
this._tabbrowser.removeTab(this._getTab());
},
moveBefore: function bt_movebefore(aBefore) {
this._tabbrowser.moveTabTo(this._getTab(), aBefore.index);
},
moveToEnd: function bt_moveend() {
this._tabbrowser.moveTabTo(this._getTab(), this._tabbrowser.browsers.length);
},
QueryInterface: XPCOMUtils.generateQI([Ci.fuelIBrowserTab])
};
//=================================================
// Annotations implementation
function Annotations(aId) {
this._id = aId;
}
Annotations.prototype = {
get names() {
return Utilities.annotations.getItemAnnotationNames(this._id);
},
has: function ann_has(aName) {
return Utilities.annotations.itemHasAnnotation(this._id, aName);
},
get: function ann_get(aName) {
if (this.has(aName))
return Utilities.annotations.getItemAnnotation(this._id, aName);
return null;
},
set: function ann_set(aName, aValue, aExpiration) {
Utilities.annotations.setItemAnnotation(this._id, aName, aValue, 0, aExpiration);
},
remove: function ann_remove(aName) {
if (aName)
Utilities.annotations.removeItemAnnotation(this._id, aName);
},
QueryInterface: XPCOMUtils.generateQI([Ci.fuelIAnnotations])
};
//=================================================
// BookmarksObserver implementation (internal class)
//
// BookmarksObserver is a global singleton which watches the browser's
// bookmarks and sends you events when things change.
//
// You can register three different kinds of event listeners on
// BookmarksObserver, using addListener, addFolderListener, and
// addRootlistener.
//
// - addListener(aId, aEvent, aListener) lets you listen to a specific
// bookmark. You can listen to the "change", "move", and "remove" events.
//
// - addFolderListener(aId, aEvent, aListener) lets you listen to a specific
// bookmark folder. You can listen to "addchild" and "removechild".
//
// - addRootListener(aEvent, aListener) lets you listen to the root bookmark
// node. This lets you hear "add", "remove", and "change" events on all
// bookmarks.
//
function BookmarksObserver() {
this._eventsDict = {};
this._folderEventsDict = {};
this._rootEvents = new Events();
Utilities.bookmarks.addObserver(this, /* ownsWeak = */ true);
}
BookmarksObserver.prototype = {
onBeginUpdateBatch: function () {},
onEndUpdateBatch: function () {},
onItemVisited: function () {},
onItemAdded: function bo_onItemAdded(aId, aFolder, aIndex, aItemType, aURI) {
this._rootEvents.dispatch("add", aId);
this._dispatchToEvents("addchild", aId, this._folderEventsDict[aFolder]);
},
onItemRemoved: function bo_onItemRemoved(aId, aFolder, aIndex) {
this._rootEvents.dispatch("remove", aId);
this._dispatchToEvents("remove", aId, this._eventsDict[aId]);
this._dispatchToEvents("removechild", aId, this._folderEventsDict[aFolder]);
},
onItemChanged: function bo_onItemChanged(aId, aProperty, aIsAnnotationProperty, aValue) {
this._rootEvents.dispatch("change", aProperty);
this._dispatchToEvents("change", aProperty, this._eventsDict[aId]);
},
onItemMoved: function bo_onItemMoved(aId, aOldParent, aOldIndex, aNewParent, aNewIndex) {
this._dispatchToEvents("move", aId, this._eventsDict[aId]);
},
_dispatchToEvents: function bo_dispatchToEvents(aEvent, aData, aEvents) {
if (aEvents) {
aEvents.dispatch(aEvent, aData);
}
},
_addListenerToDict: function bo_addListenerToDict(aId, aEvent, aListener, aDict) {
var events = aDict[aId];
if (!events) {
events = new Events();
aDict[aId] = events;
}
events.addListener(aEvent, aListener);
},
_removeListenerFromDict: function bo_removeListenerFromDict(aId, aEvent, aListener, aDict) {
var events = aDict[aId];
if (!events) {
return;
}
events.removeListener(aEvent, aListener);
if (events._listeners.length == 0) {
delete aDict[aId];
}
},
addListener: function bo_addListener(aId, aEvent, aListener) {
this._addListenerToDict(aId, aEvent, aListener, this._eventsDict);
},
removeListener: function bo_removeListener(aId, aEvent, aListener) {
this._removeListenerFromDict(aId, aEvent, aListener, this._eventsDict);
},
addFolderListener: function addFolderListener(aId, aEvent, aListener) {
this._addListenerToDict(aId, aEvent, aListener, this._folderEventsDict);
},
removeFolderListener: function removeFolderListener(aId, aEvent, aListener) {
this._removeListenerFromDict(aId, aEvent, aListener, this._folderEventsDict);
},
addRootListener: function addRootListener(aEvent, aListener) {
this._rootEvents.addListener(aEvent, aListener);
},
removeRootListener: function removeRootListener(aEvent, aListener) {
this._rootEvents.removeListener(aEvent, aListener);
},
QueryInterface: XPCOMUtils.generateQI([Ci.nsINavBookmarksObserver,
Ci.nsISupportsWeakReference])
};
//=================================================
// Bookmark implementation
//
// Bookmark event listeners are stored in BookmarksObserver, not in the
// Bookmark objects themselves. Thus, you don't have to hold on to a Bookmark
// object in order for your event listener to stay valid, and Bookmark objects
// not kept alive by the extension can be GC'ed.
//
// A consequence of this is that if you have two different Bookmark objects x
// and y for the same bookmark (i.e., x != y but x.id == y.id), and you do
//
// x.addListener("foo", fun);
// y.removeListener("foo", fun);
//
// the second line will in fact remove the listener added in the first line.
//
function Bookmark(aId, aParent, aType) {
this._id = aId;
this._parent = aParent;
this._type = aType || "bookmark";
this._annotations = new Annotations(this._id);
// Our _events object forwards to bookmarksObserver.
var self = this;
this._events = {
addListener: function bookmarkevents_al(aEvent, aListener) {
Utilities.bookmarksObserver.addListener(self._id, aEvent, aListener);
},
removeListener: function bookmarkevents_rl(aEvent, aListener) {
Utilities.bookmarksObserver.removeListener(self._id, aEvent, aListener);
},
QueryInterface: XPCOMUtils.generateQI([Ci.extIEvents])
};
// For our onItemMoved listener, which updates this._parent.
Utilities.bookmarks.addObserver(this, /* ownsWeak = */ true);
}
Bookmark.prototype = {
get id() {
return this._id;
},
get title() {
return Utilities.bookmarks.getItemTitle(this._id);
},
set title(aTitle) {
Utilities.bookmarks.setItemTitle(this._id, aTitle);
},
get uri() {
return Utilities.bookmarks.getBookmarkURI(this._id);
},
set uri(aURI) {
return Utilities.bookmarks.changeBookmarkURI(this._id, aURI);
},
get description() {
return this._annotations.get("bookmarkProperties/description");
},
set description(aDesc) {
this._annotations.set("bookmarkProperties/description", aDesc, Ci.nsIAnnotationService.EXPIRE_NEVER);
},
get keyword() {
return Utilities.bookmarks.getKeywordForBookmark(this._id);
},
set keyword(aKeyword) {
Utilities.bookmarks.setKeywordForBookmark(this._id, aKeyword);
},
get type() {
return this._type;
},
get parent() {
return this._parent;
},
set parent(aFolder) {
Utilities.bookmarks.moveItem(this._id, aFolder.id, Utilities.bookmarks.DEFAULT_INDEX);
// this._parent is updated in onItemMoved
},
get annotations() {
return this._annotations;
},
get events() {
return this._events;
},
remove : function bm_remove() {
Utilities.bookmarks.removeItem(this._id);
},
onBeginUpdateBatch: function () {},
onEndUpdateBatch: function () {},
onItemAdded: function () {},
onItemVisited: function () {},
onItemRemoved: function () {},
onItemChanged: function () {},
onItemMoved: function(aId, aOldParent, aOldIndex, aNewParent, aNewIndex) {
if (aId == this._id) {
this._parent = new BookmarkFolder(aNewParent, Utilities.bookmarks.getFolderIdForItem(aNewParent));
}
},
QueryInterface: XPCOMUtils.generateQI([Ci.fuelIBookmark,
Ci.nsINavBookmarksObserver,
Ci.nsISupportsWeakReference])
};
//=================================================
// BookmarkFolder implementation
//
// As with Bookmark, events on BookmarkFolder are handled by the
// BookmarksObserver singleton.
//
function BookmarkFolder(aId, aParent) {
this._id = aId;
this._parent = aParent;
this._annotations = new Annotations(this._id);
// Our event listeners are handled by the BookmarksObserver singleton. This
// is a bit complicated because there are three different kinds of events we
// might want to listen to here:
//
// - If this._parent is null, we're the root bookmark folder, and all our
// listeners should be root listeners.
//
// - Otherwise, events ending with "child" (addchild, removechild) are
// handled by a folder listener.
//
// - Other events are handled by a vanilla bookmark listener.
var self = this;
this._events = {
addListener: function bmfevents_al(aEvent, aListener) {
if (self._parent) {
if (/child$/.test(aEvent)) {
Utilities.bookmarksObserver.addFolderListener(self._id, aEvent, aListener);
}
else {
Utilities.bookmarksObserver.addListener(self._id, aEvent, aListener);
}
}
else {
Utilities.bookmarksObserver.addRootListener(aEvent, aListener);
}
},
removeListener: function bmfevents_rl(aEvent, aListener) {
if (self._parent) {
if (/child$/.test(aEvent)) {
Utilities.bookmarksObserver.removeFolderListener(self._id, aEvent, aListener);
}
else {
Utilities.bookmarksObserver.removeListener(self._id, aEvent, aListener);
}
}
else {
Utilities.bookmarksObserver.removeRootListener(aEvent, aListener);
}
},
QueryInterface: XPCOMUtils.generateQI([Ci.extIEvents])
};
// For our onItemMoved listener, which updates this._parent.
Utilities.bookmarks.addObserver(this, /* ownsWeak = */ true);
}
BookmarkFolder.prototype = {
get id() {
return this._id;
},
get title() {
return Utilities.bookmarks.getItemTitle(this._id);
},
set title(aTitle) {
Utilities.bookmarks.setItemTitle(this._id, aTitle);
},
get description() {
return this._annotations.get("bookmarkProperties/description");
},
set description(aDesc) {
this._annotations.set("bookmarkProperties/description", aDesc, Ci.nsIAnnotationService.EXPIRE_NEVER);
},
get type() {
return "folder";
},
get parent() {
return this._parent;
},
set parent(aFolder) {
Utilities.bookmarks.moveItem(this._id, aFolder.id, Utilities.bookmarks.DEFAULT_INDEX);
// this._parent is updated in onItemMoved
},
get annotations() {
return this._annotations;
},
get events() {
return this._events;
},
get children() {
var items = [];
var options = Utilities.history.getNewQueryOptions();
var query = Utilities.history.getNewQuery();
query.setFolders([this._id], 1);
var result = Utilities.history.executeQuery(query, options);
var rootNode = result.root;
rootNode.containerOpen = true;
var cc = rootNode.childCount;
for (var i=0; i<cc; ++i) {
var node = rootNode.getChild(i);
if (node.type == node.RESULT_TYPE_FOLDER) {
var folder = new BookmarkFolder(node.itemId, this._id);
items.push(folder);
}
else if (node.type == node.RESULT_TYPE_SEPARATOR) {
var separator = new Bookmark(node.itemId, this._id, "separator");
items.push(separator);
}
else {
var bookmark = new Bookmark(node.itemId, this._id, "bookmark");
items.push(bookmark);
}
}
rootNode.containerOpen = false;
return items;
},
addBookmark: function bmf_addbm(aTitle, aUri) {
var newBookmarkID = Utilities.bookmarks.insertBookmark(this._id, aUri, Utilities.bookmarks.DEFAULT_INDEX, aTitle);
var newBookmark = new Bookmark(newBookmarkID, this, "bookmark");
return newBookmark;
},
addSeparator: function bmf_addsep() {
var newBookmarkID = Utilities.bookmarks.insertSeparator(this._id, Utilities.bookmarks.DEFAULT_INDEX);
var newBookmark = new Bookmark(newBookmarkID, this, "separator");
return newBookmark;
},
addFolder: function bmf_addfolder(aTitle) {
var newFolderID = Utilities.bookmarks.createFolder(this._id, aTitle, Utilities.bookmarks.DEFAULT_INDEX);
var newFolder = new BookmarkFolder(newFolderID, this);
return newFolder;
},
remove: function bmf_remove() {
Utilities.bookmarks.removeItem(this._id);
},
// observer
onBeginUpdateBatch: function () {},
onEndUpdateBatch : function () {},
onItemAdded : function () {},
onItemRemoved : function () {},
onItemChanged : function () {},
onItemMoved: function bf_onItemMoved(aId, aOldParent, aOldIndex, aNewParent, aNewIndex) {
if (this._id == aId) {
this._parent = new BookmarkFolder(aNewParent, Utilities.bookmarks.getFolderIdForItem(aNewParent));
}
},
QueryInterface: XPCOMUtils.generateQI([Ci.fuelIBookmarkFolder,
Ci.nsINavBookmarksObserver,
Ci.nsISupportsWeakReference])
};
//=================================================
// BookmarkRoots implementation
function BookmarkRoots() {
}
BookmarkRoots.prototype = {
get menu() {
if (!this._menu)
this._menu = new BookmarkFolder(Utilities.bookmarks.bookmarksMenuFolder, null);
return this._menu;
},
get toolbar() {
if (!this._toolbar)
this._toolbar = new BookmarkFolder(Utilities.bookmarks.toolbarFolder, null);
return this._toolbar;
},
get tags() {
if (!this._tags)
this._tags = new BookmarkFolder(Utilities.bookmarks.tagsFolder, null);
return this._tags;
},
get unfiled() {
if (!this._unfiled)
this._unfiled = new BookmarkFolder(Utilities.bookmarks.unfiledBookmarksFolder, null);
return this._unfiled;
},
QueryInterface: XPCOMUtils.generateQI([Ci.fuelIBookmarkRoots])
};
//=================================================
// Factory - Treat Application as a singleton
// XXX This is required, because we're registered for the 'JavaScript global
// privileged property' category, whose handler always calls createInstance.
// See bug 386535.
var gSingleton = null;
var ApplicationFactory = {
createInstance: function af_ci(aOuter, aIID) {
if (aOuter != null)
throw Components.results.NS_ERROR_NO_AGGREGATION;
if (gSingleton == null) {
gSingleton = new Application();
}
return gSingleton.QueryInterface(aIID);
}
};
#include ../../../toolkit/components/exthelper/extApplication.js
//=================================================
// Application constructor
function Application() {
this.initToolkitHelpers();
}
//=================================================
// Application implementation
function ApplicationPrototype() {
// for nsIClassInfo + XPCOMUtils
this.classID = APPLICATION_CID;
// redefine the default factory for XPCOMUtils
this._xpcom_factory = ApplicationFactory;
// for nsISupports
this.QueryInterface = XPCOMUtils.generateQI([
Ci.fuelIApplication,
Ci.extIApplication,
Ci.nsIObserver,
Ci.nsISupportsWeakReference
]);
// for nsIClassInfo
this.classInfo = XPCOMUtils.generateCI({
classID: APPLICATION_CID,
contractID: APPLICATION_CONTRACTID,
interfaces: [
Ci.fuelIApplication,
Ci.extIApplication,
Ci.nsIObserver
],
flags: Ci.nsIClassInfo.SINGLETON
});
// for nsIObserver
this.observe = function (aSubject, aTopic, aData) {
// Call the extApplication version of this function first
var superPrototype = Object.getPrototypeOf(Object.getPrototypeOf(this));
superPrototype.observe.call(this, aSubject, aTopic, aData);
if (aTopic == "xpcom-shutdown") {
this._obs.removeObserver(this, "xpcom-shutdown");
Utilities.free();
}
};
Object.defineProperty(this, "bookmarks", {
get: function bookmarks () {
let bookmarks = new BookmarkRoots();
Object.defineProperty(this, "bookmarks", { value: bookmarks });
return this.bookmarks;
},
enumerable: true,
configurable: true
});
Object.defineProperty(this, "windows", {
get: function windows() {
var win = [];
var browserEnum = Utilities.windowMediator.getEnumerator("navigator:browser");
while (browserEnum.hasMoreElements())
win.push(getWindow(browserEnum.getNext()));
return win;
},
enumerable: true,
configurable: true
});
Object.defineProperty(this, "activeWindow", {
get: () => getWindow(Utilities.windowMediator.getMostRecentWindow("navigator:browser")),
enumerable: true,
configurable: true
});
};
// set the proto, defined in extApplication.js
ApplicationPrototype.prototype = extApplication.prototype;
Application.prototype = new ApplicationPrototype();
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([Application]);

View file

@ -0,0 +1,3 @@
component {fe74cf80-aa2d-11db-abbd-0800200c9a66} fuelApplication.js
contract @mozilla.org/fuel/application;1 {fe74cf80-aa2d-11db-abbd-0800200c9a66}
category JavaScript-global-privileged-property Application @mozilla.org/fuel/application;1

View file

@ -0,0 +1,347 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsISupports.idl"
#include "extIApplication.idl"
interface nsIVariant;
interface nsIURI;
interface nsIDOMHTMLDocument;
interface fuelIBookmarkFolder;
interface fuelIBrowserTab;
/**
* Interface representing a collection of annotations associated
* with a bookmark or bookmark folder.
*/
[scriptable, uuid(335c9292-91a1-4ca0-ad0b-07d5f63ed6cd)]
interface fuelIAnnotations : nsISupports
{
/**
* Array of the annotation names associated with the owning item
*/
readonly attribute nsIVariant names;
/**
* Determines if an annotation exists with the given name.
* @param aName
* The name of the annotation
* @returns true if an annotation exists with the given name,
* false otherwise.
*/
boolean has(in AString aName);
/**
* Gets the value of an annotation with the given name.
* @param aName
* The name of the annotation
* @returns A variant containing the value of the annotation. Supports
* string, boolean and number.
*/
nsIVariant get(in AString aName);
/**
* Sets the value of an annotation with the given name.
* @param aName
* The name of the annotation
* @param aValue
* The new value of the annotation. Supports string, boolean
* and number
* @param aExpiration
* The expiration policy for the annotation.
* See nsIAnnotationService.
*/
void set(in AString aName, in nsIVariant aValue, in int32_t aExpiration);
/**
* Removes the named annotation from the owner item.
* @param aName
* The name of annotation.
*/
void remove(in AString aName);
};
/**
* Interface representing a bookmark item.
*/
[scriptable, uuid(808585b6-7568-4b26-8c62-545221bf2b8c)]
interface fuelIBookmark : nsISupports
{
/**
* The id of the bookmark.
*/
readonly attribute long long id;
/**
* The title of the bookmark.
*/
attribute AString title;
/**
* The uri of the bookmark.
*/
attribute nsIURI uri;
/**
* The description of the bookmark.
*/
attribute AString description;
/**
* The keyword associated with the bookmark.
*/
attribute AString keyword;
/**
* The type of the bookmark.
* values: "bookmark", "separator"
*/
readonly attribute AString type;
/**
* The parent folder of the bookmark.
*/
attribute fuelIBookmarkFolder parent;
/**
* The annotations object for the bookmark.
*/
readonly attribute fuelIAnnotations annotations;
/**
* The events object for the bookmark.
* supports: "remove", "change", "visit", "move"
*/
readonly attribute extIEvents events;
/**
* Removes the item from the parent folder. Used to
* delete a bookmark or separator
*/
void remove();
};
/**
* Interface representing a bookmark folder. Folders
* can hold bookmarks, separators and other folders.
*/
[scriptable, uuid(9f42fe20-52de-4a55-8632-a459c7716aa0)]
interface fuelIBookmarkFolder : nsISupports
{
/**
* The id of the folder.
*/
readonly attribute long long id;
/**
* The title of the folder.
*/
attribute AString title;
/**
* The description of the folder.
*/
attribute AString description;
/**
* The type of the folder.
* values: "folder"
*/
readonly attribute AString type;
/**
* The parent folder of the folder.
*/
attribute fuelIBookmarkFolder parent;
/**
* The annotations object for the folder.
*/
readonly attribute fuelIAnnotations annotations;
/**
* The events object for the folder.
* supports: "add", "addchild", "remove", "removechild", "change", "move"
*/
readonly attribute extIEvents events;
/**
* Array of all bookmarks, separators and folders contained
* in this folder.
*/
readonly attribute nsIVariant children;
/**
* Adds a new child bookmark to this folder.
* @param aTitle
* The title of bookmark.
* @param aURI
* The uri of bookmark.
*/
fuelIBookmark addBookmark(in AString aTitle, in nsIURI aURI);
/**
* Adds a new child separator to this folder.
*/
fuelIBookmark addSeparator();
/**
* Adds a new child folder to this folder.
* @param aTitle
* The title of folder.
*/
fuelIBookmarkFolder addFolder(in AString aTitle);
/**
* Removes the folder from the parent folder.
*/
void remove();
};
/**
* Interface representing a container for bookmark roots. Roots
* are the top level parents for the various types of bookmarks in the system.
*/
[scriptable, uuid(c9a80870-eb3c-11dc-95ff-0800200c9a66)]
interface fuelIBookmarkRoots : nsISupports
{
/**
* The folder for the 'bookmarks menu' root.
*/
readonly attribute fuelIBookmarkFolder menu;
/**
* The folder for the 'personal toolbar' root.
*/
readonly attribute fuelIBookmarkFolder toolbar;
/**
* The folder for the 'tags' root.
*/
readonly attribute fuelIBookmarkFolder tags;
/**
* The folder for the 'unfiled bookmarks' root.
*/
readonly attribute fuelIBookmarkFolder unfiled;
};
/**
* Interface representing a browser window.
*/
[scriptable, uuid(207edb28-eb5e-424e-a862-b0e97C8de866)]
interface fuelIWindow : nsISupports
{
/**
* A collection of browser tabs within the browser window.
*/
readonly attribute nsIVariant tabs;
/**
* The currently-active tab within the browser window.
*/
readonly attribute fuelIBrowserTab activeTab;
/**
* Open a new browser tab, pointing to the specified URI.
* @param aURI
* The uri to open the browser tab to
*/
fuelIBrowserTab open(in nsIURI aURI);
/**
* The events object for the browser window.
* supports: "TabOpen", "TabClose", "TabMove", "TabSelect"
*/
readonly attribute extIEvents events;
};
/**
* Interface representing a browser tab.
*/
[scriptable, uuid(3073ceff-777c-41ce-9ace-ab37268147c1)]
interface fuelIBrowserTab : nsISupports
{
/**
* The current uri of this tab.
*/
readonly attribute nsIURI uri;
/**
* The current index of this tab in the browser window.
*/
readonly attribute int32_t index;
/**
* The browser window that is holding the tab.
*/
readonly attribute fuelIWindow window;
/**
* The content document of the browser tab.
*/
readonly attribute nsIDOMHTMLDocument document;
/**
* The events object for the browser tab.
* supports: "load"
*/
readonly attribute extIEvents events;
/**
* Load a new URI into this browser tab.
* @param aURI
* The uri to load into the browser tab
*/
void load(in nsIURI aURI);
/**
* Give focus to this browser tab, and bring it to the front.
*/
void focus();
/**
* Close the browser tab. This may not actually close the tab
* as script may abort the close operation.
*/
void close();
/**
* Moves this browser tab before another browser tab within the window.
* @param aBefore
* The tab before which the target tab will be moved
*/
void moveBefore(in fuelIBrowserTab aBefore);
/**
* Move this browser tab to the last tab within the window.
*/
void moveToEnd();
};
/**
* Interface for managing and accessing the applications systems
*/
[scriptable, uuid(fe74cf80-aa2d-11db-abbd-0800200c9a66)]
interface fuelIApplication : extIApplication
{
/**
* The root bookmarks object for the application.
* Contains all the bookmark roots in the system.
*/
readonly attribute fuelIBookmarkRoots bookmarks;
/**
* An array of browser windows within the application.
*/
readonly attribute nsIVariant windows;
/**
* The currently active browser window.
*/
readonly attribute fuelIWindow activeWindow;
};

View file

@ -0,0 +1,20 @@
# -*- 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/.
XPIDL_SOURCES += [
'fuelIApplication.idl',
]
XPIDL_MODULE = 'fuel'
EXTRA_COMPONENTS += [
'fuelApplication.manifest',
]
EXTRA_PP_COMPONENTS += [
'fuelApplication.js',
]

View file

@ -0,0 +1,14 @@
component {6F8BB968-C14F-4D6F-9733-6C6737B35DCE} ProfileMigrator.js
contract @mozilla.org/toolkit/profile-migrator;1 {6F8BB968-C14F-4D6F-9733-6C6737B35DCE}
component {4cec1de4-1671-4fc3-a53e-6c539dc77a26} ChromeProfileMigrator.js
contract @mozilla.org/profile/migrator;1?app=browser&type=chrome {4cec1de4-1671-4fc3-a53e-6c539dc77a26}
component {91185366-ba97-4438-acba-48deaca63386} FirefoxProfileMigrator.js
contract @mozilla.org/profile/migrator;1?app=browser&type=firefox {91185366-ba97-4438-acba-48deaca63386}
#ifdef HAS_IE_MIGRATOR
component {3d2532e3-4932-4774-b7ba-968f5899d3a4} IEProfileMigrator.js
contract @mozilla.org/profile/migrator;1?app=browser&type=ie {3d2532e3-4932-4774-b7ba-968f5899d3a4}
#endif
#ifdef HAS_SAFARI_MIGRATOR
component {4b609ecf-60b2-4655-9df4-dc149e474da1} SafariProfileMigrator.js
contract @mozilla.org/profile/migrator;1?app=browser&type=safari {4b609ecf-60b2-4655-9df4-dc149e474da1}
#endif

View file

@ -0,0 +1,463 @@
/* -*- Mode: js; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* vim: sw=2 ts=2 sts=2 et */
/* 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 Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
const Cr = Components.results;
const FILE_INPUT_STREAM_CID = "@mozilla.org/network/file-input-stream;1";
const S100NS_FROM1601TO1970 = 0x19DB1DED53E8000;
const S100NS_PER_MS = 10;
const AUTH_TYPE = {
SCHEME_HTML: 0,
SCHEME_BASIC: 1,
SCHEME_DIGEST: 2
};
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/NetUtil.jsm");
Cu.import("resource://gre/modules/FileUtils.jsm");
Cu.import("resource:///modules/MigrationUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
"resource://gre/modules/PlacesUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "OSCrypto",
"resource://gre/modules/OSCrypto.jsm");
/**
* Convert Chrome time format to Date object
*
* @param aTime
* Chrome time
* @return converted Date object
* @note Google Chrome uses FILETIME / 10 as time.
* FILETIME is based on same structure of Windows.
*/
function chromeTimeToDate(aTime)
{
return new Date((aTime * S100NS_PER_MS - S100NS_FROM1601TO1970 ) / 10000);
}
/**
* Insert bookmark items into specific folder.
*
* @param aFolderId
* id of folder where items will be inserted
* @param aItems
* bookmark items to be inserted
*/
function insertBookmarkItems(aFolderId, aItems)
{
for (let i = 0; i < aItems.length; i++) {
let item = aItems[i];
try {
if (item.type == "url") {
PlacesUtils.bookmarks.insertBookmark(aFolderId,
NetUtil.newURI(item.url),
PlacesUtils.bookmarks.DEFAULT_INDEX,
item.name);
} else if (item.type == "folder") {
let newFolderId =
PlacesUtils.bookmarks.createFolder(aFolderId,
item.name,
PlacesUtils.bookmarks.DEFAULT_INDEX);
insertBookmarkItems(newFolderId, item.children);
}
} catch (e) {
Cu.reportError(e);
}
}
}
function ChromeProfileMigrator() {
let chromeUserDataFolder = FileUtils.getDir(
#ifdef XP_WIN
"LocalAppData", ["Google", "Chrome", "User Data"]
#elifdef XP_MACOSX
"ULibDir", ["Application Support", "Google", "Chrome"]
#else
"Home", [".config", "google-chrome"]
#endif
, false);
this._chromeUserDataFolder = chromeUserDataFolder.exists() ?
chromeUserDataFolder : null;
}
ChromeProfileMigrator.prototype = Object.create(MigratorPrototype);
ChromeProfileMigrator.prototype.getResources =
function Chrome_getResources(aProfile) {
if (this._chromeUserDataFolder) {
let profileFolder = this._chromeUserDataFolder.clone();
profileFolder.append(aProfile.id);
if (profileFolder.exists()) {
let possibleResources = [GetBookmarksResource(profileFolder),
GetHistoryResource(profileFolder),
GetCookiesResource(profileFolder),
#ifdef XP_WIN
GetWindowsPasswordsResource(profileFolder)
#endif
];
return [r for each (r in possibleResources) if (r != null)];
}
}
return [];
};
Object.defineProperty(ChromeProfileMigrator.prototype, "sourceProfiles", {
get: function Chrome_sourceProfiles() {
if ("__sourceProfiles" in this)
return this.__sourceProfiles;
if (!this._chromeUserDataFolder)
return [];
let profiles = [];
try {
// Local State is a JSON file that contains profile info.
let localState = this._chromeUserDataFolder.clone();
localState.append("Local State");
if (!localState.exists())
throw new Error("Chrome's 'Local State' file does not exist.");
if (!localState.isReadable())
throw new Error("Chrome's 'Local State' file could not be read.");
let fstream = Cc[FILE_INPUT_STREAM_CID].createInstance(Ci.nsIFileInputStream);
fstream.init(localState, -1, 0, 0);
let inputStream = NetUtil.readInputStreamToString(fstream, fstream.available(),
{ charset: "UTF-8" });
let info_cache = JSON.parse(inputStream).profile.info_cache;
for (let profileFolderName in info_cache) {
let profileFolder = this._chromeUserDataFolder.clone();
profileFolder.append(profileFolderName);
profiles.push({
id: profileFolderName,
name: info_cache[profileFolderName].name || profileFolderName,
});
}
} catch (e) {
Cu.reportError("Error detecting Chrome profiles: " + e);
// If we weren't able to detect any profiles above, fallback to the Default profile.
let defaultProfileFolder = this._chromeUserDataFolder.clone();
defaultProfileFolder.append("Default");
if (defaultProfileFolder.exists()) {
profiles = [{
id: "Default",
name: "Default",
}];
}
}
// Only list profiles from which any data can be imported
return this.__sourceProfiles = profiles.filter(function(profile) {
let resources = this.getResources(profile);
return resources && resources.length > 0;
}, this);
}
});
Object.defineProperty(ChromeProfileMigrator.prototype, "sourceHomePageURL", {
get: function Chrome_sourceHomePageURL() {
let prefsFile = this._chromeUserDataFolder.clone();
prefsFile.append("Preferences");
if (prefsFile.exists()) {
// XXX reading and parsing JSON is synchronous.
let fstream = Cc[FILE_INPUT_STREAM_CID].
createInstance(Ci.nsIFileInputStream);
fstream.init(file, -1, 0, 0);
try {
return JSON.parse(
NetUtil.readInputStreamToString(fstream, fstream.available(),
{ charset: "UTF-8" })
).homepage;
}
catch(e) {
Cu.reportError("Error parsing Chrome's preferences file: " + e);
}
}
return "";
}
});
function GetBookmarksResource(aProfileFolder) {
let bookmarksFile = aProfileFolder.clone();
bookmarksFile.append("Bookmarks");
if (!bookmarksFile.exists())
return null;
return {
type: MigrationUtils.resourceTypes.BOOKMARKS,
migrate: function(aCallback) {
NetUtil.asyncFetch(bookmarksFile, MigrationUtils.wrapMigrateFunction(
function(aInputStream, aResultCode) {
if (!Components.isSuccessCode(aResultCode))
throw new Error("Could not read Bookmarks file");
// Parse Chrome bookmark file that is JSON format
let bookmarkJSON = NetUtil.readInputStreamToString(
aInputStream, aInputStream.available(), { charset : "UTF-8" });
let roots = JSON.parse(bookmarkJSON).roots;
PlacesUtils.bookmarks.runInBatchMode({
runBatched: function() {
// Importing bookmark bar items
if (roots.bookmark_bar.children &&
roots.bookmark_bar.children.length > 0) {
// Toolbar
let parentId = PlacesUtils.toolbarFolderId;
if (!MigrationUtils.isStartupMigration) {
parentId = MigrationUtils.createImportedBookmarksFolder(
"Chrome", parentId);
}
insertBookmarkItems(parentId, roots.bookmark_bar.children);
}
// Importing bookmark menu items
if (roots.other.children &&
roots.other.children.length > 0) {
// Bookmark menu
let parentId = PlacesUtils.bookmarksMenuFolderId;
if (!MigrationUtils.isStartupMigration) {
parentId = MigrationUtils.createImportedBookmarksFolder(
"Chrome", parentId);
}
insertBookmarkItems(parentId, roots.other.children);
}
}
}, null);
}, aCallback));
}
};
}
function GetHistoryResource(aProfileFolder) {
let historyFile = aProfileFolder.clone();
historyFile.append("History");
if (!historyFile.exists())
return null;
return {
type: MigrationUtils.resourceTypes.HISTORY,
migrate: function(aCallback) {
let dbConn = Services.storage.openUnsharedDatabase(historyFile);
let stmt = dbConn.createAsyncStatement(
"SELECT url, title, last_visit_time, typed_count FROM urls WHERE hidden = 0");
stmt.executeAsync({
handleResult : function(aResults) {
let places = [];
for (let row = aResults.getNextRow(); row; row = aResults.getNextRow()) {
try {
// if having typed_count, we changes transition type to typed.
let transType = PlacesUtils.history.TRANSITION_LINK;
if (row.getResultByName("typed_count") > 0)
transType = PlacesUtils.history.TRANSITION_TYPED;
places.push({
uri: NetUtil.newURI(row.getResultByName("url")),
title: row.getResultByName("title"),
visits: [{
transitionType: transType,
visitDate: chromeTimeToDate(
row.getResultByName(
"last_visit_time")) * 1000,
}],
});
} catch (e) {
Cu.reportError(e);
}
}
try {
PlacesUtils.asyncHistory.updatePlaces(places);
} catch (e) {
Cu.reportError(e);
}
},
handleError : function(aError) {
Cu.reportError("Async statement execution returned with '" +
aError.result + "', '" + aError.message + "'");
},
handleCompletion : function(aReason) {
dbConn.asyncClose();
aCallback(aReason == Ci.mozIStorageStatementCallback.REASON_FINISHED);
}
});
stmt.finalize();
}
};
}
function GetCookiesResource(aProfileFolder) {
let cookiesFile = aProfileFolder.clone();
cookiesFile.append("Cookies");
if (!cookiesFile.exists())
return null;
return {
type: MigrationUtils.resourceTypes.COOKIES,
migrate: function(aCallback) {
let dbConn = Services.storage.openUnsharedDatabase(cookiesFile);
let stmt = dbConn.createAsyncStatement(
"SELECT host_key, path, name, value, secure, httponly, expires_utc FROM cookies");
stmt.executeAsync({
handleResult : function(aResults) {
for (let row = aResults.getNextRow(); row; row = aResults.getNextRow()) {
let host_key = row.getResultByName("host_key");
if (host_key.match(/^\./)) {
// 1st character of host_key may be ".", so we have to remove it
host_key = host_key.substr(1);
}
try {
let expiresUtc =
chromeTimeToDate(row.getResultByName("expires_utc")) / 1000;
Services.cookies.add(host_key,
row.getResultByName("path"),
row.getResultByName("name"),
row.getResultByName("value"),
row.getResultByName("secure"),
row.getResultByName("httponly"),
false,
parseInt(expiresUtc));
} catch (e) {
Cu.reportError(e);
}
}
},
handleError : function(aError) {
Cu.reportError("Async statement execution returned with '" +
aError.result + "', '" + aError.message + "'");
},
handleCompletion : function(aReason) {
dbConn.asyncClose();
aCallback(aReason == Ci.mozIStorageStatementCallback.REASON_FINISHED);
},
});
stmt.finalize();
}
}
}
function GetWindowsPasswordsResource(aProfileFolder) {
let loginFile = aProfileFolder.clone();
loginFile.append("Login Data");
if (!loginFile.exists())
return null;
return {
type: MigrationUtils.resourceTypes.PASSWORDS,
migrate(aCallback) {
let dbConn = Services.storage.openUnsharedDatabase(loginFile);
let stmt = dbConn.createAsyncStatement(`
SELECT origin_url, action_url, username_element, username_value,
password_element, password_value, signon_realm, scheme, date_created,
times_used FROM logins WHERE blacklisted_by_user = 0`);
let crypto = new OSCrypto();
let utf8Converter = Cc["@mozilla.org/intl/utf8converterservice;1"].getService(Ci.nsIUTF8ConverterService);
stmt.executeAsync({
_rowToLoginInfo(row) {
let loginInfo = {
username: utf8Converter.convertURISpecToUTF8(row.getResultByName("username_value"), "UTF-8"),
password: utf8Converter.convertURISpecToUTF8(
crypto.decryptData(crypto.arrayToString(row.getResultByName("password_value")), null),
"UTF-8"),
hostName: NetUtil.newURI(row.getResultByName("origin_url")).prePath,
submitURL: null,
httpRealm: null,
usernameElement: row.getResultByName("username_element"),
passwordElement: row.getResultByName("password_element"),
timeCreated: chromeTimeToDate(row.getResultByName("date_created") + 0).getTime(),
timesUsed: row.getResultByName("times_used") + 0,
};
switch (row.getResultByName("scheme")) {
case AUTH_TYPE.SCHEME_HTML:
loginInfo.submitURL = NetUtil.newURI(row.getResultByName("action_url")).prePath;
break;
case AUTH_TYPE.SCHEME_BASIC:
case AUTH_TYPE.SCHEME_DIGEST:
// signon_realm format is URIrealm, so we need remove URI
loginInfo.httpRealm = row.getResultByName("signon_realm")
.substring(loginInfo.hostName.length + 1);
break;
default:
throw new Error("Login data scheme type not supported: " +
row.getResultByName("scheme"));
}
return loginInfo;
},
handleResult(aResults) {
for (let row = aResults.getNextRow(); row; row = aResults.getNextRow()) {
try {
let loginInfo = this._rowToLoginInfo(row);
let login = Cc["@mozilla.org/login-manager/loginInfo;1"].createInstance(Ci.nsILoginInfo);
login.init(loginInfo.hostName, loginInfo.submitURL, loginInfo.httpRealm,
loginInfo.username, loginInfo.password, loginInfo.usernameElement,
loginInfo.passwordElement);
login.QueryInterface(Ci.nsILoginMetaInfo);
login.timeCreated = loginInfo.timeCreated;
login.timeLastUsed = loginInfo.timeCreated;
login.timePasswordChanged = loginInfo.timeCreated;
login.timesUsed = loginInfo.timesUsed;
// Add the login only if there's not an existing entry
let logins = Services.logins.findLogins({}, login.hostname,
login.formSubmitURL,
login.httpRealm);
// Bug 1187190: Password changes should be propagated depending on timestamps.
if (!logins.some(l => login.matches(l, true))) {
Services.logins.addLogin(login);
}
} catch (e) {
Cu.reportError(e);
}
}
},
handleError(aError) {
Cu.reportError("Async statement execution returned with '" +
aError.result + "', '" + aError.message + "'");
},
handleCompletion(aReason) {
dbConn.asyncClose();
aCallback(aReason == Ci.mozIStorageStatementCallback.REASON_FINISHED);
crypto.finalize();
},
});
stmt.finalize();
}
};
}
ChromeProfileMigrator.prototype.classDescription = "Chrome Profile Migrator";
ChromeProfileMigrator.prototype.contractID = "@mozilla.org/profile/migrator;1?app=browser&type=chrome";
ChromeProfileMigrator.prototype.classID = Components.ID("{4cec1de4-1671-4fc3-a53e-6c539dc77a26}");
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([ChromeProfileMigrator]);

View file

@ -0,0 +1,117 @@
/* -*- Mode: js; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* vim: sw=2 ts=2 sts=2 et */
/* 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";
/*
* Migrates from a Pale Moon profile in a lossy manner in order to clean up a
* user's profile. Data is only migrated where the benefits outweigh the
* potential problems caused by importing undesired/invalid configurations
* from the source profile.
*/
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
Components.utils.import("resource:///modules/MigrationUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PlacesBackups",
"resource://gre/modules/PlacesBackups.jsm");
function FirefoxProfileMigrator() { }
FirefoxProfileMigrator.prototype = Object.create(MigratorPrototype);
FirefoxProfileMigrator.prototype._getAllProfiles = function () {
let allProfiles = new Map();
let profiles =
Components.classes["@mozilla.org/toolkit/profile-service;1"]
.getService(Components.interfaces.nsIToolkitProfileService)
.profiles;
while (profiles.hasMoreElements()) {
let profile = profiles.getNext().QueryInterface(Components.interfaces.nsIToolkitProfile);
let rootDir = profile.rootDir;
if (rootDir.exists() && rootDir.isReadable() &&
!rootDir.equals(MigrationUtils.profileStartup.directory)) {
allProfiles.set(profile.name, rootDir);
}
}
return allProfiles;
};
function sorter(a, b) {
return a.id.toLocaleLowerCase().localeCompare(b.id.toLocaleLowerCase());
}
Object.defineProperty(FirefoxProfileMigrator.prototype, "sourceProfiles", {
get: function() {
return [{id: x, name: x} for (x of this._getAllProfiles().keys())].sort(sorter);
}
});
FirefoxProfileMigrator.prototype.getResources = function(aProfile) {
let sourceProfileDir = aProfile ? this._getAllProfiles().get(aProfile.id) :
Components.classes["@mozilla.org/toolkit/profile-service;1"]
.getService(Components.interfaces.nsIToolkitProfileService)
.selectedProfile.rootDir;
if (!sourceProfileDir || !sourceProfileDir.exists() ||
!sourceProfileDir.isReadable())
return null;
// Being a startup-only migrator, we can rely on
// MigrationUtils.profileStartup being set.
let currentProfileDir = MigrationUtils.profileStartup.directory;
// Surely data cannot be imported from the current profile.
if (sourceProfileDir.equals(currentProfileDir))
return null;
let getFileResource = function(aMigrationType, aFileNames) {
let files = [];
for (let fileName of aFileNames) {
let file = sourceProfileDir.clone();
file.append(fileName);
if (file.exists()) {
files.push(file);
}
}
if (!files.length) {
return null;
}
return {
type: aMigrationType,
migrate: function(aCallback) {
for (let file of files) {
file.copyTo(currentProfileDir, "");
}
aCallback(true);
}
};
};
let types = MigrationUtils.resourceTypes;
let places = getFileResource(types.HISTORY, ["places.sqlite", "places.sqlite-wal"]);
let cookies = getFileResource(types.COOKIES, ["cookies.sqlite", "cookies.sqlite-wal"]);
let passwords = getFileResource(types.PASSWORDS,
["signons.sqlite", "logins.json", "key3.db"]);
let formData = getFileResource(types.FORMDATA, ["formhistory.sqlite"]);
let bookmarksBackups = getFileResource(types.OTHERDATA,
[PlacesBackups.profileRelativeFolderPath]);
let dictionary = getFileResource(types.OTHERDATA, ["persdict.dat"]);
return [r for each (r in [places, cookies, passwords, formData,
dictionary, bookmarksBackups]) if (r)];
}
Object.defineProperty(FirefoxProfileMigrator.prototype, "startupOnlyMigrator", {
get: function() true
});
FirefoxProfileMigrator.prototype.classDescription = "Firefox Profile Migrator";
FirefoxProfileMigrator.prototype.contractID = "@mozilla.org/profile/migrator;1?app=browser&type=firefox";
FirefoxProfileMigrator.prototype.classID = Components.ID("{91185366-ba97-4438-acba-48deaca63386}");
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([FirefoxProfileMigrator]);

View file

@ -0,0 +1,521 @@
/* 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 Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
const Cr = Components.results;
const kMainKey = "Software\\Microsoft\\Internet Explorer\\Main";
const kRegMultiSz = 7;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/NetUtil.jsm");
Cu.import("resource:///modules/MigrationUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
"resource://gre/modules/PlacesUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ctypes",
"resource://gre/modules/ctypes.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "WindowsRegistry",
"resource://gre/modules/WindowsRegistry.jsm");
Cu.importGlobalProperties(["File"]);
////////////////////////////////////////////////////////////////////////////////
//// Helpers.
let CtypesHelpers = {
_structs: {},
_functions: {},
_libs: {},
/**
* Must be invoked once before first use of any of the provided helpers.
*/
initialize: function CH_initialize() {
const WORD = ctypes.uint16_t;
const DWORD = ctypes.uint32_t;
const BOOL = ctypes.int;
this._structs.SYSTEMTIME = new ctypes.StructType('SYSTEMTIME', [
{wYear: WORD},
{wMonth: WORD},
{wDayOfWeek: WORD},
{wDay: WORD},
{wHour: WORD},
{wMinute: WORD},
{wSecond: WORD},
{wMilliseconds: WORD}
]);
this._structs.FILETIME = new ctypes.StructType('FILETIME', [
{dwLowDateTime: DWORD},
{dwHighDateTime: DWORD}
]);
try {
this._libs.kernel32 = ctypes.open("Kernel32");
this._functions.FileTimeToSystemTime =
this._libs.kernel32.declare("FileTimeToSystemTime",
ctypes.default_abi,
BOOL,
this._structs.FILETIME.ptr,
this._structs.SYSTEMTIME.ptr);
} catch (ex) {
this.finalize();
}
},
/**
* Must be invoked once after last use of any of the provided helpers.
*/
finalize: function CH_finalize() {
this._structs = {};
this._functions = {};
for each (let lib in this._libs) {
try {
lib.close();
} catch (ex) {}
}
this._libs = {};
},
/**
* Converts a FILETIME struct (2 DWORDS), to a SYSTEMTIME struct.
*
* @param aTimeHi
* Least significant DWORD.
* @param aTimeLo
* Most significant DWORD.
* @return a Date object representing the converted datetime.
*/
fileTimeToDate: function CH_fileTimeToDate(aTimeHi, aTimeLo) {
let fileTime = this._structs.FILETIME();
fileTime.dwLowDateTime = aTimeLo;
fileTime.dwHighDateTime = aTimeHi;
let systemTime = this._structs.SYSTEMTIME();
let result = this._functions.FileTimeToSystemTime(fileTime.address(),
systemTime.address());
if (result == 0)
throw new Error(ctypes.winLastError);
return new Date(systemTime.wYear,
systemTime.wMonth - 1,
systemTime.wDay,
systemTime.wHour,
systemTime.wMinute,
systemTime.wSecond,
systemTime.wMilliseconds);
}
};
/**
* Checks whether an host is an IP (v4 or v6) address.
*
* @param aHost
* The host to check.
* @return whether aHost is an IP address.
*/
function hostIsIPAddress(aHost) {
try {
Services.eTLD.getBaseDomainFromHost(aHost);
} catch (e if e.result == Cr.NS_ERROR_HOST_IS_IP_ADDRESS) {
return true;
} catch (e) {}
return false;
}
////////////////////////////////////////////////////////////////////////////////
//// Resources
function Bookmarks() {
}
Bookmarks.prototype = {
type: MigrationUtils.resourceTypes.BOOKMARKS,
get exists() !!this._favoritesFolder,
__favoritesFolder: null,
get _favoritesFolder() {
if (!this.__favoritesFolder) {
let favoritesFolder = Services.dirsvc.get("Favs", Ci.nsIFile);
if (favoritesFolder.exists() && favoritesFolder.isReadable())
this.__favoritesFolder = favoritesFolder;
}
return this.__favoritesFolder;
},
__toolbarFolderName: null,
get _toolbarFolderName() {
if (!this.__toolbarFolderName) {
// Retrieve the name of IE's favorites subfolder that holds the bookmarks
// in the toolbar. This was previously stored in the registry and changed
// in IE7 to always be called "Links".
let folderName = WindowsRegistry.readRegKey(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
"Software\\Microsoft\\Internet Explorer\\Toolbar",
"LinksFolderName");
this.__toolbarFolderName = folderName || "Links";
}
return this.__toolbarFolderName;
},
migrate: function B_migrate(aCallback) {
PlacesUtils.bookmarks.runInBatchMode({
runBatched: (function migrateBatched() {
// Import to the bookmarks menu.
let destFolderId = PlacesUtils.bookmarksMenuFolderId;
if (!MigrationUtils.isStartupMigration) {
destFolderId =
MigrationUtils.createImportedBookmarksFolder("IE", destFolderId);
}
this._migrateFolder(this._favoritesFolder, destFolderId);
aCallback(true);
}).bind(this)
}, null);
},
_migrateFolder: function B__migrateFolder(aSourceFolder, aDestFolderId) {
// TODO (bug 741993): the favorites order is stored in the Registry, at
// HCU\Software\Microsoft\Windows\CurrentVersion\Explorer\MenuOrder\Favorites
// Until we support it, bookmarks are imported in alphabetical order.
let entries = aSourceFolder.directoryEntries;
while (entries.hasMoreElements()) {
let entry = entries.getNext().QueryInterface(Ci.nsIFile);
// Make sure that entry.path == entry.target to not follow .lnk folder
// shortcuts which could lead to infinite cycles.
if (entry.isDirectory() && entry.path == entry.target) {
let destFolderId;
if (entry.leafName == this._toolbarFolderName &&
entry.parent.equals(this._favoritesFolder)) {
// Import to the bookmarks toolbar.
destFolderId = PlacesUtils.toolbarFolderId;
if (!MigrationUtils.isStartupMigration) {
destFolderId =
MigrationUtils.createImportedBookmarksFolder("IE", destFolderId);
}
}
else {
// Import to a new folder.
destFolderId =
PlacesUtils.bookmarks.createFolder(aDestFolderId, entry.leafName,
PlacesUtils.bookmarks.DEFAULT_INDEX);
}
if (entry.isReadable()) {
// Recursively import the folder.
this._migrateFolder(entry, destFolderId);
}
}
else {
// Strip the .url extension, to both check this is a valid link file,
// and get the associated title.
let matches = entry.leafName.match(/(.+)\.url$/i);
if (matches) {
let fileHandler = Cc["@mozilla.org/network/protocol;1?name=file"].
getService(Ci.nsIFileProtocolHandler);
let uri = fileHandler.readURLFile(entry);
let title = matches[1];
PlacesUtils.bookmarks.insertBookmark(aDestFolderId,
uri,
PlacesUtils.bookmarks.DEFAULT_INDEX,
title);
}
}
}
}
};
function History() {
}
History.prototype = {
type: MigrationUtils.resourceTypes.HISTORY,
get exists() true,
__typedURLs: null,
get _typedURLs() {
if (!this.__typedURLs) {
// The list of typed URLs is a sort of annotation stored in the registry.
// Currently, IE stores 25 entries and this value is not configurable,
// but we just keep reading up to the first non-existing entry to support
// possible future bumps of this limit.
this.__typedURLs = {};
let registry = Cc["@mozilla.org/windows-registry-key;1"].
createInstance(Ci.nsIWindowsRegKey);
try {
registry.open(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
"Software\\Microsoft\\Internet Explorer\\TypedURLs",
Ci.nsIWindowsRegKey.ACCESS_READ);
for (let entry = 1; registry.hasValue("url" + entry); entry++) {
let url = registry.readStringValue("url" + entry);
this.__typedURLs[url] = true;
}
} catch (ex) {
} finally {
registry.close();
}
}
return this.__typedURLs;
},
migrate: function H_migrate(aCallback) {
let places = [];
let historyEnumerator = Cc["@mozilla.org/profile/migrator/iehistoryenumerator;1"].
createInstance(Ci.nsISimpleEnumerator);
while (historyEnumerator.hasMoreElements()) {
let entry = historyEnumerator.getNext().QueryInterface(Ci.nsIPropertyBag2);
let uri = entry.get("uri").QueryInterface(Ci.nsIURI);
// MSIE stores some types of URLs in its history that we don't handle,
// like HTMLHelp and others. Since we don't properly map handling for
// all of them we just avoid importing them.
if (["http", "https", "ftp", "file"].indexOf(uri.scheme) == -1) {
continue;
}
let title = entry.get("title");
// Embed visits have no title and don't need to be imported.
if (title.length == 0) {
continue;
}
// The typed urls are already fixed-up, so we can use them for comparison.
let transitionType = this._typedURLs[uri.spec] ?
Ci.nsINavHistoryService.TRANSITION_TYPED :
Ci.nsINavHistoryService.TRANSITION_LINK;
let lastVisitTime = entry.get("time");
places.push(
{ uri: uri,
title: title,
visits: [{ transitionType: transitionType,
visitDate: lastVisitTime }]
}
);
}
// Check whether there is any history to import.
if (places.length == 0) {
aCallback(true);
return;
}
PlacesUtils.asyncHistory.updatePlaces(places, {
_success: false,
handleResult: function() {
// Importing any entry is considered a successful import.
this._success = true;
},
handleError: function() {},
handleCompletion: function() {
aCallback(this._success);
}
});
}
};
function Cookies() {
}
Cookies.prototype = {
type: MigrationUtils.resourceTypes.COOKIES,
get exists() !!this._cookiesFolder,
__cookiesFolder: null,
get _cookiesFolder() {
// Cookies are stored in txt files, in a Cookies folder whose path varies
// across the different OS versions. CookD takes care of most of these
// cases, though, in Windows Vista/7, UAC makes a difference.
// If UAC is enabled, the most common destination is CookD/Low. Though,
// if the user runs the application in administrator mode or disables UAC,
// cookies are stored in the original CookD destination. Cause running the
// browser in administrator mode is unsafe and discouraged, we just care
// about the UAC state.
if (!this.__cookiesFolder) {
let cookiesFolder = Services.dirsvc.get("CookD", Ci.nsIFile);
if (cookiesFolder.exists() && cookiesFolder.isReadable()) {
// Check if UAC is enabled.
if (Services.appinfo.QueryInterface(Ci.nsIWinAppHelper).userCanElevate) {
cookiesFolder.append("Low");
}
this.__cookiesFolder = cookiesFolder;
}
}
return this.__cookiesFolder;
},
migrate: function C_migrate(aCallback) {
CtypesHelpers.initialize();
let cookiesGenerator = (function genCookie() {
let success = false;
let entries = this._cookiesFolder.directoryEntries;
while (entries.hasMoreElements()) {
let entry = entries.getNext().QueryInterface(Ci.nsIFile);
// Skip eventual bogus entries.
if (!entry.isFile() || !/\.txt$/.test(entry.leafName))
continue;
this._readCookieFile(entry, function(aSuccess) {
// Importing even a single cookie file is considered a success.
if (aSuccess)
success = true;
try {
cookiesGenerator.next();
} catch (ex) {}
});
yield;
}
CtypesHelpers.finalize();
aCallback(success);
}).apply(this);
cookiesGenerator.next();
},
_readCookieFile: function C__readCookieFile(aFile, aCallback) {
let fileReader = Cc["@mozilla.org/files/filereader;1"].
createInstance(Ci.nsIDOMFileReader);
fileReader.addEventListener("loadend", (function onLoadEnd() {
fileReader.removeEventListener("loadend", onLoadEnd, false);
if (fileReader.readyState != fileReader.DONE) {
Cu.reportError("Could not read cookie contents: " + fileReader.error);
aCallback(false);
return;
}
let success = true;
try {
this._parseCookieBuffer(fileReader.result);
} catch (ex) {
Components.utils.reportError("Unable to migrate cookie: " + ex);
success = false;
} finally {
aCallback(success);
}
}).bind(this), false);
fileReader.readAsText(new File(aFile));
},
/**
* Parses a cookie file buffer and returns an array of the contained cookies.
*
* The cookie file format is a newline-separated-values with a "*" used as
* delimeter between multiple records.
* Each cookie has the following fields:
* - name
* - value
* - host/path
* - flags
* - Expiration time most significant integer
* - Expiration time least significant integer
* - Creation time most significant integer
* - Creation time least significant integer
* - Record delimiter "*"
*
* @note All the times are in FILETIME format.
*/
_parseCookieBuffer: function C__parseCookieBuffer(aTextBuffer) {
// Note the last record is an empty string.
let records = [r for each (r in aTextBuffer.split("*\n")) if (r)];
for (let record of records) {
let [name, value, hostpath, flags,
expireTimeLo, expireTimeHi] = record.split("\n");
// IE stores deleted cookies with a zero-length value, skip them.
if (value.length == 0)
continue;
let hostLen = hostpath.indexOf("/");
let host = hostpath.substr(0, hostLen);
let path = hostpath.substr(hostLen);
// For a non-null domain, assume it's what Mozilla considers
// a domain cookie. See bug 222343.
if (host.length > 0) {
// Fist delete any possible extant matching host cookie.
Services.cookies.remove(host, name, path, false);
// Now make it a domain cookie.
if (host[0] != "." && !hostIsIPAddress(host))
host = "." + host;
}
let expireTime = CtypesHelpers.fileTimeToDate(Number(expireTimeHi),
Number(expireTimeLo));
Services.cookies.add(host,
path,
name,
value,
Number(flags) & 0x1, // secure
false, // httpOnly
false, // session
expireTime);
}
}
};
////////////////////////////////////////////////////////////////////////////////
//// Migrator
function IEProfileMigrator()
{
}
IEProfileMigrator.prototype = Object.create(MigratorPrototype);
IEProfileMigrator.prototype.getResources = function IE_getResources() {
let resources = [
new Bookmarks()
, new History()
, new Cookies()
];
return [r for each (r in resources) if (r.exists)];
};
Object.defineProperty(IEProfileMigrator.prototype, "sourceHomePageURL", {
get: function IE_get_sourceHomePageURL() {
let defaultStartPage = WindowsRegistry.readRegKey(Ci.nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE,
kMainKey, "Default_Page_URL");
let startPage = WindowsRegistry.readRegKey(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
kMainKey, "Start Page");
// If the user didn't customize the Start Page, he is still on the default
// page, that may be considered the equivalent of our about:home. There's
// no reason to retain it, since it is heavily targeted to IE.
let homepage = startPage != defaultStartPage ? startPage : "";
// IE7+ supports secondary home pages located in a REG_MULTI_SZ key. These
// are in addition to the Start Page, and no empty entries are possible,
// thus a Start Page is always defined if any of these exists, though it
// may be the default one.
let secondaryPages = WindowsRegistry.readRegKey(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
kMainKey, "Secondary Start Pages");
if (secondaryPages) {
if (homepage)
secondaryPages.unshift(homepage);
homepage = secondaryPages.join("|");
}
return homepage;
}
});
IEProfileMigrator.prototype.classDescription = "IE Profile Migrator";
IEProfileMigrator.prototype.contractID = "@mozilla.org/profile/migrator;1?app=browser&type=ie";
IEProfileMigrator.prototype.classID = Components.ID("{3d2532e3-4932-4774-b7ba-968f5899d3a4}");
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([IEProfileMigrator]);

View file

@ -0,0 +1,644 @@
/* 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";
this.EXPORTED_SYMBOLS = ["MigrationUtils", "MigratorPrototype"];
const Cu = Components.utils;
const Ci = Components.interfaces;
const Cc = Components.classes;
const TOPIC_WILL_IMPORT_BOOKMARKS = "initial-migration-will-import-default-bookmarks";
const TOPIC_DID_IMPORT_BOOKMARKS = "initial-migration-did-import-default-bookmarks";
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Dict",
"resource://gre/modules/Dict.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
"resource://gre/modules/PlacesUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
"resource://gre/modules/NetUtil.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "BookmarkHTMLUtils",
"resource://gre/modules/BookmarkHTMLUtils.jsm");
let gMigrators = null;
let gProfileStartup = null;
let gMigrationBundle = null;
function getMigrationBundle() {
if (!gMigrationBundle) {
gMigrationBundle = Services.strings.createBundle(
"chrome://browser/locale/migration/migration.properties");
}
return gMigrationBundle;
}
/**
* Figure out what is the default browser, and if there is a migrator
* for it, return that migrator's internal name.
* For the time being, the "internal name" of a migraotr is its contract-id
* trailer (e.g. ie for @mozilla.org/profile/migrator;1?app=browser&type=ie),
* but it will soon be exposed properly.
*/
function getMigratorKeyForDefaultBrowser() {
const APP_DESC_TO_KEY = {
"Internet Explorer": "ie",
"Safari": "safari",
"Pale Moon web browser": "firefox",
"Google Chrome": "chrome", // Windows, Linux
"Chrome": "chrome", // OS X
};
let browserDesc = "";
try {
let browserDesc =
Cc["@mozilla.org/uriloader/external-protocol-service;1"].
getService(Ci.nsIExternalProtocolService).
getApplicationDescription("http");
return APP_DESC_TO_KEY[browserDesc] || "";
}
catch(ex) {
Cu.reportError("Could not detect default browser: " + ex);
}
return "";
}
/**
* Shared prototype for migrators, implementing nsIBrowserProfileMigrator.
*
* To implement a migrator:
* 1. Import this module.
* 2. Create the prototype for the migrator, extending MigratorPrototype.
* Namely: MosaicMigrator.prototype = Object.create(MigratorPrototype);
* 3. Set classDescription, contractID and classID for your migrator, and set
* NSGetFactory appropriately.
* 4. If the migrator supports multiple profiles, override the sourceProfiles
* Here we default for single-profile migrator.
* 5. Implement getResources(aProfile) (see below).
* 6. If the migrator supports reading the home page of the source browser,
* override |sourceHomePageURL| getter.
* 7. For startup-only migrators, override |startupOnlyMigrator|.
*/
this.MigratorPrototype = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsIBrowserProfileMigrator]),
/**
* OVERRIDE IF AND ONLY IF the source supports multiple profiles.
*
* Returns array of profile objects from which data may be imported. The object
* should have the following keys:
* id - a unique string identifier for the profile
* name - a pretty name to display to the user in the UI
*
* Only profiles from which data can be imported should be listed. Otherwise
* the behavior of the migration wizard isn't well-defined.
*
* For a single-profile source (e.g. safari, ie), this returns null,
* and not an empty array. That is the default implementation.
*/
get sourceProfiles() null,
/**
* MUST BE OVERRIDDEN.
*
* Returns an array of "migration resources" objects for the given profile,
* or for the "default" profile, if the migrator does not support multiple
* profiles.
*
* Each migration resource should provide:
* - a |type| getter, retunring any of the migration types (see
* nsIBrowserProfileMigrator).
*
* - a |migrate| method, taking a single argument, aCallback(bool success),
* for migrating the data for this resource. It may do its job
* synchronously or asynchronously. Either way, it must call
* aCallback(bool aSuccess) when it's done. In the case of an exception
* thrown from |migrate|, it's taken as if aCallback(false) is called.
*
* Note: In the case of a simple asynchronous implementation, you may find
* MigrationUtils.wrapMigrateFunction handy for handling aCallback easily.
*
* For each migration type listed in nsIBrowserProfileMigrator, multiple
* migration resources may be provided. This practice is useful when the
* data for a certain migration type is independently stored in few
* locations. For example, the mac version of Safari stores its "reading list"
* bookmarks in a separate property list.
*
* Note that the importation of a particular migration type is reported as
* successful if _any_ of its resources succeeded to import (that is, called,
* |aCallback(true)|). However, completion-status for a particular migration
* type is reported to the UI only once all of its migrators have called
* aCallback.
*
* @note The returned array should only include resources from which data
* can be imported. So, for example, before adding a resource for the
* BOOKMARKS migration type, you should check if you should check that the
* bookmarks file exists.
*
* @param aProfile
* The profile from which data may be imported, or an empty string
* in the case of a single-profile migrator.
* In the case of multiple-profiles migrator, it is guaranteed that
* aProfile is a value returned by the sourceProfiles getter (see
* above).
*/
getResources: function MP_getResources(aProfile) {
throw new Error("getResources must be overridden");
},
/**
* OVERRIDE IF AND ONLY IF the migrator is a startup-only migrator (For now,
* that is just the Firefox migrator, see bug 737381). Default: false.
*
* Startup-only migrators are different in two ways:
* - they may only be used during startup.
* - the user-profile is half baked during migration. The folder exists,
* but it's only accessible through MigrationUtils.profileStartup.
* The migrator can call MigrationUtils.profileStartup.doStartup
* at any point in order to initialize the profile.
*/
get startupOnlyMigrator() false,
/**
* OVERRIDE IF AND ONLY IF your migrator supports importing the homepage.
* @see nsIBrowserProfileMigrator
*/
get sourceHomePageURL() "",
/**
* DO NOT OVERRIDE - After deCOMing migration, the UI will just call
* getResources.
*
* @see nsIBrowserProfileMigrator
*/
getMigrateData: function MP_getMigrateData(aProfile) {
let types = [r.type for each (r in this._getMaybeCachedResources(aProfile))];
return types.reduce(function(a, b) a |= b, 0);
},
/**
* DO NOT OVERRIDE - After deCOMing migration, the UI will just call
* migrate for each resource.
*
* @see nsIBrowserProfileMigrator
*/
migrate: function MP_migrate(aItems, aStartup, aProfile) {
let resources = this._getMaybeCachedResources(aProfile);
if (resources.length == 0)
throw new Error("migrate called for a non-existent source");
if (aItems != Ci.nsIBrowserProfileMigrator.ALL)
resources = [r for each (r in resources) if (aItems & r.type)];
// Called either directly or through the bookmarks import callback.
function doMigrate() {
// TODO: use Map (for the items) and Set (for the resources)
// once they are iterable.
let resourcesGroupedByItems = new Dict();
resources.forEach(function(resource) {
if (resourcesGroupedByItems.has(resource.type))
resourcesGroupedByItems.get(resource.type).push(resource);
else
resourcesGroupedByItems.set(resource.type, [resource]);
});
if (resourcesGroupedByItems.count == 0)
throw new Error("No items to import");
let notify = function(aMsg, aItemType) {
Services.obs.notifyObservers(null, aMsg, aItemType);
}
notify("Migration:Started");
resourcesGroupedByItems.listkeys().forEach(function(migrationType) {
let migrationTypeA = migrationType;
let itemResources = resourcesGroupedByItems.get(migrationType);
notify("Migration:ItemBeforeMigrate", migrationType);
let itemSuccess = false;
itemResources.forEach(function(resource) {
let resourceDone = function(aSuccess) {
let resourceIndex = itemResources.indexOf(resource);
if (resourceIndex != -1) {
itemResources.splice(resourceIndex, 1);
itemSuccess |= aSuccess;
if (itemResources.length == 0) {
resourcesGroupedByItems.del(migrationType);
notify(itemSuccess ?
"Migration:ItemAfterMigrate" : "Migration:ItemError",
migrationType);
if (resourcesGroupedByItems.count == 0)
notify("Migration:Ended");
}
}
};
Services.tm.mainThread.dispatch(function() {
// If migrate throws, an error occurred, and the callback
// (itemMayBeDone) might haven't been called.
try {
resource.migrate(resourceDone);
}
catch(ex) {
Cu.reportError(ex);
resourceDone(false);
}
}, Ci.nsIThread.DISPATCH_NORMAL);
});
});
}
if (MigrationUtils.isStartupMigration && !this.startupOnlyMigrator) {
MigrationUtils.profileStartup.doStartup();
// If we're about to migrate bookmarks, first import the default bookmarks.
// Note We do not need to do so for the Firefox migrator
// (=startupOnlyMigrator), as it just copies over the places database
// from another profile.
const BOOKMARKS = MigrationUtils.resourceTypes.BOOKMARKS;
let migratingBookmarks = resources.some(function(r) r.type == BOOKMARKS);
if (migratingBookmarks) {
let browserGlue = Cc["@mozilla.org/browser/browserglue;1"].
getService(Ci.nsIObserver);
browserGlue.observe(null, TOPIC_WILL_IMPORT_BOOKMARKS, "");
// Note doMigrate doesn't care about the success of the import.
let onImportComplete = function() {
browserGlue.observe(null, TOPIC_DID_IMPORT_BOOKMARKS, "");
doMigrate();
};
BookmarkHTMLUtils.importFromURL(
"resource:///defaults/profile/bookmarks.html", true).then(
onImportComplete, onImportComplete);
return;
}
}
doMigrate();
},
/**
* DO NOT OVERRIDE - After deCOMing migration, this code
* won't be part of the migrator itself.
*
* @see nsIBrowserProfileMigrator
*/
get sourceExists() {
if (this.startupOnlyMigrator && !MigrationUtils.isStartupMigration)
return false;
// For a single-profile source, check if any data is available.
// For multiple-profiles source, make sure that at least one
// profile is available.
let exists = false;
try {
let profiles = this.sourceProfiles;
if (!profiles) {
let resources = this._getMaybeCachedResources("");
if (resources && resources.length > 0)
exists = true;
}
else {
exists = profiles.length > 0;
}
}
catch(ex) {
Cu.reportError(ex);
}
return exists;
},
/*** PRIVATE STUFF - DO NOT OVERRIDE ***/
_getMaybeCachedResources: function PMB__getMaybeCachedResources(aProfile) {
let profileKey = aProfile ? aProfile.id : "";
if (this._resourcesByProfile) {
if (profileKey in this._resourcesByProfile)
return this._resourcesByProfile[profileKey];
}
else {
this._resourcesByProfile = { };
}
return this._resourcesByProfile[profileKey] = this.getResources(aProfile);
}
};
this.MigrationUtils = Object.freeze({
resourceTypes: {
SETTINGS: Ci.nsIBrowserProfileMigrator.SETTINGS,
COOKIES: Ci.nsIBrowserProfileMigrator.COOKIES,
HISTORY: Ci.nsIBrowserProfileMigrator.HISTORY,
FORMDATA: Ci.nsIBrowserProfileMigrator.FORMDATA,
PASSWORDS: Ci.nsIBrowserProfileMigrator.PASSWORDS,
BOOKMARKS: Ci.nsIBrowserProfileMigrator.BOOKMARKS,
OTHERDATA: Ci.nsIBrowserProfileMigrator.OTHERDATA
},
/**
* Helper for implementing simple asynchronous cases of migration resources'
* |migrate(aCallback)| (see MigratorPrototype). If your |migrate| method
* just waits for some file to be read, for example, and then migrates
* everything right away, you can wrap the async-function with this helper
* and not worry about notifying the callback.
*
* For example, instead of writing:
* setTimeout(function() {
* try {
* ....
* aCallback(true);
* }
* catch() {
* aCallback(false);
* }
* }, 0);
*
* You may write:
* setTimeout(MigrationUtils.wrapMigrateFunction(function() {
* if (importingFromMosaic)
* throw Cr.NS_ERROR_UNEXPECTED;
* }, aCallback), 0);
*
* ... and aCallback will be called with aSuccess=false when importing
* from Mosaic, or with aSuccess=true otherwise.
*
* @param aFunction
* the function that will be called sometime later. If aFunction
* throws when it's called, aCallback(false) is called, otherwise
* aCallback(true) is called.
* @param aCallback
* the callback function passed to |migrate|.
* @return the wrapped function.
*/
wrapMigrateFunction: function MU_wrapMigrateFunction(aFunction, aCallback) {
return function() {
let success = false;
try {
aFunction.apply(null, arguments);
success = true;
}
catch(ex) {
Cu.reportError(ex);
}
// Do not change this to call aCallback directly in try try & catch
// blocks, because if aCallback throws, we may end up calling aCallback
// twice.
aCallback(success);
}
},
/**
* Gets a string from the migration bundle. Shorthand for
* nsIStringBundle.GetStringFromName, if aReplacements isn't passed, or for
* nsIStringBundle.formatStringFromName if it is.
*
* This method also takes care of "bumped" keys (See bug 737381 comment 8 for
* details).
*
* @param aKey
* The key of the string to retrieve.
* @param aReplacemts
* [optioanl] Array of replacements to run on the retrieved string.
* @return the retrieved string.
*
* @see nsIStringBundle
*/
getLocalizedString: function MU_getLocalizedString(aKey, aReplacements) {
const OVERRIDES = {
"4_firefox": "4_firefox_history_and_bookmarks",
"64_firefox": "64_firefox_other"
};
aKey = OVERRIDES[aKey] || aKey;
if (aReplacements === undefined)
return getMigrationBundle().GetStringFromName(aKey);
return getMigrationBundle().formatStringFromName(
aKey, aReplacements, aReplacements.length);
},
/**
* Helper for creating a folder for imported bookmarks from a particular
* migration source. The folder is created at the end of the given folder.
*
* @param aSourceNameStr
* the source name (first letter capitalized). This is used
* for reading the localized source name from the migration
* bundle (e.g. if aSourceNameStr is Mosaic, this will try to read
* sourceNameMosaic from the migration bundle).
* @param aParentId
* the item-id of the folder in which the new folder should be
* created.
* @return the item-id of the new folder.
*/
createImportedBookmarksFolder:
function MU_createImportedBookmarksFolder(aSourceNameStr, aParentId) {
let source = this.getLocalizedString("sourceName" + aSourceNameStr);
let label = this.getLocalizedString("importedBookmarksFolder", [source]);
return PlacesUtils.bookmarks.createFolder(
aParentId, label, PlacesUtils.bookmarks.DEFAULT_INDEX);
},
get _migrators() gMigrators ? gMigrators : gMigrators = new Dict(),
/*
* Returns the migrator for the given source, if any data is available
* for this source, or null otherwise.
*
* @param aKey internal name of the migration source.
* Supported values: ie (windows),
* safari (mac/windows),
* chrome (mac/windows/linux),
* firefox.
*
* If null is returned, either no data can be imported
* for the given migrator, or aMigratorKey is invalid (e.g. ie on mac,
* or mosaic everywhere). This method should be used rather than direct
* getService for future compatibility (see bug 718280).
*
* @return profile migrator implementing nsIBrowserProfileMigrator, if it can
* import any data, null otherwise.
*/
getMigrator: function MU_getMigrator(aKey) {
let migrator = null;
if (this._migrators.has(aKey)) {
migrator = this._migrators.get(aKey);
}
else {
try {
migrator = Cc["@mozilla.org/profile/migrator;1?app=browser&type=" +
aKey].createInstance(Ci.nsIBrowserProfileMigrator);
}
catch(ex) { }
this._migrators.set(aKey, migrator);
}
return migrator && migrator.sourceExists ? migrator : null;
},
// Iterates the available migrators, in the most suitable
// order for the running platform.
get migrators() {
let migratorKeysOrdered = [
#ifdef XP_WIN
"firefox", "ie", "chrome", "safari"
#elifdef XP_MACOSX
"firefox", "safari", "chrome"
#elifdef XP_UNIX
"firefox", "chrome"
#endif
];
// If a supported default browser is found check it first
// so that the wizard defaults to import from that browser.
let defaultBrowserKey = getMigratorKeyForDefaultBrowser();
if (defaultBrowserKey)
migratorKeysOrdered.sort(function (a, b) b == defaultBrowserKey ? 1 : 0);
for (let migratorKey of migratorKeysOrdered) {
let migrator = this.getMigrator(migratorKey);
if (migrator)
yield migrator;
}
},
// Whether or not we're in the process of startup migration
get isStartupMigration() gProfileStartup != null,
/**
* In the case of startup migration, this is set to the nsIProfileStartup
* instance passed to ProfileMigrator's migrate.
*
* @see showMigrationWizard
*/
get profileStartup() gProfileStartup,
/**
* Show the migration wizard. On mac, this may just focus the wizard if it's
* already running, in which case aOpener and aParams are ignored.
*
* @param [optional] aOpener
* the window that asks to open the wizard.
* @param [optional] aParams
* arguments for the migration wizard, in the form of an nsIArray.
* This is passed as-is for the params argument of
* nsIWindowWatcher.openWindow.
*/
showMigrationWizard:
function MU_showMigrationWizard(aOpener, aParams) {
let features = "chrome,dialog,modal,centerscreen,titlebar,resizable=no";
#ifdef XP_MACOSX
if (!this.isStartupMigration) {
let win = Services.wm.getMostRecentWindow("Browser:MigrationWizard");
if (win) {
win.focus();
return;
}
// On mac, the migration wiazrd should only be modal in the case of
// startup-migration.
features = "centerscreen,chrome,resizable=no";
}
#endif
Services.ww.openWindow(aOpener,
"chrome://browser/content/migration/migration.xul",
"_blank",
features,
aParams);
},
/**
* Show the migration wizard for startup-migration. This should only be
* called by ProfileMigrator (see ProfileMigrator.js), which implements
* nsIProfileMigrator.
*
* @param aProfileStartup
* the nsIProfileStartup instance provided to ProfileMigrator.migrate.
* @param [optional] aMigratorKey
* If set, the migration wizard will import from the corresponding
* migrator, bypassing the source-selection page. Otherwise, the
* source-selection page will be displayed, either with the default
* browser selected, if it could be detected and if there is a
* migrator for it, or with the first option selected as a fallback
* (The first option is hardcoded to be the most common browser for
* the OS we run on. See migration.xul).
* @param [optional] aProfileToMigrate
* If set, the migration wizard will import from the profile indicated.
*
* @throws if aMigratorKey is invalid or if it points to a non-existent
* source.
*/
startupMigration:
function MU_startupMigrator(aProfileStartup, aMigratorKey, aProfileToMigrate) {
if (!aProfileStartup) {
throw new Error("a profile-startup instance is required for startup-migration");
}
gProfileStartup = aProfileStartup;
let skipSourcePage = false, migrator = null, migratorKey = "";
if (aMigratorKey) {
migrator = this.getMigrator(aMigratorKey);
if (!migrator) {
// aMigratorKey must point to a valid source, so, if it doesn't
// cleanup and throw.
this.finishMigration();
throw new Error("startMigration was asked to open auto-migrate from " +
"a non-existent source: " + aMigratorKey);
}
migratorKey = aMigratorKey;
skipSourcePage = true;
}
else {
let defaultBrowserKey = getMigratorKeyForDefaultBrowser();
if (defaultBrowserKey) {
migrator = this.getMigrator(defaultBrowserKey);
if (migrator)
migratorKey = defaultBrowserKey;
}
}
if (!migrator) {
// If there's no migrator set so far, ensure that there is at least one
// migrator available before opening the wizard.
try {
this.migrators.next();
}
catch(ex) {
this.finishMigration();
if (!(ex instanceof StopIteration))
throw ex;
return;
}
}
let params = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
let keyCSTR = Cc["@mozilla.org/supports-cstring;1"].
createInstance(Ci.nsISupportsCString);
keyCSTR.data = migratorKey;
let skipImportSourcePageBool = Cc["@mozilla.org/supports-PRBool;1"].
createInstance(Ci.nsISupportsPRBool);
skipImportSourcePageBool.data = skipSourcePage;
let profileToMigrate = null;
if (aProfileToMigrate) {
profileToMigrate = Cc["@mozilla.org/supports-string;1"].
createInstance(Ci.nsISupportsString);
profileToMigrate.data = aProfileToMigrate;
}
params.appendElement(keyCSTR, false);
params.appendElement(migrator, false);
params.appendElement(aProfileStartup, false);
params.appendElement(skipImportSourcePageBool, false);
params.appendElement(profileToMigrate, false);
this.showMigrationWizard(null, params);
},
/**
* Cleans up references to migrators and nsIProfileInstance instances.
*/
finishMigration: function MU_finishMigration() {
gMigrators = null;
gProfileStartup = null;
gMigrationBundle = null;
}
});

View file

@ -0,0 +1,21 @@
/* 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";
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
Components.utils.import("resource:///modules/MigrationUtils.jsm");
function ProfileMigrator() {
}
ProfileMigrator.prototype = {
migrate: MigrationUtils.startupMigration.bind(MigrationUtils),
QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsIProfileMigrator]),
classDescription: "Profile Migrator",
contractID: "@mozilla.org/toolkit/profile-migrator;1",
classID: Components.ID("6F8BB968-C14F-4D6F-9733-6C6737B35DCE")
};
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([ProfileMigrator]);

View file

@ -0,0 +1,416 @@
/* 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";
let Cc = Components.classes;
let Ci = Components.interfaces;
let Cu = Components.utils;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/FileUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource:///modules/MigrationUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PropertyListUtils",
"resource://gre/modules/PropertyListUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
"resource://gre/modules/PlacesUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
"resource://gre/modules/NetUtil.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "FormHistory",
"resource://gre/modules/FormHistory.jsm");
function Bookmarks(aBookmarksFile) {
this._file = aBookmarksFile;
}
Bookmarks.prototype = {
type: MigrationUtils.resourceTypes.BOOKMARKS,
migrate: function B_migrate(aCallback) {
PropertyListUtils.read(this._file,
MigrationUtils.wrapMigrateFunction(function migrateBookmarks(aDict) {
if (!aDict)
throw new Error("Could not read Bookmarks.plist");
let children = aDict.get("Children");;
if (!children)
throw new Error("Invalid Bookmarks.plist format");
PlacesUtils.bookmarks.runInBatchMode({
runBatched: function() {
let collection = aDict.get("Title") == "com.apple.ReadingList" ?
this.READING_LIST_COLLECTION : this.ROOT_COLLECTION;
this._migrateCollection(children, collection);
}.bind(this)
}, null);
}.bind(this), aCallback));
},
// Bookmarks collections in Safari. Constants for migrateCollection.
ROOT_COLLECTION: 0,
MENU_COLLECTION: 1,
TOOLBAR_COLLECTION: 2,
READING_LIST_COLLECTION: 3,
/**
* Recursively migrate a Safari collection of bookmarks.
*
* @param aEntries
* the collection's children
* @param aCollection
* one of the values above.
*/
_migrateCollection: function B__migrateCollection(aEntries, aCollection) {
// A collection of bookmarks in Safari resembles places roots. In the
// property list files (Bookmarks.plist, ReadingList.plist) they are
// stored as regular bookmarks folders, and thus can only be distinguished
// from by their names and places in the hierarchy.
let entriesFiltered = [];
if (aCollection == this.ROOT_COLLECTION) {
for (let entry of aEntries) {
let type = entry.get("WebBookmarkType");
if (type == "WebBookmarkTypeList" && entry.has("Children")) {
let title = entry.get("Title");
let children = entry.get("Children");
if (title == "BookmarksBar")
this._migrateCollection(children, this.TOOLBAR_COLLECTION);
else if (title == "BookmarksMenu")
this._migrateCollection(children, this.MENU_COLLECTION);
else if (title == "com.apple.ReadingList")
this._migrateCollection(children, this.READING_LIST_COLLECTION);
else if (entry.get("ShouldOmitFromUI") !== true)
entriesFiltered.push(entry);
}
else if (type == "WebBookmarkTypeLeaf") {
entriesFiltered.push(entry);
}
}
}
else {
entriesFiltered = aEntries;
}
if (entriesFiltered.length == 0)
return;
let folder = -1;
switch (aCollection) {
case this.ROOT_COLLECTION: {
// In Safari, it is possible (though quite cumbersome) to move
// bookmarks to the bookmarks root, which is the parent folder of
// all bookmarks "collections". That is somewhat in parallel with
// both the places root and the unfiled-bookmarks root.
// Because the former is only an implementation detail in our UI,
// the unfiled root seems to be the best choice.
folder = PlacesUtils.unfiledBookmarksFolderId;
break;
}
case this.MENU_COLLECTION: {
folder = PlacesUtils.bookmarksMenuFolderId;
if (!MigrationUtils.isStartupMigration) {
folder = MigrationUtils.createImportedBookmarksFolder("Safari",
folder);
}
break;
}
case this.TOOLBAR_COLLECTION: {
folder = PlacesUtils.toolbarFolderId;
if (!MigrationUtils.isStartupMigration) {
folder = MigrationUtils.createImportedBookmarksFolder("Safari",
folder);
}
break;
}
case this.READING_LIST_COLLECTION: {
// Reading list items are imported as regular bookmarks.
// They are imported under their own folder, created either under the
// bookmarks menu (in the case of startup migration).
folder = PlacesUtils.bookmarks.createFolder(
PlacesUtils.bookmarksMenuFolderId,
MigrationUtils.getLocalizedString("importedSafariReadingList"),
PlacesUtils.bookmarks.DEFAULT_INDEX);
break;
}
default:
throw new Error("Unexpected value for aCollection!");
}
this._migrateEntries(entriesFiltered, folder);
},
// migrate the given array of safari bookmarks to the given places
// folder.
_migrateEntries: function B__migrateEntries(aEntries, aFolderId) {
for (let entry of aEntries) {
let type = entry.get("WebBookmarkType");
if (type == "WebBookmarkTypeList" && entry.has("Children")) {
let title = entry.get("Title");
let folderId = PlacesUtils.bookmarks.createFolder(
aFolderId, title, PlacesUtils.bookmarks.DEFAULT_INDEX);
// Empty folders may not have a children array.
if (entry.has("Children"))
this._migrateEntries(entry.get("Children"), folderId, false);
}
else if (type == "WebBookmarkTypeLeaf" && entry.has("URLString")) {
let title, uri;
if (entry.has("URIDictionary"))
title = entry.get("URIDictionary").get("title");
try {
uri = NetUtil.newURI(entry.get("URLString"));
}
catch(ex) {
Cu.reportError("Invalid uri set for Safari bookmark: " + entry.get("URLString"));
}
if (uri) {
PlacesUtils.bookmarks.insertBookmark(aFolderId, uri,
PlacesUtils.bookmarks.DEFAULT_INDEX, title);
}
}
}
}
};
function History(aHistoryFile) {
this._file = aHistoryFile;
}
History.prototype = {
type: MigrationUtils.resourceTypes.HISTORY,
// Helper method for converting the visit date property to a PRTime value.
// The visit date is stored as a string, so it's not read as a Date
// object by PropertyListUtils.
_parseCocoaDate: function H___parseCocoaDate(aCocoaDateStr) {
let asDouble = parseFloat(aCocoaDateStr);
if (!isNaN(asDouble)) {
// reference date of NSDate.
let date = new Date("1 January 2001, GMT");
date.setMilliseconds(asDouble * 1000);
return date * 1000;
}
return 0;
},
migrate: function H_migrate(aCallback) {
PropertyListUtils.read(this._file, function migrateHistory(aDict) {
try {
if (!aDict)
throw new Error("Could not read history property list");
if (!aDict.has("WebHistoryDates"))
throw new Error("Unexpected history-property list format");
// Safari's History file contains only top-level urls. It does not
// distinguish between typed urls and linked urls.
let transType = PlacesUtils.history.TRANSITION_LINK;
let places = [];
let entries = aDict.get("WebHistoryDates");
for (let entry of entries) {
if (entry.has("lastVisitedDate")) {
let visitDate = this._parseCocoaDate(entry.get("lastVisitedDate"));
places.push({ uri: NetUtil.newURI(entry.get("")),
title: entry.get("title"),
visits: [{ transitionType: transType,
visitDate: visitDate }] });
}
}
if (places.length > 0) {
PlacesUtils.asyncHistory.updatePlaces(places, {
_success: false,
handleResult: function() {
// Importing any entry is considered a successful import.
this._success = true;
},
handleError: function() {},
handleCompletion: function() {
aCallback(this._success);
}
});
}
else {
aCallback(false);
}
}
catch(ex) {
Cu.reportError(ex);
aCallback(false);
}
}.bind(this));
}
};
/**
* Safari's preferences property list is independently used for three purposes:
* (a) importation of preferences
* (b) importation of search strings
* (c) retrieving the home page.
*
* So, rather than reading it three times, it's cached and managed here.
*/
function MainPreferencesPropertyList(aPreferencesFile) {
this._file = aPreferencesFile;
this._callbacks = [];
}
MainPreferencesPropertyList.prototype = {
/**
* @see PropertyListUtils.read
*/
read: function MPPL_read(aCallback) {
if ("_dict" in this) {
aCallback(this._dict);
return;
}
let alreadyReading = this._callbacks.length > 0;
this._callbacks.push(aCallback);
if (!alreadyReading) {
PropertyListUtils.read(this._file, function readPrefs(aDict) {
this._dict = aDict;
for (let callback of this._callbacks) {
try {
callback(aDict);
}
catch(ex) {
Cu.reportError(ex);
}
}
this._callbacks.splice(0);
}.bind(this));
}
},
// Workaround for nsIBrowserProfileMigrator.sourceHomePageURL until
// it's replaced with an async method.
_readSync: function MPPL__readSync() {
if ("_dict" in this)
return this._dict;
let inputStream = Cc["@mozilla.org/network/file-input-stream;1"].
createInstance(Ci.nsIFileInputStream);
inputStream.init(this._file, -1, -1, 0);
let binaryStream = Cc["@mozilla.org/binaryinputstream;1"].
createInstance(Ci.nsIBinaryInputStream);
binaryStream.setInputStream(inputStream);
let bytes = binaryStream.readByteArray(inputStream.available());
this._dict = PropertyListUtils._readFromArrayBufferSync(
Uint8Array(bytes).buffer);
return this._dict;
}
};
function SearchStrings(aMainPreferencesPropertyListInstance) {
this._mainPreferencesPropertyList = aMainPreferencesPropertyListInstance;
}
SearchStrings.prototype = {
type: MigrationUtils.resourceTypes.OTHERDATA,
migrate: function SS_migrate(aCallback) {
this._mainPreferencesPropertyList.read(MigrationUtils.wrapMigrateFunction(
function migrateSearchStrings(aDict) {
if (!aDict)
throw new Error("Could not get preferences dictionary");
if (aDict.has("RecentSearchStrings")) {
let recentSearchStrings = aDict.get("RecentSearchStrings");
if (recentSearchStrings && recentSearchStrings.length > 0) {
let changes = [{op: "add",
fieldname: "searchbar-history",
value: searchString}
for (searchString of recentSearchStrings)];
FormHistory.update(changes);
}
}
}.bind(this), aCallback));
}
};
function SafariProfileMigrator() {
}
SafariProfileMigrator.prototype = Object.create(MigratorPrototype);
SafariProfileMigrator.prototype.getResources = function SM_getResources() {
let profileDir =
#ifdef XP_MACOSX
FileUtils.getDir("ULibDir", ["Safari"], false);
#else
FileUtils.getDir("AppData", ["Apple Computer", "Safari"], false);
#endif
if (!profileDir.exists())
return null;
let resources = [];
let pushProfileFileResource = function(aFileName, aConstructor) {
let file = profileDir.clone();
file.append(aFileName);
if (file.exists())
resources.push(new aConstructor(file));
};
pushProfileFileResource("History.plist", History);
pushProfileFileResource("Bookmarks.plist", Bookmarks);
// The Reading List feature was introduced at the same time in Windows and
// Mac versions of Safari. Not surprisingly, they are stored in the same
// format in both versions. Surpsingly, only on Windows there is a
// separate property list for it. This isn't #ifdefed out on mac, because
// Apple may fix this at some point.
pushProfileFileResource("ReadingList.plist", Bookmarks);
let prefsDir =
#ifdef XP_MACOSX
FileUtils.getDir("UsrPrfs", [], false);
#else
FileUtils.getDir("AppData", ["Apple Computer", "Preferences"], false);
#endif
let prefs = this.mainPreferencesPropertyList;
if (prefs) {
resources.push(new SearchStrings(prefs));
}
return resources;
};
Object.defineProperty(SafariProfileMigrator.prototype, "mainPreferencesPropertyList", {
get: function get_mainPreferencesPropertyList() {
if (this._mainPreferencesPropertyList === undefined) {
let file =
#ifdef XP_MACOSX
FileUtils.getDir("UsrPrfs", [], false);
#else
FileUtils.getDir("AppData", ["Apple Computer", "Preferences"], false);
#endif
if (file.exists()) {
file.append("com.apple.Safari.plist");
if (file.exists()) {
return this._mainPreferencesPropertyList =
new MainPreferencesPropertyList(file);
}
}
return this._mainPreferencesPropertyList = null;
}
return this._mainPreferencesPropertyList;
}
});
Object.defineProperty(SafariProfileMigrator.prototype, "sourceHomePageURL", {
get: function get_sourceHomePageURL() {
if (this.mainPreferencesPropertyList) {
let dict = this.mainPreferencesPropertyList._readSync();
if (dict.has("HomePage"))
return dict.get("HomePage");
}
return "";
}
});
SafariProfileMigrator.prototype.classDescription = "Safari Profile Migrator";
SafariProfileMigrator.prototype.contractID = "@mozilla.org/profile/migrator;1?app=browser&type=safari";
SafariProfileMigrator.prototype.classID = Components.ID("{4b609ecf-60b2-4655-9df4-dc149e474da1}");
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([SafariProfileMigrator]);

View file

@ -0,0 +1,474 @@
/* 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 Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
const kIMig = Ci.nsIBrowserProfileMigrator;
const kIPStartup = Ci.nsIProfileStartup;
Cu.import("resource:///modules/MigrationUtils.jsm");
var MigrationWizard = {
_source: "", // Source Profile Migrator ContractID suffix
_itemsFlags: kIMig.ALL, // Selected Import Data Sources (16-bit bitfield)
_selectedProfile: null, // Selected Profile name to import from
_wiz: null,
_migrator: null,
_autoMigrate: null,
init: function ()
{
var os = Components.classes["@mozilla.org/observer-service;1"]
.getService(Components.interfaces.nsIObserverService);
os.addObserver(this, "Migration:Started", false);
os.addObserver(this, "Migration:ItemBeforeMigrate", false);
os.addObserver(this, "Migration:ItemAfterMigrate", false);
os.addObserver(this, "Migration:ItemError", false);
os.addObserver(this, "Migration:Ended", false);
this._wiz = document.documentElement;
if ("arguments" in window && window.arguments.length > 1) {
this._source = window.arguments[0];
this._migrator = window.arguments[1] instanceof kIMig ?
window.arguments[1] : null;
this._autoMigrate = window.arguments[2].QueryInterface(kIPStartup);
this._skipImportSourcePage = window.arguments[3];
if (this._migrator && window.arguments[4]) {
let sourceProfiles = this._migrator.sourceProfiles;
this._selectedProfile = sourceProfiles.find(profile => profile.id == window.arguments[4]);
}
if (this._autoMigrate) {
// Show the "nothing" option in the automigrate case to provide an
// easily identifiable way to avoid migration and create a new profile.
var nothing = document.getElementById("nothing");
nothing.hidden = false;
}
}
this.onImportSourcePageShow();
},
uninit: function ()
{
var os = Components.classes["@mozilla.org/observer-service;1"]
.getService(Components.interfaces.nsIObserverService);
os.removeObserver(this, "Migration:Started");
os.removeObserver(this, "Migration:ItemBeforeMigrate");
os.removeObserver(this, "Migration:ItemAfterMigrate");
os.removeObserver(this, "Migration:ItemError");
os.removeObserver(this, "Migration:Ended");
MigrationUtils.finishMigration();
},
// 1 - Import Source
onImportSourcePageShow: function ()
{
this._wiz.canRewind = false;
var selectedMigrator = null;
// Figure out what source apps are are available to import from:
var group = document.getElementById("importSourceGroup");
for (var i = 0; i < group.childNodes.length; ++i) {
var migratorKey = group.childNodes[i].id;
if (migratorKey != "nothing") {
var migrator = MigrationUtils.getMigrator(migratorKey);
if (migrator) {
// Save this as the first selectable item, if we don't already have
// one, or if it is the migrator that was passed to us.
if (!selectedMigrator || this._source == migratorKey)
selectedMigrator = group.childNodes[i];
} else {
// Hide this option
group.childNodes[i].hidden = true;
}
}
}
if (selectedMigrator)
group.selectedItem = selectedMigrator;
else {
// We didn't find a migrator, notify the user
document.getElementById("noSources").hidden = false;
this._wiz.canAdvance = false;
document.getElementById("importBookmarks").hidden = true;
document.getElementById("importAll").hidden = true;
}
// Advance to the next page if the caller told us to.
if (this._migrator && this._skipImportSourcePage) {
this._wiz.advance();
this._wiz.canRewind = false;
}
},
onImportSourcePageAdvanced: function ()
{
var newSource = document.getElementById("importSourceGroup").selectedItem.id;
if (newSource == "nothing") {
document.documentElement.cancel();
return false;
}
if (!this._migrator || (newSource != this._source)) {
// Create the migrator for the selected source.
this._migrator = MigrationUtils.getMigrator(newSource);
this._itemsFlags = kIMig.ALL;
this._selectedProfile = null;
}
this._source = newSource;
// check for more than one source profile
var sourceProfiles = this._migrator.sourceProfiles;
if (this._skipImportSourcePage) {
this._wiz.currentPage.next = "homePageImport";
}
else if (sourceProfiles && sourceProfiles.length > 1) {
this._wiz.currentPage.next = "selectProfile";
}
else {
if (this._autoMigrate)
this._wiz.currentPage.next = "homePageImport";
else
this._wiz.currentPage.next = "importItems";
if (sourceProfiles && sourceProfiles.length == 1)
this._selectedProfile = sourceProfiles[0];
else
this._selectedProfile = null;
}
},
// 2 - [Profile Selection]
onSelectProfilePageShow: function ()
{
// Disabling this for now, since we ask about import sources in automigration
// too and don't want to disable the back button
// if (this._autoMigrate)
// document.documentElement.getButton("back").disabled = true;
var profiles = document.getElementById("profiles");
while (profiles.hasChildNodes())
profiles.removeChild(profiles.firstChild);
// Note that this block is still reached even if the user chose 'From File'
// and we canceled the dialog. When that happens, _migrator will be null.
if (this._migrator) {
var sourceProfiles = this._migrator.sourceProfiles;
for (let profile of sourceProfiles) {
var item = document.createElement("radio");
item.id = profile.id;
item.setAttribute("label", profile.name);
profiles.appendChild(item);
}
}
profiles.selectedItem = this._selectedProfile ? document.getElementById(this._selectedProfile.id) : profiles.firstChild;
},
onSelectProfilePageRewound: function ()
{
var profiles = document.getElementById("profiles");
this._selectedProfile = this._migrator.sourceProfiles.find(
profile => profile.id == profiles.selectedItem.id
) || null;
},
onSelectProfilePageAdvanced: function ()
{
var profiles = document.getElementById("profiles");
this._selectedProfile = this._migrator.sourceProfiles.find(
profile => profile.id == profiles.selectedItem.id
) || null;
// If we're automigrating or just doing bookmarks don't show the item selection page
if (this._autoMigrate)
this._wiz.currentPage.next = "homePageImport";
},
// 3 - ImportItems
onImportItemsPageShow: function ()
{
var dataSources = document.getElementById("dataSources");
while (dataSources.hasChildNodes())
dataSources.removeChild(dataSources.firstChild);
var items = this._migrator.getMigrateData(this._selectedProfile, this._autoMigrate);
for (var i = 0; i < 16; ++i) {
var itemID = (items >> i) & 0x1 ? Math.pow(2, i) : 0;
if (itemID > 0) {
var checkbox = document.createElement("checkbox");
checkbox.id = itemID;
checkbox.setAttribute("label",
MigrationUtils.getLocalizedString(itemID + "_" + this._source));
dataSources.appendChild(checkbox);
if (!this._itemsFlags || this._itemsFlags & itemID)
checkbox.checked = true;
}
}
},
onImportItemsPageRewound: function ()
{
this._wiz.canAdvance = true;
this.onImportItemsPageAdvanced();
},
onImportItemsPageAdvanced: function ()
{
var dataSources = document.getElementById("dataSources");
this._itemsFlags = 0;
for (var i = 0; i < dataSources.childNodes.length; ++i) {
var checkbox = dataSources.childNodes[i];
if (checkbox.localName == "checkbox" && checkbox.checked)
this._itemsFlags |= parseInt(checkbox.id);
}
},
onImportItemCommand: function (aEvent)
{
var items = document.getElementById("dataSources");
var checkboxes = items.getElementsByTagName("checkbox");
var oneChecked = false;
for (var i = 0; i < checkboxes.length; ++i) {
if (checkboxes[i].checked) {
oneChecked = true;
break;
}
}
this._wiz.canAdvance = oneChecked;
},
// 4 - Home Page Selection
onHomePageMigrationPageShow: function ()
{
// only want this on the first run
if (!this._autoMigrate) {
this._wiz.advance();
return;
}
var brandBundle = document.getElementById("brandBundle");
// These strings don't exist when not using official branding. If that's
// the case, just skip this page.
try {
var pageTitle = brandBundle.getString("homePageMigrationPageTitle");
var pageDesc = brandBundle.getString("homePageMigrationDescription");
var mainStr = brandBundle.getString("homePageSingleStartMain");
}
catch (e) {
this._wiz.advance();
return;
}
document.getElementById("homePageImport").setAttribute("label", pageTitle);
document.getElementById("homePageImportDesc").setAttribute("value", pageDesc);
this._wiz._adjustWizardHeader();
var singleStart = document.getElementById("homePageSingleStart");
singleStart.setAttribute("label", mainStr);
singleStart.setAttribute("value", "DEFAULT");
var source = null;
switch (this._source) {
case "ie":
source = "sourceNameIE";
break;
case "safari":
source = "sourceNameSafari";
break;
case "chrome":
source = "sourceNameChrome";
break;
case "firefox":
source = "sourceNameFirefox";
break;
}
// semi-wallpaper for crash when multiple profiles exist, since we haven't initialized mSourceProfile in places
this._migrator.getMigrateData(this._selectedProfile, this._autoMigrate);
var oldHomePageURL = this._migrator.sourceHomePageURL;
if (oldHomePageURL && source) {
var appName = MigrationUtils.getLocalizedString(source);
var oldHomePageLabel =
brandBundle.getFormattedString("homePageImport", [appName]);
var oldHomePage = document.getElementById("oldHomePage");
oldHomePage.setAttribute("label", oldHomePageLabel);
oldHomePage.setAttribute("value", oldHomePageURL);
oldHomePage.removeAttribute("hidden");
}
else {
// if we don't have at least two options, just advance
this._wiz.advance();
}
},
onHomePageMigrationPageAdvanced: function ()
{
// we might not have a selectedItem if we're in fallback mode
try {
var radioGroup = document.getElementById("homePageRadiogroup");
this._newHomePage = radioGroup.selectedItem.value;
} catch(ex) {}
},
// 5 - Migrating
onMigratingPageShow: function ()
{
this._wiz.getButton("cancel").disabled = true;
this._wiz.canRewind = false;
this._wiz.canAdvance = false;
// When automigrating, show all of the data that can be received from this source.
if (this._autoMigrate)
this._itemsFlags = this._migrator.getMigrateData(this._selectedProfile, this._autoMigrate);
this._listItems("migratingItems");
setTimeout(this.onMigratingMigrate, 0, this);
},
onMigratingMigrate: function (aOuter)
{
aOuter._migrator.migrate(aOuter._itemsFlags, aOuter._autoMigrate, aOuter._selectedProfile);
},
_listItems: function (aID)
{
var items = document.getElementById(aID);
while (items.hasChildNodes())
items.removeChild(items.firstChild);
var brandBundle = document.getElementById("brandBundle");
var itemID;
for (var i = 0; i < 16; ++i) {
var itemID = (this._itemsFlags >> i) & 0x1 ? Math.pow(2, i) : 0;
if (itemID > 0) {
var label = document.createElement("label");
label.id = itemID + "_migrated";
try {
label.setAttribute("value",
MigrationUtils.getLocalizedString(itemID + "_" + this._source));
items.appendChild(label);
}
catch (e) {
// if the block above throws, we've enumerated all the import data types we
// currently support and are now just wasting time, break.
break;
}
}
}
},
observe: function (aSubject, aTopic, aData)
{
switch (aTopic) {
case "Migration:Started":
break;
case "Migration:ItemBeforeMigrate":
var label = document.getElementById(aData + "_migrated");
if (label)
label.setAttribute("style", "font-weight: bold");
break;
case "Migration:ItemAfterMigrate":
var label = document.getElementById(aData + "_migrated");
if (label)
label.removeAttribute("style");
break;
case "Migration:Ended":
if (this._autoMigrate) {
if (this._newHomePage) {
try {
// set homepage properly
var prefSvc = Components.classes["@mozilla.org/preferences-service;1"]
.getService(Components.interfaces.nsIPrefService);
var prefBranch = prefSvc.getBranch(null);
if (this._newHomePage == "DEFAULT") {
prefBranch.clearUserPref("browser.startup.homepage");
}
else {
var str = Components.classes["@mozilla.org/supports-string;1"]
.createInstance(Components.interfaces.nsISupportsString);
str.data = this._newHomePage;
prefBranch.setComplexValue("browser.startup.homepage",
Components.interfaces.nsISupportsString,
str);
}
var dirSvc = Components.classes["@mozilla.org/file/directory_service;1"]
.getService(Components.interfaces.nsIProperties);
var prefFile = dirSvc.get("ProfDS", Components.interfaces.nsIFile);
prefFile.append("prefs.js");
prefSvc.savePrefFile(prefFile);
} catch(ex) {
dump(ex);
}
}
// We're done now.
this._wiz.canAdvance = true;
this._wiz.advance();
setTimeout(close, 5000);
}
else {
this._wiz.canAdvance = true;
var nextButton = this._wiz.getButton("next");
nextButton.click();
}
break;
case "Migration:ItemError":
var type = "undefined";
switch (parseInt(aData)) {
case Ci.nsIBrowserProfileMigrator.SETTINGS:
type = "settings";
break;
case Ci.nsIBrowserProfileMigrator.COOKIES:
type = "cookies";
break;
case Ci.nsIBrowserProfileMigrator.HISTORY:
type = "history";
break;
case Ci.nsIBrowserProfileMigrator.FORMDATA:
type = "form data";
break;
case Ci.nsIBrowserProfileMigrator.PASSWORDS:
type = "passwords";
break;
case Ci.nsIBrowserProfileMigrator.BOOKMARKS:
type = "bookmarks";
break;
case Ci.nsIBrowserProfileMigrator.OTHERDATA:
type = "misc. data";
break;
}
Cc["@mozilla.org/consoleservice;1"]
.getService(Ci.nsIConsoleService)
.logStringMessage("some " + type + " did not successfully migrate.");
break;
}
},
onDonePageShow: function ()
{
this._wiz.getButton("cancel").disabled = true;
this._wiz.canRewind = false;
this._listItems("doneItems");
}
};

View file

@ -0,0 +1,96 @@
<?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"?>
<!DOCTYPE dialog SYSTEM "chrome://browser/locale/migration/migration.dtd" >
<wizard id="migrationWizard"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
windowtype="Browser:MigrationWizard"
title="&migrationWizard.title;"
onload="MigrationWizard.init()"
onunload="MigrationWizard.uninit()"
style="width: 40em;"
buttons="accept,cancel"
branded="true">
<script type="application/javascript" src="chrome://browser/content/migration/migration.js"/>
<stringbundle id="brandBundle" src="chrome://branding/locale/brand.properties"/>
<wizardpage id="importSource" pageid="importSource" next="selectProfile"
label="&importSource.title;"
onpageadvanced="return MigrationWizard.onImportSourcePageAdvanced();">
<description id="importAll" control="importSourceGroup">&importFrom.label;</description>
<description id="importBookmarks" control="importSourceGroup" hidden="true">&importFromBookmarks.label;</description>
<radiogroup id="importSourceGroup" align="start">
<radio id="firefox" label="&importFromFirefox.label;" accesskey="&importFromFirefox.accesskey;"/>
#ifdef XP_WIN
<radio id="ie" label="&importFromIE.label;" accesskey="&importFromIE.accesskey;"/>
<radio id="chrome" label="&importFromChrome.label;" accesskey="&importFromChrome.accesskey;"/>
<radio id="safari" label="&importFromSafari.label;" accesskey="&importFromSafari.accesskey;"/>
#elifdef XP_MACOSX
<radio id="safari" label="&importFromSafari.label;" accesskey="&importFromSafari.accesskey;"/>
<radio id="chrome" label="&importFromChrome.label;" accesskey="&importFromChrome.accesskey;"/>
#elifdef XP_UNIX
<radio id="chrome" label="&importFromChrome.label;" accesskey="&importFromChrome.accesskey;"/>
#endif
<radio id="nothing" label="&importFromNothing.label;" accesskey="&importFromNothing.accesskey;" hidden="true"/>
</radiogroup>
<label id="noSources" hidden="true">&noMigrationSources.label;</label>
</wizardpage>
<wizardpage id="selectProfile" pageid="selectProfile" label="&selectProfile.title;"
next="importItems"
onpageshow="return MigrationWizard.onSelectProfilePageShow();"
onpagerewound="return MigrationWizard.onSelectProfilePageRewound();"
onpageadvanced="return MigrationWizard.onSelectProfilePageAdvanced();">
<description control="profiles">&selectProfile.label;</description>
<radiogroup id="profiles" align="left"/>
</wizardpage>
<wizardpage id="importItems" pageid="importItems" label="&importItems.title;"
next="homePageImport"
onpageshow="return MigrationWizard.onImportItemsPageShow();"
onpagerewound="return MigrationWizard.onImportItemsPageRewound();"
onpageadvanced="return MigrationWizard.onImportItemsPageAdvanced();"
oncommand="MigrationWizard.onImportItemCommand();">
<description control="dataSources">&importItems.label;</description>
<vbox id="dataSources" style="overflow: auto; -moz-appearance: listbox" align="left" flex="1" role="group"/>
</wizardpage>
<wizardpage id="homePageImport" pageid="homePageImport"
next="migrating"
onpageshow="return MigrationWizard.onHomePageMigrationPageShow();"
onpageadvanced="return MigrationWizard.onHomePageMigrationPageAdvanced();">
<description id="homePageImportDesc" control="homePageRadioGroup"/>
<radiogroup id="homePageRadiogroup">
<radio id="homePageSingleStart" selected="true" />
<radio id="oldHomePage" hidden="true" />
</radiogroup>
</wizardpage>
<wizardpage id="migrating" pageid="migrating" label="&migrating.title;"
next="done"
onpageshow="MigrationWizard.onMigratingPageShow();">
<description control="migratingItems">&migrating.label;</description>
<vbox id="migratingItems" style="overflow: auto;" align="left" role="group"/>
</wizardpage>
<wizardpage id="done" pageid="done" label="&done.title;"
onpageshow="MigrationWizard.onDonePageShow();">
<description control="doneItems">&done.label;</description>
<vbox id="doneItems" style="overflow: auto;" align="left" role="group"/>
</wizardpage>
</wizard>

View file

@ -0,0 +1,7 @@
# 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/migration/migration.xul (content/migration.xul)
content/browser/migration/migration.js (content/migration.js)

View file

@ -0,0 +1,53 @@
# -*- 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/.
JAR_MANIFESTS += ['jar.mn']
XPIDL_SOURCES += [
'nsIBrowserProfileMigrator.idl',
]
XPIDL_MODULE = 'migration'
if CONFIG['OS_ARCH'] == 'WINNT':
SOURCES += [
'nsIEHistoryEnumerator.cpp',
]
EXTRA_COMPONENTS += [
'FirefoxProfileMigrator.js',
'ProfileMigrator.js',
]
if CONFIG['OS_ARCH'] == 'WINNT':
EXTRA_COMPONENTS += [
'IEProfileMigrator.js',
]
DEFINES['HAS_IE_MIGRATOR'] = True
EXTRA_PP_COMPONENTS += [
'BrowserProfileMigrators.manifest',
'ChromeProfileMigrator.js',
]
if CONFIG['OS_ARCH'] == 'WINNT':
EXTRA_PP_COMPONENTS += [
'SafariProfileMigrator.js',
]
DEFINES['HAS_SAFARI_MIGRATOR'] = True
if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
EXTRA_PP_COMPONENTS += [
'SafariProfileMigrator.js',
]
DEFINES['HAS_SAFARI_MIGRATOR'] = True
EXTRA_PP_JS_MODULES += [
'MigrationUtils.jsm',
]
FINAL_LIBRARY = 'browsercomps'

View file

@ -0,0 +1,63 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsISupports.idl"
interface nsIArray;
interface nsIProfileStartup;
[scriptable, uuid(30e5a7ec-f71e-4f41-9dbd-7429c02132ec)]
interface nsIBrowserProfileMigrator : nsISupports
{
/**
* profile items to migrate. use with migrate().
*/
const unsigned short ALL = 0x0000;
const unsigned short SETTINGS = 0x0001;
const unsigned short COOKIES = 0x0002;
const unsigned short HISTORY = 0x0004;
const unsigned short FORMDATA = 0x0008;
const unsigned short PASSWORDS = 0x0010;
const unsigned short BOOKMARKS = 0x0020;
const unsigned short OTHERDATA = 0x0040;
/**
* Copy user profile information to the current active profile.
* @param aItems list of data items to migrate. see above for values.
* @param aStartup helper interface which is non-null if called during startup.
* @param aProfile profile to migrate from, if there is more than one.
*/
void migrate(in unsigned short aItems, in nsIProfileStartup aStartup, in jsval aProfile);
/**
* A bit field containing profile items that this migrator
* offers for import.
* @param aProfile the profile that we are looking for available data
* to import
* @param aDoingStartup "true" if the profile is not currently being used.
* @return bit field containing profile items (see above)
* @note a return value of 0 represents no items rather than ALL.
*/
unsigned short getMigrateData(in jsval aProfile, in boolean aDoingStartup);
/**
* Whether or not there is any data that can be imported from this
* browser (i.e. whether or not it is installed, and there exists
* a user profile)
*/
readonly attribute boolean sourceExists;
/**
* An enumeration of available profiles. If the import source does
* not support profiles, this attribute is null.
*/
readonly attribute jsval sourceProfiles;
/**
* The import source homepage. Returns null if not present/available
*/
readonly attribute AUTF8String sourceHomePageURL;
};

View file

@ -0,0 +1,139 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsIEHistoryEnumerator.h"
#include <urlhist.h>
#include <shlguid.h>
#include "nsStringAPI.h"
#include "nsNetUtil.h"
#include "nsIVariant.h"
#include "nsCOMArray.h"
#include "nsArrayEnumerator.h"
namespace {
PRTime FileTimeToPRTime(FILETIME* filetime)
{
SYSTEMTIME st;
::FileTimeToSystemTime(filetime, &st);
PRExplodedTime prt;
prt.tm_year = st.wYear;
// SYSTEMTIME's day-of-month parameter is 1-based,
// PRExplodedTime's is 0-based.
prt.tm_month = st.wMonth - 1;
prt.tm_mday = st.wDay;
prt.tm_hour = st.wHour;
prt.tm_min = st.wMinute;
prt.tm_sec = st.wSecond;
prt.tm_usec = st.wMilliseconds * 1000;
prt.tm_wday = 0;
prt.tm_yday = 0;
prt.tm_params.tp_gmt_offset = 0;
prt.tm_params.tp_dst_offset = 0;
return PR_ImplodeTime(&prt);
}
} // Anonymous namespace.
////////////////////////////////////////////////////////////////////////////////
//// nsIEHistoryEnumerator
NS_IMPL_ISUPPORTS(nsIEHistoryEnumerator, nsISimpleEnumerator)
nsIEHistoryEnumerator::nsIEHistoryEnumerator()
{
::CoInitialize(nullptr);
}
nsIEHistoryEnumerator::~nsIEHistoryEnumerator()
{
::CoUninitialize();
}
void
nsIEHistoryEnumerator::EnsureInitialized()
{
if (mURLEnumerator)
return;
HRESULT hr = ::CoCreateInstance(CLSID_CUrlHistory,
nullptr,
CLSCTX_INPROC_SERVER,
IID_IUrlHistoryStg2,
getter_AddRefs(mIEHistory));
if (FAILED(hr))
return;
hr = mIEHistory->EnumUrls(getter_AddRefs(mURLEnumerator));
if (FAILED(hr))
return;
}
NS_IMETHODIMP
nsIEHistoryEnumerator::HasMoreElements(bool* _retval)
{
*_retval = false;
EnsureInitialized();
MOZ_ASSERT(mURLEnumerator, "Should have instanced an IE History URLEnumerator");
if (!mURLEnumerator)
return NS_OK;
STATURL statURL;
ULONG fetched;
// First argument is not implemented, so doesn't matter what we pass.
HRESULT hr = mURLEnumerator->Next(1, &statURL, &fetched);
if (FAILED(hr) || fetched != 1UL) {
// Reached the last entry.
return NS_OK;
}
nsCOMPtr<nsIURI> uri;
if (statURL.pwcsUrl) {
nsDependentString url(statURL.pwcsUrl);
nsresult rv = NS_NewURI(getter_AddRefs(uri), url);
::CoTaskMemFree(statURL.pwcsUrl);
if (NS_FAILED(rv)) {
// Got a corrupt or invalid URI, continue to the next entry.
return HasMoreElements(_retval);
}
}
nsDependentString title(statURL.pwcsTitle);
PRTime lastVisited = FileTimeToPRTime(&(statURL.ftLastVisited));
mCachedNextEntry = do_CreateInstance("@mozilla.org/hash-property-bag;1");
MOZ_ASSERT(mCachedNextEntry, "Should have instanced a new property bag");
if (mCachedNextEntry) {
mCachedNextEntry->SetPropertyAsInterface(NS_LITERAL_STRING("uri"), uri);
mCachedNextEntry->SetPropertyAsAString(NS_LITERAL_STRING("title"), title);
mCachedNextEntry->SetPropertyAsInt64(NS_LITERAL_STRING("time"), lastVisited);
*_retval = true;
}
if (statURL.pwcsTitle)
::CoTaskMemFree(statURL.pwcsTitle);
return NS_OK;
}
NS_IMETHODIMP
nsIEHistoryEnumerator::GetNext(nsISupports** _retval)
{
*_retval = nullptr;
if (!mCachedNextEntry)
return NS_ERROR_FAILURE;
NS_ADDREF(*_retval = mCachedNextEntry);
// Release the cached entry, so it can't be returned twice.
mCachedNextEntry = nullptr;
return NS_OK;
}

View file

@ -0,0 +1,37 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef iehistoryenumerator___h___
#define iehistoryenumerator___h___
#include <urlhist.h>
#include "mozilla/Attributes.h"
#include "nsISimpleEnumerator.h"
#include "nsIWritablePropertyBag2.h"
#include "nsAutoPtr.h"
class nsIEHistoryEnumerator final : public nsISimpleEnumerator
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSISIMPLEENUMERATOR
nsIEHistoryEnumerator();
private:
~nsIEHistoryEnumerator();
/**
* Initializes the history reader, if needed.
*/
void EnsureInitialized();
nsRefPtr<IUrlHistoryStg2> mIEHistory;
nsRefPtr<IEnumSTATURL> mURLEnumerator;
nsCOMPtr<nsIWritablePropertyBag2> mCachedNextEntry;
};
#endif

View file

@ -0,0 +1,44 @@
# -*- 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/.
DIRS += [
'about',
'certerror',
'dirprovider',
'downloads',
'feeds',
'fuel',
'places',
'preferences',
'privatebrowsing',
'search',
'sessionstore',
'shell',
'migration',
]
if CONFIG['MOZ_BROWSER_STATUSBAR']:
DIRS += ['statusbar']
DIRS += ['build']
XPIDL_SOURCES += [
'nsIBrowserGlue.idl',
'nsIBrowserHandler.idl',
]
XPIDL_MODULE = 'browsercompsbase'
EXTRA_COMPONENTS += [ 'BrowserComponents.manifest' ]
EXTRA_PP_COMPONENTS += [
'nsBrowserContentHandler.js',
'nsBrowserGlue.js',
]
EXTRA_JS_MODULES += [
'distribution.js',
]

View file

@ -0,0 +1,812 @@
# 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/.
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
Components.utils.import("resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
"resource://gre/modules/PrivateBrowsingUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow",
"resource:///modules/RecentWindow.jsm");
const nsISupports = Components.interfaces.nsISupports;
const nsIBrowserDOMWindow = Components.interfaces.nsIBrowserDOMWindow;
const nsIBrowserHandler = Components.interfaces.nsIBrowserHandler;
const nsIBrowserHistory = Components.interfaces.nsIBrowserHistory;
const nsIChannel = Components.interfaces.nsIChannel;
const nsICommandLine = Components.interfaces.nsICommandLine;
const nsICommandLineHandler = Components.interfaces.nsICommandLineHandler;
const nsIContentHandler = Components.interfaces.nsIContentHandler;
const nsIDocShellTreeItem = Components.interfaces.nsIDocShellTreeItem;
const nsIDOMChromeWindow = Components.interfaces.nsIDOMChromeWindow;
const nsIDOMWindow = Components.interfaces.nsIDOMWindow;
const nsIFileURL = Components.interfaces.nsIFileURL;
const nsIInterfaceRequestor = Components.interfaces.nsIInterfaceRequestor;
const nsINetUtil = Components.interfaces.nsINetUtil;
const nsIPrefBranch = Components.interfaces.nsIPrefBranch;
const nsIPrefLocalizedString = Components.interfaces.nsIPrefLocalizedString;
const nsISupportsString = Components.interfaces.nsISupportsString;
const nsIURIFixup = Components.interfaces.nsIURIFixup;
const nsIWebNavigation = Components.interfaces.nsIWebNavigation;
const nsIWindowMediator = Components.interfaces.nsIWindowMediator;
const nsIWindowWatcher = Components.interfaces.nsIWindowWatcher;
const nsIWebNavigationInfo = Components.interfaces.nsIWebNavigationInfo;
const nsIBrowserSearchService = Components.interfaces.nsIBrowserSearchService;
const nsICommandLineValidator = Components.interfaces.nsICommandLineValidator;
const NS_BINDING_ABORTED = Components.results.NS_BINDING_ABORTED;
const NS_ERROR_WONT_HANDLE_CONTENT = 0x805d0001;
const NS_ERROR_ABORT = Components.results.NS_ERROR_ABORT;
const URI_INHERITS_SECURITY_CONTEXT = Components.interfaces.nsIHttpProtocolHandler
.URI_INHERITS_SECURITY_CONTEXT;
function shouldLoadURI(aURI) {
if (aURI && !aURI.schemeIs("chrome"))
return true;
dump("*** Preventing external load of chrome: URI into browser window\n");
dump(" Use -chrome <uri> instead\n");
return false;
}
function resolveURIInternal(aCmdLine, aArgument) {
var uri = aCmdLine.resolveURI(aArgument);
var urifixup = Components.classes["@mozilla.org/docshell/urifixup;1"]
.getService(nsIURIFixup);
if (!(uri instanceof nsIFileURL)) {
return urifixup.createFixupURI(aArgument,
urifixup.FIXUP_FLAG_FIX_SCHEME_TYPOS);
}
try {
if (uri.file.exists())
return uri;
}
catch (e) {
Components.utils.reportError(e);
}
// We have interpreted the argument as a relative file URI, but the file
// doesn't exist. Try URI fixup heuristics: see bug 290782.
try {
uri = urifixup.createFixupURI(aArgument, 0);
}
catch (e) {
Components.utils.reportError(e);
}
return uri;
}
var gFirstWindow = false;
const OVERRIDE_NONE = 0;
const OVERRIDE_NEW_PROFILE = 1;
const OVERRIDE_NEW_MSTONE = 2;
const OVERRIDE_NEW_BUILD_ID = 3;
/**
* Determines whether a home page override is needed.
* Returns:
* OVERRIDE_NEW_PROFILE if this is the first run with a new profile.
* OVERRIDE_NEW_MSTONE if this is the first run with a build with a different
* Goanna milestone (i.e. right after an upgrade).
* OVERRIDE_NEW_BUILD_ID if this is the first run with a new build ID of the
* same Goanna milestone (i.e. after a nightly upgrade).
* OVERRIDE_NONE otherwise.
*/
function needHomepageOverride(prefb) {
var savedmstone = null;
try {
savedmstone = prefb.getCharPref("browser.startup.homepage_override.mstone");
} catch (e) {}
if (savedmstone == "ignore")
return OVERRIDE_NONE;
var mstone = Services.appinfo.platformVersion;
var savedBuildID = null;
try {
savedBuildID = prefb.getCharPref("browser.startup.homepage_override.buildID");
} catch (e) {}
var buildID = Services.appinfo.platformBuildID;
if (mstone != savedmstone) {
// Bug 462254. Previous releases had a default pref to suppress the EULA
// agreement if the platform's installer had already shown one. Now with
// about:rights we've removed the EULA stuff and default pref, but we need
// a way to make existing profiles retain the default that we removed.
if (savedmstone)
prefb.setBoolPref("browser.rights.3.shown", true);
prefb.setCharPref("browser.startup.homepage_override.mstone", mstone);
prefb.setCharPref("browser.startup.homepage_override.buildID", buildID);
return (savedmstone ? OVERRIDE_NEW_MSTONE : OVERRIDE_NEW_PROFILE);
}
if (buildID != savedBuildID) {
prefb.setCharPref("browser.startup.homepage_override.buildID", buildID);
return OVERRIDE_NEW_BUILD_ID;
}
return OVERRIDE_NONE;
}
/**
* Gets the override page for the first run after the application has been
* updated.
* @param defaultOverridePage
* The default override page.
* @return The override page.
*/
function getPostUpdateOverridePage(defaultOverridePage) {
var um = Components.classes["@mozilla.org/updates/update-manager;1"]
.getService(Components.interfaces.nsIUpdateManager);
try {
// If the updates.xml file is deleted then getUpdateAt will throw.
var update = um.getUpdateAt(0)
.QueryInterface(Components.interfaces.nsIPropertyBag);
} catch (e) {
// This should never happen.
Components.utils.reportError("Unable to find update: " + e);
return defaultOverridePage;
}
let actions = update.getProperty("actions");
// When the update doesn't specify actions fallback to the original behavior
// of displaying the default override page.
if (!actions)
return defaultOverridePage;
// The existence of silent or the non-existence of showURL in the actions both
// mean that an override page should not be displayed.
if (actions.indexOf("silent") != -1 || actions.indexOf("showURL") == -1)
return "";
return update.getProperty("openURL") || defaultOverridePage;
}
// Flag used to indicate that the arguments to openWindow can be passed directly.
const NO_EXTERNAL_URIS = 1;
function openWindow(parent, url, target, features, args, noExternalArgs) {
var wwatch = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
.getService(nsIWindowWatcher);
if (noExternalArgs == NO_EXTERNAL_URIS) {
// Just pass in the defaultArgs directly
var argstring;
if (args) {
argstring = Components.classes["@mozilla.org/supports-string;1"]
.createInstance(nsISupportsString);
argstring.data = args;
}
return wwatch.openWindow(parent, url, target, features, argstring);
}
// Pass an array to avoid the browser "|"-splitting behavior.
var argArray = Components.classes["@mozilla.org/supports-array;1"]
.createInstance(Components.interfaces.nsISupportsArray);
// add args to the arguments array
var stringArgs = null;
if (args instanceof Array) // array
stringArgs = args;
else if (args) // string
stringArgs = [args];
if (stringArgs) {
// put the URIs into argArray
var uriArray = Components.classes["@mozilla.org/supports-array;1"]
.createInstance(Components.interfaces.nsISupportsArray);
stringArgs.forEach(function (uri) {
var sstring = Components.classes["@mozilla.org/supports-string;1"]
.createInstance(nsISupportsString);
sstring.data = uri;
uriArray.AppendElement(sstring);
});
argArray.AppendElement(uriArray);
} else {
argArray.AppendElement(null);
}
// Pass these as null to ensure that we always trigger the "single URL"
// behavior in browser.js's gBrowserInit.onLoad (which handles the window
// arguments)
argArray.AppendElement(null); // charset
argArray.AppendElement(null); // referer
argArray.AppendElement(null); // postData
argArray.AppendElement(null); // allowThirdPartyFixup
return wwatch.openWindow(parent, url, target, features, argArray);
}
function openPreferences() {
var features = "chrome,titlebar,toolbar,centerscreen,dialog=no";
var url = "chrome://browser/content/preferences/preferences.xul";
var win = getMostRecentWindow("Browser:Preferences");
if (win) {
win.focus();
} else {
openWindow(null, url, "_blank", features);
}
}
function getMostRecentWindow(aType) {
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
.getService(nsIWindowMediator);
return wm.getMostRecentWindow(aType);
}
function doSearch(searchTerm, cmdLine) {
var ss = Components.classes["@mozilla.org/browser/search-service;1"]
.getService(nsIBrowserSearchService);
var submission = ss.defaultEngine.getSubmission(searchTerm);
// fill our nsISupportsArray with uri-as-wstring, null, null, postData
var sa = Components.classes["@mozilla.org/supports-array;1"]
.createInstance(Components.interfaces.nsISupportsArray);
var wuri = Components.classes["@mozilla.org/supports-string;1"]
.createInstance(Components.interfaces.nsISupportsString);
wuri.data = submission.uri.spec;
sa.AppendElement(wuri);
sa.AppendElement(null);
sa.AppendElement(null);
sa.AppendElement(submission.postData);
// XXXbsmedberg: use handURIToExistingBrowser to obey tabbed-browsing
// preferences, but need nsIBrowserDOMWindow extensions
var wwatch = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
.getService(nsIWindowWatcher);
return wwatch.openWindow(null, gBrowserContentHandler.chromeURL,
"_blank",
"chrome,dialog=no,all" +
gBrowserContentHandler.getFeatures(cmdLine),
sa);
}
function nsBrowserContentHandler() {
}
nsBrowserContentHandler.prototype = {
classID: Components.ID("{5d0ce354-df01-421a-83fb-7ead0990c24e}"),
_xpcom_factory: {
createInstance: function bch_factory_ci(outer, iid) {
if (outer)
throw Components.results.NS_ERROR_NO_AGGREGATION;
return gBrowserContentHandler.QueryInterface(iid);
}
},
/* helper functions */
mChromeURL : null,
get chromeURL() {
if (this.mChromeURL) {
return this.mChromeURL;
}
var prefb = Components.classes["@mozilla.org/preferences-service;1"]
.getService(nsIPrefBranch);
this.mChromeURL = prefb.getCharPref("browser.chromeURL");
return this.mChromeURL;
},
/* nsISupports */
QueryInterface : XPCOMUtils.generateQI([nsICommandLineHandler,
nsIBrowserHandler,
nsIContentHandler,
nsICommandLineValidator]),
/* nsICommandLineHandler */
handle : function bch_handle(cmdLine) {
if (cmdLine.handleFlag("browser", false)) {
// Passing defaultArgs, so use NO_EXTERNAL_URIS
openWindow(null, this.chromeURL, "_blank",
"chrome,dialog=no,all" + this.getFeatures(cmdLine),
this.defaultArgs, NO_EXTERNAL_URIS);
cmdLine.preventDefault = true;
}
try {
var remoteCommand = cmdLine.handleFlagWithParam("remote", true);
}
catch (e) {
throw NS_ERROR_ABORT;
}
if (remoteCommand != null) {
try {
var a = /^\s*(\w+)\(([^\)]*)\)\s*$/.exec(remoteCommand);
var remoteVerb;
if (a) {
remoteVerb = a[1].toLowerCase();
var remoteParams = [];
var sepIndex = a[2].lastIndexOf(",");
if (sepIndex == -1)
remoteParams[0] = a[2];
else {
remoteParams[0] = a[2].substring(0, sepIndex);
remoteParams[1] = a[2].substring(sepIndex + 1);
}
}
switch (remoteVerb) {
case "openurl":
case "openfile":
// openURL(<url>)
// openURL(<url>,new-window)
// openURL(<url>,new-tab)
// First param is the URL, second param (if present) is the "target"
// (tab, window)
var url = remoteParams[0];
var target = nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW;
if (remoteParams[1]) {
var targetParam = remoteParams[1].toLowerCase()
.replace(/^\s*|\s*$/g, "");
if (targetParam == "new-tab")
target = nsIBrowserDOMWindow.OPEN_NEWTAB;
else if (targetParam == "new-window")
target = nsIBrowserDOMWindow.OPEN_NEWWINDOW;
else {
// The "target" param isn't one of our supported values, so
// assume it's part of a URL that contains commas.
url += "," + remoteParams[1];
}
}
var uri = resolveURIInternal(cmdLine, url);
handURIToExistingBrowser(uri, target, cmdLine);
break;
case "xfedocommand":
// xfeDoCommand(openBrowser)
if (remoteParams[0].toLowerCase() != "openbrowser")
throw NS_ERROR_ABORT;
// Passing defaultArgs, so use NO_EXTERNAL_URIS
openWindow(null, this.chromeURL, "_blank",
"chrome,dialog=no,all" + this.getFeatures(cmdLine),
this.defaultArgs, NO_EXTERNAL_URIS);
break;
default:
// Somebody sent us a remote command we don't know how to process:
// just abort.
throw "Unknown remote command.";
}
cmdLine.preventDefault = true;
}
catch (e) {
Components.utils.reportError(e);
// If we had a -remote flag but failed to process it, throw
// NS_ERROR_ABORT so that the xremote code knows to return a failure
// back to the handling code.
throw NS_ERROR_ABORT;
}
}
var uriparam;
try {
while ((uriparam = cmdLine.handleFlagWithParam("new-window", false))) {
var uri = resolveURIInternal(cmdLine, uriparam);
if (!shouldLoadURI(uri))
continue;
openWindow(null, this.chromeURL, "_blank",
"chrome,dialog=no,all" + this.getFeatures(cmdLine),
uri.spec);
cmdLine.preventDefault = true;
}
}
catch (e) {
Components.utils.reportError(e);
}
try {
while ((uriparam = cmdLine.handleFlagWithParam("new-tab", false))) {
var uri = resolveURIInternal(cmdLine, uriparam);
handURIToExistingBrowser(uri, nsIBrowserDOMWindow.OPEN_NEWTAB, cmdLine);
cmdLine.preventDefault = true;
}
}
catch (e) {
Components.utils.reportError(e);
}
var chromeParam = cmdLine.handleFlagWithParam("chrome", false);
if (chromeParam) {
// Handle the old preference dialog URL separately (bug 285416)
if (chromeParam == "chrome://browser/content/pref/pref.xul") {
openPreferences();
cmdLine.preventDefault = true;
} else try {
// only load URIs which do not inherit chrome privs
var features = "chrome,dialog=no,all" + this.getFeatures(cmdLine);
var uri = resolveURIInternal(cmdLine, chromeParam);
var netutil = Components.classes["@mozilla.org/network/util;1"]
.getService(nsINetUtil);
if (!netutil.URIChainHasFlags(uri, URI_INHERITS_SECURITY_CONTEXT)) {
openWindow(null, uri.spec, "_blank", features);
cmdLine.preventDefault = true;
}
}
catch (e) {
Components.utils.reportError(e);
}
}
if (cmdLine.handleFlag("preferences", false)) {
openPreferences();
cmdLine.preventDefault = true;
}
if (cmdLine.handleFlag("silent", false))
cmdLine.preventDefault = true;
try {
var privateWindowParam = cmdLine.handleFlagWithParam("private-window", false);
if (privateWindowParam) {
let resolvedURI = resolveURIInternal(cmdLine, privateWindowParam);
handURIToExistingBrowser(resolvedURI, nsIBrowserDOMWindow.OPEN_NEWTAB, cmdLine, true);
cmdLine.preventDefault = true;
}
} catch (e) {
if (e.result != Components.results.NS_ERROR_INVALID_ARG) {
throw e;
}
// NS_ERROR_INVALID_ARG is thrown when flag exists, but has no param.
if (cmdLine.handleFlag("private-window", false)) {
openWindow(null, this.chromeURL, "_blank",
"chrome,dialog=no,private,all" + this.getFeatures(cmdLine),
"about:privatebrowsing");
cmdLine.preventDefault = true;
}
}
var searchParam = cmdLine.handleFlagWithParam("search", false);
if (searchParam) {
doSearch(searchParam, cmdLine);
cmdLine.preventDefault = true;
}
// The global PB Service consumes this flag, so only eat it in per-window
// PB builds.
if (cmdLine.handleFlag("private", false)) {
PrivateBrowsingUtils.enterTemporaryAutoStartMode();
}
var fileParam = cmdLine.handleFlagWithParam("file", false);
if (fileParam) {
var file = cmdLine.resolveFile(fileParam);
var ios = Components.classes["@mozilla.org/network/io-service;1"]
.getService(Components.interfaces.nsIIOService);
var uri = ios.newFileURI(file);
openWindow(null, this.chromeURL, "_blank",
"chrome,dialog=no,all" + this.getFeatures(cmdLine),
uri.spec);
cmdLine.preventDefault = true;
}
#ifdef XP_WIN
// Handle "? searchterm" for Windows Vista start menu integration
for (var i = cmdLine.length - 1; i >= 0; --i) {
var param = cmdLine.getArgument(i);
if (param.match(/^\? /)) {
cmdLine.removeArguments(i, i);
cmdLine.preventDefault = true;
searchParam = param.substr(2);
doSearch(searchParam, cmdLine);
}
}
#endif
},
helpInfo : " -browser Open a browser window.\n" +
" -new-window <url> Open <url> in a new window.\n" +
" -new-tab <url> Open <url> in a new tab.\n" +
" -private-window <url> Open <url> in a new private window.\n" +
#ifdef XP_WIN
" -preferences Open Options dialog.\n" +
#else
" -preferences Open Preferences dialog.\n" +
#endif
" -search <term> Search <term> with your default search engine.\n",
/* nsIBrowserHandler */
get defaultArgs() {
var prefb = Components.classes["@mozilla.org/preferences-service;1"]
.getService(nsIPrefBranch);
if (!gFirstWindow) {
gFirstWindow = true;
if (PrivateBrowsingUtils.isInTemporaryAutoStartMode) {
return "about:privatebrowsing";
}
}
var overridePage = "";
var haveUpdateSession = false;
try {
// Read the old value of homepage_override.mstone before
// needHomepageOverride updates it, so that we can later add it to the
// URL if we do end up showing an overridePage. This makes it possible
// to have the overridePage's content vary depending on the version we're
// upgrading from.
let old_mstone = "unknown";
try {
old_mstone = Services.prefs.getCharPref("browser.startup.homepage_override.mstone");
} catch (ex) {}
let override = needHomepageOverride(prefb);
if (override != OVERRIDE_NONE) {
switch (override) {
case OVERRIDE_NEW_PROFILE:
// New profile.
overridePage = Services.urlFormatter.formatURLPref("startup.homepage_welcome_url");
break;
case OVERRIDE_NEW_MSTONE:
// Check whether we have a session to restore. If we do, we assume
// that this is an "update" session.
var ss = Components.classes["@mozilla.org/browser/sessionstartup;1"]
.getService(Components.interfaces.nsISessionStartup);
haveUpdateSession = ss.doRestore();
overridePage = Services.urlFormatter.formatURLPref("startup.homepage_override_url");
if (prefb.prefHasUserValue("app.update.postupdate"))
overridePage = getPostUpdateOverridePage(overridePage);
overridePage = overridePage.replace("%OLD_VERSION%", old_mstone);
break;
}
}
} catch (ex) {}
// formatURLPref might return "about:blank" if getting the pref fails
if (overridePage == "about:blank")
overridePage = "";
var startPage = "";
try {
var choice = prefb.getIntPref("browser.startup.page");
if (choice == 1 || choice == 3)
startPage = this.startPage;
} catch (e) {
Components.utils.reportError(e);
}
// Only show the startPage if we're not restoring an update session.
if (overridePage && startPage && !haveUpdateSession)
return overridePage + "|" + startPage;
return overridePage || startPage || "about:logopage";
},
get startPage() {
var uri = Services.prefs.getComplexValue("browser.startup.homepage",
nsIPrefLocalizedString).data;
if (!uri) {
Services.prefs.clearUserPref("browser.startup.homepage");
uri = Services.prefs.getComplexValue("browser.startup.homepage",
nsIPrefLocalizedString).data;
}
return uri;
},
mFeatures : null,
getFeatures : function bch_features(cmdLine) {
if (this.mFeatures === null) {
this.mFeatures = "";
try {
var width = cmdLine.handleFlagWithParam("width", false);
var height = cmdLine.handleFlagWithParam("height", false);
if (width)
this.mFeatures += ",width=" + width;
if (height)
this.mFeatures += ",height=" + height;
}
catch (e) {
}
// The global PB Service consumes this flag, so only eat it in per-window
// PB builds.
if (PrivateBrowsingUtils.isInTemporaryAutoStartMode) {
this.mFeatures = ",private";
}
}
return this.mFeatures;
},
/* nsIContentHandler */
handleContent : function bch_handleContent(contentType, context, request) {
try {
var webNavInfo = Components.classes["@mozilla.org/webnavigation-info;1"]
.getService(nsIWebNavigationInfo);
if (!webNavInfo.isTypeSupported(contentType, null)) {
throw NS_ERROR_WONT_HANDLE_CONTENT;
}
} catch (e) {
throw NS_ERROR_WONT_HANDLE_CONTENT;
}
request.QueryInterface(nsIChannel);
handURIToExistingBrowser(request.URI,
nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW, null);
request.cancel(NS_BINDING_ABORTED);
},
/* nsICommandLineValidator */
validate : function bch_validate(cmdLine) {
// Other handlers may use osint so only handle the osint flag if the url
// flag is also present and the command line is valid.
var osintFlagIdx = cmdLine.findFlag("osint", false);
var urlFlagIdx = cmdLine.findFlag("url", false);
if (urlFlagIdx > -1 && (osintFlagIdx > -1 ||
cmdLine.state == nsICommandLine.STATE_REMOTE_EXPLICIT)) {
var urlParam = cmdLine.getArgument(urlFlagIdx + 1);
if (cmdLine.length != urlFlagIdx + 2 || /firefoxurl:/.test(urlParam))
throw NS_ERROR_ABORT;
cmdLine.handleFlag("osint", false)
}
},
};
var gBrowserContentHandler = new nsBrowserContentHandler();
function handURIToExistingBrowser(uri, location, cmdLine, forcePrivate)
{
if (!shouldLoadURI(uri))
return;
// Unless using a private window is forced, open external links in private
// windows only if we're in perma-private mode.
var allowPrivate = forcePrivate || PrivateBrowsingUtils.permanentPrivateBrowsing;
var navWin = RecentWindow.getMostRecentBrowserWindow({private: allowPrivate});
if (!navWin) {
// if we couldn't load it in an existing window, open a new one
var features = "chrome,dialog=no,all" + gBrowserContentHandler.getFeatures(cmdLine);
if (forcePrivate) {
features += ",private";
}
openWindow(null, gBrowserContentHandler.chromeURL, "_blank", features, uri.spec);
return;
}
var navNav = navWin.QueryInterface(nsIInterfaceRequestor)
.getInterface(nsIWebNavigation);
var rootItem = navNav.QueryInterface(nsIDocShellTreeItem).rootTreeItem;
var rootWin = rootItem.QueryInterface(nsIInterfaceRequestor)
.getInterface(nsIDOMWindow);
var bwin = rootWin.QueryInterface(nsIDOMChromeWindow).browserDOMWindow;
bwin.openURI(uri, null, location,
nsIBrowserDOMWindow.OPEN_EXTERNAL);
}
function nsDefaultCommandLineHandler() {
}
nsDefaultCommandLineHandler.prototype = {
classID: Components.ID("{47cd0651-b1be-4a0f-b5c4-10e5a573ef71}"),
/* nsISupports */
QueryInterface : function dch_QI(iid) {
if (!iid.equals(nsISupports) &&
!iid.equals(nsICommandLineHandler))
throw Components.results.NS_ERROR_NO_INTERFACE;
return this;
},
#ifdef XP_WIN
_haveProfile: false,
#endif
/* nsICommandLineHandler */
handle : function dch_handle(cmdLine) {
var urilist = [];
#ifdef XP_WIN
// If we don't have a profile selected yet (e.g. the Profile Manager is
// displayed) we will crash if we open an url and then select a profile. To
// prevent this handle all url command line flags and set the command line's
// preventDefault to true to prevent the display of the ui. The initial
// command line will be retained when nsAppRunner calls LaunchChild though
// urls launched after the initial launch will be lost.
if (!this._haveProfile) {
try {
// This will throw when a profile has not been selected.
var fl = Components.classes["@mozilla.org/file/directory_service;1"]
.getService(Components.interfaces.nsIProperties);
var dir = fl.get("ProfD", Components.interfaces.nsILocalFile);
this._haveProfile = true;
}
catch (e) {
while ((ar = cmdLine.handleFlagWithParam("url", false))) { }
cmdLine.preventDefault = true;
}
}
#endif
try {
var ar;
while ((ar = cmdLine.handleFlagWithParam("url", false))) {
var uri = resolveURIInternal(cmdLine, ar);
urilist.push(uri);
}
}
catch (e) {
Components.utils.reportError(e);
}
let count = cmdLine.length;
for (let i = 0; i < count; ++i) {
var curarg = cmdLine.getArgument(i);
if (curarg.match(/^-/)) {
Components.utils.reportError("Warning: unrecognized command line flag " + curarg + "\n");
// To emulate the pre-nsICommandLine behavior, we ignore
// the argument after an unrecognized flag.
++i;
} else {
try {
urilist.push(resolveURIInternal(cmdLine, curarg));
}
catch (e) {
Components.utils.reportError("Error opening URI '" + curarg + "' from the command line: " + e + "\n");
}
}
}
if (urilist.length) {
if (cmdLine.state != nsICommandLine.STATE_INITIAL_LAUNCH &&
urilist.length == 1) {
// Try to find an existing window and load our URI into the
// current tab, new tab, or new window as prefs determine.
try {
handURIToExistingBrowser(urilist[0], nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW, cmdLine);
return;
}
catch (e) {
}
}
var URLlist = urilist.filter(shouldLoadURI).map(function (u) u.spec);
if (URLlist.length) {
openWindow(null, gBrowserContentHandler.chromeURL, "_blank",
"chrome,dialog=no,all" + gBrowserContentHandler.getFeatures(cmdLine),
URLlist);
}
}
else if (!cmdLine.preventDefault) {
// Passing defaultArgs, so use NO_EXTERNAL_URIS
openWindow(null, gBrowserContentHandler.chromeURL, "_blank",
"chrome,dialog=no,all" + gBrowserContentHandler.getFeatures(cmdLine),
gBrowserContentHandler.defaultArgs, NO_EXTERNAL_URIS);
}
},
helpInfo : "",
};
var components = [nsBrowserContentHandler, nsDefaultCommandLineHandler];
this.NSGetFactory = XPCOMUtils.generateNSGetFactory(components);

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,47 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsISupports.idl"
interface nsIDOMWindow;
/**
* nsIBrowserGlue is a dirty and rather fluid interface to host shared utility
* methods used by browser UI code, but which are not local to a browser window.
* The component implementing this interface is meant to be a singleton
* (service) and should progressively replace some of the shared "glue" code
* scattered in browser/base/content (e.g. bits of utilOverlay.js,
* contentAreaUtils.js, globalOverlay.js, browser.js), avoiding dynamic
* inclusion and initialization of a ton of JS code for *each* window.
* Dued to its nature and origin, this interface won't probably be the most
* elegant or stable in the mozilla codebase, but its aim is rather pragmatic:
* 1) reducing the performance overhead which affects browser window load;
* 2) allow global hooks (e.g. startup and shutdown observers) which survive
* browser windows to accomplish browser-related activities, such as shutdown
* sanitization (see bug #284086)
*
*/
[scriptable, uuid(781df699-17dc-4237-b3d7-876ddb7085e3)]
interface nsIBrowserGlue : nsISupports
{
/**
* Deletes privacy sensitive data according to user preferences
*
* @param aParentWindow an optionally null window which is the parent of the
* sanitization dialog
*
*/
void sanitize(in nsIDOMWindow aParentWindow);
/**
* Add Smart Bookmarks special queries to bookmarks menu and toolbar folder.
*/
void ensurePlacesDefaultQueriesInitialized();
/**
* Gets the most recent window that's a browser (but not a popup)
*/
nsIDOMWindow getMostRecentBrowserWindow();
};

View file

@ -0,0 +1,20 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsISupports.idl"
interface nsICommandLine;
[scriptable, uuid(8D3F5A9D-118D-4548-A137-CF7718679069)]
interface nsIBrowserHandler : nsISupports
{
attribute AUTF8String startPage;
attribute AUTF8String defaultArgs;
/**
* Extract the width and height specified on the command line, if present.
* @return A feature string with a prepended comma, e.g. ",width=500,height=400"
*/
AUTF8String getFeatures(in nsICommandLine aCmdLine);
};

View file

@ -0,0 +1,2 @@
component {6bcb9bde-9018-4443-a071-c32653469597} PlacesProtocolHandler.js
contract @mozilla.org/network/protocol;1?name=place {6bcb9bde-9018-4443-a071-c32653469597}

View file

@ -0,0 +1,49 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* vim: sw=2 ts=2 sts=2 et
* 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 Cc = Components.classes;
const Ci = Components.interfaces;
Components.utils.import("resource://gre/modules/NetUtil.jsm");
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
const SCHEME = "place";
const URL = "chrome://browser/content/places/content-ui/controller.xhtml";
function PlacesProtocolHandler() {}
PlacesProtocolHandler.prototype = {
scheme: SCHEME,
defaultPort: -1,
protocolFlags: Ci.nsIProtocolHandler.URI_DANGEROUS_TO_LOAD |
Ci.nsIProtocolHandler.URI_IS_LOCAL_RESOURCE |
Ci.nsIProtocolHandler.URI_NORELATIVE |
Ci.nsIProtocolHandler.URI_NOAUTH,
newURI: function PPH_newURI(aSpec, aOriginCharset, aBaseUri) {
let uri = Cc["@mozilla.org/network/simple-uri;1"].createInstance(Ci.nsIURI);
uri.spec = aSpec;
return uri;
},
newChannel: function PPH_newChannel(aUri) {
let chan = NetUtil.newChannel(URL);
chan.originalURI = aUri;
return chan;
},
allowPort: function PPH_allowPort(aPort, aScheme) {
return false;
},
QueryInterface: XPCOMUtils.generateQI([
Ci.nsIProtocolHandler
]),
classID: Components.ID("{6bcb9bde-9018-4443-a071-c32653469597}")
};
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([PlacesProtocolHandler]);

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,658 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* The panel is initialized based on data given in the js object passed
* as window.arguments[0]. The object must have the following fields set:
* @ action (String). Possible values:
* - "add" - for adding a new item.
* @ type (String). Possible values:
* - "bookmark"
* @ loadBookmarkInSidebar - optional, the default state for the
* "Load this bookmark in the sidebar" field.
* - "folder"
* @ URIList (Array of nsIURI objects) - optional, list of uris to
* be bookmarked under the new folder.
* - "livemark"
* @ uri (nsIURI object) - optional, the default uri for the new item.
* The property is not used for the "folder with items" type.
* @ title (String) - optional, the default title for the new item.
* @ description (String) - optional, the default description for the new
* item.
* @ defaultInsertionPoint (InsertionPoint JS object) - optional, the
* default insertion point for the new item.
* @ keyword (String) - optional, the default keyword for the new item.
* @ postData (String) - optional, POST data to accompany the keyword.
* @ charSet (String) - optional, character-set to accompany the keyword.
* Notes:
* 1) If |uri| is set for a bookmark/livemark item and |title| isn't,
* the dialog will query the history tables for the title associated
* with the given uri. If the dialog is set to adding a folder with
* bookmark items under it (see URIList), a default static title is
* used ("[Folder Name]").
* 2) The index field of the default insertion point is ignored if
* the folder picker is shown.
* - "edit" - for editing a bookmark item or a folder.
* @ type (String). Possible values:
* - "bookmark"
* @ itemId (Integer) - the id of the bookmark item.
* - "folder" (also applies to livemarks)
* @ itemId (Integer) - the id of the folder.
* @ hiddenRows (Strings array) - optional, list of rows to be hidden
* regardless of the item edited or added by the dialog.
* Possible values:
* - "title"
* - "location"
* - "description"
* - "keyword"
* - "tags"
* - "loadInSidebar"
* - "feedLocation"
* - "siteLocation"
* - "folderPicker" - hides both the tree and the menu.
* @ readOnly (Boolean) - optional, states if the panel should be read-only
*
* window.arguments[0].performed is set to true if any transaction has
* been performed by the dialog.
*/
Components.utils.import('resource://gre/modules/XPCOMUtils.jsm');
XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
"resource://gre/modules/PrivateBrowsingUtils.jsm");
const BOOKMARK_ITEM = 0;
const BOOKMARK_FOLDER = 1;
const LIVEMARK_CONTAINER = 2;
const ACTION_EDIT = 0;
const ACTION_ADD = 1;
var elementsHeight = new Map();
var BookmarkPropertiesPanel = {
/** UI Text Strings */
__strings: null,
get _strings() {
if (!this.__strings) {
this.__strings = document.getElementById("stringBundle");
}
return this.__strings;
},
_action: null,
_itemType: null,
_itemId: -1,
_uri: null,
_loadInSidebar: false,
_title: "",
_description: "",
_URIs: [],
_keyword: "",
_postData: null,
_charSet: "",
_feedURI: null,
_siteURI: null,
_defaultInsertionPoint: null,
_hiddenRows: [],
_batching: false,
_readOnly: false,
/**
* This method returns the correct label for the dialog's "accept"
* button based on the variant of the dialog.
*/
_getAcceptLabel: function BPP__getAcceptLabel() {
if (this._action == ACTION_ADD) {
if (this._URIs.length)
return this._strings.getString("dialogAcceptLabelAddMulti");
if (this._itemType == LIVEMARK_CONTAINER)
return this._strings.getString("dialogAcceptLabelAddLivemark");
if (this._dummyItem || this._loadInSidebar)
return this._strings.getString("dialogAcceptLabelAddItem");
return this._strings.getString("dialogAcceptLabelSaveItem");
}
return this._strings.getString("dialogAcceptLabelEdit");
},
/**
* This method returns the correct title for the current variant
* of this dialog.
*/
_getDialogTitle: function BPP__getDialogTitle() {
if (this._action == ACTION_ADD) {
if (this._itemType == BOOKMARK_ITEM)
return this._strings.getString("dialogTitleAddBookmark");
if (this._itemType == LIVEMARK_CONTAINER)
return this._strings.getString("dialogTitleAddLivemark");
// add folder
NS_ASSERT(this._itemType == BOOKMARK_FOLDER, "Unknown item type");
if (this._URIs.length)
return this._strings.getString("dialogTitleAddMulti");
return this._strings.getString("dialogTitleAddFolder");
}
if (this._action == ACTION_EDIT) {
return this._strings.getFormattedString("dialogTitleEdit", [this._title]);
}
return "";
},
/**
* Determines the initial data for the item edited or added by this dialog
*/
_determineItemInfo: function BPP__determineItemInfo() {
var dialogInfo = window.arguments[0];
this._action = dialogInfo.action == "add" ? ACTION_ADD : ACTION_EDIT;
this._hiddenRows = dialogInfo.hiddenRows ? dialogInfo.hiddenRows : [];
if (this._action == ACTION_ADD) {
NS_ASSERT("type" in dialogInfo, "missing type property for add action");
if ("title" in dialogInfo)
this._title = dialogInfo.title;
if ("defaultInsertionPoint" in dialogInfo) {
this._defaultInsertionPoint = dialogInfo.defaultInsertionPoint;
}
else
this._defaultInsertionPoint =
new InsertionPoint(PlacesUtils.bookmarksMenuFolderId,
PlacesUtils.bookmarks.DEFAULT_INDEX,
Ci.nsITreeView.DROP_ON);
switch (dialogInfo.type) {
case "bookmark":
this._itemType = BOOKMARK_ITEM;
if ("uri" in dialogInfo) {
NS_ASSERT(dialogInfo.uri instanceof Ci.nsIURI,
"uri property should be a uri object");
this._uri = dialogInfo.uri;
if (typeof(this._title) != "string") {
this._title = this._getURITitleFromHistory(this._uri) ||
this._uri.spec;
}
}
else {
this._uri = PlacesUtils._uri("about:blank");
this._title = this._strings.getString("newBookmarkDefault");
this._dummyItem = true;
}
if ("loadBookmarkInSidebar" in dialogInfo)
this._loadInSidebar = dialogInfo.loadBookmarkInSidebar;
if ("keyword" in dialogInfo) {
this._keyword = dialogInfo.keyword;
this._isAddKeywordDialog = true;
if ("postData" in dialogInfo)
this._postData = dialogInfo.postData;
if ("charSet" in dialogInfo)
this._charSet = dialogInfo.charSet;
}
break;
case "folder":
this._itemType = BOOKMARK_FOLDER;
if (!this._title) {
if ("URIList" in dialogInfo) {
this._title = this._strings.getString("bookmarkAllTabsDefault");
this._URIs = dialogInfo.URIList;
}
else
this._title = this._strings.getString("newFolderDefault");
this._dummyItem = true;
}
break;
case "livemark":
this._itemType = LIVEMARK_CONTAINER;
if ("feedURI" in dialogInfo)
this._feedURI = dialogInfo.feedURI;
if ("siteURI" in dialogInfo)
this._siteURI = dialogInfo.siteURI;
if (!this._title) {
if (this._feedURI) {
this._title = this._getURITitleFromHistory(this._feedURI) ||
this._feedURI.spec;
}
else
this._title = this._strings.getString("newLivemarkDefault");
}
}
if ("description" in dialogInfo)
this._description = dialogInfo.description;
}
else { // edit
NS_ASSERT("itemId" in dialogInfo);
this._itemId = dialogInfo.itemId;
this._title = PlacesUtils.bookmarks.getItemTitle(this._itemId);
this._readOnly = !!dialogInfo.readOnly;
switch (dialogInfo.type) {
case "bookmark":
this._itemType = BOOKMARK_ITEM;
this._uri = PlacesUtils.bookmarks.getBookmarkURI(this._itemId);
// keyword
this._keyword = PlacesUtils.bookmarks
.getKeywordForBookmark(this._itemId);
// Load In Sidebar
this._loadInSidebar = PlacesUtils.annotations
.itemHasAnnotation(this._itemId,
PlacesUIUtils.LOAD_IN_SIDEBAR_ANNO);
break;
case "folder":
this._itemType = BOOKMARK_FOLDER;
PlacesUtils.livemarks.getLivemark({ id: this._itemId })
.then(aLivemark => {
this._itemType = LIVEMARK_CONTAINER;
this._feedURI = aLivemark.feedURI;
this._siteURI = aLivemark.siteURI;
this._fillEditProperties();
let acceptButton = document.documentElement.getButton("accept");
acceptButton.disabled = !this._inputIsValid();
let newHeight = window.outerHeight +
this._element("descriptionField").boxObject.height;
window.resizeTo(window.outerWidth, newHeight);
}, () => undefined);
break;
}
// Description
if (PlacesUtils.annotations
.itemHasAnnotation(this._itemId, PlacesUIUtils.DESCRIPTION_ANNO)) {
this._description = PlacesUtils.annotations
.getItemAnnotation(this._itemId,
PlacesUIUtils.DESCRIPTION_ANNO);
}
}
},
/**
* This method returns the title string corresponding to a given URI.
* If none is available from the bookmark service (probably because
* the given URI doesn't appear in bookmarks or history), we synthesize
* a title from the first 100 characters of the URI.
*
* @param aURI
* nsIURI object for which we want the title
*
* @returns a title string
*/
_getURITitleFromHistory: function BPP__getURITitleFromHistory(aURI) {
NS_ASSERT(aURI instanceof Ci.nsIURI);
// get the title from History
return PlacesUtils.history.getPageTitle(aURI);
},
/**
* This method should be called by the onload of the Bookmark Properties
* dialog to initialize the state of the panel.
*/
onDialogLoad: function BPP_onDialogLoad() {
this._determineItemInfo();
document.title = this._getDialogTitle();
var acceptButton = document.documentElement.getButton("accept");
acceptButton.label = this._getAcceptLabel();
// Do not use sizeToContent, otherwise, due to bug 90276, the dialog will
// grow at every opening.
// Since elements can be uncollapsed asynchronously, we must observe their
// mutations and resize the dialog using a cached element size.
this._height = window.outerHeight;
this._mutationObserver = new MutationObserver(mutations => {
for (let mutation of mutations) {
let target = mutation.target;
let id = target.id;
if (!/^editBMPanel_.*(Row|Checkbox)$/.test(id))
continue;
let collapsed = target.getAttribute("collapsed") === "true";
let wasCollapsed = mutation.oldValue === "true";
if (collapsed == wasCollapsed)
continue;
if (collapsed) {
this._height -= elementsHeight.get(id);
elementsHeight.delete(id);
} else {
elementsHeight.set(id, target.boxObject.height);
this._height += elementsHeight.get(id);
}
window.resizeTo(window.outerWidth, this._height);
}
});
this._mutationObserver.observe(document,
{ subtree: true,
attributeOldValue: true,
attributeFilter: ["collapsed"] });
// Some controls are flexible and we want to update their cached size when
// the dialog is resized.
window.addEventListener("resize", this);
this._beginBatch();
switch (this._action) {
case ACTION_EDIT:
this._fillEditProperties();
acceptButton.disabled = this._readOnly;
break;
case ACTION_ADD:
this._fillAddProperties();
// if this is an uri related dialog disable accept button until
// the user fills an uri value.
if (this._itemType == BOOKMARK_ITEM)
acceptButton.disabled = !this._inputIsValid();
break;
}
if (!this._readOnly) {
// Listen on uri fields to enable accept button if input is valid
if (this._itemType == BOOKMARK_ITEM) {
this._element("locationField")
.addEventListener("input", this, false);
if (this._isAddKeywordDialog) {
this._element("keywordField")
.addEventListener("input", this, false);
}
}
else if (this._itemType == LIVEMARK_CONTAINER) {
this._element("feedLocationField")
.addEventListener("input", this, false);
this._element("siteLocationField")
.addEventListener("input", this, false);
}
}
},
// nsIDOMEventListener
handleEvent: function BPP_handleEvent(aEvent) {
var target = aEvent.target;
switch (aEvent.type) {
case "input":
if (target.id == "editBMPanel_locationField" ||
target.id == "editBMPanel_feedLocationField" ||
target.id == "editBMPanel_siteLocationField" ||
target.id == "editBMPanel_keywordField") {
// Check uri fields to enable accept button if input is valid
document.documentElement
.getButton("accept").disabled = !this._inputIsValid();
}
break;
case "resize":
for (let [id, oldHeight] of elementsHeight) {
let newHeight = document.getElementById(id).boxObject.height;
this._height += - oldHeight + newHeight;
elementsHeight.set(id, newHeight);
}
break;
}
},
_beginBatch: function BPP__beginBatch() {
if (this._batching)
return;
PlacesUtils.transactionManager.beginBatch(null);
this._batching = true;
},
_endBatch: function BPP__endBatch() {
if (!this._batching)
return;
PlacesUtils.transactionManager.endBatch(false);
this._batching = false;
},
_fillEditProperties: function BPP__fillEditProperties() {
gEditItemOverlay.initPanel(this._itemId,
{ hiddenRows: this._hiddenRows,
forceReadOnly: this._readOnly });
},
_fillAddProperties: function BPP__fillAddProperties() {
this._createNewItem();
// Edit the new item
gEditItemOverlay.initPanel(this._itemId,
{ hiddenRows: this._hiddenRows });
// Empty location field if the uri is about:blank, this way inserting a new
// url will be easier for the user, Accept button will be automatically
// disabled by the input listener until the user fills the field.
var locationField = this._element("locationField");
if (locationField.value == "about:blank")
locationField.value = "";
},
// nsISupports
QueryInterface: function BPP_QueryInterface(aIID) {
if (aIID.equals(Ci.nsIDOMEventListener) ||
aIID.equals(Ci.nsISupports))
return this;
throw Cr.NS_NOINTERFACE;
},
_element: function BPP__element(aID) {
return document.getElementById("editBMPanel_" + aID);
},
onDialogUnload: function BPP_onDialogUnload() {
// gEditItemOverlay does not exist anymore here, so don't rely on it.
this._mutationObserver.disconnect();
delete this._mutationObserver;
window.removeEventListener("resize", this);
// Calling removeEventListener with arguments which do not identify any
// currently registered EventListener on the EventTarget has no effect.
this._element("locationField")
.removeEventListener("input", this, false);
this._element("feedLocationField")
.removeEventListener("input", this, false);
this._element("siteLocationField")
.removeEventListener("input", this, false);
},
onDialogAccept: function BPP_onDialogAccept() {
// We must blur current focused element to save its changes correctly
document.commandDispatcher.focusedElement.blur();
// The order here is important! We have to uninit the panel first, otherwise
// late changes could force it to commit more transactions.
gEditItemOverlay.uninitPanel(true);
this._endBatch();
window.arguments[0].performed = true;
},
onDialogCancel: function BPP_onDialogCancel() {
// The order here is important! We have to uninit the panel first, otherwise
// changes done as part of Undo may change the panel contents and by
// that force it to commit more transactions.
gEditItemOverlay.uninitPanel(true);
this._endBatch();
PlacesUtils.transactionManager.undoTransaction();
window.arguments[0].performed = false;
},
/**
* This method checks to see if the input fields are in a valid state.
*
* @returns true if the input is valid, false otherwise
*/
_inputIsValid: function BPP__inputIsValid() {
if (this._itemType == BOOKMARK_ITEM &&
!this._containsValidURI("locationField"))
return false;
if (this._isAddKeywordDialog && !this._element("keywordField").value.length)
return false;
return true;
},
/**
* Determines whether the XUL textbox with the given ID contains a
* string that can be converted into an nsIURI.
*
* @param aTextboxID
* the ID of the textbox element whose contents we'll test
*
* @returns true if the textbox contains a valid URI string, false otherwise
*/
_containsValidURI: function BPP__containsValidURI(aTextboxID) {
try {
var value = this._element(aTextboxID).value;
if (value) {
PlacesUIUtils.createFixedURI(value);
return true;
}
} catch (e) { }
return false;
},
/**
* [New Item Mode] Get the insertion point details for the new item, given
* dialog state and opening arguments.
*
* The container-identifier and insertion-index are returned separately in
* the form of [containerIdentifier, insertionIndex]
*/
_getInsertionPointDetails: function BPP__getInsertionPointDetails() {
var containerId = this._defaultInsertionPoint.itemId;
var indexInContainer = this._defaultInsertionPoint.index;
return [containerId, indexInContainer];
},
/**
* Returns a transaction for creating a new bookmark item representing the
* various fields and opening arguments of the dialog.
*/
_getCreateNewBookmarkTransaction:
function BPP__getCreateNewBookmarkTransaction(aContainer, aIndex) {
var annotations = [];
var childTransactions = [];
if (this._description) {
let annoObj = { name : PlacesUIUtils.DESCRIPTION_ANNO,
type : Ci.nsIAnnotationService.TYPE_STRING,
flags : 0,
value : this._description,
expires: Ci.nsIAnnotationService.EXPIRE_NEVER };
let editItemTxn = new PlacesSetItemAnnotationTransaction(-1, annoObj);
childTransactions.push(editItemTxn);
}
if (this._loadInSidebar) {
let annoObj = { name : PlacesUIUtils.LOAD_IN_SIDEBAR_ANNO,
value : true };
let setLoadTxn = new PlacesSetItemAnnotationTransaction(-1, annoObj);
childTransactions.push(setLoadTxn);
}
if (this._postData) {
let postDataTxn = new PlacesEditBookmarkPostDataTransaction(-1, this._postData);
childTransactions.push(postDataTxn);
}
//XXX TODO: this should be in a transaction!
if (this._charSet && !PrivateBrowsingUtils.isWindowPrivate(window))
PlacesUtils.setCharsetForURI(this._uri, this._charSet);
let createTxn = new PlacesCreateBookmarkTransaction(this._uri,
aContainer,
aIndex,
this._title,
this._keyword,
annotations,
childTransactions);
return new PlacesAggregatedTransaction(this._getDialogTitle(),
[createTxn]);
},
/**
* Returns a childItems-transactions array representing the URIList with
* which the dialog has been opened.
*/
_getTransactionsForURIList: function BPP__getTransactionsForURIList() {
var transactions = [];
for (var i = 0; i < this._URIs.length; ++i) {
var uri = this._URIs[i];
var title = this._getURITitleFromHistory(uri);
var createTxn = new PlacesCreateBookmarkTransaction(uri, -1,
PlacesUtils.bookmarks.DEFAULT_INDEX,
title);
transactions.push(createTxn);
}
return transactions;
},
/**
* Returns a transaction for creating a new folder item representing the
* various fields and opening arguments of the dialog.
*/
_getCreateNewFolderTransaction:
function BPP__getCreateNewFolderTransaction(aContainer, aIndex) {
var annotations = [];
var childItemsTransactions;
if (this._URIs.length)
childItemsTransactions = this._getTransactionsForURIList();
if (this._description)
annotations.push(this._getDescriptionAnnotation(this._description));
return new PlacesCreateFolderTransaction(this._title, aContainer,
aIndex, annotations,
childItemsTransactions);
},
/**
* Returns a transaction for creating a new live-bookmark item representing
* the various fields and opening arguments of the dialog.
*/
_getCreateNewLivemarkTransaction:
function BPP__getCreateNewLivemarkTransaction(aContainer, aIndex) {
return new PlacesCreateLivemarkTransaction(this._feedURI, this._siteURI,
this._title,
aContainer, aIndex);
},
/**
* Dialog-accept code-path for creating a new item (any type)
*/
_createNewItem: function BPP__getCreateItemTransaction() {
var [container, index] = this._getInsertionPointDetails();
var txn;
switch (this._itemType) {
case BOOKMARK_FOLDER:
txn = this._getCreateNewFolderTransaction(container, index);
break;
case LIVEMARK_CONTAINER:
txn = this._getCreateNewLivemarkTransaction(container, index);
break;
default: // BOOKMARK_ITEM
txn = this._getCreateNewBookmarkTransaction(container, index);
}
PlacesUtils.transactionManager.doTransaction(txn);
this._itemId = PlacesUtils.bookmarks.getIdForItemAt(container, index);
}
};

View file

@ -0,0 +1,43 @@
<?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/"?>
<?xml-stylesheet href="chrome://browser/skin/places/editBookmarkOverlay.css"?>
<?xml-stylesheet href="chrome://browser/skin/places/places.css"?>
<?xml-stylesheet href="chrome://browser/content/places/places.css"?>
<?xul-overlay href="chrome://browser/content/places/placesOverlay.xul"?>
<?xul-overlay href="chrome://browser/content/places/editBookmarkOverlay.xul"?>
<!DOCTYPE dialog [
<!ENTITY % editBookmarkOverlayDTD SYSTEM "chrome://browser/locale/places/editBookmarkOverlay.dtd">
%editBookmarkOverlayDTD;
]>
<dialog id="bookmarkproperties"
buttons="accept, cancel"
buttoniconaccept="save"
ondialogaccept="BookmarkPropertiesPanel.onDialogAccept();"
ondialogcancel="BookmarkPropertiesPanel.onDialogCancel();"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
onload="BookmarkPropertiesPanel.onDialogLoad();"
onunload="BookmarkPropertiesPanel.onDialogUnload();"
style="min-width: 30em;"
persist="screenX screenY width">
<stringbundleset id="stringbundleset">
<stringbundle id="stringBundle"
src="chrome://browser/locale/places/bookmarkProperties.properties"/>
</stringbundleset>
<script type="application/javascript"
src="chrome://browser/content/places/editBookmarkOverlay.js"/>
<script type="application/javascript"
src="chrome://browser/content/places/bookmarkProperties.js"/>
<vbox id="editBookmarkPanelContent"/>
</dialog>

View file

@ -0,0 +1,25 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
function init() {
document.getElementById("bookmarks-view").place =
"place:queryType=1&folder=" + window.top.PlacesUIUtils.allBookmarksFolderId;
}
function searchBookmarks(aSearchString) {
var tree = document.getElementById('bookmarks-view');
if (!aSearchString)
tree.place = tree.place;
else
tree.applyFilter(aSearchString,
[PlacesUtils.bookmarksMenuFolderId,
PlacesUtils.unfiledBookmarksFolderId,
PlacesUtils.toolbarFolderId]);
}
window.addEventListener("SidebarFocused",
function()
document.getElementById("search-box").focus(),
false);

View file

@ -0,0 +1,55 @@
<?xml version="1.0"?> <!-- -*- Mode: SGML; indent-tabs-mode: nil; -*- -->
<!-- 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/content/places/places.css"?>
<?xml-stylesheet href="chrome://browser/skin/places/places.css"?>
<?xul-overlay href="chrome://global/content/editMenuOverlay.xul"?>
<?xul-overlay href="chrome://browser/content/places/placesOverlay.xul"?>
<!DOCTYPE page SYSTEM "chrome://browser/locale/places/places.dtd">
<page id="bookmarksPanel"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
onload="init();"
onunload="SidebarUtils.setMouseoverURL('');">
<script type="application/javascript"
src="chrome://browser/content/bookmarks/sidebarUtils.js"/>
<script type="application/javascript"
src="chrome://browser/content/bookmarks/bookmarksPanel.js"/>
<commandset id="placesCommands"/>
<commandset id="editMenuCommands"/>
<keyset id="placesCommandKeys"/>
<menupopup id="placesContext"/>
<!-- Bookmarks and history tooltip -->
<tooltip id="bhTooltip"/>
<hbox id="sidebar-search-container" align="center">
<label id="sidebar-search-label"
value="&search.label;" accesskey="&search.accesskey;" control="search-box"/>
<textbox id="search-box" flex="1" type="search" class="compact"
aria-controls="bookmarks-view"
oncommand="searchBookmarks(this.value);"/>
</hbox>
<tree id="bookmarks-view" class="sidebar-placesTree" type="places"
flex="1"
hidecolumnpicker="true"
context="placesContext"
onkeypress="SidebarUtils.handleTreeKeyPress(event);"
onclick="SidebarUtils.handleTreeClick(this, event, true);"
onmousemove="SidebarUtils.handleTreeMouseMove(event);"
onmouseout="SidebarUtils.setMouseoverURL('');">
<treecols>
<treecol id="title" flex="1" primary="true" hideheader="true"/>
</treecols>
<treechildren id="bookmarks-view-children" view="bookmarks-view"
class="sidebar-placesTreechildren" flex="1" tooltip="bhTooltip"/>
</tree>
</page>

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,47 @@
<!-- 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/. -->
<?xul-overlay href="chrome://browser/content/downloads/allDownloadsViewOverlay.xul"?>
<!DOCTYPE overlay [
<!ENTITY % downloadsDTD SYSTEM "chrome://browser/locale/downloads/downloads.dtd">
%downloadsDTD;
]>
<overlay id="downloadsViewOverlay"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script type="application/javascript"><![CDATA[
const DOWNLOADS_QUERY = "place:transition=" +
Components.interfaces.nsINavHistoryService.TRANSITION_DOWNLOAD +
"&sort=" +
Components.interfaces.nsINavHistoryQueryOptions.SORT_BY_DATE_DESCENDING;
ContentArea.setContentViewForQueryString(DOWNLOADS_QUERY,
function() new DownloadsPlacesView(document.getElementById("downloadsRichListBox"), false),
{ showDetailsPane: false,
toolbarSet: "back-button, forward-button, organizeButton, clearDownloadsButton, libraryToolbarSpacer, searchFilter" });
]]></script>
<window id="places">
<commandset id="downloadCommands"/>
<menupopup id="downloadsContextMenu"/>
</window>
<deck id="placesViewsDeck">
<richlistbox id="downloadsRichListBox"/>
</deck>
<toolbar id="placesToolbar">
<toolbarbutton id="clearDownloadsButton"
#ifdef XP_MACOSX
class="tabbable"
#endif
insertbefore="libraryToolbarSpacer"
label="&clearDownloadsButton.label;"
command="downloadsCmd_clearDownloads"
tooltiptext="&clearDownloadsButton.tooltip;"/>
</toolbar>
</overlay>

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,228 @@
<!-- 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 overlay [
<!ENTITY % editBookmarkOverlayDTD SYSTEM "chrome://browser/locale/places/editBookmarkOverlay.dtd">
%editBookmarkOverlayDTD;
]>
<?xml-stylesheet href="chrome://browser/skin/places/editBookmarkOverlay.css"?>
<?xml-stylesheet href="chrome://browser/skin/places/places.css"?>
<overlay id="editBookmarkOverlay"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<vbox id="editBookmarkPanelContent" flex="1">
<broadcaster id="paneElementsBroadcaster"/>
<hbox id="editBMPanel_selectionCount" hidden="true" pack="center">
<label id="editBMPanel_itemsCountText"/>
</hbox>
<grid id="editBookmarkPanelGrid" flex="1">
<columns id="editBMPanel_columns">
<column id="editBMPanel_labelColumn" />
<column flex="1" id="editBMPanel_editColumn" />
</columns>
<rows id="editBMPanel_rows">
<row id="editBMPanel_nameRow"
align="center"
collapsed="true">
<label value="&editBookmarkOverlay.name.label;"
class="editBMPanel_rowLabel"
accesskey="&editBookmarkOverlay.name.accesskey;"
control="editBMPanel_namePicker"
observes="paneElementsBroadcaster"/>
<textbox id="editBMPanel_namePicker"
observes="paneElementsBroadcaster"/>
</row>
<row id="editBMPanel_locationRow"
align="center"
collapsed="true">
<label value="&editBookmarkOverlay.location.label;"
class="editBMPanel_rowLabel"
accesskey="&editBookmarkOverlay.location.accesskey;"
control="editBMPanel_locationField"
observes="paneElementsBroadcaster"/>
<textbox id="editBMPanel_locationField"
class="uri-element"
observes="paneElementsBroadcaster"/>
</row>
<row id="editBMPanel_feedLocationRow"
align="center"
collapsed="true">
<label value="&editBookmarkOverlay.feedLocation.label;"
class="editBMPanel_rowLabel"
accesskey="&editBookmarkOverlay.feedLocation.accesskey;"
control="editBMPanel_feedLocationField"
observes="paneElementsBroadcaster"/>
<textbox id="editBMPanel_feedLocationField"
class="uri-element"
observes="paneElementsBroadcaster"/>
</row>
<row id="editBMPanel_siteLocationRow"
align="center"
collapsed="true">
<label value="&editBookmarkOverlay.siteLocation.label;"
class="editBMPanel_rowLabel"
accesskey="&editBookmarkOverlay.siteLocation.accesskey;"
control="editBMPanel_siteLocationField"
observes="paneElementsBroadcaster"/>
<textbox id="editBMPanel_siteLocationField"
class="uri-element"
observes="paneElementsBroadcaster"/>
</row>
<row id="editBMPanel_folderRow"
align="center"
collapsed="true">
<label value="&editBookmarkOverlay.folder.label;"
class="editBMPanel_rowLabel"
control="editBMPanel_folderMenuList"
observes="paneElementsBroadcaster"/>
<hbox flex="1" align="center">
<menulist id="editBMPanel_folderMenuList"
class="folder-icon"
flex="1"
oncommand="gEditItemOverlay.onFolderMenuListCommand(event);"
observes="paneElementsBroadcaster">
<menupopup>
<!-- Static item for special folders -->
<menuitem id="editBMPanel_toolbarFolderItem"
class="menuitem-iconic folder-icon"/>
<menuitem id="editBMPanel_bmRootItem"
class="menuitem-iconic folder-icon"/>
<menuitem id="editBMPanel_unfiledRootItem"
class="menuitem-iconic folder-icon"/>
<menuseparator id="editBMPanel_chooseFolderSeparator"/>
<menuitem id="editBMPanel_chooseFolderMenuItem"
label="&editBookmarkOverlay.choose.label;"
class="menuitem-iconic folder-icon"/>
<menuseparator id="editBMPanel_foldersSeparator" hidden="true"/>
</menupopup>
</menulist>
<button id="editBMPanel_foldersExpander"
class="expander-down"
tooltiptext="&editBookmarkOverlay.foldersExpanderDown.tooltip;"
tooltiptextdown="&editBookmarkOverlay.foldersExpanderDown.tooltip;"
tooltiptextup="&editBookmarkOverlay.expanderUp.tooltip;"
oncommand="gEditItemOverlay.toggleFolderTreeVisibility();"
observes="paneElementsBroadcaster"/>
</hbox>
</row>
<row id="editBMPanel_folderTreeRow"
collapsed="true"
flex="1">
<spacer/>
<vbox flex="1">
<tree id="editBMPanel_folderTree"
flex="1"
class="placesTree"
type="places"
height="150"
minheight="150"
editable="true"
onselect="gEditItemOverlay.onFolderTreeSelect();"
hidecolumnpicker="true"
observes="paneElementsBroadcaster">
<treecols>
<treecol anonid="title" flex="1" primary="true" hideheader="true"/>
</treecols>
<treechildren flex="1"/>
</tree>
<hbox id="editBMPanel_newFolderBox">
<button label="&editBookmarkOverlay.newFolderButton.label;"
id="editBMPanel_newFolderButton"
accesskey="&editBookmarkOverlay.newFolderButton.accesskey;"
oncommand="gEditItemOverlay.newFolder();"/>
</hbox>
</vbox>
</row>
<row id="editBMPanel_tagsRow"
align="center"
collapsed="true">
<label value="&editBookmarkOverlay.tags.label;"
class="editBMPanel_rowLabel"
accesskey="&editBookmarkOverlay.tags.accesskey;"
control="editBMPanel_tagsField"
observes="paneElementsBroadcaster"/>
<hbox flex="1" align="center">
<textbox id="editBMPanel_tagsField"
type="autocomplete"
class="padded"
flex="1"
autocompletesearch="places-tag-autocomplete"
completedefaultindex="true"
tabscrolling="true"
showcommentcolumn="true"
observes="paneElementsBroadcaster"
placeholder="&editBookmarkOverlay.tagsEmptyDesc.label;"/>
<button id="editBMPanel_tagsSelectorExpander"
class="expander-down"
tooltiptext="&editBookmarkOverlay.tagsExpanderDown.tooltip;"
tooltiptextdown="&editBookmarkOverlay.tagsExpanderDown.tooltip;"
tooltiptextup="&editBookmarkOverlay.expanderUp.tooltip;"
oncommand="gEditItemOverlay.toggleTagsSelector();"
observes="paneElementsBroadcaster"/>
</hbox>
</row>
<row id="editBMPanel_tagsSelectorRow"
align="center"
collapsed="true">
<spacer/>
<listbox id="editBMPanel_tagsSelector"
height="150"
observes="paneElementsBroadcaster"/>
</row>
<row id="editBMPanel_keywordRow"
align="center"
collapsed="true">
<observes element="additionalInfoBroadcaster" attribute="hidden"/>
<label value="&editBookmarkOverlay.keyword.label;"
class="editBMPanel_rowLabel"
accesskey="&editBookmarkOverlay.keyword.accesskey;"
control="editBMPanel_keywordField"
observes="paneElementsBroadcaster"/>
<textbox id="editBMPanel_keywordField"
observes="paneElementsBroadcaster"/>
</row>
<row id="editBMPanel_descriptionRow"
collapsed="true">
<observes element="additionalInfoBroadcaster" attribute="hidden"/>
<label value="&editBookmarkOverlay.description.label;"
class="editBMPanel_rowLabel"
accesskey="&editBookmarkOverlay.description.accesskey;"
control="editBMPanel_descriptionField"
observes="paneElementsBroadcaster"/>
<textbox id="editBMPanel_descriptionField"
multiline="true"
observes="paneElementsBroadcaster"/>
</row>
</rows>
</grid>
<checkbox id="editBMPanel_loadInSidebarCheckbox"
collapsed="true"
label="&editBookmarkOverlay.loadInSidebar.label;"
accesskey="&editBookmarkOverlay.loadInSidebar.accesskey;"
oncommand="gEditItemOverlay.onLoadInSidebarCheckboxCommand();"
observes="paneElementsBroadcaster">
<observes element="additionalInfoBroadcaster" attribute="hidden"/>
</checkbox>
<!-- If the ids are changing or additional fields are being added, be sure
to sync the values in places.js -->
<broadcaster id="additionalInfoBroadcaster"/>
</vbox>
</overlay>

View file

@ -0,0 +1,91 @@
/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* 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 gHistoryTree;
var gSearchBox;
var gHistoryGrouping = "";
var gSearching = false;
function HistorySidebarInit()
{
gHistoryTree = document.getElementById("historyTree");
gSearchBox = document.getElementById("search-box");
gHistoryGrouping = document.getElementById("viewButton").
getAttribute("selectedsort");
if (gHistoryGrouping == "site")
document.getElementById("bysite").setAttribute("checked", "true");
else if (gHistoryGrouping == "visited")
document.getElementById("byvisited").setAttribute("checked", "true");
else if (gHistoryGrouping == "lastvisited")
document.getElementById("bylastvisited").setAttribute("checked", "true");
else if (gHistoryGrouping == "dayandsite")
document.getElementById("bydayandsite").setAttribute("checked", "true");
else
document.getElementById("byday").setAttribute("checked", "true");
searchHistory("");
}
function GroupBy(groupingType)
{
gHistoryGrouping = groupingType;
searchHistory(gSearchBox.value);
}
function searchHistory(aInput)
{
var query = PlacesUtils.history.getNewQuery();
var options = PlacesUtils.history.getNewQueryOptions();
const NHQO = Ci.nsINavHistoryQueryOptions;
var sortingMode;
var resultType;
switch (gHistoryGrouping) {
case "visited":
resultType = NHQO.RESULTS_AS_URI;
sortingMode = NHQO.SORT_BY_VISITCOUNT_DESCENDING;
break;
case "lastvisited":
resultType = NHQO.RESULTS_AS_URI;
sortingMode = NHQO.SORT_BY_DATE_DESCENDING;
break;
case "dayandsite":
resultType = NHQO.RESULTS_AS_DATE_SITE_QUERY;
break;
case "site":
resultType = NHQO.RESULTS_AS_SITE_QUERY;
sortingMode = NHQO.SORT_BY_TITLE_ASCENDING;
break;
case "day":
default:
resultType = NHQO.RESULTS_AS_DATE_QUERY;
break;
}
if (aInput) {
query.searchTerms = aInput;
if (gHistoryGrouping != "visited" && gHistoryGrouping != "lastvisited") {
sortingMode = NHQO.SORT_BY_FRECENCY_DESCENDING;
resultType = NHQO.RESULTS_AS_URI;
}
}
options.sortingMode = sortingMode;
options.resultType = resultType;
options.includeHidden = !!aInput;
// call load() on the tree manually
// instead of setting the place attribute in history-panel.xul
// otherwise, we will end up calling load() twice
gHistoryTree.load([query], options);
}
window.addEventListener("SidebarFocused",
function()
gSearchBox.focus(),
false);

View file

@ -0,0 +1,95 @@
<?xml version="1.0"?> <!-- -*- Mode: xml; indent-tabs-mode: nil; -*- -->
# 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/content/places/places.css"?>
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<?xml-stylesheet href="chrome://browser/skin/places/places.css"?>
<?xul-overlay href="chrome://global/content/editMenuOverlay.xul"?>
<?xul-overlay href="chrome://browser/content/places/placesOverlay.xul"?>
<!DOCTYPE page [
<!ENTITY % placesDTD SYSTEM "chrome://browser/locale/places/places.dtd">
%placesDTD;
]>
<!-- we need to keep id="history-panel" for upgrade and switching
between versions of the browser -->
<page id="history-panel" orient="vertical"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
onload="HistorySidebarInit();"
onunload="SidebarUtils.setMouseoverURL('');">
<script type="application/javascript"
src="chrome://browser/content/bookmarks/sidebarUtils.js"/>
<script type="application/javascript"
src="chrome://browser/content/places/history-panel.js"/>
<commandset id="editMenuCommands"/>
<commandset id="placesCommands"/>
<keyset id="editMenuKeys">
#ifdef XP_MACOSX
<key id="key_delete2" keycode="VK_BACK" command="cmd_delete"/>
#endif
</keyset>
<!-- required to overlay the context menu -->
<menupopup id="placesContext"/>
<!-- Bookmarks and history tooltip -->
<tooltip id="bhTooltip"/>
<hbox id="sidebar-search-container" align="center">
<label id="sidebar-search-label"
value="&find.label;" accesskey="&find.accesskey;"
control="search-box"/>
<textbox id="search-box" flex="1" type="search" class="compact"
aria-controls="historyTree"
oncommand="searchHistory(this.value);"/>
<button id="viewButton" style="min-width:0px !important;" type="menu"
label="&view.label;" accesskey="&view.accesskey;" selectedsort="day"
persist="selectedsort">
<menupopup>
<menuitem id="bydayandsite" label="&byDayAndSite.label;"
accesskey="&byDayAndSite.accesskey;" type="radio"
oncommand="this.parentNode.parentNode.setAttribute('selectedsort', 'dayandsite'); GroupBy('dayandsite');"/>
<menuitem id="bysite" label="&bySite.label;"
accesskey="&bySite.accesskey;" type="radio"
oncommand="this.parentNode.parentNode.setAttribute('selectedsort', 'site'); GroupBy('site');"/>
<menuitem id="byday" label="&byDate.label;"
accesskey="&byDate.accesskey;"
type="radio"
oncommand="this.parentNode.parentNode.setAttribute('selectedsort', 'day'); GroupBy('day');"/>
<menuitem id="byvisited" label="&byMostVisited.label;"
accesskey="&byMostVisited.accesskey;"
type="radio"
oncommand="this.parentNode.parentNode.setAttribute('selectedsort', 'visited'); GroupBy('visited');"/>
<menuitem id="bylastvisited" label="&byLastVisited.label;"
accesskey="&byLastVisited.accesskey;"
type="radio"
oncommand="this.parentNode.parentNode.setAttribute('selectedsort', 'lastvisited'); GroupBy('lastvisited');"/>
</menupopup>
</button>
</hbox>
<tree id="historyTree"
class="sidebar-placesTree"
flex="1"
type="places"
context="placesContext"
hidecolumnpicker="true"
onkeypress="SidebarUtils.handleTreeKeyPress(event);"
onclick="SidebarUtils.handleTreeClick(this, event, true);"
onmousemove="SidebarUtils.handleTreeMouseMove(event);"
onmouseout="SidebarUtils.setMouseoverURL('');">
<treecols>
<treecol id="title" flex="1" primary="true" hideheader="true"/>
</treecols>
<treechildren class="sidebar-placesTreechildren" flex="1" tooltip="bhTooltip"/>
</tree>
</page>

View file

@ -0,0 +1,488 @@
<?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="placesMenuBindings"
xmlns="http://www.mozilla.org/xbl"
xmlns:xbl="http://www.mozilla.org/xbl"
xmlns:html="http://www.w3.org/1999/xhtml"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<binding id="places-popup-base"
extends="chrome://global/content/bindings/popup.xml#popup">
<content>
<xul:hbox flex="1">
<xul:vbox class="menupopup-drop-indicator-bar" hidden="true">
<xul:image class="menupopup-drop-indicator" mousethrough="always"/>
</xul:vbox>
<xul:arrowscrollbox class="popup-internal-box" flex="1" orient="vertical"
smoothscroll="false">
<children/>
</xul:arrowscrollbox>
</xul:hbox>
</content>
<implementation>
<field name="_indicatorBar">
document.getAnonymousElementByAttribute(this, "class",
"menupopup-drop-indicator-bar");
</field>
<field name="_scrollBox">
document.getAnonymousElementByAttribute(this, "class",
"popup-internal-box");
</field>
<!-- This is the view that manage the popup -->
<field name="_rootView">PlacesUIUtils.getViewForNode(this);</field>
<!-- Check if we should hide the drop indicator for the target -->
<method name="_hideDropIndicator">
<parameter name="aEvent"/>
<body><![CDATA[
let target = aEvent.target;
// Don't draw the drop indicator outside of markers.
// The markers are hidden, since otherwise sometimes popups acquire
// scrollboxes on OS X, so we can't use them directly.
let firstChildTop = this._startMarker.nextSibling.boxObject.y;
let lastChildBottom = this._endMarker.previousSibling.boxObject.y +
this._endMarker.previousSibling.boxObject.height;
let betweenMarkers = target.boxObject.y >= firstChildTop ||
target.boxObject.y <= lastChildBottom;
// Hide the dropmarker if current node is not a Places node.
return !(target && target._placesNode && betweenMarkers);
]]></body>
</method>
<!-- This function returns information about where to drop when
dragging over this popup insertion point -->
<method name="_getDropPoint">
<parameter name="aEvent"/>
<body><![CDATA[
// Can't drop if the menu isn't a folder
let resultNode = this._placesNode;
if (!PlacesUtils.nodeIsFolder(resultNode) ||
PlacesControllerDragHelper.disallowInsertion(resultNode)) {
return null;
}
var dropPoint = { ip: null, folderElt: null };
// The element we are dragging over
let elt = aEvent.target;
if (elt.localName == "menupopup")
elt = elt.parentNode;
// Calculate positions taking care of arrowscrollbox
let eventY = aEvent.layerY;
let scrollbox = this._scrollBox;
let scrollboxOffset = scrollbox.scrollBoxObject.y -
(scrollbox.boxObject.y - this.boxObject.y);
let eltY = elt.boxObject.y - scrollboxOffset;
let eltHeight = elt.boxObject.height;
if (!elt._placesNode) {
// If we are dragging over a non places node drop at the end.
dropPoint.ip = new InsertionPoint(
PlacesUtils.getConcreteItemId(resultNode),
-1,
Ci.nsITreeView.DROP_ON);
// We can set folderElt if we are dropping over a static menu that
// has an internal placespopup.
let isMenu = elt.localName == "menu" ||
(elt.localName == "toolbarbutton" &&
elt.getAttribute("type") == "menu");
if (isMenu && elt.lastChild &&
elt.lastChild.hasAttribute("placespopup"))
dropPoint.folderElt = elt;
return dropPoint;
}
if ((PlacesUtils.nodeIsFolder(elt._placesNode) &&
!PlacesUIUtils.isContentsReadOnly(elt._placesNode)) ||
PlacesUtils.nodeIsTagQuery(elt._placesNode)) {
// This is a folder or a tag container.
if (eventY - eltY < eltHeight * 0.20) {
// If mouse is in the top part of the element, drop above folder.
dropPoint.ip = new InsertionPoint(
PlacesUtils.getConcreteItemId(resultNode),
-1,
Ci.nsITreeView.DROP_BEFORE,
PlacesUtils.nodeIsTagQuery(elt._placesNode),
elt._placesNode.itemId);
return dropPoint;
}
else if (eventY - eltY < eltHeight * 0.80) {
// If mouse is in the middle of the element, drop inside folder.
dropPoint.ip = new InsertionPoint(
PlacesUtils.getConcreteItemId(elt._placesNode),
-1,
Ci.nsITreeView.DROP_ON,
PlacesUtils.nodeIsTagQuery(elt._placesNode));
dropPoint.folderElt = elt;
return dropPoint;
}
}
else if (eventY - eltY <= eltHeight / 2) {
// This is a non-folder node or a readonly folder.
// If the mouse is above the middle, drop above this item.
dropPoint.ip = new InsertionPoint(
PlacesUtils.getConcreteItemId(resultNode),
-1,
Ci.nsITreeView.DROP_BEFORE,
PlacesUtils.nodeIsTagQuery(elt._placesNode),
elt._placesNode.itemId);
return dropPoint;
}
// Drop below the item.
dropPoint.ip = new InsertionPoint(
PlacesUtils.getConcreteItemId(resultNode),
-1,
Ci.nsITreeView.DROP_AFTER,
PlacesUtils.nodeIsTagQuery(elt._placesNode),
elt._placesNode.itemId);
return dropPoint;
]]></body>
</method>
<!-- Sub-menus should be opened when the mouse drags over them, and closed
when the mouse drags off. The overFolder object manages opening and
closing of folders when the mouse hovers. -->
<field name="_overFolder"><![CDATA[({
_self: this,
_folder: {elt: null,
openTimer: null,
hoverTime: 350,
closeTimer: null},
_closeMenuTimer: null,
get elt() {
return this._folder.elt;
},
set elt(val) {
return this._folder.elt = val;
},
get openTimer() {
return this._folder.openTimer;
},
set openTimer(val) {
return this._folder.openTimer = val;
},
get hoverTime() {
return this._folder.hoverTime;
},
set hoverTime(val) {
return this._folder.hoverTime = val;
},
get closeTimer() {
return this._folder.closeTimer;
},
set closeTimer(val) {
return this._folder.closeTimer = val;
},
get closeMenuTimer() {
return this._closeMenuTimer;
},
set closeMenuTimer(val) {
return this._closeMenuTimer = val;
},
setTimer: function OF__setTimer(aTime) {
var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
timer.initWithCallback(this, aTime, timer.TYPE_ONE_SHOT);
return timer;
},
notify: function OF__notify(aTimer) {
// Function to process all timer notifications.
if (aTimer == this._folder.openTimer) {
// Timer to open a submenu that's being dragged over.
this._folder.elt.lastChild.setAttribute("autoopened", "true");
this._folder.elt.lastChild.showPopup(this._folder.elt);
this._folder.openTimer = null;
}
else if (aTimer == this._folder.closeTimer) {
// Timer to close a submenu that's been dragged off of.
// Only close the submenu if the mouse isn't being dragged over any
// of its child menus.
var draggingOverChild = PlacesControllerDragHelper
.draggingOverChildNode(this._folder.elt);
if (draggingOverChild)
this._folder.elt = null;
this.clear();
// Close any parent folders which aren't being dragged over.
// (This is necessary because of the above code that keeps a folder
// open while its children are being dragged over.)
if (!draggingOverChild)
this.closeParentMenus();
}
else if (aTimer == this.closeMenuTimer) {
// Timer to close this menu after the drag exit.
var popup = this._self;
// if we are no more dragging we can leave the menu open to allow
// for better D&D bookmark organization
if (PlacesControllerDragHelper.getSession() &&
!PlacesControllerDragHelper.draggingOverChildNode(popup.parentNode)) {
popup.hidePopup();
// Close any parent menus that aren't being dragged over;
// otherwise they'll stay open because they couldn't close
// while this menu was being dragged over.
this.closeParentMenus();
}
this._closeMenuTimer = null;
}
},
// Helper function to close all parent menus of this menu,
// as long as none of the parent's children are currently being
// dragged over.
closeParentMenus: function OF__closeParentMenus() {
var popup = this._self;
var parent = popup.parentNode;
while (parent) {
if (parent.localName == "menupopup" && parent._placesNode) {
if (PlacesControllerDragHelper.draggingOverChildNode(parent.parentNode))
break;
parent.hidePopup();
}
parent = parent.parentNode;
}
},
// The mouse is no longer dragging over the stored menubutton.
// Close the menubutton, clear out drag styles, and clear all
// timers for opening/closing it.
clear: function OF__clear() {
if (this._folder.elt && this._folder.elt.lastChild) {
if (!this._folder.elt.lastChild.hasAttribute("dragover"))
this._folder.elt.lastChild.hidePopup();
// remove menuactive style
this._folder.elt.removeAttribute("_moz-menuactive");
this._folder.elt = null;
}
if (this._folder.openTimer) {
this._folder.openTimer.cancel();
this._folder.openTimer = null;
}
if (this._folder.closeTimer) {
this._folder.closeTimer.cancel();
this._folder.closeTimer = null;
}
}
})]]></field>
<method name="_cleanupDragDetails">
<body><![CDATA[
// Called on dragend and drop.
PlacesControllerDragHelper.currentDropTarget = null;
this._rootView._draggedElt = null;
this.removeAttribute("dragover");
this.removeAttribute("dragstart");
this._indicatorBar.hidden = true;
]]></body>
</method>
</implementation>
<handlers>
<handler event="DOMMenuItemActive"><![CDATA[
let elt = event.target;
if (elt.parentNode != this)
return;
#ifdef XP_MACOSX
// XXX: The following check is a temporary hack until bug 420033 is
// resolved.
let parentElt = elt.parent;
while (parentElt) {
if (parentElt.id == "bookmarksMenuPopup" ||
parentElt.id == "goPopup")
return;
parentElt = parentElt.parentNode;
}
#endif
if (window.XULBrowserWindow) {
let elt = event.target;
let placesNode = elt._placesNode;
var linkURI;
if (placesNode && PlacesUtils.nodeIsURI(placesNode))
linkURI = placesNode.uri;
else if (elt.hasAttribute("targetURI"))
linkURI = elt.getAttribute("targetURI");
if (linkURI)
window.XULBrowserWindow.setOverLink(linkURI, null);
}
]]></handler>
<handler event="DOMMenuItemInactive"><![CDATA[
let elt = event.target;
if (elt.parentNode != this)
return;
if (window.XULBrowserWindow)
window.XULBrowserWindow.setOverLink("", null);
]]></handler>
<handler event="dragstart"><![CDATA[
if (!event.target._placesNode)
return;
let draggedElt = event.target._placesNode;
// Force a copy action if parent node is a query or we are dragging a
// not-removable node.
if (!PlacesControllerDragHelper.canMoveNode(draggedElt))
event.dataTransfer.effectAllowed = "copyLink";
// Activate the view and cache the dragged element.
this._rootView._draggedElt = draggedElt;
this._rootView.controller.setDataTransfer(event);
this.setAttribute("dragstart", "true");
event.stopPropagation();
]]></handler>
<handler event="drop"><![CDATA[
PlacesControllerDragHelper.currentDropTarget = event.target;
let dropPoint = this._getDropPoint(event);
if (dropPoint && dropPoint.ip) {
PlacesControllerDragHelper.onDrop(dropPoint.ip, event.dataTransfer);
event.preventDefault();
}
this._cleanupDragDetails();
event.stopPropagation();
]]></handler>
<handler event="dragover"><![CDATA[
PlacesControllerDragHelper.currentDropTarget = event.target;
let dt = event.dataTransfer;
let dropPoint = this._getDropPoint(event);
if (!dropPoint || !dropPoint.ip ||
!PlacesControllerDragHelper.canDrop(dropPoint.ip, dt)) {
this._indicatorBar.hidden = true;
event.stopPropagation();
return;
}
// Mark this popup as being dragged over.
this.setAttribute("dragover", "true");
if (dropPoint.folderElt) {
// We are dragging over a folder.
// _overFolder should take the care of opening it on a timer.
if (this._overFolder.elt &&
this._overFolder.elt != dropPoint.folderElt) {
// We are dragging over a new folder, let's clear old values
this._overFolder.clear();
}
if (!this._overFolder.elt) {
this._overFolder.elt = dropPoint.folderElt;
// Create the timer to open this folder.
this._overFolder.openTimer = this._overFolder
.setTimer(this._overFolder.hoverTime);
}
// Since we are dropping into a folder set the corresponding style.
dropPoint.folderElt.setAttribute("_moz-menuactive", true);
}
else {
// We are not dragging over a folder.
// Clear out old _overFolder information.
this._overFolder.clear();
}
// Autoscroll the popup strip if we drag over the scroll buttons.
let anonid = event.originalTarget.getAttribute('anonid');
let scrollDir = anonid == "scrollbutton-up" ? -1 :
anonid == "scrollbutton-down" ? 1 : 0;
if (scrollDir != 0) {
this._scrollBox.scrollByIndex(scrollDir, false);
}
// Check if we should hide the drop indicator for this target.
if (dropPoint.folderElt || this._hideDropIndicator(event)) {
this._indicatorBar.hidden = true;
event.preventDefault();
event.stopPropagation();
return;
}
// We should display the drop indicator relative to the arrowscrollbox.
let sbo = this._scrollBox.scrollBoxObject;
let newMarginTop = 0;
if (scrollDir == 0) {
let elt = this.firstChild;
while (elt && event.screenY > elt.boxObject.screenY +
elt.boxObject.height / 2)
elt = elt.nextSibling;
newMarginTop = elt ? elt.boxObject.screenY - sbo.screenY :
sbo.height;
}
else if (scrollDir == 1)
newMarginTop = sbo.height;
// Set the new marginTop based on arrowscrollbox.
newMarginTop += sbo.y - this._scrollBox.boxObject.y;
this._indicatorBar.firstChild.style.marginTop = newMarginTop + "px";
this._indicatorBar.hidden = false;
event.preventDefault();
event.stopPropagation();
]]></handler>
<handler event="dragexit"><![CDATA[
PlacesControllerDragHelper.currentDropTarget = null;
this.removeAttribute("dragover");
// If we have not moved to a valid new target clear the drop indicator
// this happens when moving out of the popup.
let target = event.relatedTarget;
if (!target)
this._indicatorBar.hidden = true;
// Close any folder being hovered over
if (this._overFolder.elt) {
this._overFolder.closeTimer = this._overFolder
.setTimer(this._overFolder.hoverTime);
}
// The autoopened attribute is set when this folder was automatically
// opened after the user dragged over it. If this attribute is set,
// auto-close the folder on drag exit.
// We should also try to close this popup if the drag has started
// from here, the timer will check if we are dragging over a child.
if (this.hasAttribute("autoopened") ||
this.hasAttribute("dragstart")) {
this._overFolder.closeMenuTimer = this._overFolder
.setTimer(this._overFolder.hoverTime);
}
event.stopPropagation();
]]></handler>
<handler event="dragend"><![CDATA[
this._cleanupDragDetails();
]]></handler>
</handlers>
</binding>
</bindings>

View file

@ -0,0 +1,54 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
var gMoveBookmarksDialog = {
_nodes: null,
_foldersTree: null,
get foldersTree() {
if (!this._foldersTree)
this._foldersTree = document.getElementById("foldersTree");
return this._foldersTree;
},
init: function() {
this._nodes = window.arguments[0];
this.foldersTree.place =
"place:excludeItems=1&excludeQueries=1&excludeReadOnlyFolders=1&folder=" +
PlacesUIUtils.allBookmarksFolderId;
},
onOK: function MBD_onOK(aEvent) {
var selectedNode = this.foldersTree.selectedNode;
NS_ASSERT(selectedNode,
"selectedNode must be set in a single-selection tree with initial selection set");
var selectedFolderID = PlacesUtils.getConcreteItemId(selectedNode);
var transactions = [];
for (var i=0; i < this._nodes.length; i++) {
// Nothing to do if the node is already under the selected folder
if (this._nodes[i].parent.itemId == selectedFolderID)
continue;
let txn = new PlacesMoveItemTransaction(this._nodes[i].itemId,
selectedFolderID,
PlacesUtils.bookmarks.DEFAULT_INDEX);
transactions.push(txn);
}
if (transactions.length != 0) {
let txn = new PlacesAggregatedTransaction("Move Items", transactions);
PlacesUtils.transactionManager.doTransaction(txn);
}
},
newFolder: function MBD_newFolder() {
// The command is disabled when the tree is not focused
this.foldersTree.focus();
goDoCommand("placesCmd_new:folder");
}
};

View file

@ -0,0 +1,53 @@
<?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/"?>
<?xml-stylesheet href="chrome://browser/skin/places/places.css"?>
<?xml-stylesheet href="chrome://browser/content/places/places.css"?>
<?xul-overlay href="chrome://browser/content/places/placesOverlay.xul"?>
<!DOCTYPE window [
<!ENTITY % moveBookmarksDTD SYSTEM "chrome://browser/locale/places/moveBookmarks.dtd">
%moveBookmarksDTD;
]>
<dialog id="moveBookmarkDialog"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
ondialogaccept="return gMoveBookmarksDialog.onOK(event);"
title="&window.title;"
onload="gMoveBookmarksDialog.init();"
style="&window.style;"
screenX="24"
screenY="24"
persist="screenX screenY width height">
<script type="application/javascript"
src="chrome://browser/content/places/moveBookmarks.js"/>
<hbox flex="1">
<label id="movetolabel" value="&moveTo.label;" control="foldersTree"/>
<hbox flex="1">
<tree id="foldersTree"
class="placesTree"
flex="1"
type="places"
seltype="single"
hidecolumnpicker="true">
<treecols>
<treecol id="title" flex="1" primary="true" hideheader="true"/>
</treecols>
<treechildren id="placesListChildren" view="placesList" flex="1"/>
</tree>
<vbox>
<button id="newFolderButton"
label="&newFolderButton.label;"
accesskey="&newFolderButton.accesskey;"
oncommand="gMoveBookmarksDialog.newFolder();"/>
</vbox>
</hbox>
</hbox>
</dialog>

View file

@ -0,0 +1,7 @@
/* 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/. */
#searchFilter {
width: 23em;
}

View file

@ -0,0 +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/. */
tree[type="places"] {
-moz-binding: url("chrome://browser/content/places/tree.xml#places-tree");
}
.toolbar-drop-indicator {
position: relative;
z-index: 1;
}
menupopup[placespopup="true"] {
-moz-binding: url("chrome://browser/content/places/menu.xml#places-popup-base");
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,478 @@
<?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://browser/content/places/places.css"?>
<?xml-stylesheet href="chrome://browser/content/places/organizer.css"?>
<?xml-stylesheet href="chrome://global/skin/"?>
<?xml-stylesheet href="chrome://browser/skin/places/places.css"?>
<?xml-stylesheet href="chrome://browser/skin/places/organizer.css"?>
<?xul-overlay href="chrome://browser/content/places/editBookmarkOverlay.xul"?>
#ifdef XP_MACOSX
<?xul-overlay href="chrome://browser/content/macBrowserOverlay.xul"?>
#else
<?xul-overlay href="chrome://browser/content/baseMenuOverlay.xul"?>
<?xul-overlay href="chrome://global/content/editMenuOverlay.xul"?>
<?xul-overlay href="chrome://browser/content/places/placesOverlay.xul"?>
#endif
<!DOCTYPE window [
<!ENTITY % placesDTD SYSTEM "chrome://browser/locale/places/places.dtd">
%placesDTD;
<!ENTITY % editMenuOverlayDTD SYSTEM "chrome://global/locale/editMenuOverlay.dtd">
%editMenuOverlayDTD;
<!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd">
%browserDTD;
]>
<window id="places"
title="&places.library.title;"
windowtype="Places:Organizer"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:html="http://www.w3.org/1999/xhtml"
onload="PlacesOrganizer.init();"
onunload="PlacesOrganizer.destroy();"
width="&places.library.width;" height="&places.library.height;"
screenX="10" screenY="10"
toggletoolbar="true"
persist="width height screenX screenY sizemode">
<script type="application/javascript"
src="chrome://browser/content/places/places.js"/>
<script type="application/javascript"
src="chrome://browser/content/utilityOverlay.js"/>
<script type="application/javascript"
src="chrome://browser/content/places/editBookmarkOverlay.js"/>
<stringbundleset id="placesStringSet">
<stringbundle id="brandStrings" src="chrome://branding/locale/brand.properties"/>
</stringbundleset>
#ifdef XP_MACOSX
#include ../../../base/content/browserMountPoints.inc
#else
<commandset id="editMenuCommands"/>
<commandset id="placesCommands"/>
#endif
<keyset id="placesCommandKeys"/>
<commandset id="organizerCommandSet">
<command id="OrganizerCommand_find:all"
oncommand="PlacesSearchBox.findAll();"/>
<command id="OrganizerCommand_export"
oncommand="PlacesOrganizer.exportBookmarks();"/>
<command id="OrganizerCommand_import"
oncommand="PlacesOrganizer.importFromFile();"/>
<command id="OrganizerCommand_browserImport"
oncommand="PlacesOrganizer.importFromBrowser();"/>
<command id="OrganizerCommand_backup"
oncommand="PlacesOrganizer.backupBookmarks();"/>
<command id="OrganizerCommand_restoreFromFile"
oncommand="PlacesOrganizer.onRestoreBookmarksFromFile();"/>
<command id="OrganizerCommand_search:save"
oncommand="PlacesOrganizer.saveSearch();"/>
<command id="OrganizerCommand_search:moreCriteria"
oncommand="PlacesQueryBuilder.addRow();"/>
<command id="OrganizerCommand:Back"
oncommand="PlacesOrganizer.back();"/>
<command id="OrganizerCommand:Forward"
oncommand="PlacesOrganizer.forward();"/>
</commandset>
<keyset id="placesOrganizerKeyset">
<!-- Instantiation Keys -->
<key id="placesKey_close" key="&cmd.close.key;" modifiers="accel"
oncommand="close();"/>
<!-- Command Keys -->
<key id="placesKey_find:all"
command="OrganizerCommand_find:all"
key="&cmd.find.key;"
modifiers="accel"/>
<!-- Back/Forward Keys Support -->
#ifndef XP_MACOSX
<key id="placesKey_goBackKb"
keycode="VK_LEFT"
command="OrganizerCommand:Back"
modifiers="alt"/>
<key id="placesKey_goForwardKb"
keycode="VK_RIGHT"
command="OrganizerCommand:Forward"
modifiers="alt"/>
#else
<key id="placesKey_goBackKb"
keycode="VK_LEFT"
command="OrganizerCommand:Back"
modifiers="accel"/>
<key id="placesKey_goForwardKb"
keycode="VK_RIGHT"
command="OrganizerCommand:Forward"
modifiers="accel"/>
#endif
#ifdef XP_UNIX
<key id="placesKey_goBackKb2"
key="&goBackCmd.commandKey;"
command="OrganizerCommand:Back"
modifiers="accel"/>
<key id="placesKey_goForwardKb2"
key="&goForwardCmd.commandKey;"
command="OrganizerCommand:Forward"
modifiers="accel"/>
#endif
</keyset>
<keyset id="editMenuKeys">
#ifdef XP_MACOSX
<key id="key_delete2" keycode="VK_BACK" command="cmd_delete"/>
#endif
</keyset>
<popupset id="placesPopupset">
<menupopup id="placesContext"/>
<menupopup id="placesColumnsContext"
onpopupshowing="ViewMenu.fillWithColumns(event, null, null, 'checkbox', null);"
oncommand="ViewMenu.showHideColumn(event.target); event.stopPropagation();"/>
</popupset>
<toolbox id="placesToolbox">
<toolbar class="chromeclass-toolbar" id="placesToolbar" align="center">
<toolbarbutton id="back-button"
command="OrganizerCommand:Back"
tooltiptext="&backButton.tooltip;"
disabled="true"/>
<toolbarbutton id="forward-button"
command="OrganizerCommand:Forward"
tooltiptext="&forwardButton.tooltip;"
disabled="true"/>
#ifdef XP_MACOSX
<toolbarbutton type="menu" class="tabbable"
onpopupshowing="document.getElementById('placeContent').focus()"
#else
<menubar id="placesMenu">
<menu accesskey="&organize.accesskey;" class="menu-iconic"
#endif
id="organizeButton" label="&organize.label;"
tooltiptext="&organize.tooltip;">
<menupopup id="organizeButtonPopup">
<menuitem id="newbookmark"
command="placesCmd_new:bookmark"
label="&cmd.new_bookmark.label;"
accesskey="&cmd.new_bookmark.accesskey;"/>
<menuitem id="newfolder"
command="placesCmd_new:folder"
label="&cmd.new_folder.label;"
accesskey="&cmd.new_folder.accesskey;"/>
<menuitem id="newseparator"
command="placesCmd_new:separator"
label="&cmd.new_separator.label;"
accesskey="&cmd.new_separator.accesskey;"/>
#ifndef XP_MACOSX
<menuseparator id="orgUndoSeparator"/>
<menuitem id="orgUndo"
command="cmd_undo"
label="&undoCmd.label;"
key="key_undo"
accesskey="&undoCmd.accesskey;"/>
<menuitem id="orgRedo"
command="cmd_redo"
label="&redoCmd.label;"
key="key_redo"
accesskey="&redoCmd.accesskey;"/>
<menuseparator id="orgCutSeparator"/>
<menuitem id="orgCut"
command="cmd_cut"
label="&cutCmd.label;"
key="key_cut"
accesskey="&cutCmd.accesskey;"
selection="separator|link|folder|mixed"/>
<menuitem id="orgCopy"
command="cmd_copy"
label="&copyCmd.label;"
key="key_copy"
accesskey="&copyCmd.accesskey;"
selection="separator|link|folder|mixed"/>
<menuitem id="orgPaste"
command="cmd_paste"
label="&pasteCmd.label;"
key="key_paste"
accesskey="&pasteCmd.accesskey;"
selection="mutable"/>
<menuitem id="orgDelete"
command="cmd_delete"
label="&deleteCmd.label;"
key="key_delete"
accesskey="&deleteCmd.accesskey;"/>
<menuseparator id="selectAllSeparator"/>
<menuitem id="orgSelectAll"
command="cmd_selectAll"
label="&selectAllCmd.label;"
key="key_selectAll"
accesskey="&selectAllCmd.accesskey;"/>
#endif
<menuseparator id="orgMoveSeparator"/>
<menuitem id="orgMoveBookmarks"
command="placesCmd_moveBookmarks"
label="&cmd.moveBookmarks.label;"
accesskey="&cmd.moveBookmarks.accesskey;"/>
#ifdef XP_MACOSX
<menuitem id="orgDelete"
command="cmd_delete"
label="&deleteCmd.label;"
key="key_delete"
accesskey="&deleteCmd.accesskey;"/>
#else
<menuseparator id="orgCloseSeparator"/>
<menuitem id="orgClose"
key="placesKey_close"
label="&file.close.label;"
accesskey="&file.close.accesskey;"
oncommand="close();"/>
#endif
</menupopup>
#ifdef XP_MACOSX
</toolbarbutton>
<toolbarbutton type="menu" class="tabbable"
#else
</menu>
<menu accesskey="&views.accesskey;" class="menu-iconic"
#endif
id="viewMenu" label="&views.label;"
tooltiptext="&views.tooltip;">
<menupopup id="viewMenuPopup">
<menu id="viewColumns"
label="&view.columns.label;" accesskey="&view.columns.accesskey;">
<menupopup onpopupshowing="ViewMenu.fillWithColumns(event, null, null, 'checkbox', null);"
oncommand="ViewMenu.showHideColumn(event.target); event.stopPropagation();"/>
</menu>
<menu id="viewSort" label="&view.sort.label;"
accesskey="&view.sort.accesskey;">
<menupopup onpopupshowing="ViewMenu.populateSortMenu(event);"
oncommand="ViewMenu.setSortColumn(event.target.column, null);">
<menuitem id="viewUnsorted" type="radio" name="columns"
label="&view.unsorted.label;" accesskey="&view.unsorted.accesskey;"
oncommand="ViewMenu.setSortColumn(null, null);"/>
<menuseparator id="directionSeparator"/>
<menuitem id="viewSortAscending" type="radio" name="direction"
label="&view.sortAscending.label;" accesskey="&view.sortAscending.accesskey;"
oncommand="ViewMenu.setSortColumn(null, 'ascending'); event.stopPropagation();"/>
<menuitem id="viewSortDescending" type="radio" name="direction"
label="&view.sortDescending.label;" accesskey="&view.sortDescending.accesskey;"
oncommand="ViewMenu.setSortColumn(null, 'descending'); event.stopPropagation();"/>
</menupopup>
</menu>
</menupopup>
#ifdef XP_MACOSX
</toolbarbutton>
<toolbarbutton type="menu" class="tabbable"
#else
</menu>
<menu accesskey="&maintenance.accesskey;" class="menu-iconic"
#endif
id="maintenanceButton" label="&maintenance.label;"
tooltiptext="&maintenance.tooltip;">
<menupopup id="maintenanceButtonPopup">
<menuitem id="backupBookmarks"
command="OrganizerCommand_backup"
label="&cmd.backup.label;"
accesskey="&cmd.backup.accesskey;"/>
<menu id="fileRestoreMenu" label="&cmd.restore2.label;"
accesskey="&cmd.restore2.accesskey;">
<menupopup id="fileRestorePopup" onpopupshowing="PlacesOrganizer.populateRestoreMenu();">
<menuitem id="restoreFromFile"
command="OrganizerCommand_restoreFromFile"
label="&cmd.restoreFromFile.label;"
accesskey="&cmd.restoreFromFile.accesskey;"/>
</menupopup>
</menu>
<menuseparator/>
<menuitem id="fileImport"
command="OrganizerCommand_import"
label="&importBookmarksFromHTML.label;"
accesskey="&importBookmarksFromHTML.accesskey;"/>
<menuitem id="fileExport"
command="OrganizerCommand_export"
label="&exportBookmarksToHTML.label;"
accesskey="&exportBookmarksToHTML.accesskey;"/>
<menuseparator/>
<menuitem id="browserImport"
command="OrganizerCommand_browserImport"
label="&importOtherBrowser.label;"
accesskey="&importOtherBrowser.accesskey;"/>
</menupopup>
#ifdef XP_MACOSX
</toolbarbutton>
#else
</menu>
</menubar>
#endif
<spacer id="libraryToolbarSpacer" flex="1"/>
<textbox id="searchFilter"
clickSelectsAll="true"
type="search"
aria-controls="placeContent"
oncommand="PlacesSearchBox.search(this.value);"
collection="bookmarks">
</textbox>
</toolbar>
</toolbox>
<hbox flex="1" id="placesView">
<tree id="placesList"
class="plain placesTree"
type="places"
hidecolumnpicker="true" context="placesContext"
onselect="PlacesOrganizer.onPlaceSelected(true);"
onclick="PlacesOrganizer.onPlacesListClick(event);"
onfocus="PlacesOrganizer.updateDetailsPane(event);"
seltype="single"
persist="width"
width="200"
minwidth="100"
maxwidth="400">
<treecols>
<treecol anonid="title" flex="1" primary="true" hideheader="true"/>
</treecols>
<treechildren flex="1"/>
</tree>
<splitter collapse="none" persist="state"></splitter>
<vbox id="contentView" flex="4">
<toolbox id="searchModifiers" hidden="true">
<toolbar id="organizerScopeBar" class="chromeclass-toolbar" align="center">
<label id="scopeBarTitle" value="&search.in.label;"/>
<toolbarbutton id="scopeBarAll" class="small-margin"
type="radio" group="scopeBar"
oncommand="PlacesQueryBuilder.onScopeSelected(this);"
label="&search.scopeBookmarks.label;"
accesskey="&search.scopeBookmarks.accesskey;"/>
<toolbarbutton id="scopeBarHistory" class="small-margin"
type="radio" group="scopeBar"
oncommand="PlacesQueryBuilder.onScopeSelected(this);"
label="&search.scopeHistory.label;"
accesskey="&search.scopeHistory.accesskey;"/>
<toolbarbutton id="scopeBarDownloads" class="small-margin"
type="radio" group="scopeBar"
oncommand="PlacesQueryBuilder.onScopeSelected(this);"
label="&search.scopeDownloads.label;"
accesskey="&search.scopeDownloads.accesskey;"/>
<toolbarbutton id="scopeBarFolder" class="small-margin"
type="radio" group="scopeBar"
oncommand="PlacesQueryBuilder.onScopeSelected(this);"
accesskey="&search.scopeFolder.accesskey;"
emptytitle="&search.scopeFolder.label;" flex="1"/>
<!-- The folder scope button should flex but not take up more room
than its label needs. The only simple way to do that is to
set a really big flex on the spacer, e.g., 2^31 - 1. -->
<spacer flex="2147483647"/>
<button id="saveSearch" class="small-margin"
label="&saveSearch.label;" accesskey="&saveSearch.accesskey;"
command="OrganizerCommand_search:save"/>
</toolbar>
</toolbox>
<deck id="placesViewsDeck"
selectedIndex="0"
flex="1">
<tree id="placeContent"
class="plain placesTree"
context="placesContext"
hidecolumnpicker="true"
flex="1"
type="places"
flatList="true"
selectfirstnode="true"
enableColumnDrag="true"
onfocus="PlacesOrganizer.updateDetailsPane(event)"
onselect="PlacesOrganizer.updateDetailsPane(event)"
onkeypress="ContentTree.onKeyPress(event);"
onopenflatcontainer="PlacesOrganizer.openFlatContainer(aContainer);">
<treecols id="placeContentColumns" context="placesColumnsContext">
<treecol label="&col.name.label;" id="placesContentTitle" anonid="title" flex="5" primary="true" ordinal="1"
persist="width hidden ordinal sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol label="&col.tags.label;" id="placesContentTags" anonid="tags" flex="2"
persist="width hidden ordinal sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol label="&col.url.label;" id="placesContentUrl" anonid="url" flex="5"
persist="width hidden ordinal sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol label="&col.lastvisit.label;" id="placesContentDate" anonid="date" flex="1" hidden="true"
persist="width hidden ordinal sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol label="&col.visitcount.label;" id="placesContentVisitCount" anonid="visitCount" flex="1" hidden="true"
persist="width hidden ordinal sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol label="&col.keyword.label;" id="placesContentKeyword" anonid="keyword" flex="1" hidden="true"
persist="width hidden ordinal sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol label="&col.description.label;" id="placesContentDescription" anonid="description" flex="1" hidden="true"
persist="width hidden ordinal sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol label="&col.dateadded.label;" id="placesContentDateAdded" anonid="dateAdded" flex="1" hidden="true"
persist="width hidden ordinal sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol label="&col.lastmodified.label;" id="placesContentLastModified" anonid="lastModified" flex="1" hidden="true"
persist="width hidden ordinal sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol label="&col.parentfolder.label;" id="placesContentParentFolder" anonid="parentFolder" flex="1" hidden="true"
persist="width hidden ordinal"/>
<splitter class="tree-splitter"/>
<treecol label="&col.parentfolderpath.label;" id="placesContentParentFolderPath" anonid="parentFolderPath" flex="1" hidden="true"
persist="width hidden ordinal"/>
</treecols>
<treechildren flex="1" onclick="ContentTree.onClick(event);"/>
</tree>
</deck>
<deck id="detailsDeck" style="height: 11em;">
<vbox id="itemsCountBox" align="center">
<spacer flex="3"/>
<label id="itemsCountText"/>
<spacer flex="1"/>
<description id="selectItemDescription">
&detailsPane.selectAnItemText.description;
</description>
<spacer flex="3"/>
</vbox>
<vbox id="infoBox" minimal="true">
<vbox id="editBookmarkPanelContent" flex="1"/>
<hbox id="infoBoxExpanderWrapper" align="center">
<button type="image" id="infoBoxExpander"
class="expander-down"
oncommand="PlacesOrganizer.toggleAdditionalInfoFields();"
observes="paneElementsBroadcaster"/>
<label id="infoBoxExpanderLabel"
lesslabel="&detailsPane.less.label;"
lessaccesskey="&detailsPane.less.accesskey;"
morelabel="&detailsPane.more.label;"
moreaccesskey="&detailsPane.more.accesskey;"
value="&detailsPane.more.label;"
accesskey="&detailsPane.more.accesskey;"
control="infoBoxExpander"/>
</hbox>
</vbox>
</deck>
</vbox>
</hbox>
</window>

View file

@ -0,0 +1,248 @@
<!-- 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 overlay [
<!ENTITY % placesDTD SYSTEM "chrome://browser/locale/places/places.dtd">
%placesDTD;
<!ENTITY % editMenuOverlayDTD SYSTEM "chrome://global/locale/editMenuOverlay.dtd">
%editMenuOverlayDTD;
]>
<overlay id="placesOverlay"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script type="application/javascript"
src="chrome://global/content/globalOverlay.js"/>
<script type="application/javascript"
src="chrome://browser/content/utilityOverlay.js"/>
<script type="application/javascript"><![CDATA[
// TODO: Bug 406371.
// A bunch of browser code depends on us defining these, sad but true :(
var Cc = Components.classes;
var Ci = Components.interfaces;
var Cr = Components.results;
Components.utils.import("resource://gre/modules/PlacesUtils.jsm");
Components.utils.import("resource:///modules/PlacesUIUtils.jsm");
]]></script>
<script type="application/javascript"
src="chrome://browser/content/places/controller.js"/>
<script type="application/javascript"
src="chrome://browser/content/places/treeView.js"/>
<!-- Bookmarks and history tooltip -->
<tooltip id="bhTooltip" noautohide="true"
onpopupshowing="return window.top.BookmarksEventHandler.fillInBHTooltip(document, event)">
<vbox id="bhTooltipTextBox" flex="1">
<label id="bhtTitleText" class="tooltip-label" />
<label id="bhtUrlText" crop="center" class="tooltip-label" />
</vbox>
</tooltip>
<commandset id="placesCommands"
commandupdater="true"
events="focus,sort,places"
oncommandupdate="goUpdatePlacesCommands();">
<command id="placesCmd_open"
oncommand="goDoPlacesCommand('placesCmd_open');"/>
<command id="placesCmd_open:window"
oncommand="goDoPlacesCommand('placesCmd_open:window');"/>
<command id="placesCmd_open:privatewindow"
oncommand="goDoPlacesCommand('placesCmd_open:privatewindow');"/>
<command id="placesCmd_open:tab"
oncommand="goDoPlacesCommand('placesCmd_open:tab');"/>
<command id="placesCmd_new:bookmark"
oncommand="goDoPlacesCommand('placesCmd_new:bookmark');"/>
<command id="placesCmd_new:livemark"
oncommand="goDoPlacesCommand('placesCmd_new:livemark');"/>
<command id="placesCmd_new:folder"
oncommand="goDoPlacesCommand('placesCmd_new:folder');"/>
<command id="placesCmd_new:separator"
oncommand="goDoPlacesCommand('placesCmd_new:separator');"/>
<command id="placesCmd_show:info"
oncommand="goDoPlacesCommand('placesCmd_show:info');"/>
<command id="placesCmd_rename"
oncommand="goDoPlacesCommand('placesCmd_show:info');"
observes="placesCmd_show:info"/>
<command id="placesCmd_reload"
oncommand="goDoPlacesCommand('placesCmd_reload');"/>
<command id="placesCmd_sortBy:name"
oncommand="goDoPlacesCommand('placesCmd_sortBy:name');"/>
<command id="placesCmd_moveBookmarks"
oncommand="goDoPlacesCommand('placesCmd_moveBookmarks');"/>
<command id="placesCmd_deleteDataHost"
oncommand="goDoPlacesCommand('placesCmd_deleteDataHost');"/>
<command id="placesCmd_createBookmark"
oncommand="goDoPlacesCommand('placesCmd_createBookmark');"/>
<command id="placesCmd_openParentFolder"
oncommand="goDoPlacesCommand('placesCmd_openParentFolder');"/>
<!-- Special versions of cut/copy/paste/delete which check for an open context menu. -->
<command id="placesCmd_cut"
oncommand="goDoPlacesCommand('placesCmd_cut');"/>
<command id="placesCmd_copy"
oncommand="goDoPlacesCommand('placesCmd_copy');"/>
<command id="placesCmd_paste"
oncommand="goDoPlacesCommand('placesCmd_paste');"/>
<command id="placesCmd_delete"
oncommand="goDoPlacesCommand('placesCmd_delete');"/>
</commandset>
<keyset id="placesCommandKeys">
<key id="key_placesCmd_openParentFolder"
keycode="VK_F1"
command="placesCmd_openParentFolder"
modifiers="accel,shift"/>
</keyset>
<menupopup id="placesContext"
onpopupshowing="this._view = PlacesUIUtils.getViewForNode(document.popupNode);
return this._view.buildContextMenu(this);"
onpopuphiding="this._view.destroyContextMenu();">
<menuitem id="placesContext_open"
command="placesCmd_open"
label="&cmd.open.label;"
accesskey="&cmd.open.accesskey;"
default="true"
selectiontype="single"
selection="link"/>
<menuitem id="placesContext_open:newtab"
command="placesCmd_open:tab"
label="&cmd.open_tab.label;"
accesskey="&cmd.open_tab.accesskey;"
selectiontype="single"
selection="link"/>
<menuitem id="placesContext_openContainer:tabs"
oncommand="var view = PlacesUIUtils.getViewForNode(document.popupNode);
view.controller.openSelectionInTabs(event);"
onclick="checkForMiddleClick(this, event);"
label="&cmd.open_all_in_tabs.label;"
accesskey="&cmd.open_all_in_tabs.accesskey;"
selectiontype="single"
selection="folder|host|query"/>
<menuitem id="placesContext_openLinks:tabs"
oncommand="var view = PlacesUIUtils.getViewForNode(document.popupNode);
view.controller.openSelectionInTabs(event);"
onclick="checkForMiddleClick(this, event);"
label="&cmd.open_all_in_tabs.label;"
accesskey="&cmd.open_all_in_tabs.accesskey;"
selectiontype="multiple"
selection="link"/>
<menuitem id="placesContext_open:newwindow"
command="placesCmd_open:window"
label="&cmd.open_window.label;"
accesskey="&cmd.open_window.accesskey;"
selectiontype="single"
selection="link"/>
<menuitem id="placesContext_open:newprivatewindow"
command="placesCmd_open:privatewindow"
label="&cmd.open_private_window.label;"
accesskey="&cmd.open_private_window.accesskey;"
selectiontype="single"
selection="link"
hideifprivatebrowsing="true"/>
<menuseparator id="placesContext_openSeparator"/>
<menuitem id="placesContext_new:bookmark"
command="placesCmd_new:bookmark"
label="&cmd.new_bookmark.label;"
accesskey="&cmd.new_bookmark.accesskey;"
selection="any"
hideifnoinsertionpoint="true"/>
<menuitem id="placesContext_new:folder"
command="placesCmd_new:folder"
label="&cmd.new_folder.label;"
accesskey="&cmd.context_new_folder.accesskey;"
selection="any"
hideifnoinsertionpoint="true"/>
<menuitem id="placesContext_new:separator"
command="placesCmd_new:separator"
label="&cmd.new_separator.label;"
accesskey="&cmd.new_separator.accesskey;"
closemenu="single"
selection="any"
hideifnoinsertionpoint="true"/>
<menuseparator id="placesContext_newSeparator"/>
<menuitem id="placesContext_createBookmark"
command="placesCmd_createBookmark"
label="&cmd.bookmarkLink.label;"
accesskey="&cmd.bookmarkLink.accesskey;"
selection="link"
forcehideselection="bookmark|tagChild"/>
<menuitem id="placesContext_cut"
command="placesCmd_cut"
label="&cutCmd.label;"
accesskey="&cutCmd.accesskey;"
closemenu="single"
selection="bookmark|folder|separator|query"
forcehideselection="tagChild|livemarkChild"/>
<menuitem id="placesContext_copy"
command="placesCmd_copy"
label="&copyCmd.label;"
closemenu="single"
accesskey="&copyCmd.accesskey;"
selection="any"/>
<menuitem id="placesContext_paste"
command="placesCmd_paste"
label="&pasteCmd.label;"
closemenu="single"
accesskey="&pasteCmd.accesskey;"
selection="any"
hideifnoinsertionpoint="true"/>
<menuseparator id="placesContext_editSeparator"/>
<menuitem id="placesContext_delete"
command="placesCmd_delete"
label="&deleteCmd.label;"
accesskey="&deleteCmd.accesskey;"
closemenu="single"
selection="bookmark|tagChild|folder|query|dynamiccontainer|separator|host"/>
<menuitem id="placesContext_delete_history"
command="placesCmd_delete"
label="&cmd.delete.label;"
accesskey="&cmd.delete.accesskey;"
closemenu="single"
selection="link"
forcehideselection="bookmark"/>
<menuitem id="placesContext_deleteHost"
command="placesCmd_deleteDataHost"
label="&cmd.deleteDomainData.label;"
accesskey="&cmd.deleteDomainData.accesskey;"
closemenu="single"
selection="link|host"
selectiontype="single"
hideifprivatebrowsing="true"
forcehideselection="bookmark"/>
<menuseparator id="placesContext_deleteSeparator"/>
<menuitem id="placesContext_reload"
command="placesCmd_reload"
label="&cmd.reloadLivebookmark.label;"
accesskey="&cmd.reloadLivebookmark.accesskey;"
closemenu="single"
selection="livemark/feedURI"/>
<menuitem id="placesContext_sortBy:name"
command="placesCmd_sortBy:name"
label="&cmd.sortby_name.label;"
accesskey="&cmd.context_sortby_name.accesskey;"
closemenu="single"
selection="folder"/>
<menuseparator id="placesContext_sortSeparator"/>
<menuitem id="placesContext_openParentFolder"
command="placesCmd_openParentFolder"
label="&cmd.openParentFolder.label;"
key="key_placesCmd_openParentFolder"
accesskey="&cmd.openParentFolder.accesskey;"
selectiontype="single"
selection="bookmark"
forcehideselection="livemarkChild|livemark/feedURI|PlacesOrganizer/OrganizerQuery"/>
<menuseparator id="placesContext_parentFolderSeparator"/>
<menuitem id="placesContext_show:info"
command="placesCmd_show:info"
label="&cmd.properties.label;"
accesskey="&cmd.properties.accesskey;"
selection="bookmark|folder|query"
forcehideselection="livemarkChild"/>
</menupopup>
</overlay>

View file

@ -0,0 +1,108 @@
# -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
var SidebarUtils = {
handleTreeClick: function SU_handleTreeClick(aTree, aEvent, aGutterSelect) {
// right-clicks are not handled here
if (aEvent.button == 2)
return;
var tbo = aTree.treeBoxObject;
var cell = tbo.getCellAt(aEvent.clientX, aEvent.clientY);
if (cell.row == -1 || cell.childElt == "twisty")
return;
var mouseInGutter = false;
if (aGutterSelect) {
var rect = tbo.getCoordsForCellItem(cell.row, cell.col, "image");
// getCoordsForCellItem returns the x coordinate in logical coordinates
// (i.e., starting from the left and right sides in LTR and RTL modes,
// respectively.) Therefore, we make sure to exclude the blank area
// before the tree item icon (that is, to the left or right of it in
// LTR and RTL modes, respectively) from the click target area.
var isRTL = window.getComputedStyle(aTree, null).direction == "rtl";
if (isRTL)
mouseInGutter = aEvent.clientX > rect.x;
else
mouseInGutter = aEvent.clientX < rect.x;
}
#ifdef XP_MACOSX
var modifKey = aEvent.metaKey || aEvent.shiftKey;
#else
var modifKey = aEvent.ctrlKey || aEvent.shiftKey;
#endif
var isContainer = tbo.view.isContainer(cell.row);
var openInTabs = isContainer &&
(aEvent.button == 1 ||
(aEvent.button == 0 && modifKey)) &&
PlacesUtils.hasChildURIs(tbo.view.nodeForTreeIndex(cell.row));
if (aEvent.button == 0 && isContainer && !openInTabs) {
tbo.view.toggleOpenState(cell.row);
return;
}
else if (!mouseInGutter && openInTabs &&
aEvent.originalTarget.localName == "treechildren") {
tbo.view.selection.select(cell.row);
PlacesUIUtils.openContainerNodeInTabs(aTree.selectedNode, aEvent, aTree);
}
else if (!mouseInGutter && !isContainer &&
aEvent.originalTarget.localName == "treechildren") {
// Clear all other selection since we're loading a link now. We must
// do this *before* attempting to load the link since openURL uses
// selection as an indication of which link to load.
tbo.view.selection.select(cell.row);
PlacesUIUtils.openNodeWithEvent(aTree.selectedNode, aEvent, aTree);
}
},
handleTreeKeyPress: function SU_handleTreeKeyPress(aEvent) {
// XXX Bug 627901: Post Fx4, this method should take a tree parameter.
let tree = aEvent.target;
let node = tree.selectedNode;
if (node) {
if (aEvent.keyCode == KeyEvent.DOM_VK_RETURN)
PlacesUIUtils.openNodeWithEvent(node, aEvent, tree);
}
},
/**
* The following function displays the URL of a node that is being
* hovered over.
*/
handleTreeMouseMove: function SU_handleTreeMouseMove(aEvent) {
if (aEvent.target.localName != "treechildren")
return;
var tree = aEvent.target.parentNode;
var tbo = tree.treeBoxObject;
var cell = tbo.getCellAt(aEvent.clientX, aEvent.clientY);
// cell.row is -1 when the mouse is hovering an empty area within the tree.
// To avoid showing a URL from a previously hovered node for a currently
// hovered non-url node, we must clear the moused-over URL in these cases.
if (cell.row != -1) {
var node = tree.view.nodeForTreeIndex(cell.row);
if (PlacesUtils.nodeIsURI(node))
this.setMouseoverURL(node.uri);
else
this.setMouseoverURL("");
}
else
this.setMouseoverURL("");
},
setMouseoverURL: function SU_setMouseoverURL(aURL) {
// When the browser window is closed with an open sidebar, the sidebar
// unload event happens after the browser's one. In this case
// top.XULBrowserWindow has been nullified already.
if (top.XULBrowserWindow) {
top.XULBrowserWindow.setOverLink(aURL, null);
}
}
};

View file

@ -0,0 +1,789 @@
<?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="placesTreeBindings"
xmlns="http://www.mozilla.org/xbl"
xmlns:xbl="http://www.mozilla.org/xbl"
xmlns:html="http://www.w3.org/1999/xhtml"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<binding id="places-tree" extends="chrome://global/content/bindings/tree.xml#tree">
<implementation>
<constructor><![CDATA[
// Force an initial build.
if (this.place)
this.place = this.place;
]]></constructor>
<destructor><![CDATA[
// Break the treeviewer->result->treeviewer cycle.
// Note: unsetting the result's viewer also unsets
// the viewer's reference to our treeBoxObject.
var result = this.result;
if (result) {
result.root.containerOpen = false;
}
// Unregister the controllber before unlinking the view, otherwise it
// may still try to update commands on a view with a null result.
if (this._controller) {
this._controller.terminate();
this.controllers.removeController(this._controller);
}
this.view = null;
]]></destructor>
<property name="controller"
readonly="true"
onget="return this._controller"/>
<!-- overriding -->
<property name="view">
<getter><![CDATA[
try {
return this.treeBoxObject.view.wrappedJSObject;
}
catch(e) {
return null;
}
]]></getter>
<setter><![CDATA[
return this.treeBoxObject.view = val;
]]></setter>
</property>
<property name="associatedElement"
readonly="true"
onget="return this"/>
<method name="applyFilter">
<parameter name="filterString"/>
<parameter name="folderRestrict"/>
<parameter name="includeHidden"/>
<body><![CDATA[
// preserve grouping
var queryNode = PlacesUtils.asQuery(this.result.root);
var options = queryNode.queryOptions.clone();
// Make sure we're getting uri results.
// We do not yet support searching into grouped queries or into
// tag containers, so we must fall to the default case.
if (PlacesUtils.nodeIsHistoryContainer(queryNode) ||
options.resultType == options.RESULTS_AS_TAG_QUERY ||
options.resultType == options.RESULTS_AS_TAG_CONTENTS)
options.resultType = options.RESULTS_AS_URI;
var query = PlacesUtils.history.getNewQuery();
query.searchTerms = filterString;
if (folderRestrict) {
query.setFolders(folderRestrict, folderRestrict.length);
options.queryType = options.QUERY_TYPE_BOOKMARKS;
}
options.includeHidden = !!includeHidden;
this.load([query], options);
]]></body>
</method>
<method name="load">
<parameter name="queries"/>
<parameter name="options"/>
<body><![CDATA[
let result = PlacesUtils.history
.executeQueries(queries, queries.length,
options);
let callback;
if (this.flatList) {
let onOpenFlatContainer = this.onOpenFlatContainer;
if (onOpenFlatContainer)
callback = new Function("aContainer", onOpenFlatContainer);
}
if (!this._controller) {
this._controller = new PlacesController(this);
this.controllers.appendController(this._controller);
}
let treeView = new PlacesTreeView(this.flatList, callback, this._controller);
// Observer removal is done within the view itself. When the tree
// goes away, treeboxobject calls view.setTree(null), which then
// calls removeObserver.
result.addObserver(treeView, false);
this.view = treeView;
if (this.getAttribute("selectfirstnode") == "true" && treeView.rowCount > 0) {
treeView.selection.select(0);
}
this._cachedInsertionPoint = undefined;
]]></body>
</method>
<property name="flatList">
<getter><![CDATA[
return this.getAttribute("flatList") == "true";
]]></getter>
<setter><![CDATA[
if (this.flatList != val) {
this.setAttribute("flatList", val);
// reload with the last place set
if (this.place)
this.place = this.place;
}
return val;
]]></setter>
</property>
<property name="onOpenFlatContainer">
<getter><![CDATA[
return this.getAttribute("onopenflatcontainer");
]]></getter>
<setter><![CDATA[
if (this.onOpenFlatContainer != val) {
this.setAttribute("onopenflatcontainer", val);
// reload with the last place set
if (this.place)
this.place = this.place;
}
return val;
]]></setter>
</property>
<!--
Causes a particular node represented by the specified placeURI to be
selected in the tree. All containers above the node in the hierarchy
will be opened, so that the node is visible.
-->
<method name="selectPlaceURI">
<parameter name="placeURI"/>
<body><![CDATA[
// Do nothing if a node matching the given uri is already selected
if (this.hasSelection && this.selectedNode.uri == placeURI)
return;
function findNode(container, placeURI, nodesURIChecked) {
var containerURI = container.uri;
if (containerURI == placeURI)
return container;
if (nodesURIChecked.indexOf(containerURI) != -1)
return null;
// never check the contents of the same query
nodesURIChecked.push(containerURI);
var wasOpen = container.containerOpen;
if (!wasOpen)
container.containerOpen = true;
for (var i = 0; i < container.childCount; ++i) {
var child = container.getChild(i);
var childURI = child.uri;
if (childURI == placeURI)
return child;
else if (PlacesUtils.nodeIsContainer(child)) {
var nested = findNode(PlacesUtils.asContainer(child), placeURI, nodesURIChecked);
if (nested)
return nested;
}
}
if (!wasOpen)
container.containerOpen = false;
return null;
}
var container = this.result.root;
NS_ASSERT(container, "No result, cannot select place URI!");
if (!container)
return;
var child = findNode(container, placeURI, []);
if (child)
this.selectNode(child);
else {
// If the specified child could not be located, clear the selection
var selection = this.view.selection;
selection.clearSelection();
}
]]></body>
</method>
<!--
Causes a particular node to be selected in the tree, resulting in all
containers above the node in the hierarchy to be opened, so that the
node is visible.
-->
<method name="selectNode">
<parameter name="node"/>
<body><![CDATA[
var view = this.view;
var parent = node.parent;
if (parent && !parent.containerOpen) {
// Build a list of all of the nodes that are the parent of this one
// in the result.
var parents = [];
var root = this.result.root;
while (parent && parent != root) {
parents.push(parent);
parent = parent.parent;
}
// Walk the list backwards (opening from the root of the hierarchy)
// opening each folder as we go.
for (var i = parents.length - 1; i >= 0; --i) {
var index = view.treeIndexForNode(parents[i]);
if (index != Ci.nsINavHistoryResultTreeViewer.INDEX_INVISIBLE &&
view.isContainer(index) && !view.isContainerOpen(index))
view.toggleOpenState(index);
}
// Select the specified node...
}
var index = view.treeIndexForNode(node);
if (index == Ci.nsINavHistoryResultTreeViewer.INDEX_INVISIBLE)
return;
view.selection.select(index);
// ... and ensure it's visible, not scrolled off somewhere.
this.treeBoxObject.ensureRowIsVisible(index);
]]></body>
</method>
<!-- nsIPlacesView -->
<property name="result">
<getter><![CDATA[
try {
return this.view.QueryInterface(Ci.nsINavHistoryResultObserver).result;
}
catch (e) {
return null;
}
]]></getter>
</property>
<!-- nsIPlacesView -->
<property name="place">
<getter><![CDATA[
return this.getAttribute("place");
]]></getter>
<setter><![CDATA[
this.setAttribute("place", val);
var queriesRef = { };
var queryCountRef = { };
var optionsRef = { };
PlacesUtils.history.queryStringToQueries(val, queriesRef, queryCountRef, optionsRef);
if (queryCountRef.value == 0)
queriesRef.value = [PlacesUtils.history.getNewQuery()];
if (!optionsRef.value)
optionsRef.value = PlacesUtils.history.getNewQueryOptions();
this.load(queriesRef.value, optionsRef.value);
return val;
]]></setter>
</property>
<!-- nsIPlacesView -->
<property name="hasSelection">
<getter><![CDATA[
return this.view && this.view.selection.count >= 1;
]]></getter>
</property>
<!-- nsIPlacesView -->
<property name="selectedNodes">
<getter><![CDATA[
let nodes = [];
if (!this.hasSelection)
return nodes;
let selection = this.view.selection;
let rc = selection.getRangeCount();
let resultview = this.view;
for (let i = 0; i < rc; ++i) {
let min = { }, max = { };
selection.getRangeAt(i, min, max);
for (let j = min.value; j <= max.value; ++j)
nodes.push(resultview.nodeForTreeIndex(j));
}
return nodes;
]]></getter>
</property>
<method name="toggleCutNode">
<parameter name="aNode"/>
<parameter name="aValue"/>
<body><![CDATA[
this.view.toggleCutNode(aNode, aValue);
]]></body>
</method>
<!-- nsIPlacesView -->
<property name="removableSelectionRanges">
<getter><![CDATA[
// This property exists in addition to selectedNodes because it
// encodes selection ranges (which only occur in list views) into
// the return value. For each removed range, the index at which items
// will be re-inserted upon the remove transaction being performed is
// the first index of the range, so that the view updates correctly.
//
// For example, if we remove rows 2,3,4 and 7,8 from a list, when we
// undo that operation, if we insert what was at row 3 at row 3 again,
// it will show up _after_ the item that was at row 5. So we need to
// insert all items at row 2, and the tree view will update correctly.
//
// Also, this function collapses the selection to remove redundant
// data, e.g. when deleting this selection:
//
// http://www.foo.com/
// (-) Some Folder
// http://www.bar.com/
//
// ... returning http://www.bar.com/ as part of the selection is
// redundant because it is implied by removing "Some Folder". We
// filter out all such redundancies since some partial amount of
// the folder's children may be selected.
//
let nodes = [];
if (!this.hasSelection)
return nodes;
var selection = this.view.selection;
var rc = selection.getRangeCount();
var resultview = this.view;
// This list is kept independently of the range selected (i.e. OUTSIDE
// the for loop) since the row index of a container is unique for the
// entire view, and we could have some really wacky selection and we
// don't want to blow up.
var containers = { };
for (var i = 0; i < rc; ++i) {
var range = [];
var min = { }, max = { };
selection.getRangeAt(i, min, max);
for (var j = min.value; j <= max.value; ++j) {
if (this.view.isContainer(j))
containers[j] = true;
if (!(this.view.getParentIndex(j) in containers))
range.push(resultview.nodeForTreeIndex(j));
}
nodes.push(range);
}
return nodes;
]]></getter>
</property>
<!-- nsIPlacesView -->
<property name="draggableSelection"
onget="return this.selectedNodes"/>
<!-- nsIPlacesView -->
<property name="selectedNode">
<getter><![CDATA[
var view = this.view;
if (!view || view.selection.count != 1)
return null;
var selection = view.selection;
var min = { }, max = { };
selection.getRangeAt(0, min, max);
return this.view.nodeForTreeIndex(min.value);
]]></getter>
</property>
<!-- nsIPlacesView -->
<property name="insertionPoint">
<getter><![CDATA[
// invalidated on selection and focus changes
if (this._cachedInsertionPoint !== undefined)
return this._cachedInsertionPoint;
// there is no insertion point for history queries
// so bail out now and save a lot of work when updating commands
var resultNode = this.result.root;
if (PlacesUtils.nodeIsQuery(resultNode) &&
PlacesUtils.asQuery(resultNode).queryOptions.queryType ==
Ci.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY)
return this._cachedInsertionPoint = null;
var orientation = Ci.nsITreeView.DROP_BEFORE;
// If there is no selection, insert at the end of the container.
if (!this.hasSelection) {
var index = this.view.rowCount - 1;
this._cachedInsertionPoint =
this._getInsertionPoint(index, orientation);
return this._cachedInsertionPoint;
}
// This is a two-part process. The first part is determining the drop
// orientation.
// * The default orientation is to drop _before_ the selected item.
// * If the selected item is a container, the default orientation
// is to drop _into_ that container.
//
// Warning: It may be tempting to use tree indexes in this code, but
// you must not, since the tree is nested and as your tree
// index may change when folders before you are opened and
// closed. You must convert your tree index to a node, and
// then use getChildIndex to find your absolute index in
// the parent container instead.
//
var resultView = this.view;
var selection = resultView.selection;
var rc = selection.getRangeCount();
var min = { }, max = { };
selection.getRangeAt(rc - 1, min, max);
// If the sole selection is a container, and we are not in
// a flatlist, insert into it.
// Note that this only applies to _single_ selections,
// if the last element within a multi-selection is a
// container, insert _adjacent_ to the selection.
//
// If the sole selection is the bookmarks toolbar folder, we insert
// into it even if it is not opened
var itemId =
PlacesUtils.getConcreteItemId(resultView.nodeForTreeIndex(max.value));
if (selection.count == 1 && resultView.isContainer(max.value) &&
!this.flatList)
orientation = Ci.nsITreeView.DROP_ON;
this._cachedInsertionPoint =
this._getInsertionPoint(max.value, orientation);
return this._cachedInsertionPoint;
]]></getter>
</property>
<method name="_getInsertionPoint">
<parameter name="index"/>
<parameter name="orientation"/>
<body><![CDATA[
var result = this.result;
var resultview = this.view;
var container = result.root;
var dropNearItemId = -1;
NS_ASSERT(container, "null container");
// When there's no selection, assume the container is the container
// the view is populated from (i.e. the result's itemId).
if (index != -1) {
var lastSelected = resultview.nodeForTreeIndex(index);
if (resultview.isContainer(index) && orientation == Ci.nsITreeView.DROP_ON) {
// If the last selected item is an open container, append _into_
// it, rather than insert adjacent to it.
container = lastSelected;
index = -1;
}
else if (lastSelected.containerOpen &&
orientation == Ci.nsITreeView.DROP_AFTER &&
lastSelected.hasChildren) {
// If the last selected item is an open container and the user is
// trying to drag into it as a first item, really insert into it.
container = lastSelected;
orientation = Ci.nsITreeView.DROP_ON;
index = 0;
}
else {
// Use the last-selected node's container.
container = lastSelected.parent;
// See comment in the treeView.js's copy of this method
if (!container || !container.containerOpen)
return null;
// Avoid the potentially expensive call to getChildIndex
// if we know this container doesn't allow insertion
if (PlacesControllerDragHelper.disallowInsertion(container))
return null;
var queryOptions = PlacesUtils.asQuery(result.root).queryOptions;
if (queryOptions.sortingMode !=
Ci.nsINavHistoryQueryOptions.SORT_BY_NONE) {
// If we are within a sorted view, insert at the end
index = -1;
}
else if (queryOptions.excludeItems ||
queryOptions.excludeQueries ||
queryOptions.excludeReadOnlyFolders) {
// Some item may be invisible, insert near last selected one.
// We don't replace index here to avoid requests to the db,
// instead it will be calculated later by the controller.
index = -1;
dropNearItemId = lastSelected.itemId;
}
else {
var lsi = container.getChildIndex(lastSelected);
index = orientation == Ci.nsITreeView.DROP_BEFORE ? lsi : lsi + 1;
}
}
}
if (PlacesControllerDragHelper.disallowInsertion(container))
return null;
return new InsertionPoint(PlacesUtils.getConcreteItemId(container),
index, orientation,
PlacesUtils.nodeIsTagQuery(container),
dropNearItemId);
]]></body>
</method>
<!-- nsIPlacesView -->
<method name="selectAll">
<body><![CDATA[
this.view.selection.selectAll();
]]></body>
</method>
<!-- This method will select the first node in the tree that matches
each given item id. It will open any parent nodes that it needs
to in order to show the selected items.
-->
<method name="selectItems">
<parameter name="aIDs"/>
<parameter name="aOpenContainers"/>
<body><![CDATA[
// By default, we do search and select within containers which were
// closed (note that containers in which nodes were not found are
// closed).
if (aOpenContainers === undefined)
aOpenContainers = true;
var ids = aIDs; // don't manipulate the caller's array
// Array of nodes found by findNodes which are to be selected
var nodes = [];
// Array of nodes found by findNodes which should be opened
var nodesToOpen = [];
// A set of URIs of container-nodes that were previously searched,
// and thus shouldn't be searched again. This is empty at the initial
// start of the recursion and gets filled in as the recursion
// progresses.
var nodesURIChecked = [];
/**
* Recursively search through a node's children for items
* with the given IDs. When a matching item is found, remove its ID
* from the IDs array, and add the found node to the nodes dictionary.
*
* NOTE: This method will leave open any node that had matching items
* in its subtree.
*/
function findNodes(node) {
var foundOne = false;
// See if node matches an ID we wanted; add to results.
// For simple folder queries, check both itemId and the concrete
// item id.
var index = ids.indexOf(node.itemId);
if (index == -1 &&
node.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT)
index = ids.indexOf(PlacesUtils.asQuery(node).folderItemId);
if (index != -1) {
nodes.push(node);
foundOne = true;
ids.splice(index, 1);
}
if (ids.length == 0 || !PlacesUtils.nodeIsContainer(node) ||
nodesURIChecked.indexOf(node.uri) != -1)
return foundOne;
PlacesUtils.asContainer(node);
if (!aOpenContainers && !node.containerOpen)
return foundOne;
nodesURIChecked.push(node.uri);
// Remember the beginning state so that we can re-close
// this node if we don't find any additional results here.
var previousOpenness = node.containerOpen;
node.containerOpen = true;
for (var child = 0; child < node.childCount && ids.length > 0;
child++) {
var childNode = node.getChild(child);
var found = findNodes(childNode);
if (!foundOne)
foundOne = found;
}
// If we didn't find any additional matches in this node's
// subtree, revert the node to its previous openness.
if (foundOne)
nodesToOpen.unshift(node);
node.containerOpen = previousOpenness;
return foundOne;
}
// Disable notifications while looking for nodes.
let result = this.result;
let didSuppressNotifications = result.suppressNotifications;
if (!didSuppressNotifications)
result.suppressNotifications = true
try {
findNodes(this.result.root);
}
finally {
if (!didSuppressNotifications)
result.suppressNotifications = false;
}
// For all the nodes we've found, highlight the corresponding
// index in the tree.
var resultview = this.view;
var selection = this.view.selection;
selection.selectEventsSuppressed = true;
selection.clearSelection();
// Open nodes containing found items
for (var i = 0; i < nodesToOpen.length; i++) {
nodesToOpen[i].containerOpen = true;
}
for (var i = 0; i < nodes.length; i++) {
var index = resultview.treeIndexForNode(nodes[i]);
if (index == Ci.nsINavHistoryResultTreeViewer.INDEX_INVISIBLE)
continue;
selection.rangedSelect(index, index, true);
}
selection.selectEventsSuppressed = false;
]]></body>
</method>
<field name="_contextMenuShown">false</field>
<method name="buildContextMenu">
<parameter name="aPopup"/>
<body><![CDATA[
this._contextMenuShown = true;
return this.controller.buildContextMenu(aPopup);
]]></body>
</method>
<method name="destroyContextMenu">
<parameter name="aPopup"/>
this._contextMenuShown = false;
<body/>
</method>
<property name="ownerWindow"
readonly="true"
onget="return window;"/>
<field name="_active">true</field>
<property name="active"
onget="return this._active"
onset="return this._active = val"/>
</implementation>
<handlers>
<handler event="focus"><![CDATA[
this._cachedInsertionPoint = undefined;
// See select handler. We need the sidebar's places commandset to be
// updated as well
document.commandDispatcher.updateCommands("focus");
]]></handler>
<handler event="select"><![CDATA[
this._cachedInsertionPoint = undefined;
// This additional complexity is here for the sidebars
var win = window;
while (true) {
win.document.commandDispatcher.updateCommands("focus");
if (win == window.top)
break;
win = win.parent;
}
]]></handler>
<handler event="dragstart"><![CDATA[
if (event.target.localName != "treechildren")
return;
let nodes = this.selectedNodes;
for (let i = 0; i < nodes.length; i++) {
let node = nodes[i];
// Disallow dragging the root node of a tree.
if (!node.parent) {
event.preventDefault();
event.stopPropagation();
return;
}
// If this node is child of a readonly container (e.g. a livemark)
// or cannot be moved, we must force a copy.
if (!PlacesControllerDragHelper.canMoveNode(node)) {
event.dataTransfer.effectAllowed = "copyLink";
break;
}
}
this._controller.setDataTransfer(event);
event.stopPropagation();
]]></handler>
<handler event="dragover"><![CDATA[
if (event.target.localName != "treechildren")
return;
let cell = this.treeBoxObject.getCellAt(event.clientX, event.clientY);
let node = cell.row != -1 ?
this.view.nodeForTreeIndex(cell.row) :
this.result.root;
// cache the dropTarget for the view
PlacesControllerDragHelper.currentDropTarget = node;
// We have to calculate the orientation since view.canDrop will use
// it and we want to be consistent with the dropfeedback.
let tbo = this.treeBoxObject;
let rowHeight = tbo.rowHeight;
let eventY = event.clientY - tbo.treeBody.boxObject.y -
rowHeight * (cell.row - tbo.getFirstVisibleRow());
let orientation = Ci.nsITreeView.DROP_BEFORE;
if (cell.row == -1) {
// If the row is not valid we try to insert inside the resultNode.
orientation = Ci.nsITreeView.DROP_ON;
}
else if (PlacesUtils.nodeIsContainer(node) &&
eventY > rowHeight * 0.75) {
// If we are below the 75% of a container the treeview we try
// to drop after the node.
orientation = Ci.nsITreeView.DROP_AFTER;
}
else if (PlacesUtils.nodeIsContainer(node) &&
eventY > rowHeight * 0.25) {
// If we are below the 25% of a container the treeview we try
// to drop inside the node.
orientation = Ci.nsITreeView.DROP_ON;
}
if (!this.view.canDrop(cell.row, orientation, event.dataTransfer))
return;
event.preventDefault();
event.stopPropagation();
]]></handler>
<handler event="dragend"><![CDATA[
PlacesControllerDragHelper.currentDropTarget = null;
]]></handler>
</handlers>
</binding>
</bindings>

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,34 @@
# 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:
% overlay chrome://browser/content/places/places.xul chrome://browser/content/places/downloadsViewOverlay.xul
# Provide another URI for the bookmarkProperties dialog so we can persist the
# attributes separately
content/browser/places/bookmarkProperties2.xul (content/bookmarkProperties.xul)
* content/browser/places/places.xul (content/places.xul)
* content/browser/places/places.js (content/places.js)
content/browser/places/places.css (content/places.css)
content/browser/places/organizer.css (content/organizer.css)
content/browser/places/bookmarkProperties.xul (content/bookmarkProperties.xul)
content/browser/places/bookmarkProperties.js (content/bookmarkProperties.js)
content/browser/places/placesOverlay.xul (content/placesOverlay.xul)
* content/browser/places/menu.xml (content/menu.xml)
content/browser/places/tree.xml (content/tree.xml)
content/browser/places/controller.js (content/controller.js)
content/browser/places/treeView.js (content/treeView.js)
* content/browser/places/browserPlacesViews.js (content/browserPlacesViews.js)
# keep the Places version of the history sidebar at history/history-panel.xul
# to prevent having to worry about between versions of the browser
* content/browser/history/history-panel.xul (content/history-panel.xul)
content/browser/places/history-panel.js (content/history-panel.js)
# ditto for the bookmarks sidebar
content/browser/bookmarks/bookmarksPanel.xul (content/bookmarksPanel.xul)
content/browser/bookmarks/bookmarksPanel.js (content/bookmarksPanel.js)
* content/browser/bookmarks/sidebarUtils.js (content/sidebarUtils.js)
content/browser/places/moveBookmarks.xul (content/moveBookmarks.xul)
content/browser/places/moveBookmarks.js (content/moveBookmarks.js)
content/browser/places/editBookmarkOverlay.xul (content/editBookmarkOverlay.xul)
content/browser/places/editBookmarkOverlay.js (content/editBookmarkOverlay.js)
* content/browser/places/downloadsViewOverlay.xul (content/downloadsViewOverlay.xul)

Some files were not shown because too many files have changed in this diff Show more