зеркало из https://github.com/mozilla/gecko-dev.git
Merge mozilla-central to mozilla-inbound
This commit is contained in:
Коммит
750b23e14d
|
@ -133,8 +133,9 @@ this.PermissionPromptPrototype = {
|
|||
* PopupNotification when it is shown. See the documentation
|
||||
* for PopupNotification for more details.
|
||||
*
|
||||
* Note that prompt() will automatically set displayURI to
|
||||
* be the URI of the requesting pricipal.
|
||||
* Note that prompt() will automatically set displayURI to
|
||||
* be the URI of the requesting pricipal, unless the displayURI is exactly
|
||||
* set to false.
|
||||
*/
|
||||
get popupOptions() {
|
||||
return {};
|
||||
|
@ -329,7 +330,10 @@ this.PermissionPromptPrototype = {
|
|||
let secondaryActions = popupNotificationActions.splice(1);
|
||||
|
||||
let options = this.popupOptions;
|
||||
options.displayURI = this.principal.URI;
|
||||
|
||||
if (!options.hasOwnProperty('displayURI') || options.displayURI) {
|
||||
options.displayURI = this.principal.URI;
|
||||
}
|
||||
|
||||
this.onBeforeShow();
|
||||
chromeWin.PopupNotifications.show(this.browser,
|
||||
|
|
|
@ -157,6 +157,55 @@ add_task(function* test_permission_prompt_for_request() {
|
|||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Tests that if the PermissionPrompt sets displayURI to false in popupOptions,
|
||||
* then there is no URI shown on the popupnotification.
|
||||
*/
|
||||
add_task(function* test_permission_prompt_for_popupOptions() {
|
||||
yield BrowserTestUtils.withNewTab({
|
||||
gBrowser,
|
||||
url: "http://example.com/",
|
||||
}, function*(browser) {
|
||||
const kTestNotificationID = "test-notification";
|
||||
const kTestMessage = "Test message";
|
||||
let mainAction = {
|
||||
label: "Main",
|
||||
accessKey: "M",
|
||||
};
|
||||
let secondaryAction = {
|
||||
label: "Secondary",
|
||||
accessKey: "S",
|
||||
};
|
||||
|
||||
let mockRequest = makeMockPermissionRequest(browser);
|
||||
let TestPrompt = {
|
||||
__proto__: PermissionUI.PermissionPromptForRequestPrototype,
|
||||
request: mockRequest,
|
||||
notificationID: kTestNotificationID,
|
||||
message: kTestMessage,
|
||||
promptActions: [mainAction, secondaryAction],
|
||||
popupOptions: {
|
||||
displayURI: false,
|
||||
},
|
||||
};
|
||||
|
||||
let shownPromise =
|
||||
BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popupshown");
|
||||
TestPrompt.prompt();
|
||||
yield shownPromise;
|
||||
let notification =
|
||||
PopupNotifications.getNotification(kTestNotificationID, browser);
|
||||
|
||||
Assert.ok(!notification.options.displayURI,
|
||||
"Should not show the URI of the requesting page");
|
||||
|
||||
let removePromise =
|
||||
BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popuphidden");
|
||||
notification.remove();
|
||||
yield removePromise;
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Tests that if the PermissionPrompt has the permissionKey
|
||||
* set that permissions can be set properly by the user. Also
|
||||
|
|
|
@ -803,11 +803,6 @@ def js_option(*args, **kwargs):
|
|||
add_old_configure_arg(js_option)
|
||||
|
||||
|
||||
include('pkg.configure')
|
||||
# Make this assignment here rather than in pkg.configure to avoid
|
||||
# requiring this file in unit tests.
|
||||
add_old_configure_assignment('PKG_CONFIG', pkg_config)
|
||||
|
||||
# Bug 1278542: This function is a workaround to resolve
|
||||
# |android_ndk_include|'s dependency on 'gonkdir.' The
|
||||
# actual implementation is located in b2g/moz.configure.
|
||||
|
|
|
@ -4,7 +4,12 @@
|
|||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
pkg_config = check_prog('PKG_CONFIG', ('pkg-config',), allow_missing=True)
|
||||
@depends('--enable-compile-environment')
|
||||
def pkg_config(compile_env):
|
||||
if compile_env:
|
||||
return ('pkg-config',)
|
||||
|
||||
pkg_config = check_prog('PKG_CONFIG', pkg_config, allow_missing=True)
|
||||
|
||||
@depends_if(pkg_config)
|
||||
@checking('for pkg-config version')
|
||||
|
@ -31,7 +36,12 @@ def pkg_check_modules(var, package_desc, when=always,
|
|||
package_desc = ' '.join(package_desc)
|
||||
package_desc = dependable(package_desc)
|
||||
|
||||
@depends_when(pkg_config, pkg_config_version, when=when)
|
||||
@depends(when, '--enable-compile-environment')
|
||||
def when_and_compile_environment(when, compile_environment):
|
||||
return when and compile_environment
|
||||
|
||||
@depends_when(pkg_config, pkg_config_version,
|
||||
when=when_and_compile_environment)
|
||||
def check_pkg_config(pkg_config, version):
|
||||
min_version = '0.9.0'
|
||||
if pkg_config is None:
|
||||
|
@ -42,7 +52,7 @@ def pkg_check_modules(var, package_desc, when=always,
|
|||
die("*** Your version of pkg-config is too old. You need version %s or newer.",
|
||||
min_version)
|
||||
|
||||
@depends_when(pkg_config, package_desc, when=when)
|
||||
@depends_when(pkg_config, package_desc, when=when_and_compile_environment)
|
||||
@imports('subprocess')
|
||||
@imports('sys')
|
||||
@imports(_from='mozbuild.configure.util', _import='LineIO')
|
||||
|
|
|
@ -144,7 +144,7 @@ var DevToolsUtils = require("devtools/shared/DevToolsUtils");
|
|||
var promise = require("devtools/shared/deprecated-sync-thenables");
|
||||
var Editor = require("devtools/client/sourceeditor/editor");
|
||||
var DebuggerEditor = require("devtools/client/sourceeditor/debugger");
|
||||
var Tooltip = require("devtools/client/shared/widgets/Tooltip");
|
||||
var Tooltip = require("devtools/client/shared/widgets/tooltip/Tooltip");
|
||||
var FastListWidget = require("devtools/client/shared/widgets/FastListWidget");
|
||||
var {LocalizationHelper, ELLIPSIS} = require("devtools/shared/l10n");
|
||||
var {PrefsHelper} = require("devtools/client/shared/prefs");
|
||||
|
|
|
@ -37,7 +37,7 @@ const {HTMLEditor} = require("devtools/client/inspector/markup/html-editor");
|
|||
const promise = require("promise");
|
||||
const defer = require("devtools/shared/defer");
|
||||
const Services = require("Services");
|
||||
const {HTMLTooltip} = require("devtools/client/shared/widgets/HTMLTooltip");
|
||||
const {HTMLTooltip} = require("devtools/client/shared/widgets/tooltip/HTMLTooltip");
|
||||
const {setImageTooltip, setBrokenImageTooltip} =
|
||||
require("devtools/client/shared/widgets/tooltip/ImageTooltipHelper");
|
||||
const {setEventTooltip} = require("devtools/client/shared/widgets/tooltip/EventTooltipHelper");
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
// - etc.
|
||||
|
||||
const {getColor} = require("devtools/client/shared/theme");
|
||||
const {HTMLTooltip} = require("devtools/client/shared/widgets/HTMLTooltip");
|
||||
const {HTMLTooltip} = require("devtools/client/shared/widgets/tooltip/HTMLTooltip");
|
||||
const {
|
||||
getImageDimensions,
|
||||
setImageTooltip,
|
||||
|
|
|
@ -22,7 +22,7 @@ const {SideMenuWidget} = require("resource://devtools/client/shared/widgets/Side
|
|||
const {VariablesView} = require("resource://devtools/client/shared/widgets/VariablesView.jsm");
|
||||
const {VariablesViewController} = require("resource://devtools/client/shared/widgets/VariablesViewController.jsm");
|
||||
const {ToolSidebar} = require("devtools/client/framework/sidebar");
|
||||
const {HTMLTooltip} = require("devtools/client/shared/widgets/HTMLTooltip");
|
||||
const {HTMLTooltip} = require("devtools/client/shared/widgets/tooltip/HTMLTooltip");
|
||||
const {setImageTooltip, getImageDimensions} =
|
||||
require("devtools/client/shared/widgets/tooltip/ImageTooltipHelper");
|
||||
const { testing: isTesting } = require("devtools/shared/flags");
|
||||
|
|
|
@ -11,7 +11,7 @@ const {SideMenuWidget} = require("resource://devtools/client/shared/widgets/Side
|
|||
const promise = require("promise");
|
||||
const Services = require("Services");
|
||||
const EventEmitter = require("devtools/shared/event-emitter");
|
||||
const Tooltip = require("devtools/client/shared/widgets/Tooltip");
|
||||
const Tooltip = require("devtools/client/shared/widgets/tooltip/Tooltip");
|
||||
const Editor = require("devtools/client/sourceeditor/editor");
|
||||
const {LocalizationHelper} = require("devtools/shared/l10n");
|
||||
const {Heritage, WidgetMethods, setNamedTimeout} =
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
const HTML_NS = "http://www.w3.org/1999/xhtml";
|
||||
const Services = require("Services");
|
||||
const {gDevTools} = require("devtools/client/framework/devtools");
|
||||
const {HTMLTooltip} = require("devtools/client/shared/widgets/HTMLTooltip");
|
||||
const {HTMLTooltip} = require("devtools/client/shared/widgets/tooltip/HTMLTooltip");
|
||||
const EventEmitter = require("devtools/shared/event-emitter");
|
||||
|
||||
let itemIdCounter = 0;
|
||||
|
|
|
@ -22,7 +22,7 @@ const TEST_URI = `data:text/xml;charset=UTF-8,<?xml version="1.0"?>
|
|||
</vbox>
|
||||
</window>`;
|
||||
|
||||
const {HTMLTooltip} = require("devtools/client/shared/widgets/HTMLTooltip");
|
||||
const {HTMLTooltip} = require("devtools/client/shared/widgets/tooltip/HTMLTooltip");
|
||||
loadHelperScript("helper_html_tooltip.js");
|
||||
|
||||
let useXulWrapper;
|
||||
|
|
|
@ -24,7 +24,7 @@ const TEST_URI = `data:text/xml;charset=UTF-8,<?xml version="1.0"?>
|
|||
</vbox>
|
||||
</window>`;
|
||||
|
||||
const {HTMLTooltip} = require("devtools/client/shared/widgets/HTMLTooltip");
|
||||
const {HTMLTooltip} = require("devtools/client/shared/widgets/tooltip/HTMLTooltip");
|
||||
loadHelperScript("helper_html_tooltip.js");
|
||||
|
||||
let useXulWrapper;
|
||||
|
|
|
@ -28,7 +28,7 @@ const TEST_URI = `data:text/xml;charset=UTF-8,<?xml version="1.0"?>
|
|||
</vbox>
|
||||
</window>`;
|
||||
|
||||
const {HTMLTooltip} = require("devtools/client/shared/widgets/HTMLTooltip");
|
||||
const {HTMLTooltip} = require("devtools/client/shared/widgets/tooltip/HTMLTooltip");
|
||||
loadHelperScript("helper_html_tooltip.js");
|
||||
|
||||
let useXulWrapper;
|
||||
|
|
|
@ -26,7 +26,7 @@ const TEST_URI = `data:text/xml;charset=UTF-8,<?xml version="1.0"?>
|
|||
</vbox>
|
||||
</window>`;
|
||||
|
||||
const {HTMLTooltip} = require("devtools/client/shared/widgets/HTMLTooltip");
|
||||
const {HTMLTooltip} = require("devtools/client/shared/widgets/tooltip/HTMLTooltip");
|
||||
loadHelperScript("helper_html_tooltip.js");
|
||||
|
||||
const TOOLTIP_HEIGHT = 30;
|
||||
|
|
|
@ -23,7 +23,7 @@ const TEST_URI = `data:text/xml;charset=UTF-8,<?xml version="1.0"?>
|
|||
</vbox>
|
||||
</window>`;
|
||||
|
||||
const {HTMLTooltip} = require("devtools/client/shared/widgets/HTMLTooltip");
|
||||
const {HTMLTooltip} = require("devtools/client/shared/widgets/tooltip/HTMLTooltip");
|
||||
loadHelperScript("helper_html_tooltip.js");
|
||||
|
||||
const TOOLTIP_HEIGHT = 200;
|
||||
|
|
|
@ -46,7 +46,7 @@ const TEST_URI = `data:text/xml;charset=UTF-8,<?xml version="1.0"?>
|
|||
</vbox>
|
||||
</window>`;
|
||||
|
||||
const {HTMLTooltip} = require("devtools/client/shared/widgets/HTMLTooltip");
|
||||
const {HTMLTooltip} = require("devtools/client/shared/widgets/tooltip/HTMLTooltip");
|
||||
loadHelperScript("helper_html_tooltip.js");
|
||||
|
||||
let useXulWrapper;
|
||||
|
|
|
@ -40,7 +40,7 @@ const TEST_URI = `data:text/xml;charset=UTF-8,<?xml version="1.0"?>
|
|||
</vbox>
|
||||
</window>`;
|
||||
|
||||
const {HTMLTooltip} = require("devtools/client/shared/widgets/HTMLTooltip");
|
||||
const {HTMLTooltip} = require("devtools/client/shared/widgets/tooltip/HTMLTooltip");
|
||||
loadHelperScript("helper_html_tooltip.js");
|
||||
|
||||
let useXulWrapper;
|
||||
|
|
|
@ -23,7 +23,7 @@ const TEST_URI = `data:text/xml;charset=UTF-8,<?xml version="1.0"?>
|
|||
</vbox>
|
||||
</window>`;
|
||||
|
||||
const {HTMLTooltip} = require("devtools/client/shared/widgets/HTMLTooltip");
|
||||
const {HTMLTooltip} = require("devtools/client/shared/widgets/tooltip/HTMLTooltip");
|
||||
loadHelperScript("helper_html_tooltip.js");
|
||||
|
||||
function getTooltipContent(doc) {
|
||||
|
|
|
@ -23,7 +23,7 @@ const TEST_URI = `data:text/xml;charset=UTF-8,<?xml version="1.0"?>
|
|||
</vbox>
|
||||
</window>`;
|
||||
|
||||
const {HTMLTooltip} = require("devtools/client/shared/widgets/HTMLTooltip");
|
||||
const {HTMLTooltip} = require("devtools/client/shared/widgets/tooltip/HTMLTooltip");
|
||||
loadHelperScript("helper_html_tooltip.js");
|
||||
|
||||
add_task(function* () {
|
||||
|
|
|
@ -23,7 +23,7 @@ const TEST_URI = `data:text/xml;charset=UTF-8,<?xml version="1.0"?>
|
|||
</vbox>
|
||||
</window>`;
|
||||
|
||||
const {HTMLTooltip} = require("devtools/client/shared/widgets/HTMLTooltip");
|
||||
const {HTMLTooltip} = require("devtools/client/shared/widgets/tooltip/HTMLTooltip");
|
||||
loadHelperScript("helper_html_tooltip.js");
|
||||
|
||||
add_task(function* () {
|
||||
|
|
|
@ -25,7 +25,7 @@ const TEST_URI = `data:text/xml;charset=UTF-8,<?xml version="1.0"?>
|
|||
</hbox>
|
||||
</window>`;
|
||||
|
||||
const {HTMLTooltip} = require("devtools/client/shared/widgets/HTMLTooltip");
|
||||
const {HTMLTooltip} = require("devtools/client/shared/widgets/tooltip/HTMLTooltip");
|
||||
loadHelperScript("helper_html_tooltip.js");
|
||||
|
||||
const TOOLBOX_WIDTH = 500;
|
||||
|
|
|
@ -26,7 +26,7 @@ const CONTAINER_HEIGHT = 300;
|
|||
const CONTAINER_WIDTH = 200;
|
||||
const TOOLTIP_HEIGHT = 50;
|
||||
|
||||
const {HTMLTooltip} = require("devtools/client/shared/widgets/HTMLTooltip");
|
||||
const {HTMLTooltip} = require("devtools/client/shared/widgets/tooltip/HTMLTooltip");
|
||||
loadHelperScript("helper_html_tooltip.js");
|
||||
|
||||
add_task(function* () {
|
||||
|
|
|
@ -22,7 +22,7 @@ const TEST_URI = `data:text/xml;charset=UTF-8,<?xml version="1.0"?>
|
|||
</vbox>
|
||||
</window>`;
|
||||
|
||||
const {HTMLTooltip} = require("devtools/client/shared/widgets/HTMLTooltip");
|
||||
const {HTMLTooltip} = require("devtools/client/shared/widgets/tooltip/HTMLTooltip");
|
||||
loadHelperScript("helper_html_tooltip.js");
|
||||
|
||||
let useXulWrapper;
|
||||
|
|
|
@ -22,7 +22,7 @@ const TEST_URI = `data:text/xml;charset=UTF-8,<?xml version="1.0"?>
|
|||
</vbox>
|
||||
</window>`;
|
||||
|
||||
const {HTMLTooltip} = require("devtools/client/shared/widgets/HTMLTooltip");
|
||||
const {HTMLTooltip} = require("devtools/client/shared/widgets/tooltip/HTMLTooltip");
|
||||
loadHelperScript("helper_html_tooltip.js");
|
||||
|
||||
// The test toolbox will be 200px tall, the anchors are 50px tall, therefore, the maximum
|
||||
|
|
|
@ -20,7 +20,6 @@ DevToolsModules(
|
|||
'FlameGraph.js',
|
||||
'Graphs.js',
|
||||
'GraphsWorker.js',
|
||||
'HTMLTooltip.js',
|
||||
'LineGraphWidget.js',
|
||||
'MdnDocsWidget.js',
|
||||
'MountainGraphWidget.js',
|
||||
|
@ -28,7 +27,6 @@ DevToolsModules(
|
|||
'SimpleListWidget.jsm',
|
||||
'Spectrum.js',
|
||||
'TableWidget.js',
|
||||
'Tooltip.js',
|
||||
'TreeWidget.js',
|
||||
'VariablesView.jsm',
|
||||
'VariablesViewController.jsm',
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
const {HTMLTooltip} = require("devtools/client/shared/widgets/HTMLTooltip");
|
||||
const {HTMLTooltip} = require("devtools/client/shared/widgets/tooltip/HTMLTooltip");
|
||||
const {MdnDocsWidget} = require("devtools/client/shared/widgets/MdnDocsWidget");
|
||||
const {KeyShortcuts} = require("devtools/client/shared/key-shortcuts");
|
||||
const XHTML_NS = "http://www.w3.org/1999/xhtml";
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
const EventEmitter = require("devtools/shared/event-emitter");
|
||||
const {KeyShortcuts} = require("devtools/client/shared/key-shortcuts");
|
||||
const {HTMLTooltip} = require("devtools/client/shared/widgets/HTMLTooltip");
|
||||
const {HTMLTooltip} = require("devtools/client/shared/widgets/tooltip/HTMLTooltip");
|
||||
|
||||
/**
|
||||
* Base class for all (color, gradient, ...)-swatch based value editors inside
|
||||
|
|
|
@ -7,11 +7,13 @@
|
|||
DevToolsModules(
|
||||
'CssDocsTooltip.js',
|
||||
'EventTooltipHelper.js',
|
||||
'HTMLTooltip.js',
|
||||
'ImageTooltipHelper.js',
|
||||
'SwatchBasedEditorTooltip.js',
|
||||
'SwatchColorPickerTooltip.js',
|
||||
'SwatchCubicBezierTooltip.js',
|
||||
'SwatchFilterTooltip.js',
|
||||
'Tooltip.js',
|
||||
'TooltipToggle.js',
|
||||
'VariableContentHelper.js',
|
||||
)
|
||||
|
|
|
@ -306,7 +306,7 @@ div.CodeMirror span.eval-text {
|
|||
}
|
||||
}
|
||||
|
||||
/* XUL panel styling (see devtools/client/shared/widgets/Tooltip.js) */
|
||||
/* XUL panel styling (see devtools/client/shared/widgets/tooltip/Tooltip.js) */
|
||||
|
||||
.theme-tooltip-panel .panel-arrowcontent {
|
||||
padding: 5px;
|
||||
|
|
|
@ -318,7 +318,7 @@ div.CodeMirror span.eval-text {
|
|||
}
|
||||
}
|
||||
|
||||
/* XUL panel styling (see devtools/client/shared/widgets/Tooltip.js) */
|
||||
/* XUL panel styling (see devtools/client/shared/widgets/tooltip/Tooltip.js) */
|
||||
|
||||
.theme-tooltip-panel .panel-arrowcontent {
|
||||
padding: 4px;
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
--bezier-grid-color: rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
/* Tooltip widget (see devtools/client/shared/widgets/Tooltip.js) */
|
||||
/* Tooltip widget (see devtools/client/shared/widgets/tooltip/Tooltip.js) */
|
||||
|
||||
.devtools-tooltip .panel-arrowcontent {
|
||||
padding: 4px;
|
||||
|
|
|
@ -79,14 +79,6 @@ add_task(function* () {
|
|||
|
||||
gBrowser.goBack();
|
||||
|
||||
info("Waiting for page to navigate");
|
||||
yield waitForSuccess({
|
||||
name: "go back",
|
||||
validator: function () {
|
||||
return content.location.href == TEST_URI1;
|
||||
},
|
||||
});
|
||||
|
||||
info("Waiting for messages to be cleared due to navigation");
|
||||
yield cleared;
|
||||
|
||||
|
|
|
@ -29,8 +29,11 @@
|
|||
info("to call importPackagedApp(" + packagedAppLocation + ")");
|
||||
ok(!win.UI._busyPromise, "UI is not busy");
|
||||
|
||||
let onValidated = waitForUpdate(win, "project-validated");
|
||||
let onDetails = waitForUpdate(win, "details");
|
||||
yield winProject.projectList.importPackagedApp(packagedAppLocation);
|
||||
yield waitForUpdate(win, "project-validated");
|
||||
yield onValidated;
|
||||
yield onDetails;
|
||||
|
||||
let project = win.AppManager.selectedProject;
|
||||
is(project.location, packagedAppLocation, "Location is valid");
|
||||
|
|
|
@ -221,6 +221,12 @@ public:
|
|||
|
||||
virtual bool HandleEndOfStream() { return false; }
|
||||
|
||||
virtual RefPtr<MediaDecoder::SeekPromise> HandleSeek(SeekTarget aTarget)
|
||||
{
|
||||
MOZ_ASSERT(false, "Can't seek in this state");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
protected:
|
||||
using Master = MediaDecoderStateMachine;
|
||||
explicit StateObject(Master* aPtr) : mMaster(aPtr) {}
|
||||
|
@ -376,6 +382,14 @@ public:
|
|||
SetState(DECODER_STATE_DECODING_FIRSTFRAME);
|
||||
return true;
|
||||
}
|
||||
|
||||
RefPtr<MediaDecoder::SeekPromise> HandleSeek(SeekTarget aTarget) override
|
||||
{
|
||||
SLOG("Not Enough Data to seek at this stage, queuing seek");
|
||||
mMaster->mQueuedSeek.RejectIfExists(__func__);
|
||||
mMaster->mQueuedSeek.mTarget = aTarget;
|
||||
return mMaster->mQueuedSeek.mPromise.Ensure(__func__);
|
||||
}
|
||||
};
|
||||
|
||||
class MediaDecoderStateMachine::DormantState
|
||||
|
@ -407,6 +421,14 @@ public:
|
|||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
RefPtr<MediaDecoder::SeekPromise> HandleSeek(SeekTarget aTarget) override
|
||||
{
|
||||
SLOG("Not Enough Data to seek at this stage, queuing seek");
|
||||
mMaster->mQueuedSeek.RejectIfExists(__func__);
|
||||
mMaster->mQueuedSeek.mTarget = aTarget;
|
||||
return mMaster->mQueuedSeek.mPromise.Ensure(__func__);
|
||||
}
|
||||
};
|
||||
|
||||
class MediaDecoderStateMachine::DecodingFirstFrameState
|
||||
|
@ -417,7 +439,22 @@ public:
|
|||
|
||||
void Enter() override
|
||||
{
|
||||
mMaster->DecodeFirstFrame();
|
||||
// Handle pending seek.
|
||||
if (mMaster->mQueuedSeek.Exists() &&
|
||||
(mMaster->mSentFirstFrameLoadedEvent ||
|
||||
Reader()->ForceZeroStartTime())) {
|
||||
mMaster->InitiateSeek(Move(mMaster->mQueuedSeek));
|
||||
return;
|
||||
}
|
||||
|
||||
// Transition to DECODING if we've decoded first frames.
|
||||
if (mMaster->mSentFirstFrameLoadedEvent) {
|
||||
SetState(DECODER_STATE_DECODING);
|
||||
return;
|
||||
}
|
||||
|
||||
// Dispatch tasks to decode first frames.
|
||||
mMaster->DispatchDecodeTasksIfNeeded();
|
||||
}
|
||||
|
||||
State GetState() const override
|
||||
|
@ -428,22 +465,68 @@ public:
|
|||
bool HandleAudioDecoded(MediaData* aAudio) override
|
||||
{
|
||||
mMaster->Push(aAudio, MediaData::AUDIO_DATA);
|
||||
mMaster->MaybeFinishDecodeFirstFrame();
|
||||
MaybeFinishDecodeFirstFrame();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HandleVideoDecoded(MediaData* aVideo, TimeStamp aDecodeStart) override
|
||||
{
|
||||
mMaster->Push(aVideo, MediaData::VIDEO_DATA);
|
||||
mMaster->MaybeFinishDecodeFirstFrame();
|
||||
MaybeFinishDecodeFirstFrame();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HandleEndOfStream() override
|
||||
{
|
||||
mMaster->MaybeFinishDecodeFirstFrame();
|
||||
MaybeFinishDecodeFirstFrame();
|
||||
return true;
|
||||
}
|
||||
|
||||
RefPtr<MediaDecoder::SeekPromise> HandleSeek(SeekTarget aTarget) override
|
||||
{
|
||||
// Should've transitioned to DECODING in Enter()
|
||||
// if mSentFirstFrameLoadedEvent is true.
|
||||
MOZ_ASSERT(!mMaster->mSentFirstFrameLoadedEvent);
|
||||
|
||||
if (!Reader()->ForceZeroStartTime()) {
|
||||
SLOG("Not Enough Data to seek at this stage, queuing seek");
|
||||
mMaster->mQueuedSeek.RejectIfExists(__func__);
|
||||
mMaster->mQueuedSeek.mTarget = aTarget;
|
||||
return mMaster->mQueuedSeek.mPromise.Ensure(__func__);
|
||||
}
|
||||
|
||||
// Since ForceZeroStartTime() is true, we should've transitioned to SEEKING
|
||||
// in Enter() if there is any queued seek.
|
||||
MOZ_ASSERT(!mMaster->mQueuedSeek.Exists());
|
||||
|
||||
SLOG("Changed state to SEEKING (to %lld)", aTarget.GetTime().ToMicroseconds());
|
||||
SeekJob seekJob;
|
||||
seekJob.mTarget = aTarget;
|
||||
RefPtr<MediaDecoder::SeekPromise> p = seekJob.mPromise.Ensure(__func__);
|
||||
mMaster->InitiateSeek(Move(seekJob));
|
||||
return p.forget();
|
||||
}
|
||||
|
||||
private:
|
||||
// Notify FirstFrameLoaded if having decoded first frames and
|
||||
// transition to SEEKING if there is any pending seek, or DECODING otherwise.
|
||||
void MaybeFinishDecodeFirstFrame()
|
||||
{
|
||||
MOZ_ASSERT(!mMaster->mSentFirstFrameLoadedEvent);
|
||||
|
||||
if ((mMaster->IsAudioDecoding() && mMaster->AudioQueue().GetSize() == 0) ||
|
||||
(mMaster->IsVideoDecoding() && mMaster->VideoQueue().GetSize() == 0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
mMaster->FinishDecodeFirstFrame();
|
||||
|
||||
if (mMaster->mQueuedSeek.Exists()) {
|
||||
mMaster->InitiateSeek(Move(mMaster->mQueuedSeek));
|
||||
} else {
|
||||
SetState(DECODER_STATE_DECODING);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class MediaDecoderStateMachine::DecodingState
|
||||
|
@ -525,6 +608,17 @@ public:
|
|||
return true;
|
||||
}
|
||||
|
||||
RefPtr<MediaDecoder::SeekPromise> HandleSeek(SeekTarget aTarget) override
|
||||
{
|
||||
mMaster->mQueuedSeek.RejectIfExists(__func__);
|
||||
SLOG("Changed state to SEEKING (to %lld)", aTarget.GetTime().ToMicroseconds());
|
||||
SeekJob seekJob;
|
||||
seekJob.mTarget = aTarget;
|
||||
RefPtr<MediaDecoder::SeekPromise> p = seekJob.mPromise.Ensure(__func__);
|
||||
mMaster->InitiateSeek(Move(seekJob));
|
||||
return p.forget();
|
||||
}
|
||||
|
||||
private:
|
||||
void CheckSlowDecoding(TimeStamp aDecodeStart)
|
||||
{
|
||||
|
@ -573,7 +667,70 @@ class MediaDecoderStateMachine::SeekingState
|
|||
: public MediaDecoderStateMachine::StateObject
|
||||
{
|
||||
public:
|
||||
explicit SeekingState(Master* aPtr) : StateObject(aPtr) {}
|
||||
explicit SeekingState(Master* aPtr, SeekJob aSeekJob)
|
||||
: StateObject(aPtr), mSeekJob(Move(aSeekJob)) {}
|
||||
|
||||
void Enter() override
|
||||
{
|
||||
// Discard the existing seek task.
|
||||
mMaster->DiscardSeekTaskIfExist();
|
||||
|
||||
mMaster->mSeekTaskRequest.DisconnectIfExists();
|
||||
|
||||
// SeekTask will register its callbacks to MediaDecoderReaderWrapper.
|
||||
mMaster->CancelMediaDecoderReaderWrapperCallback();
|
||||
|
||||
// Create a new SeekTask instance for the incoming seek task.
|
||||
if (mSeekJob.mTarget.IsAccurate() ||
|
||||
mSeekJob.mTarget.IsFast()) {
|
||||
mMaster->mSeekTask = new AccurateSeekTask(
|
||||
mMaster->mDecoderID, OwnerThread(), Reader(), mSeekJob.mTarget,
|
||||
mMaster->mInfo, mMaster->Duration(), mMaster->GetMediaTime());
|
||||
} else if (mSeekJob.mTarget.IsNextFrame()) {
|
||||
mMaster->mSeekTask = new NextFrameSeekTask(
|
||||
mMaster->mDecoderID, OwnerThread(), Reader(), mSeekJob.mTarget,
|
||||
mMaster->mInfo, mMaster->Duration(),mMaster->GetMediaTime(),
|
||||
mMaster->AudioQueue(), mMaster->VideoQueue());
|
||||
} else {
|
||||
MOZ_DIAGNOSTIC_ASSERT(false, "Cannot handle this seek task.");
|
||||
}
|
||||
|
||||
// Don't stop playback for a video-only seek since audio is playing.
|
||||
if (!mSeekJob.mTarget.IsVideoOnly()) {
|
||||
mMaster->StopPlayback();
|
||||
}
|
||||
|
||||
// mSeekJob.mTarget.mTime might be different from
|
||||
// mSeekTask->GetSeekTarget().mTime because the seek task might clamp the
|
||||
// seek target to [0, duration]. We want to update the playback position to
|
||||
// the clamped value.
|
||||
mMaster->UpdatePlaybackPositionInternal(
|
||||
mMaster->mSeekTask->GetSeekTarget().GetTime().ToMicroseconds());
|
||||
|
||||
if (mSeekJob.mTarget.mEventVisibility ==
|
||||
MediaDecoderEventVisibility::Observable) {
|
||||
mMaster->mOnPlaybackEvent.Notify(MediaEventType::SeekStarted);
|
||||
}
|
||||
|
||||
// Reset our state machine and decoding pipeline before seeking.
|
||||
if (mMaster->mSeekTask->NeedToResetMDSM()) {
|
||||
if (mSeekJob.mTarget.IsVideoOnly()) {
|
||||
mMaster->Reset(TrackInfo::kVideoTrack);
|
||||
} else {
|
||||
mMaster->Reset();
|
||||
}
|
||||
}
|
||||
|
||||
// Do the seek.
|
||||
mMaster->mSeekTaskRequest.Begin(mMaster->mSeekTask->Seek(mMaster->Duration())
|
||||
->Then(OwnerThread(), __func__, mMaster,
|
||||
&MediaDecoderStateMachine::OnSeekTaskResolved,
|
||||
&MediaDecoderStateMachine::OnSeekTaskRejected));
|
||||
|
||||
MOZ_ASSERT(!mMaster->mQueuedSeek.Exists());
|
||||
MOZ_ASSERT(!mMaster->mCurrentSeek.Exists());
|
||||
mMaster->mCurrentSeek = Move(mSeekJob);
|
||||
}
|
||||
|
||||
State GetState() const override
|
||||
{
|
||||
|
@ -611,6 +768,20 @@ public:
|
|||
MOZ_ASSERT(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
RefPtr<MediaDecoder::SeekPromise> HandleSeek(SeekTarget aTarget) override
|
||||
{
|
||||
mMaster->mQueuedSeek.RejectIfExists(__func__);
|
||||
SLOG("Changed state to SEEKING (to %lld)", aTarget.GetTime().ToMicroseconds());
|
||||
SeekJob seekJob;
|
||||
seekJob.mTarget = aTarget;
|
||||
RefPtr<MediaDecoder::SeekPromise> p = seekJob.mPromise.Ensure(__func__);
|
||||
mMaster->InitiateSeek(Move(seekJob));
|
||||
return p.forget();
|
||||
}
|
||||
|
||||
private:
|
||||
SeekJob mSeekJob;
|
||||
};
|
||||
|
||||
class MediaDecoderStateMachine::BufferingState
|
||||
|
@ -712,6 +883,17 @@ public:
|
|||
return true;
|
||||
}
|
||||
|
||||
RefPtr<MediaDecoder::SeekPromise> HandleSeek(SeekTarget aTarget) override
|
||||
{
|
||||
mMaster->mQueuedSeek.RejectIfExists(__func__);
|
||||
SLOG("Changed state to SEEKING (to %lld)", aTarget.GetTime().ToMicroseconds());
|
||||
SeekJob seekJob;
|
||||
seekJob.mTarget = aTarget;
|
||||
RefPtr<MediaDecoder::SeekPromise> p = seekJob.mPromise.Ensure(__func__);
|
||||
mMaster->InitiateSeek(Move(seekJob));
|
||||
return p.forget();
|
||||
}
|
||||
|
||||
private:
|
||||
TimeStamp mBufferingStart;
|
||||
};
|
||||
|
@ -780,6 +962,17 @@ public:
|
|||
return DECODER_STATE_COMPLETED;
|
||||
}
|
||||
|
||||
RefPtr<MediaDecoder::SeekPromise> HandleSeek(SeekTarget aTarget) override
|
||||
{
|
||||
mMaster->mQueuedSeek.RejectIfExists(__func__);
|
||||
SLOG("Changed state to SEEKING (to %lld)", aTarget.GetTime().ToMicroseconds());
|
||||
SeekJob seekJob;
|
||||
seekJob.mTarget = aTarget;
|
||||
RefPtr<MediaDecoder::SeekPromise> p = seekJob.mPromise.Ensure(__func__);
|
||||
mMaster->InitiateSeek(Move(seekJob));
|
||||
return p.forget();
|
||||
}
|
||||
|
||||
private:
|
||||
bool mSentPlaybackEndedEvent = false;
|
||||
};
|
||||
|
@ -1249,26 +1442,6 @@ MediaDecoderStateMachine::OnNotDecoded(MediaData::Type aType,
|
|||
mStateObj->HandleEndOfStream();
|
||||
}
|
||||
|
||||
void
|
||||
MediaDecoderStateMachine::MaybeFinishDecodeFirstFrame()
|
||||
{
|
||||
MOZ_ASSERT(OnTaskQueue());
|
||||
MOZ_ASSERT(!mSentFirstFrameLoadedEvent);
|
||||
|
||||
if ((IsAudioDecoding() && AudioQueue().GetSize() == 0) ||
|
||||
(IsVideoDecoding() && VideoQueue().GetSize() == 0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
FinishDecodeFirstFrame();
|
||||
|
||||
if (mQueuedSeek.Exists()) {
|
||||
InitiateSeek(Move(mQueuedSeek));
|
||||
} else {
|
||||
SetState(DECODER_STATE_DECODING);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
MediaDecoderStateMachine::OnVideoDecoded(MediaData* aVideo,
|
||||
TimeStamp aDecodeStartTime)
|
||||
|
@ -1576,9 +1749,6 @@ MediaDecoderStateMachine::SetState(State aState)
|
|||
case DECODER_STATE_DECODING:
|
||||
mStateObj = MakeUnique<DecodingState>(this);
|
||||
break;
|
||||
case DECODER_STATE_SEEKING:
|
||||
mStateObj = MakeUnique<SeekingState>(this);
|
||||
break;
|
||||
case DECODER_STATE_BUFFERING:
|
||||
mStateObj = MakeUnique<BufferingState>(this);
|
||||
break;
|
||||
|
@ -1730,29 +1900,6 @@ MediaDecoderStateMachine::Shutdown()
|
|||
->CompletionPromise();
|
||||
}
|
||||
|
||||
void
|
||||
MediaDecoderStateMachine::DecodeFirstFrame()
|
||||
{
|
||||
MOZ_ASSERT(OnTaskQueue());
|
||||
MOZ_ASSERT(mState == DECODER_STATE_DECODING_FIRSTFRAME);
|
||||
|
||||
// Handle pending seek.
|
||||
if (mQueuedSeek.Exists() &&
|
||||
(mSentFirstFrameLoadedEvent || mReader->ForceZeroStartTime())) {
|
||||
InitiateSeek(Move(mQueuedSeek));
|
||||
return;
|
||||
}
|
||||
|
||||
// Transition to DECODING if we've decoded first frames.
|
||||
if (mSentFirstFrameLoadedEvent) {
|
||||
SetState(DECODER_STATE_DECODING);
|
||||
return;
|
||||
}
|
||||
|
||||
// Dispatch tasks to decode first frames.
|
||||
DispatchDecodeTasksIfNeeded();
|
||||
}
|
||||
|
||||
void MediaDecoderStateMachine::PlayStateChanged()
|
||||
{
|
||||
MOZ_ASSERT(OnTaskQueue());
|
||||
|
@ -1841,11 +1988,6 @@ void MediaDecoderStateMachine::VisibilityChanged()
|
|||
return;
|
||||
}
|
||||
|
||||
// If not playing then there's nothing to do.
|
||||
if (mPlayState != MediaDecoder::PLAY_STATE_PLAYING) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Start timer to trigger suspended decoding state when going invisible.
|
||||
if (!mIsVisible) {
|
||||
TimeStamp target = TimeStamp::Now() + SuspendBackgroundVideoDelay();
|
||||
|
@ -1895,10 +2037,11 @@ void MediaDecoderStateMachine::VisibilityChanged()
|
|||
MediaDecoderEventVisibility::Suppressed,
|
||||
true /* aVideoOnly */);
|
||||
|
||||
InitiateSeek(Move(seekJob))
|
||||
->Then(AbstractThread::MainThread(), __func__,
|
||||
[start, info, hw](){ ReportRecoveryTelemetry(start, info, hw); },
|
||||
[](){});
|
||||
RefPtr<MediaDecoder::SeekPromise> p = seekJob.mPromise.Ensure(__func__);
|
||||
p->Then(AbstractThread::MainThread(), __func__,
|
||||
[start, info, hw](){ ReportRecoveryTelemetry(start, info, hw); },
|
||||
[](){});
|
||||
InitiateSeek(Move(seekJob));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1949,24 +2092,7 @@ MediaDecoderStateMachine::Seek(SeekTarget aTarget)
|
|||
|
||||
MOZ_ASSERT(mDuration.Ref().isSome(), "We should have got duration already");
|
||||
|
||||
// Can't seek until the start time is known.
|
||||
bool hasStartTime = mSentFirstFrameLoadedEvent || mReader->ForceZeroStartTime();
|
||||
// Can't seek when state is WAIT_FOR_CDM or DORMANT.
|
||||
bool stateAllowed = mState >= DECODER_STATE_DECODING_FIRSTFRAME;
|
||||
|
||||
if (!stateAllowed || !hasStartTime) {
|
||||
DECODER_LOG("Seek() Not Enough Data to continue at this stage, queuing seek");
|
||||
mQueuedSeek.RejectIfExists(__func__);
|
||||
mQueuedSeek.mTarget = aTarget;
|
||||
return mQueuedSeek.mPromise.Ensure(__func__);
|
||||
}
|
||||
mQueuedSeek.RejectIfExists(__func__);
|
||||
|
||||
DECODER_LOG("Changed state to SEEKING (to %lld)", aTarget.GetTime().ToMicroseconds());
|
||||
|
||||
SeekJob seekJob;
|
||||
seekJob.mTarget = aTarget;
|
||||
return InitiateSeek(Move(seekJob));
|
||||
return mStateObj->HandleSeek(aTarget);
|
||||
}
|
||||
|
||||
RefPtr<MediaDecoder::SeekPromise>
|
||||
|
@ -2047,69 +2173,17 @@ MediaDecoderStateMachine::DispatchDecodeTasksIfNeeded()
|
|||
}
|
||||
}
|
||||
|
||||
RefPtr<MediaDecoder::SeekPromise>
|
||||
void
|
||||
MediaDecoderStateMachine::InitiateSeek(SeekJob aSeekJob)
|
||||
{
|
||||
MOZ_ASSERT(OnTaskQueue());
|
||||
|
||||
SetState(DECODER_STATE_SEEKING);
|
||||
|
||||
// Discard the existing seek task.
|
||||
DiscardSeekTaskIfExist();
|
||||
|
||||
mSeekTaskRequest.DisconnectIfExists();
|
||||
|
||||
// SeekTask will register its callbacks to MediaDecoderReaderWrapper.
|
||||
CancelMediaDecoderReaderWrapperCallback();
|
||||
|
||||
// Create a new SeekTask instance for the incoming seek task.
|
||||
if (aSeekJob.mTarget.IsAccurate() ||
|
||||
aSeekJob.mTarget.IsFast()) {
|
||||
mSeekTask = new AccurateSeekTask(mDecoderID, OwnerThread(),
|
||||
mReader.get(), aSeekJob.mTarget,
|
||||
mInfo, Duration(), GetMediaTime());
|
||||
} else if (aSeekJob.mTarget.IsNextFrame()) {
|
||||
mSeekTask = new NextFrameSeekTask(mDecoderID, OwnerThread(), mReader.get(),
|
||||
aSeekJob.mTarget, mInfo, Duration(),
|
||||
GetMediaTime(), AudioQueue(), VideoQueue());
|
||||
} else {
|
||||
MOZ_DIAGNOSTIC_ASSERT(false, "Cannot handle this seek task.");
|
||||
}
|
||||
|
||||
// Don't stop playback for a video-only seek since audio is playing.
|
||||
if (!aSeekJob.mTarget.IsVideoOnly()) {
|
||||
StopPlayback();
|
||||
}
|
||||
|
||||
// aSeekJob.mTarget.mTime might be different from
|
||||
// mSeekTask->GetSeekTarget().mTime because the seek task might clamp the seek
|
||||
// target to [0, duration]. We want to update the playback position to the
|
||||
// clamped value.
|
||||
UpdatePlaybackPositionInternal(mSeekTask->GetSeekTarget().GetTime().ToMicroseconds());
|
||||
|
||||
if (aSeekJob.mTarget.mEventVisibility == MediaDecoderEventVisibility::Observable) {
|
||||
mOnPlaybackEvent.Notify(MediaEventType::SeekStarted);
|
||||
}
|
||||
|
||||
// Reset our state machine and decoding pipeline before seeking.
|
||||
if (mSeekTask->NeedToResetMDSM()) {
|
||||
if (aSeekJob.mTarget.IsVideoOnly()) {
|
||||
Reset(TrackInfo::kVideoTrack);
|
||||
} else {
|
||||
Reset();
|
||||
}
|
||||
}
|
||||
|
||||
// Do the seek.
|
||||
mSeekTaskRequest.Begin(mSeekTask->Seek(Duration())
|
||||
->Then(OwnerThread(), __func__, this,
|
||||
&MediaDecoderStateMachine::OnSeekTaskResolved,
|
||||
&MediaDecoderStateMachine::OnSeekTaskRejected));
|
||||
|
||||
MOZ_ASSERT(!mQueuedSeek.Exists());
|
||||
MOZ_ASSERT(!mCurrentSeek.Exists());
|
||||
mCurrentSeek = Move(aSeekJob);
|
||||
return mCurrentSeek.mPromise.Ensure(__func__);
|
||||
// Note we can't call SetState(DECODER_STATE_SEEKING) which does nothing
|
||||
// if we are already in the SEEKING state.
|
||||
mStateObj->Exit();
|
||||
mState = DECODER_STATE_SEEKING;
|
||||
mStateObj = MakeUnique<SeekingState>(this, Move(aSeekJob));
|
||||
mStateObj->Enter();
|
||||
}
|
||||
|
||||
void
|
||||
|
|
|
@ -472,9 +472,6 @@ protected:
|
|||
// If we don't, switch to buffering mode.
|
||||
void MaybeStartBuffering();
|
||||
|
||||
// The entry action of DECODER_STATE_DECODING_FIRSTFRAME.
|
||||
void DecodeFirstFrame();
|
||||
|
||||
// Moves the decoder into the shutdown state, and dispatches an error
|
||||
// event to the media element. This begins shutting down the decoder.
|
||||
// The decoder monitor must be held. This is only called on the
|
||||
|
@ -489,7 +486,7 @@ protected:
|
|||
void EnqueueFirstFrameLoadedEvent();
|
||||
|
||||
// Clears any previous seeking state and initiates a new seek on the decoder.
|
||||
RefPtr<MediaDecoder::SeekPromise> InitiateSeek(SeekJob aSeekJob);
|
||||
void InitiateSeek(SeekJob aSeekJob);
|
||||
|
||||
void DispatchAudioDecodeTaskIfNeeded();
|
||||
void DispatchVideoDecodeTaskIfNeeded();
|
||||
|
@ -533,10 +530,6 @@ protected:
|
|||
// must be held when calling this. Called on the decode thread.
|
||||
int64_t GetDecodedAudioDuration();
|
||||
|
||||
// Notify FirstFrameLoaded if having decoded first frames and
|
||||
// transition to SEEKING if there is any pending seek, or DECODING otherwise.
|
||||
void MaybeFinishDecodeFirstFrame();
|
||||
|
||||
void FinishDecodeFirstFrame();
|
||||
|
||||
// Completes the seek operation, moves onto the next appropriate state.
|
||||
|
|
|
@ -61,13 +61,16 @@ public:
|
|||
|
||||
void DoNotifyFinished()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
mFinishPromise.ResolveIfExists(true, __func__);
|
||||
}
|
||||
|
||||
void Forget()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
mFinishPromise.ResolveIfExists(true, __func__);
|
||||
AbstractThread::MainThread()->Dispatch(NS_NewRunnableFunction([this] () {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
mFinishPromise.ResolveIfExists(true, __func__);
|
||||
}));
|
||||
MutexAutoLock lock(mMutex);
|
||||
mStream = nullptr;
|
||||
}
|
||||
|
@ -123,6 +126,7 @@ public:
|
|||
~DecodedStreamData();
|
||||
void SetPlaying(bool aPlaying);
|
||||
MediaEventSource<int64_t>& OnOutput();
|
||||
void Forget();
|
||||
|
||||
/* The following group of fields are protected by the decoder's monitor
|
||||
* and can be read or written on any thread.
|
||||
|
@ -188,7 +192,6 @@ DecodedStreamData::DecodedStreamData(OutputStreamManager* aOutputStreamManager,
|
|||
DecodedStreamData::~DecodedStreamData()
|
||||
{
|
||||
mOutputStreamManager->Disconnect();
|
||||
mListener->Forget();
|
||||
mStream->Destroy();
|
||||
}
|
||||
|
||||
|
@ -207,6 +210,12 @@ DecodedStreamData::SetPlaying(bool aPlaying)
|
|||
}
|
||||
}
|
||||
|
||||
void
|
||||
DecodedStreamData::Forget()
|
||||
{
|
||||
mListener->Forget();
|
||||
}
|
||||
|
||||
DecodedStream::DecodedStream(AbstractThread* aOwnerThread,
|
||||
MediaQueue<MediaData>& aAudioQueue,
|
||||
MediaQueue<MediaData>& aVideoQueue,
|
||||
|
@ -363,6 +372,7 @@ DecodedStream::DestroyData(UniquePtr<DecodedStreamData> aData)
|
|||
mOutputListener.Disconnect();
|
||||
|
||||
DecodedStreamData* data = aData.release();
|
||||
data->Forget();
|
||||
nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([=] () {
|
||||
delete data;
|
||||
});
|
||||
|
|
|
@ -19,7 +19,6 @@ function runWithMSE(testFunction) {
|
|||
addLoadEvent(function () {
|
||||
SpecialPowers.pushPrefEnv({"set": [
|
||||
[ "media.mediasource.enabled", true ],
|
||||
[ "media.test.dumpDebugInfo", true ],
|
||||
]},
|
||||
bootstrapTest);
|
||||
});
|
||||
|
|
|
@ -1545,9 +1545,6 @@ var PARALLEL_TESTS = 2;
|
|||
// conditions that might not otherwise be encountered on the test data.
|
||||
var gTestPrefs = [
|
||||
['media.recorder.max_memory', 1024],
|
||||
["media.preload.default", 2], // default preload = metadata
|
||||
["media.preload.auto", 3], // auto preload = enough
|
||||
["media.test.dumpDebugInfo", true],
|
||||
];
|
||||
|
||||
// When true, we'll loop forever on whatever test we run. Use this to debug
|
||||
|
@ -1702,14 +1699,6 @@ function mediaTestCleanup(callback) {
|
|||
SpecialPowers.exactGC(callback);
|
||||
}
|
||||
|
||||
function setMediaTestsPrefs(callback, extraPrefs) {
|
||||
var prefs = gTestPrefs;
|
||||
if (extraPrefs) {
|
||||
prefs = prefs.concat(extraPrefs);
|
||||
}
|
||||
SpecialPowers.pushPrefEnv({"set": prefs}, callback);
|
||||
}
|
||||
|
||||
// B2G emulator and Android 2.3 are condidered slow platforms
|
||||
function isSlowPlatform() {
|
||||
return SpecialPowers.Services.appinfo.name == "B2G" || getAndroidVersion() == 10;
|
||||
|
|
|
@ -28,9 +28,7 @@ function done() {
|
|||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
addLoadEvent(function() {
|
||||
setMediaTestsPrefs(run);
|
||||
});
|
||||
addLoadEvent(run);
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
|
||||
|
|
|
@ -37,7 +37,7 @@ if (media == null) {
|
|||
}, {once: true});
|
||||
}
|
||||
|
||||
setMediaTestsPrefs(startTest);
|
||||
startTest();
|
||||
}
|
||||
|
||||
</script>
|
||||
|
|
|
@ -476,7 +476,7 @@ var commandsPeerConnectionOfferAnswer = [
|
|||
}
|
||||
},
|
||||
function PC_REMOTE_VERIFY_SDP_AFTER_END_OF_TRICKLE(test) {
|
||||
if (test.pcRemote.endOfTrickelSdp) {
|
||||
if (test.pcRemote.endOfTrickleSdp) {
|
||||
/* In case the endOfTrickleSdp promise is resolved already it will win the
|
||||
* race because it gets evaluated first. But if endOfTrickleSdp is still
|
||||
* pending the rejection will win the race. */
|
||||
|
|
|
@ -1996,8 +1996,6 @@ gfxWindowsPlatform::GetAcceleratedCompositorBackends(nsTArray<LayersBackend>& aB
|
|||
|
||||
if (gfxConfig::IsEnabled(Feature::D3D11_COMPOSITING)) {
|
||||
aBackends.AppendElement(LayersBackend::LAYERS_D3D11);
|
||||
} else {
|
||||
NS_WARNING("Direct3D 11-accelerated layers are not supported on this system.");
|
||||
}
|
||||
|
||||
if (gfxConfig::IsEnabled(Feature::D3D9_COMPOSITING) && !gfxPrefs::LayersPreferD3D9()) {
|
||||
|
|
|
@ -205,3 +205,34 @@ def more_deterministic(value):
|
|||
return True
|
||||
|
||||
set_define('JS_MORE_DETERMINISTIC', more_deterministic)
|
||||
|
||||
|
||||
# CTypes
|
||||
# =======================================================
|
||||
@depends(building_js, '--help')
|
||||
def ctypes_default(building_js, _):
|
||||
return not building_js
|
||||
|
||||
js_option('--enable-ctypes', help='Enable js-ctypes',
|
||||
default=ctypes_default)
|
||||
|
||||
build_ctypes = depends_if('--enable-ctypes')(lambda _: True)
|
||||
|
||||
set_config('BUILD_CTYPES', build_ctypes)
|
||||
set_define('BUILD_CTYPES', build_ctypes)
|
||||
add_old_configure_assignment('BUILD_CTYPES', build_ctypes)
|
||||
|
||||
@depends(build_ctypes, building_js)
|
||||
def js_has_ctypes(ctypes, js):
|
||||
if ctypes and js:
|
||||
return True
|
||||
|
||||
set_config('JS_HAS_CTYPES', js_has_ctypes)
|
||||
set_define('JS_HAS_CTYPES', js_has_ctypes)
|
||||
add_old_configure_assignment('JS_HAS_CTYPES', js_has_ctypes)
|
||||
|
||||
@depends('--enable-ctypes', '--enable-compile-environment', '--help')
|
||||
def ctypes_and_compile_environment(ctypes, compile_environment, _):
|
||||
return ctypes and compile_environment
|
||||
|
||||
include_when('ffi.configure', when=ctypes_and_compile_environment)
|
||||
|
|
|
@ -237,7 +237,7 @@ typedef enum JSWhyMagic
|
|||
JS_WHY_MAGIC_COUNT
|
||||
} JSWhyMagic;
|
||||
|
||||
#if defined(IS_LITTLE_ENDIAN)
|
||||
#if MOZ_LITTLE_ENDIAN
|
||||
# if defined(JS_NUNBOX32)
|
||||
typedef union jsval_layout
|
||||
{
|
||||
|
@ -285,7 +285,7 @@ typedef union jsval_layout
|
|||
uintptr_t asUIntPtr;
|
||||
} JSVAL_ALIGNMENT jsval_layout;
|
||||
# endif /* JS_PUNBOX64 */
|
||||
#else /* defined(IS_LITTLE_ENDIAN) */
|
||||
#else /* MOZ_LITTLE_ENDIAN */
|
||||
# if defined(JS_NUNBOX32)
|
||||
typedef union jsval_layout
|
||||
{
|
||||
|
@ -331,7 +331,7 @@ typedef union jsval_layout
|
|||
uintptr_t asUIntPtr;
|
||||
} JSVAL_ALIGNMENT jsval_layout;
|
||||
# endif /* JS_PUNBOX64 */
|
||||
#endif /* defined(IS_LITTLE_ENDIAN) */
|
||||
#endif /* MOZ_LITTLE_ENDIAN */
|
||||
|
||||
JS_STATIC_ASSERT(sizeof(jsval_layout) == 8);
|
||||
|
||||
|
|
|
@ -66,7 +66,7 @@ LIRGenerator::visitParameter(MParameter* param)
|
|||
|
||||
offset *= sizeof(Value);
|
||||
#if defined(JS_NUNBOX32)
|
||||
# if defined(IS_BIG_ENDIAN)
|
||||
# if MOZ_BIG_ENDIAN
|
||||
ins->getDef(0)->setOutput(LArgument(offset));
|
||||
ins->getDef(1)->setOutput(LArgument(offset + 4));
|
||||
# else
|
||||
|
|
|
@ -179,7 +179,7 @@ using mozilla::FloatingPoint;
|
|||
# define PER_SHARED_ARCH DEFINED_ON(ALL_SHARED_ARCH)
|
||||
|
||||
|
||||
#ifdef IS_LITTLE_ENDIAN
|
||||
#if MOZ_LITTLE_ENDIAN
|
||||
#define IMM32_16ADJ(X) X << 16
|
||||
#else
|
||||
#define IMM32_16ADJ(X) X
|
||||
|
|
|
@ -48,18 +48,6 @@
|
|||
/* Define to 1 to perform extra assertions and heap poisoning. */
|
||||
#undef JS_CRASH_DIAGNOSTICS
|
||||
|
||||
/* Define to 1 if the <endian.h> header is present and
|
||||
useable. See jscpucfg.h. */
|
||||
#undef JS_HAVE_ENDIAN_H
|
||||
|
||||
/* Define to 1 if the <machine/endian.h> header is present and
|
||||
useable. See jscpucfg.h. */
|
||||
#undef JS_HAVE_MACHINE_ENDIAN_H
|
||||
|
||||
/* Define to 1 if the <sys/isa_defs.h> header is present and
|
||||
useable. See jscpucfg.h. */
|
||||
#undef JS_HAVE_SYS_ISA_DEFS_H
|
||||
|
||||
/* Define to 1 if SpiderMonkey is in NUNBOX32 mode. */
|
||||
#undef JS_NUNBOX32
|
||||
|
||||
|
|
|
@ -568,7 +568,7 @@ js::XDRAtom(XDRState<mode>* xdr, MutableHandleAtom atomp)
|
|||
const Latin1Char* chars = reinterpret_cast<const Latin1Char*>(xdr->buf.read(length));
|
||||
atom = AtomizeChars(cx, chars, length);
|
||||
} else {
|
||||
#if IS_LITTLE_ENDIAN
|
||||
#if MOZ_LITTLE_ENDIAN
|
||||
/* Directly access the little endian chars in the XDR buffer. */
|
||||
const char16_t* chars = reinterpret_cast<const char16_t*>(xdr->buf.read(length * sizeof(char16_t)));
|
||||
atom = AtomizeChars(cx, chars, length);
|
||||
|
@ -596,7 +596,7 @@ js::XDRAtom(XDRState<mode>* xdr, MutableHandleAtom atomp)
|
|||
atom = AtomizeChars(cx, chars, length);
|
||||
if (chars != stackChars)
|
||||
js_free(chars);
|
||||
#endif /* !IS_LITTLE_ENDIAN */
|
||||
#endif /* !MOZ_LITTLE_ENDIAN */
|
||||
}
|
||||
|
||||
if (!atom)
|
||||
|
|
|
@ -7,119 +7,7 @@
|
|||
#ifndef jscpucfg_h
|
||||
#define jscpucfg_h
|
||||
|
||||
#define JS_HAVE_LONG_LONG
|
||||
|
||||
#if defined(_WIN64)
|
||||
|
||||
# if defined(_M_X64) || defined(_M_AMD64) || defined(_AMD64_)
|
||||
# define IS_LITTLE_ENDIAN 1
|
||||
# undef IS_BIG_ENDIAN
|
||||
# else /* !(defined(_M_X64) || defined(_M_AMD64) || defined(_AMD64_)) */
|
||||
# error "CPU type is unknown"
|
||||
# endif /* !(defined(_M_X64) || defined(_M_AMD64) || defined(_AMD64_)) */
|
||||
|
||||
#elif defined(_WIN32)
|
||||
|
||||
# ifdef __WATCOMC__
|
||||
# define HAVE_VA_LIST_AS_ARRAY 1
|
||||
# endif
|
||||
|
||||
# define IS_LITTLE_ENDIAN 1
|
||||
# undef IS_BIG_ENDIAN
|
||||
|
||||
#elif defined(__APPLE__) || defined(__powerpc__) || defined(__ppc__)
|
||||
# if __LITTLE_ENDIAN__
|
||||
# define IS_LITTLE_ENDIAN 1
|
||||
# undef IS_BIG_ENDIAN
|
||||
# elif __BIG_ENDIAN__
|
||||
# undef IS_LITTLE_ENDIAN
|
||||
# define IS_BIG_ENDIAN 1
|
||||
# endif
|
||||
|
||||
#elif defined(JS_HAVE_ENDIAN_H)
|
||||
# include <endian.h>
|
||||
|
||||
/*
|
||||
* Historically, OSes providing <endian.h> only defined
|
||||
* __BYTE_ORDER to either __LITTLE_ENDIAN or __BIG_ENDIAN.
|
||||
* The Austin group decided to standardise <endian.h> in
|
||||
* POSIX around 2011, expecting it to provide a BYTE_ORDER
|
||||
* #define set to either LITTLE_ENDIAN or BIG_ENDIAN. We
|
||||
* should try to cope with both possibilities here.
|
||||
*/
|
||||
|
||||
# if defined(__BYTE_ORDER) || defined(BYTE_ORDER)
|
||||
# if defined(__BYTE_ORDER)
|
||||
# if __BYTE_ORDER == __LITTLE_ENDIAN
|
||||
# define IS_LITTLE_ENDIAN 1
|
||||
# undef IS_BIG_ENDIAN
|
||||
# elif __BYTE_ORDER == __BIG_ENDIAN
|
||||
# undef IS_LITTLE_ENDIAN
|
||||
# define IS_BIG_ENDIAN 1
|
||||
# endif
|
||||
# endif
|
||||
# if defined(BYTE_ORDER)
|
||||
# if BYTE_ORDER == LITTLE_ENDIAN
|
||||
# define IS_LITTLE_ENDIAN 1
|
||||
# undef IS_BIG_ENDIAN
|
||||
# elif BYTE_ORDER == BIG_ENDIAN
|
||||
# undef IS_LITTLE_ENDIAN
|
||||
# define IS_BIG_ENDIAN 1
|
||||
# endif
|
||||
# endif
|
||||
# else /* !defined(__BYTE_ORDER) */
|
||||
# error "endian.h does not define __BYTE_ORDER nor BYTE_ORDER. Cannot determine endianness."
|
||||
# endif
|
||||
|
||||
/* BSDs */
|
||||
#elif defined(JS_HAVE_MACHINE_ENDIAN_H)
|
||||
# include <sys/types.h>
|
||||
# include <machine/endian.h>
|
||||
|
||||
# if defined(_BYTE_ORDER)
|
||||
# if _BYTE_ORDER == _LITTLE_ENDIAN
|
||||
# define IS_LITTLE_ENDIAN 1
|
||||
# undef IS_BIG_ENDIAN
|
||||
# elif _BYTE_ORDER == _BIG_ENDIAN
|
||||
# undef IS_LITTLE_ENDIAN
|
||||
# define IS_BIG_ENDIAN 1
|
||||
# endif
|
||||
# else /* !defined(_BYTE_ORDER) */
|
||||
# error "machine/endian.h does not define _BYTE_ORDER. Cannot determine endianness."
|
||||
# endif
|
||||
|
||||
#elif defined(JS_HAVE_SYS_ISA_DEFS_H)
|
||||
# include <sys/isa_defs.h>
|
||||
|
||||
# if defined(_BIG_ENDIAN)
|
||||
# undef IS_LITTLE_ENDIAN
|
||||
# define IS_BIG_ENDIAN 1
|
||||
# elif defined(_LITTLE_ENDIAN)
|
||||
# define IS_LITTLE_ENDIAN 1
|
||||
# undef IS_BIG_ENDIAN
|
||||
# else /* !defined(_LITTLE_ENDIAN) */
|
||||
# error "sys/isa_defs.h does not define _BIG_ENDIAN or _LITTLE_ENDIAN. Cannot determine endianness."
|
||||
# endif
|
||||
# if !defined(JS_STACK_GROWTH_DIRECTION)
|
||||
# if defined(_STACK_GROWS_UPWARD)
|
||||
# define JS_STACK_GROWTH_DIRECTION (1)
|
||||
# elif defined(_STACK_GROWS_DOWNWARD)
|
||||
# define JS_STACK_GROWTH_DIRECTION (-1)
|
||||
# endif
|
||||
# endif
|
||||
|
||||
#elif defined(__sparc) || defined(__sparc__) || \
|
||||
defined(_POWER) || defined(__hppa) || \
|
||||
defined(_MIPSEB) || defined(_BIG_ENDIAN)
|
||||
/* IA64 running HP-UX will have _BIG_ENDIAN defined.
|
||||
* IA64 running Linux will have endian.h and be handled above.
|
||||
*/
|
||||
# undef IS_LITTLE_ENDIAN
|
||||
# define IS_BIG_ENDIAN 1
|
||||
|
||||
#else /* !defined(__sparc) && !defined(__sparc__) && ... */
|
||||
# error "Cannot determine endianness of your platform. Please add support to jscpucfg.h."
|
||||
#endif
|
||||
#include "mozilla/EndianUtils.h"
|
||||
|
||||
#ifndef JS_STACK_GROWTH_DIRECTION
|
||||
# ifdef __hppa
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
using namespace js;
|
||||
|
||||
#ifdef IS_LITTLE_ENDIAN
|
||||
#if MOZ_LITTLE_ENDIAN
|
||||
#define IEEE_8087
|
||||
#else
|
||||
#define IEEE_MC68k
|
||||
|
|
|
@ -999,21 +999,6 @@ else
|
|||
AC_MSG_RESULT(no)
|
||||
fi
|
||||
|
||||
MOZ_CHECK_HEADERS(endian.h)
|
||||
if test "$ac_cv_header_endian_h" = yes; then
|
||||
AC_DEFINE(JS_HAVE_ENDIAN_H)
|
||||
fi
|
||||
|
||||
MOZ_CHECK_HEADERS([machine/endian.h],[],[],[#include <sys/types.h>])
|
||||
if test "$ac_cv_header_machine_endian_h" = yes; then
|
||||
AC_DEFINE(JS_HAVE_MACHINE_ENDIAN_H)
|
||||
fi
|
||||
|
||||
MOZ_CHECK_HEADERS(sys/isa_defs.h)
|
||||
if test "$ac_cv_header_sys_isa_defs_h" = yes; then
|
||||
AC_DEFINE(JS_HAVE_SYS_ISA_DEFS_H)
|
||||
fi
|
||||
|
||||
AC_LANG_CPLUSPLUS
|
||||
|
||||
MOZ_CXX11
|
||||
|
|
|
@ -1784,7 +1784,7 @@ DataViewObject::getDataPointer(JSContext* cx, Handle<DataViewObject*> obj, doubl
|
|||
static inline bool
|
||||
needToSwapBytes(bool littleEndian)
|
||||
{
|
||||
#if IS_LITTLE_ENDIAN
|
||||
#if MOZ_LITTLE_ENDIAN
|
||||
return !littleEndian;
|
||||
#else
|
||||
return littleEndian;
|
||||
|
|
|
@ -397,6 +397,7 @@ BufferList<AllocPolicy>::MoveFallible(bool* aSuccess, OtherAllocPolicy aAP)
|
|||
|
||||
if (!toAdvance || !result.mSegments.append(typename BufferList<OtherAllocPolicy>::Segment(iter.mData, toAdvance, toAdvance))) {
|
||||
*aSuccess = false;
|
||||
result.mSegments.clear();
|
||||
return result;
|
||||
}
|
||||
iter.Advance(*this, toAdvance);
|
||||
|
|
|
@ -51,11 +51,6 @@ public class AppConstants {
|
|||
* If MIN_SDK_VERSION is greater than or equal to the number, there
|
||||
* is no need to do the runtime check.
|
||||
*/
|
||||
public static final boolean feature10Plus = MIN_SDK_VERSION >= 10 || (MAX_SDK_VERSION >= 10 && Build.VERSION.SDK_INT >= 10);
|
||||
public static final boolean feature11Plus = MIN_SDK_VERSION >= 11 || (MAX_SDK_VERSION >= 11 && Build.VERSION.SDK_INT >= 11);
|
||||
public static final boolean feature12Plus = MIN_SDK_VERSION >= 12 || (MAX_SDK_VERSION >= 12 && Build.VERSION.SDK_INT >= 12);
|
||||
public static final boolean feature14Plus = MIN_SDK_VERSION >= 14 || (MAX_SDK_VERSION >= 14 && Build.VERSION.SDK_INT >= 14);
|
||||
public static final boolean feature15Plus = MIN_SDK_VERSION >= 15 || (MAX_SDK_VERSION >= 15 && Build.VERSION.SDK_INT >= 15);
|
||||
public static final boolean feature16Plus = MIN_SDK_VERSION >= 16 || (MAX_SDK_VERSION >= 16 && Build.VERSION.SDK_INT >= 16);
|
||||
public static final boolean feature17Plus = MIN_SDK_VERSION >= 17 || (MAX_SDK_VERSION >= 17 && Build.VERSION.SDK_INT >= 17);
|
||||
public static final boolean feature19Plus = MIN_SDK_VERSION >= 19 || (MAX_SDK_VERSION >= 19 && Build.VERSION.SDK_INT >= 19);
|
||||
|
@ -238,7 +233,7 @@ public class AppConstants {
|
|||
// it if this APK doesn't include API14 support.
|
||||
public static final boolean MOZ_ANDROID_BEAM =
|
||||
//#ifdef MOZ_ANDROID_BEAM
|
||||
Versions.feature14Plus;
|
||||
true;
|
||||
//#else
|
||||
false;
|
||||
//#endif
|
||||
|
@ -285,7 +280,7 @@ public class AppConstants {
|
|||
|
||||
public static final boolean ANDROID_DOWNLOADS_INTEGRATION =
|
||||
//#ifdef MOZ_ANDROID_DOWNLOADS_INTEGRATION
|
||||
AppConstants.Versions.feature12Plus;
|
||||
true;
|
||||
//#else
|
||||
false;
|
||||
//#endif
|
||||
|
|
|
@ -515,7 +515,7 @@ public class BrowserApp extends GeckoApp
|
|||
|
||||
// Check if this was a shortcut. Meta keys exists only on 11+.
|
||||
final Tab tab = Tabs.getInstance().getSelectedTab();
|
||||
if (Versions.feature11Plus && tab != null && event.isCtrlPressed()) {
|
||||
if (tab != null && event.isCtrlPressed()) {
|
||||
switch (keyCode) {
|
||||
case KeyEvent.KEYCODE_LEFT_BRACKET:
|
||||
tab.doBack();
|
||||
|
@ -749,7 +749,8 @@ public class BrowserApp extends GeckoApp
|
|||
"Sanitize:ClearSyncedTabs",
|
||||
"Settings:Show",
|
||||
"Telemetry:Gather",
|
||||
"Updater:Launch");
|
||||
"Updater:Launch",
|
||||
"Website:Metadata");
|
||||
|
||||
final GeckoProfile profile = getProfile();
|
||||
|
||||
|
@ -1490,7 +1491,8 @@ public class BrowserApp extends GeckoApp
|
|||
"Sanitize:ClearSyncedTabs",
|
||||
"Settings:Show",
|
||||
"Telemetry:Gather",
|
||||
"Updater:Launch");
|
||||
"Updater:Launch",
|
||||
"Website:Metadata");
|
||||
|
||||
if (AppConstants.MOZ_ANDROID_BEAM) {
|
||||
NfcAdapter nfc = NfcAdapter.getDefaultAdapter(this);
|
||||
|
@ -1930,6 +1932,13 @@ public class BrowserApp extends GeckoApp
|
|||
}
|
||||
break;
|
||||
|
||||
case "Website:Metadata":
|
||||
final NativeJSObject metadata = message.getObject("metadata");
|
||||
final String location = message.getString("location");
|
||||
|
||||
// TODO: Store metadata (Bug 1301717)
|
||||
break;
|
||||
|
||||
default:
|
||||
super.handleMessage(event, message, callback);
|
||||
break;
|
||||
|
@ -2761,10 +2770,6 @@ public class BrowserApp extends GeckoApp
|
|||
mMenu.clear();
|
||||
onCreateOptionsMenu(mMenu);
|
||||
}
|
||||
|
||||
if (!Versions.feature14Plus) {
|
||||
conditionallyNotifyEOL();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -3180,12 +3185,8 @@ public class BrowserApp extends GeckoApp
|
|||
destination = menu;
|
||||
} else if (info.parent == GECKO_TOOLS_MENU) {
|
||||
// The tools menu only exists in our -v11 resources.
|
||||
if (Versions.feature11Plus) {
|
||||
final MenuItem tools = menu.findItem(R.id.tools);
|
||||
destination = tools != null ? tools.getSubMenu() : menu;
|
||||
} else {
|
||||
destination = menu;
|
||||
}
|
||||
final MenuItem tools = menu.findItem(R.id.tools);
|
||||
destination = tools != null ? tools.getSubMenu() : menu;
|
||||
} else {
|
||||
final MenuItem parent = menu.findItem(info.parent);
|
||||
if (parent == null) {
|
||||
|
@ -3310,13 +3311,11 @@ public class BrowserApp extends GeckoApp
|
|||
}
|
||||
|
||||
// Action providers are available only ICS+.
|
||||
if (Versions.feature14Plus) {
|
||||
GeckoMenuItem share = (GeckoMenuItem) mMenu.findItem(R.id.share);
|
||||
GeckoMenuItem share = (GeckoMenuItem) mMenu.findItem(R.id.share);
|
||||
|
||||
GeckoActionProvider provider = GeckoActionProvider.getForType(GeckoActionProvider.DEFAULT_MIME_TYPE, this);
|
||||
GeckoActionProvider provider = GeckoActionProvider.getForType(GeckoActionProvider.DEFAULT_MIME_TYPE, this);
|
||||
|
||||
share.setActionProvider(provider);
|
||||
}
|
||||
share.setActionProvider(provider);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -3445,10 +3444,7 @@ public class BrowserApp extends GeckoApp
|
|||
|
||||
// NOTE: Use MenuUtils.safeSetEnabled because some actions might
|
||||
// be on the BrowserToolbar context menu.
|
||||
if (Versions.feature11Plus) {
|
||||
// There is no page menu prior to v11 resources.
|
||||
MenuUtils.safeSetEnabled(aMenu, R.id.page, false);
|
||||
}
|
||||
MenuUtils.safeSetEnabled(aMenu, R.id.page, false);
|
||||
MenuUtils.safeSetEnabled(aMenu, R.id.subscribe, false);
|
||||
MenuUtils.safeSetEnabled(aMenu, R.id.add_search_engine, false);
|
||||
MenuUtils.safeSetEnabled(aMenu, R.id.add_to_launcher, false);
|
||||
|
@ -3469,10 +3465,8 @@ public class BrowserApp extends GeckoApp
|
|||
bookmark.setChecked(tab.isBookmark());
|
||||
bookmark.setTitle(resolveBookmarkTitleID(tab.isBookmark()));
|
||||
|
||||
if (Versions.feature11Plus) {
|
||||
// We don't use icons on GB builds so not resolving icons might conserve resources.
|
||||
bookmark.setIcon(resolveBookmarkIconID(tab.isBookmark()));
|
||||
}
|
||||
// We don't use icons on GB builds so not resolving icons might conserve resources.
|
||||
bookmark.setIcon(resolveBookmarkIconID(tab.isBookmark()));
|
||||
|
||||
back.setEnabled(tab.canDoBack());
|
||||
forward.setEnabled(tab.canDoForward());
|
||||
|
@ -3526,63 +3520,58 @@ public class BrowserApp extends GeckoApp
|
|||
|
||||
// NOTE: Use MenuUtils.safeSetEnabled because some actions might
|
||||
// be on the BrowserToolbar context menu.
|
||||
if (Versions.feature11Plus) {
|
||||
MenuUtils.safeSetEnabled(aMenu, R.id.page, !isAboutHome(tab));
|
||||
}
|
||||
MenuUtils.safeSetEnabled(aMenu, R.id.page, !isAboutHome(tab));
|
||||
MenuUtils.safeSetEnabled(aMenu, R.id.subscribe, tab.hasFeeds());
|
||||
MenuUtils.safeSetEnabled(aMenu, R.id.add_search_engine, tab.hasOpenSearch());
|
||||
MenuUtils.safeSetEnabled(aMenu, R.id.add_to_launcher, !isAboutHome(tab));
|
||||
|
||||
// Action providers are available only ICS+.
|
||||
if (Versions.feature14Plus) {
|
||||
// This provider also applies to the quick share menu item.
|
||||
final GeckoActionProvider provider = ((GeckoMenuItem) share).getGeckoActionProvider();
|
||||
if (provider != null) {
|
||||
Intent shareIntent = provider.getIntent();
|
||||
// This provider also applies to the quick share menu item.
|
||||
final GeckoActionProvider provider = ((GeckoMenuItem) share).getGeckoActionProvider();
|
||||
if (provider != null) {
|
||||
Intent shareIntent = provider.getIntent();
|
||||
|
||||
// For efficiency, the provider's intent is only set once
|
||||
if (shareIntent == null) {
|
||||
shareIntent = new Intent(Intent.ACTION_SEND);
|
||||
shareIntent.setType("text/plain");
|
||||
provider.setIntent(shareIntent);
|
||||
}
|
||||
// For efficiency, the provider's intent is only set once
|
||||
if (shareIntent == null) {
|
||||
shareIntent = new Intent(Intent.ACTION_SEND);
|
||||
shareIntent.setType("text/plain");
|
||||
provider.setIntent(shareIntent);
|
||||
}
|
||||
|
||||
// Replace the existing intent's extras
|
||||
shareIntent.putExtra(Intent.EXTRA_TEXT, url);
|
||||
shareIntent.putExtra(Intent.EXTRA_SUBJECT, tab.getDisplayTitle());
|
||||
shareIntent.putExtra(Intent.EXTRA_TITLE, tab.getDisplayTitle());
|
||||
shareIntent.putExtra(ShareDialog.INTENT_EXTRA_DEVICES_ONLY, true);
|
||||
// Replace the existing intent's extras
|
||||
shareIntent.putExtra(Intent.EXTRA_TEXT, url);
|
||||
shareIntent.putExtra(Intent.EXTRA_SUBJECT, tab.getDisplayTitle());
|
||||
shareIntent.putExtra(Intent.EXTRA_TITLE, tab.getDisplayTitle());
|
||||
shareIntent.putExtra(ShareDialog.INTENT_EXTRA_DEVICES_ONLY, true);
|
||||
|
||||
// Clear the existing thumbnail extras so we don't share an old thumbnail.
|
||||
shareIntent.removeExtra("share_screenshot_uri");
|
||||
// Clear the existing thumbnail extras so we don't share an old thumbnail.
|
||||
shareIntent.removeExtra("share_screenshot_uri");
|
||||
|
||||
// Include the thumbnail of the page being shared.
|
||||
BitmapDrawable drawable = tab.getThumbnail();
|
||||
if (drawable != null) {
|
||||
Bitmap thumbnail = drawable.getBitmap();
|
||||
// Include the thumbnail of the page being shared.
|
||||
BitmapDrawable drawable = tab.getThumbnail();
|
||||
if (drawable != null) {
|
||||
Bitmap thumbnail = drawable.getBitmap();
|
||||
|
||||
// Kobo uses a custom intent extra for sharing thumbnails.
|
||||
if (Build.MANUFACTURER.equals("Kobo") && thumbnail != null) {
|
||||
File cacheDir = getExternalCacheDir();
|
||||
// Kobo uses a custom intent extra for sharing thumbnails.
|
||||
if (Build.MANUFACTURER.equals("Kobo") && thumbnail != null) {
|
||||
File cacheDir = getExternalCacheDir();
|
||||
|
||||
if (cacheDir != null) {
|
||||
File outFile = new File(cacheDir, "thumbnail.png");
|
||||
if (cacheDir != null) {
|
||||
File outFile = new File(cacheDir, "thumbnail.png");
|
||||
|
||||
try {
|
||||
final java.io.FileOutputStream out = new java.io.FileOutputStream(outFile);
|
||||
try {
|
||||
final java.io.FileOutputStream out = new java.io.FileOutputStream(outFile);
|
||||
thumbnail.compress(Bitmap.CompressFormat.PNG, 90, out);
|
||||
} finally {
|
||||
try {
|
||||
thumbnail.compress(Bitmap.CompressFormat.PNG, 90, out);
|
||||
} finally {
|
||||
try {
|
||||
out.close();
|
||||
} catch (final IOException e) { /* Nothing to do here. */ }
|
||||
}
|
||||
} catch (FileNotFoundException e) {
|
||||
Log.e(LOGTAG, "File not found", e);
|
||||
out.close();
|
||||
} catch (final IOException e) { /* Nothing to do here. */ }
|
||||
}
|
||||
|
||||
shareIntent.putExtra("share_screenshot_uri", Uri.parse(outFile.getPath()));
|
||||
} catch (FileNotFoundException e) {
|
||||
Log.e(LOGTAG, "File not found", e);
|
||||
}
|
||||
|
||||
shareIntent.putExtra("share_screenshot_uri", Uri.parse(outFile.getPath()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3671,18 +3660,12 @@ public class BrowserApp extends GeckoApp
|
|||
Telemetry.sendUIEvent(TelemetryContract.Event.UNSAVE, TelemetryContract.Method.MENU, extra);
|
||||
tab.removeBookmark();
|
||||
item.setTitle(resolveBookmarkTitleID(false));
|
||||
if (Versions.feature11Plus) {
|
||||
// We don't use icons on GB builds so not resolving icons might conserve resources.
|
||||
item.setIcon(resolveBookmarkIconID(false));
|
||||
}
|
||||
item.setIcon(resolveBookmarkIconID(false));
|
||||
} else {
|
||||
Telemetry.sendUIEvent(TelemetryContract.Event.SAVE, TelemetryContract.Method.MENU, extra);
|
||||
tab.addBookmark();
|
||||
item.setTitle(resolveBookmarkTitleID(true));
|
||||
if (Versions.feature11Plus) {
|
||||
// We don't use icons on GB builds so not resolving icons might conserve resources.
|
||||
item.setIcon(resolveBookmarkIconID(true));
|
||||
}
|
||||
item.setIcon(resolveBookmarkIconID(true));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
|
|
@ -323,12 +323,7 @@ public class DoorHangerPopup extends AnchoredPopup
|
|||
return;
|
||||
}
|
||||
|
||||
// Make the popup focusable for accessibility. This gets done here
|
||||
// so the node can be accessibility focused, but on pre-ICS devices this
|
||||
// causes crashes, so it is done after the popup is shown.
|
||||
if (Versions.feature14Plus) {
|
||||
setFocusable(true);
|
||||
}
|
||||
setFocusable(true);
|
||||
|
||||
show();
|
||||
}
|
||||
|
|
|
@ -385,9 +385,7 @@ public abstract class GeckoApp
|
|||
|
||||
onPrepareOptionsMenu(mMenu);
|
||||
|
||||
if (Versions.feature11Plus) {
|
||||
super.invalidateOptionsMenu();
|
||||
}
|
||||
super.invalidateOptionsMenu();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -852,22 +850,10 @@ public abstract class GeckoApp
|
|||
dialog.getListView().setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
|
||||
if (Versions.feature11Plus) {
|
||||
if (listView.getCheckedItemCount() == 0) {
|
||||
clearButton.setEnabled(false);
|
||||
} else {
|
||||
clearButton.setEnabled(true);
|
||||
}
|
||||
} else {
|
||||
final SparseBooleanArray items = listView.getCheckedItemPositions();
|
||||
for (int j = 0; j < items.size(); j++) {
|
||||
if (items.valueAt(j) == true) {
|
||||
clearButton.setEnabled(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (listView.getCheckedItemCount() == 0) {
|
||||
clearButton.setEnabled(false);
|
||||
} else {
|
||||
clearButton.setEnabled(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -1890,9 +1876,7 @@ public abstract class GeckoApp
|
|||
|
||||
// Try to make it fully transparent.
|
||||
if (mCameraView instanceof SurfaceView) {
|
||||
if (Versions.feature11Plus) {
|
||||
mCameraView.setAlpha(0.0f);
|
||||
}
|
||||
mCameraView.setAlpha(0.0f);
|
||||
ViewGroup mCameraLayout = (ViewGroup) findViewById(R.id.camera_layout);
|
||||
// Some phones (eg. nexus S) need at least a 8x16 preview size
|
||||
mCameraLayout.addView(mCameraView,
|
||||
|
@ -2129,13 +2113,6 @@ public abstract class GeckoApp
|
|||
refreshChrome();
|
||||
}
|
||||
|
||||
if (!Versions.feature14Plus) {
|
||||
// Update accessibility settings in case it has been changed by the
|
||||
// user. On API14+, this is handled in LayerView by registering an
|
||||
// accessibility state change listener.
|
||||
GeckoAccessibility.updateAccessibilitySettings(this);
|
||||
}
|
||||
|
||||
if (mAppStateListeners != null) {
|
||||
for (GeckoAppShell.AppStateListener listener : mAppStateListeners) {
|
||||
listener.onResume();
|
||||
|
|
|
@ -372,10 +372,6 @@ public final class IntentHelper implements GeckoEventListener,
|
|||
// We create a separate method to better encapsulate the @TargetApi use.
|
||||
@TargetApi(15)
|
||||
private static void nullIntentSelector(final Intent intent) {
|
||||
if (!AppConstants.Versions.feature15Plus) {
|
||||
return;
|
||||
}
|
||||
|
||||
intent.setSelector(null);
|
||||
}
|
||||
|
||||
|
|
|
@ -56,10 +56,6 @@ public class ScreenshotObserver {
|
|||
* have been granted by the user. Calling this method will not prompt for permissions.
|
||||
*/
|
||||
public void start() {
|
||||
if (!Versions.feature14Plus) {
|
||||
return;
|
||||
}
|
||||
|
||||
Permissions.from(context)
|
||||
.withPermissions(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
.doNotPrompt()
|
||||
|
@ -83,10 +79,6 @@ public class ScreenshotObserver {
|
|||
}
|
||||
|
||||
public void stop() {
|
||||
if (!Versions.feature14Plus) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mediaObserver == null) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -866,8 +866,7 @@ public class Tabs implements GeckoEventListener {
|
|||
needsNewTab = (flags & LOADURL_NEW_TAB) != 0;
|
||||
} else {
|
||||
// If you modify this code, be careful that intent != null.
|
||||
final boolean extraCreateNewTab = (Versions.feature12Plus) ?
|
||||
intent.getBooleanExtra(Browser.EXTRA_CREATE_NEW_TAB, false) : false;
|
||||
final boolean extraCreateNewTab = intent.getBooleanExtra(Browser.EXTRA_CREATE_NEW_TAB, false);
|
||||
final Tab applicationTab = getTabForApplicationId(applicationId);
|
||||
if (applicationTab == null || extraCreateNewTab) {
|
||||
needsNewTab = true;
|
||||
|
|
|
@ -173,7 +173,7 @@ public class PropertyAnimator implements Runnable {
|
|||
// in the current view tree. OnPreDrawListener seems broken
|
||||
// on pre-Honeycomb devices, start animation immediatelly
|
||||
// in this case.
|
||||
if (Versions.feature11Plus && treeObserver != null && treeObserver.isAlive()) {
|
||||
if (treeObserver != null && treeObserver.isAlive()) {
|
||||
treeObserver.addOnPreDrawListener(preDrawListener);
|
||||
} else {
|
||||
mFramePoster.postFirstAnimationFrame();
|
||||
|
|
|
@ -95,7 +95,7 @@ public abstract class AbstractTransactionalProvider extends ContentProvider {
|
|||
*/
|
||||
@SuppressWarnings("static-method")
|
||||
protected boolean shouldUseTransactions() {
|
||||
return Versions.feature11Plus;
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean isInBatch() {
|
||||
|
|
|
@ -409,21 +409,8 @@ public class DBUtils {
|
|||
statement.execute();
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (AppConstants.Versions.feature11Plus) {
|
||||
// This is a separate method so we can annotate it with @TargetApi.
|
||||
return executeStatementReturningChangedRows(statement);
|
||||
} else {
|
||||
statement.execute();
|
||||
final Cursor cursor = db.rawQuery("SELECT changes()", null);
|
||||
try {
|
||||
cursor.moveToFirst();
|
||||
return cursor.getInt(0);
|
||||
} finally {
|
||||
cursor.close();
|
||||
}
|
||||
|
||||
}
|
||||
// This is a separate method so we can annotate it with @TargetApi.
|
||||
return executeStatementReturningChangedRows(statement);
|
||||
} finally {
|
||||
statement.close();
|
||||
}
|
||||
|
|
|
@ -98,10 +98,7 @@ public abstract class SQLiteBridgeContentProvider extends ContentProvider {
|
|||
}
|
||||
mDatabasePerProfile = null;
|
||||
}
|
||||
|
||||
if (AppConstants.Versions.feature11Plus) {
|
||||
super.shutdown();
|
||||
}
|
||||
super.shutdown();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -213,7 +213,7 @@ public class HomePager extends ViewPager implements HomeScreen {
|
|||
}
|
||||
|
||||
// Only animate on post-HC devices, when a non-null animator is given
|
||||
final boolean shouldAnimate = Versions.feature11Plus && animator != null;
|
||||
final boolean shouldAnimate = animator != null;
|
||||
|
||||
final HomeAdapter adapter = new HomeAdapter(mContext, fm);
|
||||
adapter.setOnAddPanelListener(mAddPanelListener);
|
||||
|
|
|
@ -357,13 +357,8 @@ public class LightweightTheme implements GeckoEventListener {
|
|||
ViewParent parent;
|
||||
View curView = view;
|
||||
do {
|
||||
if (Versions.feature11Plus) {
|
||||
offsetX += (int) curView.getTranslationX() - curView.getScrollX();
|
||||
offsetY += (int) curView.getTranslationY() - curView.getScrollY();
|
||||
} else {
|
||||
offsetX -= curView.getScrollX();
|
||||
offsetY -= curView.getScrollY();
|
||||
}
|
||||
offsetX += (int) curView.getTranslationX() - curView.getScrollX();
|
||||
offsetY += (int) curView.getTranslationY() - curView.getScrollY();
|
||||
|
||||
parent = curView.getParent();
|
||||
|
||||
|
|
|
@ -152,9 +152,7 @@ public class GeckoMenuInflater extends MenuInflater {
|
|||
.setCheckable(item.checkable)
|
||||
.setIcon(item.iconRes);
|
||||
|
||||
if (Versions.feature11Plus) {
|
||||
menuItem.setShowAsAction(item.showAsAction);
|
||||
}
|
||||
menuItem.setShowAsAction(item.showAsAction);
|
||||
|
||||
if (geckoItem != null) {
|
||||
// We don't need to allow presenter updates during inflation,
|
||||
|
|
|
@ -29,9 +29,7 @@ public class MenuPanel extends LinearLayout {
|
|||
|
||||
@Override
|
||||
public boolean dispatchPopulateAccessibilityEvent (AccessibilityEvent event) {
|
||||
if (Versions.feature14Plus) {
|
||||
onPopulateAccessibilityEvent(event);
|
||||
}
|
||||
onPopulateAccessibilityEvent(event);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -71,13 +71,7 @@ public class SendTabDeviceListArrayAdapter extends ArrayAdapter<RemoteClient> {
|
|||
clear();
|
||||
|
||||
setNotifyOnChange(false); // So we don't notify for each add.
|
||||
if (AppConstants.Versions.feature11Plus) {
|
||||
addAll(records);
|
||||
} else {
|
||||
for (RemoteClient record : records) {
|
||||
add(record);
|
||||
}
|
||||
}
|
||||
addAll(records);
|
||||
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
|
|
@ -166,7 +166,7 @@ public class GeckoPreferenceFragment extends PreferenceFragment {
|
|||
}
|
||||
|
||||
final GeckoPreferences activity = (GeckoPreferences) getActivity();
|
||||
if (Versions.feature11Plus && activity.isMultiPane()) {
|
||||
if (activity.isMultiPane()) {
|
||||
// In a multi-pane activity, the title is "Settings", and the action
|
||||
// bar is along the top of the screen. We don't want to change those.
|
||||
activity.showBreadCrumbs(newTitle, newTitle);
|
||||
|
@ -237,8 +237,7 @@ public class GeckoPreferenceFragment extends PreferenceFragment {
|
|||
// The resource was invalid. Use the default resource.
|
||||
Log.e(LOGTAG, "Failed to find resource: " + resourceName + ". Displaying default settings.");
|
||||
|
||||
boolean isMultiPane = Versions.feature11Plus &&
|
||||
((GeckoPreferences) activity).isMultiPane();
|
||||
boolean isMultiPane = ((GeckoPreferences) activity).isMultiPane();
|
||||
resid = isMultiPane ? R.xml.preferences_general_tablet : R.xml.preferences;
|
||||
}
|
||||
|
||||
|
|
|
@ -222,13 +222,11 @@ OnSharedPreferenceChangeListener
|
|||
}
|
||||
}
|
||||
private void updateActionBarTitle(int title) {
|
||||
if (Versions.feature14Plus) {
|
||||
final String newTitle = getString(title);
|
||||
if (newTitle != null) {
|
||||
Log.v(LOGTAG, "Setting action bar title to " + newTitle);
|
||||
final String newTitle = getString(title);
|
||||
if (newTitle != null) {
|
||||
Log.v(LOGTAG, "Setting action bar title to " + newTitle);
|
||||
|
||||
setTitle(newTitle);
|
||||
}
|
||||
setTitle(newTitle);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -341,11 +339,9 @@ OnSharedPreferenceChangeListener
|
|||
// the correct Fragment resource.
|
||||
// Note: this seems to only be required for non-multipane devices, multipane
|
||||
// manages to automatically select the correct fragments.
|
||||
if (Versions.feature11Plus) {
|
||||
if (!getIntent().hasExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT)) {
|
||||
// Set up the default fragment if there is no explicit fragment to show.
|
||||
setupTopLevelFragmentIntent();
|
||||
}
|
||||
if (!getIntent().hasExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT)) {
|
||||
// Set up the default fragment if there is no explicit fragment to show.
|
||||
setupTopLevelFragmentIntent();
|
||||
}
|
||||
|
||||
// We must call this before setTitle to avoid crashes. Most devices don't seem to care
|
||||
|
@ -354,7 +350,7 @@ OnSharedPreferenceChangeListener
|
|||
// likely other strange devices (other Asus devices, some Samsungs) could do the same.
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
if (Versions.feature11Plus && onIsMultiPane()) {
|
||||
if (onIsMultiPane()) {
|
||||
// So that Android doesn't put the fragment title (or nothing at
|
||||
// all) in the action bar.
|
||||
updateActionBarTitle(R.string.settings_title);
|
||||
|
@ -521,11 +517,9 @@ OnSharedPreferenceChangeListener
|
|||
@Override
|
||||
public void onPause() {
|
||||
// Symmetric with onResume.
|
||||
if (Versions.feature11Plus) {
|
||||
if (isMultiPane()) {
|
||||
SharedPreferences prefs = GeckoSharedPrefs.forApp(this);
|
||||
prefs.unregisterOnSharedPreferenceChangeListener(this);
|
||||
}
|
||||
if (isMultiPane()) {
|
||||
SharedPreferences prefs = GeckoSharedPrefs.forApp(this);
|
||||
prefs.unregisterOnSharedPreferenceChangeListener(this);
|
||||
}
|
||||
|
||||
super.onPause();
|
||||
|
@ -543,14 +537,12 @@ OnSharedPreferenceChangeListener
|
|||
((GeckoApplication) getApplication()).onActivityResume(this);
|
||||
}
|
||||
|
||||
if (Versions.feature11Plus) {
|
||||
// Watch prefs, otherwise we don't reliably get told when they change.
|
||||
// See documentation for onSharedPreferenceChange for more.
|
||||
// Inexplicably only needed on tablet.
|
||||
if (isMultiPane()) {
|
||||
SharedPreferences prefs = GeckoSharedPrefs.forApp(this);
|
||||
prefs.registerOnSharedPreferenceChangeListener(this);
|
||||
}
|
||||
// Watch prefs, otherwise we don't reliably get told when they change.
|
||||
// See documentation for onSharedPreferenceChange for more.
|
||||
// Inexplicably only needed on tablet.
|
||||
if (isMultiPane()) {
|
||||
SharedPreferences prefs = GeckoSharedPrefs.forApp(this);
|
||||
prefs.registerOnSharedPreferenceChangeListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -52,13 +52,10 @@ class SyncPreference extends Preference {
|
|||
public void run() {
|
||||
setTitle(R.string.pref_sync);
|
||||
setSummary(R.string.pref_sync_summary);
|
||||
if (AppConstants.Versions.feature11Plus) {
|
||||
// Cancel any pending task.
|
||||
Picasso.with(mContext).cancelRequest(profileAvatarTarget);
|
||||
// Clear previously set icon.
|
||||
setIcon(R.drawable.sync_avatar_default);
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
return;
|
||||
|
@ -73,11 +70,6 @@ class SyncPreference extends Preference {
|
|||
}
|
||||
});
|
||||
|
||||
// Updating icons from Java is not supported prior to API 11.
|
||||
if (!AppConstants.Versions.feature11Plus) {
|
||||
return;
|
||||
}
|
||||
|
||||
final ExtendedJSONObject profileJSON = fxAccount.getProfileJSON();
|
||||
if (profileJSON == null) {
|
||||
return;
|
||||
|
|
|
@ -85,7 +85,7 @@ public class IconGridInput extends PromptInput implements OnItemClickListener {
|
|||
// Despite what the docs say, setItemChecked was not moved into the AbsListView class until sometime between
|
||||
// Android 2.3.7 and Android 4.0.3. For other versions the item won't be visually highlighted, BUT we really only
|
||||
// mSelected will still be set so that we default to its behavior.
|
||||
if (Versions.feature11Plus && mSelected > -1) {
|
||||
if (mSelected > -1) {
|
||||
view.setItemChecked(mSelected, true);
|
||||
}
|
||||
|
||||
|
|
|
@ -38,7 +38,7 @@ public class TabQueueHelper {
|
|||
private static final String LOGTAG = "Gecko" + TabQueueHelper.class.getSimpleName();
|
||||
|
||||
// Disable Tab Queue for API level 10 (GB) - Bug 1206055
|
||||
public static final boolean TAB_QUEUE_ENABLED = AppConstants.Versions.feature11Plus;
|
||||
public static final boolean TAB_QUEUE_ENABLED = true;
|
||||
|
||||
public static final String FILE_NAME = "tab_queue_url_list.json";
|
||||
public static final String LOAD_URLS_ACTION = "TAB_QUEUE_LOAD_URLS_ACTION";
|
||||
|
|
|
@ -294,9 +294,7 @@ class TabsGridLayout extends GridView
|
|||
((TabsLayoutItemView) tabView).setChecked(checked);
|
||||
}
|
||||
// setItemChecked doesn't exist until API 11, despite what the API docs say!
|
||||
if (AppConstants.Versions.feature11Plus) {
|
||||
setItemChecked(i, checked);
|
||||
}
|
||||
setItemChecked(i, checked);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -191,9 +191,7 @@ public abstract class BrowserToolbar extends ThemedRelativeLayout
|
|||
|
||||
tabsButton = (ThemedImageButton) findViewById(R.id.tabs);
|
||||
tabsCounter = (TabCounter) findViewById(R.id.tabs_counter);
|
||||
if (Versions.feature11Plus) {
|
||||
tabsCounter.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
|
||||
}
|
||||
tabsCounter.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
|
||||
|
||||
menuButton = (ThemedFrameLayout) findViewById(R.id.menu);
|
||||
menuIcon = (ThemedImageView) findViewById(R.id.menu_icon);
|
||||
|
|
|
@ -48,20 +48,8 @@ class CanvasDelegate {
|
|||
if (path != null && !path.isEmpty()) {
|
||||
// ICS added double-buffering, which made it easier for drawing the Path directly over the DST.
|
||||
// In pre-ICS, drawPath() doesn't seem to use ARGB_8888 mode for performance, hence transparency is not preserved.
|
||||
if (Versions.feature14Plus) {
|
||||
mPaint.setXfermode(mMode);
|
||||
canvas.drawPath(path, mPaint);
|
||||
} else {
|
||||
// Allocate a bitmap and draw the masking/clipping path.
|
||||
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
|
||||
(new Canvas(bitmap)).drawPath(path, mPaint);
|
||||
|
||||
mPaint.setXfermode(mMode);
|
||||
canvas.drawBitmap(bitmap, 0, 0, mPaint);
|
||||
bitmap.recycle();
|
||||
|
||||
mPaint.setXfermode(null);
|
||||
}
|
||||
mPaint.setXfermode(mMode);
|
||||
canvas.drawPath(path, mPaint);
|
||||
}
|
||||
|
||||
// Restore the canvas.
|
||||
|
|
|
@ -228,11 +228,9 @@ public class SiteIdentityPopup extends AnchoredPopup implements GeckoEventListen
|
|||
} else {
|
||||
password = login.getString("password");
|
||||
}
|
||||
if (AppConstants.Versions.feature11Plus) {
|
||||
manager.setPrimaryClip(ClipData.newPlainText("password", password));
|
||||
} else {
|
||||
manager.setText(password);
|
||||
}
|
||||
|
||||
manager.setPrimaryClip(ClipData.newPlainText("password", password));
|
||||
|
||||
SnackbarBuilder.builder(activity)
|
||||
.message(R.string.doorhanger_login_select_toast_copy)
|
||||
.duration(Snackbar.LENGTH_SHORT)
|
||||
|
|
|
@ -618,8 +618,7 @@ public class ToolbarEditText extends CustomEditText
|
|||
}
|
||||
|
||||
if ((keyCode == KeyEvent.KEYCODE_DEL ||
|
||||
(Versions.feature11Plus &&
|
||||
keyCode == KeyEvent.KEYCODE_FORWARD_DEL)) &&
|
||||
(keyCode == KeyEvent.KEYCODE_FORWARD_DEL)) &&
|
||||
removeAutocomplete(getText())) {
|
||||
// Delete autocomplete text when backspacing or forward deleting.
|
||||
return true;
|
||||
|
|
|
@ -101,82 +101,45 @@ public class DateTimePicker extends FrameLayout {
|
|||
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
|
||||
updateInputState();
|
||||
mTempDate.setTimeInMillis(mCurrentDate.getTimeInMillis());
|
||||
final boolean newBehavior = Versions.feature11Plus;
|
||||
if (newBehavior) {
|
||||
if (DEBUG) {
|
||||
Log.d(LOGTAG, "SDK version > 10, using new behavior");
|
||||
}
|
||||
if (DEBUG) {
|
||||
Log.d(LOGTAG, "SDK version > 10, using new behavior");
|
||||
}
|
||||
|
||||
// The native date picker widget on these SDKs increments
|
||||
// the next field when one field reaches the maximum.
|
||||
if (picker == mDaySpinner && mDayEnabled) {
|
||||
int maxDayOfMonth = mTempDate.getActualMaximum(Calendar.DAY_OF_MONTH);
|
||||
int old = mTempDate.get(Calendar.DAY_OF_MONTH);
|
||||
setTempDate(Calendar.DAY_OF_MONTH, old, newVal, 1, maxDayOfMonth);
|
||||
} else if (picker == mMonthSpinner && mMonthEnabled) {
|
||||
int old = mTempDate.get(Calendar.MONTH);
|
||||
setTempDate(Calendar.MONTH, old, newVal, Calendar.JANUARY, Calendar.DECEMBER);
|
||||
} else if (picker == mWeekSpinner) {
|
||||
int old = mTempDate.get(Calendar.WEEK_OF_YEAR);
|
||||
int maxWeekOfYear = mTempDate.getActualMaximum(Calendar.WEEK_OF_YEAR);
|
||||
setTempDate(Calendar.WEEK_OF_YEAR, old, newVal, 0, maxWeekOfYear);
|
||||
} else if (picker == mYearSpinner && mYearEnabled) {
|
||||
int month = mTempDate.get(Calendar.MONTH);
|
||||
mTempDate.set(Calendar.YEAR, newVal);
|
||||
// Changing the year shouldn't change the month. (in case of non-leap year a Feb 29)
|
||||
// change the day instead;
|
||||
if (month != mTempDate.get(Calendar.MONTH)) {
|
||||
mTempDate.set(Calendar.MONTH, month);
|
||||
mTempDate.set(Calendar.DAY_OF_MONTH,
|
||||
mTempDate.getActualMaximum(Calendar.DAY_OF_MONTH));
|
||||
}
|
||||
} else if (picker == mHourSpinner && mHourEnabled) {
|
||||
if (mIs12HourMode) {
|
||||
setTempDate(Calendar.HOUR, oldVal, newVal, 1, 12);
|
||||
} else {
|
||||
setTempDate(Calendar.HOUR_OF_DAY, oldVal, newVal, 0, 23);
|
||||
}
|
||||
} else if (picker == mMinuteSpinner && mMinuteEnabled) {
|
||||
setTempDate(Calendar.MINUTE, oldVal, newVal, 0, 59);
|
||||
} else if (picker == mAMPMSpinner && mHourEnabled) {
|
||||
mTempDate.set(Calendar.AM_PM, newVal);
|
||||
} else {
|
||||
throw new IllegalArgumentException();
|
||||
// The native date picker widget on these SDKs increments
|
||||
// the next field when one field reaches the maximum.
|
||||
if (picker == mDaySpinner && mDayEnabled) {
|
||||
int maxDayOfMonth = mTempDate.getActualMaximum(Calendar.DAY_OF_MONTH);
|
||||
int old = mTempDate.get(Calendar.DAY_OF_MONTH);
|
||||
setTempDate(Calendar.DAY_OF_MONTH, old, newVal, 1, maxDayOfMonth);
|
||||
} else if (picker == mMonthSpinner && mMonthEnabled) {
|
||||
int old = mTempDate.get(Calendar.MONTH);
|
||||
setTempDate(Calendar.MONTH, old, newVal, Calendar.JANUARY, Calendar.DECEMBER);
|
||||
} else if (picker == mWeekSpinner) {
|
||||
int old = mTempDate.get(Calendar.WEEK_OF_YEAR);
|
||||
int maxWeekOfYear = mTempDate.getActualMaximum(Calendar.WEEK_OF_YEAR);
|
||||
setTempDate(Calendar.WEEK_OF_YEAR, old, newVal, 0, maxWeekOfYear);
|
||||
} else if (picker == mYearSpinner && mYearEnabled) {
|
||||
int month = mTempDate.get(Calendar.MONTH);
|
||||
mTempDate.set(Calendar.YEAR, newVal);
|
||||
// Changing the year shouldn't change the month. (in case of non-leap year a Feb 29)
|
||||
// change the day instead;
|
||||
if (month != mTempDate.get(Calendar.MONTH)) {
|
||||
mTempDate.set(Calendar.MONTH, month);
|
||||
mTempDate.set(Calendar.DAY_OF_MONTH,
|
||||
mTempDate.getActualMaximum(Calendar.DAY_OF_MONTH));
|
||||
}
|
||||
} else if (picker == mHourSpinner && mHourEnabled) {
|
||||
if (mIs12HourMode) {
|
||||
setTempDate(Calendar.HOUR, oldVal, newVal, 1, 12);
|
||||
} else {
|
||||
setTempDate(Calendar.HOUR_OF_DAY, oldVal, newVal, 0, 23);
|
||||
}
|
||||
} else if (picker == mMinuteSpinner && mMinuteEnabled) {
|
||||
setTempDate(Calendar.MINUTE, oldVal, newVal, 0, 59);
|
||||
} else if (picker == mAMPMSpinner && mHourEnabled) {
|
||||
mTempDate.set(Calendar.AM_PM, newVal);
|
||||
} else {
|
||||
if (DEBUG) Log.d(LOGTAG, "Sdk version < 10, using old behavior");
|
||||
if (picker == mDaySpinner && mDayEnabled) {
|
||||
mTempDate.set(Calendar.DAY_OF_MONTH, newVal);
|
||||
} else if (picker == mMonthSpinner && mMonthEnabled) {
|
||||
mTempDate.set(Calendar.MONTH, newVal);
|
||||
if (mTempDate.get(Calendar.MONTH) == newVal + 1) {
|
||||
mTempDate.set(Calendar.MONTH, newVal);
|
||||
mTempDate.set(Calendar.DAY_OF_MONTH,
|
||||
mTempDate.getActualMaximum(Calendar.DAY_OF_MONTH));
|
||||
}
|
||||
} else if (picker == mWeekSpinner) {
|
||||
mTempDate.set(Calendar.WEEK_OF_YEAR, newVal);
|
||||
} else if (picker == mYearSpinner && mYearEnabled) {
|
||||
int month = mTempDate.get(Calendar.MONTH);
|
||||
mTempDate.set(Calendar.YEAR, newVal);
|
||||
if (month != mTempDate.get(Calendar.MONTH)) {
|
||||
mTempDate.set(Calendar.MONTH, month);
|
||||
mTempDate.set(Calendar.DAY_OF_MONTH,
|
||||
mTempDate.getActualMaximum(Calendar.DAY_OF_MONTH));
|
||||
}
|
||||
} else if (picker == mHourSpinner && mHourEnabled) {
|
||||
if (mIs12HourMode) {
|
||||
mTempDate.set(Calendar.HOUR, newVal);
|
||||
} else {
|
||||
mTempDate.set(Calendar.HOUR_OF_DAY, newVal);
|
||||
}
|
||||
} else if (picker == mMinuteSpinner && mMinuteEnabled) {
|
||||
mTempDate.set(Calendar.MINUTE, newVal);
|
||||
} else if (picker == mAMPMSpinner && mHourEnabled) {
|
||||
mTempDate.set(Calendar.AM_PM, newVal);
|
||||
} else {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
setDate(mTempDate);
|
||||
if (mDayEnabled) {
|
||||
|
|
|
@ -41,7 +41,7 @@ public class RoundedCornerLayout extends LinearLayout {
|
|||
|
||||
private void init(Context context) {
|
||||
// Bug 1201081 - clipPath with hardware acceleration crashes on r11-18.
|
||||
cannotClipPath = AppConstants.Versions.feature11Plus && !AppConstants.Versions.feature19Plus;
|
||||
cannotClipPath = !AppConstants.Versions.feature19Plus;
|
||||
|
||||
final DisplayMetrics metrics = context.getResources().getDisplayMetrics();
|
||||
|
||||
|
|
|
@ -236,13 +236,9 @@ gvjar.sources += [geckoview_source_dir + 'java/org/mozilla/gecko/' + x
|
|||
'GeckoViewChrome.java',
|
||||
'GeckoViewContent.java',
|
||||
'GeckoViewFragment.java',
|
||||
'gfx/Axis.java',
|
||||
'gfx/BitmapUtils.java',
|
||||
'gfx/BufferedImage.java',
|
||||
'gfx/BufferedImageGLInfo.java',
|
||||
'gfx/DisplayPortCalculator.java',
|
||||
'gfx/DisplayPortMetrics.java',
|
||||
'gfx/DrawTimingQueue.java',
|
||||
'gfx/DynamicToolbarAnimator.java',
|
||||
'gfx/FloatSize.java',
|
||||
'gfx/FullScreenState.java',
|
||||
|
@ -261,9 +257,7 @@ gvjar.sources += [geckoview_source_dir + 'java/org/mozilla/gecko/' + x
|
|||
'gfx/ProgressiveUpdateData.java',
|
||||
'gfx/RectUtils.java',
|
||||
'gfx/RenderTask.java',
|
||||
'gfx/SimpleScaleGestureDetector.java',
|
||||
'gfx/StackScroller.java',
|
||||
'gfx/SubdocumentScrollHelper.java',
|
||||
'gfx/SurfaceTextureListener.java',
|
||||
'gfx/ViewTransform.java',
|
||||
'InputConnectionListener.java',
|
||||
|
|
|
@ -116,6 +116,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "Snackbars", "resource://gre/modules/Sna
|
|||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "RuntimePermissions", "resource://gre/modules/RuntimePermissions.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "WebsiteMetadata", "resource://gre/modules/WebsiteMetadata.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "FontEnumerator",
|
||||
"@mozilla.org/gfx/fontenumerator;1",
|
||||
"nsIFontEnumerator");
|
||||
|
@ -3930,6 +3932,11 @@ Tab.prototype = {
|
|||
|
||||
this.browser.addEventListener("pagehide", listener, true);
|
||||
}
|
||||
|
||||
if (AppConstants.NIGHTLY_BUILD || AppConstants.MOZ_ANDROID_ACTIVITY_STREAM) {
|
||||
WebsiteMetadata.parseAsynchronously(this.browser.contentDocument);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
@ -93,16 +93,12 @@ public class GeckoAccessibility {
|
|||
event.setItemCount(message.optInt("itemCount", -1));
|
||||
event.setCurrentItemIndex(message.optInt("currentItemIndex", -1));
|
||||
event.setBeforeText(message.optString("beforeText"));
|
||||
if (Versions.feature14Plus) {
|
||||
event.setToIndex(message.optInt("toIndex", -1));
|
||||
event.setScrollable(message.optBoolean("scrollable"));
|
||||
event.setScrollX(message.optInt("scrollX", -1));
|
||||
event.setScrollY(message.optInt("scrollY", -1));
|
||||
}
|
||||
if (Versions.feature15Plus) {
|
||||
event.setMaxScrollX(message.optInt("maxScrollX", -1));
|
||||
event.setMaxScrollY(message.optInt("maxScrollY", -1));
|
||||
}
|
||||
event.setToIndex(message.optInt("toIndex", -1));
|
||||
event.setScrollable(message.optBoolean("scrollable"));
|
||||
event.setScrollX(message.optInt("scrollX", -1));
|
||||
event.setScrollY(message.optInt("scrollY", -1));
|
||||
event.setMaxScrollX(message.optInt("maxScrollX", -1));
|
||||
event.setMaxScrollY(message.optInt("maxScrollY", -1));
|
||||
}
|
||||
|
||||
private static void sendDirectAccessibilityEvent(int eventType, JSONObject message) {
|
||||
|
|
|
@ -824,21 +824,9 @@ public class GeckoAppShell
|
|||
|
||||
@JNITarget
|
||||
static public int getPreferredIconSize() {
|
||||
if (Versions.feature11Plus) {
|
||||
ActivityManager am = (ActivityManager)
|
||||
getApplicationContext().getSystemService(Context.ACTIVITY_SERVICE);
|
||||
return am.getLauncherLargeIconSize();
|
||||
} else {
|
||||
switch (getDpi()) {
|
||||
case DisplayMetrics.DENSITY_MEDIUM:
|
||||
return 48;
|
||||
case DisplayMetrics.DENSITY_XHIGH:
|
||||
return 96;
|
||||
case DisplayMetrics.DENSITY_HIGH:
|
||||
default:
|
||||
return 72;
|
||||
}
|
||||
}
|
||||
ActivityManager am = (ActivityManager)
|
||||
getApplicationContext().getSystemService(Context.ACTIVITY_SERVICE);
|
||||
return am.getLauncherLargeIconSize();
|
||||
}
|
||||
|
||||
@WrapForJNI(calledFrom = "gecko")
|
||||
|
|
|
@ -614,10 +614,8 @@ final class GeckoEditable extends JNIObject
|
|||
float tpUnderlineThickness = 0.0f;
|
||||
|
||||
// These TextPaint fields only exist on Android ICS+ and are not in the SDK.
|
||||
if (Versions.feature14Plus) {
|
||||
tpUnderlineColor = (Integer)getField(tp, "underlineColor", 0);
|
||||
tpUnderlineThickness = (Float)getField(tp, "underlineThickness", 0.0f);
|
||||
}
|
||||
tpUnderlineColor = (Integer)getField(tp, "underlineColor", 0);
|
||||
tpUnderlineThickness = (Float)getField(tp, "underlineThickness", 0.0f);
|
||||
if (tpUnderlineColor != 0) {
|
||||
rangeStyles |= IME_RANGE_UNDERLINE | IME_RANGE_LINECOLOR;
|
||||
rangeLineColor = tpUnderlineColor;
|
||||
|
|
|
@ -994,10 +994,10 @@ class GeckoInputConnection
|
|||
if (typeHint != null &&
|
||||
(typeHint.equalsIgnoreCase("date") ||
|
||||
typeHint.equalsIgnoreCase("time") ||
|
||||
(Versions.feature11Plus && (typeHint.equalsIgnoreCase("datetime") ||
|
||||
typeHint.equalsIgnoreCase("month") ||
|
||||
typeHint.equalsIgnoreCase("week") ||
|
||||
typeHint.equalsIgnoreCase("datetime-local"))))) {
|
||||
typeHint.equalsIgnoreCase("datetime") ||
|
||||
typeHint.equalsIgnoreCase("month") ||
|
||||
typeHint.equalsIgnoreCase("week") ||
|
||||
typeHint.equalsIgnoreCase("datetime-local"))) {
|
||||
state = IME_STATE_DISABLED;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,532 +0,0 @@
|
|||
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.gecko.gfx;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.mozilla.gecko.GeckoAppShell;
|
||||
import org.mozilla.gecko.PrefsHelper;
|
||||
import org.mozilla.gecko.util.FloatUtils;
|
||||
|
||||
import android.os.SystemClock;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
|
||||
/**
|
||||
* This class represents the physics for one axis of movement (i.e. either
|
||||
* horizontal or vertical). It tracks the different properties of movement
|
||||
* like displacement, velocity, viewport dimensions, etc. pertaining to
|
||||
* a particular axis.
|
||||
*/
|
||||
abstract class Axis {
|
||||
private static final String LOGTAG = "GeckoAxis";
|
||||
|
||||
private static final String PREF_SCROLLING_FRICTION_SLOW = "ui.scrolling.friction_slow";
|
||||
private static final String PREF_SCROLLING_FRICTION_FAST = "ui.scrolling.friction_fast";
|
||||
private static final String PREF_SCROLLING_MAX_EVENT_ACCELERATION = "ui.scrolling.max_event_acceleration";
|
||||
private static final String PREF_SCROLLING_OVERSCROLL_DECEL_RATE = "ui.scrolling.overscroll_decel_rate";
|
||||
private static final String PREF_SCROLLING_OVERSCROLL_SNAP_LIMIT = "ui.scrolling.overscroll_snap_limit";
|
||||
private static final String PREF_SCROLLING_MIN_SCROLLABLE_DISTANCE = "ui.scrolling.min_scrollable_distance";
|
||||
private static final String PREF_FLING_ACCEL_INTERVAL = "ui.scrolling.fling_accel_interval";
|
||||
private static final String PREF_FLING_ACCEL_BASE_MULTIPLIER = "ui.scrolling.fling_accel_base_multiplier";
|
||||
private static final String PREF_FLING_ACCEL_SUPPLEMENTAL_MULTIPLIER = "ui.scrolling.fling_accel_supplemental_multiplier";
|
||||
private static final String PREF_FLING_CURVE_FUNCTION_X1 = "ui.scrolling.fling_curve_function_x1";
|
||||
private static final String PREF_FLING_CURVE_FUNCTION_Y1 = "ui.scrolling.fling_curve_function_y1";
|
||||
private static final String PREF_FLING_CURVE_FUNCTION_X2 = "ui.scrolling.fling_curve_function_x2";
|
||||
private static final String PREF_FLING_CURVE_FUNCTION_Y2 = "ui.scrolling.fling_curve_function_y2";
|
||||
private static final String PREF_FLING_CURVE_THRESHOLD_VELOCITY = "ui.scrolling.fling_curve_threshold_velocity";
|
||||
private static final String PREF_FLING_CURVE_MAXIMUM_VELOCITY = "ui.scrolling.fling_curve_max_velocity";
|
||||
private static final String PREF_FLING_CURVE_NEWTON_ITERATIONS = "ui.scrolling.fling_curve_newton_iterations";
|
||||
|
||||
// This fraction of velocity remains after every animation frame when the velocity is low.
|
||||
private static float FRICTION_SLOW;
|
||||
// This fraction of velocity remains after every animation frame when the velocity is high.
|
||||
private static float FRICTION_FAST;
|
||||
// Below this velocity (in pixels per frame), the friction starts increasing from FRICTION_FAST
|
||||
// to FRICTION_SLOW.
|
||||
private static float VELOCITY_THRESHOLD;
|
||||
// The maximum velocity change factor between events, per ms, in %.
|
||||
// Direction changes are excluded.
|
||||
private static float MAX_EVENT_ACCELERATION;
|
||||
|
||||
// The rate of deceleration when the surface has overscrolled.
|
||||
private static float OVERSCROLL_DECEL_RATE;
|
||||
// The percentage of the surface which can be overscrolled before it must snap back.
|
||||
private static float SNAP_LIMIT;
|
||||
|
||||
// The minimum amount of space that must be present for an axis to be considered scrollable,
|
||||
// in pixels.
|
||||
private static float MIN_SCROLLABLE_DISTANCE;
|
||||
|
||||
// The interval within which if two flings are done then scrolling effect is accelerated.
|
||||
private static long FLING_ACCEL_INTERVAL;
|
||||
|
||||
// The multiplication constant of the base velocity in case of accelerated scrolling.
|
||||
private static float FLING_ACCEL_BASE_MULTIPLIER;
|
||||
|
||||
// The multiplication constant of the supplemental velocity in case of accelerated scrolling.
|
||||
private static float FLING_ACCEL_SUPPLEMENTAL_MULTIPLIER;
|
||||
|
||||
// x co-ordinate of the second bezier control point
|
||||
private static float FLING_CURVE_FUNCTION_X1;
|
||||
|
||||
// y co-ordinate of the second bezier control point
|
||||
private static float FLING_CURVE_FUNCTION_Y1;
|
||||
|
||||
// x co-ordinate of the third bezier control point
|
||||
private static float FLING_CURVE_FUNCTION_X2;
|
||||
|
||||
// y co-ordinate of the third bezier control point
|
||||
private static float FLING_CURVE_FUNCTION_Y2;
|
||||
|
||||
// Minimum velocity for curve to be implemented i.e fling curving
|
||||
private static float FLING_CURVE_THRESHOLD_VELOCITY;
|
||||
|
||||
// Maximum permitted velocity
|
||||
private static float FLING_CURVE_MAXIMUM_VELOCITY;
|
||||
|
||||
// Number of iterations in the Newton-Raphson method
|
||||
private static int FLING_CURVE_NEWTON_ITERATIONS;
|
||||
|
||||
private static float getFloatPref(Map<String, Integer> prefs, String prefName, int defaultValue) {
|
||||
Integer value = (prefs == null ? null : prefs.get(prefName));
|
||||
return (value == null || value < 0 ? defaultValue : value) / 1000f;
|
||||
}
|
||||
|
||||
private static int getIntPref(Map<String, Integer> prefs, String prefName, int defaultValue) {
|
||||
Integer value = (prefs == null ? null : prefs.get(prefName));
|
||||
return (value == null || value < 0 ? defaultValue : value);
|
||||
}
|
||||
|
||||
static void initPrefs() {
|
||||
final String[] prefs = { PREF_SCROLLING_FRICTION_FAST,
|
||||
PREF_SCROLLING_FRICTION_SLOW,
|
||||
PREF_SCROLLING_MAX_EVENT_ACCELERATION,
|
||||
PREF_SCROLLING_OVERSCROLL_DECEL_RATE,
|
||||
PREF_SCROLLING_OVERSCROLL_SNAP_LIMIT,
|
||||
PREF_SCROLLING_MIN_SCROLLABLE_DISTANCE,
|
||||
PREF_FLING_ACCEL_INTERVAL,
|
||||
PREF_FLING_ACCEL_BASE_MULTIPLIER,
|
||||
PREF_FLING_ACCEL_SUPPLEMENTAL_MULTIPLIER,
|
||||
PREF_FLING_CURVE_FUNCTION_X1,
|
||||
PREF_FLING_CURVE_FUNCTION_Y1,
|
||||
PREF_FLING_CURVE_FUNCTION_X2,
|
||||
PREF_FLING_CURVE_FUNCTION_Y2,
|
||||
PREF_FLING_CURVE_THRESHOLD_VELOCITY,
|
||||
PREF_FLING_CURVE_MAXIMUM_VELOCITY,
|
||||
PREF_FLING_CURVE_NEWTON_ITERATIONS };
|
||||
|
||||
PrefsHelper.getPrefs(prefs, new PrefsHelper.PrefHandlerBase() {
|
||||
Map<String, Integer> mPrefs = new HashMap<String, Integer>();
|
||||
|
||||
@Override public void prefValue(String name, int value) {
|
||||
mPrefs.put(name, value);
|
||||
}
|
||||
|
||||
@Override public void finish() {
|
||||
setPrefs(mPrefs);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static final float MS_PER_FRAME = 1000.0f / 60.0f;
|
||||
static final long NS_PER_FRAME = Math.round(1000000000f / 60f);
|
||||
private static final float FRAMERATE_MULTIPLIER = (1000f / 60f) / MS_PER_FRAME;
|
||||
private static final int FLING_VELOCITY_POINTS = 8;
|
||||
|
||||
// The values we use for friction are based on a 16.6ms frame, adjust them to currentNsPerFrame:
|
||||
static float getFrameAdjustedFriction(float baseFriction, long currentNsPerFrame) {
|
||||
float framerateMultiplier = (float)currentNsPerFrame / NS_PER_FRAME;
|
||||
return (float)Math.pow(Math.E, (Math.log(baseFriction) / framerateMultiplier));
|
||||
}
|
||||
|
||||
static void setPrefs(Map<String, Integer> prefs) {
|
||||
FRICTION_SLOW = getFloatPref(prefs, PREF_SCROLLING_FRICTION_SLOW, 850);
|
||||
FRICTION_FAST = getFloatPref(prefs, PREF_SCROLLING_FRICTION_FAST, 970);
|
||||
VELOCITY_THRESHOLD = 10 / FRAMERATE_MULTIPLIER;
|
||||
MAX_EVENT_ACCELERATION = getFloatPref(prefs, PREF_SCROLLING_MAX_EVENT_ACCELERATION, GeckoAppShell.getDpi() > 300 ? 100 : 40);
|
||||
OVERSCROLL_DECEL_RATE = getFloatPref(prefs, PREF_SCROLLING_OVERSCROLL_DECEL_RATE, 40);
|
||||
SNAP_LIMIT = getFloatPref(prefs, PREF_SCROLLING_OVERSCROLL_SNAP_LIMIT, 300);
|
||||
MIN_SCROLLABLE_DISTANCE = getFloatPref(prefs, PREF_SCROLLING_MIN_SCROLLABLE_DISTANCE, 500);
|
||||
FLING_ACCEL_INTERVAL = getIntPref(prefs, PREF_FLING_ACCEL_INTERVAL, 500);
|
||||
FLING_ACCEL_BASE_MULTIPLIER = getFloatPref(prefs, PREF_FLING_ACCEL_BASE_MULTIPLIER, 1000);
|
||||
FLING_ACCEL_SUPPLEMENTAL_MULTIPLIER = getFloatPref(prefs, PREF_FLING_ACCEL_SUPPLEMENTAL_MULTIPLIER, 1000);
|
||||
FLING_CURVE_FUNCTION_X1 = getFloatPref(prefs, PREF_FLING_CURVE_FUNCTION_X1, 410);
|
||||
FLING_CURVE_FUNCTION_Y1 = getFloatPref(prefs, PREF_FLING_CURVE_FUNCTION_Y1, 0);
|
||||
FLING_CURVE_FUNCTION_X2 = getFloatPref(prefs, PREF_FLING_CURVE_FUNCTION_X2, 800);
|
||||
FLING_CURVE_FUNCTION_Y2 = getFloatPref(prefs, PREF_FLING_CURVE_FUNCTION_Y2, 1000);
|
||||
FLING_CURVE_THRESHOLD_VELOCITY = getFloatPref(prefs, PREF_FLING_CURVE_THRESHOLD_VELOCITY, 30);
|
||||
FLING_CURVE_MAXIMUM_VELOCITY = getFloatPref(prefs, PREF_FLING_CURVE_MAXIMUM_VELOCITY, 70);
|
||||
FLING_CURVE_NEWTON_ITERATIONS = getIntPref(prefs, PREF_FLING_CURVE_NEWTON_ITERATIONS, 5);
|
||||
|
||||
Log.i(LOGTAG, "Prefs: " + FRICTION_SLOW + "," + FRICTION_FAST + "," + VELOCITY_THRESHOLD + ","
|
||||
+ MAX_EVENT_ACCELERATION + "," + OVERSCROLL_DECEL_RATE + "," + SNAP_LIMIT + "," + MIN_SCROLLABLE_DISTANCE);
|
||||
}
|
||||
|
||||
static {
|
||||
// set the scrolling parameters to default values on startup
|
||||
setPrefs(null);
|
||||
}
|
||||
|
||||
private enum FlingStates {
|
||||
STOPPED,
|
||||
PANNING,
|
||||
FLINGING,
|
||||
}
|
||||
|
||||
private enum Overscroll {
|
||||
NONE,
|
||||
MINUS, // Overscrolled in the negative direction
|
||||
PLUS, // Overscrolled in the positive direction
|
||||
BOTH, // Overscrolled in both directions (page is zoomed to smaller than screen)
|
||||
}
|
||||
|
||||
private final SubdocumentScrollHelper mSubscroller;
|
||||
|
||||
private int mOverscrollMode; /* Default to only overscrolling if we're allowed to scroll in a direction */
|
||||
private float mFirstTouchPos; /* Position of the first touch event on the current drag. */
|
||||
private float mTouchPos; /* Position of the most recent touch event on the current drag. */
|
||||
private float mLastTouchPos; /* Position of the touch event before touchPos. */
|
||||
private float mVelocity; /* Velocity in this direction; pixels per animation frame. */
|
||||
private final float[] mRecentVelocities; /* Circular buffer of recent velocities since last touch start. */
|
||||
private int mRecentVelocityCount; /* Number of values put into mRecentVelocities (unbounded). */
|
||||
private boolean mScrollingDisabled; /* Whether movement on this axis is locked. */
|
||||
private boolean mDisableSnap; /* Whether overscroll snapping is disabled. */
|
||||
private float mDisplacement;
|
||||
private long mLastFlingTime;
|
||||
private float mLastFlingVelocity;
|
||||
|
||||
private FlingStates mFlingState = FlingStates.STOPPED; /* The fling state we're in on this axis. */
|
||||
|
||||
protected abstract float getOrigin();
|
||||
protected abstract float getViewportLength();
|
||||
protected abstract float getPageStart();
|
||||
protected abstract float getPageLength();
|
||||
protected abstract float getVisibleEndOfLayerView();
|
||||
|
||||
Axis(SubdocumentScrollHelper subscroller) {
|
||||
mSubscroller = subscroller;
|
||||
mOverscrollMode = View.OVER_SCROLL_IF_CONTENT_SCROLLS;
|
||||
mRecentVelocities = new float[FLING_VELOCITY_POINTS];
|
||||
}
|
||||
|
||||
// Implementors can override these to show effects when the axis overscrolls
|
||||
protected void overscrollFling(float velocity) { }
|
||||
protected void overscrollPan(float displacement) { }
|
||||
|
||||
public void setOverScrollMode(int overscrollMode) {
|
||||
mOverscrollMode = overscrollMode;
|
||||
}
|
||||
|
||||
public int getOverScrollMode() {
|
||||
return mOverscrollMode;
|
||||
}
|
||||
|
||||
private float getViewportEnd() {
|
||||
return getOrigin() + getViewportLength();
|
||||
}
|
||||
|
||||
private float getPageEnd() {
|
||||
return getPageStart() + getPageLength();
|
||||
}
|
||||
|
||||
void startTouch(float pos) {
|
||||
mVelocity = 0.0f;
|
||||
mScrollingDisabled = false;
|
||||
mFirstTouchPos = mTouchPos = mLastTouchPos = pos;
|
||||
mRecentVelocityCount = 0;
|
||||
}
|
||||
|
||||
float panDistance(float currentPos) {
|
||||
return currentPos - mFirstTouchPos;
|
||||
}
|
||||
|
||||
void setScrollingDisabled(boolean disabled) {
|
||||
mScrollingDisabled = disabled;
|
||||
}
|
||||
|
||||
void saveTouchPos() {
|
||||
mLastTouchPos = mTouchPos;
|
||||
}
|
||||
|
||||
// Calculates and return the slope of the curve at given parameter t
|
||||
float getSlope(float t) {
|
||||
float y1 = FLING_CURVE_FUNCTION_Y1;
|
||||
float y2 = FLING_CURVE_FUNCTION_Y2;
|
||||
|
||||
return (3 * y1)
|
||||
+ t * (6 * y2 - 12 * y1)
|
||||
+ t * t * (9 * y1 - 9 * y2 + 3);
|
||||
}
|
||||
|
||||
// Calculates and returns the value of the bezier curve with the given parameter t and control points p1 and p2
|
||||
float cubicBezier(float p1, float p2, float t) {
|
||||
return (3 * t * (1 - t) * (1 - t) * p1)
|
||||
+ (3 * t * t * (1 - t) * p2)
|
||||
+ (t * t * t);
|
||||
}
|
||||
|
||||
// Responsible for mapping the physical velocity to a the velocity obtained after applying bezier curve (with control points (X1,Y1) and (X2,Y2))
|
||||
float flingCurve(float By) {
|
||||
int ni = FLING_CURVE_NEWTON_ITERATIONS;
|
||||
float[] guess = new float[ni];
|
||||
float y1 = FLING_CURVE_FUNCTION_Y1;
|
||||
float y2 = FLING_CURVE_FUNCTION_Y2;
|
||||
guess[0] = By;
|
||||
|
||||
for (int i = 1; i < ni; i++) {
|
||||
guess[i] = guess[i - 1] - (cubicBezier(y1, y2, guess[i - 1]) - By) / getSlope(guess[i - 1]);
|
||||
}
|
||||
// guess[4] is the final approximate root the cubic equation.
|
||||
float t = guess[4];
|
||||
|
||||
float x1 = FLING_CURVE_FUNCTION_X1;
|
||||
float x2 = FLING_CURVE_FUNCTION_X2;
|
||||
return cubicBezier(x1, x2, t);
|
||||
}
|
||||
|
||||
void updateWithTouchAt(float pos, float timeDelta) {
|
||||
float curveVelocityThreshold = FLING_CURVE_THRESHOLD_VELOCITY * GeckoAppShell.getDpi() * MS_PER_FRAME;
|
||||
float maxVelocity = FLING_CURVE_MAXIMUM_VELOCITY * GeckoAppShell.getDpi() * MS_PER_FRAME;
|
||||
|
||||
float newVelocity = (mTouchPos - pos) / timeDelta * MS_PER_FRAME;
|
||||
|
||||
if (Math.abs(newVelocity) > curveVelocityThreshold && Math.abs(newVelocity) < maxVelocity) {
|
||||
float sign = Math.signum(newVelocity);
|
||||
newVelocity = newVelocity * sign;
|
||||
float scale = maxVelocity - curveVelocityThreshold;
|
||||
float functInp = (newVelocity - curveVelocityThreshold) / scale;
|
||||
float functOut = flingCurve(functInp);
|
||||
newVelocity = functOut * scale + curveVelocityThreshold;
|
||||
newVelocity = newVelocity * sign;
|
||||
}
|
||||
|
||||
mRecentVelocities[mRecentVelocityCount % FLING_VELOCITY_POINTS] = newVelocity;
|
||||
mRecentVelocityCount++;
|
||||
|
||||
// If there's a direction change, or current velocity is very low,
|
||||
// allow setting of the velocity outright. Otherwise, use the current
|
||||
// velocity and a maximum change factor to set the new velocity.
|
||||
boolean curVelocityIsLow = Math.abs(mVelocity) < 1.0f / FRAMERATE_MULTIPLIER;
|
||||
boolean directionChange = (mVelocity > 0) != (newVelocity > 0);
|
||||
if (curVelocityIsLow || (directionChange && !FloatUtils.fuzzyEquals(newVelocity, 0.0f))) {
|
||||
mVelocity = newVelocity;
|
||||
} else {
|
||||
float maxChange = Math.abs(mVelocity * timeDelta * MAX_EVENT_ACCELERATION);
|
||||
mVelocity = Math.min(mVelocity + maxChange, Math.max(mVelocity - maxChange, newVelocity));
|
||||
}
|
||||
|
||||
mTouchPos = pos;
|
||||
}
|
||||
|
||||
boolean overscrolled() {
|
||||
return getOverscroll() != Overscroll.NONE;
|
||||
}
|
||||
|
||||
private Overscroll getOverscroll() {
|
||||
boolean minus = (getOrigin() < getPageStart());
|
||||
boolean plus = (getViewportEnd() > getPageEnd());
|
||||
if (minus && plus) {
|
||||
return Overscroll.BOTH;
|
||||
} else if (minus) {
|
||||
return Overscroll.MINUS;
|
||||
} else if (plus) {
|
||||
return Overscroll.PLUS;
|
||||
} else {
|
||||
return Overscroll.NONE;
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the amount that the page has been overscrolled. If the page hasn't been
|
||||
// overscrolled on this axis, returns 0.
|
||||
private float getExcess() {
|
||||
switch (getOverscroll()) {
|
||||
case MINUS: return getPageStart() - getOrigin();
|
||||
case PLUS: return getViewportEnd() - getPageEnd();
|
||||
case BOTH: return (getViewportEnd() - getPageEnd()) + (getPageStart() - getOrigin());
|
||||
default: return 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns true if the page is zoomed in to some degree along this axis such that scrolling is
|
||||
* possible and this axis has not been scroll locked while panning. Otherwise, returns false.
|
||||
*/
|
||||
boolean scrollable() {
|
||||
// If we're scrolling a subdocument, ignore the viewport length restrictions (since those
|
||||
// apply to the top-level document) and only take into account axis locking.
|
||||
if (mSubscroller.scrolling()) {
|
||||
return !mScrollingDisabled;
|
||||
}
|
||||
|
||||
// if we are axis locked, return false
|
||||
if (mScrollingDisabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// there is scrollable space, and we're not disabled, or the document fits the viewport
|
||||
// but we always allow overscroll anyway
|
||||
return getViewportLength() <= getPageLength() - MIN_SCROLLABLE_DISTANCE ||
|
||||
getOverScrollMode() == View.OVER_SCROLL_ALWAYS;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the resistance, as a multiplier, that should be taken into account when
|
||||
* tracking or pinching.
|
||||
*/
|
||||
float getEdgeResistance(boolean forPinching) {
|
||||
float excess = getExcess();
|
||||
if (excess > 0.0f && (getOverscroll() == Overscroll.BOTH || !forPinching)) {
|
||||
// excess can be greater than viewport length, but the resistance
|
||||
// must never drop below 0.0
|
||||
return Math.max(0.0f, SNAP_LIMIT - excess / getViewportLength());
|
||||
}
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
/* Returns the velocity. If the axis is locked, returns 0. */
|
||||
float getRealVelocity() {
|
||||
return scrollable() ? mVelocity : 0f;
|
||||
}
|
||||
|
||||
void startPan() {
|
||||
mFlingState = FlingStates.PANNING;
|
||||
}
|
||||
|
||||
private float calculateFlingVelocity() {
|
||||
int usablePoints = Math.min(mRecentVelocityCount, FLING_VELOCITY_POINTS);
|
||||
if (usablePoints <= 1) {
|
||||
return mVelocity;
|
||||
}
|
||||
float average = 0;
|
||||
for (int i = 0; i < usablePoints; i++) {
|
||||
average += mRecentVelocities[i];
|
||||
}
|
||||
return average / usablePoints;
|
||||
}
|
||||
|
||||
float accelerate(float velocity, float lastFlingVelocity) {
|
||||
return (FLING_ACCEL_BASE_MULTIPLIER * velocity + FLING_ACCEL_SUPPLEMENTAL_MULTIPLIER * lastFlingVelocity);
|
||||
}
|
||||
|
||||
void startFling(boolean stopped) {
|
||||
mDisableSnap = mSubscroller.scrolling();
|
||||
|
||||
if (stopped) {
|
||||
mFlingState = FlingStates.STOPPED;
|
||||
} else {
|
||||
long now = SystemClock.uptimeMillis();
|
||||
mVelocity = calculateFlingVelocity();
|
||||
|
||||
if ((now - mLastFlingTime < FLING_ACCEL_INTERVAL) && Math.signum(mVelocity) == Math.signum(mLastFlingVelocity)) {
|
||||
mVelocity = accelerate(mVelocity, mLastFlingVelocity);
|
||||
}
|
||||
mFlingState = FlingStates.FLINGING;
|
||||
mLastFlingVelocity = mVelocity;
|
||||
mLastFlingTime = now;
|
||||
}
|
||||
}
|
||||
|
||||
/* Advances a fling animation by one step. */
|
||||
boolean advanceFling(long realNsPerFrame) {
|
||||
if (mFlingState != FlingStates.FLINGING) {
|
||||
return false;
|
||||
}
|
||||
if (mSubscroller.scrolling() && !mSubscroller.lastScrollSucceeded()) {
|
||||
// if the subdocument stopped scrolling, it's because it reached the end
|
||||
// of the subdocument. we don't do overscroll on subdocuments, so there's
|
||||
// no point in continuing this fling.
|
||||
return false;
|
||||
}
|
||||
|
||||
float excess = getExcess();
|
||||
Overscroll overscroll = getOverscroll();
|
||||
boolean decreasingOverscroll = false;
|
||||
if ((overscroll == Overscroll.MINUS && mVelocity > 0) ||
|
||||
(overscroll == Overscroll.PLUS && mVelocity < 0))
|
||||
{
|
||||
decreasingOverscroll = true;
|
||||
}
|
||||
|
||||
if (mDisableSnap || FloatUtils.fuzzyEquals(excess, 0.0f) || decreasingOverscroll) {
|
||||
// If we aren't overscrolled, just apply friction.
|
||||
if (Math.abs(mVelocity) >= VELOCITY_THRESHOLD) {
|
||||
mVelocity *= getFrameAdjustedFriction(FRICTION_FAST, realNsPerFrame);
|
||||
} else {
|
||||
float t = mVelocity / VELOCITY_THRESHOLD;
|
||||
mVelocity *= FloatUtils.interpolate(getFrameAdjustedFriction(FRICTION_SLOW, realNsPerFrame),
|
||||
getFrameAdjustedFriction(FRICTION_FAST, realNsPerFrame), t);
|
||||
}
|
||||
} else {
|
||||
// Otherwise, decrease the velocity linearly.
|
||||
float elasticity = 1.0f - excess / (getViewportLength() * SNAP_LIMIT);
|
||||
float overscrollDecelRate = getFrameAdjustedFriction(OVERSCROLL_DECEL_RATE, realNsPerFrame);
|
||||
if (overscroll == Overscroll.MINUS) {
|
||||
mVelocity = Math.min((mVelocity + overscrollDecelRate) * elasticity, 0.0f);
|
||||
} else { // must be Overscroll.PLUS
|
||||
mVelocity = Math.max((mVelocity - overscrollDecelRate) * elasticity, 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void stopFling() {
|
||||
mVelocity = 0.0f;
|
||||
mFlingState = FlingStates.STOPPED;
|
||||
}
|
||||
|
||||
// Performs displacement of the viewport position according to the current velocity.
|
||||
void displace() {
|
||||
// if this isn't scrollable just return
|
||||
if (!scrollable())
|
||||
return;
|
||||
|
||||
if (mFlingState == FlingStates.PANNING)
|
||||
mDisplacement += (mLastTouchPos - mTouchPos) * getEdgeResistance(false);
|
||||
else
|
||||
mDisplacement += mVelocity * getEdgeResistance(false);
|
||||
|
||||
// if overscroll is disabled and we're trying to overscroll, reset the displacement
|
||||
// to remove any excess. Using getExcess alone isn't enough here since it relies on
|
||||
// getOverscroll which doesn't take into account any new displacment being applied.
|
||||
// If we using a subscroller, we don't want to alter the scrolling being done
|
||||
if (getOverScrollMode() == View.OVER_SCROLL_NEVER && !mSubscroller.scrolling()) {
|
||||
float originalDisplacement = mDisplacement;
|
||||
|
||||
if (mDisplacement + getOrigin() < getPageStart()) {
|
||||
mDisplacement = getPageStart() - getOrigin();
|
||||
} else if (mDisplacement + getOrigin() + getVisibleEndOfLayerView() > getPageEnd()) {
|
||||
mDisplacement = getPageEnd() - getOrigin() - getVisibleEndOfLayerView();
|
||||
}
|
||||
|
||||
// Return the amount of overscroll so that the overscroll controller can draw it for us
|
||||
if (originalDisplacement != mDisplacement) {
|
||||
if (mFlingState == FlingStates.FLINGING) {
|
||||
overscrollFling(mVelocity / MS_PER_FRAME * 1000);
|
||||
stopFling();
|
||||
} else if (mFlingState == FlingStates.PANNING) {
|
||||
overscrollPan(originalDisplacement - mDisplacement);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float resetDisplacement() {
|
||||
float d = mDisplacement;
|
||||
mDisplacement = 0.0f;
|
||||
return d;
|
||||
}
|
||||
|
||||
void setAutoscrollVelocity(float velocity) {
|
||||
if (mFlingState != FlingStates.STOPPED) {
|
||||
Log.e(LOGTAG, "Setting autoscroll velocity while in a fling is not allowed!");
|
||||
return;
|
||||
}
|
||||
mVelocity = velocity;
|
||||
}
|
||||
}
|
|
@ -1,771 +0,0 @@
|
|||
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.gecko.gfx;
|
||||
|
||||
import org.mozilla.gecko.GeckoAppShell;
|
||||
import org.mozilla.gecko.PrefsHelper;
|
||||
import org.mozilla.gecko.util.FloatUtils;
|
||||
|
||||
import org.json.JSONArray;
|
||||
|
||||
import android.graphics.PointF;
|
||||
import android.graphics.RectF;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
final class DisplayPortCalculator {
|
||||
private static final String LOGTAG = "GeckoDisplayPort";
|
||||
private static final PointF ZERO_VELOCITY = new PointF(0, 0);
|
||||
|
||||
private static final String PREF_DISPLAYPORT_STRATEGY = "gfx.displayport.strategy";
|
||||
private static final String PREF_DISPLAYPORT_FM_MULTIPLIER = "gfx.displayport.strategy_fm.multiplier";
|
||||
private static final String PREF_DISPLAYPORT_FM_DANGER_X = "gfx.displayport.strategy_fm.danger_x";
|
||||
private static final String PREF_DISPLAYPORT_FM_DANGER_Y = "gfx.displayport.strategy_fm.danger_y";
|
||||
private static final String PREF_DISPLAYPORT_VB_MULTIPLIER = "gfx.displayport.strategy_vb.multiplier";
|
||||
private static final String PREF_DISPLAYPORT_VB_VELOCITY_THRESHOLD = "gfx.displayport.strategy_vb.threshold";
|
||||
private static final String PREF_DISPLAYPORT_VB_REVERSE_BUFFER = "gfx.displayport.strategy_vb.reverse_buffer";
|
||||
private static final String PREF_DISPLAYPORT_VB_DANGER_X_BASE = "gfx.displayport.strategy_vb.danger_x_base";
|
||||
private static final String PREF_DISPLAYPORT_VB_DANGER_Y_BASE = "gfx.displayport.strategy_vb.danger_y_base";
|
||||
private static final String PREF_DISPLAYPORT_VB_DANGER_X_INCR = "gfx.displayport.strategy_vb.danger_x_incr";
|
||||
private static final String PREF_DISPLAYPORT_VB_DANGER_Y_INCR = "gfx.displayport.strategy_vb.danger_y_incr";
|
||||
private static final String PREF_DISPLAYPORT_PB_VELOCITY_THRESHOLD = "gfx.displayport.strategy_pb.threshold";
|
||||
|
||||
private static DisplayPortStrategy sStrategy = new VelocityBiasStrategy(null);
|
||||
|
||||
static DisplayPortMetrics calculate(ImmutableViewportMetrics metrics, PointF velocity) {
|
||||
return sStrategy.calculate(metrics, (velocity == null ? ZERO_VELOCITY : velocity));
|
||||
}
|
||||
|
||||
static boolean aboutToCheckerboard(ImmutableViewportMetrics metrics, PointF velocity, DisplayPortMetrics displayPort) {
|
||||
if (displayPort == null) {
|
||||
return true;
|
||||
}
|
||||
return sStrategy.aboutToCheckerboard(metrics, (velocity == null ? ZERO_VELOCITY : velocity), displayPort);
|
||||
}
|
||||
|
||||
static boolean drawTimeUpdate(long millis, int pixels) {
|
||||
return sStrategy.drawTimeUpdate(millis, pixels);
|
||||
}
|
||||
|
||||
static void resetPageState() {
|
||||
sStrategy.resetPageState();
|
||||
}
|
||||
|
||||
static void initPrefs() {
|
||||
final String[] prefs = { PREF_DISPLAYPORT_STRATEGY,
|
||||
PREF_DISPLAYPORT_FM_MULTIPLIER,
|
||||
PREF_DISPLAYPORT_FM_DANGER_X,
|
||||
PREF_DISPLAYPORT_FM_DANGER_Y,
|
||||
PREF_DISPLAYPORT_VB_MULTIPLIER,
|
||||
PREF_DISPLAYPORT_VB_VELOCITY_THRESHOLD,
|
||||
PREF_DISPLAYPORT_VB_REVERSE_BUFFER,
|
||||
PREF_DISPLAYPORT_VB_DANGER_X_BASE,
|
||||
PREF_DISPLAYPORT_VB_DANGER_Y_BASE,
|
||||
PREF_DISPLAYPORT_VB_DANGER_X_INCR,
|
||||
PREF_DISPLAYPORT_VB_DANGER_Y_INCR,
|
||||
PREF_DISPLAYPORT_PB_VELOCITY_THRESHOLD };
|
||||
|
||||
PrefsHelper.getPrefs(prefs, new PrefsHelper.PrefHandlerBase() {
|
||||
private final Map<String, Integer> mValues = new HashMap<String, Integer>();
|
||||
|
||||
@Override public void prefValue(String pref, int value) {
|
||||
mValues.put(pref, value);
|
||||
}
|
||||
|
||||
@Override public void finish() {
|
||||
setStrategy(mValues);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the active strategy to use.
|
||||
* See the gfx.displayport.strategy pref in mobile/android/app/mobile.js to see the
|
||||
* mapping between ints and strategies.
|
||||
*/
|
||||
static boolean setStrategy(Map<String, Integer> prefs) {
|
||||
Integer strategy = prefs.get(PREF_DISPLAYPORT_STRATEGY);
|
||||
if (strategy == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (strategy) {
|
||||
case 0:
|
||||
sStrategy = new FixedMarginStrategy(prefs);
|
||||
break;
|
||||
case 1:
|
||||
sStrategy = new VelocityBiasStrategy(prefs);
|
||||
break;
|
||||
case 2:
|
||||
sStrategy = new DynamicResolutionStrategy(prefs);
|
||||
break;
|
||||
case 3:
|
||||
sStrategy = new NoMarginStrategy(prefs);
|
||||
break;
|
||||
case 4:
|
||||
sStrategy = new PredictionBiasStrategy(prefs);
|
||||
break;
|
||||
default:
|
||||
Log.e(LOGTAG, "Invalid strategy index specified");
|
||||
return false;
|
||||
}
|
||||
Log.i(LOGTAG, "Set strategy " + sStrategy.toString());
|
||||
return true;
|
||||
}
|
||||
|
||||
private static float getFloatPref(Map<String, Integer> prefs, String prefName, int defaultValue) {
|
||||
Integer value = (prefs == null ? null : prefs.get(prefName));
|
||||
return (value == null || value < 0 ? defaultValue : value) / 1000f;
|
||||
}
|
||||
|
||||
private static abstract class DisplayPortStrategy {
|
||||
/** Calculates a displayport given a viewport and panning velocity. */
|
||||
public abstract DisplayPortMetrics calculate(ImmutableViewportMetrics metrics, PointF velocity);
|
||||
/** Returns true if a checkerboard is about to be visible and we should not throttle drawing. */
|
||||
public abstract boolean aboutToCheckerboard(ImmutableViewportMetrics metrics, PointF velocity, DisplayPortMetrics displayPort);
|
||||
/** Notify the strategy of a new recorded draw time. Return false to turn off draw time recording. */
|
||||
public boolean drawTimeUpdate(long millis, int pixels) { return false; }
|
||||
/** Reset any page-specific state stored, as the page being displayed has changed. */
|
||||
public void resetPageState() {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the dimensions for a rect that has area (width*height) that does not exceed the page size in the
|
||||
* given metrics object. The area in the returned FloatSize may be less than width*height if the page is
|
||||
* small, but it will never be larger than width*height.
|
||||
* Note that this process may change the relative aspect ratio of the given dimensions.
|
||||
*/
|
||||
private static FloatSize reshapeForPage(float width, float height, ImmutableViewportMetrics metrics) {
|
||||
// figure out how much of the desired buffer amount we can actually use on the horizontal axis
|
||||
float usableWidth = Math.min(width, metrics.getPageWidth());
|
||||
// if we reduced the buffer amount on the horizontal axis, we should take that saved memory and
|
||||
// use it on the vertical axis
|
||||
float extraUsableHeight = (float)Math.floor(((width - usableWidth) * height) / usableWidth);
|
||||
float usableHeight = Math.min(height + extraUsableHeight, metrics.getPageHeight());
|
||||
if (usableHeight < height && usableWidth == width) {
|
||||
// and the reverse - if we shrunk the buffer on the vertical axis we can add it to the horizontal
|
||||
float extraUsableWidth = (float)Math.floor(((height - usableHeight) * width) / usableHeight);
|
||||
usableWidth = Math.min(width + extraUsableWidth, metrics.getPageWidth());
|
||||
}
|
||||
return new FloatSize(usableWidth, usableHeight);
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand the given rect in all directions by a "danger zone". The size of the danger zone on an axis
|
||||
* is the size of the view on that axis multiplied by the given multiplier. The expanded rect is then
|
||||
* clamped to page bounds and returned.
|
||||
*/
|
||||
private static RectF expandByDangerZone(RectF rect, float dangerZoneXMultiplier, float dangerZoneYMultiplier, ImmutableViewportMetrics metrics) {
|
||||
// calculate the danger zone amounts in pixels
|
||||
float dangerZoneX = metrics.getWidth() * dangerZoneXMultiplier;
|
||||
float dangerZoneY = metrics.getHeight() * dangerZoneYMultiplier;
|
||||
rect = RectUtils.expand(rect, dangerZoneX, dangerZoneY);
|
||||
// clamp to page bounds
|
||||
return clampToPageBounds(rect, metrics);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the display port by expanding the viewport by the specified
|
||||
* margins, then clamping to the page size.
|
||||
*/
|
||||
private static DisplayPortMetrics getPageClampedDisplayPortMetrics(RectF margins, float zoom, ImmutableViewportMetrics metrics) {
|
||||
float left = metrics.viewportRectLeft - margins.left;
|
||||
float top = metrics.viewportRectTop - margins.top;
|
||||
float right = metrics.viewportRectRight() + margins.right;
|
||||
float bottom = metrics.viewportRectBottom() + margins.bottom;
|
||||
left = Math.max(metrics.pageRectLeft, left);
|
||||
top = Math.max(metrics.pageRectTop, top);
|
||||
right = Math.min(metrics.pageRectRight, right);
|
||||
bottom = Math.min(metrics.pageRectBottom, bottom);
|
||||
|
||||
return new DisplayPortMetrics(left, top, right, bottom, zoom);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjust the given margins so if they are applied on the viewport in the metrics, the resulting rect
|
||||
* does not exceed the page bounds. This code will maintain the total margin amount for a given axis;
|
||||
* it assumes that margins.left + metrics.getWidth() + margins.right is less than or equal to
|
||||
* metrics.getPageWidth(); and the same for the y axis.
|
||||
*/
|
||||
private static RectF shiftMarginsForPageBounds(RectF margins, ImmutableViewportMetrics metrics) {
|
||||
// check how much we're overflowing in each direction. note that at most one of leftOverflow
|
||||
// and rightOverflow can be greater than zero, and at most one of topOverflow and bottomOverflow
|
||||
// can be greater than zero, because of the assumption described in the method javadoc.
|
||||
float leftOverflow = metrics.pageRectLeft - (metrics.viewportRectLeft - margins.left);
|
||||
float rightOverflow = (metrics.viewportRectRight() + margins.right) - metrics.pageRectRight;
|
||||
float topOverflow = metrics.pageRectTop - (metrics.viewportRectTop - margins.top);
|
||||
float bottomOverflow = (metrics.viewportRectBottom() + margins.bottom) - metrics.pageRectBottom;
|
||||
|
||||
// if the margins overflow the page bounds, shift them to other side on the same axis
|
||||
if (leftOverflow > 0) {
|
||||
margins.left -= leftOverflow;
|
||||
margins.right += leftOverflow;
|
||||
} else if (rightOverflow > 0) {
|
||||
margins.right -= rightOverflow;
|
||||
margins.left += rightOverflow;
|
||||
}
|
||||
if (topOverflow > 0) {
|
||||
margins.top -= topOverflow;
|
||||
margins.bottom += topOverflow;
|
||||
} else if (bottomOverflow > 0) {
|
||||
margins.bottom -= bottomOverflow;
|
||||
margins.top += bottomOverflow;
|
||||
}
|
||||
return margins;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clamp the given rect to the page bounds and return it.
|
||||
*/
|
||||
private static RectF clampToPageBounds(RectF rect, ImmutableViewportMetrics metrics) {
|
||||
if (rect.top < metrics.pageRectTop) rect.top = metrics.pageRectTop;
|
||||
if (rect.left < metrics.pageRectLeft) rect.left = metrics.pageRectLeft;
|
||||
if (rect.right > metrics.pageRectRight) rect.right = metrics.pageRectRight;
|
||||
if (rect.bottom > metrics.pageRectBottom) rect.bottom = metrics.pageRectBottom;
|
||||
return rect;
|
||||
}
|
||||
|
||||
/**
|
||||
* This class implements the variation where we basically don't bother with a a display port.
|
||||
*/
|
||||
private static class NoMarginStrategy extends DisplayPortStrategy {
|
||||
NoMarginStrategy(Map<String, Integer> prefs) {
|
||||
// no prefs in this strategy
|
||||
}
|
||||
|
||||
@Override
|
||||
public DisplayPortMetrics calculate(ImmutableViewportMetrics metrics, PointF velocity) {
|
||||
return new DisplayPortMetrics(metrics.viewportRectLeft,
|
||||
metrics.viewportRectTop,
|
||||
metrics.viewportRectRight(),
|
||||
metrics.viewportRectBottom(),
|
||||
metrics.zoomFactor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean aboutToCheckerboard(ImmutableViewportMetrics metrics, PointF velocity, DisplayPortMetrics displayPort) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "NoMarginStrategy";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This class implements the variation where we use a fixed-size margin on the display port.
|
||||
* The margin is always 300 pixels in all directions, except when we are (a) approaching a page
|
||||
* boundary, and/or (b) if we are limited by the page size. In these cases we try to maintain
|
||||
* the area of the display port by (a) shifting the buffer to the other side on the same axis,
|
||||
* and/or (b) increasing the buffer on the other axis to compensate for the reduced buffer on
|
||||
* one axis.
|
||||
*/
|
||||
private static class FixedMarginStrategy extends DisplayPortStrategy {
|
||||
// The length of each axis of the display port will be the corresponding view length
|
||||
// multiplied by this factor.
|
||||
private final float SIZE_MULTIPLIER;
|
||||
|
||||
// If the visible rect is within the danger zone (measured as a fraction of the view size
|
||||
// from the edge of the displayport) we start redrawing to minimize checkerboarding.
|
||||
private final float DANGER_ZONE_X_MULTIPLIER;
|
||||
private final float DANGER_ZONE_Y_MULTIPLIER;
|
||||
|
||||
FixedMarginStrategy(Map<String, Integer> prefs) {
|
||||
SIZE_MULTIPLIER = getFloatPref(prefs, PREF_DISPLAYPORT_FM_MULTIPLIER, 2000);
|
||||
DANGER_ZONE_X_MULTIPLIER = getFloatPref(prefs, PREF_DISPLAYPORT_FM_DANGER_X, 100);
|
||||
DANGER_ZONE_Y_MULTIPLIER = getFloatPref(prefs, PREF_DISPLAYPORT_FM_DANGER_Y, 200);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DisplayPortMetrics calculate(ImmutableViewportMetrics metrics, PointF velocity) {
|
||||
float displayPortWidth = metrics.getWidth() * SIZE_MULTIPLIER;
|
||||
float displayPortHeight = metrics.getHeight() * SIZE_MULTIPLIER;
|
||||
|
||||
// we need to avoid having a display port that is larger than the page, or we will end up
|
||||
// painting things outside the page bounds (bug 729169). we simultaneously need to make
|
||||
// the display port as large as possible so that we redraw less. reshape the display
|
||||
// port dimensions to accomplish this.
|
||||
FloatSize usableSize = reshapeForPage(displayPortWidth, displayPortHeight, metrics);
|
||||
float horizontalBuffer = usableSize.width - metrics.getWidth();
|
||||
float verticalBuffer = usableSize.height - metrics.getHeight();
|
||||
|
||||
// and now calculate the display port margins based on how much buffer we've decided to use and
|
||||
// the page bounds, ensuring we use all of the available buffer amounts on one side or the other
|
||||
// on any given axis. (i.e. if we're scrolled to the top of the page, the vertical buffer is
|
||||
// entirely below the visible viewport, but if we're halfway down the page, the vertical buffer
|
||||
// is split).
|
||||
RectF margins = new RectF();
|
||||
margins.left = horizontalBuffer / 2.0f;
|
||||
margins.right = horizontalBuffer - margins.left;
|
||||
margins.top = verticalBuffer / 2.0f;
|
||||
margins.bottom = verticalBuffer - margins.top;
|
||||
margins = shiftMarginsForPageBounds(margins, metrics);
|
||||
|
||||
return getPageClampedDisplayPortMetrics(margins, metrics.zoomFactor, metrics);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean aboutToCheckerboard(ImmutableViewportMetrics metrics, PointF velocity, DisplayPortMetrics displayPort) {
|
||||
// Increase the size of the viewport based on the danger zone multiplier (and clamp to page
|
||||
// boundaries), and intersect it with the current displayport to determine whether we're
|
||||
// close to checkerboarding.
|
||||
RectF adjustedViewport = expandByDangerZone(metrics.getViewport(), DANGER_ZONE_X_MULTIPLIER, DANGER_ZONE_Y_MULTIPLIER, metrics);
|
||||
return !displayPort.contains(adjustedViewport);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "FixedMarginStrategy mult=" + SIZE_MULTIPLIER + ", dangerX=" + DANGER_ZONE_X_MULTIPLIER + ", dangerY=" + DANGER_ZONE_Y_MULTIPLIER;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This class implements the variation with a small fixed-size margin with velocity bias.
|
||||
* In this variation, the default margins are pretty small relative to the view size, but
|
||||
* they are affected by the panning velocity. Specifically, if we are panning on one axis,
|
||||
* we remove the margins on the other axis because we are likely axis-locked. Also once
|
||||
* we are panning in one direction above a certain threshold velocity, we shift the buffer
|
||||
* so that it is almost entirely in the direction of the pan, with a little bit in the
|
||||
* reverse direction.
|
||||
*/
|
||||
private static class VelocityBiasStrategy extends DisplayPortStrategy {
|
||||
// The length of each axis of the display port will be the corresponding view length
|
||||
// multiplied by this factor.
|
||||
private final float SIZE_MULTIPLIER;
|
||||
// The velocity above which we apply the velocity bias
|
||||
private final float VELOCITY_THRESHOLD;
|
||||
// How much of the buffer to keep in the reverse direction of the velocity
|
||||
private final float REVERSE_BUFFER;
|
||||
// If the visible rect is within the danger zone we start redrawing to minimize
|
||||
// checkerboarding. the danger zone amount is a linear function of the form:
|
||||
// viewportsize * (base + velocity * incr)
|
||||
// where base and incr are configurable values.
|
||||
private final float DANGER_ZONE_BASE_X_MULTIPLIER;
|
||||
private final float DANGER_ZONE_BASE_Y_MULTIPLIER;
|
||||
private final float DANGER_ZONE_INCR_X_MULTIPLIER;
|
||||
private final float DANGER_ZONE_INCR_Y_MULTIPLIER;
|
||||
|
||||
VelocityBiasStrategy(Map<String, Integer> prefs) {
|
||||
SIZE_MULTIPLIER = getFloatPref(prefs, PREF_DISPLAYPORT_VB_MULTIPLIER, 2000);
|
||||
VELOCITY_THRESHOLD = GeckoAppShell.getDpi() * getFloatPref(prefs, PREF_DISPLAYPORT_VB_VELOCITY_THRESHOLD, 32);
|
||||
REVERSE_BUFFER = getFloatPref(prefs, PREF_DISPLAYPORT_VB_REVERSE_BUFFER, 200);
|
||||
DANGER_ZONE_BASE_X_MULTIPLIER = getFloatPref(prefs, PREF_DISPLAYPORT_VB_DANGER_X_BASE, 1000);
|
||||
DANGER_ZONE_BASE_Y_MULTIPLIER = getFloatPref(prefs, PREF_DISPLAYPORT_VB_DANGER_Y_BASE, 1000);
|
||||
DANGER_ZONE_INCR_X_MULTIPLIER = getFloatPref(prefs, PREF_DISPLAYPORT_VB_DANGER_X_INCR, 0);
|
||||
DANGER_ZONE_INCR_Y_MULTIPLIER = getFloatPref(prefs, PREF_DISPLAYPORT_VB_DANGER_Y_INCR, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Split the given amounts into margins based on the VELOCITY_THRESHOLD and REVERSE_BUFFER values.
|
||||
* If the velocity is above the VELOCITY_THRESHOLD on an axis, split the amount into REVERSE_BUFFER
|
||||
* and 1.0 - REVERSE_BUFFER fractions. The REVERSE_BUFFER fraction is set as the margin in the
|
||||
* direction opposite to the velocity, and the remaining fraction is set as the margin in the direction
|
||||
* of the velocity. If the velocity is lower than VELOCITY_THRESHOLD, split the amount evenly into the
|
||||
* two margins on that axis.
|
||||
*/
|
||||
private RectF velocityBiasedMargins(float xAmount, float yAmount, PointF velocity) {
|
||||
RectF margins = new RectF();
|
||||
|
||||
if (velocity.x > VELOCITY_THRESHOLD) {
|
||||
margins.left = xAmount * REVERSE_BUFFER;
|
||||
} else if (velocity.x < -VELOCITY_THRESHOLD) {
|
||||
margins.left = xAmount * (1.0f - REVERSE_BUFFER);
|
||||
} else {
|
||||
margins.left = xAmount / 2.0f;
|
||||
}
|
||||
margins.right = xAmount - margins.left;
|
||||
|
||||
if (velocity.y > VELOCITY_THRESHOLD) {
|
||||
margins.top = yAmount * REVERSE_BUFFER;
|
||||
} else if (velocity.y < -VELOCITY_THRESHOLD) {
|
||||
margins.top = yAmount * (1.0f - REVERSE_BUFFER);
|
||||
} else {
|
||||
margins.top = yAmount / 2.0f;
|
||||
}
|
||||
margins.bottom = yAmount - margins.top;
|
||||
|
||||
return margins;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DisplayPortMetrics calculate(ImmutableViewportMetrics metrics, PointF velocity) {
|
||||
float displayPortWidth = metrics.getWidth() * SIZE_MULTIPLIER;
|
||||
float displayPortHeight = metrics.getHeight() * SIZE_MULTIPLIER;
|
||||
|
||||
// but if we're panning on one axis, set the margins for the other axis to zero since we are likely
|
||||
// axis locked and won't be displaying that extra area.
|
||||
if (Math.abs(velocity.x) > VELOCITY_THRESHOLD && FloatUtils.fuzzyEquals(velocity.y, 0)) {
|
||||
displayPortHeight = metrics.getHeight();
|
||||
} else if (Math.abs(velocity.y) > VELOCITY_THRESHOLD && FloatUtils.fuzzyEquals(velocity.x, 0)) {
|
||||
displayPortWidth = metrics.getWidth();
|
||||
}
|
||||
|
||||
// we need to avoid having a display port that is larger than the page, or we will end up
|
||||
// painting things outside the page bounds (bug 729169).
|
||||
displayPortWidth = Math.min(displayPortWidth, metrics.getPageWidth());
|
||||
displayPortHeight = Math.min(displayPortHeight, metrics.getPageHeight());
|
||||
float horizontalBuffer = displayPortWidth - metrics.getWidth();
|
||||
float verticalBuffer = displayPortHeight - metrics.getHeight();
|
||||
|
||||
// split the buffer amounts into margins based on velocity, and shift it to
|
||||
// take into account the page bounds
|
||||
RectF margins = velocityBiasedMargins(horizontalBuffer, verticalBuffer, velocity);
|
||||
margins = shiftMarginsForPageBounds(margins, metrics);
|
||||
|
||||
return getPageClampedDisplayPortMetrics(margins, metrics.zoomFactor, metrics);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean aboutToCheckerboard(ImmutableViewportMetrics metrics, PointF velocity, DisplayPortMetrics displayPort) {
|
||||
// calculate the danger zone amounts based on the prefs
|
||||
float dangerZoneX = metrics.getWidth() * (DANGER_ZONE_BASE_X_MULTIPLIER + (velocity.x * DANGER_ZONE_INCR_X_MULTIPLIER));
|
||||
float dangerZoneY = metrics.getHeight() * (DANGER_ZONE_BASE_Y_MULTIPLIER + (velocity.y * DANGER_ZONE_INCR_Y_MULTIPLIER));
|
||||
// clamp it such that when added to the viewport, they don't exceed page size.
|
||||
// this is a prerequisite to calling shiftMarginsForPageBounds as we do below.
|
||||
dangerZoneX = Math.min(dangerZoneX, metrics.getPageWidth() - metrics.getWidth());
|
||||
dangerZoneY = Math.min(dangerZoneY, metrics.getPageHeight() - metrics.getHeight());
|
||||
|
||||
// split the danger zone into margins based on velocity, and ensure it doesn't exceed
|
||||
// page bounds.
|
||||
RectF dangerMargins = velocityBiasedMargins(dangerZoneX, dangerZoneY, velocity);
|
||||
dangerMargins = shiftMarginsForPageBounds(dangerMargins, metrics);
|
||||
|
||||
// we're about to checkerboard if the current viewport area + the danger zone margins
|
||||
// fall out of the current displayport anywhere.
|
||||
RectF adjustedViewport = new RectF(
|
||||
metrics.viewportRectLeft - dangerMargins.left,
|
||||
metrics.viewportRectTop - dangerMargins.top,
|
||||
metrics.viewportRectRight() + dangerMargins.right,
|
||||
metrics.viewportRectBottom() + dangerMargins.bottom);
|
||||
return !displayPort.contains(adjustedViewport);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "VelocityBiasStrategy mult=" + SIZE_MULTIPLIER + ", threshold=" + VELOCITY_THRESHOLD + ", reverse=" + REVERSE_BUFFER
|
||||
+ ", dangerBaseX=" + DANGER_ZONE_BASE_X_MULTIPLIER + ", dangerBaseY=" + DANGER_ZONE_BASE_Y_MULTIPLIER
|
||||
+ ", dangerIncrX=" + DANGER_ZONE_INCR_Y_MULTIPLIER + ", dangerIncrY=" + DANGER_ZONE_INCR_Y_MULTIPLIER;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This class implements the variation where we draw more of the page at low resolution while panning.
|
||||
* In this variation, as we pan faster, we increase the page area we are drawing, but reduce the draw
|
||||
* resolution to compensate. This results in the same device-pixel area drawn; the compositor then
|
||||
* scales this up to the viewport zoom level. This results in a large area of the page drawn but it
|
||||
* looks blurry. The assumption is that drawing extra that we never display is better than checkerboarding,
|
||||
* where we draw less but never even show it on the screen.
|
||||
*/
|
||||
private static class DynamicResolutionStrategy extends DisplayPortStrategy {
|
||||
// The length of each axis of the display port will be the corresponding view length
|
||||
// multiplied by this factor.
|
||||
private static final float SIZE_MULTIPLIER = 1.5f;
|
||||
|
||||
// The velocity above which we start zooming out the display port to keep up
|
||||
// with the panning.
|
||||
private static final float VELOCITY_EXPANSION_THRESHOLD = GeckoAppShell.getDpi() / 16f;
|
||||
|
||||
// How much we increase the display port based on velocity. Assuming no friction and
|
||||
// splitting (see below), this should be be the number of frames (@60fps) between us
|
||||
// calculating the display port and the draw of the *next* display port getting composited
|
||||
// and displayed on the screen. This is because the timeline looks like this:
|
||||
// Java: pan pan pan pan pan pan ! pan pan pan pan pan pan !
|
||||
// Gecko: \-> draw -> composite / \-> draw -> composite /
|
||||
// The display port calculated on the first "pan" gets composited to the screen at the
|
||||
// first exclamation mark, and remains on the screen until the second exclamation mark.
|
||||
// In order to avoid checkerboarding, that display port must be able to contain all of
|
||||
// the panning until the second exclamation mark, which encompasses two entire draw/composite
|
||||
// cycles.
|
||||
// If we take into account friction, our velocity multiplier should be reduced as the
|
||||
// amount of pan will decrease each time. If we take into account display port splitting,
|
||||
// it should be increased as the splitting means some of the display port will be used to
|
||||
// draw in the opposite direction of the velocity. For now I'm assuming these two cancel
|
||||
// each other out.
|
||||
private static final float VELOCITY_MULTIPLIER = 60.0f;
|
||||
|
||||
// The following constants adjust how biased the display port is in the direction of panning.
|
||||
// When panning fast (above the FAST_THRESHOLD) we use the fast split factor to split the
|
||||
// display port "buffer" area, otherwise we use the slow split factor. This is based on the
|
||||
// assumption that if the user is panning fast, they are less likely to reverse directions
|
||||
// and go backwards, so we should spend more of our display port buffer in the direction of
|
||||
// panning.
|
||||
private static final float VELOCITY_FAST_THRESHOLD = VELOCITY_EXPANSION_THRESHOLD * 2.0f;
|
||||
private static final float FAST_SPLIT_FACTOR = 0.95f;
|
||||
private static final float SLOW_SPLIT_FACTOR = 0.8f;
|
||||
|
||||
// The following constants are used for viewport prediction; we use them to estimate where
|
||||
// the viewport will be soon and whether or not we should trigger a draw right now. "soon"
|
||||
// in the previous sentence really refers to the amount of time it would take to draw and
|
||||
// composite from the point at which we do the calculation, and that is not really a known
|
||||
// quantity. The velocity multiplier is how much we multiply the velocity by; it has the
|
||||
// same caveats as the VELOCITY_MULTIPLIER above except that it only needs to take into account
|
||||
// one draw/composite cycle instead of two. The danger zone multiplier is a multiplier of the
|
||||
// viewport size that we use as an extra "danger zone" around the viewport; if this danger
|
||||
// zone falls outside the display port then we are approaching the point at which we will
|
||||
// checkerboard, and hence should start drawing. Note that if DANGER_ZONE_MULTIPLIER is
|
||||
// greater than (SIZE_MULTIPLIER - 1.0f), then at zero velocity we will always be in the
|
||||
// danger zone, and thus will be constantly drawing.
|
||||
private static final float PREDICTION_VELOCITY_MULTIPLIER = 30.0f;
|
||||
private static final float DANGER_ZONE_MULTIPLIER = 0.20f; // must be less than (SIZE_MULTIPLIER - 1.0f)
|
||||
|
||||
DynamicResolutionStrategy(Map<String, Integer> prefs) {
|
||||
// ignore prefs for now
|
||||
}
|
||||
|
||||
@Override
|
||||
public DisplayPortMetrics calculate(ImmutableViewportMetrics metrics, PointF velocity) {
|
||||
float displayPortWidth = metrics.getWidth() * SIZE_MULTIPLIER;
|
||||
float displayPortHeight = metrics.getHeight() * SIZE_MULTIPLIER;
|
||||
|
||||
// for resolution calculation purposes, we need to know what the adjusted display port dimensions
|
||||
// would be if we had zero velocity, so calculate that here before we increase the display port
|
||||
// based on velocity.
|
||||
FloatSize reshapedSize = reshapeForPage(displayPortWidth, displayPortHeight, metrics);
|
||||
|
||||
// increase displayPortWidth and displayPortHeight based on the velocity, but maintaining their
|
||||
// relative aspect ratio.
|
||||
if (velocity.length() > VELOCITY_EXPANSION_THRESHOLD) {
|
||||
float velocityFactor = Math.max(Math.abs(velocity.x) / displayPortWidth,
|
||||
Math.abs(velocity.y) / displayPortHeight);
|
||||
velocityFactor *= VELOCITY_MULTIPLIER;
|
||||
|
||||
displayPortWidth += (displayPortWidth * velocityFactor);
|
||||
displayPortHeight += (displayPortHeight * velocityFactor);
|
||||
}
|
||||
|
||||
// at this point, displayPortWidth and displayPortHeight are how much of the page (in device pixels)
|
||||
// we want to be rendered by Gecko. Note here "device pixels" is equivalent to CSS pixels multiplied
|
||||
// by metrics.zoomFactor
|
||||
|
||||
// we need to avoid having a display port that is larger than the page, or we will end up
|
||||
// painting things outside the page bounds (bug 729169). we simultaneously need to make
|
||||
// the display port as large as possible so that we redraw less. reshape the display
|
||||
// port dimensions to accomplish this. this may change the aspect ratio of the display port,
|
||||
// but we are assuming that this is desirable because the advantages from pre-drawing will
|
||||
// outweigh the disadvantages from any buffer reallocations that might occur.
|
||||
FloatSize usableSize = reshapeForPage(displayPortWidth, displayPortHeight, metrics);
|
||||
float horizontalBuffer = usableSize.width - metrics.getWidth();
|
||||
float verticalBuffer = usableSize.height - metrics.getHeight();
|
||||
|
||||
// at this point, horizontalBuffer and verticalBuffer are the dimensions of the buffer area we have.
|
||||
// the buffer area is the off-screen area that is part of the display port and will be pre-drawn in case
|
||||
// the user scrolls there. we now need to split the buffer area on each axis so that we know
|
||||
// what the exact margins on each side will be. first we split the buffer amount based on the direction
|
||||
// we're moving, so that we have a larger buffer in the direction of travel.
|
||||
RectF margins = new RectF();
|
||||
margins.left = splitBufferByVelocity(horizontalBuffer, velocity.x);
|
||||
margins.right = horizontalBuffer - margins.left;
|
||||
margins.top = splitBufferByVelocity(verticalBuffer, velocity.y);
|
||||
margins.bottom = verticalBuffer - margins.top;
|
||||
|
||||
// then, we account for running into the page bounds - so that if we hit the top of the page, we need
|
||||
// to drop the top margin and move that amount to the bottom margin.
|
||||
margins = shiftMarginsForPageBounds(margins, metrics);
|
||||
|
||||
// finally, we calculate the resolution we want to render the display port area at. We do this
|
||||
// so that as we expand the display port area (because of velocity), we reduce the resolution of
|
||||
// the painted area so as to maintain the size of the buffer Gecko is painting into. we calculate
|
||||
// the reduction in resolution by comparing the display port size with and without the velocity
|
||||
// changes applied.
|
||||
// this effectively means that as we pan faster and faster, the display port grows, but we paint
|
||||
// at lower resolutions. this paints more area to reduce checkerboard at the cost of increasing
|
||||
// compositor-scaling and blurriness. Once we stop panning, the blurriness must be entirely gone.
|
||||
// Note that usable* could be less than base* if we are pinch-zoomed out into overscroll, so we
|
||||
// clamp it to make sure this doesn't increase our display resolution past metrics.zoomFactor.
|
||||
float scaleFactor = Math.min(reshapedSize.width / usableSize.width, reshapedSize.height / usableSize.height);
|
||||
float displayResolution = metrics.zoomFactor * Math.min(1.0f, scaleFactor);
|
||||
|
||||
DisplayPortMetrics dpMetrics = new DisplayPortMetrics(
|
||||
metrics.viewportRectLeft - margins.left,
|
||||
metrics.viewportRectTop - margins.top,
|
||||
metrics.viewportRectRight() + margins.right,
|
||||
metrics.viewportRectBottom() + margins.bottom,
|
||||
displayResolution);
|
||||
return dpMetrics;
|
||||
}
|
||||
|
||||
/**
|
||||
* Split the given buffer amount into two based on the velocity.
|
||||
* Given an amount of total usable buffer on an axis, this will
|
||||
* return the amount that should be used on the left/top side of
|
||||
* the axis (the side which a negative velocity vector corresponds
|
||||
* to).
|
||||
*/
|
||||
private float splitBufferByVelocity(float amount, float velocity) {
|
||||
// if no velocity, so split evenly
|
||||
if (FloatUtils.fuzzyEquals(velocity, 0)) {
|
||||
return amount / 2.0f;
|
||||
}
|
||||
// if we're moving quickly, assign more of the amount in that direction
|
||||
// since is is less likely that we will reverse direction immediately
|
||||
if (velocity < -VELOCITY_FAST_THRESHOLD) {
|
||||
return amount * FAST_SPLIT_FACTOR;
|
||||
}
|
||||
if (velocity > VELOCITY_FAST_THRESHOLD) {
|
||||
return amount * (1.0f - FAST_SPLIT_FACTOR);
|
||||
}
|
||||
// if we're moving slowly, then assign less of the amount in that direction
|
||||
if (velocity < 0) {
|
||||
return amount * SLOW_SPLIT_FACTOR;
|
||||
} else {
|
||||
return amount * (1.0f - SLOW_SPLIT_FACTOR);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean aboutToCheckerboard(ImmutableViewportMetrics metrics, PointF velocity, DisplayPortMetrics displayPort) {
|
||||
// Expand the viewport based on our velocity (and clamp it to page boundaries).
|
||||
// Then intersect it with the last-requested displayport to determine whether we're
|
||||
// close to checkerboarding.
|
||||
|
||||
RectF predictedViewport = metrics.getViewport();
|
||||
|
||||
// first we expand the viewport in the direction we're moving based on some
|
||||
// multiple of the current velocity.
|
||||
if (velocity.length() > 0) {
|
||||
if (velocity.x < 0) {
|
||||
predictedViewport.left += velocity.x * PREDICTION_VELOCITY_MULTIPLIER;
|
||||
} else if (velocity.x > 0) {
|
||||
predictedViewport.right += velocity.x * PREDICTION_VELOCITY_MULTIPLIER;
|
||||
}
|
||||
|
||||
if (velocity.y < 0) {
|
||||
predictedViewport.top += velocity.y * PREDICTION_VELOCITY_MULTIPLIER;
|
||||
} else if (velocity.y > 0) {
|
||||
predictedViewport.bottom += velocity.y * PREDICTION_VELOCITY_MULTIPLIER;
|
||||
}
|
||||
}
|
||||
|
||||
// then we expand the viewport evenly in all directions just to have an extra
|
||||
// safety zone. this also clamps it to page bounds.
|
||||
predictedViewport = expandByDangerZone(predictedViewport, DANGER_ZONE_MULTIPLIER, DANGER_ZONE_MULTIPLIER, metrics);
|
||||
return !displayPort.contains(predictedViewport);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DynamicResolutionStrategy";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This class implements the variation where we use the draw time to predict where we will be when
|
||||
* a draw completes, and draw that instead of where we are now. In this variation, when our panning
|
||||
* speed drops below a certain threshold, we draw 9 viewports' worth of content so that the user can
|
||||
* pan in any direction without encountering checkerboarding.
|
||||
* Once the user is panning, we modify the displayport to encompass an area range of where we think
|
||||
* the user will be when the draw completes. This heuristic relies on both the estimated draw time
|
||||
* the panning velocity; unexpected changes in either of these values will cause the heuristic to
|
||||
* fail and show checkerboard.
|
||||
*/
|
||||
private static class PredictionBiasStrategy extends DisplayPortStrategy {
|
||||
private static float VELOCITY_THRESHOLD;
|
||||
|
||||
private int mPixelArea; // area of the viewport, used in draw time calculations
|
||||
private int mMinFramesToDraw; // minimum number of frames we take to draw
|
||||
private int mMaxFramesToDraw; // maximum number of frames we take to draw
|
||||
|
||||
PredictionBiasStrategy(Map<String, Integer> prefs) {
|
||||
VELOCITY_THRESHOLD = GeckoAppShell.getDpi() * getFloatPref(prefs, PREF_DISPLAYPORT_PB_VELOCITY_THRESHOLD, 16);
|
||||
resetPageState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DisplayPortMetrics calculate(ImmutableViewportMetrics metrics, PointF velocity) {
|
||||
float width = metrics.getWidth();
|
||||
float height = metrics.getHeight();
|
||||
mPixelArea = (int)(width * height);
|
||||
|
||||
if (velocity.length() < VELOCITY_THRESHOLD) {
|
||||
// if we're going slow, expand the displayport to 9x viewport size
|
||||
RectF margins = new RectF(width, height, width, height);
|
||||
return getPageClampedDisplayPortMetrics(margins, metrics.zoomFactor, metrics);
|
||||
}
|
||||
|
||||
// figure out how far we expect to be
|
||||
float minDx = velocity.x * mMinFramesToDraw;
|
||||
float minDy = velocity.y * mMinFramesToDraw;
|
||||
float maxDx = velocity.x * mMaxFramesToDraw;
|
||||
float maxDy = velocity.y * mMaxFramesToDraw;
|
||||
|
||||
// figure out how many pixels we will be drawing when we draw the above-calculated range.
|
||||
// this will be larger than the viewport area.
|
||||
float pixelsToDraw = (width + Math.abs(maxDx - minDx)) * (height + Math.abs(maxDy - minDy));
|
||||
// adjust how far we will get because of the time spent drawing all these extra pixels. this
|
||||
// will again increase the number of pixels drawn so really we could keep iterating this over
|
||||
// and over, but once seems enough for now.
|
||||
maxDx = maxDx * pixelsToDraw / mPixelArea;
|
||||
maxDy = maxDy * pixelsToDraw / mPixelArea;
|
||||
|
||||
// and finally generate the displayport. the min/max stuff takes care of
|
||||
// negative velocities as well as positive.
|
||||
RectF margins = new RectF(
|
||||
-Math.min(minDx, maxDx),
|
||||
-Math.min(minDy, maxDy),
|
||||
Math.max(minDx, maxDx),
|
||||
Math.max(minDy, maxDy));
|
||||
return getPageClampedDisplayPortMetrics(margins, metrics.zoomFactor, metrics);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean aboutToCheckerboard(ImmutableViewportMetrics metrics, PointF velocity, DisplayPortMetrics displayPort) {
|
||||
// the code below is the same as in calculate() but is awkward to refactor since it has multiple outputs.
|
||||
// refer to the comments in calculate() to understand what this is doing.
|
||||
float minDx = velocity.x * mMinFramesToDraw;
|
||||
float minDy = velocity.y * mMinFramesToDraw;
|
||||
float maxDx = velocity.x * mMaxFramesToDraw;
|
||||
float maxDy = velocity.y * mMaxFramesToDraw;
|
||||
float pixelsToDraw = (metrics.getWidth() + Math.abs(maxDx - minDx)) * (metrics.getHeight() + Math.abs(maxDy - minDy));
|
||||
maxDx = maxDx * pixelsToDraw / mPixelArea;
|
||||
maxDy = maxDy * pixelsToDraw / mPixelArea;
|
||||
|
||||
// now that we have an idea of how far we will be when the draw completes, take the farthest
|
||||
// end of that range and see if it falls outside the displayport bounds. if it does, allow
|
||||
// the draw to go through
|
||||
RectF predictedViewport = metrics.getViewport();
|
||||
predictedViewport.left += maxDx;
|
||||
predictedViewport.top += maxDy;
|
||||
predictedViewport.right += maxDx;
|
||||
predictedViewport.bottom += maxDy;
|
||||
|
||||
predictedViewport = clampToPageBounds(predictedViewport, metrics);
|
||||
return !displayPort.contains(predictedViewport);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean drawTimeUpdate(long millis, int pixels) {
|
||||
// calculate the number of frames it took to draw a viewport-sized area
|
||||
float normalizedTime = (float)mPixelArea * millis / pixels;
|
||||
int normalizedFrames = (int) Math.ceil(normalizedTime * 60f / 1000f);
|
||||
// broaden our range on how long it takes to draw if the draw falls outside
|
||||
// the range. this allows it to grow gradually. this heuristic may need to
|
||||
// be tweaked into more of a floating window average or something.
|
||||
if (normalizedFrames <= mMinFramesToDraw) {
|
||||
mMinFramesToDraw--;
|
||||
} else if (normalizedFrames > mMaxFramesToDraw) {
|
||||
mMaxFramesToDraw++;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
Log.d(LOGTAG, "Widened draw range to [" + mMinFramesToDraw + ", " + mMaxFramesToDraw + "]");
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetPageState() {
|
||||
mMinFramesToDraw = 0;
|
||||
mMaxFramesToDraw = 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "PredictionBiasStrategy threshold=" + VELOCITY_THRESHOLD;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,78 +0,0 @@
|
|||
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.gecko.gfx;
|
||||
|
||||
import org.mozilla.gecko.annotation.WrapForJNI;
|
||||
import org.mozilla.gecko.util.FloatUtils;
|
||||
|
||||
import android.graphics.RectF;
|
||||
|
||||
/*
|
||||
* This class keeps track of the area we request Gecko to paint, as well
|
||||
* as the resolution of the paint. The area may be different from the visible
|
||||
* area of the page, and the resolution may be different from the resolution
|
||||
* used in the compositor to render the page. This is so that we can ask Gecko
|
||||
* to paint a much larger area without using extra memory, and then render some
|
||||
* subsection of that with compositor scaling.
|
||||
*/
|
||||
public final class DisplayPortMetrics {
|
||||
@WrapForJNI(calledFrom = "gecko")
|
||||
public final float resolution;
|
||||
@WrapForJNI(calledFrom = "gecko")
|
||||
private final RectF mPosition;
|
||||
|
||||
public DisplayPortMetrics() {
|
||||
this(0, 0, 0, 0, 1);
|
||||
}
|
||||
|
||||
@WrapForJNI(calledFrom = "gecko")
|
||||
public DisplayPortMetrics(float left, float top, float right, float bottom, float resolution) {
|
||||
this.resolution = resolution;
|
||||
mPosition = new RectF(left, top, right, bottom);
|
||||
}
|
||||
|
||||
public float getLeft() {
|
||||
return mPosition.left;
|
||||
}
|
||||
|
||||
public float getTop() {
|
||||
return mPosition.top;
|
||||
}
|
||||
|
||||
public float getRight() {
|
||||
return mPosition.right;
|
||||
}
|
||||
|
||||
public float getBottom() {
|
||||
return mPosition.bottom;
|
||||
}
|
||||
|
||||
public boolean contains(RectF rect) {
|
||||
return mPosition.contains(rect);
|
||||
}
|
||||
|
||||
public boolean fuzzyEquals(DisplayPortMetrics metrics) {
|
||||
return RectUtils.fuzzyEquals(mPosition, metrics.mPosition)
|
||||
&& FloatUtils.fuzzyEquals(resolution, metrics.resolution);
|
||||
}
|
||||
|
||||
public String toJSON() {
|
||||
StringBuilder sb = new StringBuilder(256);
|
||||
sb.append("{ \"left\": ").append(mPosition.left)
|
||||
.append(", \"top\": ").append(mPosition.top)
|
||||
.append(", \"right\": ").append(mPosition.right)
|
||||
.append(", \"bottom\": ").append(mPosition.bottom)
|
||||
.append(", \"resolution\": ").append(resolution)
|
||||
.append('}');
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DisplayPortMetrics v=(" + mPosition.left + "," + mPosition.top + "," + mPosition.right + ","
|
||||
+ mPosition.bottom + ") z=" + resolution;
|
||||
}
|
||||
}
|
|
@ -1,94 +0,0 @@
|
|||
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.gecko.gfx;
|
||||
|
||||
import android.os.SystemClock;
|
||||
|
||||
/**
|
||||
* A custom-built data structure to assist with measuring draw times.
|
||||
*
|
||||
* This class maintains a fixed-size circular buffer of DisplayPortMetrics
|
||||
* objects and associated timestamps. It provides only three operations, which
|
||||
* is all we require for our purposes of measuring draw times. Note
|
||||
* in particular that the class is designed so that even though it is
|
||||
* accessed from multiple threads, it does not require synchronization;
|
||||
* any concurrency errors that result from this are handled gracefully.
|
||||
*
|
||||
* Assuming an unrolled buffer so that mTail is greater than mHead, the data
|
||||
* stored in the buffer at entries [mHead, mTail) will never be modified, and
|
||||
* so are "safe" to read. If this reading is done on the same thread that
|
||||
* owns mHead, then reading the range [mHead, mTail) is guaranteed to be safe
|
||||
* since the range itself will not shrink.
|
||||
*/
|
||||
final class DrawTimingQueue {
|
||||
private static final String LOGTAG = "GeckoDrawTimingQueue";
|
||||
private static final int BUFFER_SIZE = 16;
|
||||
|
||||
private final DisplayPortMetrics[] mMetrics;
|
||||
private final long[] mTimestamps;
|
||||
|
||||
private int mHead;
|
||||
private int mTail;
|
||||
|
||||
DrawTimingQueue() {
|
||||
mMetrics = new DisplayPortMetrics[BUFFER_SIZE];
|
||||
mTimestamps = new long[BUFFER_SIZE];
|
||||
mHead = BUFFER_SIZE - 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new entry to the tail of the queue. If the buffer is full,
|
||||
* do nothing. This must only be called from the Java UI thread.
|
||||
*/
|
||||
boolean add(DisplayPortMetrics metrics) {
|
||||
if (mHead == mTail) {
|
||||
return false;
|
||||
}
|
||||
mMetrics[mTail] = metrics;
|
||||
mTimestamps[mTail] = SystemClock.uptimeMillis();
|
||||
mTail = (mTail + 1) % BUFFER_SIZE;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the timestamp associated with the given metrics, AND remove
|
||||
* all metrics objects from the start of the queue up to and including
|
||||
* the one provided. Note that because of draw coalescing, the metrics
|
||||
* object passed in here may not be the one at the head of the queue,
|
||||
* and so we must iterate our way through the list to find it.
|
||||
* This must only be called from the compositor thread.
|
||||
*/
|
||||
long findTimeFor(DisplayPortMetrics metrics) {
|
||||
// keep a copy of the tail pointer so that we ignore new items
|
||||
// added to the queue while we are searching. this is fine because
|
||||
// the one we are looking for will either have been added already
|
||||
// or will not be in the queue at all.
|
||||
int tail = mTail;
|
||||
// walk through the "safe" range from mHead to tail; these entries
|
||||
// will not be modified unless we change mHead.
|
||||
int i = (mHead + 1) % BUFFER_SIZE;
|
||||
while (i != tail) {
|
||||
if (mMetrics[i].fuzzyEquals(metrics)) {
|
||||
// found it, copy out the timestamp to a local var BEFORE
|
||||
// changing mHead or add could clobber the timestamp.
|
||||
long timestamp = mTimestamps[i];
|
||||
mHead = i;
|
||||
return timestamp;
|
||||
}
|
||||
i = (i + 1) % BUFFER_SIZE;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the buffer to empty.
|
||||
* This must only be called from the compositor thread.
|
||||
*/
|
||||
void reset() {
|
||||
// we can only modify mHead on this thread.
|
||||
mHead = (mTail + BUFFER_SIZE - 1) % BUFFER_SIZE;
|
||||
}
|
||||
}
|
|
@ -39,20 +39,6 @@ class GeckoLayerClient implements LayerView.Listener, PanZoomTarget
|
|||
private final Context mContext;
|
||||
private IntSize mScreenSize;
|
||||
private IntSize mWindowSize;
|
||||
private DisplayPortMetrics mDisplayPort;
|
||||
|
||||
private boolean mRecordDrawTimes;
|
||||
private final DrawTimingQueue mDrawTimingQueue;
|
||||
|
||||
/* The Gecko viewport as per the UI thread. Must be touched only on the UI thread.
|
||||
* If any events being sent to Gecko that are relative to the Gecko viewport position,
|
||||
* they must (a) be relative to this viewport, and (b) be sent on the UI thread to
|
||||
* avoid races. As long as these two conditions are satisfied, and the events being
|
||||
* sent to Gecko are processed in FIFO order, the events will properly be relative
|
||||
* to the Gecko viewport position. Note that if Gecko updates its viewport independently,
|
||||
* we get notified synchronously and also update this on the UI thread.
|
||||
*/
|
||||
private ImmutableViewportMetrics mGeckoViewport;
|
||||
|
||||
/*
|
||||
* The viewport metrics being used to draw the current frame. This is only
|
||||
|
@ -65,12 +51,6 @@ class GeckoLayerClient implements LayerView.Listener, PanZoomTarget
|
|||
/* Used as temporaries by syncViewportInfo */
|
||||
private final ViewTransform mCurrentViewTransform;
|
||||
|
||||
/* Used as the return value of progressiveUpdateCallback */
|
||||
private final ProgressiveUpdateData mProgressiveUpdateData;
|
||||
private DisplayPortMetrics mProgressiveUpdateDisplayPort;
|
||||
private boolean mLastProgressiveUpdateWasLowPrecision;
|
||||
private boolean mProgressiveUpdateWasInDanger;
|
||||
|
||||
private boolean mForceRedraw;
|
||||
|
||||
/* The current viewport metrics.
|
||||
|
@ -113,12 +93,7 @@ class GeckoLayerClient implements LayerView.Listener, PanZoomTarget
|
|||
mContext = context;
|
||||
mScreenSize = new IntSize(0, 0);
|
||||
mWindowSize = new IntSize(0, 0);
|
||||
mDisplayPort = new DisplayPortMetrics();
|
||||
mRecordDrawTimes = true;
|
||||
mDrawTimingQueue = new DrawTimingQueue();
|
||||
mCurrentViewTransform = new ViewTransform(0, 0, 1);
|
||||
mProgressiveUpdateData = new ProgressiveUpdateData();
|
||||
mProgressiveUpdateDisplayPort = new DisplayPortMetrics();
|
||||
|
||||
mForceRedraw = true;
|
||||
DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
|
||||
|
@ -157,8 +132,6 @@ class GeckoLayerClient implements LayerView.Listener, PanZoomTarget
|
|||
|
||||
sendResizeEventIfNecessary(true, null);
|
||||
|
||||
DisplayPortCalculator.initPrefs();
|
||||
|
||||
// Gecko being ready is one of the two conditions (along with having an available
|
||||
// surface) that cause us to create the compositor. So here, now that we know gecko
|
||||
// is ready, call updateCompositor() to see if we can actually do the creation.
|
||||
|
@ -294,7 +267,7 @@ class GeckoLayerClient implements LayerView.Listener, PanZoomTarget
|
|||
// Page size is owned by the layer client, so no need to notify it of
|
||||
// this change.
|
||||
|
||||
post(new Runnable() {
|
||||
mView.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mView.requestRender();
|
||||
|
@ -342,17 +315,10 @@ class GeckoLayerClient implements LayerView.Listener, PanZoomTarget
|
|||
.setZoomFactor(zoom)
|
||||
.setPageRect(pageRect, cssPageRect);
|
||||
// Since we have switched to displaying a different document, we need to update any
|
||||
// viewport-related state we have lying around. This includes mGeckoViewport and
|
||||
// mViewportMetrics. Usually this information is updated via handleViewportMessage
|
||||
// viewport-related state we have lying around (i.e. mViewportMetrics).
|
||||
// Usually this information is updated via handleViewportMessage
|
||||
// while we remain on the same document.
|
||||
post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mGeckoViewport = newMetrics;
|
||||
}
|
||||
});
|
||||
|
||||
setViewportMetrics(newMetrics);
|
||||
setViewportMetrics(newMetrics, true);
|
||||
|
||||
// Indicate that the document is about to be composited so the
|
||||
// LayerView background can be removed.
|
||||
|
@ -360,8 +326,6 @@ class GeckoLayerClient implements LayerView.Listener, PanZoomTarget
|
|||
mView.setPaintState(LayerView.PAINT_BEFORE_FIRST);
|
||||
}
|
||||
}
|
||||
DisplayPortCalculator.resetPageState();
|
||||
mDrawTimingQueue.reset();
|
||||
|
||||
mContentDocumentIsDisplayed = true;
|
||||
}
|
||||
|
@ -406,19 +370,6 @@ class GeckoLayerClient implements LayerView.Listener, PanZoomTarget
|
|||
}
|
||||
mToolbarAnimator.populateViewTransform(mCurrentViewTransform, mFrameMetrics);
|
||||
|
||||
if (layersUpdated && mRecordDrawTimes) {
|
||||
// If we got a layers update, that means a draw finished. Check to see if the area drawn matches
|
||||
// one of our requested displayports; if it does calculate the draw time and notify the
|
||||
// DisplayPortCalculator
|
||||
DisplayPortMetrics drawn = new DisplayPortMetrics(x, y, x + width, y + height, resolution);
|
||||
long time = mDrawTimingQueue.findTimeFor(drawn);
|
||||
if (time >= 0) {
|
||||
long now = SystemClock.uptimeMillis();
|
||||
time = now - time;
|
||||
mRecordDrawTimes = DisplayPortCalculator.drawTimeUpdate(time, width * height);
|
||||
}
|
||||
}
|
||||
|
||||
if (layersUpdated) {
|
||||
for (DrawListener listener : mDrawListeners) {
|
||||
listener.drawFinished();
|
||||
|
@ -678,7 +629,7 @@ class GeckoLayerClient implements LayerView.Listener, PanZoomTarget
|
|||
}
|
||||
}
|
||||
|
||||
private void geometryChanged(DisplayPortMetrics displayPort) {
|
||||
private void geometryChanged() {
|
||||
/* Let Gecko know if the screensize has changed */
|
||||
sendResizeEventIfNecessary(false, null);
|
||||
}
|
||||
|
@ -690,44 +641,10 @@ class GeckoLayerClient implements LayerView.Listener, PanZoomTarget
|
|||
setViewportSize(viewportSize.width, viewportSize.height, null);
|
||||
}
|
||||
|
||||
/** Implementation of PanZoomTarget */
|
||||
@Override
|
||||
public ImmutableViewportMetrics getViewportMetrics() {
|
||||
ImmutableViewportMetrics getViewportMetrics() {
|
||||
return mViewportMetrics;
|
||||
}
|
||||
|
||||
/** Implementation of PanZoomTarget */
|
||||
@Override
|
||||
public FullScreenState getFullScreenState() {
|
||||
return mView.getFullScreenState();
|
||||
}
|
||||
|
||||
/** Implementation of PanZoomTarget */
|
||||
@Override
|
||||
public PointF getVisibleEndOfLayerView() {
|
||||
return mToolbarAnimator.getVisibleEndOfLayerView();
|
||||
}
|
||||
|
||||
/** Implementation of PanZoomTarget */
|
||||
@Override
|
||||
public void setAnimationTarget(ImmutableViewportMetrics metrics) {
|
||||
if (mGeckoIsReady) {
|
||||
// We know what the final viewport of the animation is going to be, so
|
||||
// immediately request a draw of that area by setting the display port
|
||||
// accordingly. This way we should have the content pre-rendered by the
|
||||
// time the animation is done.
|
||||
DisplayPortMetrics displayPort = DisplayPortCalculator.calculate(metrics, null);
|
||||
}
|
||||
}
|
||||
|
||||
/** Implementation of PanZoomTarget
|
||||
* You must hold the monitor while calling this.
|
||||
*/
|
||||
@Override
|
||||
public void setViewportMetrics(ImmutableViewportMetrics metrics) {
|
||||
setViewportMetrics(metrics, true);
|
||||
}
|
||||
|
||||
/*
|
||||
* You must hold the monitor while calling this.
|
||||
*/
|
||||
|
@ -751,7 +668,7 @@ class GeckoLayerClient implements LayerView.Listener, PanZoomTarget
|
|||
|
||||
mView.requestRender();
|
||||
if (notifyGecko && mGeckoIsReady) {
|
||||
geometryChanged(null);
|
||||
geometryChanged();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -768,70 +685,24 @@ class GeckoLayerClient implements LayerView.Listener, PanZoomTarget
|
|||
viewportMetricsChanged(notifyGecko);
|
||||
}
|
||||
|
||||
/** Implementation of PanZoomTarget
|
||||
* Scroll the viewport by a certain amount. This will take viewport margins
|
||||
* and margin animation into account. If margins are currently animating,
|
||||
* this will just go ahead and modify the viewport origin, otherwise the
|
||||
* delta will be applied to the margins and the remainder will be applied to
|
||||
* the viewport origin.
|
||||
*
|
||||
* You must hold the monitor while calling this.
|
||||
*/
|
||||
@Override
|
||||
public void scrollBy(float dx, float dy) {
|
||||
// Set mViewportMetrics manually so the margin changes take.
|
||||
mViewportMetrics = mViewportMetrics.offsetViewportBy(dx, dy);
|
||||
viewportMetricsChanged(true);
|
||||
}
|
||||
|
||||
/** Implementation of PanZoomTarget */
|
||||
@Override
|
||||
public void panZoomStopped() {
|
||||
mToolbarAnimator.onPanZoomStopped();
|
||||
}
|
||||
|
||||
/** Implementation of PanZoomTarget */
|
||||
@Override
|
||||
public void forceRedraw(DisplayPortMetrics displayPort) {
|
||||
mForceRedraw = true;
|
||||
if (mGeckoIsReady) {
|
||||
geometryChanged(displayPort);
|
||||
}
|
||||
}
|
||||
|
||||
/** Implementation of PanZoomTarget */
|
||||
@Override
|
||||
public boolean post(Runnable action) {
|
||||
return mView.post(action);
|
||||
}
|
||||
|
||||
/** Implementation of PanZoomTarget */
|
||||
@Override
|
||||
public void postRenderTask(RenderTask task) {
|
||||
mView.postRenderTask(task);
|
||||
}
|
||||
|
||||
/** Implementation of PanZoomTarget */
|
||||
@Override
|
||||
public void removeRenderTask(RenderTask task) {
|
||||
mView.removeRenderTask(task);
|
||||
}
|
||||
|
||||
/** Implementation of PanZoomTarget */
|
||||
@Override
|
||||
public Object getLock() {
|
||||
Object getLock() {
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Implementation of PanZoomTarget
|
||||
/**
|
||||
* Converts a point from layer view coordinates to layer coordinates. In other words, given a
|
||||
* point measured in pixels from the top left corner of the layer view, returns the point in
|
||||
* pixels measured from the last scroll position we sent to Gecko, in CSS pixels. Assuming the
|
||||
* events being sent to Gecko are processed in FIFO order, this calculation should always be
|
||||
* correct.
|
||||
*/
|
||||
@Override
|
||||
public PointF convertViewPointToLayerPoint(PointF viewPoint) {
|
||||
PointF convertViewPointToLayerPoint(PointF viewPoint) {
|
||||
if (!mGeckoIsReady) {
|
||||
return null;
|
||||
}
|
||||
|
@ -855,8 +726,7 @@ class GeckoLayerClient implements LayerView.Listener, PanZoomTarget
|
|||
return layerPoint;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Matrix getMatrixForLayerRectToViewRect() {
|
||||
Matrix getMatrixForLayerRectToViewRect() {
|
||||
if (!mGeckoIsReady) {
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -160,11 +160,7 @@ public class LayerView extends FrameLayout {
|
|||
mPaintState = PAINT_START;
|
||||
mFullScreenState = FullScreenState.NONE;
|
||||
|
||||
if (Versions.feature14Plus) {
|
||||
mOverscroll = new OverscrollEdgeEffect(this);
|
||||
} else {
|
||||
mOverscroll = null;
|
||||
}
|
||||
mOverscroll = new OverscrollEdgeEffect(this);
|
||||
}
|
||||
|
||||
public LayerView(Context context) {
|
||||
|
@ -676,10 +672,6 @@ public class LayerView extends FrameLayout {
|
|||
return mFullScreenState != FullScreenState.NONE;
|
||||
}
|
||||
|
||||
public FullScreenState getFullScreenState() {
|
||||
return mFullScreenState;
|
||||
}
|
||||
|
||||
public void setMaxTranslation(float aMaxTranslation) {
|
||||
mToolbarAnimator.setMaxTranslation(aMaxTranslation);
|
||||
}
|
||||
|
|
|
@ -9,23 +9,7 @@ import android.graphics.Matrix;
|
|||
import android.graphics.PointF;
|
||||
|
||||
public interface PanZoomTarget {
|
||||
public ImmutableViewportMetrics getViewportMetrics();
|
||||
public FullScreenState getFullScreenState();
|
||||
public PointF getVisibleEndOfLayerView();
|
||||
|
||||
public void setAnimationTarget(ImmutableViewportMetrics viewport);
|
||||
public void setViewportMetrics(ImmutableViewportMetrics viewport);
|
||||
public void scrollBy(float dx, float dy);
|
||||
public void panZoomStopped();
|
||||
/** This triggers an (asynchronous) viewport update/redraw. */
|
||||
public void forceRedraw(DisplayPortMetrics displayPort);
|
||||
|
||||
public boolean isGeckoReady();
|
||||
public boolean post(Runnable action);
|
||||
public void postRenderTask(RenderTask task);
|
||||
public void removeRenderTask(RenderTask task);
|
||||
public Object getLock();
|
||||
public PointF convertViewPointToLayerPoint(PointF viewPoint);
|
||||
public Matrix getMatrixForLayerRectToViewRect();
|
||||
public void setScrollingRootContent(boolean isRootContent);
|
||||
}
|
||||
|
|
|
@ -1,322 +0,0 @@
|
|||
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.gecko.gfx;
|
||||
|
||||
import org.json.JSONException;
|
||||
|
||||
import android.graphics.PointF;
|
||||
import android.util.Log;
|
||||
import android.view.MotionEvent;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.ListIterator;
|
||||
import java.util.Stack;
|
||||
|
||||
/**
|
||||
* A less buggy, and smoother, replacement for the built-in Android ScaleGestureDetector.
|
||||
*
|
||||
* This gesture detector is more reliable than the built-in ScaleGestureDetector because:
|
||||
*
|
||||
* - It doesn't assume that pointer IDs are numbered 0 and 1.
|
||||
*
|
||||
* - It doesn't attempt to correct for "slop" when resting one's hand on the device. On some
|
||||
* devices (e.g. the Droid X) this can cause the ScaleGestureDetector to lose track of how many
|
||||
* pointers are down, with disastrous results (bug 706684).
|
||||
*
|
||||
* - Cancelling a zoom into a pan is handled correctly.
|
||||
*
|
||||
* - Starting with three or more fingers down, releasing fingers so that only two are down, and
|
||||
* then performing a scale gesture is handled correctly.
|
||||
*
|
||||
* - It doesn't take pressure into account, which results in smoother scaling.
|
||||
*/
|
||||
class SimpleScaleGestureDetector {
|
||||
private static final String LOGTAG = "GeckoSimpleScaleGestureDetector";
|
||||
|
||||
private final SimpleScaleGestureListener mListener;
|
||||
private long mLastEventTime;
|
||||
private boolean mScaleResult;
|
||||
|
||||
/* Information about all pointers that are down. */
|
||||
private final LinkedList<PointerInfo> mPointerInfo;
|
||||
|
||||
/** Creates a new gesture detector with the given listener. */
|
||||
SimpleScaleGestureDetector(SimpleScaleGestureListener listener) {
|
||||
mListener = listener;
|
||||
mPointerInfo = new LinkedList<PointerInfo>();
|
||||
}
|
||||
|
||||
/** Forward touch events to this function. */
|
||||
public void onTouchEvent(MotionEvent event) {
|
||||
switch (event.getAction() & MotionEvent.ACTION_MASK) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
// If we get ACTION_DOWN while still tracking any pointers,
|
||||
// something is wrong. Cancel the current gesture and start over.
|
||||
if (getPointersDown() > 0)
|
||||
onTouchEnd(event);
|
||||
onTouchStart(event);
|
||||
break;
|
||||
case MotionEvent.ACTION_POINTER_DOWN:
|
||||
onTouchStart(event);
|
||||
break;
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
onTouchMove(event);
|
||||
break;
|
||||
case MotionEvent.ACTION_POINTER_UP:
|
||||
case MotionEvent.ACTION_UP:
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
onTouchEnd(event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private int getPointersDown() {
|
||||
return mPointerInfo.size();
|
||||
}
|
||||
|
||||
private int getActionIndex(MotionEvent event) {
|
||||
return (event.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK)
|
||||
>> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
|
||||
}
|
||||
|
||||
private void onTouchStart(MotionEvent event) {
|
||||
mLastEventTime = event.getEventTime();
|
||||
mPointerInfo.addFirst(PointerInfo.create(event, getActionIndex(event)));
|
||||
if (getPointersDown() == 2) {
|
||||
sendScaleGesture(EventType.BEGIN);
|
||||
}
|
||||
}
|
||||
|
||||
private void onTouchMove(MotionEvent event) {
|
||||
mLastEventTime = event.getEventTime();
|
||||
for (int i = 0; i < event.getPointerCount(); i++) {
|
||||
PointerInfo pointerInfo = pointerInfoForEventIndex(event, i);
|
||||
if (pointerInfo != null) {
|
||||
pointerInfo.populate(event, i);
|
||||
}
|
||||
}
|
||||
|
||||
if (getPointersDown() == 2) {
|
||||
sendScaleGesture(EventType.CONTINUE);
|
||||
}
|
||||
}
|
||||
|
||||
private void onTouchEnd(MotionEvent event) {
|
||||
mLastEventTime = event.getEventTime();
|
||||
|
||||
int action = event.getAction() & MotionEvent.ACTION_MASK;
|
||||
boolean isCancel = (action == MotionEvent.ACTION_CANCEL ||
|
||||
action == MotionEvent.ACTION_DOWN);
|
||||
|
||||
int id = event.getPointerId(getActionIndex(event));
|
||||
ListIterator<PointerInfo> iterator = mPointerInfo.listIterator();
|
||||
while (iterator.hasNext()) {
|
||||
PointerInfo pointerInfo = iterator.next();
|
||||
if (!(isCancel || pointerInfo.getId() == id)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// One of the pointers we were tracking was lifted. Remove its info object from the
|
||||
// list, recycle it to avoid GC pauses, and send an onScaleEnd() notification if this
|
||||
// ended the gesture.
|
||||
iterator.remove();
|
||||
pointerInfo.recycle();
|
||||
if (getPointersDown() == 1) {
|
||||
sendScaleGesture(EventType.END);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the X coordinate of the focus location (the midpoint of the two fingers). If only
|
||||
* one finger is down, returns the location of that finger.
|
||||
*/
|
||||
public float getFocusX() {
|
||||
switch (getPointersDown()) {
|
||||
case 1:
|
||||
return mPointerInfo.getFirst().getCurrent().x;
|
||||
case 2:
|
||||
PointerInfo pointerA = mPointerInfo.getFirst(), pointerB = mPointerInfo.getLast();
|
||||
return (pointerA.getCurrent().x + pointerB.getCurrent().x) / 2.0f;
|
||||
}
|
||||
|
||||
Log.e(LOGTAG, "No gesture taking place in getFocusX()!");
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Y coordinate of the focus location (the midpoint of the two fingers). If only
|
||||
* one finger is down, returns the location of that finger.
|
||||
*/
|
||||
public float getFocusY() {
|
||||
switch (getPointersDown()) {
|
||||
case 1:
|
||||
return mPointerInfo.getFirst().getCurrent().y;
|
||||
case 2:
|
||||
PointerInfo pointerA = mPointerInfo.getFirst(), pointerB = mPointerInfo.getLast();
|
||||
return (pointerA.getCurrent().y + pointerB.getCurrent().y) / 2.0f;
|
||||
}
|
||||
|
||||
Log.e(LOGTAG, "No gesture taking place in getFocusY()!");
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
/** Returns the most recent distance between the two pointers. */
|
||||
public float getCurrentSpan() {
|
||||
if (getPointersDown() != 2) {
|
||||
Log.e(LOGTAG, "No gesture taking place in getCurrentSpan()!");
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
PointerInfo pointerA = mPointerInfo.getFirst(), pointerB = mPointerInfo.getLast();
|
||||
return PointUtils.distance(pointerA.getCurrent(), pointerB.getCurrent());
|
||||
}
|
||||
|
||||
/** Returns the second most recent distance between the two pointers. */
|
||||
public float getPreviousSpan() {
|
||||
if (getPointersDown() != 2) {
|
||||
Log.e(LOGTAG, "No gesture taking place in getPreviousSpan()!");
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
PointerInfo pointerA = mPointerInfo.getFirst(), pointerB = mPointerInfo.getLast();
|
||||
PointF a = pointerA.getPrevious(), b = pointerB.getPrevious();
|
||||
if (a == null || b == null) {
|
||||
a = pointerA.getCurrent();
|
||||
b = pointerB.getCurrent();
|
||||
}
|
||||
|
||||
return PointUtils.distance(a, b);
|
||||
}
|
||||
|
||||
/** Returns the time of the last event related to the gesture. */
|
||||
public long getEventTime() {
|
||||
return mLastEventTime;
|
||||
}
|
||||
|
||||
/** Returns true if the scale gesture is in progress and false otherwise. */
|
||||
public boolean isInProgress() {
|
||||
return getPointersDown() == 2;
|
||||
}
|
||||
|
||||
/* Sends the requested scale gesture notification to the listener. */
|
||||
private void sendScaleGesture(EventType eventType) {
|
||||
switch (eventType) {
|
||||
case BEGIN:
|
||||
mScaleResult = mListener.onScaleBegin(this);
|
||||
break;
|
||||
case CONTINUE:
|
||||
if (mScaleResult) {
|
||||
mListener.onScale(this);
|
||||
}
|
||||
break;
|
||||
case END:
|
||||
if (mScaleResult) {
|
||||
mListener.onScaleEnd(this);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the pointer info corresponding to the given pointer index, or null if the pointer
|
||||
* isn't one that's being tracked.
|
||||
*/
|
||||
private PointerInfo pointerInfoForEventIndex(MotionEvent event, int index) {
|
||||
int id = event.getPointerId(index);
|
||||
for (PointerInfo pointerInfo : mPointerInfo) {
|
||||
if (pointerInfo.getId() == id) {
|
||||
return pointerInfo;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private enum EventType {
|
||||
BEGIN,
|
||||
CONTINUE,
|
||||
END,
|
||||
}
|
||||
|
||||
/* Encapsulates information about one of the two fingers involved in the gesture. */
|
||||
private static class PointerInfo {
|
||||
/* A free list that recycles pointer info objects, to reduce GC pauses. */
|
||||
private static Stack<PointerInfo> sPointerInfoFreeList;
|
||||
|
||||
private int mId;
|
||||
private PointF mCurrent, mPrevious;
|
||||
|
||||
private PointerInfo() {
|
||||
// External users should use create() instead.
|
||||
}
|
||||
|
||||
/* Creates or recycles a new PointerInfo instance from an event and a pointer index. */
|
||||
public static PointerInfo create(MotionEvent event, int index) {
|
||||
if (sPointerInfoFreeList == null) {
|
||||
sPointerInfoFreeList = new Stack<PointerInfo>();
|
||||
}
|
||||
|
||||
PointerInfo pointerInfo;
|
||||
if (sPointerInfoFreeList.empty()) {
|
||||
pointerInfo = new PointerInfo();
|
||||
} else {
|
||||
pointerInfo = sPointerInfoFreeList.pop();
|
||||
}
|
||||
|
||||
pointerInfo.populate(event, index);
|
||||
return pointerInfo;
|
||||
}
|
||||
|
||||
/*
|
||||
* Fills in the fields of this instance from the given motion event and pointer index
|
||||
* within that event.
|
||||
*/
|
||||
public void populate(MotionEvent event, int index) {
|
||||
mId = event.getPointerId(index);
|
||||
mPrevious = mCurrent;
|
||||
mCurrent = new PointF(event.getX(index), event.getY(index));
|
||||
}
|
||||
|
||||
public void recycle() {
|
||||
mId = -1;
|
||||
mPrevious = mCurrent = null;
|
||||
sPointerInfoFreeList.push(this);
|
||||
}
|
||||
|
||||
public int getId() { return mId; }
|
||||
public PointF getCurrent() { return mCurrent; }
|
||||
public PointF getPrevious() { return mPrevious; }
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (mId == -1) {
|
||||
return "(up)";
|
||||
}
|
||||
|
||||
try {
|
||||
String prevString;
|
||||
if (mPrevious == null) {
|
||||
prevString = "n/a";
|
||||
} else {
|
||||
prevString = PointUtils.toJSON(mPrevious).toString();
|
||||
}
|
||||
|
||||
// The current position should always be non-null.
|
||||
String currentString = PointUtils.toJSON(mCurrent).toString();
|
||||
return "id=" + mId + " cur=" + currentString + " prev=" + prevString;
|
||||
} catch (JSONException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static interface SimpleScaleGestureListener {
|
||||
public boolean onScale(SimpleScaleGestureDetector detector);
|
||||
public boolean onScaleBegin(SimpleScaleGestureDetector detector);
|
||||
public void onScaleEnd(SimpleScaleGestureDetector detector);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,141 +0,0 @@
|
|||
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.gecko.gfx;
|
||||
|
||||
import org.mozilla.gecko.GeckoAppShell;
|
||||
import org.mozilla.gecko.EventDispatcher;
|
||||
import org.mozilla.gecko.util.GeckoEventListener;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import android.graphics.PointF;
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
|
||||
class SubdocumentScrollHelper implements GeckoEventListener {
|
||||
private static final String LOGTAG = "GeckoSubdocScroll";
|
||||
|
||||
private static final String MESSAGE_PANNING_OVERRIDE = "Panning:Override";
|
||||
private static final String MESSAGE_CANCEL_OVERRIDE = "Panning:CancelOverride";
|
||||
private static final String MESSAGE_SCROLL = "Gesture:Scroll";
|
||||
private static final String MESSAGE_SCROLL_ACK = "Gesture:ScrollAck";
|
||||
|
||||
private final Handler mUiHandler;
|
||||
private final EventDispatcher mEventDispatcher;
|
||||
|
||||
/* This is the amount of displacement we have accepted but not yet sent to JS; this is
|
||||
* only valid when mOverrideScrollPending is true. */
|
||||
private final PointF mPendingDisplacement;
|
||||
|
||||
/* When this is true, we're sending scroll events to JS to scroll the active subdocument. */
|
||||
private boolean mOverridePanning;
|
||||
|
||||
/* When this is true, we have received an ack for the last scroll event we sent to JS, and
|
||||
* are ready to send the next scroll event. Note we only ever have one scroll event inflight
|
||||
* at a time. */
|
||||
private boolean mOverrideScrollAck;
|
||||
|
||||
/* When this is true, we have a pending scroll that we need to send to JS; we were unable
|
||||
* to send it when it was initially requested because mOverrideScrollAck was not true. */
|
||||
private boolean mOverrideScrollPending;
|
||||
|
||||
/* When this is true, the last scroll event we sent actually did some amount of scrolling on
|
||||
* the subdocument; we use this to decide when we have reached the end of the subdocument. */
|
||||
private boolean mScrollSucceeded;
|
||||
|
||||
SubdocumentScrollHelper(EventDispatcher eventDispatcher) {
|
||||
// mUiHandler will be bound to the UI thread since that's where this constructor runs
|
||||
mUiHandler = new Handler();
|
||||
mPendingDisplacement = new PointF();
|
||||
|
||||
mEventDispatcher = eventDispatcher;
|
||||
mEventDispatcher.registerGeckoThreadListener(this,
|
||||
MESSAGE_PANNING_OVERRIDE,
|
||||
MESSAGE_CANCEL_OVERRIDE,
|
||||
MESSAGE_SCROLL_ACK);
|
||||
}
|
||||
|
||||
void destroy() {
|
||||
mEventDispatcher.unregisterGeckoThreadListener(this,
|
||||
MESSAGE_PANNING_OVERRIDE,
|
||||
MESSAGE_CANCEL_OVERRIDE,
|
||||
MESSAGE_SCROLL_ACK);
|
||||
}
|
||||
|
||||
boolean scrollBy(PointF displacement) {
|
||||
if (! mOverridePanning) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! mOverrideScrollAck) {
|
||||
mOverrideScrollPending = true;
|
||||
mPendingDisplacement.x += displacement.x;
|
||||
mPendingDisplacement.y += displacement.y;
|
||||
return true;
|
||||
}
|
||||
|
||||
JSONObject json = new JSONObject();
|
||||
try {
|
||||
json.put("x", displacement.x);
|
||||
json.put("y", displacement.y);
|
||||
} catch (JSONException e) {
|
||||
Log.e(LOGTAG, "Error forming subwindow scroll message: ", e);
|
||||
}
|
||||
GeckoAppShell.notifyObservers(MESSAGE_SCROLL, json.toString());
|
||||
|
||||
mOverrideScrollAck = false;
|
||||
mOverrideScrollPending = false;
|
||||
// clear the |mPendingDisplacement| after serializing |displacement| to
|
||||
// JSON because they might be the same object
|
||||
mPendingDisplacement.x = 0;
|
||||
mPendingDisplacement.y = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void cancel() {
|
||||
mOverridePanning = false;
|
||||
}
|
||||
|
||||
boolean scrolling() {
|
||||
return mOverridePanning;
|
||||
}
|
||||
|
||||
boolean lastScrollSucceeded() {
|
||||
return mScrollSucceeded;
|
||||
}
|
||||
|
||||
// GeckoEventListener implementation
|
||||
|
||||
@Override
|
||||
public void handleMessage(final String event, final JSONObject message) {
|
||||
// This comes in on the Gecko thread; hand off the handling to the UI thread.
|
||||
mUiHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
if (MESSAGE_PANNING_OVERRIDE.equals(event)) {
|
||||
mOverridePanning = true;
|
||||
mOverrideScrollAck = true;
|
||||
mOverrideScrollPending = false;
|
||||
mScrollSucceeded = true;
|
||||
} else if (MESSAGE_CANCEL_OVERRIDE.equals(event)) {
|
||||
mOverridePanning = false;
|
||||
} else if (MESSAGE_SCROLL_ACK.equals(event)) {
|
||||
mOverrideScrollAck = true;
|
||||
mScrollSucceeded = message.getBoolean("scrolled");
|
||||
if (mOverridePanning && mOverrideScrollPending) {
|
||||
scrollBy(mPendingDisplacement);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(LOGTAG, "Exception handling message", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -63,26 +63,19 @@ public final class Clipboard {
|
|||
public static void setText(final CharSequence text) {
|
||||
ThreadUtils.postToBackgroundThread(new Runnable() {
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
public void run() {
|
||||
// In API Level 11 and above, CLIPBOARD_SERVICE returns android.content.ClipboardManager,
|
||||
// which is a subclass of android.text.ClipboardManager.
|
||||
if (Versions.feature11Plus) {
|
||||
final android.content.ClipboardManager cm = (android.content.ClipboardManager) mContext.getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
final ClipData clip = ClipData.newPlainText("Text", text);
|
||||
try {
|
||||
cm.setPrimaryClip(clip);
|
||||
} catch (NullPointerException e) {
|
||||
// Bug 776223: This is a Samsung clipboard bug. setPrimaryClip() can throw
|
||||
// a NullPointerException if Samsung's /data/clipboard directory is full.
|
||||
// Fortunately, the text is still successfully copied to the clipboard.
|
||||
}
|
||||
return;
|
||||
final android.content.ClipboardManager cm = (android.content.ClipboardManager) mContext.getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
final ClipData clip = ClipData.newPlainText("Text", text);
|
||||
try {
|
||||
cm.setPrimaryClip(clip);
|
||||
} catch (NullPointerException e) {
|
||||
// Bug 776223: This is a Samsung clipboard bug. setPrimaryClip() can throw
|
||||
// a NullPointerException if Samsung's /data/clipboard directory is full.
|
||||
// Fortunately, the text is still successfully copied to the clipboard.
|
||||
}
|
||||
|
||||
// Deprecated.
|
||||
android.text.ClipboardManager cm = (android.text.ClipboardManager) mContext.getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
cm.setText(text);
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -92,14 +85,8 @@ public final class Clipboard {
|
|||
*/
|
||||
@WrapForJNI(calledFrom = "gecko")
|
||||
public static boolean hasText() {
|
||||
if (Versions.feature11Plus) {
|
||||
android.content.ClipboardManager cm = (android.content.ClipboardManager) mContext.getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
return cm.hasPrimaryClip();
|
||||
}
|
||||
|
||||
// Deprecated.
|
||||
android.text.ClipboardManager cm = (android.text.ClipboardManager) mContext.getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
return cm.hasText();
|
||||
android.content.ClipboardManager cm = (android.content.ClipboardManager) mContext.getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
return cm.hasPrimaryClip();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -117,19 +104,12 @@ public final class Clipboard {
|
|||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
static String getClipboardTextImpl() {
|
||||
if (Versions.feature11Plus) {
|
||||
android.content.ClipboardManager cm = (android.content.ClipboardManager) mContext.getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
if (cm.hasPrimaryClip()) {
|
||||
ClipData clip = cm.getPrimaryClip();
|
||||
if (clip != null) {
|
||||
ClipData.Item item = clip.getItemAt(0);
|
||||
return item.coerceToText(mContext).toString();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
android.text.ClipboardManager cm = (android.text.ClipboardManager) mContext.getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
if (cm.hasText()) {
|
||||
return cm.getText().toString();
|
||||
android.content.ClipboardManager cm = (android.content.ClipboardManager) mContext.getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
if (cm.hasPrimaryClip()) {
|
||||
ClipData clip = cm.getPrimaryClip();
|
||||
if (clip != null) {
|
||||
ClipData.Item item = clip.getItemAt(0);
|
||||
return item.coerceToText(mContext).toString();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче