Replace application/basilisk with browser/
15
browser/.eslintrc.js
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
"use strict";
|
||||
|
||||
module.exports = {
|
||||
"extends": [
|
||||
"../toolkit/.eslintrc.js"
|
||||
],
|
||||
|
||||
"rules": {
|
||||
"no-unused-vars": ["error", {
|
||||
"vars": "local",
|
||||
"varsIgnorePattern": "^Cc|Ci|Cu|Cr|EXPORTED_SYMBOLS",
|
||||
"args": "none",
|
||||
}]
|
||||
}
|
||||
};
|
||||
13
browser/LICENSE
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
Please see the file ../platform/toolkit/content/license.html for the
|
||||
copyright licensing conditions attached to the platform codebase,
|
||||
including copies of the licenses concerned.
|
||||
|
||||
You are not granted rights or licenses to the trademarks of Moonchild
|
||||
Productions or any other party, including without limitation the
|
||||
Basilisk name or logo.
|
||||
|
||||
The Serpent logo in branding/unofficial is derived from "Sea Serpent"
|
||||
by Lorc, licensed under the Creative Commons license CC-BY 3.0
|
||||
|
||||
For individual licensing restrictions on branding, see the LICENSE
|
||||
files in the appropriate branding directories.
|
||||
13
browser/Makefile.in
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
# 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
|
||||
|
||||
ifdef MAKENSISU
|
||||
|
||||
# For Windows build the uninstaller during the application build since the
|
||||
# uninstaller is included with the application for mar file generation.
|
||||
libs::
|
||||
$(MAKE) -C installer/windows uninstaller
|
||||
endif
|
||||
1
browser/app-rules.mk
Normal file
|
|
@ -0,0 +1 @@
|
|||
PURGECACHES_DIRS = $(DIST)/bin/browser
|
||||
14
browser/app.mozbuild
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
# 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('/toolkit/toolkit.mozbuild')
|
||||
|
||||
if CONFIG['MOZ_EXTENSIONS']:
|
||||
DIRS += ['/extensions']
|
||||
|
||||
DIRS += ['/%s' % CONFIG['MOZ_BRANDING_DIRECTORY']]
|
||||
|
||||
# Never add dirs after the browser root because they apparently won't
|
||||
# get packaged properly on Mac.
|
||||
DIRS += ['/browser']
|
||||
52
browser/app/Makefile.in
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
# 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/.
|
||||
|
||||
dist_dest = $(DIST)/$(MOZ_MACBUNDLE_NAME)
|
||||
|
||||
# hardcode en-US for the moment
|
||||
AB_CD = en-US
|
||||
|
||||
# Build a binary bootstrapping with XRE_main
|
||||
|
||||
ifndef MOZ_WINCONSOLE
|
||||
ifneq (,$(MOZ_DEBUG)$(MOZ_ASAN))
|
||||
MOZ_WINCONSOLE = 1
|
||||
else
|
||||
MOZ_WINCONSOLE = 0
|
||||
endif
|
||||
endif
|
||||
|
||||
# This switches $(INSTALL) to copy mode, like $(SYSINSTALL), so things that
|
||||
# shouldn't get 755 perms need $(IFLAGS1) for either way of calling nsinstall.
|
||||
NSDISTMODE = copy
|
||||
|
||||
include $(topsrcdir)/config/config.mk
|
||||
|
||||
ifeq ($(OS_ARCH),WINNT)
|
||||
# Rebuild basilisk.exe if the manifest changes - it's included by splash.rc.
|
||||
# (this dependency should really be just for basilisk.exe, not other targets)
|
||||
# Note the manifest file exists in the tree, so we use the explicit filename
|
||||
# here.
|
||||
EXTRA_DEPS += $(PROGRAM).manifest
|
||||
endif
|
||||
|
||||
PROGRAMS_DEST = $(DIST)/bin
|
||||
|
||||
include $(topsrcdir)/config/rules.mk
|
||||
|
||||
ifneq (,$(filter-out WINNT,$(OS_ARCH)))
|
||||
|
||||
ifdef COMPILE_ENVIRONMENT
|
||||
libs::
|
||||
cp -p $(MOZ_APP_NAME)$(BIN_SUFFIX) $(DIST)/bin/$(MOZ_APP_NAME)-bin$(BIN_SUFFIX)
|
||||
endif
|
||||
|
||||
GARBAGE += $(addprefix $(FINAL_TARGET)/defaults/pref/, basilisk.js)
|
||||
|
||||
endif
|
||||
|
||||
# channel-prefs.js is handled separate from other prefs due to bug 756325
|
||||
libs:: $(srcdir)/profile/channel-prefs.js
|
||||
$(NSINSTALL) -D $(DIST)/bin/defaults/pref
|
||||
$(call py_action,preprocessor,-Fsubstitution $(PREF_PPFLAGS) $(ACDEFINES) $^ -o $(DIST)/bin/defaults/pref/channel-prefs.js)
|
||||
48
browser/app/application.ini
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
#if MOZ_APP_STATIC_INI
|
||||
#ifdef MOZ_BUILD_APP_IS_BROWSER
|
||||
; This file is not used. If you modify it and want the application to use
|
||||
; your modifications, move it under the browser/ subdirectory and start with
|
||||
; the "-app /path/to/browser/application.ini" argument.
|
||||
#else
|
||||
; This file is not used. If you modify it and want the application to use
|
||||
; your modifications, start with the "-app /path/to/application.ini"
|
||||
; argument.
|
||||
#endif
|
||||
#endif
|
||||
#if 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/.
|
||||
#endif
|
||||
#filter substitution
|
||||
#include @TOPOBJDIR@/buildid.h
|
||||
#include @TOPOBJDIR@/source-repo.h
|
||||
|
||||
[App]
|
||||
Vendor=@MOZ_APP_VENDOR@
|
||||
Name=@MOZ_APP_BASENAME@
|
||||
RemotingName=@MOZ_APP_REMOTINGNAME@
|
||||
#ifdef MOZ_APP_DISPLAYNAME
|
||||
CodeName=@MOZ_APP_DISPLAYNAME@
|
||||
#endif
|
||||
Version=52.9.0
|
||||
#ifdef MOZ_APP_PROFILE
|
||||
Profile=@MOZ_APP_PROFILE@
|
||||
#endif
|
||||
BuildID=@MOZ_BUILDID@
|
||||
#ifdef MOZ_SOURCE_REPO
|
||||
SourceRepository=@MOZ_SOURCE_REPO@
|
||||
#endif
|
||||
#ifdef MOZ_SOURCE_STAMP
|
||||
SourceStamp=@MOZ_SOURCE_STAMP@
|
||||
#endif
|
||||
ID=@MOZ_APP_ID@
|
||||
|
||||
[Gecko]
|
||||
MinVersion=@GRE_MILESTONE@
|
||||
MaxVersion=@GRE_MILESTONE@
|
||||
|
||||
[XRE]
|
||||
#ifdef MOZ_PROFILE_MIGRATOR
|
||||
EnableProfileMigrator=1
|
||||
#endif
|
||||
3610
browser/app/blocklist.xml
Normal file
44
browser/app/dactyloidae.exe.manifest
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||
<assemblyIdentity
|
||||
version="1.0.0.0"
|
||||
processorArchitecture="*"
|
||||
name="Eclipse Hydra"
|
||||
type="win32"
|
||||
/>
|
||||
<description>Eclipse Hydra</description>
|
||||
<dependency>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity
|
||||
type="win32"
|
||||
name="Microsoft.Windows.Common-Controls"
|
||||
version="6.0.0.0"
|
||||
processorArchitecture="*"
|
||||
publicKeyToken="6595b64144ccf1df"
|
||||
language="*"
|
||||
/>
|
||||
</dependentAssembly>
|
||||
</dependency>
|
||||
<ms_asmv3:trustInfo xmlns:ms_asmv3="urn:schemas-microsoft-com:asm.v3">
|
||||
<ms_asmv3:security>
|
||||
<ms_asmv3:requestedPrivileges>
|
||||
<ms_asmv3:requestedExecutionLevel level="asInvoker" uiAccess="false" />
|
||||
</ms_asmv3:requestedPrivileges>
|
||||
</ms_asmv3:security>
|
||||
</ms_asmv3:trustInfo>
|
||||
<ms_asmv3:application xmlns:ms_asmv3="urn:schemas-microsoft-com:asm.v3">
|
||||
<ms_asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
|
||||
<dpiAware>True/PM</dpiAware>
|
||||
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2,PerMonitor</dpiAwareness>
|
||||
</ms_asmv3:windowsSettings>
|
||||
</ms_asmv3:application>
|
||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
<application>
|
||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
|
||||
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
|
||||
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
|
||||
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
|
||||
<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
|
||||
</application>
|
||||
</compatibility>
|
||||
</assembly>
|
||||
242
browser/app/macbuild/Contents/Info.plist.in
Normal file
|
|
@ -0,0 +1,242 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>English</string>
|
||||
<key>CFBundleDocumentTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>html</string>
|
||||
<string>htm</string>
|
||||
<string>shtml</string>
|
||||
<string>xht</string>
|
||||
<string>xhtml</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>document.icns</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>HTML Document</string>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>HTML</string>
|
||||
</array>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>svg</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>document.icns</string>
|
||||
<key>CFBundleTypeMIMETypes</key>
|
||||
<array>
|
||||
<string>image/svg+xml</string>
|
||||
</array>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>SVG document</string>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>TEXT</string>
|
||||
</array>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>BrowserDocument</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>text</string>
|
||||
<string>txt</string>
|
||||
<string>js</string>
|
||||
<string>log</string>
|
||||
<string>css</string>
|
||||
<string>xul</string>
|
||||
<string>rdf</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>document.icns</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Text Document</string>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>TEXT</string>
|
||||
<string>utxt</string>
|
||||
</array>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>jpeg</string>
|
||||
<string>jpg</string>
|
||||
<string>png</string>
|
||||
<string>gif</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>fileBookmark.icns</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>document.icns</string>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>GIFf</string>
|
||||
<string>JPEG</string>
|
||||
<string>PNGf</string>
|
||||
</array>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>oga</string>
|
||||
<string>ogg</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>document.icns</string>
|
||||
<key>CFBundleTypeMIMETypes</key>
|
||||
<array>
|
||||
<string>audio/ogg</string>
|
||||
</array>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>HTML5 Audio (Ogg)</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>ogv</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>document.icns</string>
|
||||
<key>CFBundleTypeMIMETypes</key>
|
||||
<array>
|
||||
<string>video/ogg</string>
|
||||
</array>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>HTML5 Video (Ogg)</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>webm</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>document.icns</string>
|
||||
<key>CFBundleTypeMIMETypes</key>
|
||||
<array>
|
||||
<string>video/webm</string>
|
||||
</array>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>HTML5 Video (WebM)</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>basilisk</string>
|
||||
<key>CFBundleGetInfoString</key>
|
||||
<string>%MAC_APP_NAME% %APP_VERSION%</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>firefox.icns</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>%MOZ_MACBUNDLE_ID%</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>%MAC_APP_NAME%</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>ATSApplicationFontsPath</key>
|
||||
<string>fonts</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>%APP_VERSION%</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>MOZB</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleURLIconFile</key>
|
||||
<string>document.icns</string>
|
||||
<key>CFBundleURLName</key>
|
||||
<string>http URL</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>http</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleURLIconFile</key>
|
||||
<string>document.icns</string>
|
||||
<key>CFBundleURLName</key>
|
||||
<string>https URL</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>https</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleURLName</key>
|
||||
<string>ftp URL</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>ftp</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleURLName</key>
|
||||
<string>file URL</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>file</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>%MAC_BUNDLE_VERSION%</string>
|
||||
<key>NSAppleScriptEnabled</key>
|
||||
<true/>
|
||||
<key>LSApplicationCategoryType</key>
|
||||
<string>public.app-category.productivity</string>
|
||||
<key>LSEnvironment</key>
|
||||
<dict>
|
||||
<key>MallocNanoZone</key>
|
||||
<string>0</string>
|
||||
</dict>
|
||||
<key>LSFileQuarantineEnabled</key>
|
||||
<true/>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>10.5.0</string>
|
||||
<key>LSMinimumSystemVersionByArchitecture</key>
|
||||
<dict>
|
||||
<key>i386</key>
|
||||
<string>10.5.0</string>
|
||||
<key>x86_64</key>
|
||||
<string>10.7.0</string>
|
||||
<key>ppc</key>
|
||||
<string>10.5.0</string>
|
||||
<key>arm64</key>
|
||||
<string>11.0.0</string>
|
||||
</dict>
|
||||
<key>NSSupportsAutomaticGraphicsSwitching</key>
|
||||
<true/>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string>GeckoNSApplication</string>
|
||||
<key>SMPrivilegedExecutables</key>
|
||||
<dict>
|
||||
<key>org.mozilla.updater</key>
|
||||
<string>identifier "org.mozilla.updater" and ((anchor apple generic and certificate leaf[field.1.2.840.113635.100.6.1.9]) or (anchor apple generic and certificate 1[field.1.2.840.113635.100.6.2.6] and certificate leaf[field.1.2.840.113635.100.6.1.13] and certificate leaf[subject.OU] = "43AQ936H96"))</string>
|
||||
</dict>
|
||||
<key>NSDisablePersistence</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
11
browser/app/macbuild/Contents/MacOS-files-copy.in
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
# Specifies files that should be copied (via deep copy, resolving symlinks)
|
||||
# from dist/bin to the .app/Contents/MacOS directory. Linking is preferred to
|
||||
# reduce disk I/O during builds, so just include dylibs which need to be in the
|
||||
# same directory as returned by dladdr(3).
|
||||
#
|
||||
# Some of these dylibs load other dylibs which are assumed to be siblings in
|
||||
# the same directory obtained from dladdr(3). With macOS 10.15, dladdr returns
|
||||
# absolute resolved paths which breaks this assumption if symlinks are used
|
||||
# because the symlink targets are in different directories. Hence the need for
|
||||
# them to be copied to the same directory.
|
||||
/*.dylib
|
||||
8
browser/app/macbuild/Contents/MacOS-files.in
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
/*.app/***
|
||||
/certutil
|
||||
/firefox-bin
|
||||
/gtest/***
|
||||
/pk12util
|
||||
/ssltunnel
|
||||
/xpcshell
|
||||
/XUL
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
/* 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/. */
|
||||
|
||||
CFBundleName = "%MAC_APP_NAME%";
|
||||
44
browser/app/macversion.py
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
#!/usr/bin/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/.
|
||||
|
||||
|
||||
from optparse import OptionParser
|
||||
import sys
|
||||
import re
|
||||
|
||||
o = OptionParser()
|
||||
o.add_option("--buildid", dest="buildid")
|
||||
o.add_option("--version", dest="version")
|
||||
|
||||
(options, args) = o.parse_args()
|
||||
|
||||
if not options.buildid:
|
||||
print >>sys.stderr, "--buildid is required"
|
||||
sys.exit(1)
|
||||
|
||||
if not options.version:
|
||||
print >>sys.stderr, "--version is required"
|
||||
sys.exit(1)
|
||||
|
||||
# We want to build a version number that matches the format allowed for
|
||||
# CFBundleVersion (nnnnn[.nn[.nn]]). We'll incorporate both the version
|
||||
# number as well as the date, so that it changes at least daily (for nightly
|
||||
# builds), but also so that newly-built older versions (e.g. beta build) aren't
|
||||
# considered "newer" than previously-built newer versions (e.g. a trunk nightly)
|
||||
|
||||
define, MOZ_BUILDID, buildid = open(options.buildid, 'r').read().split()
|
||||
|
||||
# extract only the major version (i.e. "14" from "14.0b1")
|
||||
majorVersion = re.match(r'^(\d+)[^\d].*', options.version).group(1)
|
||||
# last two digits of the year
|
||||
twodigityear = buildid[2:4]
|
||||
month = buildid[4:6]
|
||||
if month[0] == '0':
|
||||
month = month[1]
|
||||
day = buildid[6:8]
|
||||
if day[0] == '0':
|
||||
day = day[1]
|
||||
|
||||
print '%s.%s.%s' % (majorVersion + twodigityear, month, day)
|
||||
8
browser/app/module.ver
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
WIN32_MODULE_COMPANYNAME=@MOZ_APP_VENDOR@
|
||||
WIN32_MODULE_COPYRIGHT=©Eclipse Community, Basilisk and Mozilla Developers; available under the MPL 2 license.
|
||||
WIN32_MODULE_PRODUCTVERSION=@MOZ_APP_WINVERSION@
|
||||
WIN32_MODULE_PRODUCTVERSION_STRING=@MOZ_APP_VERSION@
|
||||
WIN32_MODULE_TRADEMARKS=The Hydra logo and project names are trademarks of Eclipse Community.
|
||||
WIN32_MODULE_DESCRIPTION=@MOZ_APP_DISPLAYNAME@ web browser
|
||||
WIN32_MODULE_PRODUCTNAME=@MOZ_APP_DISPLAYNAME@
|
||||
WIN32_MODULE_NAME=@MOZ_APP_DISPLAYNAME@
|
||||
74
browser/app/moz.build
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# 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 += ['profile/extensions']
|
||||
|
||||
GeckoProgram(CONFIG['MOZ_APP_NAME'])
|
||||
|
||||
JS_PREFERENCE_PP_FILES += [
|
||||
'profile/basilisk.js',
|
||||
]
|
||||
|
||||
SOURCES += [
|
||||
'nsBrowserApp.cpp',
|
||||
]
|
||||
|
||||
FINAL_TARGET_FILES += ['blocklist.xml']
|
||||
FINAL_TARGET_FILES.defaults.profile += ['profile/prefs.js']
|
||||
FINAL_TARGET_FILES.defaults += ['permissions']
|
||||
|
||||
DEFINES['APP_VERSION'] = CONFIG['MOZ_APP_VERSION']
|
||||
|
||||
LOCAL_INCLUDES += [
|
||||
'!/build',
|
||||
'/toolkit/xre',
|
||||
'/xpcom/base',
|
||||
'/xpcom/build',
|
||||
]
|
||||
|
||||
USE_LIBS += [
|
||||
'mozglue',
|
||||
]
|
||||
|
||||
if CONFIG['LIBFUZZER']:
|
||||
USE_LIBS += [ 'fuzzer' ]
|
||||
|
||||
if CONFIG['_MSC_VER']:
|
||||
# Always enter a Windows program through wmain, whether or not we're
|
||||
# a console application.
|
||||
WIN32_EXE_LDFLAGS += ['-ENTRY:wmainCRTStartup']
|
||||
|
||||
if CONFIG['OS_ARCH'] == 'WINNT':
|
||||
RCINCLUDE = 'splash.rc'
|
||||
DEFINES['MOZ_PHOENIX'] = True
|
||||
|
||||
for cdm in CONFIG['MOZ_EME_MODULES']:
|
||||
DEFINES['MOZ_%s_EME' % cdm.upper()] = True
|
||||
|
||||
# Control the default heap size.
|
||||
# This is the heap returned by GetProcessHeap().
|
||||
# As we use the CRT heap, the default size is too large and wastes VM.
|
||||
#
|
||||
# The default heap size is 1MB on Win32.
|
||||
# The heap will grow if need be.
|
||||
#
|
||||
# Set it to 256k. See bug 127069.
|
||||
if CONFIG['OS_ARCH'] == 'WINNT' and not CONFIG['GNU_CC']:
|
||||
LDFLAGS += ['/HEAP:0x40000']
|
||||
|
||||
DISABLE_STL_WRAPPING = True
|
||||
|
||||
if CONFIG['MOZ_LINKER']:
|
||||
OS_LIBS += CONFIG['MOZ_ZLIB_LIBS']
|
||||
|
||||
if CONFIG['HAVE_CLOCK_MONOTONIC']:
|
||||
OS_LIBS += CONFIG['REALTIME_LIBS']
|
||||
|
||||
if CONFIG['MOZ_GPSD']:
|
||||
DEFINES['MOZ_GPSD'] = True
|
||||
|
||||
for icon in ('firefox', 'document', 'newwindow', 'newtab', 'pbmode'):
|
||||
DEFINES[icon.upper() + '_ICO'] = '"%s/dist/branding/%s.ico"' % (
|
||||
TOPOBJDIR, icon)
|
||||
393
browser/app/nsBrowserApp.cpp
Normal file
|
|
@ -0,0 +1,393 @@
|
|||
/* -*- 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 "nsXULAppAPI.h"
|
||||
#include "mozilla/AppData.h"
|
||||
#include "application.ini.h"
|
||||
#include "nsXPCOMGlue.h"
|
||||
#include <stdlib.h>
|
||||
#if defined(XP_WIN)
|
||||
#include <windows.h>
|
||||
#elif defined(XP_UNIX)
|
||||
#include <sys/resource.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdarg.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsIFile.h"
|
||||
#include "nsStringGlue.h"
|
||||
|
||||
#ifdef XP_WIN
|
||||
#ifdef MOZ_ASAN
|
||||
// ASAN requires basilisk.exe to be built with -MD, and it's OK if we don't
|
||||
// support Windows XP SP2 in ASAN builds.
|
||||
#define XRE_DONT_SUPPORT_XPSP2
|
||||
#endif
|
||||
#define XRE_WANT_ENVIRON
|
||||
#define strcasecmp _stricmp
|
||||
#endif
|
||||
#include "BinaryPath.h"
|
||||
|
||||
#include "nsXPCOMPrivate.h" // for MAXPATHLEN and XPCOM_DLL
|
||||
|
||||
#include "mozilla/Sprintf.h"
|
||||
#include "mozilla/WindowsDllBlocklist.h"
|
||||
|
||||
#if !defined(MOZ_WIDGET_COCOA) && !defined(MOZ_WIDGET_ANDROID)
|
||||
#define MOZ_BROWSER_CAN_BE_CONTENTPROC
|
||||
#include "../../ipc/contentproc/plugin-container.cpp"
|
||||
#endif
|
||||
|
||||
using namespace mozilla;
|
||||
|
||||
#ifdef XP_MACOSX
|
||||
#define kOSXResourcesFolder "Resources"
|
||||
#endif
|
||||
#define kDesktopFolder "browser"
|
||||
|
||||
static void Output(const char *fmt, ... )
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
|
||||
#ifndef XP_WIN
|
||||
vfprintf(stderr, fmt, ap);
|
||||
#else
|
||||
char msg[2048];
|
||||
vsnprintf_s(msg, _countof(msg), _TRUNCATE, fmt, ap);
|
||||
|
||||
wchar_t wide_msg[2048];
|
||||
MultiByteToWideChar(CP_UTF8,
|
||||
0,
|
||||
msg,
|
||||
-1,
|
||||
wide_msg,
|
||||
_countof(wide_msg));
|
||||
#if MOZ_WINCONSOLE
|
||||
fwprintf_s(stderr, wide_msg);
|
||||
#else
|
||||
// Linking user32 at load-time interferes with the DLL blocklist (bug 932100).
|
||||
// This is a rare codepath, so we can load user32 at run-time instead.
|
||||
HMODULE user32 = LoadLibraryW(L"user32.dll");
|
||||
if (user32) {
|
||||
decltype(MessageBoxW)* messageBoxW =
|
||||
(decltype(MessageBoxW)*) GetProcAddress(user32, "MessageBoxW");
|
||||
if (messageBoxW) {
|
||||
messageBoxW(nullptr, wide_msg, L"XULRunner", MB_OK
|
||||
| MB_ICONERROR
|
||||
| MB_SETFOREGROUND);
|
||||
}
|
||||
FreeLibrary(user32);
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if |arg| matches the given argument name.
|
||||
*/
|
||||
static bool IsArg(const char* arg, const char* s)
|
||||
{
|
||||
if (*arg == '-')
|
||||
{
|
||||
if (*++arg == '-')
|
||||
++arg;
|
||||
return !strcasecmp(arg, s);
|
||||
}
|
||||
|
||||
#if defined(XP_WIN)
|
||||
if (*arg == '/')
|
||||
return !strcasecmp(++arg, s);
|
||||
#endif
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
XRE_GetFileFromPathType XRE_GetFileFromPath;
|
||||
XRE_CreateAppDataType XRE_CreateAppData;
|
||||
XRE_FreeAppDataType XRE_FreeAppData;
|
||||
XRE_StartupTimelineRecordType XRE_StartupTimelineRecord;
|
||||
XRE_mainType XRE_main;
|
||||
XRE_StopLateWriteChecksType XRE_StopLateWriteChecks;
|
||||
XRE_XPCShellMainType XRE_XPCShellMain;
|
||||
XRE_GetProcessTypeType XRE_GetProcessType;
|
||||
XRE_SetProcessTypeType XRE_SetProcessType;
|
||||
XRE_InitChildProcessType XRE_InitChildProcess;
|
||||
XRE_EnableSameExecutableForContentProcType XRE_EnableSameExecutableForContentProc;
|
||||
#ifdef LIBFUZZER
|
||||
XRE_LibFuzzerSetMainType XRE_LibFuzzerSetMain;
|
||||
XRE_LibFuzzerGetFuncsType XRE_LibFuzzerGetFuncs;
|
||||
#endif
|
||||
|
||||
static const nsDynamicFunctionLoad kXULFuncs[] = {
|
||||
{ "XRE_GetFileFromPath", (NSFuncPtr*) &XRE_GetFileFromPath },
|
||||
{ "XRE_CreateAppData", (NSFuncPtr*) &XRE_CreateAppData },
|
||||
{ "XRE_FreeAppData", (NSFuncPtr*) &XRE_FreeAppData },
|
||||
{ "XRE_StartupTimelineRecord", (NSFuncPtr*) &XRE_StartupTimelineRecord },
|
||||
{ "XRE_main", (NSFuncPtr*) &XRE_main },
|
||||
{ "XRE_StopLateWriteChecks", (NSFuncPtr*) &XRE_StopLateWriteChecks },
|
||||
{ "XRE_XPCShellMain", (NSFuncPtr*) &XRE_XPCShellMain },
|
||||
{ "XRE_GetProcessType", (NSFuncPtr*) &XRE_GetProcessType },
|
||||
{ "XRE_SetProcessType", (NSFuncPtr*) &XRE_SetProcessType },
|
||||
{ "XRE_InitChildProcess", (NSFuncPtr*) &XRE_InitChildProcess },
|
||||
{ "XRE_EnableSameExecutableForContentProc", (NSFuncPtr*) &XRE_EnableSameExecutableForContentProc },
|
||||
#ifdef LIBFUZZER
|
||||
{ "XRE_LibFuzzerSetMain", (NSFuncPtr*) &XRE_LibFuzzerSetMain },
|
||||
{ "XRE_LibFuzzerGetFuncs", (NSFuncPtr*) &XRE_LibFuzzerGetFuncs },
|
||||
#endif
|
||||
{ nullptr, nullptr }
|
||||
};
|
||||
|
||||
#ifdef LIBFUZZER
|
||||
int libfuzzer_main(int argc, char **argv);
|
||||
|
||||
/* This wrapper is used by the libFuzzer main to call into libxul */
|
||||
|
||||
void libFuzzerGetFuncs(const char* moduleName, LibFuzzerInitFunc* initFunc,
|
||||
LibFuzzerTestingFunc* testingFunc) {
|
||||
return XRE_LibFuzzerGetFuncs(moduleName, initFunc, testingFunc);
|
||||
}
|
||||
#endif
|
||||
|
||||
static int do_main(int argc, char* argv[], char* envp[], nsIFile *xreDirectory)
|
||||
{
|
||||
nsCOMPtr<nsIFile> appini;
|
||||
nsresult rv;
|
||||
uint32_t mainFlags = 0;
|
||||
|
||||
// Allow basilisk.exe to launch XULRunner apps via -app <application.ini>
|
||||
// Note that -app must be the *first* argument.
|
||||
const char *appDataFile = getenv("XUL_APP_FILE");
|
||||
if (appDataFile && *appDataFile) {
|
||||
rv = XRE_GetFileFromPath(appDataFile, getter_AddRefs(appini));
|
||||
if (NS_FAILED(rv)) {
|
||||
Output("Invalid path found: '%s'", appDataFile);
|
||||
return 255;
|
||||
}
|
||||
}
|
||||
else if (argc > 1 && IsArg(argv[1], "app")) {
|
||||
if (argc == 2) {
|
||||
Output("Incorrect number of arguments passed to -app");
|
||||
return 255;
|
||||
}
|
||||
|
||||
rv = XRE_GetFileFromPath(argv[2], getter_AddRefs(appini));
|
||||
if (NS_FAILED(rv)) {
|
||||
Output("application.ini path not recognized: '%s'", argv[2]);
|
||||
return 255;
|
||||
}
|
||||
|
||||
char appEnv[MAXPATHLEN];
|
||||
SprintfLiteral(appEnv, "XUL_APP_FILE=%s", argv[2]);
|
||||
if (putenv(strdup(appEnv))) {
|
||||
Output("Couldn't set %s.\n", appEnv);
|
||||
return 255;
|
||||
}
|
||||
argv[2] = argv[0];
|
||||
argv += 2;
|
||||
argc -= 2;
|
||||
} else if (argc > 1 && IsArg(argv[1], "xpcshell")) {
|
||||
for (int i = 1; i < argc; i++) {
|
||||
argv[i] = argv[i + 1];
|
||||
}
|
||||
|
||||
return XRE_XPCShellMain(--argc, argv, envp);
|
||||
}
|
||||
|
||||
if (appini) {
|
||||
nsXREAppData *appData;
|
||||
rv = XRE_CreateAppData(appini, &appData);
|
||||
if (NS_FAILED(rv)) {
|
||||
Output("Couldn't read application.ini");
|
||||
return 255;
|
||||
}
|
||||
#if defined(HAS_DLL_BLOCKLIST)
|
||||
// The dll blocklist operates in the exe vs. xullib. Pass a flag to
|
||||
// xullib so automated tests can check the result once the browser
|
||||
// is up and running.
|
||||
appData->flags |=
|
||||
DllBlocklist_CheckStatus() ? NS_XRE_DLL_BLOCKLIST_ENABLED : 0;
|
||||
#endif
|
||||
// xreDirectory already has a refcount from NS_NewLocalFile
|
||||
appData->xreDirectory = xreDirectory;
|
||||
int result = XRE_main(argc, argv, appData, mainFlags);
|
||||
XRE_FreeAppData(appData);
|
||||
return result;
|
||||
}
|
||||
|
||||
ScopedAppData appData(&sAppData);
|
||||
nsCOMPtr<nsIFile> exeFile;
|
||||
rv = mozilla::BinaryPath::GetFile(argv[0], getter_AddRefs(exeFile));
|
||||
if (NS_FAILED(rv)) {
|
||||
Output("Couldn't find the application directory.\n");
|
||||
return 255;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIFile> greDir;
|
||||
exeFile->GetParent(getter_AddRefs(greDir));
|
||||
#ifdef XP_MACOSX
|
||||
greDir->SetNativeLeafName(NS_LITERAL_CSTRING(kOSXResourcesFolder));
|
||||
#endif
|
||||
nsCOMPtr<nsIFile> appSubdir;
|
||||
greDir->Clone(getter_AddRefs(appSubdir));
|
||||
appSubdir->Append(NS_LITERAL_STRING(kDesktopFolder));
|
||||
|
||||
SetStrongPtr(appData.directory, static_cast<nsIFile*>(appSubdir.get()));
|
||||
// xreDirectory already has a refcount from NS_NewLocalFile
|
||||
appData.xreDirectory = xreDirectory;
|
||||
|
||||
#if defined(HAS_DLL_BLOCKLIST)
|
||||
appData.flags |=
|
||||
DllBlocklist_CheckStatus() ? NS_XRE_DLL_BLOCKLIST_ENABLED : 0;
|
||||
#endif
|
||||
|
||||
#ifdef LIBFUZZER
|
||||
if (getenv("LIBFUZZER"))
|
||||
XRE_LibFuzzerSetMain(argc, argv, libfuzzer_main);
|
||||
#endif
|
||||
|
||||
return XRE_main(argc, argv, &appData, mainFlags);
|
||||
}
|
||||
|
||||
static bool
|
||||
FileExists(const char *path)
|
||||
{
|
||||
#ifdef XP_WIN
|
||||
wchar_t wideDir[MAX_PATH];
|
||||
MultiByteToWideChar(CP_UTF8, 0, path, -1, wideDir, MAX_PATH);
|
||||
DWORD fileAttrs = GetFileAttributesW(wideDir);
|
||||
return fileAttrs != INVALID_FILE_ATTRIBUTES;
|
||||
#else
|
||||
return access(path, R_OK) == 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
static nsresult
|
||||
InitXPCOMGlue(const char *argv0, nsIFile **xreDirectory)
|
||||
{
|
||||
char exePath[MAXPATHLEN];
|
||||
|
||||
nsresult rv = mozilla::BinaryPath::Get(argv0, exePath);
|
||||
if (NS_FAILED(rv)) {
|
||||
Output("Couldn't find the application directory.\n");
|
||||
return rv;
|
||||
}
|
||||
|
||||
char *lastSlash = strrchr(exePath, XPCOM_FILE_PATH_SEPARATOR[0]);
|
||||
if (!lastSlash ||
|
||||
(size_t(lastSlash - exePath) > MAXPATHLEN - sizeof(XPCOM_DLL) - 1))
|
||||
return NS_ERROR_FAILURE;
|
||||
|
||||
strcpy(lastSlash + 1, XPCOM_DLL);
|
||||
|
||||
if (!FileExists(exePath)) {
|
||||
Output("Could not find the Mozilla runtime.\n");
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
// We do this because of data in bug 771745
|
||||
XPCOMGlueEnablePreload();
|
||||
|
||||
rv = XPCOMGlueStartup(exePath);
|
||||
if (NS_FAILED(rv)) {
|
||||
Output("Couldn't load XPCOM.\n");
|
||||
return rv;
|
||||
}
|
||||
|
||||
rv = XPCOMGlueLoadXULFunctions(kXULFuncs);
|
||||
if (NS_FAILED(rv)) {
|
||||
Output("Couldn't load XRE functions.\n");
|
||||
return rv;
|
||||
}
|
||||
|
||||
// This will set this thread as the main thread.
|
||||
NS_LogInit();
|
||||
|
||||
if (xreDirectory) {
|
||||
// chop XPCOM_DLL off exePath
|
||||
*lastSlash = '\0';
|
||||
#ifdef XP_MACOSX
|
||||
lastSlash = strrchr(exePath, XPCOM_FILE_PATH_SEPARATOR[0]);
|
||||
strcpy(lastSlash + 1, kOSXResourcesFolder);
|
||||
#endif
|
||||
#ifdef XP_WIN
|
||||
rv = NS_NewLocalFile(NS_ConvertUTF8toUTF16(exePath), false,
|
||||
xreDirectory);
|
||||
#else
|
||||
rv = NS_NewNativeLocalFile(nsDependentCString(exePath), false,
|
||||
xreDirectory);
|
||||
#endif
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[], char* envp[])
|
||||
{
|
||||
mozilla::TimeStamp start = mozilla::TimeStamp::Now();
|
||||
|
||||
#ifdef HAS_DLL_BLOCKLIST
|
||||
DllBlocklist_Initialize();
|
||||
|
||||
#ifdef DEBUG
|
||||
// In order to be effective against AppInit DLLs, the blocklist must be
|
||||
// initialized before user32.dll is loaded into the process (bug 932100).
|
||||
if (GetModuleHandleA("user32.dll")) {
|
||||
fprintf(stderr, "DLL blocklist was unable to intercept AppInit DLLs.\n");
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef MOZ_BROWSER_CAN_BE_CONTENTPROC
|
||||
// We are launching as a content process, delegate to the appropriate
|
||||
// main
|
||||
if (argc > 1 && IsArg(argv[1], "contentproc")) {
|
||||
nsresult rv = InitXPCOMGlue(argv[0], nullptr);
|
||||
if (NS_FAILED(rv)) {
|
||||
return 255;
|
||||
}
|
||||
|
||||
int result = content_process_main(argc, argv);
|
||||
|
||||
// InitXPCOMGlue calls NS_LogInit, so we need to balance it here.
|
||||
NS_LogTerm();
|
||||
|
||||
return result;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
nsIFile *xreDirectory;
|
||||
|
||||
nsresult rv = InitXPCOMGlue(argv[0], &xreDirectory);
|
||||
if (NS_FAILED(rv)) {
|
||||
return 255;
|
||||
}
|
||||
|
||||
#ifdef MOZ_BROWSER_CAN_BE_CONTENTPROC
|
||||
XRE_EnableSameExecutableForContentProc();
|
||||
#endif
|
||||
|
||||
int result = do_main(argc, argv, envp, xreDirectory);
|
||||
|
||||
NS_LogTerm();
|
||||
|
||||
#ifdef XP_MACOSX
|
||||
// Allow writes again. While we would like to catch writes from static
|
||||
// destructors to allow early exits to use _exit, we know that there is
|
||||
// at least one such write that we don't control (see bug 826029). For
|
||||
// now we enable writes again and early exits will have to use exit instead
|
||||
// of _exit.
|
||||
XRE_StopLateWriteChecks();
|
||||
#endif
|
||||
|
||||
return result;
|
||||
}
|
||||
17
browser/app/permissions
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
# This file has default permissions for the permission manager.
|
||||
# The file-format is strict:
|
||||
# * matchtype \t type \t permission \t host
|
||||
# * "origin" should be used for matchtype, "host" is supported for legacy reasons
|
||||
# * type is a string that identifies the type of permission (e.g. "cookie")
|
||||
# * permission is an integer between 1 and 15
|
||||
# See nsPermissionManager.cpp for more...
|
||||
|
||||
# XPInstall
|
||||
origin install 1 http://addons.basilisk-browser.org
|
||||
origin install 1 https://addons.basilisk-browser.org
|
||||
origin install 1 https://addons.mozilla.org
|
||||
origin install 1 https://testpilot.firefox.com
|
||||
|
||||
# Remote troubleshooting
|
||||
origin remote-troubleshooting 1 https://input.mozilla.org
|
||||
origin remote-troubleshooting 1 https://support.mozilla.org
|
||||
1350
browser/app/profile/basilisk.js
Normal file
5
browser/app/profile/channel-prefs.js
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
/* 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/. */
|
||||
|
||||
pref("app.update.channel", "@MOZ_UPDATE_CHANNEL@");
|
||||
6
browser/app/profile/extensions/moz.build
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# 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 += ['{972ce4c6-7e08-4474-a285-3208198ce6fd}']
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
<?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/. -->
|
||||
|
||||
|
||||
#filter substitution
|
||||
|
||||
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
|
||||
|
||||
<Description about="urn:mozilla:install-manifest">
|
||||
<em:id>{972ce4c6-7e08-4474-a285-3208198ce6fd}</em:id>
|
||||
<em:version>@MOZ_APP_VERSION@</em:version>
|
||||
|
||||
<!-- Target Application this theme can install into,
|
||||
with minimum and maximum supported versions. -->
|
||||
<em:targetApplication>
|
||||
<Description>
|
||||
<em:id>@MOZ_APP_ID@</em:id>
|
||||
<em:minVersion>@MOZ_APP_VERSION@</em:minVersion>
|
||||
<em:maxVersion>@MOZ_APP_VERSION@</em:maxVersion>
|
||||
</Description>
|
||||
</em:targetApplication>
|
||||
|
||||
<!-- Front End MetaData -->
|
||||
<em:name>Default</em:name>
|
||||
<em:description>The default theme.</em:description>
|
||||
|
||||
<!-- Front End Integration Hooks (used by Theme Manager)-->
|
||||
<em:creator>Mozilla</em:creator>
|
||||
<em:contributor>Mozilla Contributors</em:contributor>
|
||||
|
||||
<!-- Allow lightweight themes to apply to this theme -->
|
||||
<em:skinnable>true</em:skinnable>
|
||||
|
||||
<em:internalName>classic/1.0</em:internalName>
|
||||
</Description>
|
||||
|
||||
</RDF>
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# 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/.
|
||||
|
||||
DEFINES['MOZ_APP_VERSION'] = CONFIG['MOZ_APP_VERSION']
|
||||
DEFINES['MOZ_APP_ID'] = CONFIG['MOZ_APP_ID']
|
||||
|
||||
FINAL_TARGET = 'dist/bin/browser/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}'
|
||||
|
||||
FINAL_TARGET_PP_FILES += [
|
||||
'install.rdf.in',
|
||||
]
|
||||
7
browser/app/profile/pagethemes.rdf
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0"?> <!-- -*- Mode: SGML -*- -->
|
||||
<!-- 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/. -->
|
||||
|
||||
<RDF:RDF xmlns:RDF="http://www.w3.org/1999/02/22-rdf-syntax-ns#"/>
|
||||
|
||||
13
browser/app/profile/prefs.js
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
/* 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/. */
|
||||
|
||||
# Mozilla User Preferences
|
||||
|
||||
/* Do not edit this file.
|
||||
*
|
||||
* If you make changes to this file while the browser is running,
|
||||
* the changes will be overwritten when the browser exits.
|
||||
*
|
||||
* To make a manual change to preferences, you can visit the URL about:config
|
||||
*/
|
||||
19
browser/app/splash.rc
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
/* -*- 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 <windows.h>
|
||||
#include "nsNativeAppSupportWin.h"
|
||||
|
||||
IDI_APPICON ICON FIREFOX_ICO
|
||||
IDI_DOCUMENT ICON DOCUMENT_ICO
|
||||
IDI_APPLICATION ICON FIREFOX_ICO
|
||||
IDI_NEWWINDOW ICON NEWWINDOW_ICO
|
||||
IDI_NEWTAB ICON NEWTAB_ICO
|
||||
IDI_PBMODE ICON PBMODE_ICO
|
||||
|
||||
STRINGTABLE DISCARDABLE
|
||||
BEGIN
|
||||
IDS_STARTMENU_APPNAME, "@MOZ_APP_DISPLAYNAME@"
|
||||
END
|
||||
11
browser/base/.eslintrc.js
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
"use strict";
|
||||
|
||||
module.exports = {
|
||||
"rules": {
|
||||
"no-unused-vars": ["error", {
|
||||
"vars": "local",
|
||||
"varsIgnorePattern": "^Cc|Ci|Cu|Cr|EXPORTED_SYMBOLS",
|
||||
"args": "none",
|
||||
}]
|
||||
}
|
||||
};
|
||||
428
browser/base/content/aboutDialog-appUpdater.js
Normal file
|
|
@ -0,0 +1,428 @@
|
|||
/* 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/. */
|
||||
|
||||
// Note: this file is included in aboutDialog.xul if MOZ_UPDATER is defined.
|
||||
|
||||
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Components.utils.import("resource://gre/modules/DownloadUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils",
|
||||
"resource://gre/modules/UpdateUtils.jsm");
|
||||
|
||||
const PREF_APP_UPDATE_CANCELATIONS_OSX = "app.update.cancelations.osx";
|
||||
const PREF_APP_UPDATE_ELEVATE_NEVER = "app.update.elevate.never";
|
||||
|
||||
var gAppUpdater;
|
||||
|
||||
function onUnload(aEvent) {
|
||||
if (gAppUpdater.isChecking)
|
||||
gAppUpdater.checker.stopChecking(Components.interfaces.nsIUpdateChecker.CURRENT_CHECK);
|
||||
// Safe to call even when there isn't a download in progress.
|
||||
gAppUpdater.removeDownloadListener();
|
||||
gAppUpdater = null;
|
||||
}
|
||||
|
||||
|
||||
function appUpdater()
|
||||
{
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "aus",
|
||||
"@mozilla.org/updates/update-service;1",
|
||||
"nsIApplicationUpdateService");
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "checker",
|
||||
"@mozilla.org/updates/update-checker;1",
|
||||
"nsIUpdateChecker");
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "um",
|
||||
"@mozilla.org/updates/update-manager;1",
|
||||
"nsIUpdateManager");
|
||||
|
||||
this.updateDeck = document.getElementById("updateDeck");
|
||||
|
||||
// Hide the update deck when the update window is already open and it's not
|
||||
// already applied, to avoid syncing issues between them. Applied updates
|
||||
// don't have any information to sync between the windows as they both just
|
||||
// show the "Restart to continue"-type button.
|
||||
if (Services.wm.getMostRecentWindow("Update:Wizard") &&
|
||||
!this.isApplied) {
|
||||
this.updateDeck.hidden = true;
|
||||
return;
|
||||
}
|
||||
|
||||
this.bundle = Services.strings.
|
||||
createBundle("chrome://browser/locale/browser.properties");
|
||||
|
||||
let manualURL = Services.urlFormatter.formatURLPref("app.update.url.manual");
|
||||
let manualLink = document.getElementById("manualLink");
|
||||
manualLink.value = manualURL;
|
||||
manualLink.href = manualURL;
|
||||
document.getElementById("failedLink").href = manualURL;
|
||||
|
||||
if (this.updateDisabledAndLocked) {
|
||||
this.selectPanel("adminDisabled");
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.isPending || this.isApplied) {
|
||||
this.selectPanel("apply");
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.aus.isOtherInstanceHandlingUpdates) {
|
||||
this.selectPanel("otherInstanceHandlingUpdates");
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.isDownloading) {
|
||||
this.startDownload();
|
||||
// selectPanel("downloading") is called from setupDownloadingUI().
|
||||
return;
|
||||
}
|
||||
|
||||
// Honor the "Never check for updates" option by not only disabling background
|
||||
// update checks, but also in the About dialog, by presenting a
|
||||
// "Check for updates" button.
|
||||
// If updates are found, the user is then asked if he wants to "Update to <version>".
|
||||
if (!this.updateEnabled ||
|
||||
Services.prefs.prefHasUserValue(PREF_APP_UPDATE_ELEVATE_NEVER)) {
|
||||
this.selectPanel("checkForUpdates");
|
||||
return;
|
||||
}
|
||||
|
||||
// That leaves the options
|
||||
// "Check for updates, but let me choose whether to install them", and
|
||||
// "Automatically install updates".
|
||||
// In both cases, we check for updates without asking.
|
||||
// In the "let me choose" case, we ask before downloading though, in onCheckComplete.
|
||||
this.checkForUpdates();
|
||||
}
|
||||
|
||||
appUpdater.prototype =
|
||||
{
|
||||
// true when there is an update check in progress.
|
||||
isChecking: false,
|
||||
|
||||
// true when there is an update already staged / ready to be applied.
|
||||
get isPending() {
|
||||
if (this.update) {
|
||||
return this.update.state == "pending" ||
|
||||
this.update.state == "pending-service" ||
|
||||
this.update.state == "pending-elevate";
|
||||
}
|
||||
return this.um.activeUpdate &&
|
||||
(this.um.activeUpdate.state == "pending" ||
|
||||
this.um.activeUpdate.state == "pending-service" ||
|
||||
this.um.activeUpdate.state == "pending-elevate");
|
||||
},
|
||||
|
||||
// true when there is an update already installed in the background.
|
||||
get isApplied() {
|
||||
if (this.update)
|
||||
return this.update.state == "applied" ||
|
||||
this.update.state == "applied-service";
|
||||
return this.um.activeUpdate &&
|
||||
(this.um.activeUpdate.state == "applied" ||
|
||||
this.um.activeUpdate.state == "applied-service");
|
||||
},
|
||||
|
||||
// true when there is an update download in progress.
|
||||
get isDownloading() {
|
||||
if (this.update)
|
||||
return this.update.state == "downloading";
|
||||
return this.um.activeUpdate &&
|
||||
this.um.activeUpdate.state == "downloading";
|
||||
},
|
||||
|
||||
// true when updating is disabled by an administrator.
|
||||
get updateDisabledAndLocked() {
|
||||
return !this.updateEnabled &&
|
||||
Services.prefs.prefIsLocked("app.update.enabled");
|
||||
},
|
||||
|
||||
// true when updating is enabled.
|
||||
get updateEnabled() {
|
||||
try {
|
||||
return Services.prefs.getBoolPref("app.update.enabled");
|
||||
}
|
||||
catch (e) { }
|
||||
return true; // Firefox default is true
|
||||
},
|
||||
|
||||
// true when updating in background is enabled.
|
||||
get backgroundUpdateEnabled() {
|
||||
return this.updateEnabled &&
|
||||
gAppUpdater.aus.canStageUpdates;
|
||||
},
|
||||
|
||||
// true when updating is automatic.
|
||||
get updateAuto() {
|
||||
try {
|
||||
return Services.prefs.getBoolPref("app.update.auto");
|
||||
}
|
||||
catch (e) { }
|
||||
return true; // Firefox default is true
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the panel of the updateDeck.
|
||||
*
|
||||
* @param aChildID
|
||||
* The id of the deck's child to select, e.g. "apply".
|
||||
*/
|
||||
selectPanel: function(aChildID) {
|
||||
let panel = document.getElementById(aChildID);
|
||||
|
||||
let button = panel.querySelector("button");
|
||||
if (button) {
|
||||
if (aChildID == "downloadAndInstall") {
|
||||
let updateVersion = gAppUpdater.update.displayVersion;
|
||||
button.label = this.bundle.formatStringFromName("update.downloadAndInstallButton.label", [updateVersion], 1);
|
||||
button.accessKey = this.bundle.GetStringFromName("update.downloadAndInstallButton.accesskey");
|
||||
}
|
||||
this.updateDeck.selectedPanel = panel;
|
||||
if (!document.commandDispatcher.focusedElement || // don't steal the focus
|
||||
document.commandDispatcher.focusedElement.localName == "button") // except from the other buttons
|
||||
button.focus();
|
||||
|
||||
} else {
|
||||
this.updateDeck.selectedPanel = panel;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Check for updates
|
||||
*/
|
||||
checkForUpdates: function() {
|
||||
// Clear prefs that could prevent a user from discovering available updates.
|
||||
if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_CANCELATIONS_OSX)) {
|
||||
Services.prefs.clearUserPref(PREF_APP_UPDATE_CANCELATIONS_OSX);
|
||||
}
|
||||
if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_ELEVATE_NEVER)) {
|
||||
Services.prefs.clearUserPref(PREF_APP_UPDATE_ELEVATE_NEVER);
|
||||
}
|
||||
this.selectPanel("checkingForUpdates");
|
||||
this.isChecking = true;
|
||||
this.checker.checkForUpdates(this.updateCheckListener, true);
|
||||
// after checking, onCheckComplete() is called
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles oncommand for the "Restart to Update" button
|
||||
* which is presented after the download has been downloaded.
|
||||
*/
|
||||
buttonRestartAfterDownload: function() {
|
||||
if (!this.isPending && !this.isApplied) {
|
||||
return;
|
||||
}
|
||||
|
||||
gAppUpdater.selectPanel("restarting");
|
||||
|
||||
// Notify all windows that an application quit has been requested.
|
||||
let cancelQuit = Components.classes["@mozilla.org/supports-PRBool;1"].
|
||||
createInstance(Components.interfaces.nsISupportsPRBool);
|
||||
Services.obs.notifyObservers(cancelQuit, "quit-application-requested", "restart");
|
||||
|
||||
// Something aborted the quit process.
|
||||
if (cancelQuit.data) {
|
||||
gAppUpdater.selectPanel("apply");
|
||||
return;
|
||||
}
|
||||
|
||||
let appStartup = Components.classes["@mozilla.org/toolkit/app-startup;1"].
|
||||
getService(Components.interfaces.nsIAppStartup);
|
||||
|
||||
// If already in safe mode restart in safe mode (bug 327119)
|
||||
if (Services.appinfo.inSafeMode) {
|
||||
appStartup.restartInSafeMode(Components.interfaces.nsIAppStartup.eAttemptQuit);
|
||||
return;
|
||||
}
|
||||
|
||||
appStartup.quit(Components.interfaces.nsIAppStartup.eAttemptQuit |
|
||||
Components.interfaces.nsIAppStartup.eRestart);
|
||||
},
|
||||
|
||||
/**
|
||||
* Implements nsIUpdateCheckListener. The methods implemented by
|
||||
* nsIUpdateCheckListener are in a different scope from nsIIncrementalDownload
|
||||
* to make it clear which are used by each interface.
|
||||
*/
|
||||
updateCheckListener: {
|
||||
/**
|
||||
* See nsIUpdateService.idl
|
||||
*/
|
||||
onCheckComplete: function(aRequest, aUpdates, aUpdateCount) {
|
||||
gAppUpdater.isChecking = false;
|
||||
gAppUpdater.update = gAppUpdater.aus.
|
||||
selectUpdate(aUpdates, aUpdates.length);
|
||||
if (!gAppUpdater.update) {
|
||||
gAppUpdater.selectPanel("noUpdatesFound");
|
||||
return;
|
||||
}
|
||||
|
||||
if (gAppUpdater.update.unsupported) {
|
||||
if (gAppUpdater.update.detailsURL) {
|
||||
let unsupportedLink = document.getElementById("unsupportedLink");
|
||||
unsupportedLink.href = gAppUpdater.update.detailsURL;
|
||||
}
|
||||
gAppUpdater.selectPanel("unsupportedSystem");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!gAppUpdater.aus.canApplyUpdates) {
|
||||
gAppUpdater.selectPanel("manualUpdate");
|
||||
return;
|
||||
}
|
||||
|
||||
if (gAppUpdater.updateAuto) // automatically download and install
|
||||
gAppUpdater.startDownload();
|
||||
else // ask
|
||||
gAppUpdater.selectPanel("downloadAndInstall");
|
||||
},
|
||||
|
||||
/**
|
||||
* See nsIUpdateService.idl
|
||||
*/
|
||||
onError: function(aRequest, aUpdate) {
|
||||
// Errors in the update check are treated as no updates found. If the
|
||||
// update check fails repeatedly without a success the user will be
|
||||
// notified with the normal app update user interface so this is safe.
|
||||
gAppUpdater.isChecking = false;
|
||||
gAppUpdater.selectPanel("noUpdatesFound");
|
||||
},
|
||||
|
||||
/**
|
||||
* See nsISupports.idl
|
||||
*/
|
||||
QueryInterface: function(aIID) {
|
||||
if (!aIID.equals(Components.interfaces.nsIUpdateCheckListener) &&
|
||||
!aIID.equals(Components.interfaces.nsISupports))
|
||||
throw Components.results.NS_ERROR_NO_INTERFACE;
|
||||
return this;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Starts the download of an update mar.
|
||||
*/
|
||||
startDownload: function() {
|
||||
if (!this.update)
|
||||
this.update = this.um.activeUpdate;
|
||||
this.update.QueryInterface(Components.interfaces.nsIWritablePropertyBag);
|
||||
this.update.setProperty("foregroundDownload", "true");
|
||||
|
||||
this.aus.pauseDownload();
|
||||
let state = this.aus.downloadUpdate(this.update, false);
|
||||
if (state == "failed") {
|
||||
this.selectPanel("downloadFailed");
|
||||
return;
|
||||
}
|
||||
|
||||
this.setupDownloadingUI();
|
||||
},
|
||||
|
||||
/**
|
||||
* Switches to the UI responsible for tracking the download.
|
||||
*/
|
||||
setupDownloadingUI: function() {
|
||||
this.downloadStatus = document.getElementById("downloadStatus");
|
||||
this.downloadStatus.value =
|
||||
DownloadUtils.getTransferTotal(0, this.update.selectedPatch.size);
|
||||
this.selectPanel("downloading");
|
||||
this.aus.addDownloadListener(this);
|
||||
},
|
||||
|
||||
removeDownloadListener: function() {
|
||||
if (this.aus) {
|
||||
this.aus.removeDownloadListener(this);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* See nsIRequestObserver.idl
|
||||
*/
|
||||
onStartRequest: function(aRequest, aContext) {
|
||||
},
|
||||
|
||||
/**
|
||||
* See nsIRequestObserver.idl
|
||||
*/
|
||||
onStopRequest: function(aRequest, aContext, aStatusCode) {
|
||||
switch (aStatusCode) {
|
||||
case Components.results.NS_ERROR_UNEXPECTED:
|
||||
if (this.update.selectedPatch.state == "download-failed" &&
|
||||
(this.update.isCompleteUpdate || this.update.patchCount != 2)) {
|
||||
// Verification error of complete patch, informational text is held in
|
||||
// the update object.
|
||||
this.removeDownloadListener();
|
||||
this.selectPanel("downloadFailed");
|
||||
break;
|
||||
}
|
||||
// Verification failed for a partial patch, complete patch is now
|
||||
// downloading so return early and do NOT remove the download listener!
|
||||
break;
|
||||
case Components.results.NS_BINDING_ABORTED:
|
||||
// Do not remove UI listener since the user may resume downloading again.
|
||||
break;
|
||||
case Components.results.NS_OK:
|
||||
this.removeDownloadListener();
|
||||
if (this.backgroundUpdateEnabled) {
|
||||
this.selectPanel("applying");
|
||||
let self = this;
|
||||
Services.obs.addObserver(function (aSubject, aTopic, aData) {
|
||||
// Update the UI when the background updater is finished
|
||||
let status = aData;
|
||||
if (status == "applied" || status == "applied-service" ||
|
||||
status == "pending" || status == "pending-service" ||
|
||||
status == "pending-elevate") {
|
||||
// If the update is successfully applied, or if the updater has
|
||||
// fallen back to non-staged updates, show the "Restart to Update"
|
||||
// button.
|
||||
self.selectPanel("apply");
|
||||
} else if (status == "failed") {
|
||||
// Background update has failed, let's show the UI responsible for
|
||||
// prompting the user to update manually.
|
||||
self.selectPanel("downloadFailed");
|
||||
} else if (status == "downloading") {
|
||||
// We've fallen back to downloading the full update because the
|
||||
// partial update failed to get staged in the background.
|
||||
// Therefore we need to keep our observer.
|
||||
self.setupDownloadingUI();
|
||||
return;
|
||||
}
|
||||
Services.obs.removeObserver(arguments.callee, "update-staged");
|
||||
}, "update-staged", false);
|
||||
} else {
|
||||
this.selectPanel("apply");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
this.removeDownloadListener();
|
||||
this.selectPanel("downloadFailed");
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* See nsIProgressEventSink.idl
|
||||
*/
|
||||
onStatus: function(aRequest, aContext, aStatus, aStatusArg) {
|
||||
},
|
||||
|
||||
/**
|
||||
* See nsIProgressEventSink.idl
|
||||
*/
|
||||
onProgress: function(aRequest, aContext, aProgress, aProgressMax) {
|
||||
this.downloadStatus.value =
|
||||
DownloadUtils.getTransferTotal(aProgress, aProgressMax);
|
||||
},
|
||||
|
||||
/**
|
||||
* See nsISupports.idl
|
||||
*/
|
||||
QueryInterface: function(aIID) {
|
||||
if (!aIID.equals(Components.interfaces.nsIProgressEventSink) &&
|
||||
!aIID.equals(Components.interfaces.nsIRequestObserver) &&
|
||||
!aIID.equals(Components.interfaces.nsISupports))
|
||||
throw Components.results.NS_ERROR_NO_INTERFACE;
|
||||
return this;
|
||||
}
|
||||
};
|
||||
103
browser/base/content/aboutDialog.css
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
/* 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/. */
|
||||
|
||||
#aboutDialog {
|
||||
width: 620px;
|
||||
/* Set an explicit line-height to avoid discrepancies in 'auto' spacing
|
||||
across screens with different device DPI, which may cause font metrics
|
||||
to round differently. */
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
#aboutDialog label,
|
||||
#aboutDialog description {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#rightBox {
|
||||
background-image: url("chrome://branding/content/about-wordmark.svg");
|
||||
background-repeat: no-repeat;
|
||||
background-position: center top;
|
||||
/* padding-top creates room for the wordmark */
|
||||
padding-top: 38px;
|
||||
margin-top:20px;
|
||||
}
|
||||
|
||||
#rightBox:-moz-locale-dir(rtl) {
|
||||
background-position: 100% 0;
|
||||
}
|
||||
|
||||
#bottomBox {
|
||||
padding: 15px 10px 0;
|
||||
}
|
||||
|
||||
#version {
|
||||
font-weight: bold;
|
||||
margin-top: 10px;
|
||||
margin-left: 0;
|
||||
-moz-user-select: text;
|
||||
-moz-user-focus: normal;
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
#version:-moz-locale-dir(rtl) {
|
||||
direction: ltr;
|
||||
text-align: right;
|
||||
margin-left: 5px;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
#releasenotes {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
#distribution,
|
||||
#distributionId {
|
||||
display: none;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.text-blurb {
|
||||
margin-bottom: 10px;
|
||||
margin-inline-start: 0;
|
||||
padding-inline-start: 0;
|
||||
}
|
||||
|
||||
#updateButton,
|
||||
#updateDeck > hbox > label {
|
||||
margin-inline-start: 0;
|
||||
padding-inline-start: 0;
|
||||
}
|
||||
|
||||
.update-throbber {
|
||||
width: 16px;
|
||||
min-height: 16px;
|
||||
margin-inline-end: 3px;
|
||||
list-style-image: url("chrome://global/skin/icons/loading.png");
|
||||
}
|
||||
|
||||
@media (min-resolution: 1.1dppx) {
|
||||
.update-throbber {
|
||||
list-style-image: url("chrome://global/skin/icons/loading@2x.png");
|
||||
}
|
||||
}
|
||||
|
||||
description > .text-link,
|
||||
description > .text-link:focus {
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.bottom-link,
|
||||
.bottom-link:focus {
|
||||
text-align: center;
|
||||
margin: 0 40px;
|
||||
}
|
||||
|
||||
#currentChannel {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-weight: bold;
|
||||
}
|
||||
81
browser/base/content/aboutDialog.js
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
/* 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";
|
||||
|
||||
// Services = object with smart getters for common XPCOM services
|
||||
Components.utils.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
function init(aEvent)
|
||||
{
|
||||
if (aEvent.target != document)
|
||||
return;
|
||||
|
||||
try {
|
||||
var distroId = Services.prefs.getCharPref("distribution.id");
|
||||
if (distroId) {
|
||||
var distroVersion = Services.prefs.getCharPref("distribution.version");
|
||||
|
||||
var distroIdField = document.getElementById("distributionId");
|
||||
distroIdField.value = distroId + " - " + distroVersion;
|
||||
distroIdField.style.display = "block";
|
||||
|
||||
try {
|
||||
// This is in its own try catch due to bug 895473 and bug 900925.
|
||||
var distroAbout = Services.prefs.getComplexValue("distribution.about",
|
||||
Components.interfaces.nsISupportsString);
|
||||
var distroField = document.getElementById("distribution");
|
||||
distroField.value = distroAbout;
|
||||
distroField.style.display = "block";
|
||||
}
|
||||
catch (ex) {
|
||||
// Pref is unset
|
||||
Components.utils.reportError(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
// Pref is unset
|
||||
}
|
||||
|
||||
// Display warning if this is an "a#" (nightly or aurora) build
|
||||
if (/a\d+$/.test(version)) {
|
||||
document.getElementById("experimental").hidden = false;
|
||||
document.getElementById("communityDesc").hidden = true;
|
||||
}
|
||||
|
||||
// Append "(32-bit)" or "(64-bit)" build architecture to the version number:
|
||||
let bundle = Services.strings.createBundle("chrome://browser/locale/browser.properties");
|
||||
let archResource = Services.appinfo.is64Bit
|
||||
? "aboutDialog.architecture.sixtyFourBit"
|
||||
: "aboutDialog.architecture.thirtyTwoBit";
|
||||
let arch = bundle.GetStringFromName(archResource);
|
||||
versionField.textContent += ` (${arch})`;
|
||||
|
||||
#ifdef MOZ_WIDGET_GTK
|
||||
// If Linux append the toolkit "(GTK2)" or "(GTK3)"
|
||||
let toolkit = Components.classes["@mozilla.org/xre/app-info;1"].getService(Components.interfaces.nsIXULRuntime).widgetToolkit.toUpperCase();
|
||||
versionField.textContent += ` (${toolkit})`;
|
||||
#endif
|
||||
|
||||
// Get Release Notes URL from Preferences
|
||||
let releaseNotesURL = Services.prefs.getCharPref("app.releaseNotesURL");
|
||||
document.getElementById("releasenotes").setAttribute("href", releaseNotesURL);
|
||||
|
||||
#ifdef MOZ_UPDATER
|
||||
gAppUpdater = new appUpdater();
|
||||
|
||||
let channelLabel = document.getElementById("currentChannel");
|
||||
let currentChannelText = document.getElementById("currentChannelText");
|
||||
channelLabel.value = UpdateUtils.UpdateChannel;
|
||||
if (/^release($|\-)/.test(channelLabel.value))
|
||||
currentChannelText.hidden = true;
|
||||
#endif
|
||||
|
||||
#ifdef XP_MACOSX
|
||||
// it may not be sized at this point, and we need its width to calculate its position
|
||||
window.sizeToContent();
|
||||
window.moveTo((screen.availWidth / 2) - (window.outerWidth / 2), screen.availHeight / 5);
|
||||
#endif
|
||||
}
|
||||
176
browser/base/content/aboutDialog.xul
Normal file
|
|
@ -0,0 +1,176 @@
|
|||
<?xml version="1.0"?> <!-- -*- Mode: HTML -*- -->
|
||||
|
||||
# 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/aboutDialog.css" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://branding/content/aboutDialog.css" type="text/css"?>
|
||||
|
||||
<!DOCTYPE window [
|
||||
<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
|
||||
%brandDTD;
|
||||
<!ENTITY % aboutDialogDTD SYSTEM "chrome://browser/locale/aboutDialog.dtd" >
|
||||
%aboutDialogDTD;
|
||||
]>
|
||||
|
||||
#ifdef XP_MACOSX
|
||||
<?xul-overlay href="chrome://browser/content/macBrowserOverlay.xul"?>
|
||||
#endif
|
||||
|
||||
<window xmlns:html="http://www.w3.org/1999/xhtml"
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
id="aboutDialog"
|
||||
windowtype="Browser:About"
|
||||
onload="init(event);"
|
||||
#ifdef MOZ_UPDATER
|
||||
onunload="onUnload(event);"
|
||||
#endif
|
||||
#ifdef XP_MACOSX
|
||||
inwindowmenu="false"
|
||||
#else
|
||||
title="About Dactyloidae"
|
||||
#endif
|
||||
role="dialog"
|
||||
aria-describedby="version distribution distributionId communityDesc contributeDesc trademark"
|
||||
>
|
||||
|
||||
<script type="application/javascript" src="chrome://browser/content/aboutDialog.js"/>
|
||||
#ifdef MOZ_UPDATER
|
||||
<script type="application/javascript" src="chrome://browser/content/aboutDialog-appUpdater.js"/>
|
||||
#endif
|
||||
<vbox id="aboutDialogContainer">
|
||||
<hbox id="clientBox">
|
||||
<vbox id="leftBox" collapsed="true"/>
|
||||
<vbox id="rightBox" flex="1" align="center">
|
||||
<hbox align="baseline" pack="center">
|
||||
#ifdef HAVE_64BIT_BUILD
|
||||
#expand <label id="version">__MOZ_APP_VERSION_DISPLAY__ (64-bit)</label>
|
||||
#else
|
||||
#expand <label id="version">__MOZ_APP_VERSION_DISPLAY__ (32-bit)</label>
|
||||
#endif
|
||||
</hbox>
|
||||
|
||||
<label id="distribution" class="text-blurb"/>
|
||||
<label id="distributionId" class="text-blurb"/>
|
||||
|
||||
<vbox id="detailsBox">
|
||||
<vbox id="updateBox">
|
||||
#ifdef MOZ_UPDATER
|
||||
<deck id="updateDeck" orient="vertical">
|
||||
<hbox id="checkForUpdates" align="center">
|
||||
<button id="checkForUpdatesButton" align="start"
|
||||
label="&update.checkForUpdatesButton.label;"
|
||||
accesskey="&update.checkForUpdatesButton.accesskey;"
|
||||
oncommand="gAppUpdater.checkForUpdates();"/>
|
||||
<spacer flex="1"/>
|
||||
</hbox>
|
||||
<hbox id="downloadAndInstall" align="center">
|
||||
<button id="downloadAndInstallButton" align="start"
|
||||
oncommand="gAppUpdater.startDownload();"/>
|
||||
<!-- label and accesskey will be filled by JS -->
|
||||
<spacer flex="1"/>
|
||||
</hbox>
|
||||
<hbox id="apply" align="center">
|
||||
<button id="updateButton" align="start"
|
||||
label="&update.updateButton.label2;"
|
||||
accesskey="&update.updateButton.accesskey;"
|
||||
oncommand="gAppUpdater.buttonRestartAfterDownload();"/>
|
||||
<spacer flex="1"/>
|
||||
</hbox>
|
||||
<hbox id="checkingForUpdates" align="center">
|
||||
<image class="update-throbber"/><label>&update.checkingForUpdates;</label>
|
||||
</hbox>
|
||||
<hbox id="downloading" align="center">
|
||||
<image class="update-throbber"/><label>&update.downloading.start;</label><label id="downloadStatus"/><label>&update.downloading.end;</label>
|
||||
</hbox>
|
||||
<hbox id="applying" align="center">
|
||||
<image class="update-throbber"/><label>&update.applying;</label>
|
||||
</hbox>
|
||||
<hbox id="downloadFailed" align="center">
|
||||
<label>&update.failed.start;</label><label id="failedLink" class="text-link">&update.failed.linkText;</label><label>&update.failed.end;</label>
|
||||
</hbox>
|
||||
<hbox id="adminDisabled" align="center">
|
||||
<label>&update.adminDisabled;</label>
|
||||
</hbox>
|
||||
<hbox id="noUpdatesFound" align="center">
|
||||
<label>&update.noUpdatesFound;</label>
|
||||
</hbox>
|
||||
<hbox id="otherInstanceHandlingUpdates" align="center">
|
||||
<label>&update.otherInstanceHandlingUpdates;</label>
|
||||
</hbox>
|
||||
<hbox id="manualUpdate" align="center">
|
||||
<label>&update.manual.start;</label><label id="manualLink" class="text-link"/><label>&update.manual.end;</label>
|
||||
</hbox>
|
||||
<hbox id="unsupportedSystem" align="center">
|
||||
<label>&update.unsupported.start;</label><label id="unsupportedLink" class="text-link">&update.unsupported.linkText;</label><label>&update.unsupported.end;</label>
|
||||
</hbox>
|
||||
<hbox id="restarting" align="center">
|
||||
<label>&update.restarting;</label>
|
||||
</hbox>
|
||||
</deck>
|
||||
#endif
|
||||
</vbox>
|
||||
|
||||
#ifdef MOZ_UPDATER
|
||||
<description class="text-blurb" id="currentChannelText">
|
||||
&channel.description.start;<label id="currentChannel"/>&channel.description.end;
|
||||
</description>
|
||||
#endif
|
||||
<vbox id="experimental" hidden="true">
|
||||
<description class="text-blurb" id="warningDesc">
|
||||
&warningDesc.version;
|
||||
</description>
|
||||
</vbox>
|
||||
#if defined(MOZ_OFFICIAL_BRANDING) || defined(MC_OFFICIAL)
|
||||
#ifdef MC_PRIVATE_BUILD
|
||||
<description class="text-blurb" id="communityDesc">
|
||||
This is a private build of Hydra. If you did not manually build this copy from source yourself, then please download an official version from the <label class="text-link" href="https://eclipse.cx/projects/hydra">Eclipse Community website</label>.
|
||||
</description>
|
||||
#else
|
||||
<description class="text-blurb" id="communityDesc">
|
||||
<label class="text-link" href="https://eclipse.cx/projects/hydra">&brandShorterName;</label> is community software released by <label class="text-link" href="https://eclipse.cx">&vendorShortName;</label> and Mozilla developers. Learn <label class="text-link" useoriginprincipal="true" href="about:credits">who contributed</label> to this software.
|
||||
</description>
|
||||
<description class="text-blurb" id="contributeDesc">
|
||||
If you wish to contribute, please consider helping out by providing support to other users on the <label class="text-link" href="https://board.eclipse.cx/">&vendorShortName; forum</label> or getting involved in our development by tackling some of the issues found in our <label class="text-link" href="https://github.com/Eclipse-Community/UXP/">GitHub issue tracker</label>.
|
||||
</description>
|
||||
#endif
|
||||
#else
|
||||
<description class="text-blurb" id="communityDesc">
|
||||
Dactyloidae is released by <label class="text-link" href="https://eclipse.cx">Eclipse Community</label>.
|
||||
</description>
|
||||
|
||||
<description class="text-blurb">
|
||||
Dactyloidae is based on the work of the UXP team, aswell as the work of the Mozilla developers.
|
||||
</description>
|
||||
|
||||
<description class="text-blurb">
|
||||
Learn <label class="text-link" useoriginprincipal="true" href="about:credits">who contributed</label> to this software.
|
||||
</description>
|
||||
|
||||
<description class="text-blurb" id="contributeDesc">
|
||||
If you wish to contribute, please consider helping out by getting involved in our development by tackling some of the issues found in our <label class="text-link" href="https://github.com/Eclipse-Community/Dactyloidae/">GitHub issue tracker</label>.
|
||||
</description>
|
||||
#endif
|
||||
</vbox>
|
||||
</vbox>
|
||||
</hbox>
|
||||
<vbox id="bottomBox">
|
||||
<hbox pack="center">
|
||||
<label class="text-link bottom-link" useoriginprincipal="true" href="about:license">&bottomLinks.license;</label>
|
||||
<label class="text-link bottom-link" useoriginprincipal="true" href="about:rights">&bottomLinks.rights;</label>
|
||||
<label class="text-link bottom-link" href="https://www.palemoon.org/privacy.shtml">&bottomLinks.privacy;</label>
|
||||
</hbox>
|
||||
<description id="trademark">&trademarkInfo.part1;</description>
|
||||
</vbox>
|
||||
</vbox>
|
||||
|
||||
<keyset>
|
||||
<key keycode="VK_ESCAPE" oncommand="window.close();"/>
|
||||
</keyset>
|
||||
|
||||
#ifdef XP_MACOSX
|
||||
#include browserMountPoints.inc
|
||||
#endif
|
||||
</window>
|
||||
567
browser/base/content/aboutNetError.xhtml
Normal file
|
|
@ -0,0 +1,567 @@
|
|||
<?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 % netErrorDTD
|
||||
SYSTEM "chrome://global/locale/netError.dtd">
|
||||
%netErrorDTD;
|
||||
<!ENTITY % globalDTD
|
||||
SYSTEM "chrome://global/locale/global.dtd">
|
||||
%globalDTD;
|
||||
]>
|
||||
|
||||
<!-- 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>&loadError.label;</title>
|
||||
<link rel="stylesheet" href="chrome://browser/skin/aboutNetError.css" type="text/css" media="all" />
|
||||
<!-- If the location of the favicon is changed here, the FAVICON_ERRORPAGE_URL symbol in
|
||||
toolkit/components/places/src/nsFaviconService.h should be updated. -->
|
||||
<link rel="icon" type="image/png" id="favicon" href="chrome://global/skin/icons/warning-16.png"/>
|
||||
|
||||
<script type="application/javascript"><![CDATA[
|
||||
// The following parameters are parsed from the error URL:
|
||||
// e - the error code
|
||||
// s - custom CSS class to allow alternate styling/favicons
|
||||
// d - error description
|
||||
// captive - "true" to indicate we're behind a captive portal.
|
||||
// Any other value is ignored.
|
||||
|
||||
// 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.
|
||||
|
||||
let searchParams = new URLSearchParams(document.documentURI.split("?")[1]);
|
||||
|
||||
// Set to true on init if the error code is nssBadCert.
|
||||
let gIsCertError;
|
||||
|
||||
function getErrorCode()
|
||||
{
|
||||
return searchParams.get("e");
|
||||
}
|
||||
|
||||
function getCSSClass()
|
||||
{
|
||||
return searchParams.get("s");
|
||||
}
|
||||
|
||||
function getDescription()
|
||||
{
|
||||
return searchParams.get("d");
|
||||
}
|
||||
|
||||
function isCaptive() {
|
||||
return searchParams.get("captive") == "true";
|
||||
}
|
||||
|
||||
function retryThis(buttonEl)
|
||||
{
|
||||
// Note: The application may wish to handle switching off "offline mode"
|
||||
// before this event handler runs, but using a capturing event handler.
|
||||
|
||||
// Session history has the URL of the page that failed
|
||||
// to load, not the one of the error page. So, just call
|
||||
// reload(), which will also repost POST data correctly.
|
||||
try {
|
||||
location.reload();
|
||||
} catch (e) {
|
||||
// We probably tried to reload a URI that caused an exception to
|
||||
// occur; e.g. a nonexistent file.
|
||||
}
|
||||
|
||||
buttonEl.disabled = true;
|
||||
}
|
||||
|
||||
function doOverride(buttonEl) {
|
||||
var event = new CustomEvent("AboutNetErrorOverride", {bubbles:true});
|
||||
document.dispatchEvent(event);
|
||||
retryThis(buttonEl);
|
||||
}
|
||||
|
||||
function toggleDisplay(node) {
|
||||
const toggle = {
|
||||
"": "block",
|
||||
"none": "block",
|
||||
"block": "none"
|
||||
};
|
||||
return (node.style.display = toggle[node.style.display]);
|
||||
}
|
||||
|
||||
function showPrefChangeContainer() {
|
||||
const panel = document.getElementById("prefChangeContainer");
|
||||
panel.style.display = "block";
|
||||
document.getElementById("netErrorButtonContainer").style.display = "none";
|
||||
document.getElementById("prefResetButton").addEventListener("click", function resetPreferences(e) {
|
||||
const event = new CustomEvent("AboutNetErrorResetPreferences", {bubbles:true});
|
||||
document.dispatchEvent(event);
|
||||
});
|
||||
addAutofocus("prefResetButton", "beforeend");
|
||||
}
|
||||
|
||||
function setupAdvancedButton(allowOverride) {
|
||||
// Get the hostname and add it to the panel
|
||||
var panelId = gIsCertError ? "badCertAdvancedPanel" : "weakCryptoAdvancedPanel";
|
||||
var panel = document.getElementById(panelId);
|
||||
for (var span of panel.querySelectorAll("span.hostname")) {
|
||||
span.textContent = document.location.hostname;
|
||||
}
|
||||
if (!gIsCertError) {
|
||||
panel.replaceChild(document.getElementById("errorLongDesc"),
|
||||
document.getElementById("advancedLongDesc"));
|
||||
}
|
||||
|
||||
// Register click handler for the weakCryptoAdvancedPanel
|
||||
document.getElementById("advancedButton")
|
||||
.addEventListener("click", function togglePanelVisibility() {
|
||||
toggleDisplay(panel);
|
||||
});
|
||||
|
||||
if (allowOverride) {
|
||||
document.getElementById("overrideWeakCryptoPanel").style.display = "flex";
|
||||
var overrideLink = document.getElementById("overrideWeakCrypto");
|
||||
overrideLink.addEventListener("click", () => doOverride(overrideLink), false);
|
||||
}
|
||||
if (!gIsCertError) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (getCSSClass() == "expertBadCert") {
|
||||
toggleDisplay(document.getElementById("badCertAdvancedPanel"));
|
||||
}
|
||||
|
||||
disallowCertOverridesIfNeeded();
|
||||
|
||||
document.getElementById("badCertTechnicalInfo").textContent = getDescription();
|
||||
}
|
||||
|
||||
function disallowCertOverridesIfNeeded() {
|
||||
var cssClass = getCSSClass();
|
||||
// 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 (cssClass == "badStsCert" || window != top) {
|
||||
document.getElementById("exceptionDialogButton").setAttribute("hidden", "true");
|
||||
}
|
||||
if (cssClass == "badStsCert") {
|
||||
document.getElementById("badStsCertExplanation").removeAttribute("hidden");
|
||||
}
|
||||
}
|
||||
|
||||
function initPage()
|
||||
{
|
||||
var err = getErrorCode();
|
||||
gIsCertError = (err == "nssBadCert");
|
||||
// Only worry about captive portals if this is a cert error.
|
||||
let showCaptivePortalUI = isCaptive() && gIsCertError;
|
||||
if (showCaptivePortalUI) {
|
||||
err = "captivePortal";
|
||||
}
|
||||
|
||||
// if it's an unknown error or there's no title or description
|
||||
// defined, get the generic message
|
||||
var errTitle = document.getElementById("et_" + err);
|
||||
var errDesc = document.getElementById("ed_" + err);
|
||||
if (!errTitle || !errDesc)
|
||||
{
|
||||
errTitle = document.getElementById("et_generic");
|
||||
errDesc = document.getElementById("ed_generic");
|
||||
}
|
||||
|
||||
document.querySelector(".title-text").innerHTML = errTitle.innerHTML;
|
||||
|
||||
var sd = document.getElementById("errorShortDescText");
|
||||
if (sd) {
|
||||
if (gIsCertError) {
|
||||
sd.innerHTML = errDesc.innerHTML;
|
||||
}
|
||||
else {
|
||||
sd.textContent = getDescription();
|
||||
}
|
||||
}
|
||||
if (showCaptivePortalUI) {
|
||||
initPageCaptivePortal();
|
||||
return;
|
||||
}
|
||||
if (gIsCertError) {
|
||||
initPageCertError();
|
||||
return;
|
||||
}
|
||||
addAutofocus("errorTryAgain");
|
||||
|
||||
document.body.className = "neterror";
|
||||
|
||||
var ld = document.getElementById("errorLongDesc");
|
||||
if (ld)
|
||||
{
|
||||
ld.innerHTML = errDesc.innerHTML;
|
||||
}
|
||||
|
||||
if (err == "sslv3Used") {
|
||||
document.body.className = "certerror";
|
||||
}
|
||||
|
||||
if (err == "weakCryptoUsed") {
|
||||
document.body.className = "certerror";
|
||||
}
|
||||
|
||||
// remove undisplayed errors to avoid bug 39098
|
||||
var errContainer = document.getElementById("errorContainer");
|
||||
errContainer.parentNode.removeChild(errContainer);
|
||||
|
||||
var className = getCSSClass();
|
||||
if (className && className != "expertBadCert") {
|
||||
// Associate a CSS class with the root of the page, if one was passed in,
|
||||
// to allow custom styling.
|
||||
// Not "expertBadCert" though, don't want to deal with the favicon
|
||||
document.documentElement.className = className;
|
||||
|
||||
// Also, if they specified a CSS class, they must supply their own
|
||||
// favicon. In order to trigger the browser to repaint though, we
|
||||
// need to remove/add the link element.
|
||||
var favicon = document.getElementById("favicon");
|
||||
var faviconParent = favicon.parentNode;
|
||||
faviconParent.removeChild(favicon);
|
||||
favicon.setAttribute("href", "chrome://global/skin/icons/" + className + "_favicon.png");
|
||||
faviconParent.appendChild(favicon);
|
||||
}
|
||||
|
||||
if (err == "remoteXUL") {
|
||||
// Remove the "Try again" button for remote XUL errors given that
|
||||
// it is useless.
|
||||
document.getElementById("netErrorButtonContainer").style.display = "none";
|
||||
}
|
||||
|
||||
if (err == "cspBlocked") {
|
||||
// Remove the "Try again" button for CSP violations, since it's
|
||||
// almost certainly useless. (Bug 553180)
|
||||
document.getElementById("netErrorButtonContainer").style.display = "none";
|
||||
}
|
||||
|
||||
window.addEventListener("AboutNetErrorOptions", function(evt) {
|
||||
// Pinning errors are of type nssFailure2
|
||||
if (getErrorCode() == "nssFailure2" || getErrorCode() == "weakCryptoUsed") {
|
||||
const hasPrefStyleError = [
|
||||
"interrupted", // This happens with subresources that are above the max tls
|
||||
"SSL_ERROR_PROTOCOL_VERSION_ALERT",
|
||||
"SSL_ERROR_UNSUPPORTED_VERSION",
|
||||
"SSL_ERROR_NO_CYPHER_OVERLAP",
|
||||
"SSL_ERROR_NO_CIPHERS_SUPPORTED"
|
||||
].some((substring) => getDescription().includes(substring));
|
||||
// If it looks like an error that is user config based
|
||||
if (getErrorCode() == "nssFailure2" && hasPrefStyleError && options && options.changedCertPrefs) {
|
||||
showPrefChangeContainer();
|
||||
}
|
||||
}
|
||||
if (getErrorCode() == "weakCryptoUsed" || getErrorCode() == "sslv3Used") {
|
||||
setupAdvancedButton(getErrorCode() == "weakCryptoUsed");
|
||||
}
|
||||
}.bind(this), true, true);
|
||||
|
||||
var event = new CustomEvent("AboutNetErrorLoad", {bubbles:true});
|
||||
document.dispatchEvent(event);
|
||||
|
||||
if (err == "inadequateSecurityError") {
|
||||
// Remove the "Try again" button for HTTP/2 inadequate security as it
|
||||
// is useless.
|
||||
document.getElementById("errorTryAgain").style.display = "none";
|
||||
|
||||
var container = document.getElementById("errorLongDesc");
|
||||
for (var span of container.querySelectorAll("span.hostname")) {
|
||||
span.textContent = document.location.hostname;
|
||||
}
|
||||
}
|
||||
|
||||
addDomainErrorLink();
|
||||
}
|
||||
|
||||
function initPageCaptivePortal()
|
||||
{
|
||||
document.body.className = "captiveportal";
|
||||
document.title = document.getElementById("captivePortalPageTitle").textContent;
|
||||
|
||||
document.getElementById("openPortalLoginPageButton")
|
||||
.addEventListener("click", () => {
|
||||
let event = new CustomEvent("AboutNetErrorOpenCaptivePortal", {bubbles:true});
|
||||
document.dispatchEvent(event);
|
||||
});
|
||||
|
||||
addAutofocus("openPortalLoginPageButton");
|
||||
setupAdvancedButton(true);
|
||||
|
||||
addDomainErrorLink();
|
||||
|
||||
// When the portal is freed, an event is generated by the frame script
|
||||
// that we can pick up and attempt to reload the original page.
|
||||
window.addEventListener("AboutNetErrorCaptivePortalFreed", () => {
|
||||
document.location.reload();
|
||||
});
|
||||
}
|
||||
|
||||
function initPageCertError() {
|
||||
document.body.className = "certerror";
|
||||
document.title = document.getElementById("certErrorPageTitle").textContent;
|
||||
for (let host of document.querySelectorAll(".hostname")) {
|
||||
host.textContent = document.location.hostname;
|
||||
}
|
||||
|
||||
addAutofocus("returnButton");
|
||||
setupAdvancedButton(true);
|
||||
|
||||
let event = new CustomEvent("AboutNetErrorLoad", {bubbles:true});
|
||||
document.getElementById("advancedButton").dispatchEvent(event);
|
||||
|
||||
addDomainErrorLink();
|
||||
}
|
||||
|
||||
/* Only do autofocus if we're the toplevel frame; otherwise we
|
||||
don't want to call attention to ourselves! The key part is
|
||||
that autofocus happens on insertion into the tree, so we
|
||||
can remove the button, add @autofocus, and reinsert the
|
||||
button.
|
||||
*/
|
||||
function addAutofocus(buttonId, position = "afterbegin") {
|
||||
if (window.top == window) {
|
||||
var button = document.getElementById(buttonId);
|
||||
var parent = button.parentNode;
|
||||
button.remove();
|
||||
button.setAttribute("autofocus", "true");
|
||||
parent.insertAdjacentElement(position, button);
|
||||
}
|
||||
}
|
||||
|
||||
/* 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 sdid = gIsCertError ? "badCertTechnicalInfo" : "errorShortDescText";
|
||||
var sd = document.getElementById(sdid);
|
||||
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)));
|
||||
}
|
||||
|
||||
// Initialize the cert domain link.
|
||||
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 (okHost.endsWith("." + 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 (thisHost.endsWith("." + 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") {
|
||||
var panelId = gIsCertError ? "badCertAdvancedPanel" : "weakCryptoAdvancedPanel"
|
||||
toggleDisplay(document.getElementById(panelId));
|
||||
}
|
||||
}
|
||||
]]></script>
|
||||
</head>
|
||||
|
||||
<body dir="&locale.dir;">
|
||||
<!-- Contains an alternate page title set on page init for cert errors. -->
|
||||
<div id="certErrorPageTitle" style="display: none;">&certerror.pagetitle1;</div>
|
||||
<div id="captivePortalPageTitle" style="display: none;">&captivePortal.title;</div>
|
||||
|
||||
<!-- ERROR ITEM CONTAINER (removed during loading to avoid bug 39098) -->
|
||||
<div id="errorContainer">
|
||||
<div id="errorTitlesContainer">
|
||||
<h1 id="et_generic">&generic.title;</h1>
|
||||
<h1 id="et_captivePortal">&captivePortal.title;</h1>
|
||||
<h1 id="et_dnsNotFound">&dnsNotFound.title;</h1>
|
||||
<h1 id="et_fileNotFound">&fileNotFound.title;</h1>
|
||||
<h1 id="et_fileAccessDenied">&fileAccessDenied.title;</h1>
|
||||
<h1 id="et_malformedURI">&malformedURI.title;</h1>
|
||||
<h1 id="et_unknownProtocolFound">&unknownProtocolFound.title;</h1>
|
||||
<h1 id="et_connectionFailure">&connectionFailure.title;</h1>
|
||||
<h1 id="et_netTimeout">&netTimeout.title;</h1>
|
||||
<h1 id="et_redirectLoop">&redirectLoop.title;</h1>
|
||||
<h1 id="et_unknownSocketType">&unknownSocketType.title;</h1>
|
||||
<h1 id="et_netReset">&netReset.title;</h1>
|
||||
<h1 id="et_notCached">¬Cached.title;</h1>
|
||||
<h1 id="et_netOffline">&netOffline.title;</h1>
|
||||
<h1 id="et_netInterrupt">&netInterrupt.title;</h1>
|
||||
<h1 id="et_deniedPortAccess">&deniedPortAccess.title;</h1>
|
||||
<h1 id="et_proxyResolveFailure">&proxyResolveFailure.title;</h1>
|
||||
<h1 id="et_proxyConnectFailure">&proxyConnectFailure.title;</h1>
|
||||
<h1 id="et_contentEncodingError">&contentEncodingError.title;</h1>
|
||||
<h1 id="et_unsafeContentType">&unsafeContentType.title;</h1>
|
||||
<h1 id="et_nssFailure2">&nssFailure2.title;</h1>
|
||||
<h1 id="et_nssBadCert">&certerror.longpagetitle1;</h1>
|
||||
<h1 id="et_cspBlocked">&cspBlocked.title;</h1>
|
||||
<h1 id="et_remoteXUL">&remoteXUL.title;</h1>
|
||||
<h1 id="et_corruptedContentErrorv2">&corruptedContentErrorv2.title;</h1>
|
||||
<h1 id="et_sslv3Used">&sslv3Used.title;</h1>
|
||||
<h1 id="et_weakCryptoUsed">&weakCryptoUsed.title;</h1>
|
||||
<h1 id="et_inadequateSecurityError">&inadequateSecurityError.title;</h1>
|
||||
</div>
|
||||
<div id="errorDescriptionsContainer">
|
||||
<div id="ed_generic">&generic.longDesc;</div>
|
||||
<div id="ed_captivePortal">&captivePortal.longDesc;</div>
|
||||
<div id="ed_dnsNotFound">&dnsNotFound.longDesc;</div>
|
||||
<div id="ed_fileNotFound">&fileNotFound.longDesc;</div>
|
||||
<div id="ed_fileAccessDenied">&fileAccessDenied.longDesc;</div>
|
||||
<div id="ed_malformedURI">&malformedURI.longDesc;</div>
|
||||
<div id="ed_unknownProtocolFound">&unknownProtocolFound.longDesc;</div>
|
||||
<div id="ed_connectionFailure">&connectionFailure.longDesc;</div>
|
||||
<div id="ed_netTimeout">&netTimeout.longDesc;</div>
|
||||
<div id="ed_redirectLoop">&redirectLoop.longDesc;</div>
|
||||
<div id="ed_unknownSocketType">&unknownSocketType.longDesc;</div>
|
||||
<div id="ed_netReset">&netReset.longDesc;</div>
|
||||
<div id="ed_notCached">¬Cached.longDesc;</div>
|
||||
<div id="ed_netOffline">&netOffline.longDesc2;</div>
|
||||
<div id="ed_netInterrupt">&netInterrupt.longDesc;</div>
|
||||
<div id="ed_deniedPortAccess">&deniedPortAccess.longDesc;</div>
|
||||
<div id="ed_proxyResolveFailure">&proxyResolveFailure.longDesc;</div>
|
||||
<div id="ed_proxyConnectFailure">&proxyConnectFailure.longDesc;</div>
|
||||
<div id="ed_contentEncodingError">&contentEncodingError.longDesc;</div>
|
||||
<div id="ed_unsafeContentType">&unsafeContentType.longDesc;</div>
|
||||
<div id="ed_nssFailure2">&nssFailure2.longDesc2;</div>
|
||||
<div id="ed_nssBadCert">&certerror.introPara;</div>
|
||||
<div id="ed_cspBlocked">&cspBlocked.longDesc;</div>
|
||||
<div id="ed_remoteXUL">&remoteXUL.longDesc;</div>
|
||||
<div id="ed_corruptedContentErrorv2">&corruptedContentErrorv2.longDesc;</div>
|
||||
<div id="ed_sslv3Used">&sslv3Used.longDesc2;</div>
|
||||
<div id="ed_weakCryptoUsed">&weakCryptoUsed.longDesc2;</div>
|
||||
<div id="ed_inadequateSecurityError">&inadequateSecurityError.longDesc;</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- PAGE CONTAINER (for styling purposes only) -->
|
||||
<div id="errorPageContainer" class="container">
|
||||
|
||||
<!-- Error Title -->
|
||||
<div class="title">
|
||||
<h1 class="title-text"/>
|
||||
</div>
|
||||
|
||||
<!-- LONG CONTENT (the section most likely to require scrolling) -->
|
||||
<div id="errorLongContent">
|
||||
|
||||
<!-- Short Description -->
|
||||
<div id="errorShortDesc">
|
||||
<p id="errorShortDescText" />
|
||||
</div>
|
||||
<p id="badStsCertExplanation" hidden="true">&certerror.whatShouldIDo.badStsCertExplanation;</p>
|
||||
|
||||
<div id="wrongSystemTimePanel" style="display: none;">
|
||||
&certerror.wrongSystemTime;
|
||||
</div>
|
||||
|
||||
<!-- Long Description (Note: See netError.dtd for used XHTML tags) -->
|
||||
<div id="errorLongDesc" />
|
||||
|
||||
<div id="prefChangeContainer" class="button-container">
|
||||
<p>&prefReset.longDesc;</p>
|
||||
<button id="prefResetButton" class="primary" autocomplete="off">&prefReset.label;</button>
|
||||
</div>
|
||||
|
||||
<div id="certErrorAndCaptivePortalButtonContainer" class="button-container">
|
||||
<button id="returnButton" class="primary" autocomplete="off">&returnToPreviousPage.label;</button>
|
||||
<button id="openPortalLoginPageButton" class="primary" autocomplete="off">&openPortalLoginPage.label;</button>
|
||||
<div class="button-spacer"></div>
|
||||
<button id="advancedButton" autocomplete="off">&advanced.label;</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="netErrorButtonContainer" class="button-container">
|
||||
<button id="errorTryAgain" class="primary" autocomplete="off" onclick="retryThis(this);">&retry.label;</button>
|
||||
</div>
|
||||
|
||||
<div id="advancedPanelContainer">
|
||||
<div id="weakCryptoAdvancedPanel" class="advanced-panel">
|
||||
<div id="weakCryptoAdvancedDescription">
|
||||
<p>&weakCryptoAdvanced.longDesc;</p>
|
||||
</div>
|
||||
<div id="advancedLongDesc" />
|
||||
<div id="overrideWeakCryptoPanel">
|
||||
<a id="overrideWeakCrypto" href="#">&weakCryptoAdvanced.override;</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="badCertAdvancedPanel" class="advanced-panel">
|
||||
<p id="badCertTechnicalInfo"/>
|
||||
<button id="exceptionDialogButton">&securityOverride.exceptionButtonLabel;</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>
|
||||
BIN
browser/base/content/aboutRobots-icon.png
Normal file
|
After Width: | Height: | Size: 9.6 KiB |
BIN
browser/base/content/aboutRobots-widget-left.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
108
browser/base/content/aboutRobots.xhtml
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
<?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 % netErrorDTD
|
||||
SYSTEM "chrome://global/locale/netError.dtd">
|
||||
%netErrorDTD;
|
||||
<!ENTITY % globalDTD
|
||||
SYSTEM "chrome://global/locale/global.dtd">
|
||||
%globalDTD;
|
||||
<!ENTITY % aboutrobotsDTD
|
||||
SYSTEM "chrome://browser/locale/aboutRobots.dtd">
|
||||
%aboutrobotsDTD;
|
||||
]>
|
||||
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<title>&robots.pagetitle;</title>
|
||||
<link rel="stylesheet" href="chrome://global/skin/netError.css" type="text/css" media="all" />
|
||||
<link rel="icon" type="image/png" id="favicon" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8%2F9hAAAACGFjVEwAAAASAAAAAJNtBPIAAAAaZmNUTAAAAAAAAAAQAAAAEAAAAAAAAAAALuAD6AABhIDeugAAALhJREFUOI2Nk8sNxCAMRDlGohauXFOMpfTiAlxICqAELltHLqlgctg1InzMRhpFAc%2BLGWTnmoeZYamt78zXdZmaQtQMADlnU0OIAlbmJUBEcO4bRKQY2rUXIPmAGnDuG%2FBx3%2FfvOPVaDUg%2BoAPUf1PArIMCSD5glMEsUGaG%2BkyAFWIBaCsKuA%2BHGCNijLgP133XgOEtaPFMy2vUolEGJoCIzBmoRUR9%2B7rxj16DZaW%2FmgtmxnJ8V3oAnApQwNS5zpcAAAAaZmNUTAAAAAEAAAAQAAAAEAAAAAAAAAAAAB4D6AIB52fclgAAACpmZEFUAAAAAjiNY2AYBVhBc3Pzf2LEcGreqcbwH1kDNjHauWAUjAJyAADymxf9WF%2Bu8QAAABpmY1RMAAAAAwAAABAAAAAQAAAAAAAAAAAAHgPoAgEK8Q9%2FAAAAFmZkQVQAAAAEOI1jYBgFo2AUjAIIAAAEEAAB0xIn4wAAABpmY1RMAAAABQAAABAAAAAQAAAAAAAAAAAAHgPoAgHnO30FAAAAQGZkQVQAAAAGOI1jYBieYKcaw39ixHCC%2F6cwFWMTw2rz%2F1MM%2F6Vu%2Ff%2F%2F%2FxTD%2F51qEIwuRjsXILuEGLFRMApgAADhNCsVfozYcAAAABpmY1RMAAAABwAAABAAAAAQAAAAAAAAAAAAHgPoAgEKra7sAAAAFmZkQVQAAAAIOI1jYBgFo2AUjAIIAAAEEAABM9s3hAAAABpmY1RMAAAACQAAABAAAAAQAAAAAAAAAAAAHgPoAgHn3p%2BwAAAAKmZkQVQAAAAKOI1jYBgFWEFzc%2FN%2FYsRwat6pxvAfWQM2Mdq5YBSMAnIAAPKbF%2F1BhPl6AAAAGmZjVEwAAAALAAAAEAAAABAAAAAAAAAAAAAeA%2BgCAQpITFkAAAAWZmRBVAAAAAw4jWNgGAWjYBSMAggAAAQQAAHaszpmAAAAGmZjVEwAAAANAAAAEAAAABAAAAAAAAAAAAAeA%2BgCAeeCPiMAAABAZmRBVAAAAA44jWNgGJ5gpxrDf2LEcIL%2FpzAVYxPDavP%2FUwz%2FpW79%2F%2F%2F%2FFMP%2FnWoQjC5GOxcgu4QYsVEwCmAAAOE0KxUmBL0KAAAAGmZjVEwAAAAPAAAAEAAAABAAAAAAAAAAAAAeA%2BgCAQoU7coAAAAWZmRBVAAAABA4jWNgGAWjYBSMAggAAAQQAAEpOBELAAAAGmZjVEwAAAARAAAAEAAAABAAAAAAAAAAAAAeA%2BgCAeYVWtoAAAAqZmRBVAAAABI4jWNgGAVYQXNz839ixHBq3qnG8B9ZAzYx2rlgFIwCcgAA8psX%2FWvpAecAAAAaZmNUTAAAABMAAAAQAAAAEAAAAAAAAAAAAB4D6AIBC4OJMwAAABZmZEFUAAAAFDiNY2AYBaNgFIwCCAAABBAAAcBQHOkAAAAaZmNUTAAAABUAAAAQAAAAEAAAAAAAAAAAAB4D6AIB5kn7SQAAAEBmZEFUAAAAFjiNY2AYnmCnGsN%2FYsRwgv%2BnMBVjE8Nq8%2F9TDP%2Blbv3%2F%2F%2F8Uw%2F%2BdahCMLkY7FyC7hBixUTAKYAAA4TQrFc%2BcEoQAAAAaZmNUTAAAABcAAAAQAAAAEAAAAAAAAAAAAB4D6AIBC98ooAAAABZmZEFUAAAAGDiNY2AYBaNgFIwCCAAABBAAASCZDI4AAAAaZmNUTAAAABkAAAAQAAAAEAAAAAAAAAAAAB4D6AIB5qwZ%2FAAAACpmZEFUAAAAGjiNY2AYBVhBc3Pzf2LEcGreqcbwH1kDNjHauWAUjAJyAADymxf9cjJWbAAAABpmY1RMAAAAGwAAABAAAAAQAAAAAAAAAAAAHgPoAgELOsoVAAAAFmZkQVQAAAAcOI1jYBgFo2AUjAIIAAAEEAAByfEBbAAAABpmY1RMAAAAHQAAABAAAAAQAAAAAAAAAAAAHgPoAgHm8LhvAAAAQGZkQVQAAAAeOI1jYBieYKcaw39ixHCC%2F6cwFWMTw2rz%2F1MM%2F6Vu%2Ff%2F%2F%2FxTD%2F51qEIwuRjsXILuEGLFRMApgAADhNCsVlxR3%2FgAAABpmY1RMAAAAHwAAABAAAAAQAAAAAAAAAAAAHgPoAgELZmuGAAAAFmZkQVQAAAAgOI1jYBgFo2AUjAIIAAAEEAABHP5cFQAAABpmY1RMAAAAIQAAABAAAAAQAAAAAAAAAAAAHgPoAgHlgtAOAAAAKmZkQVQAAAAiOI1jYBgFWEFzc%2FN%2FYsRwat6pxvAfWQM2Mdq5YBSMAnIAAPKbF%2F0%2FMvDdAAAAAElFTkSuQmCC"/>
|
||||
|
||||
<script type="application/javascript"><![CDATA[
|
||||
var buttonClicked = false;
|
||||
function robotButton()
|
||||
{
|
||||
var button = document.getElementById('errorTryAgain');
|
||||
if (buttonClicked) {
|
||||
button.style.visibility = "hidden";
|
||||
} else {
|
||||
var newLabel = button.getAttribute("label2");
|
||||
button.textContent = newLabel;
|
||||
buttonClicked = true;
|
||||
}
|
||||
}
|
||||
]]></script>
|
||||
|
||||
<style type="text/css"><![CDATA[
|
||||
#errorPageContainer {
|
||||
background-image: none;
|
||||
}
|
||||
|
||||
#errorPageContainer:before {
|
||||
content: url('chrome://browser/content/aboutRobots-icon.png');
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
body[dir=rtl] #icon,
|
||||
body[dir=rtl] #errorPageContainer:before {
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
]]></style>
|
||||
</head>
|
||||
|
||||
<body dir="&locale.dir;">
|
||||
|
||||
<!-- PAGE CONTAINER (for styling purposes only) -->
|
||||
<div id="errorPageContainer">
|
||||
|
||||
<!-- Error Title -->
|
||||
<div id="errorTitle">
|
||||
<h1 id="errorTitleText">&robots.errorTitleText;</h1>
|
||||
</div>
|
||||
|
||||
<!-- LONG CONTENT (the section most likely to require scrolling) -->
|
||||
<div id="errorLongContent">
|
||||
|
||||
<!-- Short Description -->
|
||||
<div id="errorShortDesc">
|
||||
<p id="errorShortDescText">&robots.errorShortDescText;</p>
|
||||
</div>
|
||||
|
||||
<!-- Long Description (Note: See netError.dtd for used XHTML tags) -->
|
||||
<div id="errorLongDesc">
|
||||
<ul>
|
||||
<li>&robots.errorLongDesc1;</li>
|
||||
<li>&robots.errorLongDesc2;</li>
|
||||
<li>&robots.errorLongDesc3;</li>
|
||||
<li>&robots.errorLongDesc4;</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Short Description -->
|
||||
<div id="errorTrailerDesc">
|
||||
<p id="errorTrailerDescText">&robots.errorTrailerDescText;</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Button -->
|
||||
<button id="errorTryAgain"
|
||||
label2="&robots.dontpress;"
|
||||
onclick="robotButton();">&retry.label;</button>
|
||||
|
||||
<img src="chrome://browser/content/aboutRobots-widget-left.png"
|
||||
style="position: absolute; bottom: -12px; left: -10px;"/>
|
||||
<img src="chrome://browser/content/aboutRobots-widget-left.png"
|
||||
style="position: absolute; bottom: -12px; right: -10px; transform: scaleX(-1);"/>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
11
browser/base/content/aboutTabCrashed.css
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
html:not(.crashDumpSubmitted) #reportSent,
|
||||
html:not(.crashDumpAvailable) #reportBox,
|
||||
.container[multiple="true"] > .offers > #offerHelpMessageSingle,
|
||||
.container[multiple="false"] > .offers > #offerHelpMessageMultiple,
|
||||
.container[multiple="false"] > .button-container > #restoreAll {
|
||||
display: none;
|
||||
}
|
||||
309
browser/base/content/aboutTabCrashed.js
Normal file
|
|
@ -0,0 +1,309 @@
|
|||
/* 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 AboutTabCrashed = {
|
||||
/**
|
||||
* This can be set to true once this page receives a message from the
|
||||
* parent saying whether or not a crash report is available.
|
||||
*/
|
||||
hasReport: false,
|
||||
|
||||
/**
|
||||
* The messages that we might receive from the parent.
|
||||
*/
|
||||
MESSAGES: [
|
||||
"SetCrashReportAvailable",
|
||||
"CrashReportSent",
|
||||
"UpdateCount",
|
||||
],
|
||||
|
||||
/**
|
||||
* Items for which we will listen for click events.
|
||||
*/
|
||||
CLICK_TARGETS: [
|
||||
"closeTab",
|
||||
"restoreTab",
|
||||
"restoreAll",
|
||||
"sendReport",
|
||||
],
|
||||
|
||||
/**
|
||||
* Returns information about this crashed tab.
|
||||
*
|
||||
* @return (Object) An object with the following properties:
|
||||
* title (String):
|
||||
* The title of the page that crashed.
|
||||
* URL (String):
|
||||
* The URL of the page that crashed.
|
||||
*/
|
||||
get pageData() {
|
||||
delete this.pageData;
|
||||
|
||||
let URL = document.documentURI;
|
||||
let queryString = URL.replace(/^about:tabcrashed?e=tabcrashed/, "");
|
||||
|
||||
let titleMatch = queryString.match(/d=([^&]*)/);
|
||||
let URLMatch = queryString.match(/u=([^&]*)/);
|
||||
|
||||
return this.pageData = {
|
||||
title: titleMatch && titleMatch[1] ? decodeURIComponent(titleMatch[1]) : "",
|
||||
URL: URLMatch && URLMatch[1] ? decodeURIComponent(URLMatch[1]) : "",
|
||||
};
|
||||
},
|
||||
|
||||
init() {
|
||||
this.MESSAGES.forEach((msg) => addMessageListener(msg, this.receiveMessage.bind(this)));
|
||||
addEventListener("DOMContentLoaded", this);
|
||||
|
||||
document.title = this.pageData.title;
|
||||
},
|
||||
|
||||
receiveMessage(message) {
|
||||
switch (message.name) {
|
||||
case "UpdateCount": {
|
||||
this.setMultiple(message.data.count > 1);
|
||||
break;
|
||||
}
|
||||
case "SetCrashReportAvailable": {
|
||||
this.onSetCrashReportAvailable(message);
|
||||
break;
|
||||
}
|
||||
case "CrashReportSent": {
|
||||
this.onCrashReportSent();
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
handleEvent(event) {
|
||||
switch (event.type) {
|
||||
case "DOMContentLoaded": {
|
||||
this.onDOMContentLoaded();
|
||||
break;
|
||||
}
|
||||
case "click": {
|
||||
this.onClick(event);
|
||||
break;
|
||||
}
|
||||
case "input": {
|
||||
this.onInput(event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
onDOMContentLoaded() {
|
||||
this.CLICK_TARGETS.forEach((targetID) => {
|
||||
let el = document.getElementById(targetID);
|
||||
el.addEventListener("click", this);
|
||||
});
|
||||
|
||||
// For setting "emailMe" checkbox automatically on email value change.
|
||||
document.getElementById("email").addEventListener("input", this);
|
||||
|
||||
// Error pages are loaded as LOAD_BACKGROUND, so they don't get load events.
|
||||
let event = new CustomEvent("AboutTabCrashedLoad", {bubbles:true});
|
||||
document.dispatchEvent(event);
|
||||
|
||||
sendAsyncMessage("Load");
|
||||
},
|
||||
|
||||
onClick(event) {
|
||||
switch (event.target.id) {
|
||||
case "closeTab": {
|
||||
this.sendMessage("closeTab");
|
||||
break;
|
||||
}
|
||||
|
||||
case "restoreTab": {
|
||||
this.sendMessage("restoreTab");
|
||||
break;
|
||||
}
|
||||
|
||||
case "restoreAll": {
|
||||
this.sendMessage("restoreAll");
|
||||
break;
|
||||
}
|
||||
|
||||
case "sendReport": {
|
||||
this.showCrashReportUI(event.target.checked);
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
onInput(event) {
|
||||
switch (event.target.id) {
|
||||
case "email": {
|
||||
document.getElementById("emailMe").checked = !!event.target.value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
/**
|
||||
* After this page tells the parent that it has loaded, the parent
|
||||
* will respond with whether or not a crash report is available. This
|
||||
* method handles that message.
|
||||
*
|
||||
* @param message
|
||||
* The message from the parent, which should contain a data
|
||||
* Object property with the following properties:
|
||||
*
|
||||
* hasReport (bool):
|
||||
* Whether or not there is a crash report.
|
||||
*
|
||||
* sendReport (bool):
|
||||
* Whether or not the the user prefers to send the report
|
||||
* by default.
|
||||
*
|
||||
* includeURL (bool):
|
||||
* Whether or not the user prefers to send the URL of
|
||||
* the tab that crashed.
|
||||
*
|
||||
* emailMe (bool):
|
||||
* Whether or not to send the email address of the user
|
||||
* in the report.
|
||||
*
|
||||
* email (String):
|
||||
* The email address of the user (empty if emailMe is false).
|
||||
*
|
||||
* requestAutoSubmit (bool):
|
||||
* Whether or not we should ask the user to automatically
|
||||
* submit backlogged crash reports.
|
||||
*
|
||||
*/
|
||||
onSetCrashReportAvailable(message) {
|
||||
let data = message.data;
|
||||
|
||||
if (data.hasReport) {
|
||||
this.hasReport = true;
|
||||
document.documentElement.classList.add("crashDumpAvailable");
|
||||
|
||||
document.getElementById("sendReport").checked = data.sendReport;
|
||||
document.getElementById("includeURL").checked = data.includeURL;
|
||||
|
||||
if (data.requestEmail) {
|
||||
document.getElementById("requestEmail").hidden = false;
|
||||
document.getElementById("emailMe").checked = data.emailMe;
|
||||
if (data.emailMe) {
|
||||
document.getElementById("email").value = data.email;
|
||||
}
|
||||
}
|
||||
|
||||
this.showCrashReportUI(data.sendReport);
|
||||
} else {
|
||||
this.showCrashReportUI(false);
|
||||
}
|
||||
|
||||
if (data.requestAutoSubmit) {
|
||||
document.getElementById("requestAutoSubmit").hidden = false;
|
||||
}
|
||||
|
||||
let event = new CustomEvent("AboutTabCrashedReady", {bubbles:true});
|
||||
document.dispatchEvent(event);
|
||||
},
|
||||
|
||||
/**
|
||||
* Handler for when the parent reports that the crash report associated
|
||||
* with this about:tabcrashed page has been sent.
|
||||
*/
|
||||
onCrashReportSent() {
|
||||
document.documentElement.classList.remove("crashDumpAvailable");
|
||||
document.documentElement.classList.add("crashDumpSubmitted");
|
||||
},
|
||||
|
||||
/**
|
||||
* Toggles the display of the crash report form.
|
||||
*
|
||||
* @param shouldShow (bool)
|
||||
* True if the crash report form should be shown
|
||||
*/
|
||||
showCrashReportUI(shouldShow) {
|
||||
let options = document.getElementById("options");
|
||||
options.hidden = !shouldShow;
|
||||
},
|
||||
|
||||
/**
|
||||
* Toggles whether or not the page is one of several visible pages
|
||||
* showing the crash reporter. This controls some of the language
|
||||
* on the page, along with what the "primary" button is.
|
||||
*
|
||||
* @param hasMultiple (bool)
|
||||
* True if there are multiple crash report pages being shown.
|
||||
*/
|
||||
setMultiple(hasMultiple) {
|
||||
let main = document.getElementById("main");
|
||||
main.setAttribute("multiple", hasMultiple);
|
||||
|
||||
let restoreTab = document.getElementById("restoreTab");
|
||||
|
||||
// The "Restore All" button has the "primary" class by default, so
|
||||
// we only need to modify the "Restore Tab" button.
|
||||
if (hasMultiple) {
|
||||
restoreTab.classList.remove("primary");
|
||||
} else {
|
||||
restoreTab.classList.add("primary");
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Sends a message to the parent in response to the user choosing
|
||||
* one of the actions available on the page. This might also send up
|
||||
* crash report information if the user has chosen to submit a crash
|
||||
* report.
|
||||
*
|
||||
* @param messageName (String)
|
||||
* The message to send to the parent
|
||||
*/
|
||||
sendMessage(messageName) {
|
||||
let comments = "";
|
||||
let email = "";
|
||||
let URL = "";
|
||||
let sendReport = false;
|
||||
let emailMe = false;
|
||||
let includeURL = false;
|
||||
let autoSubmit = false;
|
||||
|
||||
if (this.hasReport) {
|
||||
sendReport = document.getElementById("sendReport").checked;
|
||||
if (sendReport) {
|
||||
comments = document.getElementById("comments").value.trim();
|
||||
|
||||
includeURL = document.getElementById("includeURL").checked;
|
||||
if (includeURL) {
|
||||
URL = this.pageData.URL.trim();
|
||||
}
|
||||
|
||||
if (!document.getElementById("requestEmail").hidden) {
|
||||
emailMe = document.getElementById("emailMe").checked;
|
||||
if (emailMe) {
|
||||
email = document.getElementById("email").value.trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let requestAutoSubmit = document.getElementById("requestAutoSubmit");
|
||||
if (requestAutoSubmit.hidden) {
|
||||
// The checkbox is hidden if the user has already opted in to sending
|
||||
// backlogged crash reports.
|
||||
autoSubmit = true;
|
||||
} else {
|
||||
autoSubmit = document.getElementById("autoSubmit").checked;
|
||||
}
|
||||
|
||||
sendAsyncMessage(messageName, {
|
||||
sendReport,
|
||||
comments,
|
||||
email,
|
||||
emailMe,
|
||||
includeURL,
|
||||
URL,
|
||||
autoSubmit,
|
||||
hasReport: this.hasReport,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
AboutTabCrashed.init();
|
||||
97
browser/base/content/aboutTabCrashed.xhtml
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
<?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 % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
|
||||
%brandDTD;
|
||||
<!ENTITY % tabCrashedDTD
|
||||
SYSTEM "chrome://browser/locale/aboutTabCrashed.dtd">
|
||||
%tabCrashedDTD;
|
||||
]>
|
||||
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<link rel="stylesheet" type="text/css" media="all"
|
||||
href="chrome://global/skin/in-content/info-pages.css"/>
|
||||
<link rel="stylesheet" type="text/css" media="all"
|
||||
href="chrome://browser/content/aboutTabCrashed.css"/>
|
||||
<link rel="stylesheet" type="text/css" media="all"
|
||||
href="chrome://browser/skin/aboutTabCrashed.css"/>
|
||||
</head>
|
||||
|
||||
<body dir="&locale.dir;">
|
||||
<div id="main" class="container" multiple="false">
|
||||
|
||||
<div class="title">
|
||||
<h1 class="title-text">&tabCrashed.header2;</h1>
|
||||
</div>
|
||||
|
||||
<div class="offers">
|
||||
<h2>&tabCrashed.offerHelp;</h2>
|
||||
<p id="offerHelpMessageSingle">&tabCrashed.single.offerHelpMessage;</p>
|
||||
<p id="offerHelpMessageMultiple">&tabCrashed.multiple.offerHelpMessage;</p>
|
||||
</div>
|
||||
|
||||
<div id="reportBox">
|
||||
<h2>&tabCrashed.requestHelp;</h2>
|
||||
<p>&tabCrashed.requestHelpMessage;</p>
|
||||
|
||||
<h2>&tabCrashed.requestReport;</h2>
|
||||
|
||||
<div class="checkbox-with-label">
|
||||
<input type="checkbox" id="sendReport"/>
|
||||
<label for="sendReport">&tabCrashed.sendReport2;</label>
|
||||
</div>
|
||||
|
||||
<ul id="options">
|
||||
<li>
|
||||
<textarea id="comments" placeholder="&tabCrashed.commentPlaceholder2;" rows="4"></textarea>
|
||||
</li>
|
||||
|
||||
<li class="checkbox-with-label">
|
||||
<input type="checkbox" id="includeURL"/>
|
||||
<label for="includeURL">&tabCrashed.includeURL2;</label>
|
||||
</li>
|
||||
|
||||
<li id="requestEmail" hidden="true">
|
||||
<div class="checkbox-with-label">
|
||||
<input type="checkbox" id="emailMe"/>
|
||||
<label for="emailMe">&tabCrashed.emailMe;</label>
|
||||
</div>
|
||||
<input type="text" id="email" placeholder="&tabCrashed.emailPlaceholder;"/>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div id="requestAutoSubmit" hidden="true">
|
||||
<h2>&tabCrashed.requestAutoSubmit2;</h2>
|
||||
<div class="checkbox-with-label">
|
||||
<input type="checkbox" id="autoSubmit"/>
|
||||
<label for="autoSubmit">&tabCrashed.autoSubmit;</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p id="reportSent">&tabCrashed.reportSent;</p>
|
||||
|
||||
<div class="button-container">
|
||||
<button id="closeTab">
|
||||
&tabCrashed.closeTab;</button>
|
||||
<button id="restoreTab" class="primary">
|
||||
&tabCrashed.restoreTab;</button>
|
||||
<button id="restoreAll" autofocus="true" class="primary">
|
||||
&tabCrashed.restoreAll;</button>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
<script type="text/javascript;version=1.8" src="chrome://browser/content/aboutTabCrashed.js"/>
|
||||
</html>
|
||||
15
browser/base/content/abouthealthreport/abouthealth.css
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
html, body {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#remote-report {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 0;
|
||||
display: flex;
|
||||
}
|
||||
134
browser/base/content/abouthealthreport/abouthealth.js
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
/* 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";
|
||||
|
||||
var {classes: Cc, interfaces: Ci, utils: Cu} = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/Preferences.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
const prefs = new Preferences("datareporting.healthreport.");
|
||||
|
||||
const PREF_REPORTING_URL = "datareporting.healthreport.about.reportUrl";
|
||||
|
||||
var healthReportWrapper = {
|
||||
init: function () {
|
||||
let iframe = document.getElementById("remote-report");
|
||||
iframe.addEventListener("load", healthReportWrapper.initRemotePage, false);
|
||||
iframe.src = this._getReportURI().spec;
|
||||
prefs.observe("uploadEnabled", this.updatePrefState, healthReportWrapper);
|
||||
},
|
||||
|
||||
uninit: function () {
|
||||
prefs.ignore("uploadEnabled", this.updatePrefState, healthReportWrapper);
|
||||
},
|
||||
|
||||
_getReportURI: function () {
|
||||
let url = Services.urlFormatter.formatURLPref(PREF_REPORTING_URL);
|
||||
return Services.io.newURI(url, null, null);
|
||||
},
|
||||
|
||||
setDataSubmission: function (enabled) {
|
||||
MozSelfSupport.healthReportDataSubmissionEnabled = enabled;
|
||||
this.updatePrefState();
|
||||
},
|
||||
|
||||
updatePrefState: function () {
|
||||
try {
|
||||
let prefs = {
|
||||
enabled: MozSelfSupport.healthReportDataSubmissionEnabled,
|
||||
};
|
||||
healthReportWrapper.injectData("prefs", prefs);
|
||||
}
|
||||
catch (ex) {
|
||||
healthReportWrapper.reportFailure(healthReportWrapper.ERROR_PREFS_FAILED);
|
||||
}
|
||||
},
|
||||
|
||||
sendCurrentEnvironment: function () {
|
||||
},
|
||||
|
||||
sendCurrentPingData: function () {
|
||||
},
|
||||
|
||||
injectData: function (type, content) {
|
||||
let report = this._getReportURI();
|
||||
|
||||
// file URIs can't be used for targetOrigin, so we use "*" for this special case
|
||||
// in all other cases, pass in the URL to the report so we properly restrict the message dispatch
|
||||
let reportUrl = report.scheme == "file" ? "*" : report.spec;
|
||||
|
||||
let data = {
|
||||
type: type,
|
||||
content: content
|
||||
}
|
||||
|
||||
let iframe = document.getElementById("remote-report");
|
||||
iframe.contentWindow.postMessage(data, reportUrl);
|
||||
},
|
||||
|
||||
handleRemoteCommand: function (evt) {
|
||||
// Do an origin check to harden against the frame content being loaded from unexpected locations.
|
||||
let allowedPrincipal = Services.scriptSecurityManager.getCodebasePrincipal(this._getReportURI());
|
||||
let targetPrincipal = evt.target.nodePrincipal;
|
||||
if (!allowedPrincipal.equals(targetPrincipal)) {
|
||||
Cu.reportError(`Origin check failed for message "${evt.detail.command}": ` +
|
||||
`target origin is "${targetPrincipal.origin}", expected "${allowedPrincipal.origin}"`);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (evt.detail.command) {
|
||||
case "DisableDataSubmission":
|
||||
this.setDataSubmission(false);
|
||||
break;
|
||||
case "EnableDataSubmission":
|
||||
this.setDataSubmission(true);
|
||||
break;
|
||||
case "RequestCurrentPrefs":
|
||||
this.updatePrefState();
|
||||
break;
|
||||
case "RequestCurrentEnvironment":
|
||||
this.sendCurrentEnvironment();
|
||||
break;
|
||||
case "RequestCurrentPingData":
|
||||
this.sendCurrentPingData();
|
||||
break;
|
||||
default:
|
||||
Cu.reportError("Unexpected remote command received: " + evt.detail.command + ". Ignoring command.");
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
initRemotePage: function () {
|
||||
let iframe = document.getElementById("remote-report").contentDocument;
|
||||
iframe.addEventListener("RemoteHealthReportCommand",
|
||||
function onCommand(e) { healthReportWrapper.handleRemoteCommand(e); },
|
||||
false);
|
||||
healthReportWrapper.updatePrefState();
|
||||
},
|
||||
|
||||
// error handling
|
||||
ERROR_INIT_FAILED: 1,
|
||||
ERROR_PAYLOAD_FAILED: 2,
|
||||
ERROR_PREFS_FAILED: 3,
|
||||
|
||||
reportFailure: function (error) {
|
||||
let details = {
|
||||
errorType: error,
|
||||
}
|
||||
healthReportWrapper.injectData("error", details);
|
||||
},
|
||||
|
||||
handleInitFailure: function () {
|
||||
healthReportWrapper.reportFailure(healthReportWrapper.ERROR_INIT_FAILED);
|
||||
},
|
||||
|
||||
handlePayloadFailure: function () {
|
||||
healthReportWrapper.reportFailure(healthReportWrapper.ERROR_PAYLOAD_FAILED);
|
||||
},
|
||||
}
|
||||
|
||||
window.addEventListener("load", function () { healthReportWrapper.init(); });
|
||||
window.addEventListener("unload", function () { healthReportWrapper.uninit(); });
|
||||
31
browser/base/content/abouthealthreport/abouthealth.xhtml
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
<?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 % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
|
||||
%brandDTD;
|
||||
<!ENTITY % securityPrefsDTD SYSTEM "chrome://browser/locale/preferences/security.dtd">
|
||||
%securityPrefsDTD;
|
||||
<!ENTITY % aboutHealthReportDTD SYSTEM "chrome://browser/locale/aboutHealthReport.dtd">
|
||||
%aboutHealthReportDTD;
|
||||
]>
|
||||
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<title>&abouthealth.pagetitle;</title>
|
||||
<link rel="icon" type="image/png" id="favicon"
|
||||
href="chrome://branding/content/icon32.png"/>
|
||||
<link rel="stylesheet"
|
||||
href="chrome://browser/content/abouthealthreport/abouthealth.css"
|
||||
type="text/css" />
|
||||
<script type="text/javascript;version=1.8"
|
||||
src="chrome://browser/content/abouthealthreport/abouthealth.js" />
|
||||
</head>
|
||||
<body>
|
||||
<iframe id="remote-report"/>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
444
browser/base/content/abouthome/aboutHome.css
Normal file
|
|
@ -0,0 +1,444 @@
|
|||
%if 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/. */
|
||||
%endif
|
||||
|
||||
html {
|
||||
font: message-box;
|
||||
background-color: hsl(0,0%,95%);
|
||||
color: #000;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
display: -moz-box;
|
||||
-moz-box-orient: vertical;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-weight: normal !important
|
||||
}
|
||||
|
||||
input,
|
||||
button {
|
||||
font-size: inherit;
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
a {
|
||||
color: -moz-nativehyperlinktext;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
#topSection {
|
||||
text-align: center;
|
||||
margin-top: 100px;
|
||||
}
|
||||
|
||||
#brandLogo {
|
||||
height: 192px;
|
||||
width: 192px;
|
||||
margin: 22px auto 31px;
|
||||
background-image: url("chrome://branding/content/about-logo.png");
|
||||
background-size: 192px auto;
|
||||
background-position: center center;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
#searchIconAndTextContainer {
|
||||
width: 470px;
|
||||
}
|
||||
|
||||
#searchIconAndTextContainer {
|
||||
display: -moz-box;
|
||||
height: 36px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#searchIcon {
|
||||
border: 1px transparent;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
background: url("chrome://browser/skin/search-indicator-magnifying-glass.svg") center center no-repeat;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
#searchText {
|
||||
margin-left: 0;
|
||||
-moz-box-flex: 1;
|
||||
padding-top: 6px;
|
||||
padding-bottom: 6px;
|
||||
padding-inline-start: 34px;
|
||||
padding-inline-end: 8px;
|
||||
background: hsla(0,0%,100%,.9) padding-box;
|
||||
border: 1px solid;
|
||||
border-radius: 2px 0 0 2px;
|
||||
border-color: hsla(210,54%,20%,.15) hsla(210,54%,20%,.17) hsla(210,54%,20%,.2);
|
||||
box-shadow: 0 1px 0 hsla(210,65%,9%,.02) inset,
|
||||
0 0 2px hsla(210,65%,9%,.1) inset,
|
||||
0 1px 0 hsla(0,0%,100%,.2);
|
||||
color: inherit;
|
||||
unicode-bidi: plaintext;
|
||||
}
|
||||
|
||||
#searchText:dir(rtl) {
|
||||
border-radius: 0 2px 2px 0;
|
||||
}
|
||||
|
||||
#searchText[aria-expanded="true"] {
|
||||
border-radius: 2px 0 0 0;
|
||||
}
|
||||
|
||||
#searchText[aria-expanded="true"]:dir(rtl) {
|
||||
border-radius: 0 2px 0 0;
|
||||
}
|
||||
|
||||
#searchText[keepfocus],
|
||||
#searchText:focus,
|
||||
#searchText[autofocus] {
|
||||
border-color: hsla(206,100%,60%,.6) hsla(206,76%,52%,.6) hsla(204,100%,40%,.6);
|
||||
}
|
||||
|
||||
#searchSubmit {
|
||||
margin-inline-start: -1px;
|
||||
color: transparent;
|
||||
background: url("chrome://browser/skin/search-arrow-go.svg#search-arrow-go") center center no-repeat, linear-gradient(hsla(0,0%,100%,.8), hsla(0,0%,100%,.1)) padding-box;
|
||||
padding: 0;
|
||||
border: 1px solid;
|
||||
border-color: hsla(210,54%,20%,.15) hsla(210,54%,20%,.17) hsla(210,54%,20%,.2);
|
||||
border-radius: 0 2px 2px 0;
|
||||
border-inline-start: 1px solid transparent;
|
||||
box-shadow: 0 0 2px hsla(0,0%,100%,.5) inset,
|
||||
0 1px 0 hsla(0,0%,100%,.2);
|
||||
cursor: pointer;
|
||||
transition-property: background-color, border-color, box-shadow;
|
||||
transition-duration: 150ms;
|
||||
width: 50px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
#searchSubmit:dir(rtl) {
|
||||
border-radius: 2px 0 0 2px;
|
||||
background-image: url("chrome://browser/skin/search-arrow-go.svg#search-arrow-go-rtl"), linear-gradient(hsla(0,0%,100%,.8), hsla(0,0%,100%,.1));
|
||||
}
|
||||
|
||||
#searchText:focus + #searchSubmit,
|
||||
#searchText[keepfocus] + #searchSubmit,
|
||||
#searchText + #searchSubmit:hover,
|
||||
#searchText[autofocus] + #searchSubmit {
|
||||
border-color: #59b5fc #45a3e7 #3294d5;
|
||||
}
|
||||
|
||||
#searchText:focus + #searchSubmit,
|
||||
#searchText[keepfocus] + #searchSubmit,
|
||||
#searchText[autofocus] + #searchSubmit {
|
||||
background-image: url("chrome://browser/skin/search-arrow-go.svg#search-arrow-go-inverted"), linear-gradient(#4cb1ff, #1793e5);
|
||||
box-shadow: 0 1px 0 hsla(0,0%,100%,.2) inset,
|
||||
0 0 0 1px hsla(0,0%,100%,.1) inset,
|
||||
0 1px 0 hsla(210,54%,20%,.03);
|
||||
}
|
||||
|
||||
#searchText:focus + #searchSubmit:dir(rtl),
|
||||
#searchText[keepfocus] + #searchSubmit:dir(rtl),
|
||||
#searchText[autofocus] + #searchSubmit:dir(rtl) {
|
||||
background-image: url("chrome://browser/skin/search-arrow-go.svg#search-arrow-go-rtl-inverted"), linear-gradient(#4cb1ff, #1793e5);
|
||||
}
|
||||
|
||||
#searchText + #searchSubmit:hover {
|
||||
background-image: url("chrome://browser/skin/search-arrow-go.svg#search-arrow-go-inverted"), linear-gradient(#66bdff, #0d9eff);
|
||||
box-shadow: 0 1px 0 hsla(0,0%,100%,.2) inset,
|
||||
0 0 0 1px hsla(0,0%,100%,.1) inset,
|
||||
0 1px 0 hsla(210,54%,20%,.03),
|
||||
0 0 4px hsla(206,100%,20%,.2);
|
||||
}
|
||||
|
||||
#searchText + #searchSubmit:dir(rtl):hover {
|
||||
background-image: url("chrome://browser/skin/search-arrow-go.svg#search-arrow-go-rtl-inverted"), linear-gradient(#66bdff, #0d9eff);
|
||||
}
|
||||
|
||||
#searchText + #searchSubmit:hover:active {
|
||||
box-shadow: 0 1px 1px hsla(211,79%,6%,.1) inset,
|
||||
0 0 1px hsla(211,79%,6%,.2) inset;
|
||||
transition-duration: 0ms;
|
||||
}
|
||||
|
||||
#launcher {
|
||||
display: -moz-box;
|
||||
-moz-box-align: center;
|
||||
-moz-box-pack: center;
|
||||
width: 100%;
|
||||
background-color: hsla(0,0%,0%,.03);
|
||||
border-top: 1px solid hsla(0,0%,0%,.03);
|
||||
box-shadow: 0 1px 2px hsla(0,0%,0%,.02) inset,
|
||||
0 -1px 0 hsla(0,0%,100%,.25);
|
||||
}
|
||||
|
||||
#launcher:not([session]),
|
||||
body[narrow] #launcher[session] {
|
||||
display: block; /* display separator and restore button on separate lines */
|
||||
text-align: center;
|
||||
white-space: nowrap; /* prevent navigational buttons from wrapping */
|
||||
}
|
||||
|
||||
.launchButton {
|
||||
display: -moz-box;
|
||||
-moz-box-orient: vertical;
|
||||
margin: 16px 1px;
|
||||
padding: 14px 6px;
|
||||
min-width: 88px;
|
||||
max-width: 176px;
|
||||
max-height: 85px;
|
||||
vertical-align: top;
|
||||
white-space: normal;
|
||||
background: transparent padding-box;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 2px;
|
||||
color: #525c66;
|
||||
font-size: 75%;
|
||||
cursor: pointer;
|
||||
transition-property: background-color, border-color, box-shadow;
|
||||
transition-duration: 150ms;
|
||||
}
|
||||
|
||||
body[narrow] #launcher[session] > .launchButton {
|
||||
margin: 4px 1px;
|
||||
}
|
||||
|
||||
.launchButton:hover {
|
||||
background-color: hsla(211,79%,6%,.03);
|
||||
border-color: hsla(210,54%,20%,.15) hsla(210,54%,20%,.17) hsla(210,54%,20%,.2);
|
||||
}
|
||||
|
||||
.launchButton:hover:active {
|
||||
background-image: linear-gradient(hsla(211,79%,6%,.02), hsla(211,79%,6%,.05));
|
||||
border-color: hsla(210,54%,20%,.2) hsla(210,54%,20%,.23) hsla(210,54%,20%,.25);
|
||||
box-shadow: 0 1px 1px hsla(211,79%,6%,.05) inset,
|
||||
0 0 1px hsla(211,79%,6%,.1) inset;
|
||||
transition-duration: 0ms;
|
||||
}
|
||||
|
||||
.launchButton[hidden],
|
||||
#launcher:not([session]) > #restorePreviousSessionSeparator,
|
||||
#launcher:not([session]) > #restorePreviousSession {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#restorePreviousSessionSeparator {
|
||||
width: 3px;
|
||||
height: 116px;
|
||||
margin: 0 10px;
|
||||
background-image: linear-gradient(hsla(0,0%,100%,0), hsla(0,0%,100%,.35), hsla(0,0%,100%,0)),
|
||||
linear-gradient(hsla(211,79%,6%,0), hsla(211,79%,6%,.2), hsla(211,79%,6%,0)),
|
||||
linear-gradient(hsla(0,0%,100%,0), hsla(0,0%,100%,.35), hsla(0,0%,100%,0));
|
||||
background-position: left top, center, right bottom;
|
||||
background-size: 1px auto;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
body[narrow] #restorePreviousSessionSeparator {
|
||||
margin: 0 auto;
|
||||
width: 512px;
|
||||
height: 3px;
|
||||
background-image: linear-gradient(to right, hsla(0,0%,100%,0), hsla(0,0%,100%,.35), hsla(0,0%,100%,0)),
|
||||
linear-gradient(to right, hsla(211,79%,6%,0), hsla(211,79%,6%,.2), hsla(211,79%,6%,0)),
|
||||
linear-gradient(to right, hsla(0,0%,100%,0), hsla(0,0%,100%,.35), hsla(0,0%,100%,0));
|
||||
background-size: auto 1px;
|
||||
}
|
||||
|
||||
#restorePreviousSession {
|
||||
max-width: none;
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
body[narrow] #restorePreviousSession {
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
.launchButton::before {
|
||||
display: block;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
margin: 0 auto 6px;
|
||||
line-height: 0; /* remove extra vertical space due to non-zero font-size */
|
||||
}
|
||||
|
||||
#downloads::before {
|
||||
content: url("chrome://browser/content/abouthome/downloads.png");
|
||||
}
|
||||
|
||||
#bookmarks::before {
|
||||
content: url("chrome://browser/content/abouthome/bookmarks.png");
|
||||
}
|
||||
|
||||
#history::before {
|
||||
content: url("chrome://browser/content/abouthome/history.png");
|
||||
}
|
||||
|
||||
#addons::before {
|
||||
content: url("chrome://browser/content/abouthome/addons.png");
|
||||
}
|
||||
|
||||
%ifdef MOZ_SERVICES_SYNC
|
||||
#sync::before {
|
||||
content: url("chrome://browser/content/abouthome/sync.png");
|
||||
}
|
||||
%endif
|
||||
|
||||
#settings::before {
|
||||
content: url("chrome://browser/content/abouthome/settings.png");
|
||||
}
|
||||
|
||||
#restorePreviousSession::before {
|
||||
content: url("chrome://browser/content/abouthome/restore-large.png");
|
||||
height: 48px;
|
||||
width: 48px;
|
||||
display: inline-block; /* display on same line as text label */
|
||||
vertical-align: middle;
|
||||
margin-bottom: 0;
|
||||
margin-inline-end: 8px;
|
||||
}
|
||||
|
||||
#restorePreviousSession:dir(rtl)::before {
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
|
||||
body[narrow] #restorePreviousSession::before {
|
||||
content: url("chrome://browser/content/abouthome/restore.png");
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
}
|
||||
|
||||
#aboutMozilla {
|
||||
display: block;
|
||||
position: relative; /* pin wordmark to edge of document, not of viewport */
|
||||
-moz-box-ordinal-group: 0;
|
||||
opacity: .5;
|
||||
transition: opacity 150ms;
|
||||
}
|
||||
|
||||
#aboutMozilla:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
#aboutMozilla::before {
|
||||
content: url("chrome://browser/content/abouthome/mozilla.png");
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
right: 12px;
|
||||
width: 69px;
|
||||
height: 19px;
|
||||
}
|
||||
|
||||
/* [HiDPI]
|
||||
* At resolutions above 1dppx, prefer downscaling the 2x Retina graphics
|
||||
* rather than upscaling the original-size ones (bug 818940).
|
||||
*/
|
||||
@media not all and (max-resolution: 1dppx) {
|
||||
#brandLogo {
|
||||
background-image: url("chrome://branding/content/about-logo@2x.png");
|
||||
}
|
||||
|
||||
.launchButton::before,
|
||||
#aboutMozilla::before {
|
||||
transform: scale(.5);
|
||||
transform-origin: 0 0;
|
||||
}
|
||||
|
||||
.launchButton:dir(rtl)::before,
|
||||
#aboutMozilla:dir(rtl)::before {
|
||||
transform: scale(.5) translateX(32px);
|
||||
}
|
||||
|
||||
#downloads::before {
|
||||
content: url("chrome://browser/content/abouthome/downloads@2x.png");
|
||||
}
|
||||
|
||||
#bookmarks::before {
|
||||
content: url("chrome://browser/content/abouthome/bookmarks@2x.png");
|
||||
}
|
||||
|
||||
#history::before {
|
||||
content: url("chrome://browser/content/abouthome/history@2x.png");
|
||||
}
|
||||
|
||||
#addons::before {
|
||||
content: url("chrome://browser/content/abouthome/addons@2x.png");
|
||||
}
|
||||
|
||||
%ifdef MOZ_SERVICES_SYNC
|
||||
#sync::before {
|
||||
content: url("chrome://browser/content/abouthome/sync@2x.png");
|
||||
}
|
||||
%endif
|
||||
|
||||
#settings::before {
|
||||
content: url("chrome://browser/content/abouthome/settings@2x.png");
|
||||
}
|
||||
|
||||
#restorePreviousSession::before {
|
||||
content: url("chrome://browser/content/abouthome/restore-large@2x.png");
|
||||
}
|
||||
|
||||
body[narrow] #restorePreviousSession::before {
|
||||
content: url("chrome://browser/content/abouthome/restore@2x.png");
|
||||
}
|
||||
|
||||
#restorePreviousSession:dir(rtl)::before {
|
||||
transform: scale(-0.5, 0.5) translateX(24px);
|
||||
transform-origin: top center;
|
||||
}
|
||||
|
||||
#aboutMozilla::before {
|
||||
content: url("chrome://browser/content/abouthome/mozilla@2x.png");
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
html {
|
||||
background-color: #1f2328;
|
||||
color: #f1f4f7;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #75bbff;
|
||||
}
|
||||
|
||||
#topSection h1 {
|
||||
color: #f1f4f7;
|
||||
}
|
||||
|
||||
#searchIcon {
|
||||
filter: invert(100%) brightness(1.05);
|
||||
}
|
||||
|
||||
#searchText {
|
||||
background: #2b3037 padding-box;
|
||||
border-color: #4b535d;
|
||||
color: #f1f4f7;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
#searchText[keepfocus],
|
||||
#searchText:focus,
|
||||
#searchText[autofocus] {
|
||||
border-color: #4aa3ff;
|
||||
}
|
||||
|
||||
#searchSubmit {
|
||||
background: url("chrome://browser/skin/search-arrow-go.svg#search-arrow-go-inverted") center center no-repeat, linear-gradient(#3e454f, #30363d) padding-box;
|
||||
border-color: #4b535d;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
#searchSubmit:dir(rtl) {
|
||||
background-image: url("chrome://browser/skin/search-arrow-go.svg#search-arrow-go-rtl-inverted"), linear-gradient(#3e454f, #30363d);
|
||||
}
|
||||
}
|
||||
|
||||
147
browser/base/content/abouthome/aboutHome.js
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
/* 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";
|
||||
|
||||
/* import-globals-from ../contentSearchUI.js */
|
||||
|
||||
// IndexedDB storage constants.
|
||||
const DATABASE_NAME = "abouthome";
|
||||
const DATABASE_VERSION = 1;
|
||||
const DATABASE_STORAGE = "persistent";
|
||||
var searchText;
|
||||
|
||||
// This global tracks if the page has been set up before, to prevent double inits
|
||||
var gInitialized = false;
|
||||
var gObserver = new MutationObserver(function (mutations) {
|
||||
for (let mutation of mutations) {
|
||||
// The addition of the restore session button changes our width:
|
||||
if (mutation.attributeName == "session") {
|
||||
fitToWidth();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener("pageshow", function () {
|
||||
// Delay search engine setup, cause browser.js::BrowserOnAboutPageLoad runs
|
||||
// later and may use asynchronous getters.
|
||||
window.gObserver.observe(document.documentElement, { attributes: true });
|
||||
let launcher = document.getElementById("launcher");
|
||||
if (launcher) {
|
||||
window.gObserver.observe(launcher, { attributes: true });
|
||||
}
|
||||
fitToWidth();
|
||||
setupSearch();
|
||||
window.addEventListener("resize", fitToWidth);
|
||||
|
||||
// Ask chrome to update snippets.
|
||||
var event = new CustomEvent("AboutHomeLoad", {bubbles:true});
|
||||
document.dispatchEvent(event);
|
||||
});
|
||||
|
||||
window.addEventListener("pagehide", function() {
|
||||
window.gObserver.disconnect();
|
||||
window.removeEventListener("resize", fitToWidth);
|
||||
});
|
||||
|
||||
window.addEventListener("keypress", ev => {
|
||||
if (ev.defaultPrevented) {
|
||||
return;
|
||||
}
|
||||
|
||||
// don't focus the search-box on keypress if something other than the
|
||||
// body or document element has focus - don't want to steal input from other elements
|
||||
// Make an exception for <a> and <button> elements (and input[type=button|submit])
|
||||
// which don't usefully take keypresses anyway.
|
||||
// (except space, which is handled below)
|
||||
if (document.activeElement && document.activeElement != document.body &&
|
||||
document.activeElement != document.documentElement &&
|
||||
!["a", "button"].includes(document.activeElement.localName) &&
|
||||
!document.activeElement.matches("input:-moz-any([type=button],[type=submit])")) {
|
||||
return;
|
||||
}
|
||||
|
||||
let modifiers = ev.ctrlKey + ev.altKey + ev.metaKey;
|
||||
// ignore Ctrl/Cmd/Alt, but not Shift
|
||||
// also ignore Tab, Insert, PageUp, etc., and Space
|
||||
if (modifiers != 0 || ev.charCode == 0 || ev.charCode == 32)
|
||||
return;
|
||||
|
||||
searchText.focus();
|
||||
// need to send the first keypress outside the search-box manually to it
|
||||
searchText.value += ev.key;
|
||||
});
|
||||
|
||||
function onSearchSubmit(aEvent)
|
||||
{
|
||||
if (aEvent instanceof MouseEvent && gContentSearchController &&
|
||||
gContentSearchController.defaultEngine) {
|
||||
gContentSearchController.search(aEvent);
|
||||
return;
|
||||
}
|
||||
|
||||
// Fallback path for builds where ContentSearch mediation fails.
|
||||
let terms = searchText && searchText.value ? searchText.value.trim() : "";
|
||||
if (!terms) {
|
||||
return;
|
||||
}
|
||||
|
||||
document.dispatchEvent(new CustomEvent("AboutHomeSearch", {
|
||||
bubbles: true,
|
||||
detail: { searchString: terms },
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
var gContentSearchController;
|
||||
|
||||
function setupSearch()
|
||||
{
|
||||
let searchSubmit = document.getElementById("searchSubmit");
|
||||
|
||||
// Set submit button label for when CSS background are disabled (e.g.
|
||||
// high contrast mode).
|
||||
searchSubmit.value =
|
||||
document.body.getAttribute("dir") == "ltr" ? "\u25B6" : "\u25C0";
|
||||
searchSubmit.addEventListener("click", onSearchSubmit, true);
|
||||
|
||||
// The "autofocus" attribute doesn't focus the form element
|
||||
// immediately when the element is first drawn, so the
|
||||
// attribute is also used for styling when the page first loads.
|
||||
searchText = document.getElementById("searchText");
|
||||
searchText.addEventListener("blur", function searchText_onBlur() {
|
||||
searchText.removeEventListener("blur", searchText_onBlur);
|
||||
searchText.removeAttribute("autofocus");
|
||||
});
|
||||
|
||||
if (!gContentSearchController) {
|
||||
gContentSearchController =
|
||||
new ContentSearchUIController(searchText, searchText.parentNode,
|
||||
"abouthome", "homepage");
|
||||
}
|
||||
|
||||
searchText.addEventListener("keypress", function(event) {
|
||||
if (event.keyCode == event.DOM_VK_RETURN) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
onSearchSubmit(event);
|
||||
}
|
||||
}, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inform the test harness that we're done loading the page.
|
||||
*/
|
||||
function loadCompleted()
|
||||
{
|
||||
}
|
||||
|
||||
function fitToWidth() {
|
||||
if (document.documentElement.scrollWidth > window.innerWidth) {
|
||||
document.body.setAttribute("narrow", "true");
|
||||
} else if (document.body.hasAttribute("narrow")) {
|
||||
document.body.removeAttribute("narrow");
|
||||
fitToWidth();
|
||||
}
|
||||
}
|
||||
51
browser/base/content/abouthome/aboutHome.xhtml
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
<?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 % aboutHomeDTD SYSTEM "chrome://browser/locale/aboutHome.dtd">
|
||||
%aboutHomeDTD;
|
||||
<!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd" >
|
||||
%browserDTD;
|
||||
]>
|
||||
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<title>&abouthome.pageTitle;</title>
|
||||
|
||||
<link rel="icon" type="image/png" id="favicon"
|
||||
href="chrome://branding/content/icon32.png"/>
|
||||
<link rel="stylesheet" type="text/css" media="all"
|
||||
href="chrome://browser/content/contentSearchUI.css"/>
|
||||
<link rel="stylesheet" type="text/css" media="all" defer="defer"
|
||||
href="chrome://browser/content/abouthome/aboutHome.css"/>
|
||||
|
||||
<script type="text/javascript;version=1.8"
|
||||
src="chrome://browser/content/abouthome/aboutHome.js"/>
|
||||
<script type="text/javascript;version=1.8"
|
||||
src="chrome://browser/content/contentSearchUI.js"/>
|
||||
</head>
|
||||
|
||||
<body dir="&locale.dir;">
|
||||
<div class="spacer"/>
|
||||
<div id="topSection">
|
||||
<h1>Where to next?</h1>
|
||||
<div id="searchIconAndTextContainer">
|
||||
<div id="searchIcon"/>
|
||||
<input type="text" name="q" value="" id="searchText" maxlength="256"
|
||||
aria-label="&contentSearchInput.label;" autofocus="autofocus"/>
|
||||
<input id="searchSubmit" type="button"
|
||||
title="&contentSearchSubmit.tooltip;"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
BIN
browser/base/content/abouthome/addons.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
browser/base/content/abouthome/addons@2x.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
BIN
browser/base/content/abouthome/bookmarks.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
browser/base/content/abouthome/bookmarks@2x.png
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
browser/base/content/abouthome/downloads.png
Normal file
|
After Width: | Height: | Size: 898 B |
BIN
browser/base/content/abouthome/downloads@2x.png
Normal file
|
After Width: | Height: | Size: 2 KiB |
BIN
browser/base/content/abouthome/history.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
browser/base/content/abouthome/history@2x.png
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
browser/base/content/abouthome/mozilla.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
browser/base/content/abouthome/mozilla@2x.png
Normal file
|
After Width: | Height: | Size: 5.5 KiB |
BIN
browser/base/content/abouthome/restore-large.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
browser/base/content/abouthome/restore-large@2x.png
Normal file
|
After Width: | Height: | Size: 7.1 KiB |
BIN
browser/base/content/abouthome/restore.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
browser/base/content/abouthome/restore@2x.png
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
BIN
browser/base/content/abouthome/settings.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
browser/base/content/abouthome/settings@2x.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
BIN
browser/base/content/abouthome/snippet1.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
browser/base/content/abouthome/snippet1@2x.png
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
browser/base/content/abouthome/snippet2.png
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
browser/base/content/abouthome/snippet2@2x.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
browser/base/content/abouthome/sync.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
browser/base/content/abouthome/sync@2x.png
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
107
browser/base/content/baseMenuOverlay.xul
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
<?xml version="1.0"?>
|
||||
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
<!DOCTYPE overlay [
|
||||
<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
|
||||
%brandDTD;
|
||||
<!ENTITY % baseMenuOverlayDTD SYSTEM "chrome://browser/locale/baseMenuOverlay.dtd">
|
||||
%baseMenuOverlayDTD;
|
||||
]>
|
||||
<overlay id="baseMenuOverlay"
|
||||
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/utilityOverlay.js"/>
|
||||
|
||||
#ifdef XP_MACOSX
|
||||
<!-- nsMenuBarX hides these and uses them to build the Application menu.
|
||||
When using Carbon widgets for Mac OS X widgets, some of these are not
|
||||
used as they only apply to Cocoa widget builds. All version of Firefox
|
||||
through Firefox 2 will use Carbon widgets. -->
|
||||
<menupopup id="menu_ToolsPopup">
|
||||
<menuitem id="menu_preferences" label="&preferencesCmdMac.label;" key="key_preferencesCmdMac" oncommand="openPreferences();"/>
|
||||
<menuitem id="menu_mac_services" label="&servicesMenuMac.label;"/>
|
||||
<menuitem id="menu_mac_hide_app" label="&hideThisAppCmdMac2.label;" key="key_hideThisAppCmdMac"/>
|
||||
<menuitem id="menu_mac_hide_others" label="&hideOtherAppsCmdMac.label;" key="key_hideOtherAppsCmdMac"/>
|
||||
<menuitem id="menu_mac_show_all" label="&showAllAppsCmdMac.label;"/>
|
||||
</menupopup>
|
||||
<!-- Mac window menu -->
|
||||
#include ../../../../toolkit/content/macWindowMenu.inc
|
||||
#endif
|
||||
|
||||
#ifdef XP_WIN
|
||||
<menu id="helpMenu"
|
||||
label="&helpMenuWin.label;"
|
||||
accesskey="&helpMenuWin.accesskey;">
|
||||
#else
|
||||
<menu id="helpMenu"
|
||||
label="&helpMenu.label;"
|
||||
accesskey="&helpMenu.accesskey;">
|
||||
#endif
|
||||
<menupopup id="menu_HelpPopup" onpopupshowing="buildHelpMenu();">
|
||||
<menuitem id="menu_openHelp"
|
||||
oncommand="openHelpLink('firefox-help')"
|
||||
onclick="checkForMiddleClick(this, event);"
|
||||
label="&productHelp2.label;"
|
||||
accesskey="&productHelp2.accesskey;"
|
||||
#ifdef XP_MACOSX
|
||||
key="key_openHelpMac"/>
|
||||
#else
|
||||
/>
|
||||
#endif
|
||||
<menuitem id="menu_keyboardShortcuts"
|
||||
oncommand="openHelpLink('keyboard-shortcuts')"
|
||||
onclick="checkForMiddleClick(this, event);"
|
||||
label="&helpKeyboardShortcuts.label;"
|
||||
accesskey="&helpKeyboardShortcuts.accesskey;"/>
|
||||
<menuitem id="troubleShooting"
|
||||
accesskey="&helpTroubleshootingInfo.accesskey;"
|
||||
label="&helpTroubleshootingInfo.label;"
|
||||
oncommand="openTroubleshootingPage()"
|
||||
onclick="checkForMiddleClick(this, event);"/>
|
||||
<menuitem id="feedbackPage"
|
||||
accesskey="&helpFeedbackPage.accesskey;"
|
||||
label="&helpFeedbackPage.label;"
|
||||
oncommand="openFeedbackPage()"
|
||||
onclick="checkForMiddleClick(this, event);"/>
|
||||
<menuitem id="helpSafeMode"
|
||||
accesskey="&helpSafeMode.accesskey;"
|
||||
label="&helpSafeMode.label;"
|
||||
stopaccesskey="&helpSafeMode.stop.accesskey;"
|
||||
stoplabel="&helpSafeMode.stop.label;"
|
||||
oncommand="safeModeRestart();"/>
|
||||
<menuseparator id="aboutSeparator"/>
|
||||
<menuitem id="aboutName"
|
||||
accesskey="&aboutProduct2.accesskey;"
|
||||
label="&aboutProduct2.label;"
|
||||
oncommand="openAboutDialog();"/>
|
||||
</menupopup>
|
||||
</menu>
|
||||
|
||||
<keyset id="baseMenuKeyset">
|
||||
#ifdef XP_MACOSX
|
||||
<key id="key_openHelpMac"
|
||||
oncommand="openHelpLink('firefox-osxkey');"
|
||||
key="&helpMac.commandkey;"
|
||||
modifiers="accel"/>
|
||||
<!-- These are used to build the Application menu under Cocoa widgets -->
|
||||
<key id="key_preferencesCmdMac"
|
||||
key="&preferencesCmdMac.commandkey;"
|
||||
modifiers="accel"/>
|
||||
<key id="key_hideThisAppCmdMac"
|
||||
key="&hideThisAppCmdMac2.commandkey;"
|
||||
modifiers="accel"/>
|
||||
<key id="key_hideOtherAppsCmdMac"
|
||||
key="&hideOtherAppsCmdMac.commandkey;"
|
||||
modifiers="accel,alt"/>
|
||||
#endif
|
||||
</keyset>
|
||||
|
||||
<stringbundleset id="stringbundleset">
|
||||
<stringbundle id="bundle_browser" src="chrome://browser/locale/browser.properties"/>
|
||||
<stringbundle id="bundle_browser_region" src="chrome://browser-region/locale/region.properties"/>
|
||||
</stringbundleset>
|
||||
</overlay>
|
||||
737
browser/base/content/browser-addons.js
Normal file
|
|
@ -0,0 +1,737 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
// Removes a doorhanger notification if all of the installs it was notifying
|
||||
// about have ended in some way.
|
||||
function removeNotificationOnEnd(notification, installs) {
|
||||
let count = installs.length;
|
||||
|
||||
function maybeRemove(install) {
|
||||
install.removeListener(this);
|
||||
|
||||
if (--count == 0) {
|
||||
// Check that the notification is still showing
|
||||
let current = PopupNotifications.getNotification(notification.id, notification.browser);
|
||||
if (current === notification)
|
||||
notification.remove();
|
||||
}
|
||||
}
|
||||
|
||||
for (let install of installs) {
|
||||
install.addListener({
|
||||
onDownloadCancelled: maybeRemove,
|
||||
onDownloadFailed: maybeRemove,
|
||||
onInstallFailed: maybeRemove,
|
||||
onInstallEnded: maybeRemove
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const gXPInstallObserver = {
|
||||
_findChildShell: function (aDocShell, aSoughtShell)
|
||||
{
|
||||
if (aDocShell == aSoughtShell)
|
||||
return aDocShell;
|
||||
|
||||
var node = aDocShell.QueryInterface(Components.interfaces.nsIDocShellTreeItem);
|
||||
for (var i = 0; i < node.childCount; ++i) {
|
||||
var docShell = node.getChildAt(i);
|
||||
docShell = this._findChildShell(docShell, aSoughtShell);
|
||||
if (docShell == aSoughtShell)
|
||||
return docShell;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
_getBrowser: function (aDocShell)
|
||||
{
|
||||
for (let browser of gBrowser.browsers) {
|
||||
if (this._findChildShell(browser.docShell, aDocShell))
|
||||
return browser;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
pendingInstalls: new WeakMap(),
|
||||
|
||||
showInstallConfirmation: function(browser, installInfo, height = undefined) {
|
||||
// If the confirmation notification is already open cache the installInfo
|
||||
// and the new confirmation will be shown later
|
||||
if (PopupNotifications.getNotification("addon-install-confirmation", browser)) {
|
||||
let pending = this.pendingInstalls.get(browser);
|
||||
if (pending) {
|
||||
pending.push(installInfo);
|
||||
} else {
|
||||
this.pendingInstalls.set(browser, [installInfo]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let showNextConfirmation = () => {
|
||||
// Make sure the browser is still alive.
|
||||
if (gBrowser.browsers.indexOf(browser) == -1)
|
||||
return;
|
||||
|
||||
let pending = this.pendingInstalls.get(browser);
|
||||
if (pending && pending.length)
|
||||
this.showInstallConfirmation(browser, pending.shift());
|
||||
}
|
||||
|
||||
// If all installs have already been cancelled in some way then just show
|
||||
// the next confirmation
|
||||
if (installInfo.installs.every(i => i.state != AddonManager.STATE_DOWNLOADED)) {
|
||||
showNextConfirmation();
|
||||
return;
|
||||
}
|
||||
|
||||
const anchorID = "addons-notification-icon";
|
||||
|
||||
// Make notifications persist a minimum of 30 seconds
|
||||
var options = {
|
||||
displayURI: installInfo.originatingURI,
|
||||
timeout: Date.now() + 30000,
|
||||
};
|
||||
|
||||
let cancelInstallation = () => {
|
||||
if (installInfo) {
|
||||
for (let install of installInfo.installs) {
|
||||
// The notification may have been closed because the add-ons got
|
||||
// cancelled elsewhere, only try to cancel those that are still
|
||||
// pending install.
|
||||
if (install.state != AddonManager.STATE_CANCELLED)
|
||||
install.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
this.acceptInstallation = null;
|
||||
|
||||
showNextConfirmation();
|
||||
};
|
||||
|
||||
let unsigned = installInfo.installs.filter(i => i.addon.signedState <= AddonManager.SIGNEDSTATE_MISSING);
|
||||
let someUnsigned = unsigned.length > 0 && unsigned.length < installInfo.installs.length;
|
||||
|
||||
options.eventCallback = (aEvent) => {
|
||||
switch (aEvent) {
|
||||
case "removed":
|
||||
cancelInstallation();
|
||||
break;
|
||||
case "shown":
|
||||
let addonList = document.getElementById("addon-install-confirmation-content");
|
||||
while (addonList.firstChild)
|
||||
addonList.firstChild.remove();
|
||||
|
||||
for (let install of installInfo.installs) {
|
||||
let container = document.createElement("hbox");
|
||||
|
||||
let name = document.createElement("label");
|
||||
name.setAttribute("value", install.addon.name);
|
||||
name.setAttribute("class", "addon-install-confirmation-name");
|
||||
container.appendChild(name);
|
||||
|
||||
if (someUnsigned && install.addon.signedState <= AddonManager.SIGNEDSTATE_MISSING) {
|
||||
let unsigned = document.createElement("label");
|
||||
unsigned.setAttribute("value", gNavigatorBundle.getString("addonInstall.unsigned"));
|
||||
unsigned.setAttribute("class", "addon-install-confirmation-unsigned");
|
||||
container.appendChild(unsigned);
|
||||
}
|
||||
|
||||
addonList.appendChild(container);
|
||||
}
|
||||
|
||||
this.acceptInstallation = () => {
|
||||
for (let install of installInfo.installs)
|
||||
install.install();
|
||||
installInfo = null;
|
||||
};
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
options.learnMoreURL = Services.urlFormatter.formatURLPref("app.support.baseURL");
|
||||
|
||||
let messageString;
|
||||
let notification = document.getElementById("addon-install-confirmation-notification");
|
||||
|
||||
messageString = gNavigatorBundle.getString("addonConfirmInstall.message");
|
||||
notification.removeAttribute("warning");
|
||||
options.learnMoreURL += "find-and-install-add-ons";
|
||||
|
||||
let brandBundle = document.getElementById("bundle_brand");
|
||||
let brandShortName = brandBundle.getString("brandShortName");
|
||||
|
||||
messageString = PluralForm.get(installInfo.installs.length, messageString);
|
||||
messageString = messageString.replace("#1", brandShortName);
|
||||
messageString = messageString.replace("#2", installInfo.installs.length);
|
||||
|
||||
let cancelButton = document.getElementById("addon-install-confirmation-cancel");
|
||||
cancelButton.label = gNavigatorBundle.getString("addonInstall.cancelButton.label");
|
||||
cancelButton.accessKey = gNavigatorBundle.getString("addonInstall.cancelButton.accesskey");
|
||||
|
||||
let acceptButton = document.getElementById("addon-install-confirmation-accept");
|
||||
acceptButton.label = gNavigatorBundle.getString("addonInstall.acceptButton.label");
|
||||
acceptButton.accessKey = gNavigatorBundle.getString("addonInstall.acceptButton.accesskey");
|
||||
|
||||
if (height) {
|
||||
let notification = document.getElementById("addon-install-confirmation-notification");
|
||||
notification.style.minHeight = height + "px";
|
||||
}
|
||||
|
||||
let tab = gBrowser.getTabForBrowser(browser);
|
||||
if (tab) {
|
||||
gBrowser.selectedTab = tab;
|
||||
}
|
||||
|
||||
let popup = PopupNotifications.show(browser, "addon-install-confirmation",
|
||||
messageString, anchorID, null, null,
|
||||
options);
|
||||
|
||||
removeNotificationOnEnd(popup, installInfo.installs);
|
||||
},
|
||||
|
||||
observe: function (aSubject, aTopic, aData)
|
||||
{
|
||||
var brandBundle = document.getElementById("bundle_brand");
|
||||
var installInfo = aSubject.QueryInterface(Components.interfaces.amIWebInstallInfo);
|
||||
var browser = installInfo.browser;
|
||||
|
||||
// Make sure the browser is still alive.
|
||||
if (!browser || gBrowser.browsers.indexOf(browser) == -1)
|
||||
return;
|
||||
|
||||
const anchorID = "addons-notification-icon";
|
||||
var messageString, action;
|
||||
var brandShortName = brandBundle.getString("brandShortName");
|
||||
|
||||
var notificationID = aTopic;
|
||||
// Make notifications persist a minimum of 30 seconds
|
||||
var options = {
|
||||
displayURI: installInfo.originatingURI,
|
||||
timeout: Date.now() + 30000,
|
||||
};
|
||||
|
||||
switch (aTopic) {
|
||||
case "addon-install-disabled": {
|
||||
notificationID = "xpinstall-disabled";
|
||||
|
||||
if (gPrefService.prefIsLocked("xpinstall.enabled")) {
|
||||
messageString = gNavigatorBundle.getString("xpinstallDisabledMessageLocked");
|
||||
buttons = [];
|
||||
}
|
||||
else {
|
||||
messageString = gNavigatorBundle.getString("xpinstallDisabledMessage");
|
||||
|
||||
action = {
|
||||
label: gNavigatorBundle.getString("xpinstallDisabledButton"),
|
||||
accessKey: gNavigatorBundle.getString("xpinstallDisabledButton.accesskey"),
|
||||
callback: function editPrefs() {
|
||||
gPrefService.setBoolPref("xpinstall.enabled", true);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
PopupNotifications.show(browser, notificationID, messageString, anchorID,
|
||||
action, null, options);
|
||||
break; }
|
||||
case "addon-install-origin-blocked": {
|
||||
messageString = gNavigatorBundle.getFormattedString("xpinstallPromptMessage",
|
||||
[brandShortName]);
|
||||
|
||||
let popup = PopupNotifications.show(browser, notificationID,
|
||||
messageString, anchorID,
|
||||
null, null, options);
|
||||
removeNotificationOnEnd(popup, installInfo.installs);
|
||||
break; }
|
||||
case "addon-install-blocked": {
|
||||
messageString = gNavigatorBundle.getFormattedString("xpinstallPromptMessage",
|
||||
[brandShortName]);
|
||||
|
||||
action = {
|
||||
label: gNavigatorBundle.getString("xpinstallPromptAllowButton"),
|
||||
accessKey: gNavigatorBundle.getString("xpinstallPromptAllowButton.accesskey"),
|
||||
callback: function() {
|
||||
installInfo.install();
|
||||
}
|
||||
};
|
||||
|
||||
let popup = PopupNotifications.show(browser, notificationID,
|
||||
messageString, anchorID,
|
||||
action, null, options);
|
||||
removeNotificationOnEnd(popup, installInfo.installs);
|
||||
break; }
|
||||
case "addon-install-started": {
|
||||
let needsDownload = function needsDownload(aInstall) {
|
||||
return aInstall.state != AddonManager.STATE_DOWNLOADED;
|
||||
}
|
||||
// If all installs have already been downloaded then there is no need to
|
||||
// show the download progress
|
||||
if (!installInfo.installs.some(needsDownload))
|
||||
return;
|
||||
notificationID = "addon-progress";
|
||||
messageString = gNavigatorBundle.getString("addonDownloadingAndVerifying");
|
||||
messageString = PluralForm.get(installInfo.installs.length, messageString);
|
||||
messageString = messageString.replace("#1", installInfo.installs.length);
|
||||
options.installs = installInfo.installs;
|
||||
options.contentWindow = browser.contentWindow;
|
||||
options.sourceURI = browser.currentURI;
|
||||
options.eventCallback = (aEvent) => {
|
||||
switch (aEvent) {
|
||||
case "removed":
|
||||
options.contentWindow = null;
|
||||
options.sourceURI = null;
|
||||
break;
|
||||
}
|
||||
};
|
||||
let notification = PopupNotifications.show(browser, notificationID, messageString,
|
||||
anchorID, null, null, options);
|
||||
notification._startTime = Date.now();
|
||||
|
||||
let cancelButton = document.getElementById("addon-progress-cancel");
|
||||
cancelButton.label = gNavigatorBundle.getString("addonInstall.cancelButton.label");
|
||||
cancelButton.accessKey = gNavigatorBundle.getString("addonInstall.cancelButton.accesskey");
|
||||
|
||||
let acceptButton = document.getElementById("addon-progress-accept");
|
||||
if (Preferences.get("xpinstall.customConfirmationUI", false)) {
|
||||
acceptButton.label = gNavigatorBundle.getString("addonInstall.acceptButton.label");
|
||||
acceptButton.accessKey = gNavigatorBundle.getString("addonInstall.acceptButton.accesskey");
|
||||
} else {
|
||||
acceptButton.hidden = true;
|
||||
}
|
||||
break; }
|
||||
case "addon-install-failed": {
|
||||
// TODO This isn't terribly ideal for the multiple failure case
|
||||
for (let install of installInfo.installs) {
|
||||
let host;
|
||||
try {
|
||||
host = options.displayURI.host;
|
||||
} catch (e) {
|
||||
// displayURI might be missing or 'host' might throw for non-nsStandardURL nsIURIs.
|
||||
}
|
||||
|
||||
if (!host)
|
||||
host = (install.sourceURI instanceof Ci.nsIStandardURL) &&
|
||||
install.sourceURI.host;
|
||||
|
||||
let error = (host || install.error == 0) ? "addonInstallError" : "addonLocalInstallError";
|
||||
let args;
|
||||
if (install.error < 0) {
|
||||
error += install.error;
|
||||
args = [brandShortName, install.name];
|
||||
} else if (install.addon.blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED) {
|
||||
error += "Blocklisted";
|
||||
args = [install.name];
|
||||
} else {
|
||||
error += "Incompatible";
|
||||
args = [brandShortName, Services.appinfo.version, install.name];
|
||||
}
|
||||
|
||||
// Add Learn More link when refusing to install an unsigned add-on
|
||||
if (install.error == AddonManager.ERROR_SIGNEDSTATE_REQUIRED) {
|
||||
options.learnMoreURL = Services.urlFormatter.formatURLPref("app.support.baseURL") + "unsigned-addons";
|
||||
}
|
||||
|
||||
messageString = gNavigatorBundle.getFormattedString(error, args);
|
||||
|
||||
if (install.error == AddonManager.ERROR_CORRUPT_FILE) {
|
||||
let detail = null;
|
||||
if (install.errorDetail !== undefined && install.errorDetail !== null) {
|
||||
detail = install.errorDetail;
|
||||
if (typeof detail != "string") {
|
||||
if (detail && typeof detail.message == "string" && detail.message) {
|
||||
detail = detail.message;
|
||||
} else {
|
||||
detail = String(detail);
|
||||
}
|
||||
}
|
||||
detail = detail.replace(/[\r\n\t]+/g, " ").trim();
|
||||
}
|
||||
|
||||
let detailString;
|
||||
if (detail) {
|
||||
detailString = gNavigatorBundle.getFormattedString("addonInstallErrorDetail", [detail]);
|
||||
} else {
|
||||
detailString = gNavigatorBundle.getFormattedString("addonInstallErrorDetailCode", [String(install.error)]);
|
||||
}
|
||||
messageString += " " + detailString;
|
||||
}
|
||||
|
||||
PopupNotifications.show(browser, notificationID, messageString, anchorID,
|
||||
action, null, options);
|
||||
|
||||
// Can't have multiple notifications with the same ID, so stop here.
|
||||
break;
|
||||
}
|
||||
this._removeProgressNotification(browser);
|
||||
break; }
|
||||
case "addon-install-confirmation": {
|
||||
let showNotification = () => {
|
||||
let height = undefined;
|
||||
|
||||
if (PopupNotifications.isPanelOpen) {
|
||||
let rect = document.getElementById("addon-progress-notification").getBoundingClientRect();
|
||||
height = rect.height;
|
||||
}
|
||||
|
||||
this._removeProgressNotification(browser);
|
||||
this.showInstallConfirmation(browser, installInfo, height);
|
||||
};
|
||||
|
||||
let progressNotification = PopupNotifications.getNotification("addon-progress", browser);
|
||||
if (progressNotification) {
|
||||
let downloadDuration = Date.now() - progressNotification._startTime;
|
||||
let securityDelay = Services.prefs.getIntPref("security.dialog_enable_delay") - downloadDuration;
|
||||
if (securityDelay > 0) {
|
||||
setTimeout(() => {
|
||||
// The download may have been cancelled during the security delay
|
||||
if (PopupNotifications.getNotification("addon-progress", browser))
|
||||
showNotification();
|
||||
}, securityDelay);
|
||||
break;
|
||||
}
|
||||
}
|
||||
showNotification();
|
||||
break; }
|
||||
case "addon-install-complete": {
|
||||
let needsRestart = installInfo.installs.some(function(i) {
|
||||
return i.addon.pendingOperations != AddonManager.PENDING_NONE;
|
||||
});
|
||||
|
||||
if (needsRestart) {
|
||||
notificationID = "addon-install-restart";
|
||||
messageString = gNavigatorBundle.getString("addonsInstalledNeedsRestart");
|
||||
action = {
|
||||
label: gNavigatorBundle.getString("addonInstallRestartButton"),
|
||||
accessKey: gNavigatorBundle.getString("addonInstallRestartButton.accesskey"),
|
||||
callback: function() {
|
||||
BrowserUtils.restartApplication();
|
||||
}
|
||||
};
|
||||
}
|
||||
else {
|
||||
messageString = gNavigatorBundle.getString("addonsInstalled");
|
||||
action = null;
|
||||
}
|
||||
|
||||
messageString = PluralForm.get(installInfo.installs.length, messageString);
|
||||
messageString = messageString.replace("#1", installInfo.installs[0].name);
|
||||
messageString = messageString.replace("#2", installInfo.installs.length);
|
||||
messageString = messageString.replace("#3", brandShortName);
|
||||
|
||||
// Remove notificaion on dismissal, since it's possible to cancel the
|
||||
// install through the addons manager UI, making the "restart" prompt
|
||||
// irrelevant.
|
||||
options.removeOnDismissal = true;
|
||||
|
||||
PopupNotifications.show(browser, notificationID, messageString, anchorID,
|
||||
action, null, options);
|
||||
break; }
|
||||
}
|
||||
},
|
||||
_removeProgressNotification(aBrowser) {
|
||||
let notification = PopupNotifications.getNotification("addon-progress", aBrowser);
|
||||
if (notification)
|
||||
notification.remove();
|
||||
}
|
||||
};
|
||||
|
||||
var LightWeightThemeWebInstaller = {
|
||||
init: function () {
|
||||
let mm = window.messageManager;
|
||||
mm.addMessageListener("LightWeightThemeWebInstaller:Install", this);
|
||||
mm.addMessageListener("LightWeightThemeWebInstaller:Preview", this);
|
||||
mm.addMessageListener("LightWeightThemeWebInstaller:ResetPreview", this);
|
||||
},
|
||||
|
||||
receiveMessage: function (message) {
|
||||
// ignore requests from background tabs
|
||||
if (message.target != gBrowser.selectedBrowser) {
|
||||
return;
|
||||
}
|
||||
|
||||
let data = message.data;
|
||||
|
||||
switch (message.name) {
|
||||
case "LightWeightThemeWebInstaller:Install": {
|
||||
this._installRequest(data.themeData, data.principal, data.baseURI);
|
||||
break;
|
||||
}
|
||||
case "LightWeightThemeWebInstaller:Preview": {
|
||||
this._preview(data.themeData, data.principal, data.baseURI);
|
||||
break;
|
||||
}
|
||||
case "LightWeightThemeWebInstaller:ResetPreview": {
|
||||
this._resetPreview(data && data.principal);
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
handleEvent: function (event) {
|
||||
switch (event.type) {
|
||||
case "TabSelect": {
|
||||
this._resetPreview();
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
get _manager () {
|
||||
let temp = {};
|
||||
Cu.import("resource://gre/modules/LightweightThemeManager.jsm", temp);
|
||||
delete this._manager;
|
||||
return this._manager = temp.LightweightThemeManager;
|
||||
},
|
||||
|
||||
_installRequest(dataString, principal, baseURI) {
|
||||
// Don't allow installing off null principals.
|
||||
if (!principal.URI) {
|
||||
return;
|
||||
}
|
||||
|
||||
let data = this._manager.parseTheme(dataString, baseURI);
|
||||
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
|
||||
// A notification bar with the option to undo is normally shown after a
|
||||
// theme is installed. But the discovery pane served from the url(s)
|
||||
// below has its own toggle switch for quick undos, so don't show the
|
||||
// notification in that case.
|
||||
let notify = this._shouldShowUndoPrompt(principal);
|
||||
if (this._isAllowed(principal)) {
|
||||
this._install(data, notify);
|
||||
return;
|
||||
}
|
||||
|
||||
let allowButtonText =
|
||||
gNavigatorBundle.getString("lwthemeInstallRequest.allowButton");
|
||||
let allowButtonAccesskey =
|
||||
gNavigatorBundle.getString("lwthemeInstallRequest.allowButton.accesskey");
|
||||
let message =
|
||||
gNavigatorBundle.getFormattedString("lwthemeInstallRequest.message",
|
||||
[principal.URI.host]);
|
||||
let buttons = [{
|
||||
label: allowButtonText,
|
||||
accessKey: allowButtonAccesskey,
|
||||
callback: function () {
|
||||
LightWeightThemeWebInstaller._install(data, notify);
|
||||
}
|
||||
}];
|
||||
|
||||
this._removePreviousNotifications();
|
||||
|
||||
let notificationBox = gBrowser.getNotificationBox();
|
||||
let notificationBar =
|
||||
notificationBox.appendNotification(message, "lwtheme-install-request", "",
|
||||
notificationBox.PRIORITY_INFO_MEDIUM,
|
||||
buttons);
|
||||
notificationBar.persistence = 1;
|
||||
},
|
||||
|
||||
_install: function (newLWTheme, notify) {
|
||||
let previousLWTheme = this._manager.currentTheme;
|
||||
|
||||
let listener = {
|
||||
onEnabling: function(aAddon, aRequiresRestart) {
|
||||
if (!aRequiresRestart) {
|
||||
return;
|
||||
}
|
||||
|
||||
let messageString = gNavigatorBundle.getFormattedString("lwthemeNeedsRestart.message",
|
||||
[aAddon.name], 1);
|
||||
|
||||
let action = {
|
||||
label: gNavigatorBundle.getString("lwthemeNeedsRestart.button"),
|
||||
accessKey: gNavigatorBundle.getString("lwthemeNeedsRestart.accesskey"),
|
||||
callback: function () {
|
||||
BrowserUtils.restartApplication();
|
||||
}
|
||||
};
|
||||
|
||||
let options = {
|
||||
timeout: Date.now() + 30000
|
||||
};
|
||||
|
||||
PopupNotifications.show(gBrowser.selectedBrowser, "addon-theme-change",
|
||||
messageString, "addons-notification-icon",
|
||||
action, null, options);
|
||||
},
|
||||
|
||||
onEnabled: function(aAddon) {
|
||||
if (notify) {
|
||||
LightWeightThemeWebInstaller._postInstallNotification(newLWTheme, previousLWTheme);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
AddonManager.addAddonListener(listener);
|
||||
this._manager.currentTheme = newLWTheme;
|
||||
AddonManager.removeAddonListener(listener);
|
||||
},
|
||||
|
||||
_postInstallNotification: function (newTheme, previousTheme) {
|
||||
function text(id) {
|
||||
return gNavigatorBundle.getString("lwthemePostInstallNotification." + id);
|
||||
}
|
||||
|
||||
let buttons = [{
|
||||
label: text("undoButton"),
|
||||
accessKey: text("undoButton.accesskey"),
|
||||
callback: function () {
|
||||
LightWeightThemeWebInstaller._manager.forgetUsedTheme(newTheme.id);
|
||||
LightWeightThemeWebInstaller._manager.currentTheme = previousTheme;
|
||||
}
|
||||
}, {
|
||||
label: text("manageButton"),
|
||||
accessKey: text("manageButton.accesskey"),
|
||||
callback: function () {
|
||||
BrowserOpenAddonsMgr("addons://list/theme");
|
||||
}
|
||||
}];
|
||||
|
||||
this._removePreviousNotifications();
|
||||
|
||||
let notificationBox = gBrowser.getNotificationBox();
|
||||
let notificationBar =
|
||||
notificationBox.appendNotification(text("message"),
|
||||
"lwtheme-install-notification", "",
|
||||
notificationBox.PRIORITY_INFO_MEDIUM,
|
||||
buttons);
|
||||
notificationBar.persistence = 1;
|
||||
notificationBar.timeout = Date.now() + 20000; // 20 seconds
|
||||
},
|
||||
|
||||
_removePreviousNotifications: function () {
|
||||
let box = gBrowser.getNotificationBox();
|
||||
|
||||
["lwtheme-install-request",
|
||||
"lwtheme-install-notification"].forEach(function (value) {
|
||||
let notification = box.getNotificationWithValue(value);
|
||||
if (notification)
|
||||
box.removeNotification(notification);
|
||||
});
|
||||
},
|
||||
|
||||
_preview(dataString, principal, baseURI) {
|
||||
if (!this._isAllowed(principal))
|
||||
return;
|
||||
|
||||
let data = this._manager.parseTheme(dataString, baseURI);
|
||||
if (!data)
|
||||
return;
|
||||
|
||||
this._resetPreview();
|
||||
gBrowser.tabContainer.addEventListener("TabSelect", this, false);
|
||||
this._manager.previewTheme(data);
|
||||
},
|
||||
|
||||
_resetPreview(principal) {
|
||||
if (!this._isAllowed(principal))
|
||||
return;
|
||||
gBrowser.tabContainer.removeEventListener("TabSelect", this, false);
|
||||
this._manager.resetPreview();
|
||||
},
|
||||
|
||||
_isAllowed(principal) {
|
||||
if (!principal || !principal.URI || !principal.URI.schemeIs("https")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let pm = Services.perms;
|
||||
return pm.testPermission(principal.URI, "install") == pm.ALLOW_ACTION;
|
||||
},
|
||||
|
||||
_shouldShowUndoPrompt(principal) {
|
||||
if (!principal || !principal.URI) {
|
||||
return true;
|
||||
}
|
||||
|
||||
let prePath = principal.URI.prePath;
|
||||
if (prePath == "https://addons.palemoon.org") {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Listen for Lightweight Theme styling changes and update the browser's theme accordingly.
|
||||
*/
|
||||
var LightweightThemeListener = {
|
||||
_modifiedStyles: [],
|
||||
|
||||
init: function () {
|
||||
XPCOMUtils.defineLazyGetter(this, "styleSheet", function() {
|
||||
for (let i = document.styleSheets.length - 1; i >= 0; i--) {
|
||||
let sheet = document.styleSheets[i];
|
||||
if (sheet.href == "chrome://browser/skin/browser-lightweightTheme.css")
|
||||
return sheet;
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
|
||||
Services.obs.addObserver(this, "lightweight-theme-styling-update", false);
|
||||
Services.obs.addObserver(this, "lightweight-theme-optimized", false);
|
||||
if (document.documentElement.hasAttribute("lwtheme"))
|
||||
this.updateStyleSheet(document.documentElement.style.backgroundImage);
|
||||
},
|
||||
|
||||
uninit: function () {
|
||||
Services.obs.removeObserver(this, "lightweight-theme-styling-update");
|
||||
Services.obs.removeObserver(this, "lightweight-theme-optimized");
|
||||
},
|
||||
|
||||
/**
|
||||
* Append the headerImage to the background-image property of all rulesets in
|
||||
* browser-lightweightTheme.css.
|
||||
*
|
||||
* @param headerImage - a string containing a CSS image for the lightweight theme header.
|
||||
*/
|
||||
updateStyleSheet: function(headerImage) {
|
||||
if (!this.styleSheet)
|
||||
return;
|
||||
this.substituteRules(this.styleSheet.cssRules, headerImage);
|
||||
},
|
||||
|
||||
substituteRules: function(ruleList, headerImage, existingStyleRulesModified = 0) {
|
||||
let styleRulesModified = 0;
|
||||
for (let i = 0; i < ruleList.length; i++) {
|
||||
let rule = ruleList[i];
|
||||
if (rule instanceof Ci.nsIDOMCSSGroupingRule) {
|
||||
// Add the number of modified sub-rules to the modified count
|
||||
styleRulesModified += this.substituteRules(rule.cssRules, headerImage, existingStyleRulesModified + styleRulesModified);
|
||||
} else if (rule instanceof Ci.nsIDOMCSSStyleRule) {
|
||||
if (!rule.style.backgroundImage)
|
||||
continue;
|
||||
let modifiedIndex = existingStyleRulesModified + styleRulesModified;
|
||||
if (!this._modifiedStyles[modifiedIndex])
|
||||
this._modifiedStyles[modifiedIndex] = { backgroundImage: rule.style.backgroundImage };
|
||||
|
||||
rule.style.backgroundImage = this._modifiedStyles[modifiedIndex].backgroundImage + ", " + headerImage;
|
||||
styleRulesModified++;
|
||||
} else {
|
||||
Cu.reportError("Unsupported rule encountered");
|
||||
}
|
||||
}
|
||||
return styleRulesModified;
|
||||
},
|
||||
|
||||
// nsIObserver
|
||||
observe: function (aSubject, aTopic, aData) {
|
||||
if ((aTopic != "lightweight-theme-styling-update" && aTopic != "lightweight-theme-optimized") ||
|
||||
!this.styleSheet)
|
||||
return;
|
||||
|
||||
if (aTopic == "lightweight-theme-optimized" && aSubject != window)
|
||||
return;
|
||||
|
||||
let themeData = JSON.parse(aData);
|
||||
if (!themeData)
|
||||
return;
|
||||
this.updateStyleSheet("url(" + themeData.headerURL + ")");
|
||||
},
|
||||
};
|
||||
257
browser/base/content/browser-captivePortal.js
Normal file
|
|
@ -0,0 +1,257 @@
|
|||
/* 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/. */
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "cps",
|
||||
"@mozilla.org/network/captive-portal-service;1",
|
||||
"nsICaptivePortalService");
|
||||
|
||||
var CaptivePortalWatcher = {
|
||||
/**
|
||||
* This constant is chosen to be large enough for a portal recheck to complete,
|
||||
* and small enough that the delay in opening a tab isn't too noticeable.
|
||||
* Please see comments for _delayedCaptivePortalDetected for more details.
|
||||
*/
|
||||
PORTAL_RECHECK_DELAY_MS: Preferences.get("captivedetect.portalRecheckDelayMS", 500),
|
||||
|
||||
// This is the value used to identify the captive portal notification.
|
||||
PORTAL_NOTIFICATION_VALUE: "captive-portal-detected",
|
||||
|
||||
// This holds a weak reference to the captive portal tab so that we
|
||||
// don't leak it if the user closes it.
|
||||
_captivePortalTab: null,
|
||||
|
||||
/**
|
||||
* If a portal is detected when we don't have focus, we first wait for focus
|
||||
* and then add the tab if, after a recheck, the portal is still active. This
|
||||
* is set to true while we wait so that in the unlikely event that we receive
|
||||
* another notification while waiting, we don't do things twice.
|
||||
*/
|
||||
_delayedCaptivePortalDetectedInProgress: false,
|
||||
|
||||
// In the situation above, this is set to true while we wait for the recheck.
|
||||
// This flag exists so that tests can appropriately simulate a recheck.
|
||||
_waitingForRecheck: false,
|
||||
|
||||
get _captivePortalNotification() {
|
||||
let nb = document.getElementById("high-priority-global-notificationbox");
|
||||
return nb.getNotificationWithValue(this.PORTAL_NOTIFICATION_VALUE);
|
||||
},
|
||||
|
||||
get canonicalURL() {
|
||||
return Services.prefs.getCharPref("captivedetect.canonicalURL");
|
||||
},
|
||||
|
||||
get _browserBundle() {
|
||||
delete this._browserBundle;
|
||||
return this._browserBundle =
|
||||
Services.strings.createBundle("chrome://browser/locale/browser.properties");
|
||||
},
|
||||
|
||||
init() {
|
||||
Services.obs.addObserver(this, "captive-portal-login", false);
|
||||
Services.obs.addObserver(this, "captive-portal-login-abort", false);
|
||||
Services.obs.addObserver(this, "captive-portal-login-success", false);
|
||||
|
||||
if (cps.state == cps.LOCKED_PORTAL) {
|
||||
// A captive portal has already been detected.
|
||||
this._captivePortalDetected();
|
||||
|
||||
// Automatically open a captive portal tab if there's no other browser window.
|
||||
let windows = Services.wm.getEnumerator("navigator:browser");
|
||||
if (windows.getNext() == window && !windows.hasMoreElements()) {
|
||||
this.ensureCaptivePortalTab();
|
||||
}
|
||||
}
|
||||
|
||||
cps.recheckCaptivePortal();
|
||||
},
|
||||
|
||||
uninit() {
|
||||
Services.obs.removeObserver(this, "captive-portal-login");
|
||||
Services.obs.removeObserver(this, "captive-portal-login-abort");
|
||||
Services.obs.removeObserver(this, "captive-portal-login-success");
|
||||
|
||||
|
||||
if (this._delayedCaptivePortalDetectedInProgress) {
|
||||
Services.obs.removeObserver(this, "xul-window-visible");
|
||||
}
|
||||
},
|
||||
|
||||
observe(aSubject, aTopic, aData) {
|
||||
switch (aTopic) {
|
||||
case "captive-portal-login":
|
||||
this._captivePortalDetected();
|
||||
break;
|
||||
case "captive-portal-login-abort":
|
||||
case "captive-portal-login-success":
|
||||
this._captivePortalGone();
|
||||
break;
|
||||
case "xul-window-visible":
|
||||
this._delayedCaptivePortalDetected();
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
_captivePortalDetected() {
|
||||
if (this._delayedCaptivePortalDetectedInProgress) {
|
||||
return;
|
||||
}
|
||||
|
||||
let win = RecentWindow.getMostRecentBrowserWindow();
|
||||
// If no browser window has focus, open and show the tab when we regain focus.
|
||||
// This is so that if a different application was focused, when the user
|
||||
// (re-)focuses a browser window, we open the tab immediately in that window
|
||||
// so they can log in before continuing to browse.
|
||||
if (win != Services.ww.activeWindow) {
|
||||
this._delayedCaptivePortalDetectedInProgress = true;
|
||||
Services.obs.addObserver(this, "xul-window-visible", false);
|
||||
}
|
||||
|
||||
this._showNotification();
|
||||
},
|
||||
|
||||
/**
|
||||
* Called after we regain focus if we detect a portal while a browser window
|
||||
* doesn't have focus. Triggers a portal recheck to reaffirm state, and adds
|
||||
* the tab if needed after a short delay to allow the recheck to complete.
|
||||
*/
|
||||
_delayedCaptivePortalDetected() {
|
||||
if (!this._delayedCaptivePortalDetectedInProgress) {
|
||||
return;
|
||||
}
|
||||
|
||||
let win = RecentWindow.getMostRecentBrowserWindow();
|
||||
if (win != Services.ww.activeWindow) {
|
||||
// The window that got focused was not a browser window.
|
||||
return;
|
||||
}
|
||||
Services.obs.removeObserver(this, "xul-window-visible");
|
||||
this._delayedCaptivePortalDetectedInProgress = false;
|
||||
|
||||
if (win != window) {
|
||||
// Some other browser window got focus, we don't have to do anything.
|
||||
return;
|
||||
}
|
||||
// Trigger a portal recheck. The user may have logged into the portal via
|
||||
// another client, or changed networks.
|
||||
cps.recheckCaptivePortal();
|
||||
this._waitingForRecheck = true;
|
||||
let requestTime = Date.now();
|
||||
|
||||
let self = this;
|
||||
Services.obs.addObserver(function observer() {
|
||||
let time = Date.now() - requestTime;
|
||||
Services.obs.removeObserver(observer, "captive-portal-check-complete");
|
||||
self._waitingForRecheck = false;
|
||||
if (cps.state != cps.LOCKED_PORTAL) {
|
||||
// We're free of the portal!
|
||||
return;
|
||||
}
|
||||
|
||||
if (time <= self.PORTAL_RECHECK_DELAY_MS) {
|
||||
// The amount of time elapsed since we requested a recheck (i.e. since
|
||||
// the browser window was focused) was small enough that we can add and
|
||||
// focus a tab with the login page with no noticeable delay.
|
||||
self.ensureCaptivePortalTab();
|
||||
}
|
||||
}, "captive-portal-check-complete", false);
|
||||
},
|
||||
|
||||
_captivePortalGone() {
|
||||
if (this._delayedCaptivePortalDetectedInProgress) {
|
||||
Services.obs.removeObserver(this, "xul-window-visible");
|
||||
this._delayedCaptivePortalDetectedInProgress = false;
|
||||
}
|
||||
|
||||
this._removeNotification();
|
||||
},
|
||||
|
||||
handleEvent(aEvent) {
|
||||
if (aEvent.type != "TabSelect" || !this._captivePortalTab || !this._captivePortalNotification) {
|
||||
return;
|
||||
}
|
||||
|
||||
let tab = this._captivePortalTab.get();
|
||||
let n = this._captivePortalNotification;
|
||||
if (!tab || !n) {
|
||||
return;
|
||||
}
|
||||
|
||||
let doc = tab.ownerDocument;
|
||||
let button = n.querySelector("button.notification-button");
|
||||
if (doc.defaultView.gBrowser.selectedTab == tab) {
|
||||
button.style.visibility = "hidden";
|
||||
} else {
|
||||
button.style.visibility = "visible";
|
||||
}
|
||||
},
|
||||
|
||||
_showNotification() {
|
||||
let buttons = [
|
||||
{
|
||||
label: this._browserBundle.GetStringFromName("captivePortal.showLoginPage"),
|
||||
callback: () => {
|
||||
this.ensureCaptivePortalTab();
|
||||
|
||||
// Returning true prevents the notification from closing.
|
||||
return true;
|
||||
},
|
||||
isDefault: true,
|
||||
},
|
||||
];
|
||||
|
||||
let message = this._browserBundle.GetStringFromName("captivePortal.infoMessage2");
|
||||
|
||||
let closeHandler = (aEventName) => {
|
||||
if (aEventName != "removed") {
|
||||
return;
|
||||
}
|
||||
gBrowser.tabContainer.removeEventListener("TabSelect", this);
|
||||
};
|
||||
|
||||
let nb = document.getElementById("high-priority-global-notificationbox");
|
||||
nb.appendNotification(message, this.PORTAL_NOTIFICATION_VALUE, "",
|
||||
nb.PRIORITY_INFO_MEDIUM, buttons, closeHandler);
|
||||
|
||||
gBrowser.tabContainer.addEventListener("TabSelect", this);
|
||||
},
|
||||
|
||||
_removeNotification() {
|
||||
let n = this._captivePortalNotification;
|
||||
if (!n || !n.parentNode) {
|
||||
return;
|
||||
}
|
||||
n.close();
|
||||
},
|
||||
|
||||
ensureCaptivePortalTab() {
|
||||
let tab;
|
||||
if (this._captivePortalTab) {
|
||||
tab = this._captivePortalTab.get();
|
||||
}
|
||||
|
||||
// If the tab is gone or going, we need to open a new one.
|
||||
if (!tab || tab.closing || !tab.parentNode) {
|
||||
tab = gBrowser.addTab(this.canonicalURL, { ownerTab: gBrowser.selectedTab });
|
||||
this._captivePortalTab = Cu.getWeakReference(tab);
|
||||
}
|
||||
|
||||
gBrowser.selectedTab = tab;
|
||||
|
||||
let canonicalURI = makeURI(this.canonicalURL);
|
||||
|
||||
// When we are no longer captive, close the tab if it's at the canonical URL.
|
||||
let tabCloser = () => {
|
||||
Services.obs.removeObserver(tabCloser, "captive-portal-login-abort");
|
||||
Services.obs.removeObserver(tabCloser, "captive-portal-login-success");
|
||||
if (!tab || tab.closing || !tab.parentNode || !tab.linkedBrowser ||
|
||||
!tab.linkedBrowser.currentURI.equalsExceptRef(canonicalURI)) {
|
||||
return;
|
||||
}
|
||||
gBrowser.removeTab(tab);
|
||||
}
|
||||
Services.obs.addObserver(tabCloser, "captive-portal-login-abort", false);
|
||||
Services.obs.addObserver(tabCloser, "captive-portal-login-success", false);
|
||||
},
|
||||
};
|
||||
12
browser/base/content/browser-charsetmenu.inc
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
# 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/.
|
||||
|
||||
<menu id="charsetMenu"
|
||||
label="&charsetMenu2.label;"
|
||||
accesskey="&charsetMenu2.accesskey;"
|
||||
oncommand="BrowserSetForcedCharacterSet(event.target.getAttribute('charset'));"
|
||||
onpopupshowing="CharsetMenu.build(event.target); UpdateCurrentCharset(this);">
|
||||
<menupopup>
|
||||
</menupopup>
|
||||
</menu>
|
||||
432
browser/base/content/browser-context.inc
Normal file
|
|
@ -0,0 +1,432 @@
|
|||
# -*- 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/.
|
||||
|
||||
<menugroup id="context-navigation">
|
||||
<menuitem id="context-back"
|
||||
class="menuitem-iconic"
|
||||
tooltiptext="&backButton.tooltip;"
|
||||
aria-label="&backCmd.label;"
|
||||
command="Browser:BackOrBackDuplicate"
|
||||
onclick="checkForMiddleClick(this, event);"/>
|
||||
<menuitem id="context-forward"
|
||||
class="menuitem-iconic"
|
||||
tooltiptext="&forwardButton.tooltip;"
|
||||
aria-label="&forwardCmd.label;"
|
||||
command="Browser:ForwardOrForwardDuplicate"
|
||||
onclick="checkForMiddleClick(this, event);"/>
|
||||
<menuitem id="context-reload"
|
||||
class="menuitem-iconic"
|
||||
tooltiptext="&reloadButton.tooltip;"
|
||||
aria-label="&reloadCmd.label;"
|
||||
oncommand="gContextMenu.reload(event);"
|
||||
onclick="checkForMiddleClick(this, event);"/>
|
||||
<menuitem id="context-stop"
|
||||
class="menuitem-iconic"
|
||||
tooltiptext="&stopButton.tooltip;"
|
||||
aria-label="&stopCmd.label;"
|
||||
command="Browser:Stop"/>
|
||||
<menuitem id="context-bookmarkpage"
|
||||
class="menuitem-iconic"
|
||||
observes="bookmarkThisPageBroadcaster"
|
||||
aria-label="&bookmarkPageCmd2.label;"
|
||||
oncommand="gContextMenu.bookmarkThisPage();"/>
|
||||
</menugroup>
|
||||
<menuseparator id="context-sep-navigation"/>
|
||||
<menuseparator id="page-menu-separator"/>
|
||||
<menuitem id="spell-no-suggestions"
|
||||
disabled="true"
|
||||
label="&spellNoSuggestions.label;"/>
|
||||
<menuitem id="spell-add-to-dictionary"
|
||||
label="&spellAddToDictionary.label;"
|
||||
accesskey="&spellAddToDictionary.accesskey;"
|
||||
oncommand="InlineSpellCheckerUI.addToDictionary();"/>
|
||||
<menuitem id="spell-undo-add-to-dictionary"
|
||||
label="&spellUndoAddToDictionary.label;"
|
||||
accesskey="&spellUndoAddToDictionary.accesskey;"
|
||||
oncommand="InlineSpellCheckerUI.undoAddToDictionary();" />
|
||||
<menuseparator id="spell-suggestions-separator"/>
|
||||
<menuitem id="context-openlinkincurrent"
|
||||
label="&openLinkCmdInCurrent.label;"
|
||||
accesskey="&openLinkCmdInCurrent.accesskey;"
|
||||
oncommand="gContextMenu.openLinkInCurrent();"/>
|
||||
# label and data-usercontextid are dynamically set.
|
||||
<menuitem id="context-openlinkincontainertab"
|
||||
accesskey="&openLinkCmdInTab.accesskey;"
|
||||
oncommand="gContextMenu.openLinkInTab(event);"/>
|
||||
<menuitem id="context-openlinkintab"
|
||||
label="&openLinkCmdInTab.label;"
|
||||
accesskey="&openLinkCmdInTab.accesskey;"
|
||||
data-usercontextid="0"
|
||||
oncommand="gContextMenu.openLinkInTab(event);"/>
|
||||
|
||||
<menu id="context-openlinkinusercontext-menu"
|
||||
label="&openLinkCmdInContainerTab.label;"
|
||||
accesskey="&openLinkCmdInContainerTab.accesskey;"
|
||||
hidden="true">
|
||||
<menupopup oncommand="gContextMenu.openLinkInTab(event);"
|
||||
onpopupshowing="return gContextMenu.createContainerMenu(event);" />
|
||||
</menu>
|
||||
|
||||
<menuitem id="context-openlink"
|
||||
label="&openLinkCmd.label;"
|
||||
accesskey="&openLinkCmd.accesskey;"
|
||||
oncommand="gContextMenu.openLink();"/>
|
||||
<menuitem id="context-openlinkprivate"
|
||||
label="&openLinkInPrivateWindowCmd.label;"
|
||||
accesskey="&openLinkInPrivateWindowCmd.accesskey;"
|
||||
oncommand="gContextMenu.openLinkInPrivateWindow();"/>
|
||||
<menuseparator id="context-sep-open"/>
|
||||
<menuitem id="context-bookmarklink"
|
||||
label="&bookmarkThisLinkCmd.label;"
|
||||
accesskey="&bookmarkThisLinkCmd.accesskey;"
|
||||
oncommand="gContextMenu.bookmarkLink();"/>
|
||||
<menuitem id="context-savelink"
|
||||
label="&saveLinkCmd.label;"
|
||||
accesskey="&saveLinkCmd.accesskey;"
|
||||
oncommand="gContextMenu.saveLink();"/>
|
||||
<menuitem id="context-copyemail"
|
||||
label="©EmailCmd.label;"
|
||||
accesskey="©EmailCmd.accesskey;"
|
||||
oncommand="gContextMenu.copyEmail();"/>
|
||||
<menuitem id="context-copylink"
|
||||
label="©LinkCmd.label;"
|
||||
accesskey="©LinkCmd.accesskey;"
|
||||
oncommand="gContextMenu.copyLink();"/>
|
||||
<menuseparator id="context-sep-copylink"/>
|
||||
<menuitem id="context-media-play"
|
||||
label="&mediaPlay.label;"
|
||||
accesskey="&mediaPlay.accesskey;"
|
||||
oncommand="gContextMenu.mediaCommand('play');"/>
|
||||
<menuitem id="context-media-pause"
|
||||
label="&mediaPause.label;"
|
||||
accesskey="&mediaPause.accesskey;"
|
||||
oncommand="gContextMenu.mediaCommand('pause');"/>
|
||||
<menuitem id="context-media-mute"
|
||||
label="&mediaMute.label;"
|
||||
accesskey="&mediaMute.accesskey;"
|
||||
oncommand="gContextMenu.mediaCommand('mute');"/>
|
||||
<menuitem id="context-media-unmute"
|
||||
label="&mediaUnmute.label;"
|
||||
accesskey="&mediaUnmute.accesskey;"
|
||||
oncommand="gContextMenu.mediaCommand('unmute');"/>
|
||||
<menu id="context-media-playbackrate" label="&mediaPlaybackRate2.label;" accesskey="&mediaPlaybackRate2.accesskey;">
|
||||
<menupopup>
|
||||
<menuitem id="context-media-playbackrate-050x"
|
||||
label="&mediaPlaybackRate050x2.label;"
|
||||
accesskey="&mediaPlaybackRate050x2.accesskey;"
|
||||
type="radio"
|
||||
name="playbackrate"
|
||||
oncommand="gContextMenu.mediaCommand('playbackRate', 0.5);"/>
|
||||
<menuitem id="context-media-playbackrate-100x"
|
||||
label="&mediaPlaybackRate100x2.label;"
|
||||
accesskey="&mediaPlaybackRate100x2.accesskey;"
|
||||
type="radio"
|
||||
name="playbackrate"
|
||||
checked="true"
|
||||
oncommand="gContextMenu.mediaCommand('playbackRate', 1.0);"/>
|
||||
<menuitem id="context-media-playbackrate-125x"
|
||||
label="&mediaPlaybackRate125x2.label;"
|
||||
accesskey="&mediaPlaybackRate125x2.accesskey;"
|
||||
type="radio"
|
||||
name="playbackrate"
|
||||
oncommand="gContextMenu.mediaCommand('playbackRate', 1.25);"/>
|
||||
<menuitem id="context-media-playbackrate-150x"
|
||||
label="&mediaPlaybackRate150x2.label;"
|
||||
accesskey="&mediaPlaybackRate150x2.accesskey;"
|
||||
type="radio"
|
||||
name="playbackrate"
|
||||
oncommand="gContextMenu.mediaCommand('playbackRate', 1.5);"/>
|
||||
<menuitem id="context-media-playbackrate-200x"
|
||||
label="&mediaPlaybackRate200x2.label;"
|
||||
accesskey="&mediaPlaybackRate200x2.accesskey;"
|
||||
type="radio"
|
||||
name="playbackrate"
|
||||
oncommand="gContextMenu.mediaCommand('playbackRate', 2.0);"/>
|
||||
</menupopup>
|
||||
</menu>
|
||||
<menuitem id="context-media-loop"
|
||||
label="&mediaLoop.label;"
|
||||
accesskey="&mediaLoop.accesskey;"
|
||||
type="checkbox"
|
||||
oncommand="gContextMenu.mediaCommand('loop');"/>
|
||||
<menuitem id="context-media-showcontrols"
|
||||
label="&mediaShowControls.label;"
|
||||
accesskey="&mediaShowControls.accesskey;"
|
||||
oncommand="gContextMenu.mediaCommand('showcontrols');"/>
|
||||
<menuitem id="context-media-hidecontrols"
|
||||
label="&mediaHideControls.label;"
|
||||
accesskey="&mediaHideControls.accesskey;"
|
||||
oncommand="gContextMenu.mediaCommand('hidecontrols');"/>
|
||||
<menuitem id="context-video-fullscreen"
|
||||
accesskey="&videoFullScreen.accesskey;"
|
||||
label="&videoFullScreen.label;"
|
||||
oncommand="gContextMenu.mediaCommand('fullscreen');"/>
|
||||
<menuitem id="context-leave-dom-fullscreen"
|
||||
accesskey="&leaveDOMFullScreen.accesskey;"
|
||||
label="&leaveDOMFullScreen.label;"
|
||||
oncommand="gContextMenu.leaveDOMFullScreen();"/>
|
||||
<menuseparator id="context-media-sep-commands"/>
|
||||
<menuitem id="context-reloadimage"
|
||||
label="&reloadImageCmd.label;"
|
||||
accesskey="&reloadImageCmd.accesskey;"
|
||||
oncommand="gContextMenu.reloadImage();"/>
|
||||
<menuitem id="context-viewimage"
|
||||
label="&viewImageCmd.label;"
|
||||
accesskey="&viewImageCmd.accesskey;"
|
||||
oncommand="gContextMenu.viewMedia(event);"
|
||||
onclick="checkForMiddleClick(this, event);"/>
|
||||
<menuitem id="context-viewvideo"
|
||||
label="&viewVideoCmd.label;"
|
||||
accesskey="&viewVideoCmd.accesskey;"
|
||||
oncommand="gContextMenu.viewMedia(event);"
|
||||
onclick="checkForMiddleClick(this, event);"/>
|
||||
#ifdef CONTEXT_COPY_IMAGE_CONTENTS
|
||||
<menuitem id="context-copyimage-contents"
|
||||
label="©ImageContentsCmd.label;"
|
||||
accesskey="©ImageContentsCmd.accesskey;"
|
||||
oncommand="goDoCommand('cmd_copyImage');"/>
|
||||
#endif
|
||||
<menuitem id="context-copyimage"
|
||||
label="©ImageCmd.label;"
|
||||
accesskey="©ImageCmd.accesskey;"
|
||||
oncommand="gContextMenu.copyMediaLocation();"/>
|
||||
<menuitem id="context-copyvideourl"
|
||||
label="©VideoURLCmd.label;"
|
||||
accesskey="©VideoURLCmd.accesskey;"
|
||||
oncommand="gContextMenu.copyMediaLocation();"/>
|
||||
<menuitem id="context-copyaudiourl"
|
||||
label="©AudioURLCmd.label;"
|
||||
accesskey="©AudioURLCmd.accesskey;"
|
||||
oncommand="gContextMenu.copyMediaLocation();"/>
|
||||
<menuseparator id="context-sep-copyimage"/>
|
||||
<menuitem id="context-saveimage"
|
||||
label="&saveImageCmd.label;"
|
||||
accesskey="&saveImageCmd.accesskey;"
|
||||
oncommand="gContextMenu.saveMedia();"/>
|
||||
<menuitem id="context-sendimage"
|
||||
label="&emailImageCmd.label;"
|
||||
accesskey="&emailImageCmd.accesskey;"
|
||||
oncommand="gContextMenu.sendMedia();"/>
|
||||
<menuitem id="context-setDesktopBackground"
|
||||
label="&setDesktopBackgroundCmd.label;"
|
||||
accesskey="&setDesktopBackgroundCmd.accesskey;"
|
||||
oncommand="gContextMenu.setDesktopBackground();"/>
|
||||
<menuitem id="context-viewimageinfo"
|
||||
label="&viewImageInfoCmd.label;"
|
||||
accesskey="&viewImageInfoCmd.accesskey;"
|
||||
oncommand="gContextMenu.viewImageInfo();"/>
|
||||
<menuitem id="context-viewimagedesc"
|
||||
label="&viewImageDescCmd.label;"
|
||||
accesskey="&viewImageDescCmd.accesskey;"
|
||||
oncommand="gContextMenu.viewImageDesc(event);"
|
||||
onclick="checkForMiddleClick(this, event);"/>
|
||||
<menuitem id="context-savevideo"
|
||||
label="&saveVideoCmd.label;"
|
||||
accesskey="&saveVideoCmd.accesskey;"
|
||||
oncommand="gContextMenu.saveMedia();"/>
|
||||
<menuitem id="context-saveaudio"
|
||||
label="&saveAudioCmd.label;"
|
||||
accesskey="&saveAudioCmd.accesskey;"
|
||||
oncommand="gContextMenu.saveMedia();"/>
|
||||
<menuitem id="context-video-saveimage"
|
||||
accesskey="&videoSaveImage.accesskey;"
|
||||
label="&videoSaveImage.label;"
|
||||
oncommand="gContextMenu.saveVideoFrameAsImage();"/>
|
||||
<menuitem id="context-sendvideo"
|
||||
label="&emailVideoCmd.label;"
|
||||
accesskey="&emailVideoCmd.accesskey;"
|
||||
oncommand="gContextMenu.sendMedia();"/>
|
||||
<menuitem id="context-sendaudio"
|
||||
label="&emailAudioCmd.label;"
|
||||
accesskey="&emailAudioCmd.accesskey;"
|
||||
oncommand="gContextMenu.sendMedia();"/>
|
||||
<menuitem id="context-ctp-play"
|
||||
label="&playPluginCmd.label;"
|
||||
accesskey="&playPluginCmd.accesskey;"
|
||||
oncommand="gContextMenu.playPlugin();"/>
|
||||
<menuitem id="context-ctp-hide"
|
||||
label="&hidePluginCmd.label;"
|
||||
accesskey="&hidePluginCmd.accesskey;"
|
||||
oncommand="gContextMenu.hidePlugin();"/>
|
||||
<menuseparator id="context-sep-ctp"/>
|
||||
<menuitem id="context-savepage"
|
||||
label="&savePageCmd.label;"
|
||||
accesskey="&savePageCmd.accesskey2;"
|
||||
oncommand="gContextMenu.savePageAs();"/>
|
||||
<menuseparator id="context-sep-sendpagetodevice" hidden="true"/>
|
||||
<menuseparator id="context-sep-viewbgimage"/>
|
||||
<menuitem id="context-viewbgimage"
|
||||
label="&viewBGImageCmd.label;"
|
||||
accesskey="&viewBGImageCmd.accesskey;"
|
||||
oncommand="gContextMenu.viewBGImage(event);"
|
||||
onclick="checkForMiddleClick(this, event);"/>
|
||||
<menuitem id="context-undo"
|
||||
label="&undoCmd.label;"
|
||||
accesskey="&undoCmd.accesskey;"
|
||||
command="cmd_undo"/>
|
||||
<menuseparator id="context-sep-undo"/>
|
||||
<menuitem id="context-cut"
|
||||
label="&cutCmd.label;"
|
||||
accesskey="&cutCmd.accesskey;"
|
||||
command="cmd_cut"/>
|
||||
<menuitem id="context-copy"
|
||||
label="©Cmd.label;"
|
||||
accesskey="©Cmd.accesskey;"
|
||||
command="cmd_copy"/>
|
||||
<menuitem id="context-paste"
|
||||
label="&pasteCmd.label;"
|
||||
accesskey="&pasteCmd.accesskey;"
|
||||
command="cmd_paste"/>
|
||||
<menuitem id="context-delete"
|
||||
label="&deleteCmd.label;"
|
||||
accesskey="&deleteCmd.accesskey;"
|
||||
command="cmd_delete"/>
|
||||
<menuseparator id="context-sep-paste"/>
|
||||
<menuitem id="context-selectall"
|
||||
label="&selectAllCmd.label;"
|
||||
accesskey="&selectAllCmd.accesskey;"
|
||||
command="cmd_selectAll"/>
|
||||
<menuseparator id="context-sep-selectall"/>
|
||||
<menuitem id="context-keywordfield"
|
||||
label="&keywordfield.label;"
|
||||
accesskey="&keywordfield.accesskey;"
|
||||
oncommand="AddKeywordForSearchField();"/>
|
||||
<menuitem id="context-searchselect"
|
||||
oncommand="BrowserSearch.loadSearchFromContext(this.searchTerms);"/>
|
||||
<menuseparator id="context-sep-sendlinktodevice" hidden="true"/>
|
||||
<menuseparator id="frame-sep"/>
|
||||
<menu id="frame" label="&thisFrameMenu.label;" accesskey="&thisFrameMenu.accesskey;">
|
||||
<menupopup>
|
||||
<menuitem id="context-showonlythisframe"
|
||||
label="&showOnlyThisFrameCmd.label;"
|
||||
accesskey="&showOnlyThisFrameCmd.accesskey;"
|
||||
oncommand="gContextMenu.showOnlyThisFrame();"/>
|
||||
<menuitem id="context-openframeintab"
|
||||
label="&openFrameCmdInTab.label;"
|
||||
accesskey="&openFrameCmdInTab.accesskey;"
|
||||
oncommand="gContextMenu.openFrameInTab();"/>
|
||||
<menuitem id="context-openframe"
|
||||
label="&openFrameCmd.label;"
|
||||
accesskey="&openFrameCmd.accesskey;"
|
||||
oncommand="gContextMenu.openFrame();"/>
|
||||
<menuseparator id="open-frame-sep"/>
|
||||
<menuitem id="context-reloadframe"
|
||||
label="&reloadFrameCmd.label;"
|
||||
accesskey="&reloadFrameCmd.accesskey;"
|
||||
oncommand="gContextMenu.reloadFrame();"/>
|
||||
<menuseparator/>
|
||||
<menuitem id="context-bookmarkframe"
|
||||
label="&bookmarkThisFrameCmd.label;"
|
||||
accesskey="&bookmarkThisFrameCmd.accesskey;"
|
||||
oncommand="gContextMenu.addBookmarkForFrame();"/>
|
||||
<menuitem id="context-saveframe"
|
||||
label="&saveFrameCmd.label;"
|
||||
accesskey="&saveFrameCmd.accesskey;"
|
||||
oncommand="gContextMenu.saveFrame();"/>
|
||||
<menuseparator/>
|
||||
<menuitem id="context-printframe"
|
||||
label="&printFrameCmd.label;"
|
||||
accesskey="&printFrameCmd.accesskey;"
|
||||
oncommand="gContextMenu.printFrame();"/>
|
||||
<menuseparator/>
|
||||
<menuitem id="context-viewframesource"
|
||||
label="&viewFrameSourceCmd.label;"
|
||||
accesskey="&viewFrameSourceCmd.accesskey;"
|
||||
oncommand="gContextMenu.viewFrameSource();"
|
||||
observes="isFrameImage"/>
|
||||
<menuitem id="context-viewframeinfo"
|
||||
label="&viewFrameInfoCmd.label;"
|
||||
accesskey="&viewFrameInfoCmd.accesskey;"
|
||||
oncommand="gContextMenu.viewFrameInfo();"/>
|
||||
</menupopup>
|
||||
</menu>
|
||||
<menuitem id="context-viewpartialsource-selection"
|
||||
label="&viewPartialSourceForSelectionCmd.label;"
|
||||
accesskey="&viewPartialSourceCmd.accesskey;"
|
||||
oncommand="gContextMenu.viewPartialSource('selection');"
|
||||
observes="isImage"/>
|
||||
<menuitem id="context-viewpartialsource-mathml"
|
||||
label="&viewPartialSourceForMathMLCmd.label;"
|
||||
accesskey="&viewPartialSourceCmd.accesskey;"
|
||||
oncommand="gContextMenu.viewPartialSource('mathml');"
|
||||
observes="isImage"/>
|
||||
<menuseparator id="context-sep-viewsource"/>
|
||||
<menuitem id="context-viewsource"
|
||||
label="&viewPageSourceCmd.label;"
|
||||
accesskey="&viewPageSourceCmd.accesskey;"
|
||||
oncommand="BrowserViewSource(gContextMenu.browser);"
|
||||
observes="canViewSource"/>
|
||||
<menuitem id="context-viewinfo"
|
||||
label="&viewPageInfoCmd.label;"
|
||||
accesskey="&viewPageInfoCmd.accesskey;"
|
||||
oncommand="gContextMenu.viewInfo();"/>
|
||||
<menuseparator id="spell-separator"/>
|
||||
<menuitem id="spell-check-enabled"
|
||||
label="&spellCheckToggle.label;"
|
||||
type="checkbox"
|
||||
accesskey="&spellCheckToggle.accesskey;"
|
||||
oncommand="InlineSpellCheckerUI.toggleEnabled(window);"/>
|
||||
<menuitem id="spell-add-dictionaries-main"
|
||||
label="&spellAddDictionaries.label;"
|
||||
accesskey="&spellAddDictionaries.accesskey;"
|
||||
oncommand="gContextMenu.addDictionaries();"/>
|
||||
<menu id="spell-dictionaries"
|
||||
label="&spellDictionaries.label;"
|
||||
accesskey="&spellDictionaries.accesskey;">
|
||||
<menupopup id="spell-dictionaries-menu">
|
||||
<menuseparator id="spell-language-separator"/>
|
||||
<menuitem id="spell-add-dictionaries"
|
||||
label="&spellAddDictionaries.label;"
|
||||
accesskey="&spellAddDictionaries.accesskey;"
|
||||
oncommand="gContextMenu.addDictionaries();"/>
|
||||
</menupopup>
|
||||
</menu>
|
||||
<menuseparator hidden="true" id="context-sep-bidi"/>
|
||||
<menuitem hidden="true" id="context-bidi-text-direction-toggle"
|
||||
label="&bidiSwitchTextDirectionItem.label;"
|
||||
accesskey="&bidiSwitchTextDirectionItem.accesskey;"
|
||||
command="cmd_switchTextDirection"/>
|
||||
<menuitem hidden="true" id="context-bidi-page-direction-toggle"
|
||||
label="&bidiSwitchPageDirectionItem.label;"
|
||||
accesskey="&bidiSwitchPageDirectionItem.accesskey;"
|
||||
oncommand="gContextMenu.switchPageDirection();"/>
|
||||
<menuseparator id="fill-login-separator" hidden="true"/>
|
||||
<menu id="fill-login"
|
||||
label="&fillLoginMenu.label;"
|
||||
label-login="&fillLoginMenu.label;"
|
||||
label-password="&fillPasswordMenu.label;"
|
||||
label-username="&fillUsernameMenu.label;"
|
||||
accesskey="&fillLoginMenu.accesskey;"
|
||||
accesskey-login="&fillLoginMenu.accesskey;"
|
||||
accesskey-password="&fillPasswordMenu.accesskey;"
|
||||
accesskey-username="&fillUsernameMenu.accesskey;"
|
||||
hidden="true">
|
||||
<menupopup id="fill-login-popup">
|
||||
<menuitem id="fill-login-no-logins"
|
||||
label="&noLoginSuggestions.label;"
|
||||
disabled="true"
|
||||
hidden="true"/>
|
||||
<menuseparator id="saved-logins-separator"/>
|
||||
<menuitem id="fill-login-saved-passwords"
|
||||
label="&viewSavedLogins.label;"
|
||||
oncommand="gContextMenu.openPasswordManager();"/>
|
||||
</menupopup>
|
||||
</menu>
|
||||
#ifdef MOZ_DEVTOOLS
|
||||
<menuseparator id="inspect-separator" hidden="true"/>
|
||||
<menuitem id="context-inspect"
|
||||
hidden="true"
|
||||
label="&inspectContextMenu.label;"
|
||||
accesskey="&inspectContextMenu.accesskey;"
|
||||
oncommand="gContextMenu.inspectNode();"/>
|
||||
#endif
|
||||
<menuseparator id="context-media-eme-separator" hidden="true"/>
|
||||
<menuitem id="context-media-eme-learnmore"
|
||||
class="menuitem-iconic"
|
||||
hidden="true"
|
||||
label="&emeLearnMoreContextMenu.label;"
|
||||
accesskey="&emeLearnMoreContextMenu.accesskey;"
|
||||
oncommand="gContextMenu.drmLearnMore(event);"
|
||||
onclick="checkForMiddleClick(this, event);"/>
|
||||
587
browser/base/content/browser-ctrlTab.js
Normal file
|
|
@ -0,0 +1,587 @@
|
|||
/* 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/. */
|
||||
|
||||
/**
|
||||
* Tab previews utility, produces thumbnails
|
||||
*/
|
||||
var tabPreviews = {
|
||||
init: function() {
|
||||
if (this._selectedTab)
|
||||
return;
|
||||
this._selectedTab = gBrowser.selectedTab;
|
||||
|
||||
gBrowser.tabContainer.addEventListener("TabSelect", this, false);
|
||||
gBrowser.tabContainer.addEventListener("SSTabRestored", this, false);
|
||||
|
||||
let screenManager = Cc["@mozilla.org/gfx/screenmanager;1"]
|
||||
.getService(Ci.nsIScreenManager);
|
||||
let left = {}, top = {}, width = {}, height = {};
|
||||
screenManager.primaryScreen.GetRectDisplayPix(left, top, width, height);
|
||||
this.aspectRatio = height.value / width.value;
|
||||
},
|
||||
|
||||
get: function(aTab) {
|
||||
let uri = aTab.linkedBrowser.currentURI.spec;
|
||||
|
||||
if (aTab.__thumbnail_lastURI &&
|
||||
aTab.__thumbnail_lastURI != uri) {
|
||||
aTab.__thumbnail = null;
|
||||
aTab.__thumbnail_lastURI = null;
|
||||
}
|
||||
|
||||
if (aTab.__thumbnail)
|
||||
return aTab.__thumbnail;
|
||||
|
||||
if (aTab.getAttribute("pending") == "true") {
|
||||
let img = new Image;
|
||||
img.src = PageThumbs.getThumbnailURL(uri);
|
||||
return img;
|
||||
}
|
||||
|
||||
return this.capture(aTab, !aTab.hasAttribute("busy"));
|
||||
},
|
||||
|
||||
capture: function(aTab, aShouldCache) {
|
||||
let browser = aTab.linkedBrowser;
|
||||
let uri = browser.currentURI.spec;
|
||||
let canvas = PageThumbs.createCanvas(window);
|
||||
PageThumbs.shouldStoreThumbnail(browser, (aDoStore) => {
|
||||
if (aDoStore && aShouldCache) {
|
||||
PageThumbs.captureAndStore(browser, function () {
|
||||
let img = new Image;
|
||||
img.src = PageThumbs.getThumbnailURL(uri);
|
||||
aTab.__thumbnail = img;
|
||||
aTab.__thumbnail_lastURI = uri;
|
||||
canvas.getContext("2d").drawImage(img, 0, 0);
|
||||
});
|
||||
} else {
|
||||
PageThumbs.captureToCanvas(browser, canvas, () => {
|
||||
if (aShouldCache) {
|
||||
aTab.__thumbnail = canvas;
|
||||
aTab.__thumbnail_lastURI = uri;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
return canvas;
|
||||
},
|
||||
|
||||
handleEvent: function(event) {
|
||||
switch (event.type) {
|
||||
case "TabSelect":
|
||||
if (this._selectedTab &&
|
||||
this._selectedTab.parentNode &&
|
||||
!this._pendingUpdate) {
|
||||
// Generate a thumbnail for the tab that was selected.
|
||||
// The timeout keeps the UI snappy and prevents us from generating thumbnails
|
||||
// for tabs that will be closed. During that timeout, don't generate other
|
||||
// thumbnails in case multiple TabSelect events occur fast in succession.
|
||||
this._pendingUpdate = true;
|
||||
setTimeout(function (self, aTab) {
|
||||
self._pendingUpdate = false;
|
||||
if (aTab.parentNode &&
|
||||
!aTab.hasAttribute("busy") &&
|
||||
!aTab.hasAttribute("pending"))
|
||||
self.capture(aTab, true);
|
||||
}, 2000, this, this._selectedTab);
|
||||
}
|
||||
this._selectedTab = event.target;
|
||||
break;
|
||||
case "SSTabRestored":
|
||||
this.capture(event.target, true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var tabPreviewPanelHelper = {
|
||||
opening: function (host) {
|
||||
host.panel.hidden = false;
|
||||
|
||||
var handler = this._generateHandler(host);
|
||||
host.panel.addEventListener("popupshown", handler, false);
|
||||
host.panel.addEventListener("popuphiding", handler, false);
|
||||
|
||||
host._prevFocus = document.commandDispatcher.focusedElement;
|
||||
},
|
||||
_generateHandler: function (host) {
|
||||
var self = this;
|
||||
return function (event) {
|
||||
if (event.target == host.panel) {
|
||||
host.panel.removeEventListener(event.type, arguments.callee, false);
|
||||
self["_" + event.type](host);
|
||||
}
|
||||
};
|
||||
},
|
||||
_popupshown: function (host) {
|
||||
if ("setupGUI" in host)
|
||||
host.setupGUI();
|
||||
},
|
||||
_popuphiding: function (host) {
|
||||
if ("suspendGUI" in host)
|
||||
host.suspendGUI();
|
||||
|
||||
if (host._prevFocus) {
|
||||
Services.focus.setFocus(host._prevFocus, Ci.nsIFocusManager.FLAG_NOSCROLL);
|
||||
host._prevFocus = null;
|
||||
} else
|
||||
gBrowser.selectedBrowser.focus();
|
||||
|
||||
if (host.tabToSelect) {
|
||||
gBrowser.selectedTab = host.tabToSelect;
|
||||
host.tabToSelect = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Ctrl-Tab panel
|
||||
*/
|
||||
var ctrlTab = {
|
||||
get panel () {
|
||||
delete this.panel;
|
||||
return this.panel = document.getElementById("ctrlTab-panel");
|
||||
},
|
||||
get showAllButton () {
|
||||
delete this.showAllButton;
|
||||
return this.showAllButton = document.getElementById("ctrlTab-showAll");
|
||||
},
|
||||
get previews () {
|
||||
delete this.previews;
|
||||
return this.previews = this.panel.getElementsByClassName("ctrlTab-preview");
|
||||
},
|
||||
get maxTabPreviews () {
|
||||
delete this.maxTabPreviews;
|
||||
return this.maxTabPreviews = this.previews.length - 1;
|
||||
},
|
||||
get canvasWidth () {
|
||||
delete this.canvasWidth;
|
||||
return this.canvasWidth = Math.ceil(screen.availWidth * .85 / this.maxTabPreviews);
|
||||
},
|
||||
get canvasHeight () {
|
||||
delete this.canvasHeight;
|
||||
return this.canvasHeight = Math.round(this.canvasWidth * tabPreviews.aspectRatio);
|
||||
},
|
||||
get keys () {
|
||||
var keys = {};
|
||||
["close", "find", "selectAll"].forEach(function (key) {
|
||||
keys[key] = document.getElementById("key_" + key)
|
||||
.getAttribute("key")
|
||||
.toLocaleLowerCase().charCodeAt(0);
|
||||
});
|
||||
delete this.keys;
|
||||
return this.keys = keys;
|
||||
},
|
||||
_selectedIndex: 0,
|
||||
get selected () {
|
||||
return this._selectedIndex < 0 ?
|
||||
document.activeElement :
|
||||
this.previews.item(this._selectedIndex);
|
||||
},
|
||||
get isOpen () {
|
||||
return this.panel.state == "open" || this.panel.state == "showing" || this._timer;
|
||||
},
|
||||
get tabCount () {
|
||||
return this.tabList.length;
|
||||
},
|
||||
get tabPreviewCount () {
|
||||
return Math.min(this.maxTabPreviews, this.tabCount);
|
||||
},
|
||||
|
||||
get tabList () {
|
||||
return this._recentlyUsedTabs;
|
||||
},
|
||||
|
||||
init: function() {
|
||||
if (!this._recentlyUsedTabs) {
|
||||
tabPreviews.init();
|
||||
|
||||
this._initRecentlyUsedTabs();
|
||||
this._init(true);
|
||||
}
|
||||
},
|
||||
|
||||
uninit: function() {
|
||||
this._recentlyUsedTabs = null;
|
||||
this._init(false);
|
||||
},
|
||||
|
||||
prefName: "browser.ctrlTab.previews",
|
||||
readPref: function() {
|
||||
var enable =
|
||||
gPrefService.getBoolPref(this.prefName) &&
|
||||
(!gPrefService.prefHasUserValue("browser.ctrlTab.disallowForScreenReaders") ||
|
||||
!gPrefService.getBoolPref("browser.ctrlTab.disallowForScreenReaders"));
|
||||
|
||||
if (enable)
|
||||
this.init();
|
||||
else
|
||||
this.uninit();
|
||||
},
|
||||
observe: function (aSubject, aTopic, aPrefName) {
|
||||
this.readPref();
|
||||
},
|
||||
|
||||
updatePreviews: function() {
|
||||
for (let i = 0; i < this.previews.length; i++)
|
||||
this.updatePreview(this.previews[i], this.tabList[i]);
|
||||
|
||||
var showAllLabel = gNavigatorBundle.getString("ctrlTab.listAllTabs.label");
|
||||
this.showAllButton.label =
|
||||
PluralForm.get(this.tabCount, showAllLabel).replace("#1", this.tabCount);
|
||||
this.showAllButton.hidden = !allTabs.canOpen;
|
||||
},
|
||||
|
||||
updatePreview: function(aPreview, aTab) {
|
||||
if (aPreview == this.showAllButton)
|
||||
return;
|
||||
|
||||
aPreview._tab = aTab;
|
||||
|
||||
if (aPreview.firstChild)
|
||||
aPreview.removeChild(aPreview.firstChild);
|
||||
if (aTab) {
|
||||
let canvasWidth = this.canvasWidth;
|
||||
let canvasHeight = this.canvasHeight;
|
||||
aPreview.appendChild(tabPreviews.get(aTab));
|
||||
aPreview.setAttribute("label", aTab.label);
|
||||
aPreview.setAttribute("tooltiptext", aTab.label);
|
||||
aPreview.setAttribute("crop", aTab.crop);
|
||||
aPreview.setAttribute("canvaswidth", canvasWidth);
|
||||
aPreview.setAttribute("canvasstyle",
|
||||
"max-width:" + canvasWidth + "px;" +
|
||||
"min-width:" + canvasWidth + "px;" +
|
||||
"max-height:" + canvasHeight + "px;" +
|
||||
"min-height:" + canvasHeight + "px;");
|
||||
if (aTab.image)
|
||||
aPreview.setAttribute("image", aTab.image);
|
||||
else
|
||||
aPreview.removeAttribute("image");
|
||||
aPreview.hidden = false;
|
||||
} else {
|
||||
aPreview.hidden = true;
|
||||
aPreview.removeAttribute("label");
|
||||
aPreview.removeAttribute("tooltiptext");
|
||||
aPreview.removeAttribute("image");
|
||||
}
|
||||
},
|
||||
|
||||
advanceFocus: function(aForward) {
|
||||
let selectedIndex = Array.indexOf(this.previews, this.selected);
|
||||
do {
|
||||
selectedIndex += aForward ? 1 : -1;
|
||||
if (selectedIndex < 0)
|
||||
selectedIndex = this.previews.length - 1;
|
||||
else if (selectedIndex >= this.previews.length)
|
||||
selectedIndex = 0;
|
||||
} while (this.previews[selectedIndex].hidden);
|
||||
|
||||
if (this._selectedIndex == -1) {
|
||||
// Focus is already in the panel.
|
||||
this.previews[selectedIndex].focus();
|
||||
} else {
|
||||
this._selectedIndex = selectedIndex;
|
||||
}
|
||||
|
||||
if (this._timer) {
|
||||
clearTimeout(this._timer);
|
||||
this._timer = null;
|
||||
this._openPanel();
|
||||
}
|
||||
},
|
||||
|
||||
_mouseOverFocus: function(aPreview) {
|
||||
if (this._trackMouseOver)
|
||||
aPreview.focus();
|
||||
},
|
||||
|
||||
pick: function(aPreview) {
|
||||
if (!this.tabCount)
|
||||
return;
|
||||
|
||||
var select = (aPreview || this.selected);
|
||||
|
||||
if (select == this.showAllButton)
|
||||
this.showAllTabs();
|
||||
else
|
||||
this.close(select._tab);
|
||||
},
|
||||
|
||||
showAllTabs: function(aPreview) {
|
||||
this.close();
|
||||
document.getElementById("Browser:ShowAllTabs").doCommand();
|
||||
},
|
||||
|
||||
remove: function(aPreview) {
|
||||
if (aPreview._tab)
|
||||
gBrowser.removeTab(aPreview._tab);
|
||||
},
|
||||
|
||||
attachTab: function(aTab, aPos) {
|
||||
if (aTab.closing)
|
||||
return;
|
||||
|
||||
if (aPos == 0)
|
||||
this._recentlyUsedTabs.unshift(aTab);
|
||||
else if (aPos)
|
||||
this._recentlyUsedTabs.splice(aPos, 0, aTab);
|
||||
else
|
||||
this._recentlyUsedTabs.push(aTab);
|
||||
},
|
||||
|
||||
detachTab: function(aTab) {
|
||||
var i = this._recentlyUsedTabs.indexOf(aTab);
|
||||
if (i >= 0)
|
||||
this._recentlyUsedTabs.splice(i, 1);
|
||||
},
|
||||
|
||||
open: function() {
|
||||
if (this.isOpen)
|
||||
return;
|
||||
|
||||
document.addEventListener("keyup", this, true);
|
||||
|
||||
this.updatePreviews();
|
||||
this._selectedIndex = 1;
|
||||
|
||||
// Add a slight delay before showing the UI, so that a quick
|
||||
// "ctrl-tab" keypress just flips back to the MRU tab.
|
||||
this._timer = setTimeout(function (self) {
|
||||
self._timer = null;
|
||||
self._openPanel();
|
||||
}, 200, this);
|
||||
},
|
||||
|
||||
_openPanel: function() {
|
||||
tabPreviewPanelHelper.opening(this);
|
||||
|
||||
this.panel.width = Math.min(screen.availWidth * .99,
|
||||
this.canvasWidth * 1.25 * this.tabPreviewCount);
|
||||
var estimateHeight = this.canvasHeight * 1.25 + 75;
|
||||
this.panel.openPopupAtScreen(screen.availLeft + (screen.availWidth - this.panel.width) / 2,
|
||||
screen.availTop + (screen.availHeight - estimateHeight) / 2,
|
||||
false);
|
||||
},
|
||||
|
||||
close: function(aTabToSelect) {
|
||||
if (!this.isOpen)
|
||||
return;
|
||||
|
||||
if (this._timer) {
|
||||
clearTimeout(this._timer);
|
||||
this._timer = null;
|
||||
this.suspendGUI();
|
||||
if (aTabToSelect)
|
||||
gBrowser.selectedTab = aTabToSelect;
|
||||
return;
|
||||
}
|
||||
|
||||
this.tabToSelect = aTabToSelect;
|
||||
this.panel.hidePopup();
|
||||
},
|
||||
|
||||
setupGUI: function() {
|
||||
this.selected.focus();
|
||||
this._selectedIndex = -1;
|
||||
|
||||
// Track mouse movement after a brief delay so that the item that happens
|
||||
// to be under the mouse pointer initially won't be selected unintentionally.
|
||||
this._trackMouseOver = false;
|
||||
setTimeout(function (self) {
|
||||
if (self.isOpen)
|
||||
self._trackMouseOver = true;
|
||||
}, 0, this);
|
||||
},
|
||||
|
||||
suspendGUI: function() {
|
||||
document.removeEventListener("keyup", this, true);
|
||||
|
||||
for (let preview of this.previews) {
|
||||
this.updatePreview(preview, null);
|
||||
}
|
||||
},
|
||||
|
||||
onKeyPress: function ctrlTab_onKeyPress(event) {
|
||||
var isOpen = this.isOpen;
|
||||
|
||||
if (isOpen) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
switch (event.keyCode) {
|
||||
case event.DOM_VK_TAB:
|
||||
if (event.ctrlKey && !event.altKey && !event.metaKey) {
|
||||
if (isOpen) {
|
||||
this.advanceFocus(!event.shiftKey);
|
||||
} else if (!event.shiftKey) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
let tabs = gBrowser.visibleTabs;
|
||||
if (tabs.length > 2) {
|
||||
this.open();
|
||||
} else if (tabs.length == 2) {
|
||||
let index = tabs[0].selected ? 1 : 0;
|
||||
gBrowser.selectedTab = tabs[index];
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (isOpen && event.ctrlKey) {
|
||||
if (event.keyCode == event.DOM_VK_DELETE) {
|
||||
this.remove(this.selected);
|
||||
break;
|
||||
}
|
||||
switch (event.charCode) {
|
||||
case this.keys.close:
|
||||
this.remove(this.selected);
|
||||
break;
|
||||
case this.keys.find:
|
||||
case this.keys.selectAll:
|
||||
this.showAllTabs();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
removeClosingTabFromUI: function(aTab) {
|
||||
if (this.tabCount == 2) {
|
||||
this.close();
|
||||
return;
|
||||
}
|
||||
|
||||
this.updatePreviews();
|
||||
|
||||
if (this.selected.hidden)
|
||||
this.advanceFocus(false);
|
||||
if (this.selected == this.showAllButton)
|
||||
this.advanceFocus(false);
|
||||
|
||||
// If the current tab is removed, another tab can steal our focus.
|
||||
if (aTab.selected && this.panel.state == "open") {
|
||||
setTimeout(function (selected) {
|
||||
selected.focus();
|
||||
}, 0, this.selected);
|
||||
}
|
||||
},
|
||||
|
||||
handleEvent: function(event) {
|
||||
switch (event.type) {
|
||||
case "SSWindowRestored":
|
||||
this._initRecentlyUsedTabs();
|
||||
break;
|
||||
case "TabAttrModified":
|
||||
// tab attribute modified (e.g. label, crop, busy, image, selected)
|
||||
for (let i = this.previews.length - 1; i >= 0; i--) {
|
||||
if (this.previews[i]._tab && this.previews[i]._tab == event.target) {
|
||||
this.updatePreview(this.previews[i], event.target);
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "TabSelect":
|
||||
this.detachTab(event.target);
|
||||
this.attachTab(event.target, 0);
|
||||
break;
|
||||
case "TabOpen":
|
||||
this.attachTab(event.target, 1);
|
||||
break;
|
||||
case "TabClose":
|
||||
this.detachTab(event.target);
|
||||
if (this.isOpen)
|
||||
this.removeClosingTabFromUI(event.target);
|
||||
break;
|
||||
case "keypress":
|
||||
this.onKeyPress(event);
|
||||
break;
|
||||
case "keyup":
|
||||
if (event.keyCode == event.DOM_VK_CONTROL)
|
||||
this.pick();
|
||||
break;
|
||||
case "popupshowing":
|
||||
if (event.target.id == "menu_viewPopup")
|
||||
document.getElementById("menu_showAllTabs").hidden = !allTabs.canOpen;
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
filterForThumbnailExpiration: function (aCallback) {
|
||||
// Save a few more thumbnails than we actually display, so that when tabs
|
||||
// are closed, the previews we add instead still get thumbnails.
|
||||
const extraThumbnails = 3;
|
||||
const thumbnailCount = Math.min(this.tabPreviewCount + extraThumbnails,
|
||||
this.tabCount);
|
||||
|
||||
let urls = [];
|
||||
for (let i = 0; i < thumbnailCount; i++)
|
||||
urls.push(this.tabList[i].linkedBrowser.currentURI.spec);
|
||||
|
||||
aCallback(urls);
|
||||
},
|
||||
|
||||
_initRecentlyUsedTabs: function () {
|
||||
this._recentlyUsedTabs =
|
||||
Array.filter(gBrowser.tabs, tab => !tab.closing)
|
||||
.sort((tab1, tab2) => tab2.lastAccessed - tab1.lastAccessed);
|
||||
},
|
||||
|
||||
_init: function(enable) {
|
||||
var toggleEventListener = enable ? "addEventListener" : "removeEventListener";
|
||||
|
||||
window[toggleEventListener]("SSWindowRestored", this, false);
|
||||
|
||||
var tabContainer = gBrowser.tabContainer;
|
||||
tabContainer[toggleEventListener]("TabOpen", this, false);
|
||||
tabContainer[toggleEventListener]("TabAttrModified", this, false);
|
||||
tabContainer[toggleEventListener]("TabSelect", this, false);
|
||||
tabContainer[toggleEventListener]("TabClose", this, false);
|
||||
|
||||
document[toggleEventListener]("keypress", this, false);
|
||||
gBrowser.mTabBox.handleCtrlTab = !enable;
|
||||
|
||||
if (enable)
|
||||
PageThumbs.addExpirationFilter(this);
|
||||
else
|
||||
PageThumbs.removeExpirationFilter(this);
|
||||
|
||||
// If we're not running, hide the "Show All Tabs" menu item,
|
||||
// as Shift+Ctrl+Tab will be handled by the tab bar.
|
||||
document.getElementById("menu_showAllTabs").hidden = !enable;
|
||||
document.getElementById("menu_viewPopup")[toggleEventListener]("popupshowing", this);
|
||||
|
||||
// Also disable the <key> to ensure Shift+Ctrl+Tab never triggers
|
||||
// Show All Tabs.
|
||||
var key_showAllTabs = document.getElementById("key_showAllTabs");
|
||||
if (enable)
|
||||
key_showAllTabs.removeAttribute("disabled");
|
||||
else
|
||||
key_showAllTabs.setAttribute("disabled", "true");
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* All Tabs menu
|
||||
*/
|
||||
var allTabs = {
|
||||
get toolbarButton() {
|
||||
return document.getElementById("alltabs-button");
|
||||
},
|
||||
|
||||
get canOpen() {
|
||||
return isElementVisible(this.toolbarButton);
|
||||
},
|
||||
|
||||
open: function() {
|
||||
if (this.canOpen) {
|
||||
// Without setTimeout, the menupopup won't stay open when invoking
|
||||
// "View > Show All Tabs" and the menu bar auto-hides.
|
||||
setTimeout(() => {
|
||||
this.toolbarButton.open = true;
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
};
|
||||
100
browser/base/content/browser-customization.js
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
/**
|
||||
* Customization handler prepares this browser window for entering and exiting
|
||||
* customization mode by handling customizationstarting and customizationending
|
||||
* events.
|
||||
*/
|
||||
var CustomizationHandler = {
|
||||
handleEvent: function(aEvent) {
|
||||
switch (aEvent.type) {
|
||||
case "customizationstarting":
|
||||
this._customizationStarting();
|
||||
break;
|
||||
case "customizationchange":
|
||||
this._customizationChange();
|
||||
break;
|
||||
case "customizationending":
|
||||
this._customizationEnding(aEvent.detail);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
isCustomizing: function() {
|
||||
return document.documentElement.hasAttribute("customizing");
|
||||
},
|
||||
|
||||
_customizationStarting: function() {
|
||||
// Disable the toolbar context menu items
|
||||
let menubar = document.getElementById("main-menubar");
|
||||
for (let childNode of menubar.childNodes)
|
||||
childNode.setAttribute("disabled", true);
|
||||
|
||||
let cmd = document.getElementById("cmd_CustomizeToolbars");
|
||||
cmd.setAttribute("disabled", "true");
|
||||
|
||||
UpdateUrlbarSearchSplitterState();
|
||||
|
||||
CombinedStopReload.uninit();
|
||||
PlacesToolbarHelper.customizeStart();
|
||||
DownloadsButton.customizeStart();
|
||||
|
||||
// The additional padding on the sides of the browser
|
||||
// can cause the customize tab to get clipped.
|
||||
let tabContainer = gBrowser.tabContainer;
|
||||
if (tabContainer.getAttribute("overflow") == "true") {
|
||||
let tabstrip = tabContainer.mTabstrip;
|
||||
tabstrip.ensureElementIsVisible(gBrowser.selectedTab, true);
|
||||
}
|
||||
},
|
||||
|
||||
_customizationChange: function() {
|
||||
PlacesToolbarHelper.customizeChange();
|
||||
},
|
||||
|
||||
_customizationEnding: function(aDetails) {
|
||||
// Update global UI elements that may have been added or removed
|
||||
if (aDetails.changed) {
|
||||
gURLBar = document.getElementById("urlbar");
|
||||
|
||||
gHomeButton.updateTooltip();
|
||||
XULBrowserWindow.init();
|
||||
|
||||
if (AppConstants.platform != "macosx")
|
||||
updateEditUIVisibility();
|
||||
|
||||
// Hacky: update the PopupNotifications' object's reference to the iconBox,
|
||||
// if it already exists, since it may have changed if the URL bar was
|
||||
// added/removed.
|
||||
if (!window.__lookupGetter__("PopupNotifications")) {
|
||||
PopupNotifications.iconBox =
|
||||
document.getElementById("notification-popup-box");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
PlacesToolbarHelper.customizeDone();
|
||||
DownloadsButton.customizeDone();
|
||||
|
||||
// The url bar splitter state is dependent on whether stop/reload
|
||||
// and the location bar are combined, so we need this ordering
|
||||
CombinedStopReload.init();
|
||||
UpdateUrlbarSearchSplitterState();
|
||||
|
||||
// Update the urlbar
|
||||
URLBarSetURI();
|
||||
XULBrowserWindow.asyncUpdateUI();
|
||||
|
||||
// Re-enable parts of the UI we disabled during the dialog
|
||||
let menubar = document.getElementById("main-menubar");
|
||||
for (let childNode of menubar.childNodes)
|
||||
childNode.setAttribute("disabled", false);
|
||||
let cmd = document.getElementById("cmd_CustomizeToolbars");
|
||||
cmd.removeAttribute("disabled");
|
||||
|
||||
gBrowser.selectedBrowser.focus();
|
||||
}
|
||||
}
|
||||
121
browser/base/content/browser-data-submission-info-bar.js
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
/* 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/. */
|
||||
|
||||
/**
|
||||
* Represents an info bar that shows a data submission notification.
|
||||
*/
|
||||
var gDataNotificationInfoBar = {
|
||||
_OBSERVERS: [
|
||||
"datareporting:notify-data-policy:request",
|
||||
"datareporting:notify-data-policy:close",
|
||||
],
|
||||
|
||||
_DATA_REPORTING_NOTIFICATION: "data-reporting",
|
||||
|
||||
get _notificationBox() {
|
||||
delete this._notificationBox;
|
||||
return this._notificationBox = document.getElementById("global-notificationbox");
|
||||
},
|
||||
|
||||
get _log() {
|
||||
},
|
||||
|
||||
init: function() {
|
||||
window.addEventListener("unload", () => {
|
||||
for (let o of this._OBSERVERS) {
|
||||
Services.obs.removeObserver(this, o);
|
||||
}
|
||||
}, false);
|
||||
|
||||
for (let o of this._OBSERVERS) {
|
||||
Services.obs.addObserver(this, o, true);
|
||||
}
|
||||
},
|
||||
|
||||
_getDataReportingNotification: function (name=this._DATA_REPORTING_NOTIFICATION) {
|
||||
return this._notificationBox.getNotificationWithValue(name);
|
||||
},
|
||||
|
||||
_displayDataPolicyInfoBar: function (request) {
|
||||
if (this._getDataReportingNotification()) {
|
||||
return;
|
||||
}
|
||||
|
||||
let brandBundle = document.getElementById("bundle_brand");
|
||||
let appName = brandBundle.getString("brandShortName");
|
||||
let vendorName = brandBundle.getString("vendorShortName");
|
||||
|
||||
let message = gNavigatorBundle.getFormattedString(
|
||||
"dataReportingNotification.message",
|
||||
[appName, vendorName]);
|
||||
|
||||
this._actionTaken = false;
|
||||
|
||||
let buttons = [{
|
||||
label: gNavigatorBundle.getString("dataReportingNotification.button.label"),
|
||||
accessKey: gNavigatorBundle.getString("dataReportingNotification.button.accessKey"),
|
||||
popup: null,
|
||||
callback: () => {
|
||||
this._actionTaken = true;
|
||||
window.openAdvancedPreferences("dataChoicesTab");
|
||||
},
|
||||
}];
|
||||
|
||||
this._log.info("Creating data reporting policy notification.");
|
||||
this._notificationBox.appendNotification(
|
||||
message,
|
||||
this._DATA_REPORTING_NOTIFICATION,
|
||||
null,
|
||||
this._notificationBox.PRIORITY_INFO_HIGH,
|
||||
buttons,
|
||||
event => {
|
||||
if (event == "removed") {
|
||||
Services.obs.notifyObservers(null, "datareporting:notify-data-policy:close", null);
|
||||
}
|
||||
}
|
||||
);
|
||||
// It is important to defer calling onUserNotifyComplete() until we're
|
||||
// actually sure the notification was displayed. If we ever called
|
||||
// onUserNotifyComplete() without showing anything to the user, that
|
||||
// would be very good for user choice. It may also have legal impact.
|
||||
request.onUserNotifyComplete();
|
||||
},
|
||||
|
||||
_clearPolicyNotification: function () {
|
||||
let notification = this._getDataReportingNotification();
|
||||
if (notification) {
|
||||
this._log.debug("Closing notification.");
|
||||
notification.close();
|
||||
}
|
||||
},
|
||||
|
||||
observe: function(subject, topic, data) {
|
||||
switch (topic) {
|
||||
case "datareporting:notify-data-policy:request":
|
||||
let request = subject.wrappedJSObject.object;
|
||||
try {
|
||||
this._displayDataPolicyInfoBar(request);
|
||||
} catch (ex) {
|
||||
request.onUserNotifyFailed(ex);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case "datareporting:notify-data-policy:close":
|
||||
// If this observer fires, it means something else took care of
|
||||
// responding. Therefore, we don't need to do anything. So, we
|
||||
// act like we took action and clear state.
|
||||
this._actionTaken = true;
|
||||
this._clearPolicyNotification();
|
||||
break;
|
||||
|
||||
default:
|
||||
}
|
||||
},
|
||||
|
||||
QueryInterface: XPCOMUtils.generateQI([
|
||||
Ci.nsIObserver,
|
||||
Ci.nsISupportsWeakReference,
|
||||
]),
|
||||
};
|
||||
23
browser/base/content/browser-doctype.inc
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
<!DOCTYPE window [
|
||||
<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
|
||||
%brandDTD;
|
||||
<!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd" >
|
||||
%browserDTD;
|
||||
<!ENTITY % baseMenuDTD SYSTEM "chrome://browser/locale/baseMenuOverlay.dtd" >
|
||||
%baseMenuDTD;
|
||||
<!ENTITY % charsetDTD SYSTEM "chrome://global/locale/charsetMenu.dtd" >
|
||||
%charsetDTD;
|
||||
<!ENTITY % textcontextDTD SYSTEM "chrome://global/locale/textcontext.dtd" >
|
||||
%textcontextDTD;
|
||||
<!ENTITY % customizeToolbarDTD SYSTEM "chrome://global/locale/customizeToolbar.dtd">
|
||||
%customizeToolbarDTD;
|
||||
<!ENTITY % placesDTD SYSTEM "chrome://browser/locale/places/places.dtd">
|
||||
%placesDTD;
|
||||
<!ENTITY % aboutHomeDTD SYSTEM "chrome://browser/locale/aboutHome.dtd">
|
||||
%aboutHomeDTD;
|
||||
#ifdef MOZ_SERVICES_SYNC
|
||||
<!ENTITY % syncBrandDTD SYSTEM "chrome://browser/locale/syncBrand.dtd">
|
||||
%syncBrandDTD;
|
||||
#endif
|
||||
]>
|
||||
|
||||
646
browser/base/content/browser-feeds.js
Normal file
|
|
@ -0,0 +1,646 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "DeferredTask",
|
||||
"resource://gre/modules/DeferredTask.jsm");
|
||||
|
||||
const TYPE_MAYBE_FEED = "application/vnd.mozilla.maybe.feed";
|
||||
const TYPE_MAYBE_AUDIO_FEED = "application/vnd.mozilla.maybe.audio.feed";
|
||||
const TYPE_MAYBE_VIDEO_FEED = "application/vnd.mozilla.maybe.video.feed";
|
||||
|
||||
const PREF_SHOW_FIRST_RUN_UI = "browser.feeds.showFirstRunUI";
|
||||
|
||||
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";
|
||||
|
||||
const PREF_UPDATE_DELAY = 2000;
|
||||
|
||||
const SETTABLE_PREFS = new Set([
|
||||
PREF_VIDEO_SELECTED_ACTION,
|
||||
PREF_AUDIO_SELECTED_ACTION,
|
||||
PREF_SELECTED_ACTION,
|
||||
PREF_VIDEO_SELECTED_READER,
|
||||
PREF_AUDIO_SELECTED_READER,
|
||||
PREF_SELECTED_READER,
|
||||
PREF_VIDEO_SELECTED_WEB,
|
||||
PREF_AUDIO_SELECTED_WEB,
|
||||
PREF_SELECTED_WEB
|
||||
]);
|
||||
|
||||
const EXECUTABLE_PREFS = new Set([
|
||||
PREF_SELECTED_APP,
|
||||
PREF_VIDEO_SELECTED_APP,
|
||||
PREF_AUDIO_SELECTED_APP
|
||||
]);
|
||||
|
||||
const VALID_ACTIONS = new Set(["ask", "reader", "bookmarks"]);
|
||||
const VALID_READERS = new Set(["web", "client", "default", "bookmarks"]);
|
||||
|
||||
XPCOMUtils.defineLazyPreferenceGetter(this, "SHOULD_LOG",
|
||||
"feeds.log", false);
|
||||
|
||||
function LOG(str) {
|
||||
if (SHOULD_LOG)
|
||||
dump("*** Feeds: " + str + "\n");
|
||||
}
|
||||
|
||||
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 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 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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps a feed type to a maybe-feed mimetype.
|
||||
*/
|
||||
function getMimeTypeForFeedType(aFeedType) {
|
||||
switch (aFeedType) {
|
||||
case Ci.nsIFeed.TYPE_VIDEO:
|
||||
return TYPE_MAYBE_VIDEO_FEED;
|
||||
|
||||
case Ci.nsIFeed.TYPE_AUDIO:
|
||||
return TYPE_MAYBE_AUDIO_FEED;
|
||||
|
||||
default:
|
||||
return TYPE_MAYBE_FEED;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The Feed Handler object manages discovery of RSS/ATOM feeds in web pages
|
||||
* and shows UI when they are discovered.
|
||||
*/
|
||||
var FeedHandler = {
|
||||
_prefChangeCallback: null,
|
||||
|
||||
/** Called when the user clicks on the Subscribe to This Page... menu item,
|
||||
* or when the user clicks the feed button when the page contains multiple
|
||||
* feeds.
|
||||
* Builds a menu of unique feeds associated with the page, and if there
|
||||
* is only one, shows the feed inline in the browser window.
|
||||
* @param container
|
||||
* The feed list container (menupopup or subview) to be populated.
|
||||
* @param isSubview
|
||||
* Whether we're creating a subview (true) or menu (false/undefined)
|
||||
* @return true if the menu/subview should be shown, false if there was only
|
||||
* one feed and the feed should be shown inline in the browser
|
||||
* window (do not show the menupopup/subview).
|
||||
*/
|
||||
buildFeedList(container, isSubview) {
|
||||
let feeds = gBrowser.selectedBrowser.feeds;
|
||||
if (!isSubview && feeds == null) {
|
||||
// XXX hack -- menu opening depends on setting of an "open"
|
||||
// attribute, and the menu refuses to open if that attribute is
|
||||
// set (because it thinks it's already open). onpopupshowing gets
|
||||
// called after the attribute is unset, and it doesn't get unset
|
||||
// if we return false. so we unset it here; otherwise, the menu
|
||||
// refuses to work past this point.
|
||||
container.parentNode.removeAttribute("open");
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let i = container.childNodes.length - 1; i >= 0; --i) {
|
||||
let node = container.childNodes[i];
|
||||
if (isSubview && node.localName == "label")
|
||||
continue;
|
||||
container.removeChild(node);
|
||||
}
|
||||
|
||||
if (!feeds || feeds.length <= 1)
|
||||
return false;
|
||||
|
||||
// Build the menu showing the available feed choices for viewing.
|
||||
let itemNodeType = isSubview ? "toolbarbutton" : "menuitem";
|
||||
for (let feedInfo of feeds) {
|
||||
let item = document.createElement(itemNodeType);
|
||||
let baseTitle = feedInfo.title || feedInfo.href;
|
||||
item.setAttribute("label", baseTitle);
|
||||
item.setAttribute("feed", feedInfo.href);
|
||||
item.setAttribute("tooltiptext", feedInfo.href);
|
||||
item.setAttribute("crop", "center");
|
||||
let className = "feed-" + itemNodeType;
|
||||
if (isSubview) {
|
||||
className += " subviewbutton";
|
||||
}
|
||||
item.setAttribute("class", className);
|
||||
container.appendChild(item);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Subscribe to a given feed. Called when
|
||||
* 1. Page has a single feed and user clicks feed icon in location bar
|
||||
* 2. Page has a single feed and user selects Subscribe menu item
|
||||
* 3. Page has multiple feeds and user selects from feed icon popup (or subview)
|
||||
* 4. Page has multiple feeds and user selects from Subscribe submenu
|
||||
* @param href
|
||||
* The feed to subscribe to. May be null, in which case the
|
||||
* event target's feed attribute is examined.
|
||||
* @param event
|
||||
* The event this method is handling. Used to decide where
|
||||
* to open the preview UI. (Optional, unless href is null)
|
||||
*/
|
||||
subscribeToFeed(href, event) {
|
||||
// Just load the feed in the content area to either subscribe or show the
|
||||
// preview UI
|
||||
if (!href)
|
||||
href = event.target.getAttribute("feed");
|
||||
urlSecurityCheck(href, gBrowser.contentPrincipal,
|
||||
Ci.nsIScriptSecurityManager.DISALLOW_INHERIT_PRINCIPAL);
|
||||
let feedURI = makeURI(href, document.characterSet);
|
||||
// Use the feed scheme so X-Moz-Is-Feed will be set
|
||||
// The value doesn't matter
|
||||
if (/^https?$/.test(feedURI.scheme))
|
||||
href = "feed:" + href;
|
||||
this.loadFeed(href, event);
|
||||
},
|
||||
|
||||
loadFeed(href, event) {
|
||||
let feeds = gBrowser.selectedBrowser.feeds;
|
||||
try {
|
||||
openUILink(href, event, { ignoreAlt: true });
|
||||
}
|
||||
finally {
|
||||
// We might default to a livebookmarks modal dialog,
|
||||
// so reset that if the user happens to click it again
|
||||
gBrowser.selectedBrowser.feeds = feeds;
|
||||
}
|
||||
},
|
||||
|
||||
get _feedMenuitem() {
|
||||
delete this._feedMenuitem;
|
||||
return this._feedMenuitem = document.getElementById("singleFeedMenuitemState");
|
||||
},
|
||||
|
||||
get _feedMenupopup() {
|
||||
delete this._feedMenupopup;
|
||||
return this._feedMenupopup = document.getElementById("multipleFeedsMenuState");
|
||||
},
|
||||
|
||||
/**
|
||||
* Update the browser UI to show whether or not feeds are available when
|
||||
* a page is loaded or the user switches tabs to a page that has feeds.
|
||||
*/
|
||||
updateFeeds() {
|
||||
if (this._updateFeedTimeout)
|
||||
clearTimeout(this._updateFeedTimeout);
|
||||
|
||||
let feeds = gBrowser.selectedBrowser.feeds;
|
||||
let haveFeeds = feeds && feeds.length > 0;
|
||||
|
||||
let feedButton = document.getElementById("feed-button");
|
||||
if (feedButton) {
|
||||
if (haveFeeds) {
|
||||
feedButton.removeAttribute("disabled");
|
||||
} else {
|
||||
feedButton.setAttribute("disabled", "true");
|
||||
}
|
||||
}
|
||||
|
||||
if (!haveFeeds) {
|
||||
this._feedMenuitem.setAttribute("disabled", "true");
|
||||
this._feedMenuitem.removeAttribute("hidden");
|
||||
this._feedMenupopup.setAttribute("hidden", "true");
|
||||
return;
|
||||
}
|
||||
|
||||
if (feeds.length > 1) {
|
||||
this._feedMenuitem.setAttribute("hidden", "true");
|
||||
this._feedMenupopup.removeAttribute("hidden");
|
||||
} else {
|
||||
this._feedMenuitem.setAttribute("feed", feeds[0].href);
|
||||
this._feedMenuitem.removeAttribute("disabled");
|
||||
this._feedMenuitem.removeAttribute("hidden");
|
||||
this._feedMenupopup.setAttribute("hidden", "true");
|
||||
}
|
||||
},
|
||||
|
||||
addFeed(link, browserForLink) {
|
||||
if (!browserForLink.feeds)
|
||||
browserForLink.feeds = [];
|
||||
|
||||
browserForLink.feeds.push({ href: link.href, title: link.title });
|
||||
|
||||
// If this addition was for the current browser, update the UI. For
|
||||
// background browsers, we'll update on tab switch.
|
||||
if (browserForLink == gBrowser.selectedBrowser) {
|
||||
// Batch updates to avoid updating the UI for multiple onLinkAdded events
|
||||
// fired within 100ms of each other.
|
||||
if (this._updateFeedTimeout)
|
||||
clearTimeout(this._updateFeedTimeout);
|
||||
this._updateFeedTimeout = setTimeout(this.updateFeeds.bind(this), 100);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the human-readable display name of a file. This could be the
|
||||
* application name.
|
||||
* @param file
|
||||
* A nsIFile to look up the name of
|
||||
* @return The display name of the application represented by the file.
|
||||
*/
|
||||
_getFileDisplayName(file) {
|
||||
switch (AppConstants.platform) {
|
||||
case "win":
|
||||
if (file instanceof Ci.nsILocalFileWin) {
|
||||
try {
|
||||
return file.getVersionInfoField("FileDescription");
|
||||
} catch (e) {}
|
||||
}
|
||||
break;
|
||||
case "macosx":
|
||||
if (file instanceof Ci.nsILocalFileMac) {
|
||||
try {
|
||||
return file.bundleDisplayName;
|
||||
} catch (e) {}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return file.leafName;
|
||||
},
|
||||
|
||||
_chooseClientApp(aTitle, aTypeName, aBrowser) {
|
||||
const prefName = getPrefAppForType(aTypeName);
|
||||
let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
|
||||
|
||||
fp.init(window, aTitle, Ci.nsIFilePicker.modeOpen);
|
||||
fp.appendFilters(Ci.nsIFilePicker.filterApps);
|
||||
|
||||
fp.open((aResult) => {
|
||||
if (aResult == Ci.nsIFilePicker.returnOK) {
|
||||
let selectedApp = fp.file;
|
||||
if (selectedApp) {
|
||||
// XXXben - we need to compare this with the running instance
|
||||
// executable just don't know how to do that via script
|
||||
// XXXmano TBD: can probably add this to nsIShellService
|
||||
let appName = "";
|
||||
switch (AppConstants.platform) {
|
||||
case "win":
|
||||
appName = AppConstants.MOZ_APP_NAME + ".exe";
|
||||
break;
|
||||
case "macosx":
|
||||
appName = AppConstants.MOZ_MACBUNDLE_NAME;
|
||||
break;
|
||||
default:
|
||||
appName = AppConstants.MOZ_APP_NAME + "-bin";
|
||||
break;
|
||||
}
|
||||
|
||||
if (fp.file.leafName != appName) {
|
||||
Services.prefs.setComplexValue(prefName, Ci.nsILocalFile, selectedApp);
|
||||
aBrowser.messageManager.sendAsyncMessage("FeedWriter:SetApplicationLauncherMenuItem",
|
||||
{ name: this._getFileDisplayName(selectedApp),
|
||||
type: "SelectedAppMenuItem" });
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
executeClientApp(aSpec, aTitle, aSubtitle, aFeedHandler) {
|
||||
// aFeedHandler is either "default", indicating the system default reader, or a pref-name containing
|
||||
// an nsILocalFile pointing to the feed handler's executable.
|
||||
|
||||
let clientApp = null;
|
||||
if (aFeedHandler == "default") {
|
||||
clientApp = Cc["@mozilla.org/browser/shell-service;1"]
|
||||
.getService(Ci.nsIShellService)
|
||||
.defaultFeedReader;
|
||||
} else {
|
||||
clientApp = Services.prefs.getComplexValue(aFeedHandler, 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
|
||||
let feedURI = NetUtil.newURI(aSpec);
|
||||
if (feedURI.schemeIs("http")) {
|
||||
feedURI.scheme = "feed";
|
||||
aSpec = feedURI.spec;
|
||||
} else {
|
||||
aSpec = "feed:" + aSpec;
|
||||
}
|
||||
|
||||
// Retrieving the shell service might fail on some systems, most
|
||||
// notably systems where GNOME is not installed.
|
||||
try {
|
||||
let ss = Cc["@mozilla.org/browser/shell-service;1"]
|
||||
.getService(Ci.nsIShellService);
|
||||
ss.openApplicationWithURI(clientApp, aSpec);
|
||||
} catch (e) {
|
||||
// If we couldn't use the shell service, fallback to using a
|
||||
// nsIProcess instance
|
||||
let p = Cc["@mozilla.org/process/util;1"]
|
||||
.createInstance(Ci.nsIProcess);
|
||||
p.init(clientApp);
|
||||
p.run(false, [aSpec], 1);
|
||||
}
|
||||
},
|
||||
|
||||
// nsISupports
|
||||
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
|
||||
Ci.nsISupportsWeakReference]),
|
||||
|
||||
init() {
|
||||
window.messageManager.addMessageListener("FeedWriter:ChooseClientApp", this);
|
||||
window.messageManager.addMessageListener("FeedWriter:GetSubscriptionUI", this);
|
||||
window.messageManager.addMessageListener("FeedWriter:SetFeedPrefsAndSubscribe", this);
|
||||
window.messageManager.addMessageListener("FeedWriter:ShownFirstRun", this);
|
||||
|
||||
Services.ppmm.addMessageListener("FeedConverter:ExecuteClientApp", this);
|
||||
|
||||
const prefs = Services.prefs;
|
||||
prefs.addObserver(PREF_SELECTED_ACTION, this, true);
|
||||
prefs.addObserver(PREF_SELECTED_READER, this, true);
|
||||
prefs.addObserver(PREF_SELECTED_WEB, this, true);
|
||||
prefs.addObserver(PREF_VIDEO_SELECTED_ACTION, this, true);
|
||||
prefs.addObserver(PREF_VIDEO_SELECTED_READER, this, true);
|
||||
prefs.addObserver(PREF_VIDEO_SELECTED_WEB, this, true);
|
||||
prefs.addObserver(PREF_AUDIO_SELECTED_ACTION, this, true);
|
||||
prefs.addObserver(PREF_AUDIO_SELECTED_READER, this, true);
|
||||
prefs.addObserver(PREF_AUDIO_SELECTED_WEB, this, true);
|
||||
},
|
||||
|
||||
uninit() {
|
||||
Services.ppmm.removeMessageListener("FeedConverter:ExecuteClientApp", this);
|
||||
|
||||
this._prefChangeCallback = null;
|
||||
},
|
||||
|
||||
// nsIObserver
|
||||
observe(subject, topic, data) {
|
||||
if (topic == "nsPref:changed") {
|
||||
LOG(`Pref changed ${data}`)
|
||||
if (this._prefChangeCallback) {
|
||||
this._prefChangeCallback.disarm();
|
||||
}
|
||||
// Multiple prefs are set at the same time, debounce to reduce noise
|
||||
// This can happen in one feed and we want to message all feed pages
|
||||
this._prefChangeCallback = new DeferredTask(() => {
|
||||
this._prefChanged(data);
|
||||
}, PREF_UPDATE_DELAY);
|
||||
this._prefChangeCallback.arm();
|
||||
}
|
||||
},
|
||||
|
||||
_prefChanged(prefName) {
|
||||
// Don't observe for PREF_*SELECTED_APP as user likely just picked one
|
||||
// That is also handled by SetApplicationLauncherMenuItem call
|
||||
// Rather than the others which happen on subscription
|
||||
switch (prefName) {
|
||||
case PREF_SELECTED_READER:
|
||||
case PREF_SELECTED_WEB:
|
||||
case PREF_VIDEO_SELECTED_READER:
|
||||
case PREF_VIDEO_SELECTED_WEB:
|
||||
case PREF_AUDIO_SELECTED_READER:
|
||||
case PREF_AUDIO_SELECTED_WEB:
|
||||
case PREF_SELECTED_ACTION:
|
||||
case PREF_VIDEO_SELECTED_ACTION:
|
||||
case PREF_AUDIO_SELECTED_ACTION:
|
||||
const response = {
|
||||
default: this._getReaderForType(Ci.nsIFeed.TYPE_FEED),
|
||||
[Ci.nsIFeed.TYPE_AUDIO]: this._getReaderForType(Ci.nsIFeed.TYPE_AUDIO),
|
||||
[Ci.nsIFeed.TYPE_VIDEO]: this._getReaderForType(Ci.nsIFeed.TYPE_VIDEO)
|
||||
};
|
||||
Services.mm.broadcastAsyncMessage("FeedWriter:PreferenceUpdated",
|
||||
response);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
_initSubscriptionUIResponse(feedType) {
|
||||
const wccr = Cc["@mozilla.org/embeddor.implemented/web-content-handler-registrar;1"].
|
||||
getService(Ci.nsIWebContentConverterService);
|
||||
const handlersRaw = wccr.getContentHandlers(getMimeTypeForFeedType(feedType));
|
||||
const handlers = [];
|
||||
for (let handler of handlersRaw) {
|
||||
LOG(`Handler found: ${handler}`);
|
||||
handlers.push({
|
||||
name: handler.name,
|
||||
uri: handler.uri
|
||||
});
|
||||
}
|
||||
let showFirstRunUI = true;
|
||||
// eslint-disable-next-line mozilla/use-default-preference-values
|
||||
try {
|
||||
showFirstRunUI = Services.prefs.getBoolPref(PREF_SHOW_FIRST_RUN_UI);
|
||||
} catch (ex) { }
|
||||
const response = { handlers, showFirstRunUI };
|
||||
let selectedClientApp;
|
||||
const feedTypePref = getPrefAppForType(feedType);
|
||||
try {
|
||||
selectedClientApp = Services.prefs.getComplexValue(feedTypePref, Ci.nsILocalFile);
|
||||
} catch (ex) {
|
||||
// Just do nothing, then we won't bother populating
|
||||
}
|
||||
|
||||
let defaultClientApp = null;
|
||||
try {
|
||||
// This can sometimes not exist
|
||||
defaultClientApp = Cc["@mozilla.org/browser/shell-service;1"]
|
||||
.getService(Ci.nsIShellService)
|
||||
.defaultFeedReader;
|
||||
} catch (ex) {
|
||||
// Just do nothing, then we don't bother populating
|
||||
}
|
||||
|
||||
if (selectedClientApp && selectedClientApp.exists()) {
|
||||
if (defaultClientApp && selectedClientApp.path != defaultClientApp.path) {
|
||||
// Only set the default menu item if it differs from the selected one
|
||||
response.defaultMenuItem = this._getFileDisplayName(defaultClientApp);
|
||||
}
|
||||
response.selectedMenuItem = this._getFileDisplayName(selectedClientApp);
|
||||
}
|
||||
response.reader = this._getReaderForType(feedType);
|
||||
return response;
|
||||
},
|
||||
|
||||
_setPref(aPrefName, aPrefValue, aIsComplex = false) {
|
||||
LOG(`FeedWriter._setPref ${aPrefName}`);
|
||||
// Ensure we have a pref that is settable
|
||||
if (aPrefName && SETTABLE_PREFS.has(aPrefName)) {
|
||||
if (aIsComplex) {
|
||||
const supportsString = Cc["@mozilla.org/supports-string;1"].
|
||||
createInstance(Ci.nsISupportsString);
|
||||
supportsString.data = aPrefValue;
|
||||
Services.prefs.setComplexValue(aPrefName, Ci.nsISupportsString, supportsString);
|
||||
} else {
|
||||
Services.prefs.setCharPref(aPrefName, aPrefValue);
|
||||
}
|
||||
} else {
|
||||
LOG(`FeedWriter._setPref ${aPrefName} not allowed`);
|
||||
}
|
||||
},
|
||||
|
||||
_getReaderForType(feedType) {
|
||||
let prefs = Services.prefs;
|
||||
let handler = "bookmarks";
|
||||
let url;
|
||||
// eslint-disable-next-line mozilla/use-default-preference-values
|
||||
try {
|
||||
handler = prefs.getCharPref(getPrefReaderForType(feedType));
|
||||
} catch (ex) { }
|
||||
|
||||
if (handler === "web") {
|
||||
try {
|
||||
url = prefs.getComplexValue(getPrefWebForType(feedType), Ci.nsISupportsString).data;
|
||||
} catch (ex) {
|
||||
LOG("FeedWriter._setSelectedHandler: invalid or no handler in prefs");
|
||||
url = null;
|
||||
}
|
||||
}
|
||||
const alwaysUse = this._getAlwaysUseState(feedType);
|
||||
const action = prefs.getCharPref(getPrefActionForType(feedType));
|
||||
return { handler, url, alwaysUse, action };
|
||||
},
|
||||
|
||||
_getAlwaysUseState(feedType) {
|
||||
try {
|
||||
return Services.prefs.getCharPref(getPrefActionForType(feedType)) != "ask";
|
||||
} catch (ex) { }
|
||||
return false;
|
||||
},
|
||||
|
||||
receiveMessage(msg) {
|
||||
let handler;
|
||||
switch (msg.name) {
|
||||
case "FeedWriter:GetSubscriptionUI":
|
||||
const response = this._initSubscriptionUIResponse(msg.data.feedType);
|
||||
msg.target.messageManager
|
||||
.sendAsyncMessage("FeedWriter:GetSubscriptionUIResponse",
|
||||
response);
|
||||
break;
|
||||
case "FeedWriter:ChooseClientApp":
|
||||
this._chooseClientApp(msg.data.title, msg.data.feedType, msg.target);
|
||||
break;
|
||||
case "FeedWriter:ShownFirstRun":
|
||||
Services.prefs.setBoolPref(PREF_SHOW_FIRST_RUN_UI, false);
|
||||
break;
|
||||
case "FeedWriter:SetFeedPrefsAndSubscribe":
|
||||
const settings = msg.data;
|
||||
if (!settings.action || !VALID_ACTIONS.has(settings.action)) {
|
||||
LOG(`Invalid action ${settings.action}`);
|
||||
return;
|
||||
}
|
||||
if (!settings.reader || !VALID_READERS.has(settings.reader)) {
|
||||
LOG(`Invalid reader ${settings.reader}`);
|
||||
return;
|
||||
}
|
||||
const actionPref = getPrefActionForType(settings.feedType);
|
||||
this._setPref(actionPref, settings.action);
|
||||
const readerPref = getPrefReaderForType(settings.feedType);
|
||||
this._setPref(readerPref, settings.reader);
|
||||
handler = null;
|
||||
|
||||
switch (settings.reader) {
|
||||
case "web":
|
||||
// This is a web set URI by content using window.registerContentHandler()
|
||||
// Lets make sure we know about it before setting it
|
||||
const webPref = getPrefWebForType(settings.feedType);
|
||||
let wccr = Cc["@mozilla.org/embeddor.implemented/web-content-handler-registrar;1"].
|
||||
getService(Ci.nsIWebContentConverterService);
|
||||
// If the user provided an invalid web URL this function won't give us a reference
|
||||
handler = wccr.getWebContentHandlerByURI(getMimeTypeForFeedType(settings.feedType), settings.uri);
|
||||
if (handler) {
|
||||
this._setPref(webPref, settings.uri, true);
|
||||
if (settings.useAsDefault) {
|
||||
wccr.setAutoHandler(getMimeTypeForFeedType(settings.feedType), handler);
|
||||
}
|
||||
msg.target.messageManager
|
||||
.sendAsyncMessage("FeedWriter:SetFeedPrefsAndSubscribeResponse",
|
||||
{ redirect: handler.getHandlerURI(settings.feedLocation) });
|
||||
} else {
|
||||
LOG(`No handler found for web ${settings.feedType} ${settings.uri}`);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
const feedService = Cc["@mozilla.org/browser/feeds/result-service;1"].
|
||||
getService(Ci.nsIFeedResultService);
|
||||
|
||||
feedService.addToClientReader(settings.feedLocation,
|
||||
settings.feedTitle,
|
||||
settings.feedSubtitle,
|
||||
settings.feedType,
|
||||
settings.reader);
|
||||
}
|
||||
break;
|
||||
case "FeedConverter:ExecuteClientApp":
|
||||
// Always check feedHandler is from a set array of executable prefs
|
||||
if (EXECUTABLE_PREFS.has(msg.data.feedHandler)) {
|
||||
this.executeClientApp(msg.data.spec, msg.data.title,
|
||||
msg.data.subtitle, msg.data.feedHandler);
|
||||
} else {
|
||||
LOG(`FeedConverter:ExecuteClientApp - Will not exec ${msg.data.feedHandler}`);
|
||||
}
|
||||
break;
|
||||
}
|
||||
},
|
||||
};
|
||||
662
browser/base/content/browser-fullScreenAndPointerLock.js
Normal file
|
|
@ -0,0 +1,662 @@
|
|||
/* -*- 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 PointerlockFsWarning = {
|
||||
|
||||
_element: null,
|
||||
_origin: null,
|
||||
|
||||
init: function() {
|
||||
this.Timeout.prototype = {
|
||||
start: function() {
|
||||
this.cancel();
|
||||
this._id = setTimeout(() => this._handle(), this._delay);
|
||||
},
|
||||
cancel: function() {
|
||||
if (this._id) {
|
||||
clearTimeout(this._id);
|
||||
this._id = 0;
|
||||
}
|
||||
},
|
||||
_handle: function() {
|
||||
this._id = 0;
|
||||
this._func();
|
||||
},
|
||||
get delay() {
|
||||
return this._delay;
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Timeout object for managing timeout request. If it is started when
|
||||
* the previous call hasn't finished, it would automatically cancelled
|
||||
* the previous one.
|
||||
*/
|
||||
Timeout: function(func, delay) {
|
||||
this._id = 0;
|
||||
this._func = func;
|
||||
this._delay = delay;
|
||||
},
|
||||
|
||||
showPointerLock: function(aOrigin) {
|
||||
if (!document.fullscreen) {
|
||||
let timeout = gPrefService.getIntPref("pointer-lock-api.warning.timeout");
|
||||
this.show(aOrigin, "pointerlock-warning", timeout, 0);
|
||||
}
|
||||
},
|
||||
|
||||
showFullScreen: function(aOrigin) {
|
||||
let timeout = gPrefService.getIntPref("full-screen-api.warning.timeout");
|
||||
let delay = gPrefService.getIntPref("full-screen-api.warning.delay");
|
||||
this.show(aOrigin, "fullscreen-warning", timeout, delay);
|
||||
},
|
||||
|
||||
// Shows a warning that the site has entered fullscreen or
|
||||
// pointer lock for a short duration.
|
||||
show: function(aOrigin, elementId, timeout, delay) {
|
||||
|
||||
if (!this._element) {
|
||||
this._element = document.getElementById(elementId);
|
||||
// Setup event listeners
|
||||
this._element.addEventListener("transitionend", this);
|
||||
window.addEventListener("mousemove", this, true);
|
||||
// The timeout to hide the warning box after a while.
|
||||
this._timeoutHide = new this.Timeout(() => {
|
||||
this._state = "hidden";
|
||||
}, timeout);
|
||||
// The timeout to show the warning box when the pointer is at the top
|
||||
this._timeoutShow = new this.Timeout(() => {
|
||||
this._state = "ontop";
|
||||
this._timeoutHide.start();
|
||||
}, delay);
|
||||
}
|
||||
|
||||
// Set the strings on the warning UI.
|
||||
if (aOrigin) {
|
||||
this._origin = aOrigin;
|
||||
}
|
||||
let uri = BrowserUtils.makeURI(this._origin);
|
||||
let host = null;
|
||||
try {
|
||||
host = uri.host;
|
||||
} catch (e) { }
|
||||
let textElem = this._element.querySelector(".pointerlockfswarning-domain-text");
|
||||
if (!host) {
|
||||
textElem.setAttribute("hidden", true);
|
||||
} else {
|
||||
textElem.removeAttribute("hidden");
|
||||
let hostElem = this._element.querySelector(".pointerlockfswarning-domain");
|
||||
// Document's principal's URI has a host. Display a warning including it.
|
||||
let utils = {};
|
||||
Cu.import("resource://gre/modules/DownloadUtils.jsm", utils);
|
||||
hostElem.textContent = utils.DownloadUtils.getURIHost(uri.spec)[0];
|
||||
}
|
||||
|
||||
this._element.dataset.identity =
|
||||
gIdentityHandler.pointerlockFsWarningClassName;
|
||||
|
||||
// User should be allowed to explicitly disable
|
||||
// the prompt if they really want.
|
||||
if (this._timeoutHide.delay <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Explicitly set the last state to hidden to avoid the warning
|
||||
// box being hidden immediately because of mousemove.
|
||||
this._state = "onscreen";
|
||||
this._lastState = "hidden";
|
||||
this._timeoutHide.start();
|
||||
},
|
||||
|
||||
close: function() {
|
||||
if (!this._element) {
|
||||
return;
|
||||
}
|
||||
// Cancel any pending timeout
|
||||
this._timeoutHide.cancel();
|
||||
this._timeoutShow.cancel();
|
||||
// Reset state of the warning box
|
||||
this._state = "hidden";
|
||||
this._element.setAttribute("hidden", true);
|
||||
// Remove all event listeners
|
||||
this._element.removeEventListener("transitionend", this);
|
||||
window.removeEventListener("mousemove", this, true);
|
||||
// Clear fields
|
||||
this._element = null;
|
||||
this._timeoutHide = null;
|
||||
this._timeoutShow = null;
|
||||
|
||||
// Ensure focus switches away from the (now hidden) warning box.
|
||||
// If the user clicked buttons in the warning box, it would have
|
||||
// been focused, and any key events would be directed at the (now
|
||||
// hidden) chrome document instead of the target document.
|
||||
gBrowser.selectedBrowser.focus();
|
||||
},
|
||||
|
||||
// State could be one of "onscreen", "ontop", "hiding", and
|
||||
// "hidden". Setting the state to "onscreen" and "ontop" takes
|
||||
// effect immediately, while setting it to "hidden" actually
|
||||
// turns the state to "hiding" before the transition finishes.
|
||||
_lastState: null,
|
||||
_STATES: ["hidden", "ontop", "onscreen"],
|
||||
get _state() {
|
||||
for (let state of this._STATES) {
|
||||
if (this._element.hasAttribute(state)) {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
return "hiding";
|
||||
},
|
||||
set _state(newState) {
|
||||
let currentState = this._state;
|
||||
if (currentState == newState) {
|
||||
return;
|
||||
}
|
||||
if (currentState != "hiding") {
|
||||
this._lastState = currentState;
|
||||
this._element.removeAttribute(currentState);
|
||||
}
|
||||
if (newState != "hidden") {
|
||||
if (currentState != "hidden") {
|
||||
this._element.setAttribute(newState, true);
|
||||
} else {
|
||||
// When the previous state is hidden, the display was none,
|
||||
// thus no box was constructed. We need to wait for the new
|
||||
// display value taking effect first, otherwise, there won't
|
||||
// be any transition. Since requestAnimationFrame callback is
|
||||
// generally triggered before any style flush and layout, we
|
||||
// should wait for the second animation frame.
|
||||
requestAnimationFrame(() => {
|
||||
requestAnimationFrame(() => {
|
||||
if (this._element) {
|
||||
this._element.setAttribute(newState, true);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
handleEvent: function(event) {
|
||||
switch (event.type) {
|
||||
case "mousemove": {
|
||||
let state = this._state;
|
||||
if (state == "hidden") {
|
||||
// If the warning box is currently hidden, show it after
|
||||
// a short delay if the pointer is at the top.
|
||||
if (event.clientY != 0) {
|
||||
this._timeoutShow.cancel();
|
||||
} else if (this._timeoutShow.delay >= 0) {
|
||||
this._timeoutShow.start();
|
||||
}
|
||||
} else {
|
||||
let elemRect = this._element.getBoundingClientRect();
|
||||
if (state == "hiding" && this._lastState != "hidden") {
|
||||
// If we are on the hiding transition, and the pointer
|
||||
// moved near the box, restore to the previous state.
|
||||
if (event.clientY <= elemRect.bottom + 50) {
|
||||
this._state = this._lastState;
|
||||
this._timeoutHide.start();
|
||||
}
|
||||
} else if (state == "ontop" || this._lastState != "hidden") {
|
||||
// State being "ontop" or the previous state not being
|
||||
// "hidden" indicates this current warning box is shown
|
||||
// in response to user's action. Hide it immediately when
|
||||
// the pointer leaves that area.
|
||||
if (event.clientY > elemRect.bottom + 50) {
|
||||
this._state = "hidden";
|
||||
this._timeoutHide.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "transitionend": {
|
||||
if (this._state == "hiding") {
|
||||
this._element.setAttribute("hidden", true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var PointerLock = {
|
||||
|
||||
init: function() {
|
||||
window.messageManager.addMessageListener("PointerLock:Entered", this);
|
||||
window.messageManager.addMessageListener("PointerLock:Exited", this);
|
||||
},
|
||||
|
||||
receiveMessage: function(aMessage) {
|
||||
switch (aMessage.name) {
|
||||
case "PointerLock:Entered": {
|
||||
PointerlockFsWarning.showPointerLock(aMessage.data.originNoSuffix);
|
||||
break;
|
||||
}
|
||||
case "PointerLock:Exited": {
|
||||
PointerlockFsWarning.close();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var FullScreen = {
|
||||
_MESSAGES: [
|
||||
"DOMFullscreen:Request",
|
||||
"DOMFullscreen:NewOrigin",
|
||||
"DOMFullscreen:Exit",
|
||||
"DOMFullscreen:Painted",
|
||||
],
|
||||
|
||||
init: function() {
|
||||
// called when we go into full screen, even if initiated by a web page script
|
||||
window.addEventListener("fullscreen", this, true);
|
||||
window.addEventListener("MozDOMFullscreen:Entered", this,
|
||||
/* useCapture */ true,
|
||||
/* wantsUntrusted */ false);
|
||||
window.addEventListener("MozDOMFullscreen:Exited", this,
|
||||
/* useCapture */ true,
|
||||
/* wantsUntrusted */ false);
|
||||
for (let type of this._MESSAGES) {
|
||||
window.messageManager.addMessageListener(type, this);
|
||||
}
|
||||
|
||||
if (window.fullScreen)
|
||||
this.toggle();
|
||||
},
|
||||
|
||||
uninit: function() {
|
||||
for (let type of this._MESSAGES) {
|
||||
window.messageManager.removeMessageListener(type, this);
|
||||
}
|
||||
this.cleanup();
|
||||
},
|
||||
|
||||
toggle: function () {
|
||||
var enterFS = window.fullScreen;
|
||||
|
||||
// Toggle the View:FullScreen command, which controls elements like the
|
||||
// fullscreen menuitem, and menubars.
|
||||
let fullscreenCommand = document.getElementById("View:FullScreen");
|
||||
if (enterFS) {
|
||||
fullscreenCommand.setAttribute("checked", enterFS);
|
||||
} else {
|
||||
fullscreenCommand.removeAttribute("checked");
|
||||
}
|
||||
|
||||
if (AppConstants.platform == "macosx") {
|
||||
// Make sure the menu items are adjusted.
|
||||
document.getElementById("enterFullScreenItem").hidden = enterFS;
|
||||
document.getElementById("exitFullScreenItem").hidden = !enterFS;
|
||||
}
|
||||
|
||||
if (!this._fullScrToggler) {
|
||||
this._fullScrToggler = document.getElementById("fullscr-toggler");
|
||||
this._fullScrToggler.addEventListener("mouseover", this._expandCallback, false);
|
||||
this._fullScrToggler.addEventListener("dragenter", this._expandCallback, false);
|
||||
this._fullScrToggler.addEventListener("touchmove", this._expandCallback, {passive: true});
|
||||
}
|
||||
|
||||
if (enterFS) {
|
||||
gNavToolbox.setAttribute("inFullscreen", true);
|
||||
document.documentElement.setAttribute("inFullscreen", true);
|
||||
if (!document.fullscreenElement && this.useLionFullScreen)
|
||||
document.documentElement.setAttribute("OSXLionFullscreen", true);
|
||||
} else {
|
||||
gNavToolbox.removeAttribute("inFullscreen");
|
||||
document.documentElement.removeAttribute("inFullscreen");
|
||||
document.documentElement.removeAttribute("OSXLionFullscreen");
|
||||
}
|
||||
|
||||
if (!document.fullscreenElement)
|
||||
this._updateToolbars(enterFS);
|
||||
|
||||
if (enterFS) {
|
||||
document.addEventListener("keypress", this._keyToggleCallback, false);
|
||||
document.addEventListener("popupshown", this._setPopupOpen, false);
|
||||
document.addEventListener("popuphidden", this._setPopupOpen, false);
|
||||
// If it is not safe to collapse, add the mouse position tracker or
|
||||
// else it won't be possible to hide the navigation toolbox again
|
||||
if (gPrefService.getBoolPref("browser.fullscreen.autohide")) {
|
||||
gBrowser.mPanelContainer.addEventListener("mousemove",
|
||||
this._collapseCallback, false);
|
||||
}
|
||||
// In DOM fullscreen mode, we hide toolbars with CSS
|
||||
if (!document.fullscreenElement)
|
||||
this.hideNavToolbox(true);
|
||||
}
|
||||
else {
|
||||
this.showNavToolbox(false);
|
||||
// This is needed if they use the context menu to quit fullscreen
|
||||
this._isPopupOpen = false;
|
||||
this.cleanup();
|
||||
// In TabsInTitlebar._update(), we cancel the appearance update on
|
||||
// resize event for exiting fullscreen, since that happens before we
|
||||
// change the UI here in the "fullscreen" event. Hence we need to
|
||||
// call it here to ensure the appearance is properly updated. See
|
||||
// TabsInTitlebar._update() and bug 1173768.
|
||||
TabsInTitlebar.updateAppearance(true);
|
||||
}
|
||||
},
|
||||
|
||||
exitDomFullScreen : function() {
|
||||
document.exitFullscreen();
|
||||
},
|
||||
|
||||
handleEvent: function (event) {
|
||||
switch (event.type) {
|
||||
case "fullscreen":
|
||||
this.toggle();
|
||||
break;
|
||||
case "MozDOMFullscreen:Entered": {
|
||||
// The event target is the element which requested the DOM
|
||||
// fullscreen. If we were entering DOM fullscreen for a remote
|
||||
// browser, the target would be `gBrowser` and the original
|
||||
// target would be the browser which was the parameter of
|
||||
// `remoteFrameFullscreenChanged` call. If the fullscreen
|
||||
// request was initiated from an in-process browser, we need
|
||||
// to get its corresponding browser here.
|
||||
let browser;
|
||||
if (event.target == gBrowser) {
|
||||
browser = event.originalTarget;
|
||||
} else {
|
||||
let topWin = event.target.ownerGlobal.top;
|
||||
browser = gBrowser.getBrowserForContentWindow(topWin);
|
||||
}
|
||||
this.enterDomFullscreen(browser);
|
||||
break;
|
||||
}
|
||||
case "MozDOMFullscreen:Exited":
|
||||
this.cleanupDomFullscreen();
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
receiveMessage: function(aMessage) {
|
||||
let browser = aMessage.target;
|
||||
switch (aMessage.name) {
|
||||
case "DOMFullscreen:Request": {
|
||||
this._windowUtils.remoteFrameFullscreenChanged(browser);
|
||||
break;
|
||||
}
|
||||
case "DOMFullscreen:NewOrigin": {
|
||||
// Don't show the warning if we've already exited fullscreen.
|
||||
if (document.fullscreen) {
|
||||
PointerlockFsWarning.showFullScreen(aMessage.data.originNoSuffix);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "DOMFullscreen:Exit": {
|
||||
this._windowUtils.remoteFrameFullscreenReverted();
|
||||
break;
|
||||
}
|
||||
case "DOMFullscreen:Painted": {
|
||||
Services.obs.notifyObservers(window, "fullscreen-painted", "");
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
enterDomFullscreen : function(aBrowser) {
|
||||
|
||||
if (!document.fullscreenElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If we have a current pointerlock warning shown then hide it
|
||||
// before transition.
|
||||
PointerlockFsWarning.close();
|
||||
|
||||
// If it is a remote browser, send a message to ask the content
|
||||
// to enter fullscreen state. We don't need to do so if it is an
|
||||
// in-process browser, since all related document should have
|
||||
// entered fullscreen state at this point.
|
||||
// This should be done before the active tab check below to ensure
|
||||
// that the content document handles the pending request. Doing so
|
||||
// before the check is fine since we also check the activeness of
|
||||
// the requesting document in content-side handling code.
|
||||
if (this._isRemoteBrowser(aBrowser)) {
|
||||
aBrowser.messageManager.sendAsyncMessage("DOMFullscreen:Entered");
|
||||
}
|
||||
|
||||
// If we've received a fullscreen notification, we have to ensure that the
|
||||
// element that's requesting fullscreen belongs to the browser that's currently
|
||||
// active. If not, we exit fullscreen since the "full-screen document" isn't
|
||||
// actually visible now.
|
||||
if (!aBrowser || gBrowser.selectedBrowser != aBrowser ||
|
||||
// The top-level window has lost focus since the request to enter
|
||||
// full-screen was made. Cancel full-screen.
|
||||
Services.focus.activeWindow != window) {
|
||||
// This function is called synchronously in fullscreen change, so
|
||||
// we have to avoid calling exitFullscreen synchronously here.
|
||||
setTimeout(() => document.exitFullscreen(), 0);
|
||||
return;
|
||||
}
|
||||
|
||||
document.documentElement.setAttribute("inDOMFullscreen", true);
|
||||
|
||||
if (gFindBarInitialized) {
|
||||
gFindBar.close(true);
|
||||
}
|
||||
|
||||
// Exit DOM full-screen mode upon open, close, or change tab.
|
||||
gBrowser.tabContainer.addEventListener("TabOpen", this.exitDomFullScreen);
|
||||
gBrowser.tabContainer.addEventListener("TabClose", this.exitDomFullScreen);
|
||||
gBrowser.tabContainer.addEventListener("TabSelect", this.exitDomFullScreen);
|
||||
|
||||
// Add listener to detect when the fullscreen window is re-focused.
|
||||
// If a fullscreen window loses focus, we show a warning when the
|
||||
// fullscreen window is refocused.
|
||||
window.addEventListener("activate", this);
|
||||
},
|
||||
|
||||
cleanup: function () {
|
||||
if (!window.fullScreen) {
|
||||
gBrowser.mPanelContainer.removeEventListener("mousemove",
|
||||
this._collapseCallback, false);
|
||||
document.removeEventListener("keypress", this._keyToggleCallback, false);
|
||||
document.removeEventListener("popupshown", this._setPopupOpen, false);
|
||||
document.removeEventListener("popuphidden", this._setPopupOpen, false);
|
||||
}
|
||||
},
|
||||
|
||||
cleanupDomFullscreen: function () {
|
||||
window.messageManager
|
||||
.broadcastAsyncMessage("DOMFullscreen:CleanUp");
|
||||
|
||||
PointerlockFsWarning.close();
|
||||
gBrowser.tabContainer.removeEventListener("TabOpen", this.exitDomFullScreen);
|
||||
gBrowser.tabContainer.removeEventListener("TabClose", this.exitDomFullScreen);
|
||||
gBrowser.tabContainer.removeEventListener("TabSelect", this.exitDomFullScreen);
|
||||
window.removeEventListener("activate", this);
|
||||
|
||||
document.documentElement.removeAttribute("inDOMFullscreen");
|
||||
},
|
||||
|
||||
_isRemoteBrowser: function (aBrowser) {
|
||||
return gMultiProcessBrowser && aBrowser.getAttribute("remote") == "true";
|
||||
},
|
||||
|
||||
get _windowUtils() {
|
||||
return window.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindowUtils);
|
||||
},
|
||||
|
||||
// Event callbacks
|
||||
_expandCallback: function()
|
||||
{
|
||||
FullScreen.showNavToolbox();
|
||||
},
|
||||
_collapseCallback: function()
|
||||
{
|
||||
FullScreen.hideNavToolbox();
|
||||
},
|
||||
_keyToggleCallback: function(aEvent)
|
||||
{
|
||||
// if we can use the keyboard (eg Ctrl+L or Ctrl+E) to open the toolbars, we
|
||||
// should provide a way to collapse them too.
|
||||
if (aEvent.keyCode == aEvent.DOM_VK_ESCAPE) {
|
||||
FullScreen.hideNavToolbox();
|
||||
}
|
||||
// F6 is another shortcut to the address bar, but its not covered in OpenLocation()
|
||||
else if (aEvent.keyCode == aEvent.DOM_VK_F6)
|
||||
FullScreen.showNavToolbox();
|
||||
},
|
||||
|
||||
// Checks whether we are allowed to collapse the chrome
|
||||
_isPopupOpen: false,
|
||||
_isChromeCollapsed: false,
|
||||
_safeToCollapse: function () {
|
||||
if (!gPrefService.getBoolPref("browser.fullscreen.autohide"))
|
||||
return false;
|
||||
|
||||
// a popup menu is open in chrome: don't collapse chrome
|
||||
if (this._isPopupOpen)
|
||||
return false;
|
||||
|
||||
// On OS X Lion we don't want to hide toolbars.
|
||||
if (this.useLionFullScreen)
|
||||
return false;
|
||||
|
||||
// a textbox in chrome is focused (location bar anyone?): don't collapse chrome
|
||||
if (document.commandDispatcher.focusedElement &&
|
||||
document.commandDispatcher.focusedElement.ownerDocument == document &&
|
||||
document.commandDispatcher.focusedElement.localName == "input") {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
_setPopupOpen: function(aEvent)
|
||||
{
|
||||
// Popups should only veto chrome collapsing if they were opened when the chrome was not collapsed.
|
||||
// Otherwise, they would not affect chrome and the user would expect the chrome to go away.
|
||||
// e.g. we wouldn't want the autoscroll icon firing this event, so when the user
|
||||
// toggles chrome when moving mouse to the top, it doesn't go away again.
|
||||
if (aEvent.type == "popupshown" && !FullScreen._isChromeCollapsed &&
|
||||
aEvent.target.localName != "tooltip" && aEvent.target.localName != "window")
|
||||
FullScreen._isPopupOpen = true;
|
||||
else if (aEvent.type == "popuphidden" && aEvent.target.localName != "tooltip" &&
|
||||
aEvent.target.localName != "window") {
|
||||
FullScreen._isPopupOpen = false;
|
||||
// Try again to hide toolbar when we close the popup.
|
||||
FullScreen.hideNavToolbox(true);
|
||||
}
|
||||
},
|
||||
|
||||
// Autohide helpers for the context menu item
|
||||
getAutohide: function(aItem)
|
||||
{
|
||||
aItem.setAttribute("checked", gPrefService.getBoolPref("browser.fullscreen.autohide"));
|
||||
},
|
||||
setAutohide: function()
|
||||
{
|
||||
gPrefService.setBoolPref("browser.fullscreen.autohide", !gPrefService.getBoolPref("browser.fullscreen.autohide"));
|
||||
// Try again to hide toolbar when we change the pref.
|
||||
FullScreen.hideNavToolbox(true);
|
||||
},
|
||||
|
||||
showNavToolbox: function(trackMouse = true) {
|
||||
this._fullScrToggler.hidden = true;
|
||||
gNavToolbox.removeAttribute("fullscreenShouldAnimate");
|
||||
gNavToolbox.style.marginTop = "";
|
||||
|
||||
if (!this._isChromeCollapsed) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Track whether mouse is near the toolbox
|
||||
if (trackMouse && !this.useLionFullScreen) {
|
||||
gBrowser.mPanelContainer.addEventListener("mousemove",
|
||||
this._collapseCallback, false);
|
||||
}
|
||||
|
||||
this._isChromeCollapsed = false;
|
||||
},
|
||||
|
||||
hideNavToolbox: function (aAnimate = false) {
|
||||
if (this._isChromeCollapsed || !this._safeToCollapse())
|
||||
return;
|
||||
|
||||
this._fullScrToggler.hidden = false;
|
||||
|
||||
if (aAnimate && gPrefService.getBoolPref("browser.fullscreen.animate")) {
|
||||
gNavToolbox.setAttribute("fullscreenShouldAnimate", true);
|
||||
// Hide the fullscreen toggler until the transition ends.
|
||||
let listener = () => {
|
||||
gNavToolbox.removeEventListener("transitionend", listener, true);
|
||||
if (this._isChromeCollapsed)
|
||||
this._fullScrToggler.hidden = false;
|
||||
};
|
||||
gNavToolbox.addEventListener("transitionend", listener, true);
|
||||
this._fullScrToggler.hidden = true;
|
||||
}
|
||||
|
||||
gNavToolbox.style.marginTop =
|
||||
-gNavToolbox.getBoundingClientRect().height + "px";
|
||||
this._isChromeCollapsed = true;
|
||||
gBrowser.mPanelContainer.removeEventListener("mousemove",
|
||||
this._collapseCallback, false);
|
||||
},
|
||||
|
||||
_updateToolbars: function (aEnterFS) {
|
||||
for (let el of document.querySelectorAll("toolbar[fullscreentoolbar=true]")) {
|
||||
if (aEnterFS) {
|
||||
// Give the main nav bar and the tab bar the fullscreen context menu,
|
||||
// otherwise remove context menu to prevent breakage
|
||||
el.setAttribute("saved-context", el.getAttribute("context"));
|
||||
if (el.id == "nav-bar" || el.id == "TabsToolbar")
|
||||
el.setAttribute("context", "autohide-context");
|
||||
else
|
||||
el.removeAttribute("context");
|
||||
|
||||
// Set the inFullscreen attribute to allow specific styling
|
||||
// in fullscreen mode
|
||||
el.setAttribute("inFullscreen", true);
|
||||
} else {
|
||||
if (el.hasAttribute("saved-context")) {
|
||||
el.setAttribute("context", el.getAttribute("saved-context"));
|
||||
el.removeAttribute("saved-context");
|
||||
}
|
||||
el.removeAttribute("inFullscreen");
|
||||
}
|
||||
}
|
||||
|
||||
ToolbarIconColor.inferFromText();
|
||||
|
||||
// For Lion fullscreen, all fullscreen controls are hidden, don't
|
||||
// bother to touch them. If we don't stop here, the following code
|
||||
// could cause the native fullscreen button be shown unexpectedly.
|
||||
// See bug 1165570.
|
||||
if (this.useLionFullScreen) {
|
||||
return;
|
||||
}
|
||||
|
||||
var fullscreenctls = document.getElementById("window-controls");
|
||||
var navbar = document.getElementById("nav-bar");
|
||||
var ctlsOnTabbar = window.toolbar.visible;
|
||||
if (fullscreenctls.parentNode == navbar && ctlsOnTabbar) {
|
||||
fullscreenctls.removeAttribute("flex");
|
||||
document.getElementById("TabsToolbar").appendChild(fullscreenctls);
|
||||
}
|
||||
else if (fullscreenctls.parentNode.id == "TabsToolbar" && !ctlsOnTabbar) {
|
||||
fullscreenctls.setAttribute("flex", "1");
|
||||
navbar.appendChild(fullscreenctls);
|
||||
}
|
||||
fullscreenctls.hidden = !aEnterFS;
|
||||
}
|
||||
};
|
||||
XPCOMUtils.defineLazyGetter(FullScreen, "useLionFullScreen", function() {
|
||||
// We'll only use OS X Lion full screen if we're
|
||||
// * on OS X
|
||||
// * on Lion or higher (Darwin 11+)
|
||||
// * have fullscreenbutton="true"
|
||||
return AppConstants.isPlatformAndVersionAtLeast("macosx", 11) &&
|
||||
document.documentElement.getAttribute("fullscreenbutton") == "true";
|
||||
});
|
||||
526
browser/base/content/browser-fullZoom.js
Normal file
|
|
@ -0,0 +1,526 @@
|
|||
/* 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/. */
|
||||
|
||||
/**
|
||||
* Controls the "full zoom" setting and its site-specific preferences.
|
||||
*/
|
||||
var FullZoom = {
|
||||
// Identifies the setting in the content prefs database.
|
||||
name: "browser.content.full-zoom",
|
||||
|
||||
// browser.zoom.siteSpecific preference cache
|
||||
_siteSpecificPref: undefined,
|
||||
|
||||
// browser.zoom.updateBackgroundTabs preference cache
|
||||
updateBackgroundTabs: undefined,
|
||||
|
||||
// This maps the browser to monotonically increasing integer
|
||||
// tokens. _browserTokenMap[browser] is increased each time the zoom is
|
||||
// changed in the browser. See _getBrowserToken and _ignorePendingZoomAccesses.
|
||||
_browserTokenMap: new WeakMap(),
|
||||
|
||||
// Stores initial locations if we receive onLocationChange
|
||||
// events before we're initialized.
|
||||
_initialLocations: new WeakMap(),
|
||||
|
||||
get siteSpecific() {
|
||||
return this._siteSpecificPref;
|
||||
},
|
||||
|
||||
// nsISupports
|
||||
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIDOMEventListener,
|
||||
Ci.nsIObserver,
|
||||
Ci.nsIContentPrefObserver,
|
||||
Ci.nsISupportsWeakReference,
|
||||
Ci.nsISupports]),
|
||||
|
||||
// Initialization & Destruction
|
||||
|
||||
init: function() {
|
||||
gBrowser.addEventListener("ZoomChangeUsingMouseWheel", this);
|
||||
|
||||
// Register ourselves with the service so we know when our pref changes.
|
||||
this._cps2 = Cc["@mozilla.org/content-pref/service;1"].
|
||||
getService(Ci.nsIContentPrefService2);
|
||||
this._cps2.addObserverForName(this.name, this);
|
||||
|
||||
this._siteSpecificPref =
|
||||
gPrefService.getBoolPref("browser.zoom.siteSpecific");
|
||||
this.updateBackgroundTabs =
|
||||
gPrefService.getBoolPref("browser.zoom.updateBackgroundTabs");
|
||||
// Listen for changes to the browser.zoom branch so we can enable/disable
|
||||
// updating background tabs and per-site saving and restoring of zoom levels.
|
||||
gPrefService.addObserver("browser.zoom.", this, true);
|
||||
|
||||
// If we received onLocationChange events for any of the current browsers
|
||||
// before we were initialized we want to replay those upon initialization.
|
||||
for (let browser of gBrowser.browsers) {
|
||||
if (this._initialLocations.has(browser)) {
|
||||
this.onLocationChange(...this._initialLocations.get(browser), browser);
|
||||
}
|
||||
}
|
||||
|
||||
// This should be nulled after initialization.
|
||||
this._initialLocations = null;
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
gPrefService.removeObserver("browser.zoom.", this);
|
||||
this._cps2.removeObserverForName(this.name, this);
|
||||
gBrowser.removeEventListener("ZoomChangeUsingMouseWheel", this);
|
||||
},
|
||||
|
||||
|
||||
// Event Handlers
|
||||
|
||||
// nsIDOMEventListener
|
||||
|
||||
handleEvent: function(event) {
|
||||
switch (event.type) {
|
||||
case "ZoomChangeUsingMouseWheel":
|
||||
let browser = this._getTargetedBrowser(event);
|
||||
this._ignorePendingZoomAccesses(browser);
|
||||
this._applyZoomToPref(browser);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
// nsIObserver
|
||||
|
||||
observe: function (aSubject, aTopic, aData) {
|
||||
switch (aTopic) {
|
||||
case "nsPref:changed":
|
||||
switch (aData) {
|
||||
case "browser.zoom.siteSpecific":
|
||||
this._siteSpecificPref =
|
||||
gPrefService.getBoolPref("browser.zoom.siteSpecific");
|
||||
break;
|
||||
case "browser.zoom.updateBackgroundTabs":
|
||||
this.updateBackgroundTabs =
|
||||
gPrefService.getBoolPref("browser.zoom.updateBackgroundTabs");
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
// nsIContentPrefObserver
|
||||
|
||||
onContentPrefSet: function(aGroup, aName, aValue, aIsPrivate) {
|
||||
this._onContentPrefChanged(aGroup, aValue, aIsPrivate);
|
||||
},
|
||||
|
||||
onContentPrefRemoved: function(aGroup, aName, aIsPrivate) {
|
||||
this._onContentPrefChanged(aGroup, undefined, aIsPrivate);
|
||||
},
|
||||
|
||||
/**
|
||||
* Appropriately updates the zoom level after a content preference has
|
||||
* changed.
|
||||
*
|
||||
* @param aGroup The group of the changed preference.
|
||||
* @param aValue The new value of the changed preference. Pass undefined to
|
||||
* indicate the preference's removal.
|
||||
*/
|
||||
_onContentPrefChanged: function(aGroup, aValue, aIsPrivate) {
|
||||
if (this._isNextContentPrefChangeInternal) {
|
||||
// Ignore changes that FullZoom itself makes. This works because the
|
||||
// content pref service calls callbacks before notifying observers, and it
|
||||
// does both in the same turn of the event loop.
|
||||
delete this._isNextContentPrefChangeInternal;
|
||||
return;
|
||||
}
|
||||
|
||||
let browser = gBrowser.selectedBrowser;
|
||||
if (!browser.currentURI)
|
||||
return;
|
||||
|
||||
let ctxt = this._loadContextFromBrowser(browser);
|
||||
let domain = this._cps2.extractDomain(browser.currentURI.spec);
|
||||
if (aGroup) {
|
||||
if (aGroup == domain && ctxt.usePrivateBrowsing == aIsPrivate)
|
||||
this._applyPrefToZoom(aValue, browser);
|
||||
return;
|
||||
}
|
||||
|
||||
this._globalValue = aValue === undefined ? aValue :
|
||||
this._ensureValid(aValue);
|
||||
|
||||
// If the current page doesn't have a site-specific preference, then its
|
||||
// zoom should be set to the new global preference now that the global
|
||||
// preference has changed.
|
||||
let hasPref = false;
|
||||
let token = this._getBrowserToken(browser);
|
||||
this._cps2.getByDomainAndName(browser.currentURI.spec, this.name, ctxt, {
|
||||
handleResult: function () { hasPref = true; },
|
||||
handleCompletion: function () {
|
||||
if (!hasPref && token.isCurrent)
|
||||
this._applyPrefToZoom(undefined, browser);
|
||||
}.bind(this)
|
||||
});
|
||||
},
|
||||
|
||||
// location change observer
|
||||
|
||||
/**
|
||||
* Called when the location of a tab changes.
|
||||
* When that happens, we need to update the current zoom level if appropriate.
|
||||
*
|
||||
* @param aURI
|
||||
* A URI object representing the new location.
|
||||
* @param aIsTabSwitch
|
||||
* Whether this location change has happened because of a tab switch.
|
||||
* @param aBrowser
|
||||
* (optional) browser object displaying the document
|
||||
*/
|
||||
onLocationChange: function(aURI, aIsTabSwitch, aBrowser) {
|
||||
let browser = aBrowser || gBrowser.selectedBrowser;
|
||||
|
||||
// If we haven't been initialized yet but receive an onLocationChange
|
||||
// notification then let's store and replay it upon initialization.
|
||||
if (this._initialLocations) {
|
||||
this._initialLocations.set(browser, [aURI, aIsTabSwitch]);
|
||||
return;
|
||||
}
|
||||
|
||||
// Ignore all pending async zoom accesses in the browser. Pending accesses
|
||||
// that started before the location change will be prevented from applying
|
||||
// to the new location.
|
||||
this._ignorePendingZoomAccesses(browser);
|
||||
|
||||
if (!aURI || (aIsTabSwitch && !this.siteSpecific)) {
|
||||
this._notifyOnLocationChange(browser);
|
||||
return;
|
||||
}
|
||||
|
||||
// Avoid the cps roundtrip and apply the default/global pref.
|
||||
if (aURI.spec == "about:blank") {
|
||||
this._applyPrefToZoom(undefined, browser,
|
||||
this._notifyOnLocationChange.bind(this, browser));
|
||||
return;
|
||||
}
|
||||
|
||||
// Media documents should always start at 1, and are not affected by prefs.
|
||||
if (!aIsTabSwitch && browser.isSyntheticDocument) {
|
||||
ZoomManager.setZoomForBrowser(browser, 1);
|
||||
// _ignorePendingZoomAccesses already called above, so no need here.
|
||||
this._notifyOnLocationChange(browser);
|
||||
return;
|
||||
}
|
||||
|
||||
// See if the zoom pref is cached.
|
||||
let ctxt = this._loadContextFromBrowser(browser);
|
||||
let pref = this._cps2.getCachedByDomainAndName(aURI.spec, this.name, ctxt);
|
||||
if (pref) {
|
||||
this._applyPrefToZoom(pref.value, browser,
|
||||
this._notifyOnLocationChange.bind(this, browser));
|
||||
return;
|
||||
}
|
||||
|
||||
// It's not cached, so we have to asynchronously fetch it.
|
||||
let value = undefined;
|
||||
let token = this._getBrowserToken(browser);
|
||||
this._cps2.getByDomainAndName(aURI.spec, this.name, ctxt, {
|
||||
handleResult: function (resultPref) { value = resultPref.value; },
|
||||
handleCompletion: function () {
|
||||
if (!token.isCurrent) {
|
||||
this._notifyOnLocationChange(browser);
|
||||
return;
|
||||
}
|
||||
this._applyPrefToZoom(value, browser,
|
||||
this._notifyOnLocationChange.bind(this, browser));
|
||||
}.bind(this)
|
||||
});
|
||||
},
|
||||
|
||||
// update state of zoom type menu item
|
||||
|
||||
updateMenu: function() {
|
||||
var menuItem = document.getElementById("toggle_zoom");
|
||||
|
||||
menuItem.setAttribute("checked", !ZoomManager.useFullZoom);
|
||||
},
|
||||
|
||||
// Setting & Pref Manipulation
|
||||
|
||||
/**
|
||||
* Reduces the zoom level of the page in the current browser.
|
||||
*/
|
||||
reduce: function() {
|
||||
ZoomManager.reduce();
|
||||
let browser = gBrowser.selectedBrowser;
|
||||
this._ignorePendingZoomAccesses(browser);
|
||||
this._applyZoomToPref(browser);
|
||||
},
|
||||
|
||||
/**
|
||||
* Enlarges the zoom level of the page in the current browser.
|
||||
*/
|
||||
enlarge: function() {
|
||||
ZoomManager.enlarge();
|
||||
let browser = gBrowser.selectedBrowser;
|
||||
this._ignorePendingZoomAccesses(browser);
|
||||
this._applyZoomToPref(browser);
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the zoom level for the given browser to the given floating
|
||||
* point value, where 1 is the default zoom level.
|
||||
*/
|
||||
setZoom: function (value, browser = gBrowser.selectedBrowser) {
|
||||
ZoomManager.setZoomForBrowser(browser, value);
|
||||
this._ignorePendingZoomAccesses(browser);
|
||||
this._applyZoomToPref(browser);
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the zoom level of the page in the given browser to the global zoom
|
||||
* level.
|
||||
*
|
||||
* @return A promise which resolves when the zoom reset has been applied.
|
||||
*/
|
||||
reset: function(browser = gBrowser.selectedBrowser) {
|
||||
let token = this._getBrowserToken(browser);
|
||||
let result = this._getGlobalValue(browser).then(value => {
|
||||
if (token.isCurrent) {
|
||||
ZoomManager.setZoomForBrowser(browser, value === undefined ? 1 : value);
|
||||
this._ignorePendingZoomAccesses(browser);
|
||||
Services.obs.notifyObservers(browser, "browser-fullZoom:zoomReset", "");
|
||||
}
|
||||
});
|
||||
this._removePref(browser);
|
||||
return result;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set the zoom level for a given browser.
|
||||
*
|
||||
* Per nsPresContext::setFullZoom, we can set the zoom to its current value
|
||||
* without significant impact on performance, as the setting is only applied
|
||||
* if it differs from the current setting. In fact getting the zoom and then
|
||||
* checking ourselves if it differs costs more.
|
||||
*
|
||||
* And perhaps we should always set the zoom even if it was more expensive,
|
||||
* since nsDocumentViewer::SetTextZoom claims that child documents can have
|
||||
* a different text zoom (although it would be unusual), and it implies that
|
||||
* those child text zooms should get updated when the parent zoom gets set,
|
||||
* and perhaps the same is true for full zoom
|
||||
* (although nsDocumentViewer::SetFullZoom doesn't mention it).
|
||||
*
|
||||
* So when we apply new zoom values to the browser, we simply set the zoom.
|
||||
* We don't check first to see if the new value is the same as the current
|
||||
* one.
|
||||
*
|
||||
* @param aValue The zoom level value.
|
||||
* @param aBrowser The zoom is set in this browser. Required.
|
||||
* @param aCallback If given, it's asynchronously called when complete.
|
||||
*/
|
||||
_applyPrefToZoom: function(aValue, aBrowser, aCallback) {
|
||||
if (!this.siteSpecific || gInPrintPreviewMode) {
|
||||
this._executeSoon(aCallback);
|
||||
return;
|
||||
}
|
||||
|
||||
// The browser is sometimes half-destroyed because this method is called
|
||||
// by content pref service callbacks, which themselves can be called at any
|
||||
// time, even after browsers are closed.
|
||||
if (!aBrowser.parentNode || aBrowser.isSyntheticDocument) {
|
||||
this._executeSoon(aCallback);
|
||||
return;
|
||||
}
|
||||
|
||||
if (aValue !== undefined) {
|
||||
ZoomManager.setZoomForBrowser(aBrowser, this._ensureValid(aValue));
|
||||
this._ignorePendingZoomAccesses(aBrowser);
|
||||
this._executeSoon(aCallback);
|
||||
return;
|
||||
}
|
||||
|
||||
let token = this._getBrowserToken(aBrowser);
|
||||
this._getGlobalValue(aBrowser).then(value => {
|
||||
if (token.isCurrent) {
|
||||
ZoomManager.setZoomForBrowser(aBrowser, value === undefined ? 1 : value);
|
||||
this._ignorePendingZoomAccesses(aBrowser);
|
||||
}
|
||||
this._executeSoon(aCallback);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Saves the zoom level of the page in the given browser to the content
|
||||
* prefs store.
|
||||
*
|
||||
* @param browser The zoom of this browser will be saved. Required.
|
||||
*/
|
||||
_applyZoomToPref: function(browser) {
|
||||
Services.obs.notifyObservers(browser, "browser-fullZoom:zoomChange", "");
|
||||
if (!this.siteSpecific ||
|
||||
gInPrintPreviewMode ||
|
||||
browser.isSyntheticDocument)
|
||||
return;
|
||||
|
||||
this._cps2.set(browser.currentURI.spec, this.name,
|
||||
ZoomManager.getZoomForBrowser(browser),
|
||||
this._loadContextFromBrowser(browser), {
|
||||
handleCompletion: function () {
|
||||
this._isNextContentPrefChangeInternal = true;
|
||||
}.bind(this),
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Removes from the content prefs store the zoom level of the given browser.
|
||||
*
|
||||
* @param browser The zoom of this browser will be removed. Required.
|
||||
*/
|
||||
_removePref: function(browser) {
|
||||
Services.obs.notifyObservers(browser, "browser-fullZoom:zoomReset", "");
|
||||
if (browser.isSyntheticDocument)
|
||||
return;
|
||||
let ctxt = this._loadContextFromBrowser(browser);
|
||||
this._cps2.removeByDomainAndName(browser.currentURI.spec, this.name, ctxt, {
|
||||
handleCompletion: function () {
|
||||
this._isNextContentPrefChangeInternal = true;
|
||||
}.bind(this),
|
||||
});
|
||||
},
|
||||
|
||||
// Utilities
|
||||
|
||||
/**
|
||||
* Returns the zoom change token of the given browser. Asynchronous
|
||||
* operations that access the given browser's zoom should use this method to
|
||||
* capture the token before starting and use token.isCurrent to determine if
|
||||
* it's safe to access the zoom when done. If token.isCurrent is false, then
|
||||
* after the async operation started, either the browser's zoom was changed or
|
||||
* the browser was destroyed, and depending on what the operation is doing, it
|
||||
* may no longer be safe to set and get its zoom.
|
||||
*
|
||||
* @param browser The token of this browser will be returned.
|
||||
* @return An object with an "isCurrent" getter.
|
||||
*/
|
||||
_getBrowserToken: function(browser) {
|
||||
let map = this._browserTokenMap;
|
||||
if (!map.has(browser))
|
||||
map.set(browser, 0);
|
||||
return {
|
||||
token: map.get(browser),
|
||||
get isCurrent() {
|
||||
// At this point, the browser may have been destructed and unbound but
|
||||
// its outer ID not removed from the map because outer-window-destroyed
|
||||
// hasn't been received yet. In that case, the browser is unusable, it
|
||||
// has no properties, so return false. Check for this case by getting a
|
||||
// property, say, docShell.
|
||||
return map.get(browser) === this.token && browser.parentNode;
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the browser that the supplied zoom event is associated with.
|
||||
* @param event The ZoomChangeUsingMouseWheel event.
|
||||
* @return The associated browser element, if one exists, otherwise null.
|
||||
*/
|
||||
_getTargetedBrowser: function(event) {
|
||||
let target = event.originalTarget;
|
||||
|
||||
// With remote content browsers, the event's target is the browser
|
||||
// we're looking for.
|
||||
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
|
||||
if (target instanceof window.XULElement &&
|
||||
target.localName == "browser" &&
|
||||
target.namespaceURI == XUL_NS)
|
||||
return target;
|
||||
|
||||
// With in-process content browsers, the event's target is the content
|
||||
// document.
|
||||
if (target.nodeType == Node.DOCUMENT_NODE)
|
||||
return gBrowser.getBrowserForDocument(target);
|
||||
|
||||
throw new Error("Unexpected ZoomChangeUsingMouseWheel event source");
|
||||
},
|
||||
|
||||
/**
|
||||
* Increments the zoom change token for the given browser so that pending
|
||||
* async operations know that it may be unsafe to access they zoom when they
|
||||
* finish.
|
||||
*
|
||||
* @param browser Pending accesses in this browser will be ignored.
|
||||
*/
|
||||
_ignorePendingZoomAccesses: function(browser) {
|
||||
let map = this._browserTokenMap;
|
||||
map.set(browser, (map.get(browser) || 0) + 1);
|
||||
},
|
||||
|
||||
_ensureValid: function(aValue) {
|
||||
// Note that undefined is a valid value for aValue that indicates a known-
|
||||
// not-to-exist value.
|
||||
if (isNaN(aValue))
|
||||
return 1;
|
||||
|
||||
if (aValue < ZoomManager.MIN)
|
||||
return ZoomManager.MIN;
|
||||
|
||||
if (aValue > ZoomManager.MAX)
|
||||
return ZoomManager.MAX;
|
||||
|
||||
return aValue;
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the global browser.content.full-zoom content preference.
|
||||
*
|
||||
* @param browser The browser pertaining to the zoom.
|
||||
* @returns Promise<prefValue>
|
||||
* Resolves to the preference value when done.
|
||||
*/
|
||||
_getGlobalValue: function(browser) {
|
||||
// * !("_globalValue" in this) => global value not yet cached.
|
||||
// * this._globalValue === undefined => global value known not to exist.
|
||||
// * Otherwise, this._globalValue is a number, the global value.
|
||||
return new Promise(resolve => {
|
||||
if ("_globalValue" in this) {
|
||||
resolve(this._globalValue);
|
||||
return;
|
||||
}
|
||||
let value = undefined;
|
||||
this._cps2.getGlobal(this.name, this._loadContextFromBrowser(browser), {
|
||||
handleResult: function (pref) { value = pref.value; },
|
||||
handleCompletion: (reason) => {
|
||||
this._globalValue = this._ensureValid(value);
|
||||
resolve(this._globalValue);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the load context from the given Browser.
|
||||
*
|
||||
* @param Browser The Browser whose load context will be returned.
|
||||
* @return The nsILoadContext of the given Browser.
|
||||
*/
|
||||
_loadContextFromBrowser: function(browser) {
|
||||
return browser.loadContext;
|
||||
},
|
||||
|
||||
/**
|
||||
* Asynchronously broadcasts "browser-fullZoom:location-change" so that
|
||||
* listeners can be notified when the zoom levels on those pages change.
|
||||
* The notification is always asynchronous so that observers are guaranteed a
|
||||
* consistent behavior.
|
||||
*/
|
||||
_notifyOnLocationChange: function(browser) {
|
||||
this._executeSoon(function () {
|
||||
Services.obs.notifyObservers(browser, "browser-fullZoom:location-change", "");
|
||||
});
|
||||
},
|
||||
|
||||
_executeSoon: function(callback) {
|
||||
if (!callback)
|
||||
return;
|
||||
Services.tm.mainThread.dispatch(callback, Ci.nsIThread.DISPATCH_NORMAL);
|
||||
},
|
||||
};
|
||||
1416
browser/base/content/browser-gestureSupport.js
Normal file
351
browser/base/content/browser-media.js
Normal file
|
|
@ -0,0 +1,351 @@
|
|||
/* -*- 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 gEMEHandler = {
|
||||
get uiEnabled() {
|
||||
#ifdef MOZ_EME
|
||||
let emeUIEnabled = Services.prefs.getBoolPref("browser.eme.ui.enabled");
|
||||
// Force-disable on WinXP:
|
||||
if (navigator.platform.toLowerCase().startsWith("win")) {
|
||||
emeUIEnabled = emeUIEnabled && parseFloat(Services.sysinfo.get("version")) >= 6;
|
||||
}
|
||||
return emeUIEnabled;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
},
|
||||
ensureEMEEnabled: function(browser, keySystem) {
|
||||
Services.prefs.setBoolPref("media.eme.enabled", true);
|
||||
if (keySystem) {
|
||||
if (keySystem.startsWith("com.adobe") &&
|
||||
Services.prefs.getPrefType("media.gmp-eme-adobe.enabled") &&
|
||||
!Services.prefs.getBoolPref("media.gmp-eme-adobe.enabled")) {
|
||||
Services.prefs.setBoolPref("media.gmp-eme-adobe.enabled", true);
|
||||
} else if (keySystem == "com.widevine.alpha" &&
|
||||
Services.prefs.getPrefType("media.gmp-widevinecdm.enabled") &&
|
||||
!Services.prefs.getBoolPref("media.gmp-widevinecdm.enabled")) {
|
||||
Services.prefs.setBoolPref("media.gmp-widevinecdm.enabled", true);
|
||||
}
|
||||
}
|
||||
browser.reload();
|
||||
},
|
||||
isKeySystemVisible: function(keySystem) {
|
||||
if (!keySystem) {
|
||||
return false;
|
||||
}
|
||||
if (keySystem.startsWith("com.adobe") &&
|
||||
Services.prefs.getPrefType("media.gmp-eme-adobe.visible")) {
|
||||
return Services.prefs.getBoolPref("media.gmp-eme-adobe.visible");
|
||||
}
|
||||
if (keySystem == "com.widevine.alpha" &&
|
||||
Services.prefs.getPrefType("media.gmp-widevinecdm.visible")) {
|
||||
return Services.prefs.getBoolPref("media.gmp-widevinecdm.visible");
|
||||
}
|
||||
return true;
|
||||
},
|
||||
getEMEDisabledFragment: function(msgId) {
|
||||
let mainMessage = gNavigatorBundle.getString("emeNotifications.drmContentDisabled.message");
|
||||
let [prefix, suffix] = mainMessage.split(/%(?:\$\d)?S/).map(s => document.createTextNode(s));
|
||||
let text = gNavigatorBundle.getString("emeNotifications.drmContentDisabled.learnMoreLabel");
|
||||
let baseURL = Services.urlFormatter.formatURLPref("app.support.baseURL");
|
||||
let link = document.createElement("label");
|
||||
link.className = "text-link";
|
||||
link.setAttribute("href", baseURL + "drm-content");
|
||||
link.textContent = text;
|
||||
|
||||
let fragment = document.createDocumentFragment();
|
||||
[prefix, link, suffix].forEach(n => fragment.appendChild(n));
|
||||
return fragment;
|
||||
},
|
||||
getMessageWithBrandName: function(notificationId) {
|
||||
let msgId = "emeNotifications." + notificationId + ".message";
|
||||
return gNavigatorBundle.getFormattedString(msgId, [this._brandShortName]);
|
||||
},
|
||||
receiveMessage: function({target: browser, data: data}) {
|
||||
let parsedData;
|
||||
try {
|
||||
parsedData = JSON.parse(data);
|
||||
} catch (ex) {
|
||||
Cu.reportError("Malformed EME video message with data: " + data);
|
||||
return;
|
||||
}
|
||||
let {status: status, keySystem: keySystem} = parsedData;
|
||||
// Don't need to show if disabled or keysystem not visible.
|
||||
if (!this.uiEnabled || !this.isKeySystemVisible(keySystem)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let notificationId;
|
||||
let buttonCallback;
|
||||
// Notification message can be either a string or a DOM fragment.
|
||||
let notificationMessage;
|
||||
switch (status) {
|
||||
case "available":
|
||||
case "cdm-created":
|
||||
// Only show the chain icon for proprietary CDMs. Clearkey is not one.
|
||||
if (keySystem != "org.w3.clearkey") {
|
||||
this.showPopupNotificationForSuccess(browser, keySystem);
|
||||
}
|
||||
// ... and bail!
|
||||
return;
|
||||
|
||||
case "api-disabled":
|
||||
case "cdm-disabled":
|
||||
notificationId = "drmContentDisabled";
|
||||
buttonCallback = gEMEHandler.ensureEMEEnabled.bind(gEMEHandler, browser, keySystem)
|
||||
notificationMessage = this.getEMEDisabledFragment();
|
||||
break;
|
||||
|
||||
case "cdm-insufficient-version":
|
||||
notificationId = "drmContentCDMInsufficientVersion";
|
||||
notificationMessage = this.getMessageWithBrandName(notificationId);
|
||||
break;
|
||||
|
||||
case "cdm-not-installed":
|
||||
notificationId = "drmContentCDMInstalling";
|
||||
notificationMessage = this.getMessageWithBrandName(notificationId);
|
||||
break;
|
||||
|
||||
case "cdm-not-supported":
|
||||
// Not to pop up user-level notification because they cannot do anything
|
||||
// about it.
|
||||
return;
|
||||
default:
|
||||
Cu.reportError(new Error("Unknown message ('" + status + "') dealing with EME key request: " + data));
|
||||
return;
|
||||
}
|
||||
|
||||
// Now actually create the notification
|
||||
|
||||
let box = gBrowser.getNotificationBox(browser);
|
||||
if (box.getNotificationWithValue(notificationId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let buttons = [];
|
||||
if (buttonCallback) {
|
||||
let msgPrefix = "emeNotifications." + notificationId + ".";
|
||||
let btnLabelId = msgPrefix + "button.label";
|
||||
let btnAccessKeyId = msgPrefix + "button.accesskey";
|
||||
buttons.push({
|
||||
label: gNavigatorBundle.getString(btnLabelId),
|
||||
accessKey: gNavigatorBundle.getString(btnAccessKeyId),
|
||||
callback: buttonCallback
|
||||
});
|
||||
}
|
||||
|
||||
let iconURL = "chrome://browser/skin/drm-icon.svg#chains-black";
|
||||
|
||||
box.appendNotification(notificationMessage, notificationId, iconURL,
|
||||
box.PRIORITY_WARNING_MEDIUM, buttons);
|
||||
},
|
||||
showPopupNotificationForSuccess: function(browser, keySystem) {
|
||||
// We're playing EME content! Remove any "we can't play because..." messages.
|
||||
var box = gBrowser.getNotificationBox(browser);
|
||||
["drmContentDisabled",
|
||||
"drmContentCDMInstalling"
|
||||
].forEach(function (value) {
|
||||
var notification = box.getNotificationWithValue(value);
|
||||
if (notification)
|
||||
box.removeNotification(notification);
|
||||
});
|
||||
|
||||
// Don't bother creating it if it's already there:
|
||||
if (PopupNotifications.getNotification("drmContentPlaying", browser)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let msgPrefix = "emeNotifications.drmContentPlaying.";
|
||||
let msgId = msgPrefix + "message2";
|
||||
let btnLabelId = msgPrefix + "button.label";
|
||||
let btnAccessKeyId = msgPrefix + "button.accesskey";
|
||||
|
||||
let message = gNavigatorBundle.getFormattedString(msgId, [this._brandShortName]);
|
||||
let anchorId = "eme-notification-icon";
|
||||
let firstPlayPref = "browser.eme.ui.firstContentShown";
|
||||
if (!Services.prefs.getPrefType(firstPlayPref) ||
|
||||
!Services.prefs.getBoolPref(firstPlayPref)) {
|
||||
document.getElementById(anchorId).setAttribute("firstplay", "true");
|
||||
Services.prefs.setBoolPref(firstPlayPref, true);
|
||||
} else {
|
||||
document.getElementById(anchorId).removeAttribute("firstplay");
|
||||
}
|
||||
|
||||
let mainAction = {
|
||||
label: gNavigatorBundle.getString(btnLabelId),
|
||||
accessKey: gNavigatorBundle.getString(btnAccessKeyId),
|
||||
callback: function() { openPreferences("paneContent"); },
|
||||
dismiss: true
|
||||
};
|
||||
let options = {
|
||||
dismissed: true,
|
||||
eventCallback: aTopic => aTopic == "swapping",
|
||||
learnMoreURL: Services.urlFormatter.formatURLPref("app.support.baseURL") + "drm-content",
|
||||
};
|
||||
PopupNotifications.show(browser, "drmContentPlaying", message, anchorId, mainAction, null, options);
|
||||
},
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIMessageListener])
|
||||
};
|
||||
|
||||
XPCOMUtils.defineLazyGetter(gEMEHandler, "_brandShortName", function() {
|
||||
return document.getElementById("bundle_brand").getString("brandShortName");
|
||||
});
|
||||
|
||||
let gDecoderDoctorHandler = {
|
||||
getLabelForNotificationBox(type) {
|
||||
if (type == "adobe-cdm-not-found" &&
|
||||
AppConstants.platform == "win") {
|
||||
if (AppConstants.isPlatformAndVersionAtMost("win", "5.9")) {
|
||||
// We supply our own Learn More button so we don't need to populate the message here.
|
||||
return gNavigatorBundle.getFormattedString("emeNotifications.drmContentDisabled.message", [""]);
|
||||
}
|
||||
return gNavigatorBundle.getString("decoder.noCodecs.message");
|
||||
}
|
||||
if (type == "adobe-cdm-not-activated" &&
|
||||
AppConstants.platform == "win") {
|
||||
if (AppConstants.isPlatformAndVersionAtMost("win", "5.9")) {
|
||||
return gNavigatorBundle.getString("decoder.noCodecsXP.message");
|
||||
}
|
||||
if (!AppConstants.isPlatformAndVersionAtLeast("win", "6.1")) {
|
||||
return gNavigatorBundle.getString("decoder.noCodecsVista.message");
|
||||
}
|
||||
return gNavigatorBundle.getString("decoder.noCodecs.message");
|
||||
}
|
||||
if (type == "platform-decoder-not-found") {
|
||||
if (AppConstants.isPlatformAndVersionAtLeast("win", "6.1")) {
|
||||
return gNavigatorBundle.getString("decoder.noHWAcceleration.message");
|
||||
}
|
||||
if (AppConstants.isPlatformAndVersionAtLeast("win", "6")) {
|
||||
return gNavigatorBundle.getString("decoder.noHWAccelerationVista.message");
|
||||
}
|
||||
if (AppConstants.platform == "linux") {
|
||||
return gNavigatorBundle.getString("decoder.noCodecsLinux.message");
|
||||
}
|
||||
}
|
||||
if (type == "cannot-initialize-pulseaudio") {
|
||||
return gNavigatorBundle.getString("decoder.noPulseAudio.message");
|
||||
}
|
||||
if (type == "unsupported-libavcodec" &&
|
||||
AppConstants.platform == "linux") {
|
||||
return gNavigatorBundle.getString("decoder.unsupportedLibavcodec.message");
|
||||
}
|
||||
return "";
|
||||
},
|
||||
|
||||
getSumoForLearnHowButton(type) {
|
||||
if (AppConstants.platform == "win") {
|
||||
return "fix-video-audio-problems-firefox-windows";
|
||||
}
|
||||
if (type == "cannot-initialize-pulseaudio") {
|
||||
return "fix-common-audio-and-video-issues";
|
||||
}
|
||||
return "";
|
||||
},
|
||||
|
||||
receiveMessage({target: browser, data: data}) {
|
||||
let box = gBrowser.getNotificationBox(browser);
|
||||
let notificationId = "decoder-doctor-notification";
|
||||
if (box.getNotificationWithValue(notificationId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let parsedData;
|
||||
try {
|
||||
parsedData = JSON.parse(data);
|
||||
} catch (ex) {
|
||||
Cu.reportError("Malformed Decoder Doctor message with data: " + data);
|
||||
return;
|
||||
}
|
||||
// parsedData (the result of parsing the incoming 'data' json string)
|
||||
// contains analysis information from Decoder Doctor:
|
||||
// - 'type' is the type of issue, it determines which text to show in the
|
||||
// infobar.
|
||||
// - 'decoderDoctorReportId' is the Decoder Doctor issue identifier, to be
|
||||
// used here as key for the prefs used to store at-issue formats.
|
||||
// - 'formats' contains a comma-separated list of formats (or key systems)
|
||||
// that suffer the issue. These are kept in a pref, which the backend
|
||||
// uses to later find when an issue is resolved.
|
||||
// - 'isSolved' is true when the notification actually indicates the
|
||||
// resolution of that issue.
|
||||
let {type, isSolved, decoderDoctorReportId, formats} = parsedData;
|
||||
type = type.toLowerCase();
|
||||
// Error out early on invalid ReportId
|
||||
if (!(/^\w+$/mi).test(decoderDoctorReportId)) {
|
||||
return
|
||||
}
|
||||
let title = gDecoderDoctorHandler.getLabelForNotificationBox(type);
|
||||
if (!title) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We keep the list of formats in prefs for the sake of the decoder itself,
|
||||
// which reads it to determine when issues get solved for these formats.
|
||||
// (Writing prefs from e10s content is now allowed.)
|
||||
let formatsPref = "media.decoder-doctor." + decoderDoctorReportId + ".formats";
|
||||
let buttonClickedPref = "media.decoder-doctor." + decoderDoctorReportId + ".button-clicked";
|
||||
let formatsInPref = Services.prefs.getPrefType(formatsPref) &&
|
||||
Services.prefs.getCharPref(formatsPref);
|
||||
|
||||
if (!isSolved) {
|
||||
if (!formats) {
|
||||
Cu.reportError("Malformed Decoder Doctor unsolved message with no formats");
|
||||
return;
|
||||
}
|
||||
if (!formatsInPref) {
|
||||
Services.prefs.setCharPref(formatsPref, formats);
|
||||
} else {
|
||||
// Split existing formats into an array of strings.
|
||||
let existing = formatsInPref.split(",").map(String.trim);
|
||||
// Keep given formats that were not already recorded.
|
||||
let newbies = formats.split(",").map(String.trim)
|
||||
.filter(x => !existing.includes(x));
|
||||
// And rewrite pref with the added new formats (if any).
|
||||
if (newbies.length) {
|
||||
Services.prefs.setCharPref(formatsPref,
|
||||
existing.concat(newbies).join(", "));
|
||||
}
|
||||
}
|
||||
|
||||
let buttons = [];
|
||||
let sumo = gDecoderDoctorHandler.getSumoForLearnHowButton(type);
|
||||
if (sumo) {
|
||||
buttons.push({
|
||||
label: gNavigatorBundle.getString("decoder.noCodecs.button"),
|
||||
accessKey: gNavigatorBundle.getString("decoder.noCodecs.accesskey"),
|
||||
callback() {
|
||||
let clickedInPref = Services.prefs.getPrefType(buttonClickedPref) &&
|
||||
Services.prefs.getBoolPref(buttonClickedPref);
|
||||
if (!clickedInPref) {
|
||||
Services.prefs.setBoolPref(buttonClickedPref, true);
|
||||
}
|
||||
|
||||
let baseURL = Services.urlFormatter.formatURLPref("app.support.baseURL");
|
||||
openUILinkIn(baseURL + sumo, "tab");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
box.appendNotification(
|
||||
title,
|
||||
notificationId,
|
||||
"", // This uses the info icon as specified below.
|
||||
box.PRIORITY_INFO_LOW,
|
||||
buttons
|
||||
);
|
||||
} else if (formatsInPref) {
|
||||
// Issue is solved, and prefs haven't been cleared yet, meaning it's the
|
||||
// first time we get this resolution -> Clear prefs.
|
||||
Services.prefs.clearUserPref(formatsPref);
|
||||
Services.prefs.clearUserPref(buttonClickedPref);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
window.getGroupMessageManager("browsers").addMessageListener("DecoderDoctor:Notification", gDecoderDoctorHandler);
|
||||
window.getGroupMessageManager("browsers").addMessageListener("EMEVideo:ContentMediaKeysRequest", gEMEHandler);
|
||||
window.addEventListener("unload", function() {
|
||||
window.getGroupMessageManager("browsers").removeMessageListener("EMEVideo:ContentMediaKeysRequest", gEMEHandler);
|
||||
window.getGroupMessageManager("browsers").removeMessageListener("DecoderDoctor:Notification", gDecoderDoctorHandler);
|
||||
}, false);
|
||||
511
browser/base/content/browser-menubar.inc
Normal file
|
|
@ -0,0 +1,511 @@
|
|||
# -*- 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/.
|
||||
|
||||
<menubar id="main-menubar"
|
||||
onpopupshowing="if (event.target.parentNode.parentNode == this &&
|
||||
#ifdef MOZ_WIDGET_GTK
|
||||
document.documentElement.getAttribute('shellshowingmenubar') != 'true')
|
||||
#else
|
||||
!('@mozilla.org/widget/nativemenuservice;1' in Cc))
|
||||
#endif
|
||||
this.setAttribute('openedwithkey',
|
||||
event.target.parentNode.openedWithKey);"
|
||||
style="border:0px;padding:0px;margin:0px;-moz-appearance:none">
|
||||
<menu id="file-menu" label="&fileMenu.label;"
|
||||
accesskey="&fileMenu.accesskey;">
|
||||
<menupopup id="menu_FilePopup"
|
||||
onpopupshowing="updateUserContextUIVisibility();">
|
||||
<menuitem id="menu_newNavigatorTab"
|
||||
label="&tabCmd.label;"
|
||||
command="cmd_newNavigatorTab"
|
||||
key="key_newNavigatorTab"
|
||||
accesskey="&tabCmd.accesskey;"/>
|
||||
<menu id="menu_newUserContext"
|
||||
label="&newUserContext.label;"
|
||||
accesskey="&newUserContext.accesskey;"
|
||||
hidden="true">
|
||||
<menupopup onpopupshowing="return createUserContextMenu(event);" />
|
||||
</menu>
|
||||
<menuitem id="menu_newNavigator"
|
||||
label="&newNavigatorCmd.label;"
|
||||
accesskey="&newNavigatorCmd.accesskey;"
|
||||
key="key_newNavigator"
|
||||
command="cmd_newNavigator"/>
|
||||
<menuitem id="menu_newPrivateWindow"
|
||||
label="&newPrivateWindow.label;"
|
||||
accesskey="&newPrivateWindow.accesskey;"
|
||||
command="Tools:PrivateBrowsing"
|
||||
key="key_privatebrowsing"/>
|
||||
#ifdef MAC_NON_BROWSER_WINDOW
|
||||
<menuitem id="menu_openLocation"
|
||||
label="&openLocationCmd.label;"
|
||||
command="Browser:OpenLocation"
|
||||
key="focusURLBar"/>
|
||||
#endif
|
||||
<menuitem id="menu_openFile"
|
||||
label="&openFileCmd.label;"
|
||||
command="Browser:OpenFile"
|
||||
key="openFileKb"
|
||||
accesskey="&openFileCmd.accesskey;"/>
|
||||
<menuitem id="menu_close"
|
||||
class="show-only-for-keyboard"
|
||||
label="&closeCmd.label;"
|
||||
key="key_close"
|
||||
accesskey="&closeCmd.accesskey;"
|
||||
command="cmd_close"/>
|
||||
<menuitem id="menu_closeWindow"
|
||||
class="show-only-for-keyboard"
|
||||
hidden="true"
|
||||
command="cmd_closeWindow"
|
||||
key="key_closeWindow"
|
||||
label="&closeWindow.label;"
|
||||
accesskey="&closeWindow.accesskey;"/>
|
||||
<menuseparator/>
|
||||
<menuitem id="menu_savePage"
|
||||
label="&savePageCmd.label;"
|
||||
accesskey="&savePageCmd.accesskey;"
|
||||
key="key_savePage"
|
||||
command="Browser:SavePage"/>
|
||||
<menuitem id="menu_sendLink"
|
||||
label="&emailPageCmd.label;"
|
||||
accesskey="&emailPageCmd.accesskey;"
|
||||
command="Browser:SendLink"/>
|
||||
<menuseparator/>
|
||||
#if !defined(MOZ_WIDGET_GTK)
|
||||
<menuitem id="menu_printSetup"
|
||||
label="&printSetupCmd.label;"
|
||||
accesskey="&printSetupCmd.accesskey;"
|
||||
command="cmd_pageSetup"/>
|
||||
#endif
|
||||
#ifndef XP_MACOSX
|
||||
<menuitem id="menu_printPreview"
|
||||
label="&printPreviewCmd.label;"
|
||||
accesskey="&printPreviewCmd.accesskey;"
|
||||
command="cmd_printPreview"/>
|
||||
#endif
|
||||
<menuitem id="menu_print"
|
||||
label="&printCmd.label;"
|
||||
accesskey="&printCmd.accesskey;"
|
||||
key="printKb"
|
||||
command="cmd_print"/>
|
||||
<menuseparator/>
|
||||
<menuitem id="goOfflineMenuitem"
|
||||
label="&goOfflineCmd.label;"
|
||||
accesskey="&goOfflineCmd.accesskey;"
|
||||
type="checkbox"
|
||||
observes="workOfflineMenuitemState"
|
||||
oncommand="BrowserOffline.toggleOfflineStatus();"/>
|
||||
<menuitem id="menu_FileQuitItem"
|
||||
#ifdef XP_WIN
|
||||
label="&quitApplicationCmdWin2.label;"
|
||||
accesskey="&quitApplicationCmdWin2.accesskey;"
|
||||
#else
|
||||
#ifdef XP_MACOSX
|
||||
label="&quitApplicationCmdMac2.label;"
|
||||
#else
|
||||
label="&quitApplicationCmd.label;"
|
||||
accesskey="&quitApplicationCmd.accesskey;"
|
||||
#endif
|
||||
#ifdef XP_UNIX
|
||||
key="key_quitApplication"
|
||||
#endif
|
||||
#endif
|
||||
command="cmd_quitApplication"/>
|
||||
</menupopup>
|
||||
</menu>
|
||||
|
||||
<menu id="edit-menu" label="&editMenu.label;"
|
||||
accesskey="&editMenu.accesskey;">
|
||||
<menupopup id="menu_EditPopup"
|
||||
onpopupshowing="updateEditUIVisibility()"
|
||||
onpopuphidden="updateEditUIVisibility()">
|
||||
<menuitem id="menu_undo"
|
||||
label="&undoCmd.label;"
|
||||
key="key_undo"
|
||||
accesskey="&undoCmd.accesskey;"
|
||||
command="cmd_undo"/>
|
||||
<menuitem id="menu_redo"
|
||||
label="&redoCmd.label;"
|
||||
key="key_redo"
|
||||
accesskey="&redoCmd.accesskey;"
|
||||
command="cmd_redo"/>
|
||||
<menuseparator/>
|
||||
<menuitem id="menu_cut"
|
||||
label="&cutCmd.label;"
|
||||
key="key_cut"
|
||||
accesskey="&cutCmd.accesskey;"
|
||||
command="cmd_cut"/>
|
||||
<menuitem id="menu_copy"
|
||||
label="©Cmd.label;"
|
||||
key="key_copy"
|
||||
accesskey="©Cmd.accesskey;"
|
||||
command="cmd_copy"/>
|
||||
<menuitem id="menu_paste"
|
||||
label="&pasteCmd.label;"
|
||||
key="key_paste"
|
||||
accesskey="&pasteCmd.accesskey;"
|
||||
command="cmd_paste"/>
|
||||
<menuitem id="menu_delete"
|
||||
label="&deleteCmd.label;"
|
||||
key="key_delete"
|
||||
accesskey="&deleteCmd.accesskey;"
|
||||
command="cmd_delete"/>
|
||||
<menuseparator/>
|
||||
<menuitem id="menu_selectAll"
|
||||
label="&selectAllCmd.label;"
|
||||
key="key_selectAll"
|
||||
accesskey="&selectAllCmd.accesskey;"
|
||||
command="cmd_selectAll"/>
|
||||
<menuseparator/>
|
||||
<menuitem id="menu_find"
|
||||
label="&findOnCmd.label;"
|
||||
accesskey="&findOnCmd.accesskey;"
|
||||
key="key_find"
|
||||
command="cmd_find"/>
|
||||
<menuitem id="menu_findAgain"
|
||||
class="show-only-for-keyboard"
|
||||
label="&findAgainCmd.label;"
|
||||
accesskey="&findAgainCmd.accesskey;"
|
||||
key="key_findAgain"
|
||||
command="cmd_findAgain"/>
|
||||
<menuseparator hidden="true" id="textfieldDirection-separator"/>
|
||||
<menuitem id="textfieldDirection-swap"
|
||||
command="cmd_switchTextDirection"
|
||||
key="key_switchTextDirection"
|
||||
label="&bidiSwitchTextDirectionItem.label;"
|
||||
accesskey="&bidiSwitchTextDirectionItem.accesskey;"
|
||||
hidden="true"/>
|
||||
</menupopup>
|
||||
</menu>
|
||||
|
||||
<menu id="view-menu" label="&viewMenu.label;"
|
||||
accesskey="&viewMenu.accesskey;">
|
||||
<menupopup id="menu_viewPopup"
|
||||
onpopupshowing="updateCharacterEncodingMenuState();">
|
||||
<menu id="viewToolbarsMenu"
|
||||
label="&viewToolbarsMenu.label;"
|
||||
accesskey="&viewToolbarsMenu.accesskey;">
|
||||
<menupopup onpopupshowing="onViewToolbarsPopupShowing(event);">
|
||||
<menuseparator/>
|
||||
<menuitem id="menu_customizeToolbars"
|
||||
label="&viewCustomizeToolbar.label;"
|
||||
accesskey="&viewCustomizeToolbar.accesskey;"
|
||||
command="cmd_CustomizeToolbars"/>
|
||||
</menupopup>
|
||||
</menu>
|
||||
<menu id="viewSidebarMenuMenu"
|
||||
label="&viewSidebarMenu.label;"
|
||||
accesskey="&viewSidebarMenu.accesskey;">
|
||||
<menupopup id="viewSidebarMenu">
|
||||
<menuitem id="menu_bookmarksSidebar"
|
||||
key="viewBookmarksSidebarKb"
|
||||
observes="viewBookmarksSidebar"/>
|
||||
<menuitem id="menu_historySidebar"
|
||||
key="key_gotoHistory"
|
||||
observes="viewHistorySidebar"
|
||||
label="&historyButton.label;"/>
|
||||
</menupopup>
|
||||
</menu>
|
||||
<menuseparator/>
|
||||
<menu id="viewFullZoomMenu" label="&fullZoom.label;"
|
||||
accesskey="&fullZoom.accesskey;"
|
||||
onpopupshowing="FullZoom.updateMenu();">
|
||||
<menupopup>
|
||||
<menuitem id="menu_zoomEnlarge"
|
||||
key="key_fullZoomEnlarge"
|
||||
label="&fullZoomEnlargeCmd.label;"
|
||||
accesskey="&fullZoomEnlargeCmd.accesskey;"
|
||||
command="cmd_fullZoomEnlarge"/>
|
||||
<menuitem id="menu_zoomReduce"
|
||||
key="key_fullZoomReduce"
|
||||
label="&fullZoomReduceCmd.label;"
|
||||
accesskey="&fullZoomReduceCmd.accesskey;"
|
||||
command="cmd_fullZoomReduce"/>
|
||||
<menuseparator/>
|
||||
<menuitem id="menu_zoomReset"
|
||||
key="key_fullZoomReset"
|
||||
label="&fullZoomResetCmd.label;"
|
||||
accesskey="&fullZoomResetCmd.accesskey;"
|
||||
command="cmd_fullZoomReset"/>
|
||||
<menuseparator/>
|
||||
<menuitem id="toggle_zoom"
|
||||
label="&fullZoomToggleCmd.label;"
|
||||
accesskey="&fullZoomToggleCmd.accesskey;"
|
||||
type="checkbox"
|
||||
command="cmd_fullZoomToggle"
|
||||
checked="false"/>
|
||||
</menupopup>
|
||||
</menu>
|
||||
<menu id="pageStyleMenu" label="&pageStyleMenu.label;"
|
||||
accesskey="&pageStyleMenu.accesskey;" observes="isImage">
|
||||
<menupopup onpopupshowing="gPageStyleMenu.fillPopup(this);">
|
||||
<menuitem id="menu_pageStyleNoStyle"
|
||||
label="&pageStyleNoStyle.label;"
|
||||
accesskey="&pageStyleNoStyle.accesskey;"
|
||||
oncommand="gPageStyleMenu.disableStyle();"
|
||||
type="radio"/>
|
||||
<menuitem id="menu_pageStylePersistentOnly"
|
||||
label="&pageStylePersistentOnly.label;"
|
||||
accesskey="&pageStylePersistentOnly.accesskey;"
|
||||
oncommand="gPageStyleMenu.switchStyleSheet('');"
|
||||
type="radio"
|
||||
checked="true"/>
|
||||
<menuseparator/>
|
||||
</menupopup>
|
||||
</menu>
|
||||
#include browser-charsetmenu.inc
|
||||
<menuseparator/>
|
||||
#ifdef XP_MACOSX
|
||||
<menuitem id="enterFullScreenItem"
|
||||
accesskey="&enterFullScreenCmd.accesskey;"
|
||||
label="&enterFullScreenCmd.label;"
|
||||
key="key_fullScreen">
|
||||
<observes element="View:FullScreen" attribute="oncommand"/>
|
||||
<observes element="View:FullScreen" attribute="disabled"/>
|
||||
</menuitem>
|
||||
<menuitem id="exitFullScreenItem"
|
||||
accesskey="&exitFullScreenCmd.accesskey;"
|
||||
label="&exitFullScreenCmd.label;"
|
||||
key="key_fullScreen"
|
||||
hidden="true">
|
||||
<observes element="View:FullScreen" attribute="oncommand"/>
|
||||
<observes element="View:FullScreen" attribute="disabled"/>
|
||||
</menuitem>
|
||||
#else
|
||||
<menuitem id="fullScreenItem"
|
||||
accesskey="&fullScreenCmd.accesskey;"
|
||||
label="&fullScreenCmd.label;"
|
||||
key="key_fullScreen"
|
||||
type="checkbox"
|
||||
observes="View:FullScreen"/>
|
||||
#endif
|
||||
<menuitem id="menu_readerModeItem"
|
||||
observes="View:ReaderView"
|
||||
hidden="true"/>
|
||||
<menuitem id="menu_showAllTabs"
|
||||
hidden="true"
|
||||
accesskey="&showAllTabsCmd.accesskey;"
|
||||
label="&showAllTabsCmd.label;"
|
||||
command="Browser:ShowAllTabs"
|
||||
key="key_showAllTabs"/>
|
||||
<menuseparator hidden="true" id="documentDirection-separator"/>
|
||||
<menuitem id="documentDirection-swap"
|
||||
hidden="true"
|
||||
label="&bidiSwitchPageDirectionItem.label;"
|
||||
accesskey="&bidiSwitchPageDirectionItem.accesskey;"
|
||||
oncommand="gBrowser.selectedBrowser
|
||||
.messageManager
|
||||
.sendAsyncMessage('SwitchDocumentDirection');"/>
|
||||
</menupopup>
|
||||
</menu>
|
||||
|
||||
<menu id="history-menu"
|
||||
label="&historyMenu.label;"
|
||||
accesskey="&historyMenu.accesskey;">
|
||||
<menupopup id="goPopup"
|
||||
#ifndef XP_MACOSX
|
||||
placespopup="true"
|
||||
#endif
|
||||
oncommand="this.parentNode._placesView._onCommand(event);"
|
||||
onclick="checkForMiddleClick(this, event);"
|
||||
onpopupshowing="if (!this.parentNode._placesView)
|
||||
new HistoryMenu(event);"
|
||||
tooltip="bhTooltip"
|
||||
popupsinherittooltip="true">
|
||||
<menuitem id="menu_showAllHistory"
|
||||
label="&showAllHistoryCmd2.label;"
|
||||
#ifndef XP_MACOSX
|
||||
key="showAllHistoryKb"
|
||||
#endif
|
||||
command="Browser:ShowAllHistory"/>
|
||||
<menuitem id="sanitizeItem"
|
||||
label="&clearRecentHistory.label;"
|
||||
key="key_sanitize"
|
||||
command="Tools:Sanitize"/>
|
||||
<menuseparator id="sanitizeSeparator"/>
|
||||
#ifdef MOZ_SERVICES_SYNC
|
||||
<menuitem id="sync-tabs-menuitem"
|
||||
class="syncTabsMenuItem"
|
||||
label="&syncTabsMenu2.label;"
|
||||
oncommand="BrowserOpenSyncTabs();"
|
||||
disabled="true"/>
|
||||
#endif
|
||||
<menuitem id="historyRestoreLastSession"
|
||||
label="&historyRestoreLastSession.label;"
|
||||
command="Browser:RestoreLastSession"/>
|
||||
<menu id="historyUndoMenu"
|
||||
class="recentlyClosedTabsMenu"
|
||||
label="&historyUndoMenu.label;"
|
||||
disabled="true">
|
||||
<menupopup id="historyUndoPopup"
|
||||
#ifndef XP_MACOSX
|
||||
placespopup="true"
|
||||
#endif
|
||||
onpopupshowing="document.getElementById('history-menu')._placesView.populateUndoSubmenu();"/>
|
||||
</menu>
|
||||
<menu id="historyUndoWindowMenu"
|
||||
class="recentlyClosedWindowsMenu"
|
||||
label="&historyUndoWindowMenu.label;"
|
||||
disabled="true">
|
||||
<menupopup id="historyUndoWindowPopup"
|
||||
#ifndef XP_MACOSX
|
||||
placespopup="true"
|
||||
#endif
|
||||
onpopupshowing="document.getElementById('history-menu')._placesView.populateUndoWindowSubmenu();"/>
|
||||
</menu>
|
||||
<menuseparator id="startHistorySeparator"
|
||||
class="hide-if-empty-places-result"/>
|
||||
</menupopup>
|
||||
</menu>
|
||||
|
||||
<menu id="bookmarksMenu"
|
||||
label="&bookmarksMenu.label;"
|
||||
accesskey="&bookmarksMenu.accesskey;"
|
||||
ondragenter="PlacesMenuDNDHandler.onDragEnter(event);"
|
||||
ondragover="PlacesMenuDNDHandler.onDragOver(event);"
|
||||
ondrop="PlacesMenuDNDHandler.onDrop(event);">
|
||||
<menupopup id="bookmarksMenuPopup"
|
||||
#ifndef XP_MACOSX
|
||||
placespopup="true"
|
||||
#endif
|
||||
context="placesContext"
|
||||
openInTabs="children"
|
||||
oncommand="BookmarksEventHandler.onCommand(event, this.parentNode._placesView);"
|
||||
onclick="BookmarksEventHandler.onClick(event, this.parentNode._placesView);"
|
||||
onpopupshowing="BookmarkingUI.onMainMenuPopupShowing(event);
|
||||
if (!this.parentNode._placesView)
|
||||
new PlacesMenu(event, 'place:folder=BOOKMARKS_MENU');"
|
||||
tooltip="bhTooltip" popupsinherittooltip="true">
|
||||
<menuitem id="bookmarksShowAll"
|
||||
label="&showAllBookmarks2.label;"
|
||||
command="Browser:ShowAllBookmarks"
|
||||
key="manBookmarkKb"/>
|
||||
<menuseparator id="organizeBookmarksSeparator"/>
|
||||
<menuitem id="menu_bookmarkThisPage"
|
||||
command="Browser:AddBookmarkAs"
|
||||
observes="bookmarkThisPageBroadcaster"
|
||||
key="addBookmarkAsKb"/>
|
||||
<menuitem id="subscribeToPageMenuitem"
|
||||
#ifndef XP_MACOSX
|
||||
class="menuitem-iconic"
|
||||
#endif
|
||||
label="&subscribeToPageMenuitem.label;"
|
||||
oncommand="return FeedHandler.subscribeToFeed(null, event);"
|
||||
onclick="checkForMiddleClick(this, event);"
|
||||
observes="singleFeedMenuitemState"/>
|
||||
<menu id="subscribeToPageMenupopup"
|
||||
#ifndef XP_MACOSX
|
||||
class="menu-iconic"
|
||||
#endif
|
||||
label="&subscribeToPageMenupopup.label;"
|
||||
observes="multipleFeedsMenuState">
|
||||
<menupopup id="subscribeToPageSubmenuMenupopup"
|
||||
onpopupshowing="return FeedHandler.buildFeedList(event.target);"
|
||||
oncommand="return FeedHandler.subscribeToFeed(null, event);"
|
||||
onclick="checkForMiddleClick(this, event);"/>
|
||||
</menu>
|
||||
<menuitem id="menu_bookmarkAllTabs"
|
||||
label="&addCurPagesCmd.label;"
|
||||
class="show-only-for-keyboard"
|
||||
command="Browser:BookmarkAllTabs"
|
||||
key="bookmarkAllTabsKb"/>
|
||||
<menuseparator/>
|
||||
<menuitem label="&recentBookmarks.label;"
|
||||
id="menu_recentBookmarks"
|
||||
disabled="true"/>
|
||||
<menuseparator id="bookmarksToolbarSeparator"/>
|
||||
<menu id="bookmarksToolbarFolderMenu"
|
||||
class="menu-iconic bookmark-item"
|
||||
label="&personalbarCmd.label;"
|
||||
container="true">
|
||||
<menupopup id="bookmarksToolbarFolderPopup"
|
||||
#ifndef XP_MACOSX
|
||||
placespopup="true"
|
||||
#endif
|
||||
context="placesContext"
|
||||
onpopupshowing="if (!this.parentNode._placesView)
|
||||
new PlacesMenu(event, 'place:folder=TOOLBAR');"/>
|
||||
</menu>
|
||||
<menu id="menu_unsortedBookmarks"
|
||||
class="menu-iconic bookmark-item"
|
||||
label="&otherBookmarksCmd.label;"
|
||||
container="true">
|
||||
<menupopup id="otherBookmarksFolderPopup"
|
||||
#ifndef XP_MACOSX
|
||||
placespopup="true"
|
||||
#endif
|
||||
context="placesContext"
|
||||
onpopupshowing="if (!this.parentNode._placesView)
|
||||
new PlacesMenu(event, 'place:folder=UNFILED_BOOKMARKS');"/>
|
||||
</menu>
|
||||
<menuseparator id="bookmarksMenuItemsSeparator"/>
|
||||
<!-- Bookmarks menu items -->
|
||||
</menupopup>
|
||||
</menu>
|
||||
|
||||
<menu id="tools-menu"
|
||||
label="&toolsMenu.label;"
|
||||
accesskey="&toolsMenu.accesskey;">
|
||||
<menupopup id="menu_ToolsPopup"
|
||||
#ifdef MOZ_SERVICES_SYNC
|
||||
onpopupshowing="gSyncUI.updateUI();"
|
||||
#endif
|
||||
>
|
||||
<menuitem id="menu_openDownloads"
|
||||
label="&downloads.label;"
|
||||
accesskey="&downloads.accesskey;"
|
||||
key="key_openDownloads"
|
||||
command="Tools:Downloads"/>
|
||||
<menuitem id="menu_openAddons"
|
||||
label="&addons.label;"
|
||||
accesskey="&addons.accesskey;"
|
||||
key="key_openAddons"
|
||||
command="Tools:Addons"/>
|
||||
#ifdef MOZ_SERVICES_SYNC
|
||||
<!-- only one of sync-setup or sync-menu will be showing at once -->
|
||||
<menuitem id="sync-setup"
|
||||
label="&syncSetup.label;"
|
||||
accesskey="&syncSetup.accesskey;"
|
||||
observes="sync-setup-state"
|
||||
oncommand="gSyncUI.openSetup()"/>
|
||||
<menuitem id="sync-syncnowitem"
|
||||
label="&syncSyncNowItem.label;"
|
||||
accesskey="&syncSyncNowItem.accesskey;"
|
||||
observes="sync-syncnow-state"
|
||||
oncommand="gSyncUI.doSync(event);"/>
|
||||
#endif
|
||||
<menuseparator id="devToolsSeparator"/>
|
||||
<menu id="webDeveloperMenu"
|
||||
label="&webDeveloperMenu.label;"
|
||||
accesskey="&webDeveloperMenu.accesskey;">
|
||||
<menupopup id="menuWebDeveloperPopup">
|
||||
<menuitem id="menu_pageSource"
|
||||
observes="devtoolsMenuBroadcaster_PageSource"
|
||||
accesskey="&pageSourceCmd.accesskey;"/>
|
||||
<menuitem id="javascriptConsole"
|
||||
observes="devtoolsMenuBroadcaster_ErrorConsole"
|
||||
accesskey="&errorConsoleCmd.accesskey;"/>
|
||||
</menupopup>
|
||||
</menu>
|
||||
<menuitem id="menu_pageInfo"
|
||||
accesskey="&pageInfoCmd.accesskey;"
|
||||
label="&pageInfoCmd.label;"
|
||||
#ifndef XP_WIN
|
||||
key="key_viewInfo"
|
||||
#endif
|
||||
command="View:PageInfo"/>
|
||||
<menuseparator id="prefSep"/>
|
||||
<menuitem id="menu_preferences"
|
||||
label="&preferencesCmd2.label;"
|
||||
accesskey="&preferencesCmd2.accesskey;"
|
||||
oncommand="openPreferences();"/>
|
||||
</menupopup>
|
||||
</menu>
|
||||
|
||||
#ifdef XP_MACOSX
|
||||
<menu id="windowMenu" />
|
||||
#endif
|
||||
<menu id="helpMenu" />
|
||||
</menubar>
|
||||
2050
browser/base/content/browser-places.js
Normal file
482
browser/base/content/browser-plugins.js
Normal file
|
|
@ -0,0 +1,482 @@
|
|||
/* -*- 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 gPluginHandler = {
|
||||
PREF_SESSION_PERSIST_MINUTES: "plugin.sessionPermissionNow.intervalInMinutes",
|
||||
PREF_PERSISTENT_DAYS: "plugin.persistentPermissionAlways.intervalInDays",
|
||||
MESSAGES: [
|
||||
"PluginContent:ShowClickToPlayNotification",
|
||||
"PluginContent:RemoveNotification",
|
||||
"PluginContent:UpdateHiddenPluginUI",
|
||||
"PluginContent:HideNotificationBar",
|
||||
"PluginContent:InstallSinglePlugin",
|
||||
"PluginContent:ShowPluginCrashedNotification",
|
||||
"PluginContent:LinkClickCallback",
|
||||
],
|
||||
|
||||
init: function () {
|
||||
const mm = window.messageManager;
|
||||
for (let msg of this.MESSAGES) {
|
||||
mm.addMessageListener(msg, this);
|
||||
}
|
||||
window.addEventListener("unload", this);
|
||||
},
|
||||
|
||||
uninit: function () {
|
||||
const mm = window.messageManager;
|
||||
for (let msg of this.MESSAGES) {
|
||||
mm.removeMessageListener(msg, this);
|
||||
}
|
||||
window.removeEventListener("unload", this);
|
||||
},
|
||||
|
||||
handleEvent: function (event) {
|
||||
if (event.type == "unload") {
|
||||
this.uninit();
|
||||
}
|
||||
},
|
||||
|
||||
receiveMessage: function (msg) {
|
||||
switch (msg.name) {
|
||||
case "PluginContent:ShowClickToPlayNotification":
|
||||
this.showClickToPlayNotification(msg.target, msg.data.plugins, msg.data.showNow,
|
||||
msg.principal, msg.data.location);
|
||||
break;
|
||||
case "PluginContent:RemoveNotification":
|
||||
this.removeNotification(msg.target, msg.data.name);
|
||||
break;
|
||||
case "PluginContent:UpdateHiddenPluginUI":
|
||||
this.updateHiddenPluginUI(msg.target, msg.data.haveInsecure, msg.data.actions,
|
||||
msg.principal, msg.data.location);
|
||||
break;
|
||||
case "PluginContent:HideNotificationBar":
|
||||
this.hideNotificationBar(msg.target, msg.data.name);
|
||||
break;
|
||||
case "PluginContent:InstallSinglePlugin":
|
||||
this.installSinglePlugin(msg.data.pluginInfo);
|
||||
break;
|
||||
case "PluginContent:ShowPluginCrashedNotification":
|
||||
this.showPluginCrashedNotification(msg.target, msg.data.messageString,
|
||||
msg.data.pluginID);
|
||||
break;
|
||||
case "PluginContent:LinkClickCallback":
|
||||
switch (msg.data.name) {
|
||||
case "managePlugins":
|
||||
case "openHelpPage":
|
||||
case "openPluginUpdatePage":
|
||||
this[msg.data.name].call(this, msg.data.pluginTag);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
Cu.reportError("gPluginHandler did not expect to handle message " + msg.name);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
// Callback for user clicking on a disabled plugin
|
||||
managePlugins: function () {
|
||||
BrowserOpenAddonsMgr("addons://list/plugin");
|
||||
},
|
||||
|
||||
// Callback for user clicking on the link in a click-to-play plugin
|
||||
// (where the plugin has an update)
|
||||
openPluginUpdatePage: function(pluginTag) {
|
||||
let url = Services.blocklist.getPluginInfoURL(pluginTag);
|
||||
if (!url) {
|
||||
url = Services.blocklist.getPluginBlocklistURL(pluginTag);
|
||||
}
|
||||
openUILinkIn(url, "tab");
|
||||
},
|
||||
|
||||
// Callback for user clicking a "reload page" link
|
||||
reloadPage: function (browser) {
|
||||
browser.reload();
|
||||
},
|
||||
|
||||
// Callback for user clicking the help icon
|
||||
openHelpPage: function () {
|
||||
openHelpLink("plugin-crashed", false);
|
||||
},
|
||||
|
||||
_clickToPlayNotificationEventCallback: function(event) {
|
||||
if (event == "dismissed") {
|
||||
// Once the popup is dismissed, clicking the icon should show the full
|
||||
// list again
|
||||
this.options.primaryPlugin = null;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Called from the plugin doorhanger to set the new permissions for a plugin
|
||||
* and activate plugins if necessary.
|
||||
* aNewState should be either "allownow" "allowalways" or "block"
|
||||
*/
|
||||
_updatePluginPermission: function (aNotification, aPluginInfo, aNewState) {
|
||||
let permission;
|
||||
let expireType;
|
||||
let expireTime;
|
||||
|
||||
// Update the permission manager.
|
||||
// Also update the current state of pluginInfo.fallbackType so that
|
||||
// subsequent opening of the notification shows the current state.
|
||||
switch (aNewState) {
|
||||
case "allownow":
|
||||
permission = Ci.nsIPermissionManager.ALLOW_ACTION;
|
||||
expireType = Ci.nsIPermissionManager.EXPIRE_SESSION;
|
||||
expireTime = Date.now() + Services.prefs.getIntPref(this.PREF_SESSION_PERSIST_MINUTES) * 60 * 1000;
|
||||
aPluginInfo.fallbackType = Ci.nsIObjectLoadingContent.PLUGIN_ACTIVE;
|
||||
break;
|
||||
|
||||
case "allowalways":
|
||||
permission = Ci.nsIPermissionManager.ALLOW_ACTION;
|
||||
expireType = Ci.nsIPermissionManager.EXPIRE_TIME;
|
||||
expireTime = Date.now() +
|
||||
Services.prefs.getIntPref(this.PREF_PERSISTENT_DAYS) * 24 * 60 * 60 * 1000;
|
||||
aPluginInfo.fallbackType = Ci.nsIObjectLoadingContent.PLUGIN_ACTIVE;
|
||||
break;
|
||||
|
||||
case "block":
|
||||
permission = Ci.nsIPermissionManager.PROMPT_ACTION;
|
||||
expireType = Ci.nsIPermissionManager.EXPIRE_NEVER;
|
||||
expireTime = 0;
|
||||
switch (aPluginInfo.blocklistState) {
|
||||
case Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE:
|
||||
aPluginInfo.fallbackType = Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE;
|
||||
break;
|
||||
case Ci.nsIBlocklistService.STATE_VULNERABLE_NO_UPDATE:
|
||||
aPluginInfo.fallbackType = Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE;
|
||||
break;
|
||||
default:
|
||||
aPluginInfo.fallbackType = Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY;
|
||||
}
|
||||
break;
|
||||
|
||||
// In case a plugin has already been allowed in another tab, the "continue allowing" button
|
||||
// shouldn't change any permissions but should run the plugin-enablement code below.
|
||||
case "continue":
|
||||
aPluginInfo.fallbackType = Ci.nsIObjectLoadingContent.PLUGIN_ACTIVE;
|
||||
break;
|
||||
default:
|
||||
Cu.reportError(Error("Unexpected plugin state: " + aNewState));
|
||||
return;
|
||||
}
|
||||
|
||||
let browser = aNotification.browser;
|
||||
if (aNewState != "continue") {
|
||||
let principal = aNotification.options.principal;
|
||||
Services.perms.addFromPrincipal(principal, aPluginInfo.permissionString,
|
||||
permission, expireType, expireTime);
|
||||
aPluginInfo.pluginPermissionType = expireType;
|
||||
}
|
||||
|
||||
browser.messageManager.sendAsyncMessage("BrowserPlugins:ActivatePlugins", {
|
||||
pluginInfo: aPluginInfo,
|
||||
newState: aNewState,
|
||||
});
|
||||
},
|
||||
|
||||
showClickToPlayNotification: function (browser, plugins, showNow,
|
||||
principal, location) {
|
||||
// It is possible that we've received a message from the frame script to show
|
||||
// a click to play notification for a principal that no longer matches the one
|
||||
// that the browser's content now has assigned (ie, the browser has browsed away
|
||||
// after the message was sent, but before the message was received). In that case,
|
||||
// we should just ignore the message.
|
||||
if (!principal.equals(browser.contentPrincipal)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Data URIs, when linked to from some page, inherit the principal of that
|
||||
// page. That means that we also need to compare the actual locations to
|
||||
// ensure we aren't getting a message from a Data URI that we're no longer
|
||||
// looking at.
|
||||
let receivedURI = BrowserUtils.makeURI(location);
|
||||
if (!browser.documentURI.equalsExceptRef(receivedURI)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let notification = PopupNotifications.getNotification("click-to-play-plugins", browser);
|
||||
|
||||
// If this is a new notification, create a pluginData map, otherwise append
|
||||
let pluginData;
|
||||
if (notification) {
|
||||
pluginData = notification.options.pluginData;
|
||||
} else {
|
||||
pluginData = new Map();
|
||||
}
|
||||
|
||||
for (var pluginInfo of plugins) {
|
||||
if (pluginData.has(pluginInfo.permissionString)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If a block contains an infoURL, we should always prefer that to the default
|
||||
// URL that we construct in-product, even for other blocklist types.
|
||||
let url = Services.blocklist.getPluginInfoURL(pluginInfo.pluginTag);
|
||||
|
||||
if (pluginInfo.blocklistState != Ci.nsIBlocklistService.STATE_NOT_BLOCKED) {
|
||||
if (!url) {
|
||||
url = Services.blocklist.getPluginBlocklistURL(pluginInfo.pluginTag);
|
||||
}
|
||||
}
|
||||
else {
|
||||
url = Services.urlFormatter.formatURLPref("app.support.baseURL") + "clicktoplay";
|
||||
}
|
||||
pluginInfo.detailsLink = url;
|
||||
|
||||
pluginData.set(pluginInfo.permissionString, pluginInfo);
|
||||
}
|
||||
|
||||
let primaryPluginPermission = null;
|
||||
if (showNow) {
|
||||
primaryPluginPermission = plugins[0].permissionString;
|
||||
}
|
||||
|
||||
if (notification) {
|
||||
// Don't modify the notification UI while it's on the screen, that would be
|
||||
// jumpy and might allow clickjacking.
|
||||
if (showNow) {
|
||||
notification.options.primaryPlugin = primaryPluginPermission;
|
||||
notification.reshow();
|
||||
browser.messageManager.sendAsyncMessage("BrowserPlugins:NotificationShown");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let options = {
|
||||
dismissed: !showNow,
|
||||
eventCallback: this._clickToPlayNotificationEventCallback,
|
||||
primaryPlugin: primaryPluginPermission,
|
||||
pluginData: pluginData,
|
||||
principal: principal,
|
||||
};
|
||||
PopupNotifications.show(browser, "click-to-play-plugins",
|
||||
"", "plugins-notification-icon",
|
||||
null, null, options);
|
||||
browser.messageManager.sendAsyncMessage("BrowserPlugins:NotificationShown");
|
||||
},
|
||||
|
||||
removeNotification: function (browser, name) {
|
||||
let notification = PopupNotifications.getNotification(name, browser);
|
||||
if (notification)
|
||||
PopupNotifications.remove(notification);
|
||||
},
|
||||
|
||||
hideNotificationBar: function (browser, name) {
|
||||
let notificationBox = gBrowser.getNotificationBox(browser);
|
||||
let notification = notificationBox.getNotificationWithValue(name);
|
||||
if (notification)
|
||||
notificationBox.removeNotification(notification, true);
|
||||
},
|
||||
|
||||
updateHiddenPluginUI: function (browser, haveInsecure, actions,
|
||||
principal, location) {
|
||||
let origin = principal.originNoSuffix;
|
||||
|
||||
// It is possible that we've received a message from the frame script to show
|
||||
// the hidden plugin notification for a principal that no longer matches the one
|
||||
// that the browser's content now has assigned (ie, the browser has browsed away
|
||||
// after the message was sent, but before the message was received). In that case,
|
||||
// we should just ignore the message.
|
||||
if (!principal.equals(browser.contentPrincipal)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Data URIs, when linked to from some page, inherit the principal of that
|
||||
// page. That means that we also need to compare the actual locations to
|
||||
// ensure we aren't getting a message from a Data URI that we're no longer
|
||||
// looking at.
|
||||
let receivedURI = BrowserUtils.makeURI(location);
|
||||
if (!browser.documentURI.equalsExceptRef(receivedURI)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Set up the icon
|
||||
document.getElementById("plugins-notification-icon").classList.
|
||||
toggle("plugin-blocked", haveInsecure);
|
||||
|
||||
// Now configure the notification bar
|
||||
let notificationBox = gBrowser.getNotificationBox(browser);
|
||||
|
||||
function hideNotification() {
|
||||
let n = notificationBox.getNotificationWithValue("plugin-hidden");
|
||||
if (n) {
|
||||
notificationBox.removeNotification(n, true);
|
||||
}
|
||||
}
|
||||
|
||||
// There are three different cases when showing an infobar:
|
||||
// 1. A single type of plugin is hidden on the page. Show the UI for that
|
||||
// plugin.
|
||||
// 2a. Multiple types of plugins are hidden on the page. Show the multi-UI
|
||||
// with the vulnerable styling.
|
||||
// 2b. Multiple types of plugins are hidden on the page, but none are
|
||||
// vulnerable. Show the nonvulnerable multi-UI.
|
||||
function showNotification() {
|
||||
let n = notificationBox.getNotificationWithValue("plugin-hidden");
|
||||
if (n) {
|
||||
// If something is already shown, just keep it
|
||||
return;
|
||||
}
|
||||
|
||||
let message;
|
||||
// Icons set directly cannot be manipulated using moz-image-region, so
|
||||
// we use CSS classes instead.
|
||||
let brand = document.getElementById("bundle_brand").getString("brandShortName");
|
||||
|
||||
if (actions.length == 1) {
|
||||
let pluginInfo = actions[0];
|
||||
let pluginName = pluginInfo.pluginName;
|
||||
|
||||
switch (pluginInfo.fallbackType) {
|
||||
case Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY:
|
||||
message = gNavigatorBundle.getFormattedString(
|
||||
"pluginActivateNew.message",
|
||||
[pluginName, origin]);
|
||||
break;
|
||||
case Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE:
|
||||
message = gNavigatorBundle.getFormattedString(
|
||||
"pluginActivateOutdated.message",
|
||||
[pluginName, origin, brand]);
|
||||
break;
|
||||
case Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE:
|
||||
message = gNavigatorBundle.getFormattedString(
|
||||
"pluginActivateVulnerable.message",
|
||||
[pluginName, origin, brand]);
|
||||
}
|
||||
} else {
|
||||
// Multi-plugin
|
||||
message = gNavigatorBundle.getFormattedString(
|
||||
"pluginActivateMultiple.message", [origin]);
|
||||
}
|
||||
|
||||
let buttons = [
|
||||
{
|
||||
label: gNavigatorBundle.getString("pluginContinueBlocking.label"),
|
||||
accessKey: gNavigatorBundle.getString("pluginContinueBlocking.accesskey"),
|
||||
callback: function() {
|
||||
Services.perms.addFromPrincipal(principal,
|
||||
"plugin-hidden-notification",
|
||||
Services.perms.DENY_ACTION);
|
||||
}
|
||||
},
|
||||
{
|
||||
label: gNavigatorBundle.getString("pluginActivateTrigger.label"),
|
||||
accessKey: gNavigatorBundle.getString("pluginActivateTrigger.accesskey"),
|
||||
callback: function() {
|
||||
let curNotification =
|
||||
PopupNotifications.getNotification("click-to-play-plugins",
|
||||
browser);
|
||||
if (curNotification) {
|
||||
curNotification.reshow();
|
||||
}
|
||||
}
|
||||
}
|
||||
];
|
||||
n = notificationBox.
|
||||
appendNotification(message, "plugin-hidden", null,
|
||||
notificationBox.PRIORITY_INFO_HIGH, buttons);
|
||||
if (haveInsecure) {
|
||||
n.classList.add('pluginVulnerable');
|
||||
}
|
||||
}
|
||||
|
||||
if (actions.length == 0) {
|
||||
hideNotification();
|
||||
} else {
|
||||
let notificationPermission = Services.perms.testPermissionFromPrincipal(
|
||||
principal, "plugin-hidden-notification");
|
||||
if (notificationPermission == Ci.nsIPermissionManager.DENY_ACTION) {
|
||||
hideNotification();
|
||||
} else {
|
||||
showNotification();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
contextMenuCommand: function (browser, plugin, command) {
|
||||
browser.messageManager.sendAsyncMessage("BrowserPlugins:ContextMenuCommand",
|
||||
{ command: command }, { plugin: plugin });
|
||||
},
|
||||
|
||||
// Crashed-plugin observer. Notified once per plugin crash, before events
|
||||
// are dispatched to individual plugin instances.
|
||||
NPAPIPluginCrashed : function(subject, topic, data) {
|
||||
let propertyBag = subject;
|
||||
if (!(propertyBag instanceof Ci.nsIPropertyBag2) ||
|
||||
!(propertyBag instanceof Ci.nsIWritablePropertyBag2) ||
|
||||
!propertyBag.hasKey("runID") ||
|
||||
!propertyBag.hasKey("pluginName")) {
|
||||
Cu.reportError("A NPAPI plugin crashed, but the properties of this plugin " +
|
||||
"cannot be read.");
|
||||
return;
|
||||
}
|
||||
|
||||
let runID = propertyBag.getPropertyAsUint32("runID");
|
||||
let uglyPluginName = propertyBag.getPropertyAsAString("pluginName");
|
||||
let pluginName = BrowserUtils.makeNicePluginName(uglyPluginName);
|
||||
let pluginDumpID = propertyBag.getPropertyAsAString("pluginDumpID");
|
||||
|
||||
// If we don't have a minidumpID, we can't (or didn't) submit anything.
|
||||
// This can happen if the plugin is killed from the task manager.
|
||||
let state = "noSubmit";
|
||||
|
||||
let mm = window.getGroupMessageManager("browsers");
|
||||
mm.broadcastAsyncMessage("BrowserPlugins:NPAPIPluginProcessCrashed",
|
||||
{ pluginName, runID, state });
|
||||
},
|
||||
|
||||
/**
|
||||
* Shows a plugin-crashed notification bar for a browser that has had an
|
||||
* invisiable NPAPI plugin crash, or a GMP plugin crash.
|
||||
*
|
||||
* @param browser
|
||||
* The browser to show the notification for.
|
||||
* @param messageString
|
||||
* The string to put in the notification bar
|
||||
* @param pluginID
|
||||
* The unique-per-process identifier for the NPAPI plugin or GMP.
|
||||
* For a GMP, this is the pluginID. For NPAPI plugins (where "pluginID"
|
||||
* means something different), this is the runID.
|
||||
*/
|
||||
showPluginCrashedNotification: function (browser, messageString, pluginID) {
|
||||
// If there's already an existing notification bar, don't do anything.
|
||||
let notificationBox = gBrowser.getNotificationBox(browser);
|
||||
let notification = notificationBox.getNotificationWithValue("plugin-crashed");
|
||||
if (notification) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Configure the notification bar
|
||||
let priority = notificationBox.PRIORITY_WARNING_MEDIUM;
|
||||
let iconURL = "chrome://mozapps/skin/plugins/notifyPluginCrashed.png";
|
||||
let reloadLabel = gNavigatorBundle.getString("crashedpluginsMessage.reloadButton.label");
|
||||
let reloadKey = gNavigatorBundle.getString("crashedpluginsMessage.reloadButton.accesskey");
|
||||
|
||||
let buttons = [{
|
||||
label: reloadLabel,
|
||||
accessKey: reloadKey,
|
||||
popup: null,
|
||||
callback: function() { browser.reload(); },
|
||||
}];
|
||||
|
||||
notification = notificationBox.appendNotification(messageString, "plugin-crashed",
|
||||
iconURL, priority, buttons);
|
||||
|
||||
// Add the "learn more" link.
|
||||
let XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
|
||||
let link = notification.ownerDocument.createElementNS(XULNS, "label");
|
||||
link.className = "text-link";
|
||||
link.setAttribute("value", gNavigatorBundle.getString("crashedpluginsMessage.learnMore"));
|
||||
let crashurl = formatURL("app.support.baseURL", true);
|
||||
crashurl += "plugin-crashed-notificationbar";
|
||||
link.href = crashurl;
|
||||
let description = notification.ownerDocument.getAnonymousElementByAttribute(notification, "anonid", "messageText");
|
||||
description.appendChild(link);
|
||||
},
|
||||
};
|
||||
|
||||
gPluginHandler.init();
|
||||
84
browser/base/content/browser-refreshblocker.js
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
/**
|
||||
* If the user has opted into blocking refresh and redirect attempts by
|
||||
* default, this handles showing the notification to the user which
|
||||
* gives them the option to let the refresh or redirect proceed.
|
||||
*/
|
||||
var RefreshBlocker = {
|
||||
init() {
|
||||
gBrowser.addEventListener("RefreshBlocked", this);
|
||||
},
|
||||
|
||||
uninit() {
|
||||
gBrowser.removeEventListener("RefreshBlocked", this);
|
||||
},
|
||||
|
||||
handleEvent: function(event) {
|
||||
if (event.type == "RefreshBlocked") {
|
||||
this.block(event.originalTarget, event.detail);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Shows the blocked refresh / redirect notification for some browser.
|
||||
*
|
||||
* @param browser (<xul:browser>)
|
||||
* The browser that had the refresh blocked. This will be the browser
|
||||
* for which we'll show the notification on.
|
||||
* @param data (object)
|
||||
* An object with the following properties:
|
||||
*
|
||||
* URI (string)
|
||||
* The URI that a page is attempting to refresh or redirect to.
|
||||
*
|
||||
* delay (int)
|
||||
* The delay (in milliseconds) before the page was going to reload
|
||||
* or redirect.
|
||||
*
|
||||
* sameURI (bool)
|
||||
* true if we're refreshing the page. false if we're redirecting.
|
||||
*
|
||||
* outerWindowID (int)
|
||||
* The outerWindowID of the frame that requested the refresh or
|
||||
* redirect.
|
||||
*/
|
||||
block(browser, data) {
|
||||
let brandBundle = document.getElementById("bundle_brand");
|
||||
let brandShortName = brandBundle.getString("brandShortName");
|
||||
let message =
|
||||
gNavigatorBundle.getFormattedString(data.sameURI ? "refreshBlocked.refreshLabel"
|
||||
: "refreshBlocked.redirectLabel",
|
||||
[brandShortName]);
|
||||
|
||||
let notificationBox = gBrowser.getNotificationBox(browser);
|
||||
let notification = notificationBox.getNotificationWithValue("refresh-blocked");
|
||||
|
||||
if (notification) {
|
||||
notification.label = message;
|
||||
} else {
|
||||
let refreshButtonText =
|
||||
gNavigatorBundle.getString("refreshBlocked.goButton");
|
||||
let refreshButtonAccesskey =
|
||||
gNavigatorBundle.getString("refreshBlocked.goButton.accesskey");
|
||||
|
||||
let buttons = [{
|
||||
label: refreshButtonText,
|
||||
accessKey: refreshButtonAccesskey,
|
||||
callback: function (notification, button) {
|
||||
if (browser.messageManager) {
|
||||
browser.messageManager.sendAsyncMessage("RefreshBlocker:Refresh", data);
|
||||
}
|
||||
}
|
||||
}];
|
||||
|
||||
notificationBox.appendNotification(message, "refresh-blocked",
|
||||
"chrome://browser/skin/Info.png",
|
||||
notificationBox.PRIORITY_INFO_MEDIUM,
|
||||
buttons);
|
||||
}
|
||||
}
|
||||
};
|
||||
376
browser/base/content/browser-sets.inc
Normal file
|
|
@ -0,0 +1,376 @@
|
|||
# -*- 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/.
|
||||
|
||||
#ifdef XP_UNIX
|
||||
#ifndef XP_MACOSX
|
||||
#define XP_GNOME 1
|
||||
#endif
|
||||
#endif
|
||||
|
||||
<stringbundleset id="stringbundleset">
|
||||
<stringbundle id="bundle_brand" src="chrome://branding/locale/brand.properties"/>
|
||||
<stringbundle id="bundle_shell" src="chrome://browser/locale/shellservice.properties"/>
|
||||
<stringbundle id="bundle_preferences" src="chrome://browser/locale/preferences/preferences.properties"/>
|
||||
</stringbundleset>
|
||||
|
||||
<commandset id="mainCommandSet">
|
||||
<command id="cmd_newNavigator" oncommand="OpenBrowserWindow()" reserved="true"/>
|
||||
<command id="cmd_handleBackspace" oncommand="BrowserHandleBackspace();" />
|
||||
<command id="cmd_handleShiftBackspace" oncommand="BrowserHandleShiftBackspace();" />
|
||||
|
||||
<command id="cmd_newNavigatorTab" oncommand="BrowserOpenTab(event);" reserved="true"/>
|
||||
<command id="cmd_newNavigatorTabNoEvent" oncommand="BrowserOpenTab();" reserved="true"/>
|
||||
<command id="Browser:OpenFile" oncommand="BrowserOpenFileWindow();"/>
|
||||
<command id="Browser:SavePage" oncommand="saveBrowser(gBrowser.selectedBrowser);"/>
|
||||
|
||||
<command id="Browser:SendLink"
|
||||
oncommand="MailIntegration.sendLinkForBrowser(gBrowser.selectedBrowser);"/>
|
||||
|
||||
<command id="cmd_pageSetup" oncommand="PrintUtils.showPageSetup();"/>
|
||||
<command id="cmd_print" oncommand="PrintUtils.printWindow(window.gBrowser.selectedBrowser.outerWindowID, window.gBrowser.selectedBrowser);"/>
|
||||
<command id="cmd_printPreview" oncommand="PrintUtils.printPreview(PrintPreviewListener);"/>
|
||||
<command id="cmd_close" oncommand="BrowserCloseTabOrWindow()" reserved="true"/>
|
||||
<command id="cmd_closeWindow" oncommand="BrowserTryToCloseWindow()" reserved="true"/>
|
||||
<command id="cmd_toggleMute" oncommand="gBrowser.selectedTab.toggleMuteAudio()"/>
|
||||
<command id="cmd_CustomizeToolbars" oncommand="BrowserCustomizeToolbar()"/>
|
||||
<command id="cmd_quitApplication" oncommand="goQuitApplication()" reserved="true"/>
|
||||
|
||||
|
||||
<commandset id="editMenuCommands"/>
|
||||
|
||||
<command id="View:PageSource" oncommand="BrowserViewSource(window.gBrowser.selectedBrowser);" observes="canViewSource"/>
|
||||
<command id="View:PageInfo" oncommand="BrowserPageInfo();"/>
|
||||
<command id="View:FullScreen" oncommand="BrowserFullScreen();"/>
|
||||
<command id="View:ReaderView" oncommand="ReaderParent.toggleReaderMode(event);"/>
|
||||
<command id="cmd_find"
|
||||
oncommand="gFindBar.onFindCommand();"
|
||||
observes="isImage"/>
|
||||
<command id="cmd_findAgain"
|
||||
oncommand="gFindBar.onFindAgainCommand(false);"
|
||||
observes="isImage"/>
|
||||
<command id="cmd_findPrevious"
|
||||
oncommand="gFindBar.onFindAgainCommand(true);"
|
||||
observes="isImage"/>
|
||||
#ifdef XP_MACOSX
|
||||
<command id="cmd_findSelection" oncommand="gFindBar.onFindSelectionCommand();"/>
|
||||
#endif
|
||||
<!-- work-around bug 392512 -->
|
||||
<command id="Browser:AddBookmarkAs"
|
||||
oncommand="PlacesCommandHook.bookmarkCurrentPage(true, PlacesUtils.bookmarksMenuFolderId);"/>
|
||||
<!-- The command disabled state must be manually updated through
|
||||
PlacesCommandHook.updateBookmarkAllTabsCommand() -->
|
||||
<command id="Browser:BookmarkAllTabs"
|
||||
oncommand="PlacesCommandHook.bookmarkCurrentPages();"/>
|
||||
<command id="Browser:Home" oncommand="BrowserHome();"/>
|
||||
<command id="Browser:Back" oncommand="BrowserBack();" disabled="true"/>
|
||||
<command id="Browser:BackOrBackDuplicate" oncommand="BrowserBack(event);" disabled="true">
|
||||
<observes element="Browser:Back" attribute="disabled"/>
|
||||
</command>
|
||||
<command id="Browser:Forward" oncommand="BrowserForward();" disabled="true"/>
|
||||
<command id="Browser:ForwardOrForwardDuplicate" oncommand="BrowserForward(event);" disabled="true">
|
||||
<observes element="Browser:Forward" attribute="disabled"/>
|
||||
</command>
|
||||
<command id="Browser:Stop" oncommand="BrowserStop();" disabled="true"/>
|
||||
<command id="Browser:Reload" oncommand="if (event.shiftKey) BrowserReloadSkipCache(); else BrowserReload()" disabled="true"/>
|
||||
<command id="Browser:ReloadOrDuplicate" oncommand="BrowserReloadOrDuplicate(event)" disabled="true">
|
||||
<observes element="Browser:Reload" attribute="disabled"/>
|
||||
</command>
|
||||
<command id="Browser:ReloadSkipCache" oncommand="BrowserReloadSkipCache()" disabled="true">
|
||||
<observes element="Browser:Reload" attribute="disabled"/>
|
||||
</command>
|
||||
<command id="Browser:NextTab" oncommand="gBrowser.tabContainer.advanceSelectedTab(1, true);" reserved="true"/>
|
||||
<command id="Browser:PrevTab" oncommand="gBrowser.tabContainer.advanceSelectedTab(-1, true);" reserved="true"/>
|
||||
<command id="Browser:ShowAllTabs" oncommand="allTabs.open();"/>
|
||||
<command id="cmd_fullZoomReduce" oncommand="FullZoom.reduce()"/>
|
||||
<command id="cmd_fullZoomEnlarge" oncommand="FullZoom.enlarge()"/>
|
||||
<command id="cmd_fullZoomReset" oncommand="FullZoom.reset()"/>
|
||||
<command id="cmd_fullZoomToggle" oncommand="ZoomManager.toggleZoom();"/>
|
||||
<command id="cmd_gestureRotateLeft" oncommand="gGestureSupport.rotate(event.sourceEvent)"/>
|
||||
<command id="cmd_gestureRotateRight" oncommand="gGestureSupport.rotate(event.sourceEvent)"/>
|
||||
<command id="cmd_gestureRotateEnd" oncommand="gGestureSupport.rotateEnd()"/>
|
||||
<command id="Browser:OpenLocation" oncommand="openLocation();"/>
|
||||
<command id="Browser:RestoreLastSession" oncommand="restoreLastSession();" disabled="true"/>
|
||||
<command id="Browser:NewUserContextTab" oncommand="openNewUserContextTab(event.sourceEvent);" reserved="true"/>
|
||||
<command id="Browser:OpenAboutContainers" oncommand="openPreferences('paneContainers');"/>
|
||||
|
||||
<command id="Tools:Search" oncommand="BrowserSearch.webSearch();"/>
|
||||
<command id="Tools:Downloads" oncommand="BrowserDownloadsUI();"/>
|
||||
<command id="Tools:Addons" oncommand="BrowserOpenAddonsMgr();"/>
|
||||
<command id="Tools:ErrorConsole" oncommand="toJavaScriptConsole()"/>
|
||||
<command id="Tools:Sanitize"
|
||||
oncommand="Cc['@mozilla.org/browser/browserglue;1'].getService(Ci.nsIBrowserGlue).sanitize(window);"/>
|
||||
<command id="Tools:PrivateBrowsing"
|
||||
oncommand="OpenBrowserWindow({private: true});" reserved="true"/>
|
||||
<command id="History:UndoCloseTab" oncommand="undoCloseTab();"/>
|
||||
<command id="History:UndoCloseWindow" oncommand="undoCloseWindow();"/>
|
||||
</commandset>
|
||||
|
||||
<commandset id="placesCommands">
|
||||
<command id="Browser:ShowAllBookmarks"
|
||||
oncommand="PlacesCommandHook.showPlacesOrganizer('UnfiledBookmarks');"/>
|
||||
<command id="Browser:ShowAllHistory"
|
||||
oncommand="PlacesCommandHook.showPlacesOrganizer('History');"/>
|
||||
</commandset>
|
||||
|
||||
<broadcasterset id="mainBroadcasterSet">
|
||||
<broadcaster id="viewBookmarksSidebar" autoCheck="false" label="&bookmarksButton.label;"
|
||||
type="checkbox" group="sidebar" sidebarurl="chrome://browser/content/bookmarks/bookmarksPanel.xul"
|
||||
oncommand="SidebarUI.toggle('viewBookmarksSidebar');"/>
|
||||
|
||||
<!-- for both places and non-places, the sidebar lives at
|
||||
chrome://browser/content/history/history-panel.xul so there are no
|
||||
problems when switching between versions -->
|
||||
<broadcaster id="viewHistorySidebar" autoCheck="false" sidebartitle="&historyButton.label;"
|
||||
type="checkbox" group="sidebar"
|
||||
sidebarurl="chrome://browser/content/history/history-panel.xul"
|
||||
oncommand="SidebarUI.toggle('viewHistorySidebar');"/>
|
||||
|
||||
<broadcaster id="viewWebPanelsSidebar" autoCheck="false"
|
||||
type="checkbox" group="sidebar" sidebarurl="chrome://browser/content/web-panels.xul"
|
||||
oncommand="SidebarUI.toggle('viewWebPanelsSidebar');"/>
|
||||
|
||||
<broadcaster id="bookmarkThisPageBroadcaster"
|
||||
label="&bookmarkThisPageCmd.label;"
|
||||
bookmarklabel="&bookmarkThisPageCmd.label;"
|
||||
editlabel="&editThisBookmarkCmd.label;"/>
|
||||
|
||||
<!-- popup blocking menu items -->
|
||||
<broadcaster id="blockedPopupAllowSite"
|
||||
accesskey="&allowPopups.accesskey;"
|
||||
oncommand="gPopupBlockerObserver.toggleAllowPopupsForSite(event);"/>
|
||||
<broadcaster id="blockedPopupEditSettings"
|
||||
#ifdef XP_WIN
|
||||
label="&editPopupSettings.label;"
|
||||
#else
|
||||
label="&editPopupSettingsUnix.label;"
|
||||
#endif
|
||||
accesskey="&editPopupSettings.accesskey;"
|
||||
oncommand="gPopupBlockerObserver.editPopupSettings();"/>
|
||||
<broadcaster id="blockedPopupDontShowMessage"
|
||||
accesskey="&dontShowMessage.accesskey;"
|
||||
type="checkbox"
|
||||
oncommand="gPopupBlockerObserver.dontShowMessage();"/>
|
||||
<broadcaster id="blockedPopupsSeparator"/>
|
||||
<broadcaster id="isImage"/>
|
||||
<broadcaster id="canViewSource"/>
|
||||
<broadcaster id="isFrameImage"/>
|
||||
<broadcaster id="singleFeedMenuitemState" disabled="true"/>
|
||||
<broadcaster id="multipleFeedsMenuState" hidden="true"/>
|
||||
|
||||
<!-- Sync broadcasters -->
|
||||
<!-- A broadcaster of a number of attributes suitable for "sync now" UI -
|
||||
A 'syncstatus' attribute is set while actively syncing, and the label
|
||||
attribute which changes from "sync now" to "syncing" etc. -->
|
||||
#ifdef MOZ_SERVICES_SYNC
|
||||
<broadcaster id="sync-status"/>
|
||||
<!-- broadcasters of the "hidden" attribute to reflect setup state for
|
||||
menus -->
|
||||
<broadcaster id="sync-setup-state"/>
|
||||
<broadcaster id="sync-syncnow-state" hidden="true"/>
|
||||
#endif
|
||||
<broadcaster id="workOfflineMenuitemState"/>
|
||||
|
||||
<broadcaster id="devtoolsMenuBroadcaster_ErrorConsole"
|
||||
label="&errorConsoleCmd.label;"
|
||||
command="Tools:ErrorConsole"/>
|
||||
|
||||
<broadcaster id="devtoolsMenuBroadcaster_PageSource"
|
||||
label="&pageSourceCmd.label;"
|
||||
key="key_viewSource"
|
||||
command="View:PageSource">
|
||||
<observes element="canViewSource" attribute="disabled"/>
|
||||
|
||||
</broadcaster>
|
||||
</broadcasterset>
|
||||
|
||||
<keyset id="mainKeyset">
|
||||
<key id="key_newNavigator"
|
||||
key="&newNavigatorCmd.key;"
|
||||
command="cmd_newNavigator"
|
||||
modifiers="accel"/>
|
||||
<key id="key_newNavigatorTab" key="&tabCmd.commandkey;" modifiers="accel" command="cmd_newNavigatorTabNoEvent"/>
|
||||
<key id="focusURLBar" key="&openCmd.commandkey;" command="Browser:OpenLocation"
|
||||
modifiers="accel"/>
|
||||
#ifndef XP_MACOSX
|
||||
<key id="focusURLBar2" key="&urlbar.accesskey;" command="Browser:OpenLocation"
|
||||
modifiers="alt"/>
|
||||
#endif
|
||||
|
||||
#
|
||||
# Search Command Key Logic works like this:
|
||||
#
|
||||
# Unix: Ctrl+K (cross platform binding)
|
||||
# Ctrl+J (in case of emacs Ctrl-K conflict)
|
||||
# Mac: Cmd+K (cross platform binding)
|
||||
# Cmd+Opt+F (platform convention)
|
||||
# Win: Ctrl+K (cross platform binding)
|
||||
# Ctrl+E (IE compat)
|
||||
#
|
||||
# We support Ctrl+K on all platforms now and advertise it in the menu since it is
|
||||
# our standard - it is a "safe" choice since it is near no harmful keys like "W" as
|
||||
# "E" is. People mourning the loss of Ctrl+K for emacs compat can switch their GTK
|
||||
# system setting to use emacs emulation, and we should respect it. Focus-Search-Box
|
||||
# is a fundamental keybinding and we are maintaining a XP binding so that it is easy
|
||||
# for people to switch to Linux.
|
||||
#
|
||||
<key id="key_search" key="&searchFocus.commandkey;" command="Tools:Search" modifiers="accel"/>
|
||||
#ifdef XP_MACOSX
|
||||
<key id="key_search2" key="&findOnCmd.commandkey;" command="Tools:Search" modifiers="accel,alt"/>
|
||||
#endif
|
||||
#ifdef XP_WIN
|
||||
<key id="key_search2" key="&searchFocus.commandkey2;" command="Tools:Search" modifiers="accel"/>
|
||||
#endif
|
||||
#ifdef XP_GNOME
|
||||
<key id="key_search2" key="&searchFocusUnix.commandkey;" command="Tools:Search" modifiers="accel"/>
|
||||
<key id="key_openDownloads" key="&downloadsUnix.commandkey;" command="Tools:Downloads" modifiers="accel,shift"/>
|
||||
#else
|
||||
<key id="key_openDownloads" key="&downloads.commandkey;" command="Tools:Downloads" modifiers="accel"/>
|
||||
#endif
|
||||
<key id="key_openAddons" key="&addons.commandkey;" command="Tools:Addons" modifiers="accel,shift"/>
|
||||
<key id="openFileKb" key="&openFileCmd.commandkey;" command="Browser:OpenFile" modifiers="accel"/>
|
||||
<key id="key_savePage" key="&savePageCmd.commandkey;" command="Browser:SavePage" modifiers="accel"/>
|
||||
<key id="printKb" key="&printCmd.commandkey;" command="cmd_print" modifiers="accel"/>
|
||||
<key id="key_close" key="&closeCmd.key;" command="cmd_close" modifiers="accel"/>
|
||||
<key id="key_closeWindow" key="&closeCmd.key;" command="cmd_closeWindow" modifiers="accel,shift"/>
|
||||
<key id="key_toggleMute" key="&toggleMuteCmd.key;" command="cmd_toggleMute" modifiers="control"/>
|
||||
<key id="key_undo"
|
||||
key="&undoCmd.key;"
|
||||
modifiers="accel"/>
|
||||
#ifdef XP_UNIX
|
||||
<key id="key_redo" key="&undoCmd.key;" modifiers="accel,shift"/>
|
||||
#else
|
||||
<key id="key_redo" key="&redoCmd.key;" modifiers="accel"/>
|
||||
#endif
|
||||
<key id="key_cut"
|
||||
key="&cutCmd.key;"
|
||||
modifiers="accel"/>
|
||||
<key id="key_copy"
|
||||
key="©Cmd.key;"
|
||||
modifiers="accel"/>
|
||||
<key id="key_paste"
|
||||
key="&pasteCmd.key;"
|
||||
modifiers="accel"/>
|
||||
<key id="key_delete" keycode="VK_DELETE" command="cmd_delete"/>
|
||||
<key id="key_selectAll" key="&selectAllCmd.key;" modifiers="accel"/>
|
||||
|
||||
<key id="key_handleBackspace" keycode="VK_BACK" command="cmd_handleBackspace"/>
|
||||
<key id="key_handleShiftBackspace" keycode="VK_BACK" command="cmd_handleShiftBackspace" modifiers="shift"/>
|
||||
#ifndef XP_MACOSX
|
||||
<key id="goBackKb" keycode="VK_LEFT" command="Browser:Back" modifiers="alt"/>
|
||||
<key id="goForwardKb" keycode="VK_RIGHT" command="Browser:Forward" modifiers="alt"/>
|
||||
#else
|
||||
<key id="goBackKb" keycode="VK_LEFT" command="Browser:Back" modifiers="accel" />
|
||||
<key id="goForwardKb" keycode="VK_RIGHT" command="Browser:Forward" modifiers="accel" />
|
||||
#endif
|
||||
#ifdef XP_UNIX
|
||||
<key id="goBackKb2" key="&goBackCmd.commandKey;" command="Browser:Back" modifiers="accel"/>
|
||||
<key id="goForwardKb2" key="&goForwardCmd.commandKey;" command="Browser:Forward" modifiers="accel"/>
|
||||
#endif
|
||||
<key id="goHome" keycode="VK_HOME" command="Browser:Home" modifiers="alt"/>
|
||||
<key keycode="VK_F5" command="Browser:Reload"/>
|
||||
#ifndef XP_MACOSX
|
||||
<key id="showAllHistoryKb" key="&showAllHistoryCmd.commandkey;" command="Browser:ShowAllHistory" modifiers="accel,shift"/>
|
||||
<key keycode="VK_F5" command="Browser:ReloadSkipCache" modifiers="accel"/>
|
||||
<key id="key_fullScreen" keycode="VK_F11" command="View:FullScreen"/>
|
||||
#else
|
||||
<key id="key_fullScreen" key="&fullScreenCmd.macCommandKey;" command="View:FullScreen" modifiers="accel,control"/>
|
||||
<key id="key_fullScreen_old" key="&fullScreenCmd.macCommandKey;" command="View:FullScreen" modifiers="accel,shift"/>
|
||||
<key keycode="VK_F11" command="View:FullScreen"/>
|
||||
#endif
|
||||
<key id="key_toggleReaderMode" key="&toggleReaderMode.key;" command="View:ReaderView" modifiers="accel,alt" disabled="true"/>
|
||||
<key key="&reloadCmd.commandkey;" command="Browser:Reload" modifiers="accel" id="key_reload"/>
|
||||
<key key="&reloadCmd.commandkey;" command="Browser:ReloadSkipCache" modifiers="accel,shift"/>
|
||||
<key id="key_viewSource" key="&pageSourceCmd.commandkey;" command="View:PageSource" modifiers="accel"/>
|
||||
#ifndef XP_WIN
|
||||
<key id="key_viewInfo" key="&pageInfoCmd.commandkey;" command="View:PageInfo" modifiers="accel"/>
|
||||
#endif
|
||||
<key id="key_find" key="&findOnCmd.commandkey;" command="cmd_find" modifiers="accel"/>
|
||||
<key id="key_findAgain" key="&findAgainCmd.commandkey;" command="cmd_findAgain" modifiers="accel"/>
|
||||
<key id="key_findPrevious" key="&findAgainCmd.commandkey;" command="cmd_findPrevious" modifiers="accel,shift"/>
|
||||
#ifdef XP_MACOSX
|
||||
<key id="key_findSelection" key="&findSelectionCmd.commandkey;" command="cmd_findSelection" modifiers="accel"/>
|
||||
#endif
|
||||
<key keycode="&findAgainCmd.commandkey2;" command="cmd_findAgain"/>
|
||||
<key keycode="&findAgainCmd.commandkey2;" command="cmd_findPrevious" modifiers="shift"/>
|
||||
|
||||
<key id="addBookmarkAsKb" key="&bookmarkThisPageCmd.commandkey;" command="Browser:AddBookmarkAs" modifiers="accel"/>
|
||||
# Accel+Shift+A-F are reserved on GTK
|
||||
#ifndef MOZ_WIDGET_GTK
|
||||
<key id="bookmarkAllTabsKb" key="&bookmarkThisPageCmd.commandkey;" oncommand="PlacesCommandHook.bookmarkCurrentPages();" modifiers="accel,shift"/>
|
||||
<key id="manBookmarkKb" key="&bookmarksCmd.commandkey;" command="Browser:ShowAllBookmarks" modifiers="accel,shift"/>
|
||||
#else
|
||||
<key id="manBookmarkKb" key="&bookmarksGtkCmd.commandkey;" command="Browser:ShowAllBookmarks" modifiers="accel,shift"/>
|
||||
#endif
|
||||
<key id="viewBookmarksSidebarKb" key="&bookmarksCmd.commandkey;" command="viewBookmarksSidebar" modifiers="accel"/>
|
||||
#ifdef XP_WIN
|
||||
# Cmd+I is conventially mapped to Info on MacOS X, thus it should not be
|
||||
# overridden for other purposes there.
|
||||
<key id="viewBookmarksSidebarWinKb" key="&bookmarksWinCmd.commandkey;" command="viewBookmarksSidebar" modifiers="accel"/>
|
||||
#endif
|
||||
|
||||
<key id="key_stop" keycode="VK_ESCAPE" command="Browser:Stop"/>
|
||||
|
||||
#ifdef XP_MACOSX
|
||||
<key id="key_stop_mac" modifiers="accel" key="&stopCmd.macCommandKey;" command="Browser:Stop"/>
|
||||
#endif
|
||||
|
||||
<key id="key_gotoHistory"
|
||||
key="&historySidebarCmd.commandKey;"
|
||||
#ifdef XP_MACOSX
|
||||
modifiers="accel,shift"
|
||||
#else
|
||||
modifiers="accel"
|
||||
#endif
|
||||
command="viewHistorySidebar"/>
|
||||
|
||||
<key id="key_fullZoomReduce" key="&fullZoomReduceCmd.commandkey;" command="cmd_fullZoomReduce" modifiers="accel"/>
|
||||
<key key="&fullZoomReduceCmd.commandkey2;" command="cmd_fullZoomReduce" modifiers="accel"/>
|
||||
<key id="key_fullZoomEnlarge" key="&fullZoomEnlargeCmd.commandkey;" command="cmd_fullZoomEnlarge" modifiers="accel"/>
|
||||
<key key="&fullZoomEnlargeCmd.commandkey2;" command="cmd_fullZoomEnlarge" modifiers="accel"/>
|
||||
<key key="&fullZoomEnlargeCmd.commandkey3;" command="cmd_fullZoomEnlarge" modifiers="accel"/>
|
||||
<key id="key_fullZoomReset" key="&fullZoomResetCmd.commandkey;" command="cmd_fullZoomReset" modifiers="accel"/>
|
||||
<key key="&fullZoomResetCmd.commandkey2;" command="cmd_fullZoomReset" modifiers="accel"/>
|
||||
|
||||
<key id="key_showAllTabs" command="Browser:ShowAllTabs" keycode="VK_TAB" modifiers="control,shift"/>
|
||||
|
||||
<key id="key_switchTextDirection" key="&bidiSwitchTextDirectionItem.commandkey;" command="cmd_switchTextDirection" modifiers="accel,shift" />
|
||||
|
||||
<key id="key_privatebrowsing" command="Tools:PrivateBrowsing" key="&privateBrowsingCmd.commandkey;" modifiers="accel,shift"/>
|
||||
<key id="key_sanitize" command="Tools:Sanitize" keycode="VK_DELETE" modifiers="accel,shift"/>
|
||||
#ifdef XP_MACOSX
|
||||
<key id="key_sanitize_mac" command="Tools:Sanitize" keycode="VK_BACK" modifiers="accel,shift"/>
|
||||
<key id="key_quitApplication" key="&quitApplicationCmdUnix.key;" modifiers="accel" reserved="true"/>
|
||||
#elifdef XP_UNIX
|
||||
<key id="key_quitApplication" key="&quitApplicationCmdUnix.key;" command="cmd_quitApplication" modifiers="accel"/>
|
||||
#endif
|
||||
|
||||
#ifdef FULL_BROWSER_WINDOW
|
||||
<key id="key_undoCloseTab" command="History:UndoCloseTab" key="&tabCmd.commandkey;" modifiers="accel,shift"/>
|
||||
#endif
|
||||
<key id="key_undoCloseWindow" command="History:UndoCloseWindow" key="&newNavigatorCmd.key;" modifiers="accel,shift"/>
|
||||
|
||||
#ifdef XP_GNOME
|
||||
#define NUM_SELECT_TAB_MODIFIER alt
|
||||
#else
|
||||
#define NUM_SELECT_TAB_MODIFIER accel
|
||||
#endif
|
||||
|
||||
#expand <key id="key_selectTab1" oncommand="gBrowser.selectTabAtIndex(0, event);" key="1" modifiers="__NUM_SELECT_TAB_MODIFIER__"/>
|
||||
#expand <key id="key_selectTab2" oncommand="gBrowser.selectTabAtIndex(1, event);" key="2" modifiers="__NUM_SELECT_TAB_MODIFIER__"/>
|
||||
#expand <key id="key_selectTab3" oncommand="gBrowser.selectTabAtIndex(2, event);" key="3" modifiers="__NUM_SELECT_TAB_MODIFIER__"/>
|
||||
#expand <key id="key_selectTab4" oncommand="gBrowser.selectTabAtIndex(3, event);" key="4" modifiers="__NUM_SELECT_TAB_MODIFIER__"/>
|
||||
#expand <key id="key_selectTab5" oncommand="gBrowser.selectTabAtIndex(4, event);" key="5" modifiers="__NUM_SELECT_TAB_MODIFIER__"/>
|
||||
#expand <key id="key_selectTab6" oncommand="gBrowser.selectTabAtIndex(5, event);" key="6" modifiers="__NUM_SELECT_TAB_MODIFIER__"/>
|
||||
#expand <key id="key_selectTab7" oncommand="gBrowser.selectTabAtIndex(6, event);" key="7" modifiers="__NUM_SELECT_TAB_MODIFIER__"/>
|
||||
#expand <key id="key_selectTab8" oncommand="gBrowser.selectTabAtIndex(7, event);" key="8" modifiers="__NUM_SELECT_TAB_MODIFIER__"/>
|
||||
#expand <key id="key_selectLastTab" oncommand="gBrowser.selectTabAtIndex(-1, event);" key="9" modifiers="__NUM_SELECT_TAB_MODIFIER__"/>
|
||||
|
||||
</keyset>
|
||||
|
||||
# Used by baseMenuOverlay
|
||||
#ifdef XP_MACOSX
|
||||
<commandset id="baseMenuCommandSet" />
|
||||
#endif
|
||||
<keyset id="baseMenuKeyset" />
|
||||
331
browser/base/content/browser-sidebar.js
Normal file
|
|
@ -0,0 +1,331 @@
|
|||
/* 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/. */
|
||||
|
||||
/**
|
||||
* SidebarUI controls showing and hiding the browser sidebar.
|
||||
*
|
||||
* @note
|
||||
* Some of these methods take a commandID argument - we expect to find a
|
||||
* xul:broadcaster element with the specified ID.
|
||||
* The following attributes on that element may be used and/or modified:
|
||||
* - id (required) the string to match commandID. The convention
|
||||
* is to use this naming scheme: 'view<sidebar-name>Sidebar'.
|
||||
* - sidebarurl (required) specifies the URL to load in this sidebar.
|
||||
* - sidebartitle or label (in that order) specify the title to
|
||||
* display on the sidebar.
|
||||
* - checked indicates whether the sidebar is currently displayed.
|
||||
* Note that toggleSidebar updates this attribute when
|
||||
* it changes the sidebar's visibility.
|
||||
* - group this attribute must be set to "sidebar".
|
||||
*/
|
||||
var SidebarUI = {
|
||||
browser: null,
|
||||
|
||||
_box: null,
|
||||
_title: null,
|
||||
_splitter: null,
|
||||
|
||||
init() {
|
||||
this._box = document.getElementById("sidebar-box");
|
||||
this.browser = document.getElementById("sidebar");
|
||||
this._title = document.getElementById("sidebar-title");
|
||||
this._splitter = document.getElementById("sidebar-splitter");
|
||||
|
||||
if (!this.adoptFromWindow(window.opener)) {
|
||||
let commandID = this._box.getAttribute("sidebarcommand");
|
||||
if (commandID) {
|
||||
let command = document.getElementById(commandID);
|
||||
if (command) {
|
||||
this._delayedLoad = true;
|
||||
this._box.hidden = false;
|
||||
this._splitter.hidden = false;
|
||||
command.setAttribute("checked", "true");
|
||||
} else {
|
||||
// Remove the |sidebarcommand| attribute, because the element it
|
||||
// refers to no longer exists, so we should assume this sidebar
|
||||
// panel has been uninstalled. (249883)
|
||||
this._box.removeAttribute("sidebarcommand");
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
uninit() {
|
||||
let enumerator = Services.wm.getEnumerator(null);
|
||||
enumerator.getNext();
|
||||
if (!enumerator.hasMoreElements()) {
|
||||
document.persist("sidebar-box", "sidebarcommand");
|
||||
document.persist("sidebar-box", "width");
|
||||
document.persist("sidebar-box", "src");
|
||||
document.persist("sidebar-title", "value");
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Try and adopt the status of the sidebar from another window.
|
||||
* @param {Window} sourceWindow - Window to use as a source for sidebar status.
|
||||
* @return true if we adopted the state, or false if the caller should
|
||||
* initialize the state itself.
|
||||
*/
|
||||
adoptFromWindow(sourceWindow) {
|
||||
// No source window, or it being closed, or not chrome, or in a different
|
||||
// private-browsing context means we can't adopt.
|
||||
if (!sourceWindow || sourceWindow.closed ||
|
||||
!sourceWindow.document.documentURIObject.schemeIs("chrome") ||
|
||||
PrivateBrowsingUtils.isWindowPrivate(window) != PrivateBrowsingUtils.isWindowPrivate(sourceWindow)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the opener had a sidebar, open the same sidebar in our window.
|
||||
// The opener can be the hidden window too, if we're coming from the state
|
||||
// where no windows are open, and the hidden window has no sidebar box.
|
||||
let sourceUI = sourceWindow.SidebarUI;
|
||||
if (!sourceUI || !sourceUI._box) {
|
||||
// no source UI or no _box means we also can't adopt the state.
|
||||
return false;
|
||||
}
|
||||
if (sourceUI._box.hidden) {
|
||||
// just hidden means we have adopted the hidden state.
|
||||
return true;
|
||||
}
|
||||
|
||||
let commandID = sourceUI._box.getAttribute("sidebarcommand");
|
||||
let commandElem = document.getElementById(commandID);
|
||||
|
||||
// dynamically generated sidebars will fail this check, but we still
|
||||
// consider it adopted.
|
||||
if (!commandElem) {
|
||||
return true;
|
||||
}
|
||||
|
||||
this._title.setAttribute("value",
|
||||
sourceUI._title.getAttribute("value"));
|
||||
this._box.setAttribute("width", sourceUI._box.boxObject.width);
|
||||
|
||||
this._box.setAttribute("sidebarcommand", commandID);
|
||||
// Note: we're setting 'src' on this._box, which is a <vbox>, not on
|
||||
// the <browser id="sidebar">. This lets us delay the actual load until
|
||||
// delayedStartup().
|
||||
this._box.setAttribute("src", sourceUI.browser.getAttribute("src"));
|
||||
this._delayedLoad = true;
|
||||
|
||||
this._box.hidden = false;
|
||||
this._splitter.hidden = false;
|
||||
commandElem.setAttribute("checked", "true");
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* If loading a sidebar was delayed on startup, start the load now.
|
||||
*/
|
||||
startDelayedLoad() {
|
||||
if (!this._delayedLoad) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.browser.setAttribute("src", this._box.getAttribute("src"));
|
||||
},
|
||||
|
||||
/**
|
||||
* Fire a "SidebarFocused" event on the sidebar's |window| to give the sidebar
|
||||
* a chance to adjust focus as needed. An additional event is needed, because
|
||||
* we don't want to focus the sidebar when it's opened on startup or in a new
|
||||
* window, only when the user opens the sidebar.
|
||||
*/
|
||||
_fireFocusedEvent() {
|
||||
let event = new CustomEvent("SidebarFocused", {bubbles: true});
|
||||
this.browser.contentWindow.dispatchEvent(event);
|
||||
|
||||
// Run the original function for backwards compatibility.
|
||||
fireSidebarFocusedEvent();
|
||||
},
|
||||
|
||||
/**
|
||||
* True if the sidebar is currently open.
|
||||
*/
|
||||
get isOpen() {
|
||||
return !this._box.hidden;
|
||||
},
|
||||
|
||||
/**
|
||||
* The ID of the current sidebar (ie, the ID of the broadcaster being used).
|
||||
* This can be set even if the sidebar is hidden.
|
||||
*/
|
||||
get currentID() {
|
||||
return this._box.getAttribute("sidebarcommand");
|
||||
},
|
||||
|
||||
get title() {
|
||||
return this._title.value;
|
||||
},
|
||||
|
||||
set title(value) {
|
||||
this._title.value = value;
|
||||
},
|
||||
|
||||
/**
|
||||
* Toggle the visibility of the sidebar. If the sidebar is hidden or is open
|
||||
* with a different commandID, then the sidebar will be opened using the
|
||||
* specified commandID. Otherwise the sidebar will be hidden.
|
||||
*
|
||||
* @param {string} commandID ID of the xul:broadcaster element to use.
|
||||
* @return {Promise}
|
||||
*/
|
||||
toggle(commandID = this.currentID) {
|
||||
if (this.isOpen && commandID == this.currentID) {
|
||||
this.hide();
|
||||
return Promise.resolve();
|
||||
}
|
||||
return this.show(commandID);
|
||||
},
|
||||
|
||||
/**
|
||||
* Show the sidebar, using the parameters from the specified broadcaster.
|
||||
* @see SidebarUI note.
|
||||
*
|
||||
* @param {string} commandID ID of the xul:broadcaster element to use.
|
||||
*/
|
||||
show(commandID) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let sidebarBroadcaster = document.getElementById(commandID);
|
||||
if (!sidebarBroadcaster || sidebarBroadcaster.localName != "broadcaster") {
|
||||
reject(new Error("Invalid sidebar broadcaster specified: " + commandID));
|
||||
return;
|
||||
}
|
||||
|
||||
let broadcasters = document.getElementsByAttribute("group", "sidebar");
|
||||
for (let broadcaster of broadcasters) {
|
||||
// skip elements that observe sidebar broadcasters and random
|
||||
// other elements
|
||||
if (broadcaster.localName != "broadcaster") {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (broadcaster != sidebarBroadcaster) {
|
||||
broadcaster.removeAttribute("checked");
|
||||
} else {
|
||||
sidebarBroadcaster.setAttribute("checked", "true");
|
||||
}
|
||||
}
|
||||
|
||||
this._box.hidden = false;
|
||||
this._splitter.hidden = false;
|
||||
|
||||
this._box.setAttribute("sidebarcommand", sidebarBroadcaster.id);
|
||||
|
||||
let title = sidebarBroadcaster.getAttribute("sidebartitle");
|
||||
if (!title) {
|
||||
title = sidebarBroadcaster.getAttribute("label");
|
||||
}
|
||||
this._title.value = title;
|
||||
|
||||
let url = sidebarBroadcaster.getAttribute("sidebarurl");
|
||||
this.browser.setAttribute("src", url); // kick off async load
|
||||
|
||||
// We set this attribute here in addition to setting it on the <browser>
|
||||
// element itself, because the code in SidebarUI.uninit() persists this
|
||||
// attribute, not the "src" of the <browser id="sidebar">. The reason it
|
||||
// does that is that we want to delay sidebar load a bit when a browser
|
||||
// window opens. See delayedStartup() and SidebarUI.startDelayedLoad().
|
||||
this._box.setAttribute("src", url);
|
||||
|
||||
if (this.browser.contentDocument.location.href != url) {
|
||||
let onLoad = event => {
|
||||
this.browser.removeEventListener("load", onLoad, true);
|
||||
|
||||
// We're handling the 'load' event before it bubbles up to the usual
|
||||
// (non-capturing) event handlers. Let it bubble up before firing the
|
||||
// SidebarFocused event.
|
||||
setTimeout(() => this._fireFocusedEvent(), 0);
|
||||
|
||||
// Run the original function for backwards compatibility.
|
||||
sidebarOnLoad(event);
|
||||
|
||||
resolve();
|
||||
};
|
||||
|
||||
this.browser.addEventListener("load", onLoad, true);
|
||||
} else {
|
||||
// Older code handled this case, so we do it too.
|
||||
this._fireFocusedEvent();
|
||||
resolve();
|
||||
}
|
||||
|
||||
let selBrowser = gBrowser.selectedBrowser;
|
||||
selBrowser.messageManager.sendAsyncMessage("Sidebar:VisibilityChange",
|
||||
{commandID: commandID, isOpen: true}
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Hide the sidebar.
|
||||
*/
|
||||
hide() {
|
||||
if (!this.isOpen) {
|
||||
return;
|
||||
}
|
||||
|
||||
let commandID = this._box.getAttribute("sidebarcommand");
|
||||
let sidebarBroadcaster = document.getElementById(commandID);
|
||||
|
||||
if (sidebarBroadcaster.getAttribute("checked") != "true") {
|
||||
return;
|
||||
}
|
||||
|
||||
// Replace the document currently displayed in the sidebar with about:blank
|
||||
// so that we can free memory by unloading the page. We need to explicitly
|
||||
// create a new content viewer because the old one doesn't get destroyed
|
||||
// until about:blank has loaded (which does not happen as long as the
|
||||
// element is hidden).
|
||||
this.browser.setAttribute("src", "about:blank");
|
||||
this.browser.docShell.createAboutBlankContentViewer(null);
|
||||
|
||||
sidebarBroadcaster.removeAttribute("checked");
|
||||
this._box.setAttribute("sidebarcommand", "");
|
||||
this._title.value = "";
|
||||
this._box.hidden = true;
|
||||
this._splitter.hidden = true;
|
||||
|
||||
let selBrowser = gBrowser.selectedBrowser;
|
||||
selBrowser.focus();
|
||||
selBrowser.messageManager.sendAsyncMessage("Sidebar:VisibilityChange",
|
||||
{commandID: commandID, isOpen: false}
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* This exists for backards compatibility - it will be called once a sidebar is
|
||||
* ready, following any request to show it.
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
function fireSidebarFocusedEvent() {}
|
||||
|
||||
/**
|
||||
* This exists for backards compatibility - it gets called when a sidebar has
|
||||
* been loaded.
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
function sidebarOnLoad(event) {}
|
||||
|
||||
/**
|
||||
* This exists for backards compatibility, and is equivilent to
|
||||
* SidebarUI.toggle() without the forceOpen param. With forceOpen set to true,
|
||||
* it is equalivent to SidebarUI.show().
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
function toggleSidebar(commandID, forceOpen = false) {
|
||||
Deprecated.warning("toggleSidebar() is deprecated, please use SidebarUI.toggle() or SidebarUI.show() instead",
|
||||
"https://developer.mozilla.org/en-US/Add-ons/Code_snippets/Sidebar");
|
||||
|
||||
if (forceOpen) {
|
||||
SidebarUI.show(commandID);
|
||||
} else {
|
||||
SidebarUI.toggle(commandID);
|
||||
}
|
||||
}
|
||||
469
browser/base/content/browser-syncui.js
Normal file
|
|
@ -0,0 +1,469 @@
|
|||
# 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/.
|
||||
|
||||
// gSyncUI handles updating the tools menu
|
||||
var gSyncUI = {
|
||||
_obs: ["weave:service:sync:start",
|
||||
"weave:service:sync:delayed",
|
||||
"weave:service:quota:remaining",
|
||||
"weave:service:setup-complete",
|
||||
"weave:service:login:start",
|
||||
"weave:service:login:finish",
|
||||
"weave:service:logout:finish",
|
||||
"weave:service:start-over",
|
||||
"weave:ui:login:error",
|
||||
"weave:ui:sync:error",
|
||||
"weave:ui:sync:finish",
|
||||
"weave:ui:clear-error",
|
||||
],
|
||||
|
||||
_unloaded: false,
|
||||
|
||||
init: function() {
|
||||
// Proceed to set up the UI if Sync has already started up.
|
||||
// Otherwise we'll do it when Sync is firing up.
|
||||
let xps = Components.classes["@mozilla.org/weave/service;1"]
|
||||
.getService(Components.interfaces.nsISupports)
|
||||
.wrappedJSObject;
|
||||
if (xps.ready) {
|
||||
this.initUI();
|
||||
return;
|
||||
}
|
||||
|
||||
Services.obs.addObserver(this, "weave:service:ready", true);
|
||||
|
||||
// Remove the observer if the window is closed before the observer
|
||||
// was triggered.
|
||||
window.addEventListener("unload", function onUnload() {
|
||||
gSyncUI._unloaded = true;
|
||||
window.removeEventListener("unload", onUnload, false);
|
||||
Services.obs.removeObserver(gSyncUI, "weave:service:ready");
|
||||
|
||||
if (Weave.Status.ready) {
|
||||
gSyncUI._obs.forEach(function(topic) {
|
||||
Services.obs.removeObserver(gSyncUI, topic);
|
||||
});
|
||||
}
|
||||
}, false);
|
||||
},
|
||||
|
||||
initUI: function() {
|
||||
// If this is a browser window?
|
||||
if (gBrowser) {
|
||||
this._obs.push("weave:notification:added");
|
||||
}
|
||||
|
||||
this._obs.forEach(function(topic) {
|
||||
Services.obs.addObserver(this, topic, true);
|
||||
}, this);
|
||||
|
||||
if (gBrowser && Weave.Notifications.notifications.length) {
|
||||
this.initNotifications();
|
||||
}
|
||||
this.updateUI();
|
||||
},
|
||||
|
||||
initNotifications: function() {
|
||||
const XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
|
||||
let notificationbox = document.createElementNS(XULNS, "notificationbox");
|
||||
notificationbox.id = "sync-notifications";
|
||||
notificationbox.setAttribute("flex", "1");
|
||||
|
||||
let bottombox = document.getElementById("browser-bottombox");
|
||||
bottombox.insertBefore(notificationbox, bottombox.firstChild);
|
||||
|
||||
// Force a style flush to ensure that our binding is attached.
|
||||
notificationbox.clientTop;
|
||||
|
||||
// notificationbox will listen to observers from now on.
|
||||
Services.obs.removeObserver(this, "weave:notification:added");
|
||||
},
|
||||
|
||||
_wasDelayed: false,
|
||||
|
||||
_needsSetup: function() {
|
||||
let firstSync = "";
|
||||
try {
|
||||
firstSync = Services.prefs.getCharPref("services.sync.firstSync");
|
||||
} catch (e) { }
|
||||
return Weave.Status.checkSetup() == Weave.CLIENT_NOT_CONFIGURED ||
|
||||
firstSync == "notReady";
|
||||
},
|
||||
|
||||
updateUI: function() {
|
||||
let needsSetup = this._needsSetup();
|
||||
document.getElementById("sync-setup-state").hidden = !needsSetup;
|
||||
document.getElementById("sync-syncnow-state").hidden = needsSetup;
|
||||
|
||||
if (!gBrowser)
|
||||
return;
|
||||
|
||||
let button = document.getElementById("sync-button");
|
||||
if (!button)
|
||||
return;
|
||||
|
||||
button.removeAttribute("status");
|
||||
this._updateLastSyncTime();
|
||||
if (needsSetup)
|
||||
button.removeAttribute("tooltiptext");
|
||||
},
|
||||
|
||||
|
||||
// Functions called by observers
|
||||
onActivityStart: function() {
|
||||
if (!gBrowser)
|
||||
return;
|
||||
|
||||
let button = document.getElementById("sync-button");
|
||||
if (!button)
|
||||
return;
|
||||
|
||||
button.setAttribute("status", "active");
|
||||
},
|
||||
|
||||
onSyncDelay: function() {
|
||||
// basically, we want to just inform users that stuff is going to take a while
|
||||
let title = this._stringBundle.GetStringFromName("error.sync.no_node_found.title");
|
||||
let description = this._stringBundle.GetStringFromName("error.sync.no_node_found");
|
||||
let buttons = [new Weave.NotificationButton(
|
||||
this._stringBundle.GetStringFromName("error.sync.serverStatusButton.label"),
|
||||
this._stringBundle.GetStringFromName("error.sync.serverStatusButton.accesskey"),
|
||||
function() { gSyncUI.openServerStatus(); return true; }
|
||||
)];
|
||||
let notification = new Weave.Notification(
|
||||
title, description, null, Weave.Notifications.PRIORITY_INFO, buttons);
|
||||
Weave.Notifications.replaceTitle(notification);
|
||||
this._wasDelayed = true;
|
||||
},
|
||||
|
||||
onLoginFinish: function() {
|
||||
// Clear out any login failure notifications
|
||||
let title = this._stringBundle.GetStringFromName("error.login.title");
|
||||
this.clearError(title);
|
||||
},
|
||||
|
||||
onSetupComplete: function() {
|
||||
this.onLoginFinish();
|
||||
},
|
||||
|
||||
onLoginError: function() {
|
||||
// if login fails, any other notifications are essentially moot
|
||||
Weave.Notifications.removeAll();
|
||||
|
||||
// if we haven't set up the client, don't show errors
|
||||
if (this._needsSetup()) {
|
||||
this.updateUI();
|
||||
return;
|
||||
}
|
||||
|
||||
let title = this._stringBundle.GetStringFromName("error.login.title");
|
||||
|
||||
let description;
|
||||
if (Weave.Status.sync == Weave.PROLONGED_SYNC_FAILURE) {
|
||||
// Convert to days
|
||||
let lastSync =
|
||||
Services.prefs.getIntPref("services.sync.errorhandler.networkFailureReportTimeout") / 86400;
|
||||
description =
|
||||
this._stringBundle.formatStringFromName("error.sync.prolonged_failure", [lastSync], 1);
|
||||
} else {
|
||||
let reason = Weave.Utils.getErrorString(Weave.Status.login);
|
||||
description =
|
||||
this._stringBundle.formatStringFromName("error.sync.description", [reason], 1);
|
||||
}
|
||||
|
||||
let buttons = [];
|
||||
buttons.push(new Weave.NotificationButton(
|
||||
this._stringBundle.GetStringFromName("error.login.prefs.label"),
|
||||
this._stringBundle.GetStringFromName("error.login.prefs.accesskey"),
|
||||
function() { gSyncUI.openPrefs(); return true; }
|
||||
));
|
||||
|
||||
let notification = new Weave.Notification(title, description, null,
|
||||
Weave.Notifications.PRIORITY_WARNING, buttons);
|
||||
Weave.Notifications.replaceTitle(notification);
|
||||
this.updateUI();
|
||||
},
|
||||
|
||||
onLogout: function() {
|
||||
this.updateUI();
|
||||
},
|
||||
|
||||
onStartOver: function() {
|
||||
this.clearError();
|
||||
},
|
||||
|
||||
onQuotaNotice: function(subject, data) {
|
||||
let title = this._stringBundle.GetStringFromName("warning.sync.quota.label");
|
||||
let description = this._stringBundle.GetStringFromName("warning.sync.quota.description");
|
||||
let buttons = [];
|
||||
buttons.push(new Weave.NotificationButton(
|
||||
this._stringBundle.GetStringFromName("error.sync.viewQuotaButton.label"),
|
||||
this._stringBundle.GetStringFromName("error.sync.viewQuotaButton.accesskey"),
|
||||
function() { gSyncUI.openQuotaDialog(); return true; }
|
||||
));
|
||||
|
||||
let notification = new Weave.Notification(
|
||||
title, description, null, Weave.Notifications.PRIORITY_WARNING, buttons);
|
||||
Weave.Notifications.replaceTitle(notification);
|
||||
},
|
||||
|
||||
openServerStatus: function () {
|
||||
let statusURL = Services.prefs.getCharPref("services.sync.statusURL");
|
||||
window.openUILinkIn(statusURL, "tab");
|
||||
},
|
||||
|
||||
// Commands
|
||||
doSync: function() {
|
||||
setTimeout(function() Weave.Service.errorHandler.syncAndReportErrors(), 0);
|
||||
},
|
||||
|
||||
handleToolbarButton: function() {
|
||||
if (this._needsSetup())
|
||||
this.openSetup();
|
||||
else
|
||||
this.doSync();
|
||||
},
|
||||
|
||||
//XXXzpao should be part of syncCommon.js - which we might want to make a module...
|
||||
// To be fixed in a followup (bug 583366)
|
||||
|
||||
/**
|
||||
* Invoke the Sync setup wizard.
|
||||
*
|
||||
* @param wizardType
|
||||
* Indicates type of wizard to launch:
|
||||
* null -- regular set up wizard
|
||||
* "pair" -- pair a device first
|
||||
* "reset" -- reset sync
|
||||
*/
|
||||
|
||||
openSetup: function(wizardType) {
|
||||
let win = Services.wm.getMostRecentWindow("Weave:AccountSetup");
|
||||
if (win)
|
||||
win.focus();
|
||||
else {
|
||||
window.openDialog("chrome://browser/content/sync/setup.xul",
|
||||
"weaveSetup", "centerscreen,chrome,resizable=no",
|
||||
wizardType);
|
||||
}
|
||||
},
|
||||
|
||||
openAddDevice: function () {
|
||||
if (!Weave.Utils.ensureMPUnlocked())
|
||||
return;
|
||||
|
||||
let win = Services.wm.getMostRecentWindow("Sync:AddDevice");
|
||||
if (win)
|
||||
win.focus();
|
||||
else
|
||||
window.openDialog("chrome://browser/content/sync/addDevice.xul",
|
||||
"syncAddDevice", "centerscreen,chrome,resizable=no");
|
||||
},
|
||||
|
||||
openQuotaDialog: function() {
|
||||
let win = Services.wm.getMostRecentWindow("Sync:ViewQuota");
|
||||
if (win)
|
||||
win.focus();
|
||||
else
|
||||
Services.ww.activeWindow.openDialog(
|
||||
"chrome://browser/content/sync/quota.xul", "",
|
||||
"centerscreen,chrome,dialog,modal");
|
||||
},
|
||||
|
||||
openPrefs: function() {
|
||||
openPreferences("paneSync");
|
||||
},
|
||||
|
||||
// Helpers
|
||||
_updateLastSyncTime: function() {
|
||||
if (!gBrowser)
|
||||
return;
|
||||
|
||||
let syncButton = document.getElementById("sync-button");
|
||||
if (!syncButton)
|
||||
return;
|
||||
|
||||
let lastSync;
|
||||
try {
|
||||
lastSync = Services.prefs.getCharPref("services.sync.lastSync");
|
||||
}
|
||||
catch (e) { };
|
||||
if (!lastSync || this._needsSetup()) {
|
||||
syncButton.removeAttribute("tooltiptext");
|
||||
return;
|
||||
}
|
||||
|
||||
// Show the day-of-week and time (HH:MM) of last sync
|
||||
let lastSyncDate = new Date(lastSync).toLocaleFormat("%a %H:%M");
|
||||
let lastSyncLabel =
|
||||
this._stringBundle.formatStringFromName("lastSync2.label", [lastSyncDate], 1);
|
||||
|
||||
syncButton.setAttribute("tooltiptext", lastSyncLabel);
|
||||
},
|
||||
|
||||
clearError: function(errorString) {
|
||||
Weave.Notifications.removeAll(errorString);
|
||||
this.updateUI();
|
||||
},
|
||||
|
||||
onSyncFinish: function() {
|
||||
let title = this._stringBundle.GetStringFromName("error.sync.title");
|
||||
|
||||
// Clear out sync failures on a successful sync
|
||||
this.clearError(title);
|
||||
|
||||
if (this._wasDelayed && Weave.Status.sync != Weave.NO_SYNC_NODE_FOUND) {
|
||||
title = this._stringBundle.GetStringFromName("error.sync.no_node_found.title");
|
||||
this.clearError(title);
|
||||
this._wasDelayed = false;
|
||||
}
|
||||
},
|
||||
|
||||
onSyncError: function() {
|
||||
let title = this._stringBundle.GetStringFromName("error.sync.title");
|
||||
|
||||
if (Weave.Status.login != Weave.LOGIN_SUCCEEDED) {
|
||||
this.onLoginError();
|
||||
return;
|
||||
}
|
||||
|
||||
let description;
|
||||
if (Weave.Status.sync == Weave.PROLONGED_SYNC_FAILURE) {
|
||||
// Convert to days
|
||||
let lastSync =
|
||||
Services.prefs.getIntPref("services.sync.errorhandler.networkFailureReportTimeout") / 86400;
|
||||
description =
|
||||
this._stringBundle.formatStringFromName("error.sync.prolonged_failure", [lastSync], 1);
|
||||
} else {
|
||||
let error = Weave.Utils.getErrorString(Weave.Status.sync);
|
||||
description =
|
||||
this._stringBundle.formatStringFromName("error.sync.description", [error], 1);
|
||||
}
|
||||
let priority = Weave.Notifications.PRIORITY_WARNING;
|
||||
let buttons = [];
|
||||
|
||||
// Check if the client is outdated in some way
|
||||
let outdated = Weave.Status.sync == Weave.VERSION_OUT_OF_DATE;
|
||||
for (let [engine, reason] in Iterator(Weave.Status.engines))
|
||||
outdated = outdated || reason == Weave.VERSION_OUT_OF_DATE;
|
||||
|
||||
if (outdated) {
|
||||
description = this._stringBundle.GetStringFromName(
|
||||
"error.sync.needUpdate.description");
|
||||
buttons.push(new Weave.NotificationButton(
|
||||
this._stringBundle.GetStringFromName("error.sync.needUpdate.label"),
|
||||
this._stringBundle.GetStringFromName("error.sync.needUpdate.accesskey"),
|
||||
function() {
|
||||
window.openUILinkIn(Services.prefs.getCharPref("services.sync.outdated.url"), "tab");
|
||||
return true;
|
||||
}
|
||||
));
|
||||
}
|
||||
else if (Weave.Status.sync == Weave.OVER_QUOTA) {
|
||||
description = this._stringBundle.GetStringFromName(
|
||||
"error.sync.quota.description");
|
||||
buttons.push(new Weave.NotificationButton(
|
||||
this._stringBundle.GetStringFromName(
|
||||
"error.sync.viewQuotaButton.label"),
|
||||
this._stringBundle.GetStringFromName(
|
||||
"error.sync.viewQuotaButton.accesskey"),
|
||||
function() { gSyncUI.openQuotaDialog(); return true; } )
|
||||
);
|
||||
}
|
||||
else if (Weave.Status.enforceBackoff) {
|
||||
priority = Weave.Notifications.PRIORITY_INFO;
|
||||
buttons.push(new Weave.NotificationButton(
|
||||
this._stringBundle.GetStringFromName("error.sync.serverStatusButton.label"),
|
||||
this._stringBundle.GetStringFromName("error.sync.serverStatusButton.accesskey"),
|
||||
function() { gSyncUI.openServerStatus(); return true; }
|
||||
));
|
||||
}
|
||||
else {
|
||||
priority = Weave.Notifications.PRIORITY_INFO;
|
||||
buttons.push(new Weave.NotificationButton(
|
||||
this._stringBundle.GetStringFromName("error.sync.tryAgainButton.label"),
|
||||
this._stringBundle.GetStringFromName("error.sync.tryAgainButton.accesskey"),
|
||||
function() { gSyncUI.doSync(); return true; }
|
||||
));
|
||||
}
|
||||
|
||||
let notification =
|
||||
new Weave.Notification(title, description, null, priority, buttons);
|
||||
Weave.Notifications.replaceTitle(notification);
|
||||
|
||||
if (this._wasDelayed && Weave.Status.sync != Weave.NO_SYNC_NODE_FOUND) {
|
||||
title = this._stringBundle.GetStringFromName("error.sync.no_node_found.title");
|
||||
Weave.Notifications.removeAll(title);
|
||||
this._wasDelayed = false;
|
||||
}
|
||||
|
||||
this.updateUI();
|
||||
},
|
||||
|
||||
observe: function(subject, topic, data) {
|
||||
if (this._unloaded) {
|
||||
Cu.reportError("SyncUI observer called after unload: " + topic);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (topic) {
|
||||
case "weave:service:sync:start":
|
||||
this.onActivityStart();
|
||||
break;
|
||||
case "weave:ui:sync:finish":
|
||||
this.onSyncFinish();
|
||||
break;
|
||||
case "weave:ui:sync:error":
|
||||
this.onSyncError();
|
||||
break;
|
||||
case "weave:service:sync:delayed":
|
||||
this.onSyncDelay();
|
||||
break;
|
||||
case "weave:service:quota:remaining":
|
||||
this.onQuotaNotice();
|
||||
break;
|
||||
case "weave:service:setup-complete":
|
||||
this.onSetupComplete();
|
||||
break;
|
||||
case "weave:service:login:start":
|
||||
this.onActivityStart();
|
||||
break;
|
||||
case "weave:service:login:finish":
|
||||
this.onLoginFinish();
|
||||
break;
|
||||
case "weave:ui:login:error":
|
||||
this.onLoginError();
|
||||
break;
|
||||
case "weave:service:logout:finish":
|
||||
this.onLogout();
|
||||
break;
|
||||
case "weave:service:start-over":
|
||||
this.onStartOver();
|
||||
break;
|
||||
case "weave:service:ready":
|
||||
this.initUI();
|
||||
break;
|
||||
case "weave:notification:added":
|
||||
this.initNotifications();
|
||||
break;
|
||||
case "weave:ui:clear-error":
|
||||
this.clearError();
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
QueryInterface: XPCOMUtils.generateQI([
|
||||
Ci.nsIObserver,
|
||||
Ci.nsISupportsWeakReference
|
||||
])
|
||||
};
|
||||
|
||||
XPCOMUtils.defineLazyGetter(gSyncUI, "_stringBundle", function() {
|
||||
//XXXzpao these strings should probably be moved from /services to /browser... (bug 583381)
|
||||
// but for now just make it work
|
||||
return Cc["@mozilla.org/intl/stringbundle;1"].
|
||||
getService(Ci.nsIStringBundleService).
|
||||
createBundle("chrome://weave/locale/services/sync.properties");
|
||||
});
|
||||
|
||||
37
browser/base/content/browser-tabPreviews.xml
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
<?xml version="1.0"?>
|
||||
|
||||
# -*- Mode: HTML -*-
|
||||
# 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="tabPreviews"
|
||||
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="ctrlTab-preview" extends="chrome://global/content/bindings/button.xml#button-base">
|
||||
<content pack="center">
|
||||
<xul:stack>
|
||||
<xul:vbox class="ctrlTab-preview-inner" align="center" pack="center"
|
||||
xbl:inherits="width=canvaswidth">
|
||||
<xul:hbox class="tabPreview-canvas" xbl:inherits="style=canvasstyle">
|
||||
<children/>
|
||||
</xul:hbox>
|
||||
<xul:label xbl:inherits="value=label,crop" class="plain"/>
|
||||
</xul:vbox>
|
||||
<xul:hbox class="ctrlTab-favicon-container" xbl:inherits="hidden=noicon">
|
||||
<xul:image class="ctrlTab-favicon" xbl:inherits="src=image"/>
|
||||
</xul:hbox>
|
||||
</xul:stack>
|
||||
</content>
|
||||
<handlers>
|
||||
<handler event="mouseover" action="ctrlTab._mouseOverFocus(this);"/>
|
||||
<handler event="command" action="ctrlTab.pick(this);"/>
|
||||
<handler event="click" button="1" action="ctrlTab.remove(this);"/>
|
||||
#ifdef XP_MACOSX
|
||||
# Control+click is a right click on OS X
|
||||
<handler event="click" button="2" action="ctrlTab.pick(this);"/>
|
||||
#endif
|
||||
</handlers>
|
||||
</binding>
|
||||
</bindings>
|
||||
17
browser/base/content/browser-tabsintitlebar-stub.js
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
// This file is used as a stub object for platforms which
|
||||
// don't have CAN_DRAW_IN_TITLEBAR defined.
|
||||
|
||||
var TabsInTitlebar = {
|
||||
init: function () {},
|
||||
uninit: function () {},
|
||||
allowedBy: function (condition, allow) {},
|
||||
updateAppearance: function(aForce) {},
|
||||
get enabled() {
|
||||
return document.documentElement.getAttribute("tabsintitlebar") == "true";
|
||||
},
|
||||
};
|
||||
307
browser/base/content/browser-tabsintitlebar.js
Normal file
|
|
@ -0,0 +1,307 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
// Note: the file browser-tabsintitlebar-stub.js is used instead of
|
||||
// this one on platforms which don't have CAN_DRAW_IN_TITLEBAR defined.
|
||||
|
||||
var TabsInTitlebar = {
|
||||
init: function () {
|
||||
if (this._initialized) {
|
||||
return;
|
||||
}
|
||||
this._readPref();
|
||||
Services.prefs.addObserver(this._prefName, this, false);
|
||||
|
||||
// We need to update the appearance of the titlebar when the menu changes
|
||||
// from the active to the inactive state. We can't, however, rely on
|
||||
// DOMMenuBarInactive, because the menu fires this event and then removes
|
||||
// the inactive attribute after an event-loop spin.
|
||||
//
|
||||
// Because updating the appearance involves sampling the heights and margins
|
||||
// of various elements, it's important that the layout be more or less
|
||||
// settled before updating the titlebar. So instead of listening to
|
||||
// DOMMenuBarActive and DOMMenuBarInactive, we use a MutationObserver to
|
||||
// watch the "invalid" attribute directly.
|
||||
let menu = document.getElementById("toolbar-menubar");
|
||||
this._menuObserver = new MutationObserver(this._onMenuMutate);
|
||||
this._menuObserver.observe(menu, {attributes: true});
|
||||
|
||||
this.onAreaReset = function(aArea) {
|
||||
if (aArea == CustomizableUI.AREA_TABSTRIP || aArea == CustomizableUI.AREA_MENUBAR)
|
||||
this._update(true);
|
||||
};
|
||||
this.onWidgetAdded = this.onWidgetRemoved = function(aWidgetId, aArea) {
|
||||
if (aArea == CustomizableUI.AREA_TABSTRIP || aArea == CustomizableUI.AREA_MENUBAR)
|
||||
this._update(true);
|
||||
};
|
||||
CustomizableUI.addListener(this);
|
||||
|
||||
addEventListener("resolutionchange", this, false);
|
||||
|
||||
this._initialized = true;
|
||||
if (this._updateOnInit) {
|
||||
// We don't need to call this with 'true', even if original calls
|
||||
// (before init()) did, because this will be the first call and so
|
||||
// we will update anyway.
|
||||
this._update();
|
||||
}
|
||||
},
|
||||
|
||||
allowedBy: function (condition, allow) {
|
||||
if (allow) {
|
||||
if (condition in this._disallowed) {
|
||||
delete this._disallowed[condition];
|
||||
this._update(true);
|
||||
}
|
||||
} else if (!(condition in this._disallowed)) {
|
||||
this._disallowed[condition] = null;
|
||||
this._update(true);
|
||||
}
|
||||
},
|
||||
|
||||
updateAppearance: function(aForce) {
|
||||
this._update(aForce);
|
||||
},
|
||||
|
||||
get enabled() {
|
||||
return document.documentElement.getAttribute("tabsintitlebar") == "true";
|
||||
},
|
||||
|
||||
observe: function (subject, topic, data) {
|
||||
if (topic == "nsPref:changed")
|
||||
this._readPref();
|
||||
},
|
||||
|
||||
handleEvent: function (aEvent) {
|
||||
if (aEvent.type == "resolutionchange" && aEvent.target == window) {
|
||||
this._update(true);
|
||||
}
|
||||
},
|
||||
|
||||
_onMenuMutate: function (aMutations) {
|
||||
for (let mutation of aMutations) {
|
||||
if (mutation.attributeName == "inactive" ||
|
||||
mutation.attributeName == "autohide") {
|
||||
TabsInTitlebar._update(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_initialized: false,
|
||||
_updateOnInit: false,
|
||||
_disallowed: {},
|
||||
_prefName: "browser.tabs.drawInTitlebar",
|
||||
_lastSizeMode: null,
|
||||
|
||||
_readPref: function () {
|
||||
this.allowedBy("pref",
|
||||
Services.prefs.getBoolPref(this._prefName));
|
||||
},
|
||||
|
||||
_update: function (aForce=false) {
|
||||
let $ = id => document.getElementById(id);
|
||||
let rect = ele => ele.getBoundingClientRect();
|
||||
let verticalMargins = cstyle => parseFloat(cstyle.marginBottom) + parseFloat(cstyle.marginTop);
|
||||
|
||||
if (window.fullScreen)
|
||||
return;
|
||||
|
||||
// In some edgecases it is possible for this to fire before we've initialized.
|
||||
// Don't run now, but don't forget to run it when we do initialize.
|
||||
if (!this._initialized) {
|
||||
this._updateOnInit = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!aForce) {
|
||||
// _update is called on resize events, because the window is not ready
|
||||
// after sizemode events. However, we only care about the event when the
|
||||
// sizemode is different from the last time we updated the appearance of
|
||||
// the tabs in the titlebar.
|
||||
let sizemode = document.documentElement.getAttribute("sizemode");
|
||||
if (this._lastSizeMode == sizemode) {
|
||||
return;
|
||||
}
|
||||
let oldSizeMode = this._lastSizeMode;
|
||||
this._lastSizeMode = sizemode;
|
||||
// Don't update right now if we are leaving fullscreen, since the UI is
|
||||
// still changing in the consequent "fullscreen" event. Code there will
|
||||
// call this function again when everything is ready.
|
||||
// See browser-fullScreen.js: FullScreen.toggle and bug 1173768.
|
||||
if (oldSizeMode == "fullscreen") {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let allowed = (Object.keys(this._disallowed)).length == 0;
|
||||
|
||||
let titlebar = $("titlebar");
|
||||
let titlebarContent = $("titlebar-content");
|
||||
let menubar = $("toolbar-menubar");
|
||||
|
||||
if (allowed) {
|
||||
// We set the tabsintitlebar attribute first so that our CSS for
|
||||
// tabsintitlebar manifests before we do our measurements.
|
||||
document.documentElement.setAttribute("tabsintitlebar", "true");
|
||||
updateTitlebarDisplay();
|
||||
|
||||
// Try to avoid reflows in this code by calculating dimensions first and
|
||||
// then later set the properties affecting layout together in a batch.
|
||||
|
||||
// Get the full height of the tabs toolbar:
|
||||
let tabsToolbar = $("TabsToolbar");
|
||||
let tabsStyles = window.getComputedStyle(tabsToolbar);
|
||||
let fullTabsHeight = rect(tabsToolbar).height + verticalMargins(tabsStyles);
|
||||
// Buttons first:
|
||||
let captionButtonsBoxWidth = rect($("titlebar-buttonbox-container")).width;
|
||||
|
||||
let secondaryButtonsWidth, menuHeight, fullMenuHeight, menuStyles;
|
||||
if (AppConstants.platform == "macosx") {
|
||||
secondaryButtonsWidth = rect($("titlebar-secondary-buttonbox")).width;
|
||||
// No need to look up the menubar stuff on OS X:
|
||||
menuHeight = 0;
|
||||
fullMenuHeight = 0;
|
||||
} else {
|
||||
// Otherwise, get the height and margins separately for the menubar
|
||||
menuHeight = rect(menubar).height;
|
||||
menuStyles = window.getComputedStyle(menubar);
|
||||
fullMenuHeight = verticalMargins(menuStyles) + menuHeight;
|
||||
}
|
||||
|
||||
// And get the height of what's in the titlebar:
|
||||
let titlebarContentHeight = rect(titlebarContent).height;
|
||||
|
||||
// Begin setting CSS properties which will cause a reflow
|
||||
|
||||
// If the menubar is around (menuHeight is non-zero), try to adjust
|
||||
// its full height (i.e. including margins) to match the titlebar,
|
||||
// by changing the menubar's bottom padding
|
||||
if (menuHeight) {
|
||||
// Calculate the difference between the titlebar's height and that of the menubar
|
||||
let menuTitlebarDelta = titlebarContentHeight - fullMenuHeight;
|
||||
let paddingBottom;
|
||||
// The titlebar is bigger:
|
||||
if (menuTitlebarDelta > 0) {
|
||||
fullMenuHeight += menuTitlebarDelta;
|
||||
// If there is already padding on the menubar, we need to add that
|
||||
// to the difference so the total padding is correct:
|
||||
if ((paddingBottom = menuStyles.paddingBottom)) {
|
||||
menuTitlebarDelta += parseFloat(paddingBottom);
|
||||
}
|
||||
menubar.style.paddingBottom = menuTitlebarDelta + "px";
|
||||
// The menubar is bigger, but has bottom padding we can remove:
|
||||
} else if (menuTitlebarDelta < 0 && (paddingBottom = menuStyles.paddingBottom)) {
|
||||
let existingPadding = parseFloat(paddingBottom);
|
||||
// menuTitlebarDelta is negative; work out what's left, but don't set negative padding:
|
||||
let desiredPadding = Math.max(0, existingPadding + menuTitlebarDelta);
|
||||
menubar.style.paddingBottom = desiredPadding + "px";
|
||||
// We've changed the menu height now:
|
||||
fullMenuHeight += desiredPadding - existingPadding;
|
||||
}
|
||||
}
|
||||
|
||||
// Next, we calculate how much we need to stretch the titlebar down to
|
||||
// go all the way to the bottom of the tab strip, if necessary.
|
||||
let tabAndMenuHeight = fullTabsHeight + fullMenuHeight;
|
||||
|
||||
if (tabAndMenuHeight > titlebarContentHeight) {
|
||||
// We need to increase the titlebar content's outer height (ie including margins)
|
||||
// to match the tab and menu height:
|
||||
let extraMargin = tabAndMenuHeight - titlebarContentHeight;
|
||||
if (AppConstants.platform != "macosx") {
|
||||
titlebarContent.style.marginBottom = extraMargin + "px";
|
||||
}
|
||||
|
||||
titlebarContentHeight += extraMargin;
|
||||
} else {
|
||||
titlebarContent.style.removeProperty("margin-bottom");
|
||||
}
|
||||
|
||||
// Then add a negative margin to the titlebar, so that the following elements
|
||||
// will overlap it by the lesser of the titlebar height or the tabstrip+menu.
|
||||
let minTitlebarOrTabsHeight = Math.min(titlebarContentHeight, tabAndMenuHeight);
|
||||
titlebar.style.marginBottom = "-" + minTitlebarOrTabsHeight + "px";
|
||||
|
||||
// Finally, size the placeholders:
|
||||
if (AppConstants.platform == "macosx") {
|
||||
this._sizePlaceholder("fullscreen-button", secondaryButtonsWidth);
|
||||
}
|
||||
this._sizePlaceholder("caption-buttons", captionButtonsBoxWidth);
|
||||
|
||||
} else {
|
||||
document.documentElement.removeAttribute("tabsintitlebar");
|
||||
updateTitlebarDisplay();
|
||||
|
||||
if (AppConstants.platform == "macosx") {
|
||||
let secondaryButtonsWidth = rect($("titlebar-secondary-buttonbox")).width;
|
||||
this._sizePlaceholder("fullscreen-button", secondaryButtonsWidth);
|
||||
}
|
||||
|
||||
// Reset the margins and padding that might have been modified:
|
||||
titlebarContent.style.marginTop = "";
|
||||
titlebarContent.style.marginBottom = "";
|
||||
titlebar.style.marginBottom = "";
|
||||
menubar.style.paddingBottom = "";
|
||||
}
|
||||
|
||||
ToolbarIconColor.inferFromText();
|
||||
if (CustomizationHandler.isCustomizing()) {
|
||||
gCustomizeMode.updateLWTStyling();
|
||||
}
|
||||
},
|
||||
|
||||
_sizePlaceholder: function (type, width) {
|
||||
Array.forEach(document.querySelectorAll(".titlebar-placeholder[type='"+ type +"']"),
|
||||
function (node) { node.width = width; });
|
||||
},
|
||||
|
||||
uninit: function () {
|
||||
this._initialized = false;
|
||||
removeEventListener("resolutionchange", this);
|
||||
Services.prefs.removeObserver(this._prefName, this);
|
||||
this._menuObserver.disconnect();
|
||||
CustomizableUI.removeListener(this);
|
||||
}
|
||||
};
|
||||
|
||||
function updateTitlebarDisplay() {
|
||||
if (AppConstants.platform == "macosx") {
|
||||
// OS X and the other platforms differ enough to necessitate this kind of
|
||||
// special-casing. Like the other platforms where we CAN_DRAW_IN_TITLEBAR,
|
||||
// we draw in the OS X titlebar when putting the tabs up there. However, OS X
|
||||
// also draws in the titlebar when a lightweight theme is applied, regardless
|
||||
// of whether or not the tabs are drawn in the titlebar.
|
||||
if (TabsInTitlebar.enabled) {
|
||||
document.documentElement.setAttribute("chromemargin-nonlwtheme", "0,-1,-1,-1");
|
||||
document.documentElement.setAttribute("chromemargin", "0,-1,-1,-1");
|
||||
document.documentElement.removeAttribute("drawtitle");
|
||||
} else {
|
||||
// We set chromemargin-nonlwtheme to "" instead of removing it as a way of
|
||||
// making sure that LightweightThemeConsumer doesn't take it upon itself to
|
||||
// detect this value again if and when we do a lwtheme state change.
|
||||
document.documentElement.setAttribute("chromemargin-nonlwtheme", "");
|
||||
let isCustomizing = document.documentElement.hasAttribute("customizing");
|
||||
let hasLWTheme = document.documentElement.hasAttribute("lwtheme");
|
||||
let isPrivate = PrivateBrowsingUtils.isWindowPrivate(window);
|
||||
if ((!hasLWTheme || isCustomizing) && !isPrivate) {
|
||||
document.documentElement.removeAttribute("chromemargin");
|
||||
}
|
||||
document.documentElement.setAttribute("drawtitle", "true");
|
||||
}
|
||||
} else if (TabsInTitlebar.enabled) {
|
||||
// not OS X
|
||||
document.documentElement.setAttribute("chromemargin", "0,2,2,2");
|
||||
} else {
|
||||
document.documentElement.removeAttribute("chromemargin");
|
||||
}
|
||||
}
|
||||
|
||||
function onTitlebarMaxClick() {
|
||||
if (window.windowState == window.STATE_MAXIMIZED)
|
||||
window.restore();
|
||||
else
|
||||
window.maximize();
|
||||
}
|
||||
142
browser/base/content/browser-thumbnails.js
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
/* 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/. */
|
||||
|
||||
/**
|
||||
* Keeps thumbnails of open web pages up-to-date.
|
||||
*/
|
||||
var gBrowserThumbnails = {
|
||||
/**
|
||||
* Pref that controls whether we can store SSL content on disk
|
||||
*/
|
||||
PREF_DISK_CACHE_SSL: "browser.cache.disk_cache_ssl",
|
||||
|
||||
_captureDelayMS: 1000,
|
||||
|
||||
/**
|
||||
* Used to keep track of disk_cache_ssl preference
|
||||
*/
|
||||
_sslDiskCacheEnabled: null,
|
||||
|
||||
/**
|
||||
* Map of capture() timeouts assigned to their browsers.
|
||||
*/
|
||||
_timeouts: null,
|
||||
|
||||
/**
|
||||
* List of tab events we want to listen for.
|
||||
*/
|
||||
_tabEvents: ["TabClose", "TabSelect"],
|
||||
|
||||
init: function() {
|
||||
PageThumbs.addExpirationFilter(this);
|
||||
gBrowser.addTabsProgressListener(this);
|
||||
Services.prefs.addObserver(this.PREF_DISK_CACHE_SSL, this, false);
|
||||
|
||||
this._sslDiskCacheEnabled =
|
||||
Services.prefs.getBoolPref(this.PREF_DISK_CACHE_SSL);
|
||||
|
||||
this._tabEvents.forEach(function (aEvent) {
|
||||
gBrowser.tabContainer.addEventListener(aEvent, this, false);
|
||||
}, this);
|
||||
|
||||
this._timeouts = new WeakMap();
|
||||
},
|
||||
|
||||
uninit: function() {
|
||||
PageThumbs.removeExpirationFilter(this);
|
||||
gBrowser.removeTabsProgressListener(this);
|
||||
Services.prefs.removeObserver(this.PREF_DISK_CACHE_SSL, this);
|
||||
|
||||
this._tabEvents.forEach(function (aEvent) {
|
||||
gBrowser.tabContainer.removeEventListener(aEvent, this, false);
|
||||
}, this);
|
||||
},
|
||||
|
||||
handleEvent: function(aEvent) {
|
||||
switch (aEvent.type) {
|
||||
case "scroll":
|
||||
let browser = aEvent.currentTarget;
|
||||
if (this._timeouts.has(browser))
|
||||
this._delayedCapture(browser);
|
||||
break;
|
||||
case "TabSelect":
|
||||
this._delayedCapture(aEvent.target.linkedBrowser);
|
||||
break;
|
||||
case "TabClose": {
|
||||
this._clearTimeout(aEvent.target.linkedBrowser);
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
observe: function() {
|
||||
this._sslDiskCacheEnabled =
|
||||
Services.prefs.getBoolPref(this.PREF_DISK_CACHE_SSL);
|
||||
},
|
||||
|
||||
filterForThumbnailExpiration:
|
||||
function Thumbnails_filterForThumbnailExpiration(aCallback) {
|
||||
aCallback(this._topSiteURLs);
|
||||
},
|
||||
|
||||
/**
|
||||
* State change progress listener for all tabs.
|
||||
*/
|
||||
onStateChange: function(aBrowser, aWebProgress,
|
||||
aRequest, aStateFlags, aStatus) {
|
||||
if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
|
||||
aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK)
|
||||
this._delayedCapture(aBrowser);
|
||||
},
|
||||
|
||||
_capture: function(aBrowser) {
|
||||
// Only capture about:newtab top sites.
|
||||
if (this._topSiteURLs.indexOf(aBrowser.currentURI.spec) == -1)
|
||||
return;
|
||||
this._shouldCapture(aBrowser, function (aResult) {
|
||||
if (aResult) {
|
||||
PageThumbs.captureAndStoreIfStale(aBrowser);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
_delayedCapture: function(aBrowser) {
|
||||
if (this._timeouts.has(aBrowser))
|
||||
clearTimeout(this._timeouts.get(aBrowser));
|
||||
else
|
||||
aBrowser.addEventListener("scroll", this, true);
|
||||
|
||||
let timeout = setTimeout(function () {
|
||||
this._clearTimeout(aBrowser);
|
||||
this._capture(aBrowser);
|
||||
}.bind(this), this._captureDelayMS);
|
||||
|
||||
this._timeouts.set(aBrowser, timeout);
|
||||
},
|
||||
|
||||
_shouldCapture: function(aBrowser, aCallback) {
|
||||
// Capture only if it's the currently selected tab.
|
||||
if (aBrowser != gBrowser.selectedBrowser) {
|
||||
aCallback(false);
|
||||
return;
|
||||
}
|
||||
PageThumbs.shouldStoreThumbnail(aBrowser, aCallback);
|
||||
},
|
||||
|
||||
get _topSiteURLs() {
|
||||
return NewTabUtils.links.getLinks().reduce((urls, link) => {
|
||||
if (link)
|
||||
urls.push(link.url);
|
||||
return urls;
|
||||
}, []);
|
||||
},
|
||||
|
||||
_clearTimeout: function(aBrowser) {
|
||||
if (this._timeouts.has(aBrowser)) {
|
||||
aBrowser.removeEventListener("scroll", this, false);
|
||||
clearTimeout(this._timeouts.get(aBrowser));
|
||||
this._timeouts.delete(aBrowser);
|
||||
}
|
||||
}
|
||||
};
|
||||
1209
browser/base/content/browser.css
Normal file
8085
browser/base/content/browser.js
Normal file
936
browser/base/content/browser.xul
Normal file
|
|
@ -0,0 +1,936 @@
|
|||
#filter substitution
|
||||
<?xml version="1.0"?>
|
||||
# -*- Mode: HTML -*-
|
||||
#
|
||||
# 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/browser.css" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://browser/content/places/places.css" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://browser/content/usercontext/usercontext.css" type="text/css"?>
|
||||
#ifdef MOZ_DEVTOOLS
|
||||
<?xml-stylesheet href="chrome://devtools/skin/devtools-browser.css" type="text/css"?>
|
||||
#endif
|
||||
<?xml-stylesheet href="chrome://browser/skin/controlcenter/panel.css" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://browser/skin/customizableui/panelUI.css" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://browser/skin/" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://browser/skin/browser-lightweightTheme.css" type="text/css"?>
|
||||
|
||||
<?xul-overlay href="chrome://global/content/editMenuOverlay.xul"?>
|
||||
<?xul-overlay href="chrome://browser/content/baseMenuOverlay.xul"?>
|
||||
<?xul-overlay href="chrome://browser/content/places/placesOverlay.xul"?>
|
||||
|
||||
# All DTD information is stored in a separate file so that it can be shared by
|
||||
# hiddenWindow.xul.
|
||||
#include browser-doctype.inc
|
||||
|
||||
<window id="main-window"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:html="http://www.w3.org/1999/xhtml"
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
onload="gBrowserInit.onLoad()" onunload="gBrowserInit.onUnload()" onclose="return WindowIsClosing();"
|
||||
title="&mainWindow.title;"
|
||||
title_normal="&mainWindow.title;"
|
||||
#ifdef XP_MACOSX
|
||||
title_privatebrowsing="&mainWindow.title;&mainWindow.titlemodifiermenuseparator;&mainWindow.titlePrivateBrowsingSuffix;"
|
||||
titledefault="&mainWindow.title;"
|
||||
titlemodifier=""
|
||||
titlemodifier_normal=""
|
||||
titlemodifier_privatebrowsing="&mainWindow.titlePrivateBrowsingSuffix;"
|
||||
#else
|
||||
title_privatebrowsing="&mainWindow.titlemodifier; &mainWindow.titlePrivateBrowsingSuffix;"
|
||||
titlemodifier="&mainWindow.titlemodifier;"
|
||||
titlemodifier_normal="&mainWindow.titlemodifier;"
|
||||
titlemodifier_privatebrowsing="&mainWindow.titlemodifier; &mainWindow.titlePrivateBrowsingSuffix;"
|
||||
#endif
|
||||
#ifdef MOZ_CAN_DRAW_IN_TITLEBAR
|
||||
#ifdef XP_WIN
|
||||
chromemargin="0,2,2,2"
|
||||
#else
|
||||
chromemargin="0,-1,-1,-1"
|
||||
#endif
|
||||
tabsintitlebar="true"
|
||||
#endif
|
||||
titlemenuseparator="&mainWindow.titlemodifiermenuseparator;"
|
||||
lightweightthemes="true"
|
||||
lightweightthemesfooter="browser-bottombox"
|
||||
windowtype="navigator:browser"
|
||||
macanimationtype="document"
|
||||
screenX="4" screenY="4"
|
||||
fullscreenbutton="true"
|
||||
sizemode="normal"
|
||||
retargetdocumentfocus="urlbar"
|
||||
persist="screenX screenY width height sizemode">
|
||||
|
||||
# All JS files which are not content (only) dependent that browser.xul
|
||||
# wishes to include *must* go into the global-scripts.inc file
|
||||
# so that they can be shared by macBrowserOverlay.xul.
|
||||
#include global-scripts.inc
|
||||
<script type="application/javascript" src="chrome://browser/content/nsContextMenu.js"/>
|
||||
|
||||
<script type="application/javascript" src="chrome://global/content/contentAreaUtils.js"/>
|
||||
|
||||
<script type="application/javascript" src="chrome://browser/content/downloads/downloads.js"/>
|
||||
<script type="application/javascript" src="chrome://browser/content/downloads/indicator.js"/>
|
||||
<script type="application/javascript" src="chrome://browser/content/places/editBookmarkOverlay.js"/>
|
||||
|
||||
<script type="application/javascript" src="chrome://browser/content/themeManager.js"/>
|
||||
|
||||
# All sets except for popupsets (commands, keys, stringbundles and broadcasters) *must* go into the
|
||||
# browser-sets.inc file for sharing with hiddenWindow.xul.
|
||||
#define FULL_BROWSER_WINDOW
|
||||
#include browser-sets.inc
|
||||
#undef FULL_BROWSER_WINDOW
|
||||
|
||||
<popupset id="mainPopupSet">
|
||||
<menupopup id="tabContextMenu"
|
||||
onpopupshowing="if (event.target == this) TabContextMenu.updateContextMenu(this);"
|
||||
onpopuphidden="if (event.target == this) TabContextMenu.contextTab = null;">
|
||||
<menuitem id="context_reloadTab" label="&reloadTab.label;" accesskey="&reloadTab.accesskey;"
|
||||
oncommand="gBrowser.reloadTab(TabContextMenu.contextTab);"/>
|
||||
<menuitem id="context_toggleMuteTab" oncommand="TabContextMenu.contextTab.toggleMuteAudio();"/>
|
||||
<menuseparator/>
|
||||
<menuitem id="context_pinTab" label="&pinTab.label;"
|
||||
accesskey="&pinTab.accesskey;"
|
||||
oncommand="gBrowser.pinTab(TabContextMenu.contextTab);"/>
|
||||
<menuitem id="context_unpinTab" label="&unpinTab.label;" hidden="true"
|
||||
accesskey="&unpinTab.accesskey;"
|
||||
oncommand="gBrowser.unpinTab(TabContextMenu.contextTab);"/>
|
||||
<menuitem id="context_dublicTab" label="&dublicTab.label;"
|
||||
accesskey="&dublicTab.accesskey;"
|
||||
oncommand="duplicateTabIn(TabContextMenu.contextTab,'tabshifted');"/>
|
||||
<menuitem id="context_openTabInWindow" label="&moveToNewWindow.label;"
|
||||
accesskey="&moveToNewWindow.accesskey;"
|
||||
tbattr="tabbrowser-multiple"
|
||||
oncommand="gBrowser.replaceTabWithWindow(TabContextMenu.contextTab);"/>
|
||||
<menuseparator id="context_sendTabToDevice_separator" hidden="true"/>
|
||||
<menuseparator/>
|
||||
<menuitem id="context_reloadAllTabs" label="&reloadAllTabs.label;" accesskey="&reloadAllTabs.accesskey;"
|
||||
tbattr="tabbrowser-multiple-visible"
|
||||
oncommand="gBrowser.reloadAllTabs();"/>
|
||||
<menuitem id="context_bookmarkAllTabs"
|
||||
label="&bookmarkAllTabs.label;"
|
||||
accesskey="&bookmarkAllTabs.accesskey;"
|
||||
command="Browser:BookmarkAllTabs"/>
|
||||
<menuitem id="context_closeTabsToTheStart" label="&closeTabsToTheStart.label;" accesskey="&closeTabsToTheStart.accesskey;"
|
||||
oncommand="gBrowser.removeTabsToTheStartFrom(TabContextMenu.contextTab, {animate: true});"/>
|
||||
<menuitem id="context_closeTabsToTheEnd" label="&closeTabsToTheEnd.label;" accesskey="&closeTabsToTheEnd.accesskey;"
|
||||
oncommand="gBrowser.removeTabsToTheEndFrom(TabContextMenu.contextTab, {animate: true});"/>
|
||||
<menuitem id="context_closeOtherTabs" label="&closeOtherTabs.label;" accesskey="&closeOtherTabs.accesskey;"
|
||||
oncommand="gBrowser.removeAllTabsBut(TabContextMenu.contextTab);"/>
|
||||
<menuseparator/>
|
||||
<menuitem id="context_undoCloseTab"
|
||||
label="&undoCloseTab.label;"
|
||||
accesskey="&undoCloseTab.accesskey;"
|
||||
observes="History:UndoCloseTab"/>
|
||||
<menuitem id="context_closeTab" label="&closeTab.label;" accesskey="&closeTab.accesskey;"
|
||||
oncommand="gBrowser.removeTab(TabContextMenu.contextTab, { animate: true });"/>
|
||||
</menupopup>
|
||||
|
||||
<!-- bug 415444/582485: event.stopPropagation is here for the cloned version
|
||||
of this menupopup -->
|
||||
<menupopup id="backForwardMenu"
|
||||
onpopupshowing="return FillHistoryMenu(event.target);"
|
||||
oncommand="gotoHistoryIndex(event); event.stopPropagation();"
|
||||
onclick="checkForMiddleClick(this, event);"/>
|
||||
<tooltip id="aHTMLTooltip" page="true"/>
|
||||
<tooltip id="remoteBrowserTooltip"/>
|
||||
|
||||
<!-- for search and content formfill/pw manager -->
|
||||
|
||||
<panel type="autocomplete-richlistbox"
|
||||
id="PopupAutoComplete"
|
||||
noautofocus="true"
|
||||
hidden="true"
|
||||
overflowpadding="4"
|
||||
norolluponanchor="true" />
|
||||
|
||||
<!-- for search with one-off buttons -->
|
||||
<panel type="autocomplete" id="PopupSearchAutoComplete" noautofocus="true" hidden="true"/>
|
||||
|
||||
<!-- for url bar autocomplete -->
|
||||
<panel type="autocomplete-richlistbox"
|
||||
id="PopupAutoCompleteRichResult"
|
||||
noautofocus="true"
|
||||
hidden="true"
|
||||
flip="none"
|
||||
level="parent"
|
||||
overflowpadding="30" />
|
||||
|
||||
<!-- for date/time picker. consumeoutsideclicks is set to never, so that
|
||||
clicks on the anchored input box are never consumed. -->
|
||||
<panel id="DateTimePickerPanel"
|
||||
type="arrow"
|
||||
hidden="true"
|
||||
orient="vertical"
|
||||
noautofocus="true"
|
||||
norolluponanchor="true"
|
||||
consumeoutsideclicks="never"
|
||||
level="parent"
|
||||
tabspecific="true">
|
||||
<iframe id="dateTimePopupFrame"/>
|
||||
</panel>
|
||||
|
||||
<!-- for select dropdowns. The menupopup is what shows the list of options,
|
||||
and the popuponly menulist makes things like the menuactive attributes
|
||||
work correctly on the menupopup. ContentSelectDropdown expects the
|
||||
popuponly menulist to be its immediate parent. -->
|
||||
<menulist popuponly="true" id="ContentSelectDropdown" hidden="true">
|
||||
<menupopup rolluponmousewheel="true"
|
||||
activateontab="true" position="after_start"
|
||||
#ifdef XP_WIN
|
||||
consumeoutsideclicks="false" ignorekeys="shortcuts"
|
||||
#endif
|
||||
/>
|
||||
</menulist>
|
||||
|
||||
<!-- for invalid form error message -->
|
||||
<panel id="invalid-form-popup" type="arrow" orient="vertical" noautofocus="true" hidden="true" level="parent">
|
||||
<description/>
|
||||
</panel>
|
||||
|
||||
<panel id="editBookmarkPanel"
|
||||
type="arrow"
|
||||
orient="vertical"
|
||||
ignorekeys="true"
|
||||
hidden="true"
|
||||
tabspecific="true"
|
||||
onpopupshown="StarUI.panelShown(event);"
|
||||
aria-labelledby="editBookmarkPanelTitle">
|
||||
<row id="editBookmarkPanelHeader" align="center" hidden="true">
|
||||
<vbox align="center">
|
||||
<image id="editBookmarkPanelStarIcon"/>
|
||||
</vbox>
|
||||
<vbox>
|
||||
<label id="editBookmarkPanelTitle"/>
|
||||
<description id="editBookmarkPanelDescription"/>
|
||||
</vbox>
|
||||
</row>
|
||||
<vbox id="editBookmarkPanelContent" flex="1" hidden="true"/>
|
||||
<hbox id="editBookmarkPanelBottomButtons" pack="end">
|
||||
#ifndef XP_UNIX
|
||||
<button id="editBookmarkPanelDoneButton"
|
||||
class="editBookmarkPanelBottomButton"
|
||||
label="&editBookmark.done.label;"
|
||||
default="true"
|
||||
oncommand="StarUI.panel.hidePopup();"/>
|
||||
<button id="editBookmarkPanelRemoveButton"
|
||||
class="editBookmarkPanelBottomButton"
|
||||
oncommand="StarUI.removeBookmarkButtonCommand();"
|
||||
accesskey="&editBookmark.removeBookmark.accessKey;"/>
|
||||
#else
|
||||
<button id="editBookmarkPanelRemoveButton"
|
||||
class="editBookmarkPanelBottomButton"
|
||||
oncommand="StarUI.removeBookmarkButtonCommand();"
|
||||
accesskey="&editBookmark.removeBookmark.accessKey;"/>
|
||||
<button id="editBookmarkPanelDoneButton"
|
||||
class="editBookmarkPanelBottomButton"
|
||||
label="&editBookmark.done.label;"
|
||||
default="true"
|
||||
oncommand="StarUI.panel.hidePopup();"/>
|
||||
#endif
|
||||
</hbox>
|
||||
</panel>
|
||||
|
||||
<menupopup id="toolbar-context-menu"
|
||||
onpopupshowing="onViewToolbarsPopupShowing(event, document.getElementById('viewToolbarsMenuSeparator'));">
|
||||
<menuitem oncommand="gCustomizeMode.addToPanel(document.popupNode)"
|
||||
accesskey="&customizeMenu.moveToPanel.accesskey;"
|
||||
label="&customizeMenu.moveToPanel.label;"
|
||||
contexttype="toolbaritem"
|
||||
class="customize-context-moveToPanel"/>
|
||||
<menuitem oncommand="gCustomizeMode.removeFromArea(document.popupNode)"
|
||||
accesskey="&customizeMenu.removeFromToolbar.accesskey;"
|
||||
label="&customizeMenu.removeFromToolbar.label;"
|
||||
contexttype="toolbaritem"
|
||||
class="customize-context-removeFromToolbar"/>
|
||||
<menuitem id="toolbar-context-reloadAllTabs"
|
||||
class="toolbaritem-tabsmenu"
|
||||
contexttype="tabbar"
|
||||
oncommand="gBrowser.reloadAllTabs();"
|
||||
label="&toolbarContextMenu.reloadAllTabs.label;"
|
||||
accesskey="&toolbarContextMenu.reloadAllTabs.accesskey;"/>
|
||||
<menuitem id="toolbar-context-bookmarkAllTabs"
|
||||
class="toolbaritem-tabsmenu"
|
||||
contexttype="tabbar"
|
||||
command="Browser:BookmarkAllTabs"
|
||||
label="&toolbarContextMenu.bookmarkAllTabs.label;"
|
||||
accesskey="&toolbarContextMenu.bookmarkAllTabs.accesskey;"/>
|
||||
<menuitem id="toolbar-context-undoCloseTab"
|
||||
class="toolbaritem-tabsmenu"
|
||||
contexttype="tabbar"
|
||||
label="&toolbarContextMenu.undoCloseTab.label;"
|
||||
accesskey="&toolbarContextMenu.undoCloseTab.accesskey;"
|
||||
observes="History:UndoCloseTab"/>
|
||||
<menuseparator/>
|
||||
<menuseparator id="viewToolbarsMenuSeparator"/>
|
||||
<!-- XXXgijs: we're using oncommand handler here to avoid the event being
|
||||
redirected to the command element, thus preventing
|
||||
listeners on the menupopup or further up the tree from
|
||||
seeing the command event pass by. The observes attribute is
|
||||
here so that the menuitem is still disabled and re-enabled
|
||||
correctly. -->
|
||||
<menuitem oncommand="BrowserCustomizeToolbar()"
|
||||
observes="cmd_CustomizeToolbars"
|
||||
class="viewCustomizeToolbar"
|
||||
label="&viewCustomizeToolbar.label;"
|
||||
accesskey="&viewCustomizeToolbar.accesskey;"/>
|
||||
</menupopup>
|
||||
|
||||
<menupopup id="blockedPopupOptions"
|
||||
onpopupshowing="gPopupBlockerObserver.fillPopupList(event);"
|
||||
onpopuphiding="gPopupBlockerObserver.onPopupHiding(event);">
|
||||
<menuitem observes="blockedPopupAllowSite"/>
|
||||
<menuitem observes="blockedPopupEditSettings"/>
|
||||
<menuitem observes="blockedPopupDontShowMessage"/>
|
||||
<menuseparator observes="blockedPopupsSeparator"/>
|
||||
</menupopup>
|
||||
|
||||
<menupopup id="autohide-context"
|
||||
onpopupshowing="FullScreen.getAutohide(this.firstChild);">
|
||||
<menuitem type="checkbox" label="&fullScreenAutohide.label;"
|
||||
accesskey="&fullScreenAutohide.accesskey;"
|
||||
oncommand="FullScreen.setAutohide();"/>
|
||||
<menuseparator/>
|
||||
<menuitem label="&fullScreenExit.label;"
|
||||
accesskey="&fullScreenExit.accesskey;"
|
||||
oncommand="BrowserFullScreen();"/>
|
||||
</menupopup>
|
||||
|
||||
<menupopup id="contentAreaContextMenu" pagemenu="#page-menu-separator"
|
||||
onpopupshowing="if (event.target != this)
|
||||
return true;
|
||||
gContextMenu = new nsContextMenu(this, event.shiftKey);
|
||||
if (gContextMenu.shouldDisplay)
|
||||
updateEditUIVisibility();
|
||||
return gContextMenu.shouldDisplay;"
|
||||
onpopuphiding="if (event.target != this)
|
||||
return;
|
||||
gContextMenu.hiding();
|
||||
gContextMenu = null;
|
||||
updateEditUIVisibility();">
|
||||
#include browser-context.inc
|
||||
</menupopup>
|
||||
|
||||
<menupopup id="placesContext">
|
||||
<menuseparator id="placesContext_recentlyBookmarkedSeparator"
|
||||
ignoreitem="true"
|
||||
hidden="true"/>
|
||||
<menuitem id="placesContext_hideRecentlyBookmarked"
|
||||
label="&hideRecentlyBookmarked.label;"
|
||||
accesskey="&hideRecentlyBookmarked.accesskey;"
|
||||
oncommand="BookmarkingUI.hideRecentlyBookmarked();"
|
||||
closemenu="single"
|
||||
ignoreitem="true"
|
||||
hidden="true"/>
|
||||
<menuitem id="placesContext_showRecentlyBookmarked"
|
||||
label="&showRecentlyBookmarked.label;"
|
||||
accesskey="&showRecentlyBookmarked.accesskey;"
|
||||
oncommand="BookmarkingUI.showRecentlyBookmarked();"
|
||||
closemenu="single"
|
||||
ignoreitem="true"
|
||||
hidden="true"/>
|
||||
</menupopup>
|
||||
|
||||
<panel id="ctrlTab-panel" hidden="true" norestorefocus="true" level="top">
|
||||
<hbox>
|
||||
<button class="ctrlTab-preview" flex="1"/>
|
||||
<button class="ctrlTab-preview" flex="1"/>
|
||||
<button class="ctrlTab-preview" flex="1"/>
|
||||
<button class="ctrlTab-preview" flex="1"/>
|
||||
<button class="ctrlTab-preview" flex="1"/>
|
||||
<button class="ctrlTab-preview" flex="1"/>
|
||||
</hbox>
|
||||
<hbox pack="center">
|
||||
<button id="ctrlTab-showAll" class="ctrlTab-preview" noicon="true"/>
|
||||
</hbox>
|
||||
</panel>
|
||||
|
||||
<!-- Bookmarks and history tooltip -->
|
||||
<tooltip id="bhTooltip"/>
|
||||
|
||||
<tooltip id="tabbrowser-tab-tooltip" onpopupshowing="gBrowser.createTooltip(event);"/>
|
||||
|
||||
<tooltip id="back-button-tooltip">
|
||||
<label class="tooltip-label" value="&backButton.tooltip;"/>
|
||||
#ifdef XP_MACOSX
|
||||
<label class="tooltip-label" value="&backForwardButtonMenuMac.tooltip;"/>
|
||||
#else
|
||||
<label class="tooltip-label" value="&backForwardButtonMenu.tooltip;"/>
|
||||
#endif
|
||||
</tooltip>
|
||||
|
||||
<tooltip id="forward-button-tooltip">
|
||||
<label class="tooltip-label" value="&forwardButton.tooltip;"/>
|
||||
#ifdef XP_MACOSX
|
||||
<label class="tooltip-label" value="&backForwardButtonMenuMac.tooltip;"/>
|
||||
#else
|
||||
<label class="tooltip-label" value="&backForwardButtonMenu.tooltip;"/>
|
||||
#endif
|
||||
</tooltip>
|
||||
|
||||
#include popup-notifications.inc
|
||||
|
||||
#include ../../components/customizableui/content/panelUI.inc.xul
|
||||
#include ../../components/controlcenter/content/panel.inc.xul
|
||||
|
||||
<hbox id="downloads-animation-container" mousethrough="always">
|
||||
<vbox id="downloads-notification-anchor">
|
||||
<vbox id="downloads-indicator-notification"/>
|
||||
</vbox>
|
||||
</hbox>
|
||||
|
||||
<hbox id="bookmarked-notification-container" mousethrough="always">
|
||||
<vbox id="bookmarked-notification-anchor">
|
||||
<vbox id="bookmarked-notification"/>
|
||||
</vbox>
|
||||
<vbox id="bookmarked-notification-dropmarker-anchor">
|
||||
<image id="bookmarked-notification-dropmarker-icon"/>
|
||||
</vbox>
|
||||
</hbox>
|
||||
|
||||
<tooltip id="dynamic-shortcut-tooltip"
|
||||
onpopupshowing="UpdateDynamicShortcutTooltipText(this);"/>
|
||||
</popupset>
|
||||
|
||||
#ifdef MOZ_CAN_DRAW_IN_TITLEBAR
|
||||
<vbox id="titlebar">
|
||||
<hbox id="titlebar-content">
|
||||
<spacer id="titlebar-spacer" flex="1"/>
|
||||
<hbox id="titlebar-buttonbox-container">
|
||||
#ifdef XP_WIN
|
||||
<hbox id="private-browsing-indicator-titlebar">
|
||||
<hbox class="private-browsing-indicator"/>
|
||||
</hbox>
|
||||
#endif
|
||||
<hbox id="titlebar-buttonbox">
|
||||
<toolbarbutton class="titlebar-button" id="titlebar-min" oncommand="window.minimize();"/>
|
||||
<toolbarbutton class="titlebar-button" id="titlebar-max" oncommand="onTitlebarMaxClick();"/>
|
||||
<toolbarbutton class="titlebar-button" id="titlebar-close" command="cmd_closeWindow"/>
|
||||
</hbox>
|
||||
</hbox>
|
||||
#ifdef XP_MACOSX
|
||||
<!-- OS X does not natively support RTL for its titlebar items, so we prevent this secondary
|
||||
buttonbox from reversing order in RTL by forcing an LTR direction. -->
|
||||
<hbox id="titlebar-secondary-buttonbox" dir="ltr">
|
||||
<hbox class="private-browsing-indicator"/>
|
||||
<hbox id="titlebar-fullscreen-button"/>
|
||||
</hbox>
|
||||
#endif
|
||||
</hbox>
|
||||
</vbox>
|
||||
#endif
|
||||
|
||||
<deck flex="1" id="tab-view-deck">
|
||||
<vbox flex="1" id="browser-panel">
|
||||
|
||||
<toolbox id="navigator-toolbox" mode="icons">
|
||||
<!-- Menu -->
|
||||
<toolbar type="menubar" id="toolbar-menubar" class="chromeclass-menubar" customizable="true"
|
||||
mode="icons" iconsize="small"
|
||||
#ifdef MENUBAR_CAN_AUTOHIDE
|
||||
toolbarname="&menubarCmd.label;"
|
||||
accesskey="&menubarCmd.accesskey;"
|
||||
#if defined(MOZ_WIDGET_GTK)
|
||||
autohide="true"
|
||||
#endif
|
||||
#endif
|
||||
context="toolbar-context-menu">
|
||||
<toolbaritem id="menubar-items" align="center">
|
||||
# The entire main menubar is placed into browser-menubar.inc, so that it can be shared by
|
||||
# hiddenWindow.xul.
|
||||
#include browser-menubar.inc
|
||||
</toolbaritem>
|
||||
|
||||
#ifdef MOZ_CAN_DRAW_IN_TITLEBAR
|
||||
#ifndef XP_MACOSX
|
||||
<hbox class="titlebar-placeholder" type="caption-buttons" ordinal="1000"
|
||||
id="titlebar-placeholder-on-menubar-for-caption-buttons" persist="width"
|
||||
skipintoolbarset="true"/>
|
||||
#endif
|
||||
#endif
|
||||
</toolbar>
|
||||
|
||||
<toolbar id="TabsToolbar"
|
||||
fullscreentoolbar="true"
|
||||
customizable="true"
|
||||
mode="icons"
|
||||
iconsize="small"
|
||||
aria-label="&tabsToolbar.label;"
|
||||
context="toolbar-context-menu"
|
||||
collapsed="true">
|
||||
|
||||
#if defined(MOZ_WIDGET_GTK)
|
||||
<hbox id="private-browsing-indicator"
|
||||
skipintoolbarset="true"/>
|
||||
#endif
|
||||
|
||||
<tabs id="tabbrowser-tabs"
|
||||
class="tabbrowser-tabs"
|
||||
tabbrowser="content"
|
||||
flex="1"
|
||||
setfocus="false"
|
||||
tooltip="tabbrowser-tab-tooltip">
|
||||
<tab class="tabbrowser-tab" selected="true" visuallyselected="true" fadein="true"/>
|
||||
</tabs>
|
||||
|
||||
<toolbarbutton id="new-tab-button"
|
||||
class="toolbarbutton-1 chromeclass-toolbar-additional"
|
||||
label="&tabCmd.label;"
|
||||
command="cmd_newNavigatorTab"
|
||||
onclick="checkForMiddleClick(this, event);"
|
||||
tooltip="dynamic-shortcut-tooltip"
|
||||
ondrop="newTabButtonObserver.onDrop(event)"
|
||||
ondragover="newTabButtonObserver.onDragOver(event)"
|
||||
ondragenter="newTabButtonObserver.onDragOver(event)"
|
||||
ondragexit="newTabButtonObserver.onDragExit(event)"
|
||||
cui-areatype="toolbar"
|
||||
removable="true"/>
|
||||
|
||||
<toolbarbutton id="alltabs-button"
|
||||
class="toolbarbutton-1 chromeclass-toolbar-additional tabs-alltabs-button"
|
||||
type="menu"
|
||||
label="&listAllTabs.label;"
|
||||
tooltiptext="&listAllTabs.label;"
|
||||
removable="false">
|
||||
<menupopup id="alltabs-popup"
|
||||
position="after_end">
|
||||
<menuitem id="alltabs_undoCloseTab"
|
||||
class="menuitem-iconic"
|
||||
key="key_undoCloseTab"
|
||||
label="&undoCloseTab.label;"
|
||||
observes="History:UndoCloseTab"/>
|
||||
<menuseparator id="alltabs-popup-separator-1"/>
|
||||
<menu id="alltabs_containersTab"
|
||||
label="&newUserContext.label;">
|
||||
<menupopup id="alltabs_containersMenuTab" />
|
||||
</menu>
|
||||
<menuseparator id="alltabs-popup-separator-2"/>
|
||||
</menupopup>
|
||||
</toolbarbutton>
|
||||
|
||||
#if !defined(MOZ_WIDGET_GTK)
|
||||
<hbox class="private-browsing-indicator" skipintoolbarset="true"/>
|
||||
#endif
|
||||
#ifdef MOZ_CAN_DRAW_IN_TITLEBAR
|
||||
<hbox class="titlebar-placeholder" type="caption-buttons"
|
||||
id="titlebar-placeholder-on-TabsToolbar-for-captions-buttons" persist="width"
|
||||
#ifndef XP_MACOSX
|
||||
ordinal="1000"
|
||||
#endif
|
||||
skipintoolbarset="true"/>
|
||||
|
||||
#ifdef XP_MACOSX
|
||||
<hbox class="titlebar-placeholder" type="fullscreen-button"
|
||||
id="titlebar-placeholder-on-TabsToolbar-for-fullscreen-button" persist="width"
|
||||
skipintoolbarset="true"/>
|
||||
#endif
|
||||
#endif
|
||||
</toolbar>
|
||||
|
||||
<toolbar id="nav-bar"
|
||||
aria-label="&navbarCmd.label;"
|
||||
fullscreentoolbar="true" mode="icons" customizable="true"
|
||||
iconsize="small"
|
||||
customizationtarget="nav-bar-customization-target"
|
||||
overflowable="true"
|
||||
overflowbutton="nav-bar-overflow-button"
|
||||
overflowtarget="widget-overflow-list"
|
||||
overflowpanel="widget-overflow"
|
||||
context="toolbar-context-menu">
|
||||
|
||||
<hbox id="nav-bar-customization-target" flex="1">
|
||||
|
||||
<toolbaritem id="urlbar-container" flex="400" persist="width"
|
||||
removable="false"
|
||||
class="chromeclass-location" overflows="false">
|
||||
<toolbarbutton id="back-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
|
||||
label="&backCmd.label;"
|
||||
command="Browser:BackOrBackDuplicate"
|
||||
onclick="checkForMiddleClick(this, event);"
|
||||
tooltip="back-button-tooltip"
|
||||
context="backForwardMenu"/>
|
||||
<hbox id="urlbar-wrapper" flex="1">
|
||||
<toolbarbutton id="forward-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
|
||||
label="&forwardCmd.label;"
|
||||
command="Browser:ForwardOrForwardDuplicate"
|
||||
onclick="checkForMiddleClick(this, event);"
|
||||
tooltip="forward-button-tooltip"
|
||||
context="backForwardMenu"/>
|
||||
|
||||
<toolbarbutton id="home-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
|
||||
removable="true"
|
||||
label="&homeButton.label;"
|
||||
ondragover="homeButtonObserver.onDragOver(event)"
|
||||
ondragenter="homeButtonObserver.onDragOver(event)"
|
||||
ondrop="homeButtonObserver.onDrop(event)"
|
||||
ondragexit="homeButtonObserver.onDragExit(event)"
|
||||
key="goHome"
|
||||
onclick="BrowserGoHome(event);"
|
||||
cui-areatype="toolbar"
|
||||
aboutHomeOverrideTooltip="&abouthome.pageTitle;"/>
|
||||
<textbox id="urlbar" flex="1"
|
||||
placeholder="&urlbar.placeholder2;"
|
||||
type="autocomplete"
|
||||
autocompletesearch="unifiedcomplete"
|
||||
autocompletesearchparam="enable-actions"
|
||||
autocompletepopup="PopupAutoCompleteRichResult"
|
||||
completeselectedindex="true"
|
||||
shrinkdelay="250"
|
||||
tabscrolling="true"
|
||||
showcommentcolumn="true"
|
||||
showimagecolumn="true"
|
||||
enablehistory="true"
|
||||
maxrows="10"
|
||||
newlines="stripsurroundingwhitespace"
|
||||
ontextentered="this.handleCommand(param);"
|
||||
ontextreverted="return this.handleRevert();"
|
||||
pageproxystate="invalid">
|
||||
<!-- Use onclick instead of normal popup= syntax since the popup
|
||||
code fires onmousedown, and hence eats our favicon drag events. -->
|
||||
<box id="identity-box" role="button"
|
||||
align="center"
|
||||
aria-label="&urlbar.viewSiteInfo.label;"
|
||||
onclick="gIdentityHandler.handleIdentityButtonEvent(event);"
|
||||
onkeypress="gIdentityHandler.handleIdentityButtonEvent(event);"
|
||||
ondragstart="gIdentityHandler.onDragStart(event);">
|
||||
<image id="identity-icon"
|
||||
consumeanchor="identity-box"
|
||||
onclick="PageProxyClickHandler(event);"/>
|
||||
<image id="sharing-icon" mousethrough="always"/>
|
||||
<box id="blocked-permissions-container" align="center">
|
||||
<image data-permission-id="geo" class="blocked-permission-icon geo-icon" role="button"
|
||||
tooltiptext="&urlbar.geolocationBlocked.tooltip;"/>
|
||||
<image data-permission-id="desktop-notification" class="blocked-permission-icon desktop-notification-icon" role="button"
|
||||
tooltiptext="&urlbar.webNotificationsBlocked.tooltip;"/>
|
||||
<image data-permission-id="camera" class="blocked-permission-icon camera-icon" role="button"
|
||||
tooltiptext="&urlbar.cameraBlocked.tooltip;"/>
|
||||
<image data-permission-id="microphone" class="blocked-permission-icon microphone-icon" role="button"
|
||||
tooltiptext="&urlbar.microphoneBlocked.tooltip;"/>
|
||||
<image data-permission-id="screen" class="blocked-permission-icon screen-icon" role="button"
|
||||
tooltiptext="&urlbar.screenBlocked.tooltip;"/>
|
||||
</box>
|
||||
<box id="notification-popup-box"
|
||||
hidden="true"
|
||||
onmouseover="document.getElementById('identity-icon').classList.add('no-hover');"
|
||||
onmouseout="document.getElementById('identity-icon').classList.remove('no-hover');"
|
||||
align="center">
|
||||
<image id="default-notification-icon" class="notification-anchor-icon" role="button"
|
||||
tooltiptext="&urlbar.defaultNotificationAnchor.tooltip;"/>
|
||||
<image id="geo-notification-icon" class="notification-anchor-icon geo-icon" role="button"
|
||||
tooltiptext="&urlbar.geolocationNotificationAnchor.tooltip;"/>
|
||||
<image id="addons-notification-icon" class="notification-anchor-icon install-icon" role="button"
|
||||
tooltiptext="&urlbar.addonsNotificationAnchor.tooltip;"/>
|
||||
<image id="indexedDB-notification-icon" class="notification-anchor-icon indexedDB-icon" role="button"
|
||||
tooltiptext="&urlbar.indexedDBNotificationAnchor.tooltip;"/>
|
||||
<image id="password-notification-icon" class="notification-anchor-icon login-icon" role="button"
|
||||
tooltiptext="&urlbar.passwordNotificationAnchor.tooltip;"/>
|
||||
<image id="plugins-notification-icon" class="notification-anchor-icon plugin-icon" role="button"
|
||||
tooltiptext="&urlbar.pluginsNotificationAnchor.tooltip;"/>
|
||||
<image id="web-notifications-notification-icon" class="notification-anchor-icon desktop-notification-icon" role="button"
|
||||
tooltiptext="&urlbar.webNotificationAnchor.tooltip;"/>
|
||||
<image id="webRTC-shareDevices-notification-icon" class="notification-anchor-icon camera-icon" role="button"
|
||||
tooltiptext="&urlbar.webRTCShareDevicesNotificationAnchor.tooltip;"/>
|
||||
<image id="webRTC-shareMicrophone-notification-icon" class="notification-anchor-icon microphone-icon" role="button"
|
||||
tooltiptext="&urlbar.webRTCShareMicrophoneNotificationAnchor.tooltip;"/>
|
||||
<image id="webRTC-shareScreen-notification-icon" class="notification-anchor-icon screen-icon" role="button"
|
||||
tooltiptext="&urlbar.webRTCShareScreenNotificationAnchor.tooltip;"/>
|
||||
<image id="servicesInstall-notification-icon" class="notification-anchor-icon service-icon" role="button"
|
||||
tooltiptext="&urlbar.servicesNotificationAnchor.tooltip;"/>
|
||||
<image id="eme-notification-icon" class="notification-anchor-icon drm-icon" role="button"
|
||||
tooltiptext="&urlbar.emeNotificationAnchor.tooltip;"/>
|
||||
</box>
|
||||
<image id="tracking-protection-icon"/>
|
||||
<image id="connection-icon"/>
|
||||
<hbox id="identity-icon-labels">
|
||||
<label id="identity-icon-label" class="plain" flex="1"/>
|
||||
<label id="identity-icon-country-label" class="plain"/>
|
||||
</hbox>
|
||||
</box>
|
||||
<box id="urlbar-display-box" align="center">
|
||||
<label id="switchtab" class="urlbar-display urlbar-display-switchtab" value="&urlbar.switchToTab.label;"/>
|
||||
<label id="extension" class="urlbar-display urlbar-display-extension" value="&urlbar.extension.label;"/>
|
||||
</box>
|
||||
<hbox id="urlbar-icons">
|
||||
<image id="page-report-button"
|
||||
class="urlbar-icon"
|
||||
hidden="true"
|
||||
tooltiptext="&pageReportIcon.tooltip;"
|
||||
onmousedown="gPopupBlockerObserver.onReportButtonMousedown(event);"/>
|
||||
<image id="reader-mode-button"
|
||||
class="urlbar-icon"
|
||||
hidden="true"
|
||||
onclick="ReaderParent.buttonClick(event);"/>
|
||||
<toolbarbutton id="urlbar-zoom-button"
|
||||
onclick="FullZoom.reset();"
|
||||
tooltiptext="&urlbar.zoomReset.tooltip;"
|
||||
hidden="true"/>
|
||||
</hbox>
|
||||
<hbox id="userContext-icons" hidden="true">
|
||||
<label id="userContext-label"/>
|
||||
<image id="userContext-indicator"/>
|
||||
</hbox>
|
||||
<toolbarbutton id="urlbar-go-button"
|
||||
class="chromeclass-toolbar-additional"
|
||||
onclick="gURLBar.handleCommand(event);"
|
||||
tooltiptext="&goEndCap.tooltip;"/>
|
||||
<toolbarbutton id="urlbar-reload-button"
|
||||
class="chromeclass-toolbar-additional"
|
||||
command="Browser:ReloadOrDuplicate"
|
||||
onclick="checkForMiddleClick(this, event);"
|
||||
tooltiptext="&reloadButton.tooltip;"/>
|
||||
<toolbarbutton id="urlbar-stop-button"
|
||||
class="chromeclass-toolbar-additional"
|
||||
command="Browser:Stop"
|
||||
tooltiptext="&stopButton.tooltip;"/>
|
||||
</textbox>
|
||||
</hbox>
|
||||
</toolbaritem>
|
||||
|
||||
<!-- This is a placeholder for the Downloads Indicator. It is visible
|
||||
during the customization of the toolbar, in the palette, and before
|
||||
the Downloads Indicator overlay is loaded. -->
|
||||
<toolbarbutton id="downloads-button"
|
||||
class="toolbarbutton-1 chromeclass-toolbar-additional badged-button"
|
||||
key="key_openDownloads"
|
||||
oncommand="DownloadsIndicatorView.onCommand(event);"
|
||||
ondrop="DownloadsIndicatorView.onDrop(event);"
|
||||
ondragover="DownloadsIndicatorView.onDragOver(event);"
|
||||
ondragenter="DownloadsIndicatorView.onDragOver(event);"
|
||||
label="&downloads.label;"
|
||||
removable="true"
|
||||
cui-areatype="toolbar"
|
||||
tooltip="dynamic-shortcut-tooltip"/>
|
||||
</hbox>
|
||||
|
||||
<toolbarbutton id="nav-bar-overflow-button"
|
||||
class="toolbarbutton-1 chromeclass-toolbar-additional overflow-button"
|
||||
skipintoolbarset="true"
|
||||
tooltiptext="&navbarOverflow.label;"/>
|
||||
|
||||
<toolbaritem id="PanelUI-button"
|
||||
class="chromeclass-toolbar-additional"
|
||||
removable="false">
|
||||
<toolbarbutton id="PanelUI-menu-button"
|
||||
class="toolbarbutton-1 badged-button"
|
||||
consumeanchor="PanelUI-button"
|
||||
label="&brandShortName;"
|
||||
tooltiptext="&appmenu.tooltip;"/>
|
||||
</toolbaritem>
|
||||
|
||||
<hbox id="window-controls" hidden="true" pack="end" skipintoolbarset="true"
|
||||
ordinal="1000">
|
||||
<toolbarbutton id="minimize-button"
|
||||
tooltiptext="&fullScreenMinimize.tooltip;"
|
||||
oncommand="window.minimize();"/>
|
||||
|
||||
<toolbarbutton id="restore-button"
|
||||
#ifdef XP_MACOSX
|
||||
# Prior to 10.7 there wasn't a native fullscreen button so we use #restore-button
|
||||
# to exit fullscreen and want it to behave like other toolbar buttons.
|
||||
class="toolbarbutton-1"
|
||||
#endif
|
||||
tooltiptext="&fullScreenRestore.tooltip;"
|
||||
oncommand="BrowserFullScreen();"/>
|
||||
|
||||
<toolbarbutton id="close-button"
|
||||
tooltiptext="&fullScreenClose.tooltip;"
|
||||
oncommand="BrowserTryToCloseWindow();"/>
|
||||
</hbox>
|
||||
</toolbar>
|
||||
|
||||
<toolbarset id="customToolbars" context="toolbar-context-menu"/>
|
||||
|
||||
<toolbar id="PersonalToolbar"
|
||||
mode="icons" iconsize="small"
|
||||
class="chromeclass-directories"
|
||||
context="toolbar-context-menu"
|
||||
toolbarname="&personalbarCmd.label;" accesskey="&personalbarCmd.accesskey;"
|
||||
collapsed="true"
|
||||
customizable="true">
|
||||
<toolbaritem id="personal-bookmarks"
|
||||
title="&bookmarksToolbarItem.label;"
|
||||
cui-areatype="toolbar"
|
||||
removable="true">
|
||||
<toolbarbutton id="bookmarks-toolbar-placeholder"
|
||||
class="toolbarbutton-1"
|
||||
mousethrough="never"
|
||||
label="&bookmarksToolbarItem.label;"
|
||||
oncommand="PlacesToolbarHelper.onPlaceholderCommand();"/>
|
||||
<hbox flex="1"
|
||||
id="PlacesToolbar"
|
||||
context="placesContext"
|
||||
onclick="BookmarksEventHandler.onClick(event, this._placesView);"
|
||||
oncommand="BookmarksEventHandler.onCommand(event, this._placesView);"
|
||||
tooltip="bhTooltip"
|
||||
popupsinherittooltip="true">
|
||||
<hbox flex="1">
|
||||
<hbox id="PlacesToolbarDropIndicatorHolder" align="center" collapsed="true">
|
||||
<image id="PlacesToolbarDropIndicator"
|
||||
mousethrough="always"
|
||||
collapsed="true"/>
|
||||
</hbox>
|
||||
<scrollbox orient="horizontal"
|
||||
id="PlacesToolbarItems"
|
||||
flex="1"/>
|
||||
<toolbarbutton type="menu"
|
||||
id="PlacesChevron"
|
||||
class="chevron"
|
||||
mousethrough="never"
|
||||
collapsed="true"
|
||||
tooltiptext="&bookmarksToolbarChevron.tooltip;"
|
||||
onpopupshowing="document.getElementById('PlacesToolbar')
|
||||
._placesView._onChevronPopupShowing(event);">
|
||||
<menupopup id="PlacesChevronPopup"
|
||||
placespopup="true"
|
||||
tooltip="bhTooltip" popupsinherittooltip="true"
|
||||
context="placesContext"/>
|
||||
</toolbarbutton>
|
||||
</hbox>
|
||||
</hbox>
|
||||
</toolbaritem>
|
||||
</toolbar>
|
||||
|
||||
<!-- This is a shim which will go away ASAP. See bug 749804 for details -->
|
||||
<toolbar id="addon-bar" toolbar-delegate="nav-bar" mode="icons" iconsize="small"
|
||||
customizable="true">
|
||||
<hbox id="addonbar-closebutton"/>
|
||||
<statusbar id="status-bar"/>
|
||||
</toolbar>
|
||||
|
||||
<toolbarpalette id="BrowserToolbarPalette">
|
||||
|
||||
# Update primaryToolbarButtons in browser/themes/shared/browser.inc when adding
|
||||
# or removing default items with the toolbarbutton-1 class.
|
||||
|
||||
<toolbarbutton id="print-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
|
||||
#ifdef XP_MACOSX
|
||||
command="cmd_print"
|
||||
tooltip="dynamic-shortcut-tooltip"
|
||||
#else
|
||||
command="cmd_printPreview"
|
||||
tooltiptext="&printButton.tooltip;"
|
||||
#endif
|
||||
label="&printButton.label;"/>
|
||||
|
||||
|
||||
<toolbarbutton id="new-window-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
|
||||
label="&newNavigatorCmd.label;"
|
||||
command="key_newNavigator"
|
||||
tooltip="dynamic-shortcut-tooltip"
|
||||
ondrop="newWindowButtonObserver.onDrop(event)"
|
||||
ondragover="newWindowButtonObserver.onDragOver(event)"
|
||||
ondragenter="newWindowButtonObserver.onDragOver(event)"
|
||||
ondragexit="newWindowButtonObserver.onDragExit(event)"/>
|
||||
|
||||
<toolbarbutton id="fullscreen-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
|
||||
observes="View:FullScreen"
|
||||
type="checkbox"
|
||||
label="&fullScreenCmd.label;"
|
||||
tooltip="dynamic-shortcut-tooltip"/>
|
||||
|
||||
#ifdef MOZ_SERVICES_SYNC
|
||||
<toolbarbutton id="sync-button"
|
||||
class="toolbarbutton-1 chromeclass-toolbar-additional"
|
||||
label="&syncToolbarButton.label;"
|
||||
oncommand="gSyncUI.handleToolbarButton();"/>>
|
||||
|
||||
<toolbarbutton id="sync-tabs-button"
|
||||
class="toolbarbutton-1 chromeclass-toolbar-additional"
|
||||
label="&syncTabsToolbarButton.label;"
|
||||
oncommand="BrowserOpenSyncTabs();"/>
|
||||
#endif
|
||||
</toolbarpalette>
|
||||
</toolbox>
|
||||
|
||||
<hbox id="fullscr-toggler" hidden="true"/>
|
||||
|
||||
<deck id="content-deck" flex="1">
|
||||
<hbox flex="1" id="browser">
|
||||
<vbox id="browser-border-start" hidden="true" layer="true"/>
|
||||
<vbox id="sidebar-box" hidden="true" class="chromeclass-extrachrome">
|
||||
<sidebarheader id="sidebar-header" align="center">
|
||||
<label id="sidebar-title" persist="value" flex="1" crop="end" control="sidebar"/>
|
||||
<image id="sidebar-throbber"/>
|
||||
<toolbarbutton class="close-icon tabbable" tooltiptext="&sidebarCloseButton.tooltip;" oncommand="SidebarUI.hide();"/>
|
||||
</sidebarheader>
|
||||
<browser id="sidebar" flex="1" autoscroll="false" disablehistory="true" disablefullscreen="true"
|
||||
style="min-width: 14em; width: 18em; max-width: 36em;" tooltip="aHTMLTooltip"/>
|
||||
</vbox>
|
||||
|
||||
<splitter id="sidebar-splitter" class="chromeclass-extrachrome sidebar-splitter" hidden="true"/>
|
||||
<vbox id="appcontent" flex="1">
|
||||
<notificationbox id="high-priority-global-notificationbox" notificationside="top"/>
|
||||
<tabbrowser id="content"
|
||||
flex="1" contenttooltip="aHTMLTooltip"
|
||||
tabcontainer="tabbrowser-tabs"
|
||||
contentcontextmenu="contentAreaContextMenu"
|
||||
autocompletepopup="PopupAutoComplete"
|
||||
selectmenulist="ContentSelectDropdown"
|
||||
datetimepicker="DateTimePickerPanel"
|
||||
authdosprotected="true" />
|
||||
</vbox>
|
||||
<vbox id="browser-border-end" hidden="true" layer="true"/>
|
||||
</hbox>
|
||||
#include ../../components/customizableui/content/customizeMode.inc.xul
|
||||
</deck>
|
||||
|
||||
<html:div id="fullscreen-warning" class="pointerlockfswarning" hidden="true">
|
||||
<html:div class="pointerlockfswarning-domain-text">
|
||||
&fullscreenWarning.beforeDomain.label;
|
||||
<html:span class="pointerlockfswarning-domain"/>
|
||||
&fullscreenWarning.afterDomain.label;
|
||||
</html:div>
|
||||
<html:div class="pointerlockfswarning-generic-text">
|
||||
&fullscreenWarning.generic.label;
|
||||
</html:div>
|
||||
<html:button id="fullscreen-exit-button"
|
||||
onclick="FullScreen.exitDomFullScreen();">
|
||||
#ifdef XP_MACOSX
|
||||
&exitDOMFullscreenMac.button;
|
||||
#else
|
||||
&exitDOMFullscreen.button;
|
||||
#endif
|
||||
</html:button>
|
||||
</html:div>
|
||||
|
||||
<html:div id="pointerlock-warning" class="pointerlockfswarning" hidden="true">
|
||||
<html:div class="pointerlockfswarning-domain-text">
|
||||
&pointerlockWarning.beforeDomain.label;
|
||||
<html:span class="pointerlockfswarning-domain"/>
|
||||
&pointerlockWarning.afterDomain.label;
|
||||
</html:div>
|
||||
<html:div class="pointerlockfswarning-generic-text">
|
||||
&pointerlockWarning.generic.label;
|
||||
</html:div>
|
||||
</html:div>
|
||||
|
||||
<vbox id="browser-bottombox" layer="true">
|
||||
<notificationbox id="global-notificationbox" notificationside="bottom"/>
|
||||
</vbox>
|
||||
|
||||
<svg:svg height="0">
|
||||
#include tab-shape.inc.svg
|
||||
<svg:clipPath id="urlbar-back-button-clip-path">
|
||||
#ifndef XP_MACOSX
|
||||
<svg:path d="M -9,-4 l 0,1 a 15 15 0 0,1 0,30 l 0,1 l 10000,0 l 0,-32 l -10000,0 z" />
|
||||
#else
|
||||
<svg:path d="M -11,-5 a 16 16 0 0 1 0,34 l 10000,0 l 0,-34 l -10000,0 z"/>
|
||||
#endif
|
||||
</svg:clipPath>
|
||||
#ifdef XP_WIN
|
||||
<svg:clipPath id="urlbar-back-button-clip-path-win10">
|
||||
<svg:path d="M -6,-2 l 0,1 a 15 15 0 0,1 0,30 l 0,1 l 10000,0 l 0,-32 l -10000,0 z" />
|
||||
</svg:clipPath>
|
||||
#endif
|
||||
</svg:svg>
|
||||
|
||||
</vbox>
|
||||
# <iframe id="tab-view"> is dynamically appended as the 2nd child of #tab-view-deck.
|
||||
# Introducing the iframe dynamically, as needed, was found to be better than
|
||||
# starting with an empty iframe here in browser.xul from a Ts standpoint.
|
||||
</deck>
|
||||
|
||||
</window>
|
||||
12
browser/base/content/browserMountPoints.inc
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<stringbundleset id="stringbundleset"/>
|
||||
|
||||
<commandset id="mainCommandSet"/>
|
||||
<commandset id="baseMenuCommandSet"/>
|
||||
<commandset id="placesCommands"/>
|
||||
|
||||
<broadcasterset id="mainBroadcasterSet"/>
|
||||
|
||||
<keyset id="mainKeyset"/>
|
||||
<keyset id="baseMenuKeyset"/>
|
||||
|
||||
<menubar id="main-menubar"/>
|
||||
1437
browser/base/content/content.js
Normal file
216
browser/base/content/contentSearchUI.css
Normal file
|
|
@ -0,0 +1,216 @@
|
|||
/* 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/. */
|
||||
|
||||
.contentSearchSuggestionTable {
|
||||
background-color: hsla(0,0%,100%,.99);
|
||||
border: 1px solid hsla(0, 0%, 0%, .2);
|
||||
border-top: none;
|
||||
box-shadow: 0 5px 10px hsla(0, 0%, 0%, .1);
|
||||
position: absolute;
|
||||
left: 0;
|
||||
z-index: 1001;
|
||||
-moz-user-select: none;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.contentSearchSuggestionTable:-moz-dir(rtl) {
|
||||
left: auto;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.contentSearchSuggestionsList {
|
||||
border-bottom: 1px solid hsl(0, 0%, 92%);
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.contentSearchSuggestionTable,
|
||||
.contentSearchSuggestionsList {
|
||||
border-spacing: 0;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
text-align: start;
|
||||
}
|
||||
|
||||
.contentSearchHeaderRow,
|
||||
.contentSearchSuggestionRow {
|
||||
margin: 0;
|
||||
max-width: inherit;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.contentSearchHeaderRow > td > img,
|
||||
.contentSearchSuggestionRow > td > .historyIcon {
|
||||
margin-inline-end: 8px;
|
||||
margin-bottom: -3px;
|
||||
}
|
||||
|
||||
.contentSearchSuggestionTable .historyIcon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
display: inline-block;
|
||||
background-image: url("chrome://browser/skin/search-history-icon.svg#search-history-icon");
|
||||
}
|
||||
|
||||
.contentSearchSuggestionRow.selected > td > .historyIcon {
|
||||
background-image: url("chrome://browser/skin/search-history-icon.svg#search-history-icon-active");
|
||||
}
|
||||
|
||||
.contentSearchHeader > img {
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.contentSearchSuggestionRow.remote > td > .historyIcon {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.contentSearchSuggestionRow.selected {
|
||||
background-color: Highlight;
|
||||
color: HighlightText;
|
||||
}
|
||||
|
||||
.contentSearchHeader,
|
||||
.contentSearchSuggestionEntry {
|
||||
margin: 0;
|
||||
max-width: inherit;
|
||||
overflow: hidden;
|
||||
padding: 4px 10px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-size: 75%;
|
||||
}
|
||||
|
||||
.contentSearchHeader {
|
||||
background-color: hsl(0, 0%, 97%);
|
||||
color: #666;
|
||||
border-bottom: 1px solid hsl(0, 0%, 92%);
|
||||
}
|
||||
|
||||
.contentSearchSuggestionsContainer {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border-spacing: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.contentSearchSearchWithHeaderSearchText {
|
||||
white-space: pre;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.contentSearchOneOffItem {
|
||||
-moz-appearance: none;
|
||||
height: 32px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: none;
|
||||
background: none;
|
||||
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAWCAYAAAABxvaqAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gofECQNNVW2/AAAABBJREFUGFdjOHPmzH8GehEA/KpKg9YTf4AAAAAASUVORK5CYII=');
|
||||
background-repeat: no-repeat;
|
||||
background-position: right center;
|
||||
}
|
||||
|
||||
.contentSearchOneOffItem:-moz-dir(rtl) {
|
||||
background-position: left center;
|
||||
}
|
||||
|
||||
.contentSearchOneOffItem > img {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin-bottom: -2px;
|
||||
}
|
||||
|
||||
.contentSearchOneOffItem:not(.last-row) {
|
||||
border-bottom: 1px solid hsl(0, 0%, 92%);
|
||||
}
|
||||
|
||||
.contentSearchOneOffItem.end-of-row {
|
||||
background-image: none;
|
||||
}
|
||||
|
||||
.contentSearchOneOffItem.selected {
|
||||
background-color: Highlight;
|
||||
background-image: none;
|
||||
}
|
||||
|
||||
.contentSearchOneOffsTable {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.contentSearchSettingsButton {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 32px;
|
||||
border: none;
|
||||
border-top: 1px solid hsla(0, 0%, 0%, .08);
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.contentSearchSettingsButton.selected {
|
||||
background-color: hsl(0, 0%, 90%);
|
||||
}
|
||||
|
||||
.contentSearchSettingsButton:active {
|
||||
background-color: hsl(0, 0%, 85%);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.contentSearchSuggestionTable,
|
||||
.contentSearchSuggestionsList,
|
||||
.contentSearchHeader,
|
||||
.contentSearchSuggestionEntry,
|
||||
.contentSearchSettingsButton {
|
||||
color-scheme: dark;
|
||||
}
|
||||
|
||||
.contentSearchSuggestionTable {
|
||||
background-color: #2b3037;
|
||||
border-color: #4b535d;
|
||||
box-shadow: 0 5px 10px rgba(0, 0, 0, 0.45);
|
||||
color: #f1f4f7;
|
||||
}
|
||||
|
||||
.contentSearchSuggestionsList {
|
||||
border-bottom-color: #4b535d;
|
||||
}
|
||||
|
||||
.contentSearchSuggestionTable .historyIcon {
|
||||
filter: invert(100%) brightness(1.05);
|
||||
}
|
||||
|
||||
.contentSearchHeader {
|
||||
background-color: #252a31;
|
||||
color: #b9c6d4;
|
||||
border-bottom-color: #4b535d;
|
||||
}
|
||||
|
||||
.contentSearchSuggestionRow.selected,
|
||||
.contentSearchOneOffItem.selected {
|
||||
background-color: #1b84d6;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.contentSearchOneOffItem:not(.last-row) {
|
||||
border-bottom-color: #3d434b;
|
||||
}
|
||||
|
||||
.contentSearchSettingsButton {
|
||||
border-top-color: #4b535d;
|
||||
background-color: #252a31;
|
||||
color: #d6dde5;
|
||||
}
|
||||
|
||||
.contentSearchSettingsButton.selected {
|
||||
background-color: #343a43;
|
||||
}
|
||||
|
||||
.contentSearchSettingsButton:active {
|
||||
background-color: #3e454f;
|
||||
}
|
||||
}
|
||||
915
browser/base/content/contentSearchUI.js
Normal file
|
|
@ -0,0 +1,915 @@
|
|||
/* 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.ContentSearchUIController = (function () {
|
||||
|
||||
const MAX_DISPLAYED_SUGGESTIONS = 6;
|
||||
const SUGGESTION_ID_PREFIX = "searchSuggestion";
|
||||
const ONE_OFF_ID_PREFIX = "oneOff";
|
||||
|
||||
const HTML_NS = "http://www.w3.org/1999/xhtml";
|
||||
|
||||
/**
|
||||
* Creates a new object that manages search suggestions and their UI for a text
|
||||
* box.
|
||||
*
|
||||
* The UI consists of an html:table that's inserted into the DOM after the given
|
||||
* text box and styled so that it appears as a dropdown below the text box.
|
||||
*
|
||||
* @param inputElement
|
||||
* Search suggestions will be based on the text in this text box.
|
||||
* Assumed to be an html:input. xul:textbox is untested but might work.
|
||||
* @param tableParent
|
||||
* The suggestion table is appended as a child to this element. Since
|
||||
* the table is absolutely positioned and its top and left values are set
|
||||
* to be relative to the top and left of the page, either the parent and
|
||||
* all its ancestors should not be positioned elements (i.e., their
|
||||
* positions should be "static"), or the parent's position should be the
|
||||
* top left of the page.
|
||||
* @param healthReportKey
|
||||
* This will be sent with the search data for FHR to record the search.
|
||||
* @param searchPurpose
|
||||
* Sent with search data, see nsISearchEngine.getSubmission.
|
||||
* @param idPrefix
|
||||
* The IDs of elements created by the object will be prefixed with this
|
||||
* string.
|
||||
*/
|
||||
function ContentSearchUIController(inputElement, tableParent, healthReportKey,
|
||||
searchPurpose, idPrefix="") {
|
||||
this.input = inputElement;
|
||||
this._idPrefix = idPrefix;
|
||||
this._healthReportKey = healthReportKey;
|
||||
this._searchPurpose = searchPurpose;
|
||||
|
||||
let tableID = idPrefix + "searchSuggestionTable";
|
||||
this.input.autocomplete = "off";
|
||||
this.input.setAttribute("aria-autocomplete", "true");
|
||||
this.input.setAttribute("aria-controls", tableID);
|
||||
tableParent.appendChild(this._makeTable(tableID));
|
||||
|
||||
this.input.addEventListener("keypress", this);
|
||||
this.input.addEventListener("input", this);
|
||||
this.input.addEventListener("focus", this);
|
||||
this.input.addEventListener("blur", this);
|
||||
window.addEventListener("ContentSearchService", this);
|
||||
|
||||
this._stickyInputValue = "";
|
||||
this._hideSuggestions();
|
||||
|
||||
this._getSearchEngines();
|
||||
this._getStrings();
|
||||
}
|
||||
|
||||
ContentSearchUIController.prototype = {
|
||||
|
||||
// The timeout (ms) of the remote suggestions. Corresponds to
|
||||
// SearchSuggestionController.remoteTimeout. Uses
|
||||
// SearchSuggestionController's default timeout if falsey.
|
||||
remoteTimeout: undefined,
|
||||
_oneOffButtons: [],
|
||||
// Setting up the one off buttons causes an uninterruptible reflow. If we
|
||||
// receive the list of engines while the newtab page is loading, this reflow
|
||||
// may regress performance - so we set this flag and only set up the buttons
|
||||
// if it's set when the suggestions table is actually opened.
|
||||
_pendingOneOffRefresh: undefined,
|
||||
|
||||
get defaultEngine() {
|
||||
return this._defaultEngine;
|
||||
},
|
||||
|
||||
set defaultEngine(engine) {
|
||||
if (this._defaultEngine && this._defaultEngine.icon) {
|
||||
URL.revokeObjectURL(this._defaultEngine.icon);
|
||||
}
|
||||
let icon;
|
||||
if (engine.iconBuffer) {
|
||||
icon = this._getFaviconURIFromBuffer(engine.iconBuffer);
|
||||
}
|
||||
else {
|
||||
icon = this._getImageURIForCurrentResolution(
|
||||
"chrome://mozapps/skin/places/defaultFavicon.png");
|
||||
}
|
||||
this._defaultEngine = {
|
||||
name: engine.name,
|
||||
icon: icon,
|
||||
};
|
||||
this._updateDefaultEngineHeader();
|
||||
|
||||
if (engine && document.activeElement == this.input) {
|
||||
this._speculativeConnect();
|
||||
}
|
||||
},
|
||||
|
||||
get engines() {
|
||||
return this._engines;
|
||||
},
|
||||
|
||||
set engines(val) {
|
||||
this._engines = val;
|
||||
this._pendingOneOffRefresh = true;
|
||||
},
|
||||
|
||||
// The selectedIndex is the index of the element with the "selected" class in
|
||||
// the list obtained by concatenating the suggestion rows, one-off buttons, and
|
||||
// search settings button.
|
||||
get selectedIndex() {
|
||||
let allElts = [...this._suggestionsList.children,
|
||||
...this._oneOffButtons,
|
||||
document.getElementById("contentSearchSettingsButton")];
|
||||
for (let i = 0; i < allElts.length; ++i) {
|
||||
let elt = allElts[i];
|
||||
if (elt.classList.contains("selected")) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
},
|
||||
|
||||
set selectedIndex(idx) {
|
||||
// Update the table's rows, and the input when there is a selection.
|
||||
this._table.removeAttribute("aria-activedescendant");
|
||||
this.input.removeAttribute("aria-activedescendant");
|
||||
|
||||
let allElts = [...this._suggestionsList.children,
|
||||
...this._oneOffButtons,
|
||||
document.getElementById("contentSearchSettingsButton")];
|
||||
// If we are selecting a suggestion and a one-off is selected, don't deselect it.
|
||||
let excludeIndex = idx < this.numSuggestions && this.selectedButtonIndex > -1 ?
|
||||
this.numSuggestions + this.selectedButtonIndex : -1;
|
||||
for (let i = 0; i < allElts.length; ++i) {
|
||||
let elt = allElts[i];
|
||||
let ariaSelectedElt = i < this.numSuggestions ? elt.firstChild : elt;
|
||||
if (i == idx) {
|
||||
elt.classList.add("selected");
|
||||
ariaSelectedElt.setAttribute("aria-selected", "true");
|
||||
this.input.setAttribute("aria-activedescendant", ariaSelectedElt.id);
|
||||
}
|
||||
else if (i != excludeIndex) {
|
||||
elt.classList.remove("selected");
|
||||
ariaSelectedElt.setAttribute("aria-selected", "false");
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
get selectedButtonIndex() {
|
||||
let elts = [...this._oneOffButtons,
|
||||
document.getElementById("contentSearchSettingsButton")];
|
||||
for (let i = 0; i < elts.length; ++i) {
|
||||
if (elts[i].classList.contains("selected")) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
},
|
||||
|
||||
set selectedButtonIndex(idx) {
|
||||
let elts = [...this._oneOffButtons,
|
||||
document.getElementById("contentSearchSettingsButton")];
|
||||
for (let i = 0; i < elts.length; ++i) {
|
||||
let elt = elts[i];
|
||||
if (i == idx) {
|
||||
elt.classList.add("selected");
|
||||
elt.setAttribute("aria-selected", "true");
|
||||
}
|
||||
else {
|
||||
elt.classList.remove("selected");
|
||||
elt.setAttribute("aria-selected", "false");
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
get selectedEngineName() {
|
||||
let selectedElt = this._oneOffsTable.querySelector(".selected");
|
||||
if (selectedElt) {
|
||||
return selectedElt.engineName;
|
||||
}
|
||||
return this.defaultEngine.name;
|
||||
},
|
||||
|
||||
get numSuggestions() {
|
||||
return this._suggestionsList.children.length;
|
||||
},
|
||||
|
||||
selectAndUpdateInput: function (idx) {
|
||||
this.selectedIndex = idx;
|
||||
let newValue = this.suggestionAtIndex(idx) || this._stickyInputValue;
|
||||
// Setting the input value when the value has not changed commits the current
|
||||
// IME composition, which we don't want to do.
|
||||
if (this.input.value != newValue) {
|
||||
this.input.value = newValue;
|
||||
}
|
||||
this._updateSearchWithHeader();
|
||||
},
|
||||
|
||||
suggestionAtIndex: function (idx) {
|
||||
let row = this._suggestionsList.children[idx];
|
||||
return row ? row.textContent : null;
|
||||
},
|
||||
|
||||
deleteSuggestionAtIndex: function (idx) {
|
||||
// Only form history suggestions can be deleted.
|
||||
if (this.isFormHistorySuggestionAtIndex(idx)) {
|
||||
let suggestionStr = this.suggestionAtIndex(idx);
|
||||
this._sendMsg("RemoveFormHistoryEntry", suggestionStr);
|
||||
this._suggestionsList.children[idx].remove();
|
||||
this.selectAndUpdateInput(-1);
|
||||
}
|
||||
},
|
||||
|
||||
isFormHistorySuggestionAtIndex: function (idx) {
|
||||
let row = this._suggestionsList.children[idx];
|
||||
return row && row.classList.contains("formHistory");
|
||||
},
|
||||
|
||||
addInputValueToFormHistory: function () {
|
||||
this._sendMsg("AddFormHistoryEntry", this.input.value);
|
||||
},
|
||||
|
||||
handleEvent: function (event) {
|
||||
this["_on" + event.type[0].toUpperCase() + event.type.substr(1)](event);
|
||||
},
|
||||
|
||||
_onCommand: function(aEvent) {
|
||||
if (this.selectedButtonIndex == this._oneOffButtons.length) {
|
||||
// Settings button was selected.
|
||||
this._sendMsg("ManageEngines");
|
||||
return;
|
||||
}
|
||||
|
||||
this.search(aEvent);
|
||||
|
||||
if (aEvent) {
|
||||
aEvent.preventDefault();
|
||||
}
|
||||
},
|
||||
|
||||
search: function (aEvent) {
|
||||
if (!this.defaultEngine) {
|
||||
return; // Not initialized yet.
|
||||
}
|
||||
|
||||
let searchText = this.input;
|
||||
let searchTerms;
|
||||
if (this._table.hidden ||
|
||||
aEvent.originalTarget.id == "contentSearchDefaultEngineHeader" ||
|
||||
aEvent instanceof KeyboardEvent) {
|
||||
searchTerms = searchText.value;
|
||||
}
|
||||
else {
|
||||
searchTerms = this.suggestionAtIndex(this.selectedIndex) || searchText.value;
|
||||
}
|
||||
// Send an event that will perform a search and Firefox Health Report will
|
||||
// record that a search from the healthReportKey passed to the constructor.
|
||||
let eventData = {
|
||||
engineName: this.selectedEngineName,
|
||||
searchString: searchTerms,
|
||||
healthReportKey: this._healthReportKey,
|
||||
searchPurpose: this._searchPurpose,
|
||||
originalEvent: {
|
||||
shiftKey: aEvent.shiftKey,
|
||||
ctrlKey: aEvent.ctrlKey,
|
||||
metaKey: aEvent.metaKey,
|
||||
altKey: aEvent.altKey,
|
||||
},
|
||||
};
|
||||
if ("button" in aEvent) {
|
||||
eventData.originalEvent.button = aEvent.button;
|
||||
}
|
||||
|
||||
if (this.suggestionAtIndex(this.selectedIndex)) {
|
||||
eventData.selection = {
|
||||
index: this.selectedIndex,
|
||||
kind: undefined,
|
||||
};
|
||||
if (aEvent instanceof MouseEvent) {
|
||||
eventData.selection.kind = "mouse";
|
||||
} else if (aEvent instanceof KeyboardEvent) {
|
||||
eventData.selection.kind = "key";
|
||||
}
|
||||
}
|
||||
|
||||
this._sendMsg("Search", eventData);
|
||||
this.addInputValueToFormHistory();
|
||||
},
|
||||
|
||||
_onInput: function () {
|
||||
if (!this.input.value) {
|
||||
this._stickyInputValue = "";
|
||||
this._hideSuggestions();
|
||||
}
|
||||
else if (this.input.value != this._stickyInputValue) {
|
||||
// Only fetch new suggestions if the input value has changed.
|
||||
this._getSuggestions();
|
||||
this.selectAndUpdateInput(-1);
|
||||
}
|
||||
this._updateSearchWithHeader();
|
||||
},
|
||||
|
||||
_onKeypress: function (event) {
|
||||
let selectedIndexDelta = 0;
|
||||
let selectedSuggestionDelta = 0;
|
||||
let selectedOneOffDelta = 0;
|
||||
|
||||
switch (event.keyCode) {
|
||||
case event.DOM_VK_UP:
|
||||
if (this._table.hidden) {
|
||||
return;
|
||||
}
|
||||
if (event.getModifierState("Accel")) {
|
||||
if (event.shiftKey) {
|
||||
selectedSuggestionDelta = -1;
|
||||
break;
|
||||
}
|
||||
this._cycleCurrentEngine(true);
|
||||
break;
|
||||
}
|
||||
if (event.altKey) {
|
||||
selectedOneOffDelta = -1;
|
||||
break;
|
||||
}
|
||||
selectedIndexDelta = -1;
|
||||
break;
|
||||
case event.DOM_VK_DOWN:
|
||||
if (this._table.hidden) {
|
||||
this._getSuggestions();
|
||||
return;
|
||||
}
|
||||
if (event.getModifierState("Accel")) {
|
||||
if (event.shiftKey) {
|
||||
selectedSuggestionDelta = 1;
|
||||
break;
|
||||
}
|
||||
this._cycleCurrentEngine(false);
|
||||
break;
|
||||
}
|
||||
if (event.altKey) {
|
||||
selectedOneOffDelta = 1;
|
||||
break;
|
||||
}
|
||||
selectedIndexDelta = 1;
|
||||
break;
|
||||
case event.DOM_VK_TAB:
|
||||
if (this._table.hidden) {
|
||||
return;
|
||||
}
|
||||
// Shift+tab when either the first or no one-off is selected, as well as
|
||||
// tab when the settings button is selected, should change focus as normal.
|
||||
if ((this.selectedButtonIndex <= 0 && event.shiftKey) ||
|
||||
this.selectedButtonIndex == this._oneOffButtons.length && !event.shiftKey) {
|
||||
return;
|
||||
}
|
||||
selectedOneOffDelta = event.shiftKey ? -1 : 1;
|
||||
break;
|
||||
case event.DOM_VK_RIGHT:
|
||||
// Allow normal caret movement until the caret is at the end of the input.
|
||||
if (this.input.selectionStart != this.input.selectionEnd ||
|
||||
this.input.selectionEnd != this.input.value.length) {
|
||||
return;
|
||||
}
|
||||
if (this.numSuggestions && this.selectedIndex >= 0 &&
|
||||
this.selectedIndex < this.numSuggestions) {
|
||||
this.input.value = this.suggestionAtIndex(this.selectedIndex);
|
||||
this.input.setAttribute("selection-index", this.selectedIndex);
|
||||
this.input.setAttribute("selection-kind", "key");
|
||||
} else {
|
||||
// If we didn't select anything, make sure to remove the attributes
|
||||
// in case they were populated last time.
|
||||
this.input.removeAttribute("selection-index");
|
||||
this.input.removeAttribute("selection-kind");
|
||||
}
|
||||
this._stickyInputValue = this.input.value;
|
||||
this._hideSuggestions();
|
||||
return;
|
||||
case event.DOM_VK_RETURN:
|
||||
this._onCommand(event);
|
||||
return;
|
||||
case event.DOM_VK_DELETE:
|
||||
if (this.selectedIndex >= 0) {
|
||||
this.deleteSuggestionAtIndex(this.selectedIndex);
|
||||
}
|
||||
return;
|
||||
case event.DOM_VK_ESCAPE:
|
||||
if (!this._table.hidden) {
|
||||
this._hideSuggestions();
|
||||
}
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
let currentIndex = this.selectedIndex;
|
||||
if (selectedIndexDelta) {
|
||||
let newSelectedIndex = currentIndex + selectedIndexDelta;
|
||||
if (newSelectedIndex < -1) {
|
||||
newSelectedIndex = this.numSuggestions + this._oneOffButtons.length;
|
||||
}
|
||||
// If are moving up from the first one off, we have to deselect the one off
|
||||
// manually because the selectedIndex setter tries to exclude the selected
|
||||
// one-off (which is desirable for accel+shift+up/down).
|
||||
if (currentIndex == this.numSuggestions && selectedIndexDelta == -1) {
|
||||
this.selectedButtonIndex = -1;
|
||||
}
|
||||
this.selectAndUpdateInput(newSelectedIndex);
|
||||
}
|
||||
|
||||
else if (selectedSuggestionDelta) {
|
||||
let newSelectedIndex;
|
||||
if (currentIndex >= this.numSuggestions || currentIndex == -1) {
|
||||
// No suggestion already selected, select the first/last one appropriately.
|
||||
newSelectedIndex = selectedSuggestionDelta == 1 ?
|
||||
0 : this.numSuggestions - 1;
|
||||
}
|
||||
else {
|
||||
newSelectedIndex = currentIndex + selectedSuggestionDelta;
|
||||
}
|
||||
if (newSelectedIndex >= this.numSuggestions) {
|
||||
newSelectedIndex = -1;
|
||||
}
|
||||
this.selectAndUpdateInput(newSelectedIndex);
|
||||
}
|
||||
|
||||
else if (selectedOneOffDelta) {
|
||||
let newSelectedIndex;
|
||||
let currentButton = this.selectedButtonIndex;
|
||||
if (currentButton == -1 || currentButton == this._oneOffButtons.length) {
|
||||
// No one-off already selected, select the first/last one appropriately.
|
||||
newSelectedIndex = selectedOneOffDelta == 1 ?
|
||||
0 : this._oneOffButtons.length - 1;
|
||||
}
|
||||
else {
|
||||
newSelectedIndex = currentButton + selectedOneOffDelta;
|
||||
}
|
||||
// Allow selection of the settings button via the tab key.
|
||||
if (newSelectedIndex == this._oneOffButtons.length &&
|
||||
event.keyCode != event.DOM_VK_TAB) {
|
||||
newSelectedIndex = -1;
|
||||
}
|
||||
this.selectedButtonIndex = newSelectedIndex;
|
||||
}
|
||||
|
||||
// Prevent the input's caret from moving.
|
||||
event.preventDefault();
|
||||
},
|
||||
|
||||
_currentEngineIndex: -1,
|
||||
_cycleCurrentEngine: function (aReverse) {
|
||||
if ((this._currentEngineIndex == this._engines.length - 1 && !aReverse) ||
|
||||
(this._currentEngineIndex == 0 && aReverse)) {
|
||||
return;
|
||||
}
|
||||
this._currentEngineIndex += aReverse ? -1 : 1;
|
||||
let engineName = this._engines[this._currentEngineIndex].name;
|
||||
this._sendMsg("SetCurrentEngine", engineName);
|
||||
},
|
||||
|
||||
_onFocus: function () {
|
||||
if (this._mousedown) {
|
||||
return;
|
||||
}
|
||||
// When the input box loses focus to something in our table, we refocus it
|
||||
// immediately. This causes the focus highlight to flicker, so we set a
|
||||
// custom attribute which consumers should use for focus highlighting. This
|
||||
// attribute is removed only when we do not immediately refocus the input
|
||||
// box, thus eliminating flicker.
|
||||
this.input.setAttribute("keepfocus", "true");
|
||||
this._speculativeConnect();
|
||||
},
|
||||
|
||||
_onBlur: function () {
|
||||
if (this._mousedown) {
|
||||
// At this point, this.input has lost focus, but a new element has not yet
|
||||
// received it. If we re-focus this.input directly, the new element will
|
||||
// steal focus immediately, so we queue it instead.
|
||||
setTimeout(() => this.input.focus(), 0);
|
||||
return;
|
||||
}
|
||||
this.input.removeAttribute("keepfocus");
|
||||
this._hideSuggestions();
|
||||
},
|
||||
|
||||
_onMousemove: function (event) {
|
||||
let idx = this._indexOfTableItem(event.target);
|
||||
if (idx >= this.numSuggestions) {
|
||||
this.selectedButtonIndex = idx - this.numSuggestions;
|
||||
return;
|
||||
}
|
||||
this.selectedIndex = idx;
|
||||
},
|
||||
|
||||
_onMouseup: function (event) {
|
||||
if (event.button == 2) {
|
||||
return;
|
||||
}
|
||||
this._onCommand(event);
|
||||
},
|
||||
|
||||
_onMouseout: function (event) {
|
||||
// We only deselect one-off buttons and the settings button when they are
|
||||
// moused out.
|
||||
let idx = this._indexOfTableItem(event.originalTarget);
|
||||
if (idx >= this.numSuggestions) {
|
||||
this.selectedButtonIndex = -1;
|
||||
}
|
||||
},
|
||||
|
||||
_onClick: function (event) {
|
||||
this._onMouseup(event);
|
||||
},
|
||||
|
||||
_onContentSearchService: function (event) {
|
||||
let methodName = "_onMsg" + event.detail.type;
|
||||
if (methodName in this) {
|
||||
this[methodName](event.detail.data);
|
||||
}
|
||||
},
|
||||
|
||||
_onMsgFocusInput: function (event) {
|
||||
this.input.focus();
|
||||
},
|
||||
|
||||
_onMsgSuggestions: function (suggestions) {
|
||||
// Ignore the suggestions if their search string or engine doesn't match
|
||||
// ours. Due to the async nature of message passing, this can easily happen
|
||||
// when the user types quickly.
|
||||
if (this._stickyInputValue != suggestions.searchString ||
|
||||
this.defaultEngine.name != suggestions.engineName) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._clearSuggestionRows();
|
||||
|
||||
// Position and size the table.
|
||||
let { left } = this.input.getBoundingClientRect();
|
||||
this._table.style.top = this.input.offsetHeight + "px";
|
||||
this._table.style.minWidth = this.input.offsetWidth + "px";
|
||||
this._table.style.maxWidth = (window.innerWidth - left - 40) + "px";
|
||||
|
||||
// Add the suggestions to the table.
|
||||
let searchWords =
|
||||
new Set(suggestions.searchString.trim().toLowerCase().split(/\s+/));
|
||||
for (let i = 0; i < MAX_DISPLAYED_SUGGESTIONS; i++) {
|
||||
let type, idx;
|
||||
if (i < suggestions.formHistory.length) {
|
||||
[type, idx] = ["formHistory", i];
|
||||
}
|
||||
else {
|
||||
let j = i - suggestions.formHistory.length;
|
||||
if (j < suggestions.remote.length) {
|
||||
[type, idx] = ["remote", j];
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
this._suggestionsList.appendChild(
|
||||
this._makeTableRow(type, suggestions[type][idx], i, searchWords));
|
||||
}
|
||||
|
||||
if (this._table.hidden) {
|
||||
this.selectedIndex = -1;
|
||||
if (this._pendingOneOffRefresh) {
|
||||
this._setUpOneOffButtons();
|
||||
delete this._pendingOneOffRefresh;
|
||||
}
|
||||
this._currentEngineIndex =
|
||||
this._engines.findIndex(aEngine => aEngine.name == this.defaultEngine.name);
|
||||
this._table.hidden = false;
|
||||
this.input.setAttribute("aria-expanded", "true");
|
||||
this._originalDefaultEngine = {
|
||||
name: this.defaultEngine.name,
|
||||
icon: this.defaultEngine.icon,
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
_onMsgSuggestionsCancelled: function () {
|
||||
if (!this._table.hidden) {
|
||||
this._hideSuggestions();
|
||||
}
|
||||
},
|
||||
|
||||
_onMsgState: function (state) {
|
||||
this.engines = state.engines;
|
||||
// No point updating the default engine (and the header) if there's no change.
|
||||
if (this.defaultEngine &&
|
||||
this.defaultEngine.name == state.currentEngine.name &&
|
||||
this.defaultEngine.icon == state.currentEngine.icon) {
|
||||
return;
|
||||
}
|
||||
this.defaultEngine = state.currentEngine;
|
||||
},
|
||||
|
||||
_onMsgCurrentState: function (state) {
|
||||
this._onMsgState(state);
|
||||
},
|
||||
|
||||
_onMsgCurrentEngine: function (engine) {
|
||||
this.defaultEngine = engine;
|
||||
this._pendingOneOffRefresh = true;
|
||||
},
|
||||
|
||||
_onMsgStrings: function (strings) {
|
||||
this._strings = strings;
|
||||
this._updateDefaultEngineHeader();
|
||||
this._updateSearchWithHeader();
|
||||
document.getElementById("contentSearchSettingsButton").textContent =
|
||||
this._strings.searchSettings;
|
||||
this.input.setAttribute("placeholder", this._strings.searchPlaceholder);
|
||||
},
|
||||
|
||||
_updateDefaultEngineHeader: function () {
|
||||
let header = document.getElementById("contentSearchDefaultEngineHeader");
|
||||
header.firstChild.setAttribute("src", this.defaultEngine.icon);
|
||||
if (!this._strings) {
|
||||
return;
|
||||
}
|
||||
while (header.firstChild.nextSibling) {
|
||||
header.firstChild.nextSibling.remove();
|
||||
}
|
||||
header.appendChild(document.createTextNode(
|
||||
this._strings.searchHeader.replace("%S", this.defaultEngine.name)));
|
||||
},
|
||||
|
||||
_updateSearchWithHeader: function () {
|
||||
if (!this._strings) {
|
||||
return;
|
||||
}
|
||||
let searchWithHeader = document.getElementById("contentSearchSearchWithHeader");
|
||||
if (this.input.value) {
|
||||
searchWithHeader.innerHTML = this._strings.searchForSomethingWith;
|
||||
searchWithHeader.querySelector('.contentSearchSearchWithHeaderSearchText').textContent = this.input.value;
|
||||
} else {
|
||||
searchWithHeader.textContent = this._strings.searchWithHeader;
|
||||
}
|
||||
},
|
||||
|
||||
_speculativeConnect: function () {
|
||||
if (this.defaultEngine) {
|
||||
this._sendMsg("SpeculativeConnect", this.defaultEngine.name);
|
||||
}
|
||||
},
|
||||
|
||||
_makeTableRow: function (type, suggestionStr, currentRow, searchWords) {
|
||||
let row = document.createElementNS(HTML_NS, "tr");
|
||||
row.dir = "auto";
|
||||
row.classList.add("contentSearchSuggestionRow");
|
||||
row.classList.add(type);
|
||||
row.setAttribute("role", "presentation");
|
||||
row.addEventListener("mousemove", this);
|
||||
row.addEventListener("mouseup", this);
|
||||
|
||||
let entry = document.createElementNS(HTML_NS, "td");
|
||||
let img = document.createElementNS(HTML_NS, "div");
|
||||
img.setAttribute("class", "historyIcon");
|
||||
entry.appendChild(img);
|
||||
entry.classList.add("contentSearchSuggestionEntry");
|
||||
entry.setAttribute("role", "option");
|
||||
entry.id = this._idPrefix + SUGGESTION_ID_PREFIX + currentRow;
|
||||
entry.setAttribute("aria-selected", "false");
|
||||
|
||||
let suggestionWords = suggestionStr.trim().toLowerCase().split(/\s+/);
|
||||
for (let i = 0; i < suggestionWords.length; i++) {
|
||||
let word = suggestionWords[i];
|
||||
let wordSpan = document.createElementNS(HTML_NS, "span");
|
||||
if (searchWords.has(word)) {
|
||||
wordSpan.classList.add("typed");
|
||||
}
|
||||
wordSpan.textContent = word;
|
||||
entry.appendChild(wordSpan);
|
||||
if (i < suggestionWords.length - 1) {
|
||||
entry.appendChild(document.createTextNode(" "));
|
||||
}
|
||||
}
|
||||
|
||||
row.appendChild(entry);
|
||||
return row;
|
||||
},
|
||||
|
||||
// Converts favicon array buffer into a data URI.
|
||||
_getFaviconURIFromBuffer: function (buffer) {
|
||||
let blob = new Blob([buffer]);
|
||||
return URL.createObjectURL(blob);
|
||||
},
|
||||
|
||||
// Adds "@2x" to the name of the given PNG url for "retina" screens.
|
||||
_getImageURIForCurrentResolution: function (uri) {
|
||||
if (window.devicePixelRatio > 1) {
|
||||
return uri.replace(/\.png$/, "@2x.png");
|
||||
}
|
||||
return uri;
|
||||
},
|
||||
|
||||
_getSearchEngines: function () {
|
||||
this._sendMsg("GetState");
|
||||
},
|
||||
|
||||
_getStrings: function () {
|
||||
this._sendMsg("GetStrings");
|
||||
},
|
||||
|
||||
_getSuggestions: function () {
|
||||
this._stickyInputValue = this.input.value;
|
||||
if (this.defaultEngine) {
|
||||
this._sendMsg("GetSuggestions", {
|
||||
engineName: this.defaultEngine.name,
|
||||
searchString: this.input.value,
|
||||
remoteTimeout: this.remoteTimeout,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
_clearSuggestionRows: function() {
|
||||
while (this._suggestionsList.firstElementChild) {
|
||||
this._suggestionsList.firstElementChild.remove();
|
||||
}
|
||||
},
|
||||
|
||||
_hideSuggestions: function () {
|
||||
this.input.setAttribute("aria-expanded", "false");
|
||||
this.selectedIndex = -1;
|
||||
this.selectedButtonIndex = -1;
|
||||
this._currentEngineIndex = -1;
|
||||
this._table.hidden = true;
|
||||
},
|
||||
|
||||
_indexOfTableItem: function (elt) {
|
||||
if (elt.classList.contains("contentSearchOneOffItem")) {
|
||||
return this.numSuggestions + this._oneOffButtons.indexOf(elt);
|
||||
}
|
||||
if (elt.classList.contains("contentSearchSettingsButton")) {
|
||||
return this.numSuggestions + this._oneOffButtons.length;
|
||||
}
|
||||
while (elt && elt.localName != "tr") {
|
||||
elt = elt.parentNode;
|
||||
}
|
||||
if (!elt) {
|
||||
throw new Error("Element is not a row");
|
||||
}
|
||||
return elt.rowIndex;
|
||||
},
|
||||
|
||||
_makeTable: function (id) {
|
||||
this._table = document.createElementNS(HTML_NS, "table");
|
||||
this._table.id = id;
|
||||
this._table.hidden = true;
|
||||
this._table.classList.add("contentSearchSuggestionTable");
|
||||
this._table.setAttribute("role", "presentation");
|
||||
|
||||
// When the search input box loses focus, we want to immediately give focus
|
||||
// back to it if the blur was because the user clicked somewhere in the table.
|
||||
// onBlur uses the _mousedown flag to detect this.
|
||||
this._table.addEventListener("mousedown", () => { this._mousedown = true; });
|
||||
document.addEventListener("mouseup", () => { delete this._mousedown; });
|
||||
|
||||
// Deselect the selected element on mouseout if it wasn't a suggestion.
|
||||
this._table.addEventListener("mouseout", this);
|
||||
|
||||
// If a search is loaded in the same tab, ensure the suggestions dropdown
|
||||
// is hidden immediately when the page starts loading and not when it first
|
||||
// appears, in order to provide timely feedback to the user.
|
||||
window.addEventListener("beforeunload", () => { this._hideSuggestions(); });
|
||||
|
||||
let headerRow = document.createElementNS(HTML_NS, "tr");
|
||||
let header = document.createElementNS(HTML_NS, "td");
|
||||
headerRow.setAttribute("class", "contentSearchHeaderRow");
|
||||
header.setAttribute("class", "contentSearchHeader");
|
||||
let iconImg = document.createElementNS(HTML_NS, "img");
|
||||
header.appendChild(iconImg);
|
||||
header.id = "contentSearchDefaultEngineHeader";
|
||||
headerRow.appendChild(header);
|
||||
headerRow.addEventListener("click", this);
|
||||
this._table.appendChild(headerRow);
|
||||
|
||||
let row = document.createElementNS(HTML_NS, "tr");
|
||||
row.setAttribute("class", "contentSearchSuggestionsContainer");
|
||||
let cell = document.createElementNS(HTML_NS, "td");
|
||||
cell.setAttribute("class", "contentSearchSuggestionsContainer");
|
||||
this._suggestionsList = document.createElementNS(HTML_NS, "table");
|
||||
this._suggestionsList.setAttribute("class", "contentSearchSuggestionsList");
|
||||
cell.appendChild(this._suggestionsList);
|
||||
row.appendChild(cell);
|
||||
this._table.appendChild(row);
|
||||
this._suggestionsList.setAttribute("role", "listbox");
|
||||
|
||||
this._oneOffsTable = document.createElementNS(HTML_NS, "table");
|
||||
this._oneOffsTable.setAttribute("class", "contentSearchOneOffsTable");
|
||||
this._oneOffsTable.classList.add("contentSearchSuggestionsContainer");
|
||||
this._oneOffsTable.setAttribute("role", "group");
|
||||
this._table.appendChild(this._oneOffsTable);
|
||||
|
||||
headerRow = document.createElementNS(HTML_NS, "tr");
|
||||
header = document.createElementNS(HTML_NS, "td");
|
||||
headerRow.setAttribute("class", "contentSearchHeaderRow");
|
||||
header.setAttribute("class", "contentSearchHeader");
|
||||
headerRow.appendChild(header);
|
||||
header.id = "contentSearchSearchWithHeader";
|
||||
this._oneOffsTable.appendChild(headerRow);
|
||||
|
||||
let button = document.createElementNS(HTML_NS, "button");
|
||||
button.setAttribute("class", "contentSearchSettingsButton");
|
||||
button.classList.add("contentSearchHeaderRow");
|
||||
button.classList.add("contentSearchHeader");
|
||||
button.id = "contentSearchSettingsButton";
|
||||
button.addEventListener("click", this);
|
||||
button.addEventListener("mousemove", this);
|
||||
this._table.appendChild(button);
|
||||
|
||||
return this._table;
|
||||
},
|
||||
|
||||
_setUpOneOffButtons: function () {
|
||||
// Sometimes we receive a CurrentEngine message from the ContentSearch service
|
||||
// before we've received a State message - i.e. before we have our engines.
|
||||
if (!this._engines) {
|
||||
return;
|
||||
}
|
||||
|
||||
while (this._oneOffsTable.firstChild.nextSibling) {
|
||||
this._oneOffsTable.firstChild.nextSibling.remove();
|
||||
}
|
||||
|
||||
this._oneOffButtons = [];
|
||||
|
||||
let engines = this._engines.filter(aEngine => aEngine.name != this.defaultEngine.name)
|
||||
.filter(aEngine => !aEngine.hidden);
|
||||
if (!engines.length) {
|
||||
this._oneOffsTable.hidden = true;
|
||||
return;
|
||||
}
|
||||
|
||||
const kDefaultButtonWidth = 49; // 48px + 1px border.
|
||||
let rowWidth = this.input.offsetWidth - 2; // 2px border.
|
||||
let enginesPerRow = Math.floor(rowWidth / kDefaultButtonWidth);
|
||||
let buttonWidth = Math.floor(rowWidth / enginesPerRow);
|
||||
|
||||
let row = document.createElementNS(HTML_NS, "tr");
|
||||
let cell = document.createElementNS(HTML_NS, "td");
|
||||
row.setAttribute("class", "contentSearchSuggestionsContainer");
|
||||
cell.setAttribute("class", "contentSearchSuggestionsContainer");
|
||||
|
||||
for (let i = 0; i < engines.length; ++i) {
|
||||
let engine = engines[i];
|
||||
if (i > 0 && i % enginesPerRow == 0) {
|
||||
row.appendChild(cell);
|
||||
this._oneOffsTable.appendChild(row);
|
||||
row = document.createElementNS(HTML_NS, "tr");
|
||||
cell = document.createElementNS(HTML_NS, "td");
|
||||
row.setAttribute("class", "contentSearchSuggestionsContainer");
|
||||
cell.setAttribute("class", "contentSearchSuggestionsContainer");
|
||||
}
|
||||
let button = document.createElementNS(HTML_NS, "button");
|
||||
button.setAttribute("class", "contentSearchOneOffItem");
|
||||
let img = document.createElementNS(HTML_NS, "img");
|
||||
let uri;
|
||||
if (engine.iconBuffer) {
|
||||
uri = this._getFaviconURIFromBuffer(engine.iconBuffer);
|
||||
}
|
||||
else {
|
||||
uri = this._getImageURIForCurrentResolution(
|
||||
"chrome://browser/skin/search-engine-placeholder.png");
|
||||
}
|
||||
img.setAttribute("src", uri);
|
||||
img.addEventListener("load", function imgLoad() {
|
||||
img.removeEventListener("load", imgLoad);
|
||||
URL.revokeObjectURL(uri);
|
||||
});
|
||||
button.appendChild(img);
|
||||
button.style.width = buttonWidth + "px";
|
||||
button.setAttribute("title", engine.name);
|
||||
|
||||
button.engineName = engine.name;
|
||||
button.addEventListener("click", this);
|
||||
button.addEventListener("mousemove", this);
|
||||
|
||||
if (engines.length - i <= enginesPerRow - (i % enginesPerRow)) {
|
||||
button.classList.add("last-row");
|
||||
}
|
||||
|
||||
if ((i + 1) % enginesPerRow == 0) {
|
||||
button.classList.add("end-of-row");
|
||||
}
|
||||
|
||||
button.id = ONE_OFF_ID_PREFIX + i;
|
||||
cell.appendChild(button);
|
||||
this._oneOffButtons.push(button);
|
||||
}
|
||||
row.appendChild(cell);
|
||||
this._oneOffsTable.appendChild(row);
|
||||
this._oneOffsTable.hidden = false;
|
||||
},
|
||||
|
||||
_sendMsg: function (type, data=null) {
|
||||
dispatchEvent(new CustomEvent("ContentSearchClient", {
|
||||
detail: {
|
||||
type: type,
|
||||
data: data,
|
||||
},
|
||||
}));
|
||||
},
|
||||
};
|
||||
|
||||
return ContentSearchUIController;
|
||||
})();
|
||||
BIN
browser/base/content/defaultthemes/1.footer.jpg
Normal file
|
After Width: | Height: | Size: 148 KiB |
BIN
browser/base/content/defaultthemes/1.header.jpg
Normal file
|
After Width: | Height: | Size: 260 KiB |