mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-09-08 08:48:39 +09:00
Add Pale Moon
This commit is contained in:
parent
dcd9973243
commit
fe8028fa2e
1173 changed files with 143053 additions and 934 deletions
46
application/palemoon/components/BrowserComponents.manifest
Normal file
46
application/palemoon/components/BrowserComponents.manifest
Normal 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}
|
||||
188
application/palemoon/components/about/AboutRedirector.cpp
Normal file
188
application/palemoon/components/about/AboutRedirector.cpp
Normal 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
|
||||
32
application/palemoon/components/about/AboutRedirector.h
Normal file
32
application/palemoon/components/about/AboutRedirector.h
Normal 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__
|
||||
19
application/palemoon/components/about/moz.build
Normal file
19
application/palemoon/components/about/moz.build
Normal 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',
|
||||
]
|
||||
8
application/palemoon/components/build/Makefile.in
Normal file
8
application/palemoon/components/build/Makefile.in
Normal 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 =
|
||||
36
application/palemoon/components/build/moz.build
Normal file
36
application/palemoon/components/build/moz.build
Normal 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']
|
||||
43
application/palemoon/components/build/nsBrowserCompsCID.h
Normal file
43
application/palemoon/components/build/nsBrowserCompsCID.h
Normal 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 } }
|
||||
126
application/palemoon/components/build/nsModule.cpp
Normal file
126
application/palemoon/components/build/nsModule.cpp
Normal 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;
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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>
|
||||
7
application/palemoon/components/certerror/jar.mn
Normal file
7
application/palemoon/components/certerror/jar.mn
Normal 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)
|
||||
8
application/palemoon/components/certerror/moz.build
Normal file
8
application/palemoon/components/certerror/moz.build
Normal 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']
|
||||
|
|
@ -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
|
||||
|
|
@ -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__
|
||||
19
application/palemoon/components/dirprovider/moz.build
Normal file
19
application/palemoon/components/dirprovider/moz.build
Normal 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'
|
||||
]
|
||||
355
application/palemoon/components/distribution.js
Normal file
355
application/palemoon/components/distribution.js
Normal 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;
|
||||
}
|
||||
|
|
@ -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
|
||||
2401
application/palemoon/components/downloads/DownloadsCommon.jsm
Normal file
2401
application/palemoon/components/downloads/DownloadsCommon.jsm
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
278
application/palemoon/components/downloads/DownloadsStartup.js
Normal file
278
application/palemoon/components/downloads/DownloadsStartup.js
Normal 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]);
|
||||
177
application/palemoon/components/downloads/DownloadsTaskbar.jsm
Normal file
177
application/palemoon/components/downloads/DownloadsTaskbar.jsm
Normal 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);
|
||||
}
|
||||
},
|
||||
};
|
||||
151
application/palemoon/components/downloads/DownloadsUI.js
Normal file
151
application/palemoon/components/downloads/DownloadsUI.js
Normal 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]);
|
||||
|
|
@ -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
|
|
@ -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>
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -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>
|
||||
|
|
@ -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;
|
||||
}
|
||||
188
application/palemoon/components/downloads/content/download.xml
Normal file
188
application/palemoon/components/downloads/content/download.xml
Normal 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>
|
||||
132
application/palemoon/components/downloads/content/downloads.css
Normal file
132
application/palemoon/components/downloads/content/downloads.css
Normal 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;
|
||||
}
|
||||
1813
application/palemoon/components/downloads/content/downloads.js
Normal file
1813
application/palemoon/components/downloads/content/downloads.js
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -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>
|
||||
594
application/palemoon/components/downloads/content/indicator.js
Normal file
594
application/palemoon/components/downloads/content/indicator.js
Normal 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");
|
||||
}
|
||||
};
|
||||
|
|
@ -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>
|
||||
18
application/palemoon/components/downloads/jar.mn
Normal file
18
application/palemoon/components/downloads/jar.mn
Normal 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)
|
||||
19
application/palemoon/components/downloads/moz.build
Normal file
19
application/palemoon/components/downloads/moz.build
Normal 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',
|
||||
]
|
||||
28
application/palemoon/components/feeds/BrowserFeeds.manifest
Normal file
28
application/palemoon/components/feeds/BrowserFeeds.manifest
Normal 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}
|
||||
591
application/palemoon/components/feeds/FeedConverter.js
Normal file
591
application/palemoon/components/feeds/FeedConverter.js
Normal 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);
|
||||
1421
application/palemoon/components/feeds/FeedWriter.js
Normal file
1421
application/palemoon/components/feeds/FeedWriter.js
Normal file
File diff suppressed because it is too large
Load diff
932
application/palemoon/components/feeds/WebContentConverter.js
Normal file
932
application/palemoon/components/feeds/WebContentConverter.js
Normal 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]);
|
||||
|
|
@ -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);
|
||||
}
|
||||
23
application/palemoon/components/feeds/content/subscribe.js
Normal file
23
application/palemoon/components/feeds/content/subscribe.js
Normal 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();
|
||||
}
|
||||
};
|
||||
|
|
@ -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>
|
||||
40
application/palemoon/components/feeds/content/subscribe.xml
Normal file
40
application/palemoon/components/feeds/content/subscribe.xml
Normal 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>
|
||||
|
||||
9
application/palemoon/components/feeds/jar.mn
Normal file
9
application/palemoon/components/feeds/jar.mn
Normal 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)
|
||||
39
application/palemoon/components/feeds/moz.build
Normal file
39
application/palemoon/components/feeds/moz.build
Normal 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
|
||||
363
application/palemoon/components/feeds/nsFeedSniffer.cpp
Normal file
363
application/palemoon/components/feeds/nsFeedSniffer.cpp
Normal 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;
|
||||
}
|
||||
37
application/palemoon/components/feeds/nsFeedSniffer.h
Normal file
37
application/palemoon/components/feeds/nsFeedSniffer.h
Normal 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;
|
||||
};
|
||||
|
||||
|
|
@ -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);
|
||||
};
|
||||
|
|
@ -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);
|
||||
};
|
||||
|
||||
818
application/palemoon/components/fuel/fuelApplication.js
Normal file
818
application/palemoon/components/fuel/fuelApplication.js
Normal 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]);
|
||||
|
||||
|
|
@ -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
|
||||
347
application/palemoon/components/fuel/fuelIApplication.idl
Normal file
347
application/palemoon/components/fuel/fuelIApplication.idl
Normal 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;
|
||||
};
|
||||
20
application/palemoon/components/fuel/moz.build
Normal file
20
application/palemoon/components/fuel/moz.build
Normal 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',
|
||||
]
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -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]);
|
||||
|
|
@ -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]);
|
||||
521
application/palemoon/components/migration/IEProfileMigrator.js
Normal file
521
application/palemoon/components/migration/IEProfileMigrator.js
Normal 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]);
|
||||
644
application/palemoon/components/migration/MigrationUtils.jsm
Normal file
644
application/palemoon/components/migration/MigrationUtils.jsm
Normal 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;
|
||||
}
|
||||
});
|
||||
21
application/palemoon/components/migration/ProfileMigrator.js
Normal file
21
application/palemoon/components/migration/ProfileMigrator.js
Normal 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]);
|
||||
|
|
@ -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]);
|
||||
474
application/palemoon/components/migration/content/migration.js
Normal file
474
application/palemoon/components/migration/content/migration.js
Normal 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");
|
||||
}
|
||||
};
|
||||
|
|
@ -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>
|
||||
|
||||
7
application/palemoon/components/migration/jar.mn
Normal file
7
application/palemoon/components/migration/jar.mn
Normal 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)
|
||||
53
application/palemoon/components/migration/moz.build
Normal file
53
application/palemoon/components/migration/moz.build
Normal 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'
|
||||
|
||||
|
|
@ -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;
|
||||
};
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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
|
||||
44
application/palemoon/components/moz.build
Normal file
44
application/palemoon/components/moz.build
Normal 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',
|
||||
]
|
||||
812
application/palemoon/components/nsBrowserContentHandler.js
Normal file
812
application/palemoon/components/nsBrowserContentHandler.js
Normal 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);
|
||||
1961
application/palemoon/components/nsBrowserGlue.js
Normal file
1961
application/palemoon/components/nsBrowserGlue.js
Normal file
File diff suppressed because it is too large
Load diff
47
application/palemoon/components/nsIBrowserGlue.idl
Normal file
47
application/palemoon/components/nsIBrowserGlue.idl
Normal 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();
|
||||
};
|
||||
20
application/palemoon/components/nsIBrowserHandler.idl
Normal file
20
application/palemoon/components/nsIBrowserHandler.idl
Normal 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);
|
||||
};
|
||||
|
|
@ -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}
|
||||
|
|
@ -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]);
|
||||
1337
application/palemoon/components/places/PlacesUIUtils.jsm
Normal file
1337
application/palemoon/components/places/PlacesUIUtils.jsm
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
|
@ -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>
|
||||
|
|
@ -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);
|
||||
|
|
@ -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>
|
||||
1744
application/palemoon/components/places/content/browserPlacesViews.js
Normal file
1744
application/palemoon/components/places/content/browserPlacesViews.js
Normal file
File diff suppressed because it is too large
Load diff
1895
application/palemoon/components/places/content/controller.js
Normal file
1895
application/palemoon/components/places/content/controller.js
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -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
|
|
@ -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>
|
||||
|
|
@ -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);
|
||||
|
|
@ -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>
|
||||
488
application/palemoon/components/places/content/menu.xml
Normal file
488
application/palemoon/components/places/content/menu.xml
Normal 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>
|
||||
|
|
@ -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");
|
||||
}
|
||||
};
|
||||
|
|
@ -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>
|
||||
|
|
@ -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;
|
||||
}
|
||||
16
application/palemoon/components/places/content/places.css
Normal file
16
application/palemoon/components/places/content/places.css
Normal 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");
|
||||
}
|
||||
1562
application/palemoon/components/places/content/places.js
Normal file
1562
application/palemoon/components/places/content/places.js
Normal file
File diff suppressed because it is too large
Load diff
478
application/palemoon/components/places/content/places.xul
Normal file
478
application/palemoon/components/places/content/places.xul
Normal 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="©Cmd.label;"
|
||||
key="key_copy"
|
||||
accesskey="©Cmd.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>
|
||||
248
application/palemoon/components/places/content/placesOverlay.xul
Normal file
248
application/palemoon/components/places/content/placesOverlay.xul
Normal 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="©Cmd.label;"
|
||||
closemenu="single"
|
||||
accesskey="©Cmd.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>
|
||||
108
application/palemoon/components/places/content/sidebarUtils.js
Normal file
108
application/palemoon/components/places/content/sidebarUtils.js
Normal 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);
|
||||
}
|
||||
}
|
||||
};
|
||||
789
application/palemoon/components/places/content/tree.xml
Normal file
789
application/palemoon/components/places/content/tree.xml
Normal 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>
|
||||
1765
application/palemoon/components/places/content/treeView.js
Normal file
1765
application/palemoon/components/places/content/treeView.js
Normal file
File diff suppressed because it is too large
Load diff
34
application/palemoon/components/places/jar.mn
Normal file
34
application/palemoon/components/places/jar.mn
Normal 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
Loading…
Add table
Add a link
Reference in a new issue