merge mozilla-central to mozilla-inbound. r=merge a=merge
35
Makefile.in
|
@ -105,7 +105,7 @@ default:: $(BUILD_BACKEND_FILES)
|
|||
endif
|
||||
|
||||
install_manifests := \
|
||||
$(addprefix dist/,branding idl include public private sdk xpi-stage) \
|
||||
$(addprefix dist/,branding idl include public private xpi-stage) \
|
||||
_tests \
|
||||
$(NULL)
|
||||
# Skip the dist/bin install manifest when using the hybrid
|
||||
|
@ -160,22 +160,7 @@ endif
|
|||
@$(TUP) $(if $(findstring s,$(filter-out --%,$(MAKEFLAGS))),,--verbose)
|
||||
$(call BUILDSTATUS,TIER_FINISH tup)
|
||||
|
||||
# process_install_manifest needs to be invoked with --no-remove when building
|
||||
# js as standalone because automated builds are building nspr separately and
|
||||
# that would remove the resulting files.
|
||||
# Eventually, a standalone js build would just be able to build nspr itself,
|
||||
# removing the need for the former.
|
||||
ifdef JS_STANDALONE
|
||||
NO_REMOVE=1
|
||||
endif
|
||||
|
||||
# For an artifact build, _tests will already be partly populated, so run
|
||||
# this install manifest with NO_REMOVE set in this case.
|
||||
ifdef MOZ_ARTIFACT_BUILDS
|
||||
install-_tests: NO_REMOVE=1
|
||||
endif
|
||||
|
||||
.PHONY: $(addprefix install-,$(subst /,_,$(install_manifests)))
|
||||
.PHONY: $(addprefix install-,$(install_manifests))
|
||||
$(addprefix install-,$(install_manifests)): install-%: $(install_manifest_depends)
|
||||
ifneq (,$(filter FasterMake+RecursiveMake,$(BUILD_BACKENDS)))
|
||||
@# If we're using the hybrid FasterMake/RecursiveMake backend, we want
|
||||
|
@ -183,7 +168,7 @@ ifneq (,$(filter FasterMake+RecursiveMake,$(BUILD_BACKENDS)))
|
|||
@# same directory, because that would blow up
|
||||
$(if $(wildcard _build_manifests/install/$(subst /,_,$*)),$(if $(wildcard faster/install_$(subst /,_,$*)*),$(error FasterMake and RecursiveMake ends of the hybrid build system want to handle $*)))
|
||||
endif
|
||||
$(addprefix $(call py_action,process_install_manifest,$(if $(NO_REMOVE),--no-remove )$*) ,$(wildcard _build_manifests/install/$(subst /,_,$*)))
|
||||
$(addprefix $(call py_action,process_install_manifest,--track install_$(subst /,_,$*).track $*) ,$(wildcard _build_manifests/install/$(subst /,_,$*)))
|
||||
|
||||
# Dummy wrapper rule to allow the faster backend to piggy back
|
||||
$(addprefix install-,$(subst /,_,$(filter dist/%,$(install_manifests)))): install-dist_%: install-dist/% ;
|
||||
|
@ -191,10 +176,9 @@ $(addprefix install-,$(subst /,_,$(filter dist/%,$(install_manifests)))): instal
|
|||
.PHONY: install-tests
|
||||
install-tests: install-test-files
|
||||
|
||||
# Force --no-remove, because $objdir/_tests is handled by multiple manifests.
|
||||
.PHONY: install-test-files
|
||||
install-test-files:
|
||||
$(call py_action,process_install_manifest,--no-remove _tests _build_manifests/install/_test_files)
|
||||
$(call py_action,process_install_manifest,--track install__test_files.track _tests _build_manifests/install/_test_files)
|
||||
|
||||
include $(topsrcdir)/build/moz-automation.mk
|
||||
|
||||
|
@ -204,6 +188,9 @@ ifneq ($(filter-out maybe_clobber_profiledbuild,$(MAKECMDGOALS)),)
|
|||
GARBAGE_DIRS += dist _tests
|
||||
endif
|
||||
|
||||
# Dummy rule for the cases below where we don't depend on dist/include
|
||||
recurse_pre-export::
|
||||
|
||||
# Windows PGO builds don't perform a clean before the 2nd pass. So, we want
|
||||
# to preserve content for the 2nd pass on Windows. Everywhere else, we always
|
||||
# process the install manifests as part of export.
|
||||
|
@ -214,19 +201,15 @@ ifndef NO_PROFILE_GUIDED_OPTIMIZE
|
|||
ifneq ($(OS_ARCH)_$(GNU_CC), WINNT_)
|
||||
recurse_pre-export:: install-manifests
|
||||
binaries::
|
||||
@$(MAKE) install-manifests NO_REMOVE=1 install_manifests=dist/include
|
||||
@$(MAKE) install-manifests install_manifests=dist/include
|
||||
endif
|
||||
endif
|
||||
else # !MOZ_PROFILE_USE (normal build)
|
||||
recurse_pre-export:: install-manifests
|
||||
binaries::
|
||||
@$(MAKE) install-manifests NO_REMOVE=1 install_manifests=dist/include
|
||||
@$(MAKE) install-manifests install_manifests=dist/include
|
||||
endif
|
||||
|
||||
# For historical reasons that are unknown, $(DIST)/sdk is always blown away
|
||||
# with no regard for PGO passes. This decision could probably be revisited.
|
||||
recurse_pre-export:: install-dist/sdk
|
||||
|
||||
recurse_artifact:
|
||||
$(topsrcdir)/mach --log-no-times artifact install
|
||||
|
||||
|
|
|
@ -439,7 +439,6 @@ var SidebarUI = {
|
|||
sidebarBroadcaster.removeAttribute("checked");
|
||||
this._box.setAttribute("sidebarcommand", "");
|
||||
this._box.removeAttribute("checked");
|
||||
this.title = "";
|
||||
this._box.hidden = this._splitter.hidden = true;
|
||||
|
||||
let selBrowser = gBrowser.selectedBrowser;
|
||||
|
|
|
@ -482,6 +482,13 @@ const gStoragePressureObserver = {
|
|||
return;
|
||||
}
|
||||
|
||||
const NOTIFICATION_VALUE = "storage-pressure-notification";
|
||||
let notificationBox = document.getElementById("high-priority-global-notificationbox");
|
||||
if (notificationBox.getNotificationWithValue(NOTIFICATION_VALUE)) {
|
||||
// Do not display the 2nd notification when there is already one
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't display notification twice within the given interval.
|
||||
// This is because
|
||||
// - not to annoy user
|
||||
|
@ -503,7 +510,6 @@ const gStoragePressureObserver = {
|
|||
let usage = subject.QueryInterface(Ci.nsISupportsPRUint64).data
|
||||
let prefStrBundle = document.getElementById("bundle_preferences");
|
||||
let brandShortName = document.getElementById("bundle_brand").getString("brandShortName");
|
||||
let notificationBox = document.getElementById("high-priority-global-notificationbox");
|
||||
buttons.push({
|
||||
label: prefStrBundle.getString("spaceAlert.learnMoreButton.label"),
|
||||
accessKey: prefStrBundle.getString("spaceAlert.learnMoreButton.accesskey"),
|
||||
|
@ -552,7 +558,7 @@ const gStoragePressureObserver = {
|
|||
}
|
||||
|
||||
notificationBox.appendNotification(
|
||||
msg, "storage-pressure-notification", null, notificationBox.PRIORITY_WARNING_HIGH, buttons, null);
|
||||
msg, NOTIFICATION_VALUE, null, notificationBox.PRIORITY_WARNING_HIGH, buttons, null);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -79,3 +79,23 @@ add_task(async function() {
|
|||
await SpecialPowers.pushPrefEnv({set: [["browser.preferences.useOldOrganization", false]]});
|
||||
await testOverUsageThresholdNotification();
|
||||
});
|
||||
|
||||
// Test not displaying the 2nd notification if one is already being displayed
|
||||
add_task(async function() {
|
||||
const TEST_NOTIFICATION_INTERVAL_MS = 0;
|
||||
await SpecialPowers.pushPrefEnv({set: [["browser.storageManager.enabled", true]]});
|
||||
await SpecialPowers.pushPrefEnv({set: [["browser.storageManager.pressureNotification.minIntervalMS", TEST_NOTIFICATION_INTERVAL_MS]]});
|
||||
|
||||
await notifyStoragePressure();
|
||||
await notifyStoragePressure();
|
||||
let notificationbox = document.getElementById("high-priority-global-notificationbox");
|
||||
let allNotifications = notificationbox.allNotifications;
|
||||
let pressureNotificationCount = 0;
|
||||
allNotifications.forEach(notification => {
|
||||
if (notification.getAttribute("value") == "storage-pressure-notification") {
|
||||
pressureNotificationCount++;
|
||||
}
|
||||
});
|
||||
is(pressureNotificationCount, 1, "Should not display the 2nd notification when there is already one");
|
||||
notificationbox.removeAllNotifications();
|
||||
});
|
||||
|
|
|
@ -553,7 +553,7 @@
|
|||
<hbox pack="end">
|
||||
<button id="locationSettingsButton"
|
||||
class="accessory-button"
|
||||
label="&permissionSettingsButton2.label;"
|
||||
label="&locationSettingsButton.label;"
|
||||
accesskey="&locationSettingsButton.accesskey;"
|
||||
searchkeywords="&removepermission2.label;
|
||||
&removeallpermissions2.label;
|
||||
|
@ -571,7 +571,7 @@
|
|||
<hbox pack="end">
|
||||
<button id="cameraSettingsButton"
|
||||
class="accessory-button"
|
||||
label="&permissionSettingsButton2.label;"
|
||||
label="&cameraSettingsButton.label;"
|
||||
accesskey="&cameraSettingsButton.accesskey;"
|
||||
searchkeywords="&removepermission2.label;
|
||||
&removeallpermissions2.label;
|
||||
|
@ -589,7 +589,7 @@
|
|||
<hbox pack="end">
|
||||
<button id="microphoneSettingsButton"
|
||||
class="accessory-button"
|
||||
label="&permissionSettingsButton2.label;"
|
||||
label="µphoneSettingsButton.label;"
|
||||
accesskey="µphoneSettingsButton.accesskey;"
|
||||
searchkeywords="&removepermission2.label;
|
||||
&removeallpermissions2.label;
|
||||
|
@ -609,7 +609,7 @@
|
|||
<hbox pack="end">
|
||||
<button id="notificationSettingsButton"
|
||||
class="accessory-button"
|
||||
label="&permissionSettingsButton2.label;"
|
||||
label="¬ificationSettingsButton.label;"
|
||||
accesskey="¬ificationSettingsButton.accesskey;"
|
||||
searchkeywords="&removepermission2.label;
|
||||
&removeallpermissions2.label;
|
||||
|
|
|
@ -106,7 +106,7 @@ var gSyncPane = {
|
|||
|
||||
// Use cached values while we wait for the up-to-date values
|
||||
let cachedComputerName = Services.prefs.getCharPref("services.sync.client.name", "");
|
||||
document.getElementById("fxaEmailAddress1").textContent = username;
|
||||
document.querySelector(".fxaEmailAddress").value = username;
|
||||
this._populateComputerName(cachedComputerName);
|
||||
this.page = FXA_PAGE_LOGGED_IN;
|
||||
},
|
||||
|
@ -255,8 +255,7 @@ var gSyncPane = {
|
|||
.wrappedJSObject;
|
||||
|
||||
let displayNameLabel = document.getElementById("fxaDisplayName");
|
||||
let fxaEmailAddress1Label = document.getElementById("fxaEmailAddress1");
|
||||
fxaEmailAddress1Label.hidden = false;
|
||||
let fxaEmailAddressLabels = document.querySelectorAll(".fxaEmailAddress");
|
||||
displayNameLabel.hidden = true;
|
||||
|
||||
// determine the fxa status...
|
||||
|
@ -291,9 +290,9 @@ var gSyncPane = {
|
|||
fxaLoginStatus.selectedIndex = FXA_LOGIN_VERIFIED;
|
||||
syncReady = true;
|
||||
}
|
||||
fxaEmailAddress1Label.textContent = data.email;
|
||||
document.getElementById("fxaEmailAddress2").textContent = data.email;
|
||||
document.getElementById("fxaEmailAddress3").textContent = data.email;
|
||||
fxaEmailAddressLabels.forEach((label) => {
|
||||
label.value = data.email;
|
||||
});
|
||||
this._populateComputerName(Weave.Service.clientsEngine.localName);
|
||||
let engines = document.getElementById("fxaSyncEngines")
|
||||
for (let checkbox of engines.querySelectorAll("checkbox")) {
|
||||
|
@ -318,9 +317,9 @@ var gSyncPane = {
|
|||
if (data.email) {
|
||||
// A hack to handle that the user's email address may have changed.
|
||||
// This can probably be removed as part of bug 1383663.
|
||||
fxaEmailAddress1Label.textContent = data.email;
|
||||
document.getElementById("fxaEmailAddress2").textContent = data.email;
|
||||
document.getElementById("fxaEmailAddress3").textContent = data.email;
|
||||
fxaEmailAddressLabels.forEach((label) => {
|
||||
label.value = data.email;
|
||||
});
|
||||
}
|
||||
if (data.displayName) {
|
||||
fxaLoginStatus.setAttribute("hasName", true);
|
||||
|
|
|
@ -102,18 +102,16 @@
|
|||
onkeypress="gSyncPane.openChangeProfileImage(event);"
|
||||
tooltiptext="&profilePicture.tooltip;"/>
|
||||
<vbox flex="1" pack="center">
|
||||
<hbox flex="1">
|
||||
<hbox flex="1" align="center">
|
||||
<label id="fxaDisplayName" hidden="true"/>
|
||||
<label id="fxaEmailAddress1"/>
|
||||
</hbox>
|
||||
<hbox flex="1" align="center">
|
||||
<label id="fxaDisplayName" hidden="true"/>
|
||||
<label class="fxaEmailAddress" flex="1" crop="end"/>
|
||||
<button id="fxaUnlinkButton"
|
||||
class="accessory-button"
|
||||
label="&disconnect3.label;"
|
||||
accesskey="&disconnect3.accesskey;"/>
|
||||
</hbox>
|
||||
<hbox>
|
||||
<html:a id="verifiedManage" class="openLink" target="_blank"
|
||||
<html:a id="verifiedManage" class="openLink"
|
||||
accesskey="&verifiedManage.accesskey;"
|
||||
onkeypress="gSyncPane.openManageFirefoxAccount(event);">&verifiedManage.label;</html:a>
|
||||
</hbox>
|
||||
|
@ -125,12 +123,12 @@
|
|||
<vbox>
|
||||
<image class="fxaProfileImage"/>
|
||||
</vbox>
|
||||
<vbox flex="1">
|
||||
<vbox flex="1" pack="center">
|
||||
<hbox>
|
||||
<vbox><image class="fxaLoginRejectedWarning"/></vbox>
|
||||
<image class="fxaLoginRejectedWarning"/>
|
||||
<description flex="1">
|
||||
&signedInUnverified.beforename.label;
|
||||
<label id="fxaEmailAddress2"/>
|
||||
<label class="fxaEmailAddress"/>
|
||||
&signedInUnverified.aftername.label;
|
||||
</description>
|
||||
</hbox>
|
||||
|
@ -146,12 +144,12 @@
|
|||
<vbox>
|
||||
<image class="fxaProfileImage"/>
|
||||
</vbox>
|
||||
<vbox flex="1">
|
||||
<vbox flex="1" pack="center">
|
||||
<hbox>
|
||||
<vbox><image class="fxaLoginRejectedWarning"/></vbox>
|
||||
<image class="fxaLoginRejectedWarning"/>
|
||||
<description flex="1">
|
||||
&signedInLoginFailure.beforename.label;
|
||||
<label id="fxaEmailAddress3"/>
|
||||
<label class="fxaEmailAddress"/>
|
||||
&signedInLoginFailure.aftername.label;
|
||||
</description>
|
||||
</hbox>
|
||||
|
|
|
@ -13,6 +13,9 @@ fi
|
|||
if test "${MOZ_UPDATE_CHANNEL}" = "nightly"; then
|
||||
ac_add_options --with-macbundlename-prefix=Firefox
|
||||
fi
|
||||
if test "${MOZ_UPDATE_CHANNEL}" = "nightly-try"; then
|
||||
ac_add_options --with-macbundlename-prefix=Firefox
|
||||
fi
|
||||
|
||||
ac_add_options --with-branding=browser/branding/nightly
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ if test "$OS_ARCH" = "WINNT"; then
|
|||
MOZ_MAINTENANCE_SERVICE=1
|
||||
if ! test "$HAVE_64BIT_BUILD"; then
|
||||
if test "$MOZ_UPDATE_CHANNEL" = "nightly" -o \
|
||||
"$MOZ_UPDATE_CHANNEL" = "nightly-try" -o \
|
||||
"$MOZ_UPDATE_CHANNEL" = "aurora" -o \
|
||||
"$MOZ_UPDATE_CHANNEL" = "aurora-dev" -o \
|
||||
"$MOZ_UPDATE_CHANNEL" = "beta" -o \
|
||||
|
|
|
@ -586,11 +586,10 @@ dataReportingNotification.button.accessKey = C
|
|||
|
||||
# Process hang reporter
|
||||
processHang.label = A web page is slowing down your browser. What would you like to do?
|
||||
# LOCALIZATION NOTE (processHang.add-on.label): The first %S is the name of
|
||||
# an extension. The second %S is the name of the product (e.g., Firefox)
|
||||
processHang.add-on.label = A script in the extension “%S” is causing %S to slow down.
|
||||
# LOCALIZATION NOTE (processHang.add-on.label): %1$S is the name of the
|
||||
# extension. %2$S is the name of the product (e.g., Firefox)
|
||||
processHang.add-on.label = A script in the extension “%1$S” is causing %2$S to slow down.
|
||||
processHang.add-on.learn-more.text = Learn more
|
||||
processHang.add-on.learn-more.url = https://support.mozilla.org/en-US/kb/warning-unresponsive-script?cache=no#w_other-causes
|
||||
processHang.button_stop.label = Stop It
|
||||
processHang.button_stop.accessKey = S
|
||||
processHang.button_stop_sandbox.label = Temporarily Disable Extension on Page
|
||||
|
|
|
@ -14,20 +14,22 @@
|
|||
<!ENTITY popupExceptions.label "Exceptions…">
|
||||
<!ENTITY popupExceptions.accesskey "E">
|
||||
|
||||
<!ENTITY permissionSettingsButton2.label "Settings…">
|
||||
|
||||
<!ENTITY notificationPermissions.label "Notifications">
|
||||
<!ENTITY notificationSettingsButton.accesskey "n">
|
||||
<!ENTITY notificationSettingsButton.label "Settings…">
|
||||
<!ENTITY notificationSettingsButton.accesskey "t">
|
||||
<!ENTITY notificationPermissionsLearnMore.label "Learn more">
|
||||
|
||||
<!ENTITY locationPermissions.label "Location">
|
||||
<!ENTITY locationSettingsButton.accesskey "l">
|
||||
<!ENTITY locationSettingsButton.label "Settings…">
|
||||
<!ENTITY locationSettingsButton.accesskey "t">
|
||||
|
||||
<!ENTITY cameraPermissions.label "Camera">
|
||||
<!ENTITY cameraSettingsButton.accesskey "c">
|
||||
<!ENTITY cameraSettingsButton.label "Settings…">
|
||||
<!ENTITY cameraSettingsButton.accesskey "t">
|
||||
|
||||
<!ENTITY microphonePermissions.label "Microphone">
|
||||
<!ENTITY microphoneSettingsButton.accesskey "m">
|
||||
<!ENTITY microphoneSettingsButton.label "Settings…">
|
||||
<!ENTITY microphoneSettingsButton.accesskey "t">
|
||||
|
||||
<!ENTITY fontsAndColors.label "Fonts & Colors">
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<!-- Localization note (reportDeceptiveSiteMenu.title) - Label of the Help menu
|
||||
item. Either this or safeb.palm.notdeceptive.label from
|
||||
phishing-afterload-warning-message.dtd is shown. -->
|
||||
<!ENTITY reportDeceptiveSiteMenu.title "Report deceptive site…">
|
||||
<!ENTITY reportDeceptiveSiteMenu.title "Report Deceptive Site…">
|
||||
<!-- Localization note (reportDeceptiveSiteMenu.accesskey) - Because
|
||||
safeb.palm.notdeceptive.label from phishing-afterload-warning-message.dtd and
|
||||
reportDeceptiveSiteMenu.title are never shown at the same time, the same
|
||||
|
|
|
@ -342,7 +342,7 @@ var ProcessHangMonitor = {
|
|||
[addonName, brandBundle.getString("brandShortName")]);
|
||||
|
||||
let linkText = bundle.getString("processHang.add-on.learn-more.text");
|
||||
let linkURL = bundle.getString("processHang.add-on.learn-more.url");
|
||||
let linkURL = "https://support.mozilla.org/kb/warning-unresponsive-script#w_other-causes";
|
||||
|
||||
let link = doc.createElement("label");
|
||||
link.setAttribute("class", "text-link");
|
||||
|
|
|
@ -578,23 +578,6 @@ html|span.ac-emphasize-text-url {
|
|||
list-style-image: url("chrome://browser/skin/Info.png");
|
||||
}
|
||||
|
||||
/* Reader mode button */
|
||||
|
||||
#reader-mode-button {
|
||||
list-style-image: url("chrome://browser/skin/readerMode.svg");
|
||||
-moz-image-region: rect(0, 16px, 16px, 0);
|
||||
}
|
||||
|
||||
#reader-mode-button:hover,
|
||||
#reader-mode-button[readeractive]:hover {
|
||||
-moz-image-region: rect(0, 32px, 16px, 16px);
|
||||
}
|
||||
|
||||
#reader-mode-button:hover:active,
|
||||
#reader-mode-button[readeractive] {
|
||||
-moz-image-region: rect(0, 48px, 16px, 32px);
|
||||
}
|
||||
|
||||
/* Bookmarking panel */
|
||||
#editBookmarkPanelStarIcon {
|
||||
list-style-image: url("chrome://browser/skin/places/starred48.png");
|
||||
|
@ -624,8 +607,9 @@ html|span.ac-emphasize-text-url {
|
|||
|
||||
%include ../shared/sidebar.inc.css
|
||||
|
||||
#sidebar {
|
||||
background-color: Window;
|
||||
#sidebar-box {
|
||||
background-color: -moz-Field;
|
||||
color: -moz-FieldText;
|
||||
}
|
||||
|
||||
#sidebar-header {
|
||||
|
|
|
@ -4,6 +4,11 @@
|
|||
|
||||
/* Sidebars */
|
||||
|
||||
:root {
|
||||
-moz-appearance: none;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
#sidebar-search-container {
|
||||
padding: 4px;
|
||||
}
|
||||
|
|
|
@ -587,21 +587,6 @@ html|span.ac-emphasize-text-url {
|
|||
}
|
||||
}
|
||||
|
||||
/* Reader mode button */
|
||||
|
||||
#reader-mode-button {
|
||||
list-style-image: url("chrome://browser/skin/readerMode.svg");
|
||||
-moz-image-region: rect(0, 16px, 16px, 0);
|
||||
}
|
||||
|
||||
#reader-mode-button:hover:active {
|
||||
-moz-image-region: rect(0, 32px, 16px, 16px);
|
||||
}
|
||||
|
||||
#reader-mode-button[readeractive] {
|
||||
-moz-image-region: rect(0, 48px, 16px, 32px);
|
||||
}
|
||||
|
||||
/* BOOKMARKING PANEL */
|
||||
#editBookmarkPanelStarIcon {
|
||||
list-style-image: url("chrome://browser/skin/places/starred48.png");
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
class="fieldtext"
|
||||
width="16" height="16" viewBox="0 0 16 16">
|
||||
#include ../icon-colors.inc.svg
|
||||
<defs>
|
||||
<path id="shape-notifications-addons" d="M10,15c0.5,0,1-0.4,1-1v-3c0,0,0-0.8,0.8-0.8c0.6,0,0.6,0.8,1.8,0.8c0.6,0,1.5-0.2,1.5-2c0-1.8-0.9-2-1.5-2 c-1.1,0-1.1,0.7-1.8,0.7C11,7.7,11,7,11,7V6c0-0.6-0.5-1-1-1H8c0,0-0.8,0-0.8-0.8C7.2,3.6,8,3.6,8,2.5C8,1.9,7.8,1,6,1 C4.2,1,4,1.9,4,2.5c0,1.1,0.8,1.1,0.8,1.8C4.8,5,4,5,4,5H2C1.5,5,1,5.4,1,6l0,1.5c0,0-0.1,1,1.1,1c0.8,0,0.9-1,1.9-1 C4.5,7.4,5,8,5,9c0,1-0.5,1.6-1,1.6c-1,0-1.1-1.1-1.9-1.1C0.9,9.5,1,10.8,1,10.8V14c0,0.6,0.5,1,1,1l2.6,0c0,0,1.1,0,1.1-1 c0-0.8-1-0.1-1-1.1c0-0.5,0.7-1.2,1.8-1.2s1.8,0.7,1.8,1.2c0,1-1.1,0.3-1.1,1.1c0,1,1.2,1,1.2,1H10z"/>
|
||||
</defs>
|
||||
<use xlink:href="#shape-notifications-addons"/>
|
||||
</svg>
|
До Ширина: | Высота: | Размер: 1.1 KiB |
|
@ -2,5 +2,5 @@
|
|||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<path fill="context-fill" d="M14.3 15h-1.5a.789.789 0 0 1-.8-.8s.3-3.6-3.2-7.2c-2.5-3-7-3.2-7-3.2a.713.713 0 0 1-.8-.7V1.7a.713.713 0 0 1 .8-.7s6.3.4 9.6 4.5c3.3 3.1 3.6 8.8 3.6 8.8a.632.632 0 0 1-.7.7zM1.8 6s3.7.5 5.8 2.4c2.1 2 2.5 5.9 2.5 5.9 0 .4-.1.8-.5.8H8.1c-.4 0-.6-.3-.6-.8a5.929 5.929 0 0 0-1.8-4.2 7.256 7.256 0 0 0-3.9-1.4A.713.713 0 0 1 1 8V6.7a.713.713 0 0 1 .8-.7zM3 11a2 2 0 1 1-2 2 2.006 2.006 0 0 1 2-2z"/>
|
||||
<path fill="context-fill" d="M3.5 10A2.5 2.5 0 1 0 6 12.5 2.5 2.5 0 0 0 3.5 10zM2 1a1 1 0 0 0 0 2 10.883 10.883 0 0 1 11 11 1 1 0 0 0 2 0A12.862 12.862 0 0 0 2 1zm0 4a1 1 0 0 0 0 2 6.926 6.926 0 0 1 7 7 1 1 0 0 0 2 0 8.9 8.9 0 0 0-9-9z"/>
|
||||
</svg>
|
||||
|
|
До Ширина: | Высота: | Размер: 729 B После Ширина: | Высота: | Размер: 544 B |
|
@ -2,5 +2,5 @@
|
|||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<path fill="context-fill" d="M9.5 1A5.549 5.549 0 0 0 4 6.5a5.291 5.291 0 0 0 .9 3L1.3 13a1.217 1.217 0 0 0 0 1.6 1.2 1.2 0 0 0 .8.4 1.33 1.33 0 0 0 .8-.3l3.5-3.5a5.291 5.291 0 0 0 3 .9A5.551 5.551 0 0 0 9.5 1zm0 9A3.543 3.543 0 0 1 6 6.5a3.5 3.5 0 0 1 7 0A3.543 3.543 0 0 1 9.5 10z"/>
|
||||
<path fill="context-fill" d="M15.707 14.293l-4.822-4.822a6.019 6.019 0 1 0-1.414 1.414l4.822 4.822a1 1 0 0 0 1.414-1.414zM6 10a4 4 0 1 1 4-4 4 4 0 0 1-4 4z"/>
|
||||
</svg>
|
||||
|
|
До Ширина: | Высота: | Размер: 591 B После Ширина: | Высота: | Размер: 464 B |
|
@ -2,5 +2,5 @@
|
|||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<path fill="context-fill" d="M12 11V5l3 3zm-7 1h6l-3 3zm5-1H6a.945.945 0 0 1-1-1V6a.945.945 0 0 1 1-1h4a.945.945 0 0 1 1 1v4a1 1 0 0 1-1 1zm0-3a.945.945 0 0 0-1-1H7a.945.945 0 0 0-1 1v1a.945.945 0 0 0 1 1h2a.945.945 0 0 0 1-1zM8 1l3 3H5zM4 5v6L1 8z"/>
|
||||
<path fill="context-fill" d="M7.707 8.293a1 1 0 0 0-1.414 0L3 11.586V9a1 1 0 0 0-2 0v5a1 1 0 0 0 1 1h5a1 1 0 1 0 0-2H4.414l3.293-3.293a1 1 0 0 0 0-1.414zM14 1H9a1 1 0 0 0 0 2h2.586L8.293 6.293a1 1 0 1 0 1.414 1.414L13 4.414V7a1 1 0 0 0 2 0V2a1 1 0 0 0-1-1z"/>
|
||||
</svg>
|
||||
|
|
До Ширина: | Высота: | Размер: 557 B После Ширина: | Высота: | Размер: 565 B |
|
@ -2,5 +2,5 @@
|
|||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<path fill="context-fill" d="M11.4 13.7l-.6-1.5c.3-.2.5-.4.8-.6.2-.2.4-.5.6-.7l1.5.6c.3.1.5 0 .7-.3l.4-.9c.1-.3 0-.5-.3-.7L12.9 9a6.054 6.054 0 0 0 0-1.9l1.5-.6a.517.517 0 0 0 .3-.7l-.4-.9a.517.517 0 0 0-.7-.3l-1.5.6a1.874 1.874 0 0 0-.6-.7c-.2-.2-.5-.4-.7-.6l.6-1.5c.1-.3 0-.5-.3-.7l-.9-.4c-.3-.1-.5 0-.7.3L9 3.1a6.054 6.054 0 0 0-1.9 0l-.6-1.5a.517.517 0 0 0-.7-.3l-.9.4a.61.61 0 0 0-.3.7l.6 1.5a1.874 1.874 0 0 0-.7.6 4.349 4.349 0 0 0-.6.7l-1.5-.7c-.3-.1-.5 0-.7.3l-.3.9a.574.574 0 0 0 .2.7l1.5.6a6.054 6.054 0 0 0 0 1.9l-1.5.6a.517.517 0 0 0-.3.7l.4.9a.517.517 0 0 0 .7.3l1.5-.6a1.874 1.874 0 0 0 .6.7c.2.2.5.4.7.6l-.6 1.5c-.1.3 0 .5.3.7l.9.4c.3.1.5 0 .7-.3l.5-1.5a6.054 6.054 0 0 0 1.9 0l.6 1.5a.517.517 0 0 0 .7.3l.9-.4c.2-.1.4-.4.3-.6zm-5-4.1a2.263 2.263 0 1 1 3.2-3.2 2.263 2.263 0 0 1-3.2 3.2z"/>
|
||||
<path fill="context-fill" d="M15 7h-2.1a4.967 4.967 0 0 0-.732-1.753l1.49-1.49a1 1 0 0 0-1.414-1.414l-1.49 1.49A4.968 4.968 0 0 0 9 3.1V1a1 1 0 0 0-2 0v2.1a4.968 4.968 0 0 0-1.753.732l-1.49-1.49a1 1 0 0 0-1.414 1.415l1.49 1.49A4.967 4.967 0 0 0 3.1 7H1a1 1 0 0 0 0 2h2.1a4.968 4.968 0 0 0 .737 1.763c-.014.013-.032.017-.045.03l-1.45 1.45a1 1 0 1 0 1.414 1.414l1.45-1.45c.013-.013.018-.031.03-.045A4.968 4.968 0 0 0 7 12.9V15a1 1 0 0 0 2 0v-2.1a4.968 4.968 0 0 0 1.753-.732l1.49 1.49a1 1 0 0 0 1.414-1.414l-1.49-1.49A4.967 4.967 0 0 0 12.9 9H15a1 1 0 0 0 0-2zM5 8a3 3 0 1 1 3 3 3 3 0 0 1-3-3z"/>
|
||||
</svg>
|
||||
|
|
До Ширина: | Высота: | Размер: 1.1 KiB После Ширина: | Высота: | Размер: 900 B |
2
browser/themes/shared/icons/fullscreen-enter.svg → browser/themes/shared/icons/window.svg
Executable file → Normal file
|
@ -2,5 +2,5 @@
|
|||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<path fill="context-fill" d="M7.707 8.293a1 1 0 0 0-1.414 0L3 11.586V9a1 1 0 0 0-2 0v5a1 1 0 0 0 1 1h5a1 1 0 1 0 0-2H4.414l3.293-3.293a1 1 0 0 0 0-1.414zM14 1H9a1 1 0 0 0 0 2h2.586L8.293 6.293a1 1 0 1 0 1.414 1.414L13 4.414V7a1 1 0 0 0 2 0V2a1 1 0 0 0-1-1z"/>
|
||||
<path fill="context-fill" d="M13 1H3a3 3 0 0 0-3 3v8a3 3 0 0 0 3 3h11a2 2 0 0 0 2-2V4a3 3 0 0 0-3-3zm1 11a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V6h12zm0-7H2V4a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1z"/>
|
||||
</svg>
|
До Ширина: | Высота: | Размер: 565 B После Ширина: | Высота: | Размер: 490 B |
|
@ -1,6 +1,7 @@
|
|||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<path fill="context-fill" d="M13 9H9v4H7V9H3V7h4V3h2v4h4z"/>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="context-fill">
|
||||
<path d="M14 7H9V2a1 1 0 0 0-2 0v5H2a1 1 0 0 0 0 2h5v5a1 1 0 0 0 2 0V9h5a1 1 0 0 0 0-2z"/>
|
||||
</svg>
|
||||
|
||||
|
|
До Ширина: | Высота: | Размер: 366 B После Ширина: | Высота: | Размер: 397 B |
|
@ -1,6 +1,6 @@
|
|||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<path fill="context-fill" d="M3 7h10v2H3z"/>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="context-fill">
|
||||
<rect x="2" y="7" width="12" height="2" rx="1"/>
|
||||
</svg>
|
||||
|
|
До Ширина: | Высота: | Размер: 350 B После Ширина: | Высота: | Размер: 354 B |
|
@ -110,7 +110,7 @@
|
|||
}
|
||||
|
||||
#urlbar[actiontype="extension"] > #identity-box > #identity-icon {
|
||||
list-style-image: url(chrome://browser/skin/addons/addon-install-anchor.svg);
|
||||
list-style-image: url(chrome://mozapps/skin/extensions/extensionGeneric-16.svg);
|
||||
}
|
||||
|
||||
/* SHARING ICON */
|
||||
|
|
|
@ -566,20 +566,19 @@ separator.thin:not([orient="vertical"]) {
|
|||
color: var(--in-content-link-color);
|
||||
}
|
||||
|
||||
#fxaLoginStatus[hasName] #fxaEmailAddress1 {
|
||||
font-size: 1.1rem;
|
||||
#fxaDisplayName {
|
||||
font-weight: bold;
|
||||
margin-inline-end: 10px !important;
|
||||
}
|
||||
|
||||
#fxaEmailAddress1,
|
||||
#fxaEmailAddress2,
|
||||
#fxaEmailAddress3 {
|
||||
word-break: break-all;
|
||||
.fxaEmailAddress {
|
||||
margin-inline-end: 8px !important;
|
||||
}
|
||||
|
||||
.fxaLoginRejectedWarning {
|
||||
list-style-image: url(chrome://browser/skin/warning.svg);
|
||||
filter: drop-shadow(0 1px 0 hsla(206, 50%, 10%, .15));
|
||||
margin: 4px 8px 0px 0px;
|
||||
margin-inline-start: 4px;
|
||||
margin-inline-end: 8px;
|
||||
}
|
||||
|
||||
#fxaSyncEngines > vbox > checkbox {
|
||||
|
|
|
@ -20,7 +20,6 @@
|
|||
skin/classic/browser/addons/addon-install-installed.svg (../shared/addons/addon-install-installed.svg)
|
||||
skin/classic/browser/addons/addon-install-restart.svg (../shared/addons/addon-install-restart.svg)
|
||||
skin/classic/browser/addons/addon-install-warning.svg (../shared/addons/addon-install-warning.svg)
|
||||
* skin/classic/browser/addons/addon-install-anchor.svg (../shared/addons/addon-install-anchor.svg)
|
||||
* skin/classic/browser/controlcenter/conn-not-secure.svg (../shared/controlcenter/conn-not-secure.svg)
|
||||
* skin/classic/browser/controlcenter/connection.svg (../shared/controlcenter/connection.svg)
|
||||
* skin/classic/browser/controlcenter/mcb-disabled.svg (../shared/controlcenter/mcb-disabled.svg)
|
||||
|
@ -147,7 +146,6 @@
|
|||
skin/classic/browser/forget.svg (../shared/icons/forget.svg)
|
||||
skin/classic/browser/forward.svg (../shared/icons/forward.svg)
|
||||
skin/classic/browser/fullscreen.svg (../shared/icons/fullscreen.svg)
|
||||
skin/classic/browser/fullscreen-enter.svg (../shared/icons/fullscreen-enter.svg)
|
||||
skin/classic/browser/fullscreen-exit.svg (../shared/icons/fullscreen-exit.svg)
|
||||
skin/classic/browser/history.svg (../shared/icons/history.svg)
|
||||
skin/classic/browser/home.svg (../shared/icons/home.svg)
|
||||
|
@ -175,6 +173,7 @@
|
|||
skin/classic/browser/synced-tabs.svg (../shared/icons/synced-tabs.svg)
|
||||
skin/classic/browser/toolbar.svg (../shared/icons/toolbar.svg)
|
||||
skin/classic/browser/webIDE.svg (../shared/icons/webIDE.svg)
|
||||
skin/classic/browser/window.svg (../shared/icons/window.svg)
|
||||
skin/classic/browser/zoom-in.svg (../shared/icons/zoom-in.svg)
|
||||
skin/classic/browser/zoom-out.svg (../shared/icons/zoom-out.svg)
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
/* Menu panel and palette styles */
|
||||
|
||||
#appMenuRecentlyClosedWindows,
|
||||
#appMenu-new-window-button {
|
||||
list-style-image: url(chrome://browser/skin/new-window.svg);
|
||||
}
|
||||
|
@ -63,7 +62,7 @@
|
|||
}
|
||||
|
||||
#appMenu-fullscreen-button {
|
||||
list-style-image: url(chrome://browser/skin/fullscreen-enter.svg);
|
||||
list-style-image: url(chrome://browser/skin/fullscreen.svg);
|
||||
}
|
||||
|
||||
#appMenu-fullscreen-button[checked] {
|
||||
|
@ -123,3 +122,8 @@ toolbarpaletteitem[place="palette"] > #bookmarks-menu-button,
|
|||
#appMenuRestoreLastSession {
|
||||
list-style-image: url("chrome://browser/skin/reload.svg");
|
||||
}
|
||||
|
||||
#appMenuRecentlyClosedWindows {
|
||||
list-style-image: url(chrome://browser/skin/window.svg);
|
||||
}
|
||||
|
||||
|
|
|
@ -175,7 +175,7 @@ html|*#webRTC-previewVideo {
|
|||
/* INSTALL ADDONS */
|
||||
|
||||
.install-icon {
|
||||
list-style-image: url(chrome://browser/skin/addons/addon-install-anchor.svg);
|
||||
list-style-image: url(chrome://mozapps/skin/extensions/extensionGeneric-16.svg);
|
||||
}
|
||||
|
||||
.popup-notification-icon[popupid="xpinstall-disabled"],
|
||||
|
|
Двоичные данные
browser/themes/shared/reader/reader-mode-16.png
До Ширина: | Высота: | Размер: 502 B |
Двоичные данные
browser/themes/shared/reader/reader-mode-16@2x.png
До Ширина: | Высота: | Размер: 1.0 KiB |
|
@ -1,29 +1,6 @@
|
|||
<?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/. -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="48" height="16" viewBox="0 0 48 16">
|
||||
<defs>
|
||||
<path id="glyphShape-readerMode-book" d="M5.5,5h-2C3.2,5,3,5.2,3,5.5S3.2,6,3.5,6h2 C5.8,6,6,5.8,6,5.5S5.8,5,5.5,5z M5.5,7h-2C3.2,7,3,7.2,3,7.5S3.2,8,3.5,8h2C5.8,8,6,7.8,6,7.5S5.8,7,5.5,7z M5.5,9h-2 C3.2,9,3,9.2,3,9.5S3.2,10,3.5,10h2C5.8,10,6,9.8,6,9.5S5.8,9,5.5,9z M15.4,2c0,0-3.1,0-4.4,0S8.1,2.5,8,4.3C7.9,2.5,6.3,2,5,2 S0.6,2,0.6,2C0.3,2,0,2.3,0,2.7v9.6C0,12.6,0.3,13,0.6,13c0,0,2.6,0,4.4,0c1.6,0,2.8,1,3,2.3C8.2,14,9.4,13,11,13 c1.8,0,4.4,0,4.4,0c0.4,0,0.6-0.4,0.6-0.8V2.7C16,2.3,15.7,2,15.4,2z M14,11L14,11c-0.2,0-1.6,0-3,0c-1.6,0-2.9,0.8-3,2.2 C7.9,11.8,6.6,11,5,11c-1.4,0-2.8,0-3,0l0,0l0,0V4c0,0,2.7,0,3.5,0C6.6,4,8,5.5,8,6.8C8,5.5,9.4,4,10.5,4C11.3,4,14,4,14,4V11 L14,11z"/>
|
||||
<linearGradient id="gradient-state-default" x1="0%" y1="0%" x2="0" y2="100%">
|
||||
<stop stop-color="#989898" offset="0%"/>
|
||||
<stop stop-color="#808080" offset="100%"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="gradient-state-hover" x1="0%" y1="0%" x2="0" y2="100%">
|
||||
<stop stop-color="#24aef4" offset="0%"/>
|
||||
<stop stop-color="#177bdb" offset="100%"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="gradient-state-pressed" x1="0%" y1="0%" x2="0" y2="100%">
|
||||
<stop stop-color="#ff9300" offset="0%"/>
|
||||
<stop stop-color="#ff5500" offset="100%"/>
|
||||
</linearGradient>
|
||||
<style>
|
||||
.icon-state-default { fill: url(#gradient-state-default); }
|
||||
.icon-state-hover { fill: url(#gradient-state-hover); }
|
||||
.icon-state-pressed { fill: url(#gradient-state-pressed); }
|
||||
</style>
|
||||
</defs>
|
||||
<use xlink:href="#glyphShape-readerMode-book" class="icon-state-default"/>
|
||||
<use xlink:href="#glyphShape-readerMode-book" class="icon-state-hover" transform="translate(16)"/>
|
||||
<use xlink:href="#glyphShape-readerMode-book" class="icon-state-pressed" transform="translate(32)"/>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16">
|
||||
<path fill="context-fill" fill-opacity="context-fill-opacity" d="M13 0H3a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2zm0 13a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V3a1 1 0 0 1 1-1h8a1 1 0 0 1 1 1zm-2.5-9h-5a.5.5 0 0 0 0 1h5a.5.5 0 0 0 0-1zm0 2h-5a.5.5 0 0 0 0 1h5a.5.5 0 1 0 0-1zm0 2h-5a.5.5 0 0 0 0 1h5a.5.5 0 1 0 0-1zm-3 2h-2a.5.5 0 0 0 0 1h2a.5.5 0 1 0 0-1z"/>
|
||||
</svg>
|
||||
|
|
До Ширина: | Высота: | Размер: 2.2 KiB После Ширина: | Высота: | Размер: 664 B |
|
@ -345,9 +345,11 @@
|
|||
background-clip: padding-box;
|
||||
}
|
||||
|
||||
%ifdef MENUBAR_CAN_AUTOHIDE
|
||||
#toolbar-menubar:not([autohide=true]) ~ #TabsToolbar > #tabbrowser-tabs > .tabbrowser-tab > .tab-stack > .tab-background {
|
||||
border-top-style: solid;
|
||||
}
|
||||
%endif
|
||||
|
||||
.tab-background[selected=true] {
|
||||
border-left-style: solid;
|
||||
|
|
|
@ -125,6 +125,8 @@
|
|||
opacity: 0;
|
||||
}
|
||||
|
||||
/* Page actions */
|
||||
|
||||
#page-action-buttons {
|
||||
-moz-box-align: center;
|
||||
/* Add more space between the last icon and the urlbar's edge. */
|
||||
|
@ -220,7 +222,19 @@
|
|||
animation-name: bookmark-animation-rtl;
|
||||
}
|
||||
|
||||
/* Reader mode icon */
|
||||
|
||||
#reader-mode-button {
|
||||
list-style-image: url(chrome://browser/skin/readerMode.svg);
|
||||
}
|
||||
|
||||
#reader-mode-button[readeractive] {
|
||||
fill: var(--toolbarbutton-icon-fill-attention);
|
||||
fill-opacity: 1;
|
||||
}
|
||||
|
||||
/* Zoom button */
|
||||
|
||||
#urlbar-zoom-button {
|
||||
margin: 0 3px;
|
||||
font-size: .8em;
|
||||
|
|
|
@ -16,12 +16,6 @@
|
|||
}
|
||||
|
||||
@media (-moz-windows-default-theme) {
|
||||
.sidebar-header,
|
||||
#sidebar-header {
|
||||
-moz-appearance: none;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.menu-accel,
|
||||
.menu-iconic-accel {
|
||||
color: graytext;
|
||||
|
|
|
@ -766,23 +766,6 @@ treechildren.searchbar-treebody::-moz-tree-row(selected) {
|
|||
-moz-image-region: rect(0, 48px, 16px, 32px);
|
||||
}
|
||||
|
||||
/* Reader mode button */
|
||||
|
||||
#reader-mode-button {
|
||||
list-style-image: url("chrome://browser/skin/readerMode.svg");
|
||||
-moz-image-region: rect(0, 16px, 16px, 0);
|
||||
}
|
||||
|
||||
#reader-mode-button:hover,
|
||||
#reader-mode-button[readeractive]:hover {
|
||||
-moz-image-region: rect(0, 32px, 16px, 16px);
|
||||
}
|
||||
|
||||
#reader-mode-button:hover:active,
|
||||
#reader-mode-button[readeractive] {
|
||||
-moz-image-region: rect(0, 48px, 16px, 32px);
|
||||
}
|
||||
|
||||
/* bookmarking panel */
|
||||
|
||||
#editBookmarkPanelStarIcon {
|
||||
|
@ -813,8 +796,9 @@ treechildren.searchbar-treebody::-moz-tree-row(selected) {
|
|||
|
||||
%include ../shared/sidebar.inc.css
|
||||
|
||||
#sidebar {
|
||||
background-color: Window;
|
||||
#sidebar-box {
|
||||
background-color: -moz-Field;
|
||||
color: -moz-FieldText;
|
||||
}
|
||||
|
||||
#sidebar-header {
|
||||
|
|
|
@ -3,11 +3,24 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/* Sidebars */
|
||||
|
||||
:root {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
#sidebar-search-container {
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.sidebar-placesTree {
|
||||
-moz-appearance: none;
|
||||
border: 0;
|
||||
margin: 0;
|
||||
border-top: 1px solid ThreeDShadow;
|
||||
}
|
||||
|
||||
.sidebar-placesTreechildren::-moz-tree-cell,
|
||||
.sidebar-placesTreechildren::-moz-tree-twisty {
|
||||
padding: 0 4px;
|
||||
}
|
||||
|
||||
.sidebar-placesTreechildren::-moz-tree-cell(leaf) ,
|
||||
|
|
|
@ -3693,7 +3693,7 @@ MediaElementTableCount(HTMLMediaElement* aElement, nsIURI* aURI)
|
|||
void
|
||||
HTMLMediaElement::AddMediaElementToURITable()
|
||||
{
|
||||
NS_ASSERTION(mDecoder && mDecoder->GetResource(), "Call this only with decoder Load called");
|
||||
NS_ASSERTION(mDecoder, "Call this only with decoder Load called");
|
||||
NS_ASSERTION(MediaElementTableCount(this, mLoadingSrc) == 0,
|
||||
"Should not have entry for element in element table before addition");
|
||||
if (!gElementTable) {
|
||||
|
|
|
@ -8,9 +8,10 @@ KillScriptWithDebugMessage=A script on this page may be busy, or it may have sto
|
|||
KillScriptLocation=Script: %S
|
||||
|
||||
KillAddonScriptTitle=Warning: Unresponsive add-on script
|
||||
# LOCALIZATION NOTE (KillAddonScriptMessage): The first %S is the name of an add-on. The second %S is the name of the application (e.g., Firefox).
|
||||
KillAddonScriptMessage=A script from the add-on “%S” is running on this page, and making %S unresponsive.\n\nIt may be busy, or it may have stopped responsing permanently. You can stop the script now, or you can continue to see if it will complete.
|
||||
KillAddonScriptGlobalMessage=Prevent the add-on script from running on this page until it next reloads.
|
||||
# LOCALIZATION NOTE (KillAddonScriptMessage): %1$S is the name of an extension.
|
||||
# %2$S is the name of the application (e.g., Firefox).
|
||||
KillAddonScriptMessage=A script from the extension “%1$S” is running on this page, and making %2$S unresponsive.\n\nIt may be busy, or it may have stopped responding permanently. You can stop the script now, or you can continue to see if it will complete.
|
||||
KillAddonScriptGlobalMessage=Prevent the extension script from running on this page until it next reloads
|
||||
|
||||
StopScriptButton=Stop script
|
||||
DebugScriptButton=Debug script
|
||||
|
|
|
@ -350,6 +350,13 @@ ChannelMediaDecoder::CanPlayThroughImpl()
|
|||
return GetStatistics().CanPlayThrough();
|
||||
}
|
||||
|
||||
bool
|
||||
ChannelMediaDecoder::IsLiveStream()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
return mResource->IsLiveStream();
|
||||
}
|
||||
|
||||
void
|
||||
ChannelMediaDecoder::OnPlaybackEvent(MediaEventType aEvent)
|
||||
{
|
||||
|
|
|
@ -67,8 +67,6 @@ protected:
|
|||
public:
|
||||
explicit ChannelMediaDecoder(MediaDecoderInit& aInit);
|
||||
|
||||
MediaResource* GetResource() const override final;
|
||||
|
||||
void Shutdown() override;
|
||||
|
||||
bool CanClone();
|
||||
|
@ -85,6 +83,8 @@ public:
|
|||
void Resume() override;
|
||||
|
||||
private:
|
||||
MediaResource* GetResource() const override final;
|
||||
|
||||
// Create a new state machine to run this decoder.
|
||||
MediaDecoderStateMachine* CreateStateMachine();
|
||||
|
||||
|
@ -104,6 +104,8 @@ private:
|
|||
|
||||
bool CanPlayThroughImpl() override final;
|
||||
|
||||
bool IsLiveStream() override final;
|
||||
|
||||
// The actual playback rate computation.
|
||||
void ComputePlaybackRate();
|
||||
|
||||
|
|
|
@ -565,7 +565,6 @@ void
|
|||
MediaDecoder::FinishShutdown()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
mDecoderStateMachine->BreakCycles();
|
||||
SetStateMachine(nullptr);
|
||||
mVideoFrameContainer = nullptr;
|
||||
MediaShutdownManager::Instance().Unregister(this);
|
||||
|
@ -682,6 +681,11 @@ MediaDecoder::CallSeek(const SeekTarget& aTarget)
|
|||
AbstractThread::AutoEnter context(AbstractMainThread());
|
||||
DiscardOngoingSeekIfExists();
|
||||
|
||||
// Since we don't have a listener for changes in IsLiveStream, our best bet
|
||||
// is to ensure IsLiveStream is uptodate when seek begins. This value will be
|
||||
// checked when seek is completed.
|
||||
mDecoderStateMachine->DispatchIsLiveStream(IsLiveStream());
|
||||
|
||||
mDecoderStateMachine->InvokeSeek(aTarget)
|
||||
->Then(mAbstractMainThread, __func__, this,
|
||||
&MediaDecoder::OnSeekResolved, &MediaDecoder::OnSeekRejected)
|
||||
|
@ -804,9 +808,9 @@ MediaDecoder::FirstFrameLoaded(nsAutoPtr<MediaInfo> aInfo,
|
|||
AbstractThread::AutoEnter context(AbstractMainThread());
|
||||
|
||||
LOG("FirstFrameLoaded, channels=%u rate=%u hasAudio=%d hasVideo=%d "
|
||||
"mPlayState=%s",
|
||||
"mPlayState=%s transportSeekable=%d",
|
||||
aInfo->mAudio.mChannels, aInfo->mAudio.mRate, aInfo->HasAudio(),
|
||||
aInfo->HasVideo(), PlayStateStr());
|
||||
aInfo->HasVideo(), PlayStateStr(), GetResource()->IsTransportSeekable());
|
||||
|
||||
mInfo = aInfo.forget();
|
||||
|
||||
|
|
|
@ -120,15 +120,6 @@ public:
|
|||
// Called if the media file encounters a network error.
|
||||
void NetworkError();
|
||||
|
||||
// Get the current MediaResource being used.
|
||||
// Note: The MediaResource is refcounted, but it outlives the MediaDecoder,
|
||||
// so it's OK to use the reference returned by this function without
|
||||
// refcounting, *unless* you need to store and use the reference after the
|
||||
// MediaDecoder has been destroyed. You might need to do this if you're
|
||||
// wrapping the MediaResource in some kind of byte stream interface to be
|
||||
// passed to a platform decoder.
|
||||
virtual MediaResource* GetResource() const = 0;
|
||||
|
||||
// Return the principal of the current URI being played or downloaded.
|
||||
virtual already_AddRefed<nsIPrincipal> GetCurrentPrincipal();
|
||||
|
||||
|
@ -490,6 +481,15 @@ protected:
|
|||
media::TimeUnit::FromMicroseconds(250000);
|
||||
|
||||
private:
|
||||
// Get the current MediaResource being used.
|
||||
// Note: The MediaResource is refcounted, but it outlives the MediaDecoder,
|
||||
// so it's OK to use the reference returned by this function without
|
||||
// refcounting, *unless* you need to store and use the reference after the
|
||||
// MediaDecoder has been destroyed. You might need to do this if you're
|
||||
// wrapping the MediaResource in some kind of byte stream interface to be
|
||||
// passed to a platform decoder.
|
||||
virtual MediaResource* GetResource() const = 0;
|
||||
|
||||
nsCString GetDebugInfo();
|
||||
|
||||
// Called when the owner's activity changed.
|
||||
|
@ -510,6 +510,7 @@ private:
|
|||
void DisconnectMirrors();
|
||||
|
||||
virtual bool CanPlayThroughImpl() = 0;
|
||||
virtual bool IsLiveStream() = 0;
|
||||
|
||||
// The state machine object for handling the decoding. It is safe to
|
||||
// call methods of this object from other threads. Its internal data
|
||||
|
|
|
@ -257,7 +257,6 @@ protected:
|
|||
using Master = MediaDecoderStateMachine;
|
||||
explicit StateObject(Master* aPtr) : mMaster(aPtr) { }
|
||||
TaskQueue* OwnerThread() const { return mMaster->mTaskQueue; }
|
||||
MediaResource* Resource() const { return mMaster->mResource; }
|
||||
ReaderProxy* Reader() const { return mMaster->mReader; }
|
||||
const MediaInfo& Info() const { return mMaster->Info(); }
|
||||
MediaQueue<AudioData>& AudioQueue() const { return mMaster->mAudioQueue; }
|
||||
|
@ -2470,8 +2469,7 @@ SeekingState::SeekCompleted()
|
|||
{
|
||||
const auto newCurrentTime = CalculateNewCurrentTime();
|
||||
|
||||
bool isLiveStream = Resource()->IsLiveStream();
|
||||
if (newCurrentTime == mMaster->Duration() && !isLiveStream) {
|
||||
if (newCurrentTime == mMaster->Duration() && !mMaster->mIsLiveStream) {
|
||||
// Seeked to end of media. Explicitly finish the queues so DECODING
|
||||
// will transition to COMPLETED immediately. Note we don't do
|
||||
// this when playing a live stream, since the end of media will advance
|
||||
|
@ -2687,7 +2685,6 @@ MediaDecoderStateMachine::MediaDecoderStateMachine(MediaDecoder* aDecoder,
|
|||
mVideoDecodeSuspended(false),
|
||||
mVideoDecodeSuspendTimer(mTaskQueue),
|
||||
mOutputStreamManager(new OutputStreamManager()),
|
||||
mResource(aDecoder->GetResource()),
|
||||
mVideoDecodeMode(VideoDecodeMode::Normal),
|
||||
mIsMSE(aDecoder->IsMSE()),
|
||||
INIT_MIRROR(mBuffered, TimeIntervals()),
|
||||
|
@ -3455,10 +3452,8 @@ MediaDecoderStateMachine::FinishDecodeFirstFrame()
|
|||
|
||||
mMediaSink->Redraw(Info().mVideo);
|
||||
|
||||
LOG("Media duration %" PRId64 ", "
|
||||
"transportSeekable=%d, mediaSeekable=%d",
|
||||
Duration().ToMicroseconds(), mResource->IsTransportSeekable(),
|
||||
mMediaSeekable);
|
||||
LOG("Media duration %" PRId64 ", mediaSeekable=%d",
|
||||
Duration().ToMicroseconds(), mMediaSeekable);
|
||||
|
||||
// Get potentially updated metadata
|
||||
mReader->ReadUpdatedMetadata(mInfo.ptr());
|
||||
|
|
|
@ -219,10 +219,15 @@ public:
|
|||
OwnerThread()->DispatchStateChange(r.forget());
|
||||
}
|
||||
|
||||
// Drop reference to mResource. Only called during shutdown dance.
|
||||
void BreakCycles() {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
mResource = nullptr;
|
||||
void DispatchIsLiveStream(bool aIsLiveStream)
|
||||
{
|
||||
RefPtr<MediaDecoderStateMachine> self = this;
|
||||
nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
|
||||
"MediaDecoderStateMachine::DispatchIsLiveStream",
|
||||
[self, aIsLiveStream]() {
|
||||
self->mIsLiveStream = aIsLiveStream;
|
||||
});
|
||||
OwnerThread()->DispatchStateChange(r.forget());
|
||||
}
|
||||
|
||||
TimedMetadataEventSource& TimedMetadataEvent() {
|
||||
|
@ -584,6 +589,8 @@ private:
|
|||
|
||||
bool mCanPlayThrough = false;
|
||||
|
||||
bool mIsLiveStream = false;
|
||||
|
||||
// True if we shouldn't play our audio (but still write it to any capturing
|
||||
// streams). When this is true, the audio thread will never start again after
|
||||
// it has stopped.
|
||||
|
@ -633,9 +640,6 @@ private:
|
|||
// Data about MediaStreams that are being fed by the decoder.
|
||||
const RefPtr<OutputStreamManager> mOutputStreamManager;
|
||||
|
||||
// Media data resource from the decoder.
|
||||
RefPtr<MediaResource> mResource;
|
||||
|
||||
// Track the current video decode mode.
|
||||
VideoDecodeMode mVideoDecodeMode;
|
||||
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
#include "nsIChannel.h"
|
||||
#include "nsIURI.h"
|
||||
#include "nsISeekableStream.h"
|
||||
#include "nsIStreamingProtocolController.h"
|
||||
#include "nsIStreamListener.h"
|
||||
#include "nsIChannelEventSink.h"
|
||||
#include "nsIInterfaceRequestor.h"
|
||||
|
@ -244,12 +243,6 @@ public:
|
|||
*/
|
||||
virtual nsresult GetCachedRanges(MediaByteRangeSet& aRanges) = 0;
|
||||
|
||||
// Returns true if the resource is a live stream.
|
||||
virtual bool IsLiveStream()
|
||||
{
|
||||
return GetLength() == -1;
|
||||
}
|
||||
|
||||
virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
|
||||
return 0;
|
||||
}
|
||||
|
@ -336,6 +329,9 @@ public:
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
// Returns true if the resource is a live stream.
|
||||
bool IsLiveStream() { return GetLength() == -1; }
|
||||
|
||||
size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override
|
||||
{
|
||||
// Might be useful to track in the future:
|
||||
|
|
|
@ -21,8 +21,6 @@ public:
|
|||
{
|
||||
}
|
||||
|
||||
MediaResource* GetResource() const override final;
|
||||
|
||||
void Shutdown() override;
|
||||
|
||||
// Returns true if the HLS backend is pref'ed on.
|
||||
|
@ -43,6 +41,8 @@ public:
|
|||
void Resume() override;
|
||||
|
||||
private:
|
||||
MediaResource* GetResource() const override final;
|
||||
|
||||
MediaDecoderStateMachine* CreateStateMachine();
|
||||
|
||||
bool CanPlayThroughImpl() override final
|
||||
|
@ -52,6 +52,8 @@ private:
|
|||
return true;
|
||||
}
|
||||
|
||||
bool IsLiveStream() override final { return false; }
|
||||
|
||||
RefPtr<HLSResource> mResource;
|
||||
};
|
||||
|
||||
|
|
|
@ -81,11 +81,6 @@ public:
|
|||
|
||||
bool IsTransportSeekable() override { return true; }
|
||||
|
||||
bool IsLiveStream() override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
java::GeckoHLSResourceWrapper::GlobalRef GetResourceWrapper() {
|
||||
return mHLSResourceWrapper;
|
||||
}
|
||||
|
|
|
@ -152,12 +152,21 @@ VideoDecoderChild::InitIPDL(const VideoInfo& aVideoInfo,
|
|||
{
|
||||
RefPtr<VideoDecoderManagerChild> manager =
|
||||
VideoDecoderManagerChild::GetSingleton();
|
||||
// If the manager isn't available, then don't initialize mIPDLSelfRef and
|
||||
|
||||
// The manager isn't available because VideoDecoderManagerChild has been
|
||||
// initialized with null end points and we don't want to decode video on GPU
|
||||
// process anymore. Return false here so that we can fallback to other PDMs.
|
||||
if (!manager) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The manager doesn't support sending messages because we've just crashed
|
||||
// and are working on reinitialization. Don't initialize mIPDLSelfRef and
|
||||
// leave us in an error state. We'll then immediately reject the promise when
|
||||
// Init() is called and the caller can try again. Hopefully by then the new
|
||||
// manager is ready, or we've notified the caller of it being no longer
|
||||
// available. If not, then the cycle repeats until we're ready.
|
||||
if (!manager || !manager->CanSend()) {
|
||||
if (!manager->CanSend()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -27,8 +27,6 @@ class MediaSourceDecoder : public MediaDecoder
|
|||
public:
|
||||
explicit MediaSourceDecoder(MediaDecoderInit& aInit);
|
||||
|
||||
MediaResource* GetResource() const override final;
|
||||
|
||||
nsresult Load(nsIPrincipal* aPrincipal);
|
||||
media::TimeIntervals GetSeekable() override;
|
||||
media::TimeIntervals GetBuffered() override;
|
||||
|
@ -64,10 +62,12 @@ public:
|
|||
void NotifyInitDataArrived();
|
||||
|
||||
private:
|
||||
MediaResource* GetResource() const override final;
|
||||
MediaDecoderStateMachine* CreateStateMachine();
|
||||
void DoSetMediaSourceDuration(double aDuration);
|
||||
media::TimeInterval ClampIntervalToEnd(const media::TimeInterval& aInterval);
|
||||
bool CanPlayThroughImpl() override;
|
||||
bool IsLiveStream() override final { return !mEnded; }
|
||||
|
||||
RefPtr<MediaSourceResource> mResource;
|
||||
|
||||
|
|
|
@ -59,11 +59,6 @@ public:
|
|||
|
||||
bool IsTransportSeekable() override { return true; }
|
||||
|
||||
bool IsLiveStream() override
|
||||
{
|
||||
MonitorAutoLock mon(mMonitor);
|
||||
return !mEnded;
|
||||
}
|
||||
void SetEnded(bool aEnded)
|
||||
{
|
||||
MonitorAutoLock mon(mMonitor);
|
||||
|
|
|
@ -70,7 +70,7 @@ install:: ../js-config.h
|
|||
#
|
||||
|
||||
install::
|
||||
$(call py_action,process_install_manifest,--no-remove --no-symlinks $(DESTDIR)$(includedir) $(DEPTH)/_build_manifests/install/dist_include)
|
||||
$(call py_action,process_install_manifest,--track install_dist_include.track --no-symlinks $(DESTDIR)$(includedir) $(DEPTH)/_build_manifests/install/dist_include)
|
||||
|
||||
#
|
||||
# END SpiderMonkey header installation
|
||||
|
|
|
@ -269,8 +269,10 @@ PRMJ_FormatTime(char* buf, int buflen, const char* fmt, PRMJTime* prtm)
|
|||
int fake_tm_year = 0;
|
||||
#ifdef XP_WIN
|
||||
_invalid_parameter_handler oldHandler;
|
||||
#ifndef __MINGW32__
|
||||
int oldReportMode;
|
||||
#endif
|
||||
#endif // __MINGW32__
|
||||
#endif //XP_WIN
|
||||
|
||||
memset(&a, 0, sizeof(struct tm));
|
||||
|
||||
|
@ -343,15 +345,23 @@ PRMJ_FormatTime(char* buf, int buflen, const char* fmt, PRMJTime* prtm)
|
|||
|
||||
#ifdef XP_WIN
|
||||
oldHandler = _set_invalid_parameter_handler(PRMJ_InvalidParameterHandler);
|
||||
#ifndef __MINGW32__
|
||||
/*
|
||||
* MinGW doesn't have _CrtSetReportMode and defines it to be a no-op.
|
||||
* We ifdef it off to avoid warnings about unused variables
|
||||
*/
|
||||
oldReportMode = _CrtSetReportMode(_CRT_ASSERT, 0);
|
||||
#endif
|
||||
#endif // __MINGW32__
|
||||
#endif // XP_WIN
|
||||
|
||||
result = strftime(buf, buflen, fmt, &a);
|
||||
|
||||
#ifdef XP_WIN
|
||||
_set_invalid_parameter_handler(oldHandler);
|
||||
#ifndef __MINGW32__
|
||||
_CrtSetReportMode(_CRT_ASSERT, oldReportMode);
|
||||
#endif
|
||||
#endif // __MINGW32__
|
||||
#endif // XP_WIN
|
||||
|
||||
if (fake_tm_year && result) {
|
||||
char real_year[16];
|
||||
|
|
|
@ -1311,6 +1311,27 @@ nsRefreshDriver::RemoveRefreshObserver(nsARefreshObserver* aObserver,
|
|||
return array.RemoveElement(aObserver);
|
||||
}
|
||||
|
||||
void
|
||||
nsRefreshDriver::PostScrollEvent(mozilla::Runnable* aScrollEvent)
|
||||
{
|
||||
mScrollEvents.AppendElement(aScrollEvent);
|
||||
EnsureTimerStarted();
|
||||
}
|
||||
|
||||
void
|
||||
nsRefreshDriver::DispatchScrollEvents()
|
||||
{
|
||||
// Scroll events are one-shot, so after running them we can drop them.
|
||||
// However, dispatching a scroll event can potentially cause more scroll
|
||||
// events to be posted, so we move the initial set into a temporary array
|
||||
// first. (Newly posted scroll events will be dispatched on the next tick.)
|
||||
ScrollEventArray events;
|
||||
events.SwapElements(mScrollEvents);
|
||||
for (auto& event : events) {
|
||||
event->Run();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
nsRefreshDriver::AddPostRefreshObserver(nsAPostRefreshObserver* aObserver)
|
||||
{
|
||||
|
@ -1817,7 +1838,7 @@ nsRefreshDriver::Tick(int64_t aNowEpoch, TimeStamp aNowTime)
|
|||
mWarningThreshold = 1;
|
||||
|
||||
nsCOMPtr<nsIPresShell> presShell = mPresContext->GetPresShell();
|
||||
if (!presShell || (ObserverCount() == 0 && ImageRequestCount() == 0)) {
|
||||
if (!presShell || (ObserverCount() == 0 && ImageRequestCount() == 0 && mScrollEvents.Length() == 0)) {
|
||||
// Things are being destroyed, or we no longer have any observers.
|
||||
// We don't want to stop the timer when observers are initially
|
||||
// removed, because sometimes observers can be added and removed
|
||||
|
@ -1876,6 +1897,7 @@ nsRefreshDriver::Tick(int64_t aNowEpoch, TimeStamp aNowTime)
|
|||
DispatchAnimationEvents();
|
||||
DispatchPendingEvents();
|
||||
RunFrameRequestCallbacks(aNowTime);
|
||||
DispatchScrollEvents();
|
||||
|
||||
if (mPresContext && mPresContext->GetPresShell()) {
|
||||
Maybe<AutoProfilerTracing> tracingStyleFlush;
|
||||
|
|
|
@ -37,6 +37,7 @@ class nsIRunnable;
|
|||
|
||||
namespace mozilla {
|
||||
class RefreshDriverTimer;
|
||||
class Runnable;
|
||||
namespace layout {
|
||||
class VsyncChild;
|
||||
} // namespace layout
|
||||
|
@ -125,6 +126,9 @@ public:
|
|||
bool RemoveRefreshObserver(nsARefreshObserver *aObserver,
|
||||
mozilla::FlushType aFlushType);
|
||||
|
||||
void PostScrollEvent(mozilla::Runnable* aScrollEvent);
|
||||
void DispatchScrollEvents();
|
||||
|
||||
/**
|
||||
* Add an observer that will be called after each refresh. The caller
|
||||
* must remove the observer before it is deleted. This does not trigger
|
||||
|
@ -362,6 +366,7 @@ public:
|
|||
|
||||
private:
|
||||
typedef nsTObserverArray<nsARefreshObserver*> ObserverArray;
|
||||
typedef nsTArray<RefPtr<mozilla::Runnable>> ScrollEventArray;
|
||||
typedef nsTHashtable<nsISupportsHashKey> RequestTable;
|
||||
struct ImageStartData {
|
||||
ImageStartData()
|
||||
|
@ -468,6 +473,7 @@ private:
|
|||
RequestTable mRequests;
|
||||
ImageStartTable mStartTable;
|
||||
AutoTArray<nsCOMPtr<nsIRunnable>, 16> mEarlyRunners;
|
||||
ScrollEventArray mScrollEvents;
|
||||
|
||||
struct PendingEvent {
|
||||
nsCOMPtr<nsINode> mTarget;
|
||||
|
|
|
@ -176,7 +176,6 @@ static void Shutdown();
|
|||
|
||||
#include "mozilla/dom/power/PowerManagerService.h"
|
||||
#include "mozilla/dom/time/TimeService.h"
|
||||
#include "StreamingProtocolService.h"
|
||||
|
||||
#include "nsIPresentationService.h"
|
||||
|
||||
|
@ -199,7 +198,6 @@ using mozilla::dom::workers::ServiceWorkerManager;
|
|||
using mozilla::dom::workers::WorkerDebuggerManager;
|
||||
using mozilla::dom::UDPSocketChild;
|
||||
using mozilla::dom::time::TimeService;
|
||||
using mozilla::net::StreamingProtocolControllerService;
|
||||
using mozilla::gmp::GeckoMediaPluginService;
|
||||
using mozilla::dom::NotificationTelemetryService;
|
||||
|
||||
|
@ -279,8 +277,6 @@ NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsIPowerManagerService,
|
|||
PowerManagerService::GetInstance)
|
||||
NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsITimeService,
|
||||
TimeService::GetInstance)
|
||||
NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsIStreamingProtocolControllerService,
|
||||
StreamingProtocolControllerService::GetInstance)
|
||||
|
||||
NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsIMediaManagerService,
|
||||
MediaManager::GetInstance)
|
||||
|
@ -686,7 +682,6 @@ NS_DEFINE_NAMED_CID(NS_POWERMANAGERSERVICE_CID);
|
|||
NS_DEFINE_NAMED_CID(OSFILECONSTANTSSERVICE_CID);
|
||||
NS_DEFINE_NAMED_CID(UDPSOCKETCHILD_CID);
|
||||
NS_DEFINE_NAMED_CID(NS_TIMESERVICE_CID);
|
||||
NS_DEFINE_NAMED_CID(NS_MEDIASTREAMCONTROLLERSERVICE_CID);
|
||||
NS_DEFINE_NAMED_CID(NS_MEDIAMANAGERSERVICE_CID);
|
||||
#ifdef MOZ_WEBSPEECH_TEST_BACKEND
|
||||
NS_DEFINE_NAMED_CID(NS_FAKE_SPEECH_RECOGNITION_SERVICE_CID);
|
||||
|
@ -960,7 +955,6 @@ static const mozilla::Module::CIDEntry kLayoutCIDs[] = {
|
|||
{ &kUDPSOCKETCHILD_CID, false, nullptr, UDPSocketChildConstructor },
|
||||
{ &kGECKO_MEDIA_PLUGIN_SERVICE_CID, true, nullptr, GeckoMediaPluginServiceConstructor },
|
||||
{ &kNS_TIMESERVICE_CID, false, nullptr, nsITimeServiceConstructor },
|
||||
{ &kNS_MEDIASTREAMCONTROLLERSERVICE_CID, false, nullptr, nsIStreamingProtocolControllerServiceConstructor },
|
||||
{ &kNS_MEDIAMANAGERSERVICE_CID, false, nullptr, nsIMediaManagerServiceConstructor },
|
||||
#ifdef ACCESSIBILITY
|
||||
{ &kNS_ACCESSIBILITY_SERVICE_CID, false, nullptr, CreateA11yService },
|
||||
|
@ -1089,7 +1083,6 @@ static const mozilla::Module::ContractIDEntry kLayoutContracts[] = {
|
|||
{ OSFILECONSTANTSSERVICE_CONTRACTID, &kOSFILECONSTANTSSERVICE_CID },
|
||||
{ "@mozilla.org/udp-socket-child;1", &kUDPSOCKETCHILD_CID },
|
||||
{ TIMESERVICE_CONTRACTID, &kNS_TIMESERVICE_CID },
|
||||
{ MEDIASTREAMCONTROLLERSERVICE_CONTRACTID, &kNS_MEDIASTREAMCONTROLLERSERVICE_CID },
|
||||
{ MEDIAMANAGERSERVICE_CONTRACTID, &kNS_MEDIAMANAGERSERVICE_CID },
|
||||
#ifdef ACCESSIBILITY
|
||||
{ "@mozilla.org/accessibilityService;1", &kNS_ACCESSIBILITY_SERVICE_CID },
|
||||
|
|
|
@ -2075,6 +2075,9 @@ ScrollFrameHelper::ScrollFrameHelper(nsContainerFrame* aOuter,
|
|||
|
||||
ScrollFrameHelper::~ScrollFrameHelper()
|
||||
{
|
||||
if (mScrollEvent) {
|
||||
mScrollEvent->Revoke();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -4727,26 +4730,19 @@ void ScrollFrameHelper::CurPosAttributeChanged(nsIContent* aContent)
|
|||
/* ============= Scroll events ========== */
|
||||
|
||||
ScrollFrameHelper::ScrollEvent::ScrollEvent(ScrollFrameHelper* aHelper)
|
||||
: mHelper(aHelper)
|
||||
: Runnable("ScrollFrameHelper::ScrollEvent")
|
||||
, mHelper(aHelper)
|
||||
{
|
||||
mDriver = mHelper->mOuter->PresContext()->RefreshDriver();
|
||||
mDriver->AddRefreshObserver(this, FlushType::Layout);
|
||||
mHelper->mOuter->PresContext()->RefreshDriver()->PostScrollEvent(this);
|
||||
}
|
||||
|
||||
ScrollFrameHelper::ScrollEvent::~ScrollEvent()
|
||||
NS_IMETHODIMP
|
||||
ScrollFrameHelper::ScrollEvent::Run()
|
||||
{
|
||||
if (mDriver) {
|
||||
mDriver->RemoveRefreshObserver(this, FlushType::Layout);
|
||||
mDriver = nullptr;
|
||||
if (mHelper) {
|
||||
mHelper->FireScrollEvent();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ScrollFrameHelper::ScrollEvent::WillRefresh(mozilla::TimeStamp aTime)
|
||||
{
|
||||
mDriver->RemoveRefreshObserver(this, FlushType::Layout);
|
||||
mDriver = nullptr;
|
||||
mHelper->FireScrollEvent();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -4754,6 +4750,7 @@ ScrollFrameHelper::FireScrollEvent()
|
|||
{
|
||||
AutoProfilerTracing tracing("Paint", "FireScrollEvent");
|
||||
MOZ_ASSERT(mScrollEvent);
|
||||
mScrollEvent->Revoke();
|
||||
mScrollEvent = nullptr;
|
||||
|
||||
ActiveLayerTracker::SetCurrentScrollHandlerFrame(mOuter);
|
||||
|
|
|
@ -100,36 +100,30 @@ public:
|
|||
/**
|
||||
* This class handles the dispatching of scroll events to content.
|
||||
*
|
||||
* nsRefreshDriver maintains three lists of refresh observers, one for each
|
||||
* flush type: FlushType::Style, FlushType::Layout, and FlushType::Display.
|
||||
* Scroll events are posted to the refresh driver via
|
||||
* nsRefreshDriver::PostScrollEvent(), and they are fired during a refresh
|
||||
* driver tick, after running requestAnimationFrame callbacks but before
|
||||
* the style flush. This allows rAF callbacks to perform scrolling and have
|
||||
* that scrolling be reflected on the same refresh driver tick, while at
|
||||
* the same time allowing scroll event listeners to make style changes and
|
||||
* have those style changes be reflected on the same refresh driver tick.
|
||||
*
|
||||
* During a tick, it runs through each list of observers, in order, and runs
|
||||
* them. To iterate over each list, it uses an EndLimitedIterator, which is
|
||||
* designed to iterate only over elements present when the iterator was
|
||||
* created, not elements added afterwards. This means that, for a given flush
|
||||
* type, a refresh observer added during the execution of another refresh
|
||||
* observer of that flush type, will not run until the next tick.
|
||||
* ScrollEvents cannot be refresh observers, because none of the existing
|
||||
* categories of refresh observers (FlushType::Style, FlushType::Layout,
|
||||
* and FlushType::Display) are run at the desired time in a refresh driver
|
||||
* tick. They behave similarly to refresh observers in that their presence
|
||||
* causes the refresh driver to tick.
|
||||
*
|
||||
* During main-thread animation-driven scrolling, ScrollEvents are *posted*
|
||||
* by AsyncScroll::WillRefresh(). AsyncScroll registers itself as a FlushType::Style
|
||||
* refresh observer.
|
||||
*
|
||||
* Posting a scroll event, as of bug 1250550, registers a FlushType::Layout
|
||||
* refresh observer, which *fires* the event when run. This allows the event
|
||||
* to be fired to content in the same refresh driver tick as it is posted.
|
||||
* This is an important invariant to maintain to reduce scroll event latency
|
||||
* for main-thread scrolling.
|
||||
* ScrollEvents are one-shot runnables; the refresh driver drops them after
|
||||
* running them.
|
||||
*/
|
||||
class ScrollEvent : public nsARefreshObserver {
|
||||
class ScrollEvent : public Runnable {
|
||||
public:
|
||||
NS_INLINE_DECL_REFCOUNTING(ScrollEvent, override)
|
||||
explicit ScrollEvent(ScrollFrameHelper *helper);
|
||||
void WillRefresh(mozilla::TimeStamp aTime) override;
|
||||
protected:
|
||||
virtual ~ScrollEvent();
|
||||
NS_DECL_NSIRUNNABLE
|
||||
explicit ScrollEvent(ScrollFrameHelper* aHelper);
|
||||
void Revoke() { mHelper = nullptr; }
|
||||
private:
|
||||
ScrollFrameHelper *mHelper;
|
||||
RefPtr<nsRefreshDriver> mDriver;
|
||||
ScrollFrameHelper* mHelper;
|
||||
};
|
||||
|
||||
class AsyncScrollPortEvent : public Runnable {
|
||||
|
|
|
@ -151,7 +151,7 @@ static LogState *createLogState()
|
|||
{
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < sizeof(openLogTable); i++) {
|
||||
for (i = 0; i < MAX_OPEN_LOGS; i++) {
|
||||
if (openLogTable[i] == NULL) {
|
||||
openLogTable[i] = calloc(1, sizeof(LogState));
|
||||
openLogTable[i]->fakeFd = FAKE_FD_BASE + i;
|
||||
|
|
|
@ -34,6 +34,26 @@ public:
|
|||
|
||||
#define MOZ_ALIGNOF(T) mozilla::AlignmentFinder<T>::alignment
|
||||
|
||||
namespace detail {
|
||||
template<typename T>
|
||||
struct AlignasHelper
|
||||
{
|
||||
T mT;
|
||||
};
|
||||
} // namespace detail
|
||||
|
||||
/*
|
||||
* Use this instead of alignof to align struct field as if it is inside
|
||||
* a struct. On some platforms, there exist types which have different
|
||||
* alignment between when it is used on its own and when it is used on
|
||||
* a struct field.
|
||||
*
|
||||
* Known examples are 64bit types (uint64_t, double) on 32bit Linux,
|
||||
* where they have 8byte alignment on their own, and 4byte alignment
|
||||
* when in struct.
|
||||
*/
|
||||
#define MOZ_ALIGNAS_IN_STRUCT(T) alignas(mozilla::detail::AlignasHelper<T>)
|
||||
|
||||
/*
|
||||
* Declare the MOZ_ALIGNED_DECL macro for declaring aligned types.
|
||||
*
|
||||
|
|
|
@ -85,7 +85,7 @@ struct Nothing { };
|
|||
template<class T>
|
||||
class MOZ_NON_PARAM MOZ_INHERIT_TYPE_ANNOTATIONS_FROM_TEMPLATE_ARGS Maybe
|
||||
{
|
||||
alignas(T) unsigned char mStorage[sizeof(T)];
|
||||
MOZ_ALIGNAS_IN_STRUCT(T) unsigned char mStorage[sizeof(T)];
|
||||
char mIsSome; // not bool -- guarantees minimal space consumption
|
||||
|
||||
// GCC fails due to -Werror=strict-aliasing if |mStorage| is directly cast to
|
||||
|
|
|
@ -632,7 +632,9 @@ pref("media.mediadrm-widevinecdm.visible", true);
|
|||
pref("media.eme.enabled", true);
|
||||
#endif
|
||||
|
||||
#ifdef NIGHTLY_BUILD
|
||||
pref("media.hls.enabled", true);
|
||||
#endif
|
||||
|
||||
// Whether to suspend decoding of videos in background tabs.
|
||||
pref("media.suspend-bkgnd-video.enabled", true);
|
||||
|
|
|
@ -27,7 +27,6 @@
|
|||
android:layout_marginBottom="@dimen/activity_stream_base_margin"
|
||||
android:layout_gravity="center"
|
||||
tools:background="@drawable/favicon_globe" />
|
||||
<!-- todo: overrideScaleType, scaleType=fitCnetetr -->
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
|
|
|
@ -5,12 +5,16 @@
|
|||
<merge xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:gecko="http://schemas.android.com/tools">
|
||||
|
||||
<!-- The default visibilities are set in code. -->
|
||||
<!-- The default visibilities are set in code.
|
||||
|
||||
centerInside will center smaller favicons and draw a colored border around them. -->
|
||||
<org.mozilla.gecko.widget.FaviconView
|
||||
android:id="@+id/favicon_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
gecko:enableRoundCorners="false"
|
||||
gecko:overrideScaleType="false"
|
||||
android:scaleType="centerInside"
|
||||
/>
|
||||
|
||||
<ImageView
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
android:layout_height="match_parent"
|
||||
android:layout_marginBottom="@dimen/activity_stream_base_margin">
|
||||
|
||||
<!-- centerInside will center smaller favicons and draw a colored border around them. -->
|
||||
<org.mozilla.gecko.widget.FaviconView
|
||||
android:id="@+id/favicon"
|
||||
android:layout_width="match_parent"
|
||||
|
@ -16,6 +17,8 @@
|
|||
android:scaleType="centerInside"
|
||||
gecko:overrideScaleType="false" />
|
||||
|
||||
<!-- scrollHorizontally=false allows drags on the TextView to scroll the ViewPager.
|
||||
See https://stackoverflow.com/a/18171834/2219998 -->
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="match_parent"
|
||||
|
@ -24,12 +27,12 @@
|
|||
android:padding="5dp"
|
||||
android:drawablePadding="2dp"
|
||||
android:maxLines="1"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
android:gravity="center"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="12sp"
|
||||
android:layout_gravity="bottom"
|
||||
android:scrollHorizontally="false"
|
||||
tools:text="Lorem Ipsum here is a title" />
|
||||
|
||||
</FrameLayout>
|
||||
|
|
|
@ -52,6 +52,7 @@ public class AppConstants {
|
|||
public static final boolean feature19Plus = MIN_SDK_VERSION >= 19 || (MAX_SDK_VERSION >= 19 && Build.VERSION.SDK_INT >= 19);
|
||||
public static final boolean feature20Plus = MIN_SDK_VERSION >= 20 || (MAX_SDK_VERSION >= 20 && Build.VERSION.SDK_INT >= 20);
|
||||
public static final boolean feature21Plus = MIN_SDK_VERSION >= 21 || (MAX_SDK_VERSION >= 21 && Build.VERSION.SDK_INT >= 21);
|
||||
public static final boolean feature23Plus = MIN_SDK_VERSION >= 23 || (MAX_SDK_VERSION >= 23 && Build.VERSION.SDK_INT >= 23);
|
||||
public static final boolean feature24Plus = MIN_SDK_VERSION >= 24 || (MAX_SDK_VERSION >= 24 && Build.VERSION.SDK_INT >= 24);
|
||||
public static final boolean feature26Plus = MIN_SDK_VERSION >= 26 || (MAX_SDK_VERSION >= 26 && Build.VERSION.SDK_INT >= 26);
|
||||
|
||||
|
|
|
@ -28,7 +28,12 @@ public static final String MOZ_LEANPLUM_SDK_CLIENTID =
|
|||
null;
|
||||
//#endif
|
||||
|
||||
public static final String MOZ_MMA_SENDER_ID = "242693410970";
|
||||
public static final String MOZ_MMA_SENDER_ID =
|
||||
//#ifdef MOZ_MMA_GCM_SENDERID
|
||||
"@MOZ_MMA_GCM_SENDERID@";
|
||||
//#else
|
||||
null;
|
||||
//#endif
|
||||
|
||||
public static MmaInterface getMma() {
|
||||
//#ifdef MOZ_ANDROID_MMA
|
||||
|
|
|
@ -955,6 +955,7 @@ sync_java_files = [TOPSRCDIR + '/mobile/android/services/src/main/java/org/mozil
|
|||
'sync/repositories/android/BookmarksRepository.java',
|
||||
'sync/repositories/android/BookmarksRepositorySession.java',
|
||||
'sync/repositories/android/BookmarksSessionHelper.java',
|
||||
'sync/repositories/android/BookmarksValidationRepository.java',
|
||||
'sync/repositories/android/BrowserContractHelpers.java',
|
||||
'sync/repositories/android/CachedSQLiteOpenHelper.java',
|
||||
'sync/repositories/android/ClientsDatabase.java',
|
||||
|
@ -1067,6 +1068,7 @@ sync_java_files = [TOPSRCDIR + '/mobile/android/services/src/main/java/org/mozil
|
|||
'sync/stage/ServerSyncStage.java',
|
||||
'sync/stage/SyncClientsEngineStage.java',
|
||||
'sync/stage/UploadMetaGlobalStage.java',
|
||||
'sync/stage/ValidateBookmarksSyncStage.java',
|
||||
'sync/stage/VersionedServerSyncStage.java',
|
||||
'sync/SyncConfiguration.java',
|
||||
'sync/SyncConfigurationException.java',
|
||||
|
@ -1095,6 +1097,10 @@ sync_java_files = [TOPSRCDIR + '/mobile/android/services/src/main/java/org/mozil
|
|||
'sync/UnexpectedJSONException.java',
|
||||
'sync/UnknownSynchronizerConfigurationVersionException.java',
|
||||
'sync/Utils.java',
|
||||
'sync/validation/BookmarkValidationResults.java',
|
||||
'sync/validation/BookmarkValidator.java',
|
||||
'sync/validation/CollectionValidator.java',
|
||||
'sync/validation/ValidationResults.java',
|
||||
'tokenserver/TokenServerClient.java',
|
||||
'tokenserver/TokenServerClientDelegate.java',
|
||||
'tokenserver/TokenServerException.java',
|
||||
|
|
|
@ -60,6 +60,7 @@ def _defines():
|
|||
DEFINES[var] = 1
|
||||
|
||||
for var in ('MOZ_ANDROID_GCM_SENDERID',
|
||||
'MOZ_MMA_GCM_SENDERID',
|
||||
'MOZ_ANDROID_MAX_SDK_VERSION',
|
||||
'MOZ_ANDROID_MIN_SDK_VERSION',
|
||||
'MOZ_PKG_SPECIAL',
|
||||
|
|
|
@ -2767,7 +2767,22 @@ public class BrowserProvider extends SharedBrowserDatabaseProvider {
|
|||
compiledStatement.clearBindings();
|
||||
compiledStatement.bindLong(1, syncVersion); // NB: 1-based index.
|
||||
compiledStatement.bindString(2, guid);
|
||||
changed += compiledStatement.executeUpdateDelete();
|
||||
|
||||
// We expect this to be 1.
|
||||
final int didUpdate = compiledStatement.executeUpdateDelete();
|
||||
|
||||
// These strong assertions are here to help figure out root cause of Bug 1392078.
|
||||
// This will throw if there are duplicate GUIDs present.
|
||||
if (didUpdate > 1) {
|
||||
throw new IllegalStateException("Modified more than a single GUID during syncVersion update");
|
||||
}
|
||||
|
||||
// This will throw if the requested GUID is missing from the database.
|
||||
if (didUpdate == 0) {
|
||||
throw new IllegalStateException("Expected to modify syncVersion for a guid, but did not");
|
||||
}
|
||||
|
||||
changed += didUpdate;
|
||||
}
|
||||
|
||||
markWriteSuccessful(db);
|
||||
|
|
|
@ -35,7 +35,7 @@ public class TelemetrySyncPingBuilder extends TelemetryLocalPingBuilder {
|
|||
final TelemetryStageCollector stage = stages.get(stageName);
|
||||
|
||||
// Skip stages that did nothing.
|
||||
if (stage.inbound == 0 && stage.outbound == 0) {
|
||||
if (stage.inbound == 0 && stage.outbound == 0 && stage.error == null && stage.validation == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -83,6 +83,10 @@ public class TelemetrySyncPingBuilder extends TelemetryLocalPingBuilder {
|
|||
if (stage.error != null) {
|
||||
stageJSON.put("failureReason", stage.error);
|
||||
}
|
||||
// As above for validation too.
|
||||
if (stage.validation != null) {
|
||||
stageJSON.put("validation", stage.validation);
|
||||
}
|
||||
|
||||
addUnchecked(engines, stageJSON);
|
||||
}
|
||||
|
|
|
@ -8,3 +8,4 @@ MOZ_UPDATER=
|
|||
MOZ_ANDROID_ANR_REPORTER=
|
||||
MOZ_ANDROID_SHARED_ID=org.mozilla.firefox.sharedID
|
||||
MOZ_ANDROID_GCM_SENDERID=965234145045
|
||||
MOZ_MMA_GCM_SENDERID=242693410970
|
|
@ -8,3 +8,4 @@ MOZ_UPDATER=1
|
|||
MOZ_ANDROID_ANR_REPORTER=1
|
||||
MOZ_ANDROID_SHARED_ID=org.mozilla.fennec.sharedID
|
||||
MOZ_ANDROID_GCM_SENDERID=965234145045
|
||||
MOZ_MMA_GCM_SENDERID=242693410970
|
||||
|
|
|
@ -8,3 +8,4 @@ MOZ_UPDATER=
|
|||
MOZ_ANDROID_ANR_REPORTER=
|
||||
MOZ_ANDROID_SHARED_ID=org.mozilla.firefox.sharedID
|
||||
MOZ_ANDROID_GCM_SENDERID=965234145045
|
||||
MOZ_MMA_GCM_SENDERID=242693410970
|
|
@ -7,3 +7,4 @@ MOZ_APP_DISPLAYNAME="Fennec `echo $USER | sed 's/-/_/g'`"
|
|||
MOZ_UPDATER=
|
||||
MOZ_ANDROID_ANR_REPORTER=
|
||||
MOZ_ANDROID_GCM_SENDERID=965234145045
|
||||
MOZ_MMA_GCM_SENDERID=242693410970
|
||||
|
|
|
@ -90,6 +90,9 @@
|
|||
case "unwantedBlocked" :
|
||||
error = "unwanted";
|
||||
break;
|
||||
case "harmfulBlocked" :
|
||||
error = "harmful";
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
@ -123,6 +126,13 @@
|
|||
el.remove();
|
||||
}
|
||||
|
||||
if (error !== "harmful") {
|
||||
el = document.getElementById("errorTitleText_harmful");
|
||||
el.remove();
|
||||
el = document.getElementById("errorShortDescText_harmful");
|
||||
el.remove();
|
||||
}
|
||||
|
||||
if (!getOverride()) {
|
||||
var btn = document.getElementById("ignoreWarningButton");
|
||||
if (btn) {
|
||||
|
@ -131,7 +141,11 @@
|
|||
}
|
||||
|
||||
// Set sitename
|
||||
document.getElementById(error + "_sitename").textContent = getHostString();
|
||||
let siteElem = document.getElementById(error + "_sitename");
|
||||
if (siteElem) {
|
||||
siteElem.textContent = getHostString();
|
||||
}
|
||||
|
||||
document.title = document.getElementById("errorTitleText_" + error)
|
||||
.innerHTML;
|
||||
|
||||
|
@ -151,15 +165,17 @@
|
|||
<h1 id="errorTitleText_phishing" class="errorTitleText">&safeb.blocked.phishingPage.title3;</h1>
|
||||
<h1 id="errorTitleText_malware" class="errorTitleText">&safeb.blocked.malwarePage.title;</h1>
|
||||
<h1 id="errorTitleText_unwanted" class="errorTitleText">&safeb.blocked.unwantedPage.title;</h1>
|
||||
<h1 id="errorTitleText_harmful" class="errorTitleText">&safeb.blocked.harmfulPage.title;</h1>
|
||||
</div>
|
||||
|
||||
<div id="errorLongContent">
|
||||
|
||||
|
||||
<!-- Short Description -->
|
||||
<div id="errorShortDesc">
|
||||
<p id="errorShortDescText_phishing">&safeb.blocked.phishingPage.shortDesc3;</p>
|
||||
<p id="errorShortDescText_malware">&safeb.blocked.malwarePage.shortDesc;</p>
|
||||
<p id="errorShortDescText_unwanted">&safeb.blocked.unwantedPage.shortDesc;</p>
|
||||
<p id="errorShortDescText_harmful">&safeb.blocked.harmfulPage.shortDesc;</p>
|
||||
</div>
|
||||
|
||||
<!-- Long Description -->
|
||||
|
|
|
@ -14,6 +14,8 @@ import android.view.View;
|
|||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import org.mozilla.gecko.AppConstants;
|
||||
|
||||
public class ActivityUtils {
|
||||
private ActivityUtils() {
|
||||
}
|
||||
|
@ -39,6 +41,13 @@ public class ActivityUtils {
|
|||
newVis = View.SYSTEM_UI_FLAG_VISIBLE;
|
||||
}
|
||||
|
||||
if (AppConstants.Versions.feature23Plus) {
|
||||
// We also have to set SYSTEM_UI_FLAG_LIGHT_STATUS_BAR with to current system ui status
|
||||
// to support both light and dark status bar.
|
||||
final int oldVis = window.getDecorView().getSystemUiVisibility();
|
||||
newVis |= (oldVis & View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
|
||||
}
|
||||
|
||||
window.getDecorView().setSystemUiVisibility(newVis);
|
||||
} else {
|
||||
window.setFlags(fullscreen ?
|
||||
|
|
|
@ -23,3 +23,5 @@
|
|||
<!ENTITY safeb.blocked.unwantedPage.shortDesc "This web page at <span id='unwanted_sitename'/> has been reported to contain unwanted software and has been blocked based on your security preferences.">
|
||||
<!ENTITY safeb.blocked.unwantedPage.longDesc "Unwanted software pages try to install software that can be deceptive and affect your system in unexpected ways.">
|
||||
|
||||
<!ENTITY safeb.blocked.harmfulPage.title "The site ahead may contain malware">
|
||||
<!ENTITY safeb.blocked.harmfulPage.shortDesc "&brandShortName; blocked this page because it might try to install dangerous apps that steal or delete your information (for example, photos, passwords, messages and credit cards).">
|
|
@ -88,7 +88,7 @@ project_flag('MOZ_SWITCHBOARD',
|
|||
|
||||
project_flag('MOZ_ANDROID_HLS_SUPPORT',
|
||||
help='Enable HLS (HTTP Live Streaming) support (currently using the ExoPlayer library)',
|
||||
default=True)
|
||||
default=milestone.is_nightly)
|
||||
|
||||
option(env='MOZ_ANDROID_ACTIVITY_STREAM',
|
||||
help='Enable Activity Stream on Android (replacing the default HomePager)',
|
||||
|
|
|
@ -44,6 +44,7 @@ import org.mozilla.gecko.sync.stage.NoSuchStageException;
|
|||
import org.mozilla.gecko.sync.stage.PasswordsServerSyncStage;
|
||||
import org.mozilla.gecko.sync.stage.SyncClientsEngineStage;
|
||||
import org.mozilla.gecko.sync.stage.UploadMetaGlobalStage;
|
||||
import org.mozilla.gecko.sync.stage.ValidateBookmarksSyncStage;
|
||||
import org.mozilla.gecko.sync.telemetry.TelemetryCollector;
|
||||
import org.mozilla.gecko.sync.telemetry.TelemetryStageCollector;
|
||||
|
||||
|
@ -207,6 +208,7 @@ public class GlobalSession implements HttpResponseObserver {
|
|||
stages.put(Stage.syncRecentHistory, new RecentHistoryServerSyncStage());
|
||||
|
||||
stages.put(Stage.syncBookmarks, new BookmarksServerSyncStage());
|
||||
stages.put(Stage.validateBookmarks, new ValidateBookmarksSyncStage());
|
||||
stages.put(Stage.syncFormHistory, new FormHistoryServerSyncStage());
|
||||
stages.put(Stage.syncFullHistory, new HistoryServerSyncStage());
|
||||
|
||||
|
|
|
@ -96,6 +96,7 @@ public class SyncConfiguration {
|
|||
public static final String CLIENTS_COLLECTION_TIMESTAMP = "serverClientsTimestamp"; // When the collection was touched.
|
||||
public static final String CLIENT_RECORD_TIMESTAMP = "serverClientRecordTimestamp"; // When our record was touched.
|
||||
public static final String MIGRATION_SENTINEL_CHECK_TIMESTAMP = "migrationSentinelCheckTimestamp"; // When we last looked in meta/fxa_credentials.
|
||||
public static final String BOOKMARK_VALIDATION_CHECK_TIMESTAMP = "bookmarkValidationCheckTimestamp"; // Last time we considered performing validation
|
||||
|
||||
public static final String PREF_CLUSTER_URL = "clusterURL";
|
||||
public static final String PREF_SYNC_ID = "syncID";
|
||||
|
@ -458,6 +459,14 @@ public class SyncConfiguration {
|
|||
return getPrefs().getLong(SyncConfiguration.MIGRATION_SENTINEL_CHECK_TIMESTAMP, 0L);
|
||||
}
|
||||
|
||||
public void persistLastValidationCheckTimestamp(long timestamp) {
|
||||
getEditor().putLong(SyncConfiguration.BOOKMARK_VALIDATION_CHECK_TIMESTAMP, timestamp).commit();
|
||||
}
|
||||
|
||||
public long getLastValidationCheckTimestamp() {
|
||||
return getPrefs().getLong(SyncConfiguration.BOOKMARK_VALIDATION_CHECK_TIMESTAMP, 0L);
|
||||
}
|
||||
|
||||
public void purgeCryptoKeys() {
|
||||
if (collectionKeys != null) {
|
||||
collectionKeys.clear();
|
||||
|
|
|
@ -27,7 +27,7 @@ public class ConfigurableServer15Repository extends Server15Repository {
|
|||
private final long batchLimit;
|
||||
private final ServerSyncStage.MultipleBatches multipleBatches;
|
||||
private final ServerSyncStage.HighWaterMark highWaterMark;
|
||||
|
||||
private final boolean forceFullFetch;
|
||||
public ConfigurableServer15Repository(
|
||||
String collection,
|
||||
long syncDeadline,
|
||||
|
@ -39,7 +39,8 @@ public class ConfigurableServer15Repository extends Server15Repository {
|
|||
String sort,
|
||||
ServerSyncStage.MultipleBatches multipleBatches,
|
||||
ServerSyncStage.HighWaterMark highWaterMark,
|
||||
RepositoryStateProvider stateProvider) throws URISyntaxException {
|
||||
RepositoryStateProvider stateProvider,
|
||||
boolean forceFullFetch) throws URISyntaxException {
|
||||
super(
|
||||
collection,
|
||||
syncDeadline,
|
||||
|
@ -53,6 +54,7 @@ public class ConfigurableServer15Repository extends Server15Repository {
|
|||
this.sortOrder = sort;
|
||||
this.multipleBatches = multipleBatches;
|
||||
this.highWaterMark = highWaterMark;
|
||||
this.forceFullFetch = forceFullFetch;
|
||||
|
||||
// Sanity check: let's ensure we're configured correctly. At this point in time, it doesn't make
|
||||
// sense to use H.W.M. with a non-persistent state provider. This might change if we start retrying
|
||||
|
@ -81,4 +83,10 @@ public class ConfigurableServer15Repository extends Server15Repository {
|
|||
public boolean getAllowHighWaterMark() {
|
||||
return highWaterMark.equals(ServerSyncStage.HighWaterMark.Enabled);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getFullFetchForced() {
|
||||
return forceFullFetch;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -111,6 +111,10 @@ public class Server15Repository extends Repository {
|
|||
return false;
|
||||
}
|
||||
|
||||
public boolean getFullFetchForced() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* A point in time by which this repository's session must complete fetch and store operations.
|
||||
* Particularly pertinent for batching downloads performed by the session (should we fetch
|
||||
|
|
|
@ -115,6 +115,10 @@ public class Server15RepositorySession extends RepositorySession {
|
|||
*/
|
||||
@Override
|
||||
public long getLastSyncTimestamp() {
|
||||
if (serverRepository.getFullFetchForced()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!serverRepository.getAllowHighWaterMark() || !serverRepository.getSortOrder().equals("oldest")) {
|
||||
return super.getLastSyncTimestamp();
|
||||
}
|
||||
|
|
|
@ -90,7 +90,12 @@ public class VersioningDelegateHelper {
|
|||
// At this point we can be sure that all records in our guidsMap have been successfully
|
||||
// uploaded. Move syncVersions forward for each GUID en masse.
|
||||
final Bundle data = new Bundle();
|
||||
data.putSerializable(BrowserContract.METHOD_PARAM_DATA, this.localVersionsOfGuids);
|
||||
final int versionMapSizeBeforeUpdate;
|
||||
|
||||
synchronized (this.localVersionsOfGuids) {
|
||||
data.putSerializable(BrowserContract.METHOD_PARAM_DATA, this.localVersionsOfGuids);
|
||||
versionMapSizeBeforeUpdate = this.localVersionsOfGuids.size();
|
||||
}
|
||||
|
||||
final Bundle result = context.getContentResolver().call(
|
||||
repositoryContentUri,
|
||||
|
@ -103,11 +108,21 @@ public class VersioningDelegateHelper {
|
|||
throw new IllegalStateException("Expected to receive a result after decrementing change counters");
|
||||
}
|
||||
|
||||
int changed = (int) result.getSerializable(BrowserContract.METHOD_RESULT);
|
||||
if (changed != this.localVersionsOfGuids.size()) {
|
||||
// TODO perhaps not worth throwing, but this shouldn't happen either...
|
||||
throw new IllegalStateException("Decremented incorrect number of change counters");
|
||||
final int changed = (int) result.getSerializable(BrowserContract.METHOD_RESULT);
|
||||
final int versionMapSizeAfterUpdate = this.localVersionsOfGuids.size();
|
||||
|
||||
// None of these should happen, but something's clearly amiss. These strong assertions are
|
||||
// here to help figure out root cause for Bug 1392078.
|
||||
if (versionMapSizeBeforeUpdate != versionMapSizeAfterUpdate) {
|
||||
throw new IllegalStateException("Version/guid map changed during syncVersion update");
|
||||
}
|
||||
|
||||
if (changed < versionMapSizeBeforeUpdate) {
|
||||
throw new IllegalStateException("Updated fewer sync versions than expected");
|
||||
}
|
||||
|
||||
if (changed > versionMapSizeBeforeUpdate) {
|
||||
throw new IllegalStateException("Updated more sync versions than expected");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,229 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.gecko.sync.repositories.android;
|
||||
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.SystemClock;
|
||||
|
||||
import org.mozilla.gecko.background.common.log.Logger;
|
||||
import org.mozilla.gecko.sync.ExtendedJSONObject;
|
||||
import org.mozilla.gecko.sync.delegates.ClientsDataDelegate;
|
||||
import org.mozilla.gecko.sync.repositories.InactiveSessionException;
|
||||
import org.mozilla.gecko.sync.repositories.InvalidSessionTransitionException;
|
||||
import org.mozilla.gecko.sync.repositories.NoStoreDelegateException;
|
||||
import org.mozilla.gecko.sync.repositories.Repository;
|
||||
import org.mozilla.gecko.sync.repositories.RepositorySession;
|
||||
import org.mozilla.gecko.sync.repositories.RepositorySessionBundle;
|
||||
import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionBeginDelegate;
|
||||
import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCreationDelegate;
|
||||
import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFetchRecordsDelegate;
|
||||
import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFinishDelegate;
|
||||
import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionWipeDelegate;
|
||||
import org.mozilla.gecko.sync.repositories.domain.BookmarkRecord;
|
||||
import org.mozilla.gecko.sync.repositories.domain.Record;
|
||||
import org.mozilla.gecko.sync.telemetry.TelemetryStageCollector;
|
||||
import org.mozilla.gecko.sync.validation.BookmarkValidationResults;
|
||||
import org.mozilla.gecko.sync.validation.BookmarkValidator;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
/**
|
||||
* This repository fetches all records that are present on the client or server, and runs
|
||||
* the BookmarkValidator on these, so that we can include that data in telemetry.
|
||||
*
|
||||
* This is obviously expensive, so it's worth noting that we don't run it frequently.
|
||||
* @see org.mozilla.gecko.sync.stage.ValidateBookmarksSyncStage for the set of requirements
|
||||
* that must be met in order to run validation.
|
||||
*
|
||||
* It's mostly concerned with the client's view of the world, and how client propagates that
|
||||
* view outwards. That is why we're wrapping a regular bookmarks session here, and capturing
|
||||
* any side-effects that its internal 'fetch' methods will have.
|
||||
*
|
||||
* We're not concerned with how client's view of the world is shaped - that is, we're
|
||||
* not capturing record reconciliation here directly. These sorts of effects will
|
||||
* (hopefully) be determined from validation results in aggregate.
|
||||
*
|
||||
* @see BookmarkValidationResults for the concrete set of problems it checks for.
|
||||
*/
|
||||
public class BookmarksValidationRepository extends Repository {
|
||||
private static final String LOG_TAG = "BookmarksValidationRepository";
|
||||
|
||||
protected final ClientsDataDelegate clientsDataDelegate;
|
||||
private final TelemetryStageCollector parentCollector;
|
||||
|
||||
public BookmarksValidationRepository(ClientsDataDelegate clientsDataDelegate, TelemetryStageCollector collector) {
|
||||
this.clientsDataDelegate = clientsDataDelegate;
|
||||
this.parentCollector = collector;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createSession(RepositorySessionCreationDelegate delegate, Context context) {
|
||||
delegate.onSessionCreated(new BookmarksValidationRepositorySession(this, context));
|
||||
}
|
||||
|
||||
public class BookmarksValidationRepositorySession extends RepositorySession {
|
||||
|
||||
private final ConcurrentLinkedQueue<BookmarkRecord> local = new ConcurrentLinkedQueue<>();
|
||||
private final ConcurrentLinkedQueue<BookmarkRecord> remote = new ConcurrentLinkedQueue<>();
|
||||
private long startTime;
|
||||
private final BookmarksRepositorySession wrappedSession;
|
||||
|
||||
public BookmarksValidationRepositorySession(Repository r, Context context) {
|
||||
super(r);
|
||||
startTime = SystemClock.elapsedRealtime();
|
||||
wrappedSession = new BookmarksRepositorySession(r, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLastSyncTimestamp() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void wipe(RepositorySessionWipeDelegate delegate) {
|
||||
Logger.error(LOG_TAG, "wipe() called on bookmark validator");
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized boolean isActive() {
|
||||
return this.wrappedSession.isActive();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void begin(final RepositorySessionBeginDelegate delegate) throws InvalidSessionTransitionException {
|
||||
wrappedSession.begin(new RepositorySessionBeginDelegate() {
|
||||
|
||||
@Override
|
||||
public void onBeginFailed(Exception ex) {
|
||||
delegate.onBeginFailed(ex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBeginSucceeded(RepositorySession session) {
|
||||
delegate.onBeginSucceeded(BookmarksValidationRepositorySession.this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RepositorySessionBeginDelegate deferredBeginDelegate(ExecutorService executor) {
|
||||
final RepositorySessionBeginDelegate deferred = delegate.deferredBeginDelegate(executor);
|
||||
return new RepositorySessionBeginDelegate() {
|
||||
@Override
|
||||
public void onBeginSucceeded(RepositorySession session) {
|
||||
if (wrappedSession != session) {
|
||||
Logger.warn(LOG_TAG, "Got onBeginSucceeded for session " + session + ", not our inner session!");
|
||||
}
|
||||
deferred.onBeginSucceeded(BookmarksValidationRepositorySession.this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBeginFailed(Exception ex) {
|
||||
deferred.onBeginFailed(ex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RepositorySessionBeginDelegate deferredBeginDelegate(ExecutorService executor) {
|
||||
return this;
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finish(final RepositorySessionFinishDelegate delegate) throws InactiveSessionException {
|
||||
wrappedSession.finish(new RepositorySessionFinishDelegate() {
|
||||
@Override
|
||||
public void onFinishFailed(Exception ex) {
|
||||
delegate.onFinishFailed(ex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFinishSucceeded(RepositorySession session, RepositorySessionBundle bundle) {
|
||||
delegate.onFinishSucceeded(BookmarksValidationRepositorySession.this, bundle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RepositorySessionFinishDelegate deferredFinishDelegate(ExecutorService executor) {
|
||||
return this;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void validateForTelemetry() {
|
||||
ArrayList<BookmarkRecord> localRecords = new ArrayList<>(local);
|
||||
ArrayList<BookmarkRecord> remoteRecords = new ArrayList<>(remote);
|
||||
BookmarkValidationResults results = BookmarkValidator.validateClientAgainstServer(localRecords, remoteRecords);
|
||||
ExtendedJSONObject o = new ExtendedJSONObject();
|
||||
o.put("took", SystemClock.elapsedRealtime() - startTime);
|
||||
o.put("checked", Math.max(localRecords.size(), remoteRecords.size()));
|
||||
o.put("problems", results.jsonSummary());
|
||||
Logger.info(LOG_TAG, "Completed validation in " + (SystemClock.elapsedRealtime() - startTime) + " ms");
|
||||
parentCollector.validation = o;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fetchModified(final RepositorySessionFetchRecordsDelegate delegate) {
|
||||
wrappedSession.fetchAll(new RepositorySessionFetchRecordsDelegate() {
|
||||
@Override
|
||||
public void onFetchFailed(Exception ex) {
|
||||
local.clear();
|
||||
remote.clear();
|
||||
delegate.onFetchFailed(ex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFetchedRecord(Record record) {
|
||||
local.add((BookmarkRecord)record);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFetchCompleted(long fetchEnd) {
|
||||
validateForTelemetry();
|
||||
delegate.onFetchCompleted(fetchEnd);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBatchCompleted() {}
|
||||
|
||||
@Override
|
||||
public RepositorySessionFetchRecordsDelegate deferredFetchDelegate(ExecutorService executor) {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fetch(String[] guids, RepositorySessionFetchRecordsDelegate delegate) throws InactiveSessionException {
|
||||
Logger.error(LOG_TAG, "fetch guids[] called on bookmark validator");
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fetchAll(RepositorySessionFetchRecordsDelegate delegate) {
|
||||
this.fetchModified(delegate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void store(Record record) throws NoStoreDelegateException {
|
||||
remote.add((BookmarkRecord)record);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void storeIncomplete() {
|
||||
super.storeIncomplete();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void storeDone() {
|
||||
super.storeDone();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -81,7 +81,8 @@ public class BookmarksServerSyncStage extends VersionedServerSyncStage {
|
|||
BOOKMARKS_SORT,
|
||||
getAllowedMultipleBatches(),
|
||||
getAllowedToUseHighWaterMark(),
|
||||
getRepositoryStateProvider()
|
||||
getRepositoryStateProvider(),
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -75,7 +75,8 @@ public class FormHistoryServerSyncStage extends ServerSyncStage {
|
|||
FORM_HISTORY_SORT,
|
||||
getAllowedMultipleBatches(),
|
||||
getAllowedToUseHighWaterMark(),
|
||||
getRepositoryStateProvider()
|
||||
getRepositoryStateProvider(),
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -41,6 +41,8 @@ public interface GlobalSyncStage {
|
|||
syncFullHistory("history"),
|
||||
syncFormHistory("forms"),
|
||||
|
||||
validateBookmarks,
|
||||
|
||||
uploadMetaGlobal,
|
||||
completed;
|
||||
|
||||
|
|
|
@ -93,7 +93,8 @@ public class HistoryServerSyncStage extends ServerSyncStage {
|
|||
HISTORY_SORT,
|
||||
getAllowedMultipleBatches(),
|
||||
getAllowedToUseHighWaterMark(),
|
||||
getRepositoryStateProvider()
|
||||
getRepositoryStateProvider(),
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -94,7 +94,8 @@ public class RecentHistoryServerSyncStage extends HistoryServerSyncStage {
|
|||
HISTORY_SORT,
|
||||
getAllowedMultipleBatches(),
|
||||
getAllowedToUseHighWaterMark(),
|
||||
getRepositoryStateProvider());
|
||||
getRepositoryStateProvider(),
|
||||
false);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,204 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.gecko.sync.stage;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.os.SystemClock;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import org.mozilla.gecko.AppConstants;
|
||||
import org.mozilla.gecko.db.BrowserContract;
|
||||
import org.mozilla.gecko.sync.MetaGlobalException;
|
||||
import org.mozilla.gecko.sync.NonObjectJSONException;
|
||||
import org.mozilla.gecko.sync.SynchronizerConfiguration;
|
||||
import org.mozilla.gecko.sync.middleware.BufferingMiddlewareRepository;
|
||||
import org.mozilla.gecko.sync.middleware.MiddlewareRepository;
|
||||
import org.mozilla.gecko.sync.middleware.storage.MemoryBufferStorage;
|
||||
import org.mozilla.gecko.sync.repositories.ConfigurableServer15Repository;
|
||||
import org.mozilla.gecko.sync.repositories.RecordFactory;
|
||||
import org.mozilla.gecko.sync.repositories.Repository;
|
||||
import org.mozilla.gecko.sync.repositories.android.BookmarksValidationRepository;
|
||||
import org.mozilla.gecko.sync.repositories.android.BrowserContractHelpers;
|
||||
import org.mozilla.gecko.sync.repositories.domain.BookmarkRecordFactory;
|
||||
import org.mozilla.gecko.sync.repositories.domain.VersionConstants;
|
||||
import org.mozilla.gecko.sync.telemetry.TelemetryCollector;
|
||||
import org.mozilla.gecko.sync.telemetry.TelemetryStageCollector;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* This sync stage runs bookmark validation for including in telemetry. Bookmark validation
|
||||
* is fairly costly, so we don't want to run it frequently, and there are a number of
|
||||
* checks to make sure it's a good idea to run it.
|
||||
*
|
||||
* We only even consider running validation once a day, and during that time we
|
||||
* make a number of checks to determine if we should run it:
|
||||
*
|
||||
* - We only run validation (VALIDATION_PROBABILITY * 100)% of the time (based on Math.random())
|
||||
* - We must be on nightly (may be relaxed eventually).
|
||||
* - You must have have fewer than MAX_BOOKMARKS_COUNT bookmarks in your local database
|
||||
* - There must be enough time left in the sync deadline (at least TIME_REQUIRED_TO_VALIDATE ms).
|
||||
* - The bookmarks collection must have ran, reported telemetry, and not included an error in
|
||||
* the telemetry stage.
|
||||
*/
|
||||
public class ValidateBookmarksSyncStage extends ServerSyncStage {
|
||||
protected static final String LOG_TAG = "ValidateBookmarksStage";
|
||||
|
||||
private static final String BOOKMARKS_SORT = "oldest";
|
||||
private static final long BOOKMARKS_BATCH_LIMIT = 5000;
|
||||
|
||||
// Maximum number of (local) bookmarks we have where we're still
|
||||
// willing to run a validation.
|
||||
private static final long MAX_BOOKMARKS_COUNT = 1000;
|
||||
|
||||
// Probability that, given all other requirements are met, we'll run a validation.
|
||||
private static final double VALIDATION_PROBABILITY = 0.1;
|
||||
|
||||
// If we have less than this amount of time left, we don't bother validating.
|
||||
// Currently 2 minutes, which would be an insane amount of time for a
|
||||
// validation to take, but it's better to be conservative here.
|
||||
private static final long TIME_REQUIRED_TO_VALIDATE = TimeUnit.MINUTES.toMillis(2);
|
||||
|
||||
// We only even attempt to validate this frequently
|
||||
private static final long VALIDATION_INTERVAL = TimeUnit.DAYS.toMillis(1);
|
||||
|
||||
@Override
|
||||
protected String getCollection() {
|
||||
return "bookmarks";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getEngineName() {
|
||||
return "bookmarks";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getStorageVersion() {
|
||||
return VersionConstants.BOOKMARKS_ENGINE_VERSION;
|
||||
}
|
||||
|
||||
/**
|
||||
* We can't validate without all of the records, so a HWM doesn't make sense.
|
||||
*
|
||||
* @return HighWaterMark.Disabled
|
||||
*/
|
||||
@Override
|
||||
protected HighWaterMark getAllowedToUseHighWaterMark() {
|
||||
return HighWaterMark.Disabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Full batching is allowed, because we want all of the records.
|
||||
*
|
||||
* @return MultipleBatches.Enabled
|
||||
*/
|
||||
@Override
|
||||
protected MultipleBatches getAllowedMultipleBatches() {
|
||||
return MultipleBatches.Enabled;
|
||||
}
|
||||
@Override
|
||||
protected Repository getRemoteRepository() throws URISyntaxException {
|
||||
return new ConfigurableServer15Repository(
|
||||
getCollection(),
|
||||
session.getSyncDeadline(),
|
||||
session.config.storageURL(),
|
||||
session.getAuthHeaderProvider(),
|
||||
session.config.infoCollections,
|
||||
session.config.infoConfiguration,
|
||||
BOOKMARKS_BATCH_LIMIT,
|
||||
BOOKMARKS_SORT,
|
||||
getAllowedMultipleBatches(),
|
||||
getAllowedToUseHighWaterMark(),
|
||||
getRepositoryStateProvider(),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Repository getLocalRepository() {
|
||||
TelemetryStageCollector bookmarkCollector =
|
||||
this.telemetryStageCollector.getSyncCollector().collectorFor("bookmarks");
|
||||
return new BookmarksValidationRepository(session.getClientsDelegate(), bookmarkCollector);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RecordFactory getRecordFactory() {
|
||||
return new BookmarkRecordFactory();
|
||||
}
|
||||
|
||||
private long getLocalBookmarkRecordCount() {
|
||||
final Context context = session.getContext();
|
||||
final Cursor cursor = context.getContentResolver().query(
|
||||
BrowserContractHelpers.BOOKMARKS_CONTENT_URI,
|
||||
new String[] {BrowserContract.Bookmarks._ID},
|
||||
null, null, null
|
||||
);
|
||||
if (cursor == null) {
|
||||
return -1;
|
||||
}
|
||||
try {
|
||||
return cursor.getCount();
|
||||
} finally {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
// This implements the logic described in the header comment.
|
||||
private boolean shouldValidate() {
|
||||
if (!AppConstants.NIGHTLY_BUILD) {
|
||||
return false;
|
||||
}
|
||||
final long consideredValidationLast = session.config.getLastValidationCheckTimestamp();
|
||||
final long now = System.currentTimeMillis();
|
||||
|
||||
if (now - consideredValidationLast < VALIDATION_INTERVAL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
session.config.persistLastValidationCheckTimestamp(now);
|
||||
|
||||
if (Math.random() > VALIDATION_PROBABILITY) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Note that syncDeadline is relative to the elapsedRealtime clock, not `now`.
|
||||
final long timeToSyncDeadline = session.getSyncDeadline() - SystemClock.elapsedRealtime();
|
||||
if (timeToSyncDeadline < TIME_REQUIRED_TO_VALIDATE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// See if we'll have somewhere to store the validation results
|
||||
TelemetryCollector syncCollector = telemetryStageCollector.getSyncCollector();
|
||||
if (!syncCollector.hasCollectorFor("bookmarks")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// And make sure that bookmarks sync didn't hit an error.
|
||||
TelemetryStageCollector stageCollector = syncCollector.collectorFor("bookmarks");
|
||||
if (stageCollector.error != null) {
|
||||
return false;
|
||||
}
|
||||
long count = getLocalBookmarkRecordCount();
|
||||
if (count < 0 || count > MAX_BOOKMARKS_COUNT) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isEnabled() throws MetaGlobalException {
|
||||
if (session == null || session.getContext() == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return super.isEnabled() && shouldValidate();
|
||||
}
|
||||
|
||||
}
|
|
@ -64,6 +64,10 @@ public class TelemetryCollector {
|
|||
return collector;
|
||||
}
|
||||
|
||||
public boolean hasCollectorFor(@NonNull String stageName) {
|
||||
return stageCollectors.containsKey(stageName);
|
||||
}
|
||||
|
||||
public void setRestarted() {
|
||||
this.didRestart = true;
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ public class TelemetryStageCollector {
|
|||
public volatile int outboundFailed = 0;
|
||||
public volatile int reconciled = 0;
|
||||
public volatile ExtendedJSONObject error = null;
|
||||
public volatile ExtendedJSONObject validation = null;
|
||||
|
||||
public TelemetryStageCollector(TelemetryCollector syncCollector) {
|
||||
this.syncCollector = syncCollector;
|
||||
|
|
|
@ -0,0 +1,173 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.gecko.sync.validation;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Stores data that describes the set of problems found during bookmark validation.
|
||||
* It stores enough info so that meaningful action to repair or visualize the problems could
|
||||
* be taken in the future, although at the moment the primary use is to generate data for
|
||||
* telemetry.
|
||||
*
|
||||
* Currently it only holds a subset of what is stored by desktop's implementation.
|
||||
*/
|
||||
public class BookmarkValidationResults extends ValidationResults {
|
||||
/**
|
||||
* Simple class used to store a pair of guids that refer to a parent/child relationship.
|
||||
*/
|
||||
public static class ParentChildPair {
|
||||
public String parent;
|
||||
public String child;
|
||||
|
||||
public ParentChildPair(String parentID, String childID) {
|
||||
this.parent = parentID;
|
||||
this.child = childID;
|
||||
}
|
||||
|
||||
// There are a few cases where it's difficult for the validator to guarantee uniqueness of
|
||||
// it's complaints, and so it helps to be able to store these in a Map/Set by value.
|
||||
// The implementations were automatically generated.
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ParentChildPair that = (ParentChildPair) o;
|
||||
|
||||
if (!parent.equals(that.parent)) {
|
||||
return false;
|
||||
}
|
||||
return child.equals(that.child);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = parent.hashCode();
|
||||
result = 31 * result + child.hashCode();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* True if we saw the root record (the record with a guid of "places") on the server.
|
||||
*/
|
||||
public boolean rootOnServer = false;
|
||||
|
||||
/**
|
||||
* List of records where the parent refers to a child that does not exist.
|
||||
*/
|
||||
public List<ParentChildPair> missingChildren = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* List of records where the parent refers to a child that was deleted.
|
||||
*/
|
||||
public List<ParentChildPair> deletedChildren = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* List of records where the parent was deleted but the child was not.
|
||||
*/
|
||||
public List<ParentChildPair> deletedParents = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* List of records with either no parent, or where the parent could not be found.
|
||||
*/
|
||||
public List<ParentChildPair> orphans = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* List of records who have the same child listed multiple times in their children array.
|
||||
*/
|
||||
public List<ParentChildPair> duplicateChildren = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* List of records who refer to parents that are not folders.
|
||||
*/
|
||||
public List<ParentChildPair> parentNotFolder = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* List of server-side records where the child's parentid and parent's children array disagree
|
||||
* with each-other.
|
||||
*/
|
||||
public Set<ParentChildPair> parentChildMismatches = new HashSet<>();
|
||||
|
||||
/**
|
||||
* Map of child guid to list of parent guids for records that show up in multiple parents.
|
||||
*/
|
||||
public Map<String, List<String>> multipleParents = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Set of ids of records present on the server but missing from the client.
|
||||
*/
|
||||
public Set<String> clientMissing = new HashSet<>();
|
||||
|
||||
/**
|
||||
* Set of ids of records present on the client but missing from the server.
|
||||
*/
|
||||
public Set<String> serverMissing = new HashSet<>();
|
||||
|
||||
/**
|
||||
* List of ids of records present on the client, with tombstones on the server.
|
||||
*/
|
||||
public Set<String> serverDeleted = new HashSet<>();
|
||||
|
||||
/**
|
||||
* List of ids of items where the child guids differ between client and server.
|
||||
* Represents sdiff:childGUIDs, and part of structuralDifferences in the summary.
|
||||
*/
|
||||
public Set<String> structuralDifferenceChildGUIDs = new HashSet<>();
|
||||
|
||||
/**
|
||||
* List of ids of items where parentIDs differ between client and server.
|
||||
* Represents sdiff:parentid, and part of structuralDifferences in the summary.
|
||||
*/
|
||||
public Set<String> structuralDifferenceParentIDs = new HashSet<>();
|
||||
|
||||
/**
|
||||
* List of ids where the client and server disagree about something not covered by a structural
|
||||
* difference.
|
||||
*/
|
||||
public Set<String> differences = new HashSet<>();
|
||||
|
||||
|
||||
private static void addProp(Map<String, Integer> m, String propName, int count) {
|
||||
if (count != 0) {
|
||||
m.put(propName, count);
|
||||
}
|
||||
}
|
||||
|
||||
public Map<String, Integer> summarizeResults() {
|
||||
Map<String, Integer> m = new HashMap<>();
|
||||
if (rootOnServer) {
|
||||
addProp(m, "rootOnServer", 1);
|
||||
}
|
||||
addProp(m, "parentChildMismatches", parentChildMismatches.size());
|
||||
addProp(m, "missingChildren", missingChildren.size());
|
||||
addProp(m, "deletedChildren", deletedChildren.size());
|
||||
addProp(m, "deletedParents", deletedParents.size());
|
||||
addProp(m, "multipleParents", multipleParents.size());
|
||||
addProp(m, "orphans", orphans.size());
|
||||
addProp(m, "duplicateChildren", duplicateChildren.size());
|
||||
addProp(m, "parentNotFolder", parentNotFolder.size());
|
||||
addProp(m, "clientMissing", clientMissing.size());
|
||||
addProp(m, "serverMissing", serverMissing.size());
|
||||
addProp(m, "serverDeleted", serverDeleted.size());
|
||||
addProp(m, "sdiff:childGUIDs", structuralDifferenceChildGUIDs.size());
|
||||
addProp(m, "sdiff:parentid", structuralDifferenceParentIDs.size());
|
||||
addProp(m, "structuralDifferences", structuralDifferenceParentIDs.size() + structuralDifferenceChildGUIDs.size());
|
||||
addProp(m, "differences", differences.size());
|
||||
return m;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,287 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.gecko.sync.validation;
|
||||
|
||||
import org.json.simple.JSONArray;
|
||||
import org.mozilla.gecko.sync.repositories.domain.BookmarkRecord;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
public class BookmarkValidator {
|
||||
private List<BookmarkRecord> localRecords;
|
||||
private List<BookmarkRecord> remoteRecords;
|
||||
|
||||
private Map<String, BookmarkRecord> remoteGUIDToRecord = new HashMap<>();
|
||||
|
||||
|
||||
/**
|
||||
* Construct a bookmark validator from local and remote records
|
||||
* */
|
||||
public BookmarkValidator(List<BookmarkRecord> localRecords, List<BookmarkRecord> remoteRecords) {
|
||||
this.localRecords = localRecords;
|
||||
this.remoteRecords = remoteRecords;
|
||||
for (BookmarkRecord r : remoteRecords) {
|
||||
remoteGUIDToRecord.put(r.guid, r);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkServerFolder(BookmarkRecord r, BookmarkValidationResults results) {
|
||||
if (r.children == null) {
|
||||
return;
|
||||
}
|
||||
HashSet<String> seenChildGUIDs = new HashSet<>();
|
||||
|
||||
for (int i = 0; i < r.children.size(); ++i) {
|
||||
String childGUID = (String) r.children.get(i);
|
||||
if (seenChildGUIDs.contains(childGUID)) {
|
||||
results.duplicateChildren.add(
|
||||
new BookmarkValidationResults.ParentChildPair(r.guid, childGUID)
|
||||
);
|
||||
continue;
|
||||
}
|
||||
seenChildGUIDs.add(childGUID);
|
||||
|
||||
BookmarkRecord child = remoteGUIDToRecord.get(childGUID);
|
||||
|
||||
if (child == null) {
|
||||
results.missingChildren.add(
|
||||
new BookmarkValidationResults.ParentChildPair(r.guid, childGUID)
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
List<String> parentsContainingChild = results.multipleParents.get(childGUID);
|
||||
if (parentsContainingChild == null) {
|
||||
// Common case, we've never seen a parent with this child before, so we add a
|
||||
// record indicating we've seen one, and what the id was. If there aren't any
|
||||
// other parents of this record, we clean this up before returning the results
|
||||
// to the validator's caller (in cleanupValidationResults).
|
||||
parentsContainingChild = new ArrayList<>();
|
||||
parentsContainingChild.add(r.guid);
|
||||
results.multipleParents.put(childGUID, parentsContainingChild);
|
||||
} else {
|
||||
parentsContainingChild.add(r.guid);
|
||||
}
|
||||
|
||||
if (child.deleted) {
|
||||
results.deletedChildren.add(
|
||||
new BookmarkValidationResults.ParentChildPair(r.guid, childGUID)
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!child.parentID.equals(r.guid)) {
|
||||
results.parentChildMismatches.add(
|
||||
new BookmarkValidationResults.ParentChildPair(r.guid, childGUID)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for errors with the parent that aren't covered (or that might only be partially
|
||||
// covered) by the checks in `checkServerFolder`.
|
||||
private void checkServerParent(BookmarkRecord record, BookmarkValidationResults results) {
|
||||
String parentID = record.parentID;
|
||||
if (parentID.equals("places")) {
|
||||
// Parent is the places root, so we don't care.
|
||||
return;
|
||||
}
|
||||
BookmarkRecord listedParent = remoteGUIDToRecord.get(parentID);
|
||||
if (listedParent == null) {
|
||||
results.orphans.add(
|
||||
new BookmarkValidationResults.ParentChildPair(parentID, record.guid)
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (listedParent.deleted) {
|
||||
results.deletedParents.add(
|
||||
new BookmarkValidationResults.ParentChildPair(parentID, record.guid)
|
||||
);
|
||||
} else if (!listedParent.isFolder()) {
|
||||
results.parentNotFolder.add(
|
||||
new BookmarkValidationResults.ParentChildPair(parentID, record.guid)
|
||||
);
|
||||
} else {
|
||||
boolean foundChild = false;
|
||||
for (int i = 0; i < listedParent.children.size() && !foundChild; ++i) {
|
||||
String childGUID = (String) listedParent.children.get(i);
|
||||
foundChild = childGUID.equals(record.guid);
|
||||
}
|
||||
if (!foundChild) {
|
||||
results.parentChildMismatches.add(
|
||||
new BookmarkValidationResults.ParentChildPair(parentID, record.guid)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void inspectServerRecords(BookmarkValidationResults results) {
|
||||
for (BookmarkRecord record : remoteRecords) {
|
||||
if (record.deleted) {
|
||||
continue;
|
||||
}
|
||||
if (record.guid.equals("places")) {
|
||||
results.rootOnServer = true;
|
||||
continue;
|
||||
}
|
||||
if (record.isFolder()) {
|
||||
checkServerFolder(record, results);
|
||||
}
|
||||
checkServerParent(record, results);
|
||||
}
|
||||
}
|
||||
|
||||
private static class ClientServerPair {
|
||||
BookmarkRecord client;
|
||||
BookmarkRecord server;
|
||||
ClientServerPair(BookmarkRecord client, BookmarkRecord server) {
|
||||
this.client = client;
|
||||
this.server = server;
|
||||
}
|
||||
}
|
||||
|
||||
// Should return false if one or both are missing (and so further comparisons are meaningless.
|
||||
// If they're inconsistent, the error should be recorded in `results`.
|
||||
private boolean checkMissing(ClientServerPair p, BookmarkValidationResults results) {
|
||||
|
||||
boolean clientExists = p.client != null && !p.client.deleted;
|
||||
boolean serverExists = p.server != null && !p.server.deleted;
|
||||
|
||||
boolean serverTombstone = p.server != null && p.server.deleted;
|
||||
|
||||
if (clientExists && !serverExists) {
|
||||
if (serverTombstone) {
|
||||
results.serverDeleted.add(p.client.guid);
|
||||
} else {
|
||||
results.serverMissing.add(p.client.guid);
|
||||
}
|
||||
} else if (serverExists && !clientExists) {
|
||||
results.clientMissing.add(p.server.guid);
|
||||
// Should we distinguish between deleted and missing here? Desktop doesn't,
|
||||
// so to keep consistent metrics we don't either, but maybe both should?
|
||||
}
|
||||
|
||||
return clientExists && serverExists;
|
||||
}
|
||||
|
||||
// Returns true if it found any structural differences, and records them in `results.
|
||||
private boolean checkStructuralDifferences(ClientServerPair p, BookmarkValidationResults results) {
|
||||
boolean sawDiff = false;
|
||||
if (!p.client.parentID.equals(p.server.parentID)) {
|
||||
results.structuralDifferenceParentIDs.add(p.client.guid);
|
||||
// Just record we saw it, since we still want to check the childGUIDs
|
||||
sawDiff = true;
|
||||
}
|
||||
|
||||
if (p.client.children != null && p.server.children != null) {
|
||||
if (p.client.children.size() == p.server.children.size()) {
|
||||
for (int i = 0; i < p.client.children.size(); ++i) {
|
||||
String clientChildGUID = (String) p.client.children.get(i);
|
||||
String serverChildGUID = (String) p.server.children.get(i);
|
||||
if (!clientChildGUID.equals(serverChildGUID)) {
|
||||
results.structuralDifferenceChildGUIDs.add(p.client.guid);
|
||||
sawDiff = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// They have different sizes, so the contents must be different.
|
||||
sawDiff = true;
|
||||
results.structuralDifferenceChildGUIDs.add(p.client.guid);
|
||||
}
|
||||
}
|
||||
|
||||
return sawDiff;
|
||||
}
|
||||
|
||||
// Avoid false positive entries in "differences" caused by our use of congruentWith
|
||||
private BookmarkRecord normalizeRecord(BookmarkRecord r) {
|
||||
if (r.collection == null) {
|
||||
r.collection = "bookmarks";
|
||||
}
|
||||
if (r.tags == null) {
|
||||
// Desktop considers these the same and android doesn't. It's unclear if this is a bug.
|
||||
r.tags = new JSONArray();
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
private void compareClientWithServer(BookmarkValidationResults results) {
|
||||
Map<String, ClientServerPair> pairsById = new HashMap<>();
|
||||
for (BookmarkRecord r : remoteRecords) {
|
||||
pairsById.put(r.guid, new ClientServerPair(null, normalizeRecord(r)));
|
||||
}
|
||||
for (BookmarkRecord r : localRecords) {
|
||||
if (r.deleted) {
|
||||
continue;
|
||||
}
|
||||
ClientServerPair p = pairsById.get(r.guid);
|
||||
if (p != null) {
|
||||
p.client = normalizeRecord(r);
|
||||
} else {
|
||||
pairsById.put(r.guid, new ClientServerPair(normalizeRecord(r), null));
|
||||
}
|
||||
}
|
||||
for (Entry<String, ClientServerPair> e : pairsById.entrySet()) {
|
||||
ClientServerPair p = e.getValue();
|
||||
if (!checkMissing(p, results)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip checking for differences if we see a structural difference, since congruentWith
|
||||
// checks structural differences as well.
|
||||
boolean sawStructuralDifference = checkStructuralDifferences(p, results);
|
||||
|
||||
if (!sawStructuralDifference &&
|
||||
(!p.client.congruentWith(p.server) || !p.server.congruentWith(p.client))) {
|
||||
results.differences.add(p.client.guid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove anything in the validation results that shouldn't be reported as an error,
|
||||
// e.g. "multipleParents" with only one item in the list
|
||||
private void cleanupValidationResults(BookmarkValidationResults results) {
|
||||
Map<String, List<String>> filteredMultipleParents = new HashMap<>();
|
||||
for (Entry<String, List<String>> entry : results.multipleParents.entrySet()) {
|
||||
if (entry.getValue().size() >= 2) {
|
||||
filteredMultipleParents.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
results.multipleParents = filteredMultipleParents;
|
||||
}
|
||||
|
||||
private BookmarkValidationResults validate() {
|
||||
BookmarkValidationResults results = new BookmarkValidationResults();
|
||||
inspectServerRecords(results);
|
||||
compareClientWithServer(results);
|
||||
cleanupValidationResults(results);
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiate a validator from client and server records, and perform validation.
|
||||
*/
|
||||
public static BookmarkValidationResults validateClientAgainstServer(List<BookmarkRecord> client, List<BookmarkRecord> server) {
|
||||
BookmarkValidator v = new BookmarkValidator(client, server);
|
||||
return v.validate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the server-side portion of the validation only.
|
||||
*/
|
||||
public static BookmarkValidationResults validateServer(List<BookmarkRecord> server) {
|
||||
BookmarkValidator v = new BookmarkValidator(new ArrayList<BookmarkRecord>(), server);
|
||||
BookmarkValidationResults results = new BookmarkValidationResults();
|
||||
v.inspectServerRecords(results);
|
||||
v.compareClientWithServer(results);
|
||||
return v.validate();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.gecko.sync.validation;
|
||||
|
||||
public interface CollectionValidator {
|
||||
public ValidationResults validate();
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.gecko.sync.validation;
|
||||
|
||||
import org.json.simple.JSONArray;
|
||||
import org.json.simple.JSONObject;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public abstract class ValidationResults {
|
||||
/**
|
||||
* Get the problems found by the validator. Must not contain numbers less than or equal to zero.
|
||||
* Must use the same names for problems as other platforms!
|
||||
*/
|
||||
public abstract Map<String, Integer> summarizeResults();
|
||||
|
||||
/**
|
||||
* Get the summary as JSON suitable for including in telemetry
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public JSONArray jsonSummary() {
|
||||
Map<String, Integer> problems = summarizeResults();
|
||||
JSONArray result = new JSONArray();
|
||||
for (Map.Entry<String, Integer> problem : problems.entrySet()) {
|
||||
JSONObject o = new JSONObject();
|
||||
o.put("name", problem.getKey());
|
||||
o.put("count", problem.getValue());
|
||||
result.add(o);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public boolean anyProblemsExist() {
|
||||
return summarizeResults().size() > 0;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,165 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
package org.mozilla.gecko.sync.test;
|
||||
|
||||
import org.json.simple.JSONArray;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mozilla.gecko.background.testhelpers.TestRunner;
|
||||
import org.mozilla.gecko.sync.repositories.domain.BookmarkRecord;
|
||||
import org.mozilla.gecko.sync.validation.BookmarkValidationResults;
|
||||
import org.mozilla.gecko.sync.validation.BookmarkValidator;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
@RunWith(TestRunner.class)
|
||||
public class TestBookmarkValidator {
|
||||
|
||||
private List<BookmarkRecord> getDummyRecords() {
|
||||
List<BookmarkRecord> l = new ArrayList<>();
|
||||
{
|
||||
BookmarkRecord r = new BookmarkRecord();
|
||||
r.guid = "menu";
|
||||
r.parentID = "places";
|
||||
r.type = "folder";
|
||||
r.title = "foo";
|
||||
r.children = childrenFromGUIDs("aaaaaaaaaaaa", "bbbbbbbbbbbb", "cccccccccccc");
|
||||
l.add(r);
|
||||
}
|
||||
{
|
||||
BookmarkRecord r = new BookmarkRecord();
|
||||
r.guid = "aaaaaaaaaaaa";
|
||||
r.parentID = "menu";
|
||||
r.type = "folder";
|
||||
r.title = "stuff";
|
||||
r.children = new JSONArray();
|
||||
l.add(r);
|
||||
}
|
||||
{
|
||||
BookmarkRecord r = new BookmarkRecord();
|
||||
r.guid = "bbbbbbbbbbbb";
|
||||
r.parentID = "menu";
|
||||
r.type = "bookmark";
|
||||
r.title = "bar";
|
||||
r.bookmarkURI = "http://baz.com";
|
||||
l.add(r);
|
||||
}
|
||||
{
|
||||
BookmarkRecord r = new BookmarkRecord();
|
||||
r.guid = "cccccccccccc";
|
||||
r.parentID = "menu";
|
||||
r.type = "query";
|
||||
r.title = "";
|
||||
r.bookmarkURI = "place:type=6&sort=14&maxResults=10";
|
||||
l.add(r);
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMatching() {
|
||||
List<BookmarkRecord> client = getDummyRecords();
|
||||
List<BookmarkRecord> server = getDummyRecords();
|
||||
BookmarkValidationResults v = BookmarkValidator.validateClientAgainstServer(client, server);
|
||||
assertFalse(v.anyProblemsExist());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClientMissing() {
|
||||
List<BookmarkRecord> client = getDummyRecords();
|
||||
List<BookmarkRecord> server = getDummyRecords();
|
||||
BookmarkRecord removed = client.remove(client.size() - 1);
|
||||
BookmarkValidationResults v = BookmarkValidator.validateClientAgainstServer(client, server);
|
||||
assertTrue(v.anyProblemsExist());
|
||||
assertEquals(v.clientMissing.size(), 1);
|
||||
assertTrue(v.clientMissing.contains(removed.guid));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testServerMissing() {
|
||||
// Also tests some other server validation
|
||||
List<BookmarkRecord> client = getDummyRecords();
|
||||
List<BookmarkRecord> server = getDummyRecords();
|
||||
BookmarkRecord removed = server.remove(server.size() - 1);
|
||||
BookmarkValidationResults v = BookmarkValidator.validateClientAgainstServer(client, server);
|
||||
assertTrue(v.anyProblemsExist());
|
||||
assertEquals(v.serverMissing.size(), 1);
|
||||
assertTrue(v.serverMissing.contains(removed.guid));
|
||||
assertEquals(v.missingChildren.size(), 1);
|
||||
assertTrue(v.missingChildren.contains(
|
||||
new BookmarkValidationResults.ParentChildPair("menu", removed.guid)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOrphans() {
|
||||
List<BookmarkRecord> server = getDummyRecords();
|
||||
BookmarkRecord last = server.get(server.size() - 1);
|
||||
last.parentID = "asdfasdfasdf";
|
||||
BookmarkValidationResults v = BookmarkValidator.validateServer(server);
|
||||
assertTrue(v.anyProblemsExist());
|
||||
|
||||
assertEquals(v.orphans.size(), 1);
|
||||
assertTrue(v.orphans.contains(
|
||||
new BookmarkValidationResults.ParentChildPair("asdfasdfasdf", last.guid)));
|
||||
|
||||
assertEquals(v.parentChildMismatches.size(), 1);
|
||||
assertTrue(v.parentChildMismatches.contains(
|
||||
new BookmarkValidationResults.ParentChildPair("menu", last.guid)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testServerDeleted() {
|
||||
List<BookmarkRecord> client = getDummyRecords();
|
||||
List<BookmarkRecord> server = getDummyRecords();
|
||||
BookmarkRecord removed = server.remove(server.size() - 1);
|
||||
server.add(createTombstone(removed.guid));
|
||||
BookmarkValidationResults v = BookmarkValidator.validateClientAgainstServer(client, server);
|
||||
assertTrue(v.anyProblemsExist());
|
||||
|
||||
assertEquals(v.serverDeleted.size(), 1);
|
||||
assertTrue(v.serverDeleted.contains(removed.guid));
|
||||
assertEquals(v.serverMissing.size(), 0);
|
||||
|
||||
assertEquals(v.deletedChildren.size(), 1);
|
||||
assertTrue(v.deletedChildren.contains(
|
||||
new BookmarkValidationResults.ParentChildPair(removed.parentID, removed.guid)));
|
||||
|
||||
assertEquals(v.missingChildren.size(), 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultipleParents() {
|
||||
List<BookmarkRecord> server = getDummyRecords();
|
||||
BookmarkRecord otherFolder = server.get(1);
|
||||
assertEquals(otherFolder.type, "folder");
|
||||
otherFolder.children = childrenFromGUIDs("bbbbbbbbbbbb");
|
||||
|
||||
BookmarkValidationResults v = BookmarkValidator.validateServer(server);
|
||||
assertTrue(v.anyProblemsExist());
|
||||
|
||||
assertEquals(v.parentChildMismatches.size(), 1);
|
||||
assertTrue(v.parentChildMismatches.contains(
|
||||
new BookmarkValidationResults.ParentChildPair(otherFolder.guid, "bbbbbbbbbbbb")));
|
||||
|
||||
assertEquals(v.multipleParents.size(), 1);
|
||||
assertTrue(v.multipleParents.containsKey("bbbbbbbbbbbb"));
|
||||
List<String> parentGUIDs = v.multipleParents.get("bbbbbbbbbbbb");
|
||||
assertEquals(parentGUIDs.size(), 2);
|
||||
assertTrue(parentGUIDs.contains("menu"));
|
||||
assertTrue(parentGUIDs.contains("aaaaaaaaaaaa"));
|
||||
}
|
||||
|
||||
private BookmarkRecord createTombstone(String guid) {
|
||||
return new BookmarkRecord(guid, BookmarkRecord.COLLECTION_NAME, 0, true);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private JSONArray childrenFromGUIDs(String... guids) {
|
||||
JSONArray children = new JSONArray();
|
||||
children.addAll(Arrays.asList(guids));
|
||||
return children;
|
||||
}
|
||||
}
|